1#![allow(unsafe_code)]
11
12use std::ffi::CString;
13use std::io;
14
15const IFNAMSIZ: usize = 16;
19
20#[derive(Debug, thiserror::Error)]
22pub enum NetIfError {
23 #[error("interface name contains NUL byte")]
25 InvalidName,
26 #[error("interface name too long (max {} bytes)", IFNAMSIZ - 1)]
28 NameTooLong,
29 #[error("if_nametoindex failed for {name:?}: {source}")]
31 NotFound {
32 name: String,
34 #[source]
36 source: io::Error,
37 },
38}
39
40#[cfg(target_os = "linux")]
62pub fn if_nametoindex(name: &str) -> Result<u32, NetIfError> {
63 if name.len() >= IFNAMSIZ {
65 return Err(NetIfError::NameTooLong);
66 }
67
68 let c_name = CString::new(name).map_err(|_| NetIfError::InvalidName)?;
70
71 let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
77 if idx == 0 {
78 let err = io::Error::last_os_error();
80 return Err(NetIfError::NotFound {
81 name: name.to_string(),
82 source: err,
83 });
84 }
85 Ok(idx)
86}
87
88#[cfg(not(target_os = "linux"))]
90pub fn if_nametoindex(_name: &str) -> Result<u32, NetIfError> {
91 Err(NetIfError::NotFound {
92 name: _name.to_string(),
93 source: io::Error::new(io::ErrorKind::Unsupported, "non-Linux platform"),
94 })
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_lo_ifindex() {
103 match if_nametoindex("lo") {
105 Ok(idx) => {
106 assert_eq!(idx, 1, "lo must have ifindex=1");
107 }
108 Err(NetIfError::NotFound { .. }) => {
109 }
111 Err(e) => panic!("unexpected error for lo: {:?}", e),
112 }
113 }
114
115 #[test]
116 fn test_nonexistent_iface() {
117 let result = if_nametoindex("zz_nx99");
119 assert!(matches!(result, Err(NetIfError::NotFound { .. })));
120 }
121
122 #[test]
123 fn test_name_too_long() {
124 let long_name = "a".repeat(IFNAMSIZ);
126 assert_eq!(long_name.len(), IFNAMSIZ);
127 let result = if_nametoindex(&long_name);
128 assert!(matches!(result, Err(NetIfError::NameTooLong)));
129 }
130
131 #[test]
132 fn test_name_with_nul() {
133 let result = if_nametoindex("eth\0");
134 assert!(matches!(result, Err(NetIfError::InvalidName)));
135 }
136
137 #[test]
138 fn test_boundary_name_length() {
139 let max_name = "a".repeat(IFNAMSIZ - 1);
141 let result = if_nametoindex(&max_name);
143 assert!(!matches!(result, Err(NetIfError::NameTooLong)));
144 }
145}