1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use smol_str::SmolStr;
use std::io;
/// Returns the name of the interface by the given index.
///
/// ## Example
///
/// ```rust
/// use getifs::{ifindex_to_name, interfaces};
///
/// let interface = interfaces().unwrap().into_iter().next().unwrap();
/// let name = ifindex_to_name(interface.index()).unwrap();
///
/// assert_eq!(interface.name(), &name);
/// ```
pub fn ifindex_to_name(idx: u32) -> io::Result<SmolStr> {
ifindex_to_name_in(idx)
}
#[cfg(bsd_like)]
fn ifindex_to_name_in(idx: u32) -> io::Result<SmolStr> {
use std::ffi::CStr;
let mut ifname = [0u8; libc::IF_NAMESIZE + 1];
let res = unsafe { libc::if_indextoname(idx as _, ifname.as_mut_ptr() as *mut libc::c_char) };
if res.is_null() {
return Err(io::Error::last_os_error());
}
// Use CStr to handle null-terminated string
let name = unsafe { CStr::from_ptr(ifname.as_ptr() as *const libc::c_char) };
// Convert to string and then to SmolStr
name
.to_str()
.map(SmolStr::new)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
#[cfg(linux_like)]
fn ifindex_to_name_in(idx: u32) -> io::Result<SmolStr> {
use rustix::net::{netdevice::index_to_name_inlined, socket, AddressFamily, SocketType};
let socket_fd = socket(AddressFamily::INET, SocketType::DGRAM, None)?;
// `index_to_name_inlined` (rustix 1.1) returns a stack-allocated
// `InlinedName` — no intermediate `String` on the heap. Interface
// names are bounded by `IF_NAMESIZE` (16), which fits within
// `SmolStr`'s inline capacity (23), so this stays allocation-free.
index_to_name_inlined(socket_fd, idx)
.map(|name| SmolStr::new(name.as_str()))
.map_err(Into::into)
}
/// Returns the name of the interface by the given index.
#[cfg(windows)]
fn ifindex_to_name_in(idx: u32) -> io::Result<SmolStr> {
use std::ffi::CStr;
use windows_sys::Win32::NetworkManagement::{
IpHelper::{ConvertInterfaceIndexToLuid, ConvertInterfaceLuidToAlias},
Ndis::NET_LUID_LH,
};
let mut luid = NET_LUID_LH { Value: 0 };
// Convert index to LUID
let result = unsafe { ConvertInterfaceIndexToLuid(idx, &mut luid) };
if result != 0 {
return Err(io::Error::last_os_error());
}
// Get alias (friendly name)
let mut name_buf = [0u16; 256]; // IF_MAX_STRING_SIZE + 1
let result = unsafe { ConvertInterfaceLuidToAlias(&luid, name_buf.as_mut_ptr(), name_buf.len()) };
if result != 0 {
return Err(io::Error::last_os_error());
}
// Convert to string
match crate::utils::friendly_name(name_buf.as_mut_ptr()) {
Some(name) => Ok(name),
None => {
// Last-ditch fallback via `if_indextoname`. Guard against a null
// return — feeding that straight into `CStr::from_ptr` is UB.
let mut name_buf = [0u8; 256];
// SAFETY: `if_indextoname` writes into `name_buf` (≥ IF_NAMESIZE)
// and returns either a pointer into that buffer or null.
let hname = unsafe {
windows_sys::Win32::NetworkManagement::IpHelper::if_indextoname(idx, name_buf.as_mut_ptr())
};
if hname.is_null() {
return Err(io::Error::last_os_error());
}
// SAFETY: non-null `hname` points into `name_buf`, which is
// alive for the duration of this call and holds a NUL-terminated
// C string produced by `if_indextoname`.
unsafe { Ok(CStr::from_ptr(hname as _).to_string_lossy().into()) }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
// Covers the `Err(...)` arm — a wildly out-of-range index has no
// matching interface on any platform, so `if_indextoname` /
// `ConvertInterfaceIndexToLuid` returns null/non-zero and we
// surface the OS error.
#[test]
fn out_of_range_index_returns_err() {
// 0xFFFE_FFFE is far above any real interface index on any host.
assert!(ifindex_to_name(0xFFFE_FFFE).is_err());
}
// Covers the success arm by round-tripping the first interface
// returned by `interfaces()`. Skipped on DragonFly because of
// vmactions interface churn — see the matching gate on
// `name_to_idx::tests::round_trip_first_interface`.
#[cfg(not(target_os = "dragonfly"))]
#[test]
fn round_trip_first_interface() {
let ift = crate::interfaces().unwrap();
let first = ift.iter().next().unwrap();
let name = ifindex_to_name(first.index()).unwrap();
assert_eq!(first.name(), &name);
}
}