1use std::net::Ipv4Addr;
7
8#[derive(Debug, Clone)]
10pub struct InterfaceInfo {
11 pub name: String,
13 pub ipv4_addrs: Vec<Ipv4Addr>,
15 pub is_up: bool,
17 pub is_multicast: bool,
19 pub is_loopback: bool,
21}
22
23pub fn interface_allowed(name: &str, whitelist: &[String], blacklist: &[String]) -> bool {
28 if blacklist
29 .iter()
30 .any(|p| glob_match(p.as_bytes(), name.as_bytes()))
31 {
32 return false;
33 }
34 whitelist.is_empty()
35 || whitelist
36 .iter()
37 .any(|p| glob_match(p.as_bytes(), name.as_bytes()))
38}
39
40pub fn glob_match(pattern: &[u8], name: &[u8]) -> bool {
44 match (pattern, name) {
45 ([], []) => true,
47 ([], _) => false,
49 ([b'*', rest @ ..], _) => {
51 glob_match(rest, name) || (!name.is_empty() && glob_match(pattern, &name[1..]))
52 }
53 ([b'?', p_rest @ ..], [_, n_rest @ ..]) => glob_match(p_rest, n_rest),
55 ([b'?', ..], []) => false,
57 ([p, p_rest @ ..], [n, n_rest @ ..]) if p == n => glob_match(p_rest, n_rest),
59 _ => false,
61 }
62}
63
64pub fn list_interfaces() -> Vec<InterfaceInfo> {
70 #[cfg(unix)]
71 {
72 list_interfaces_unix()
73 }
74 #[cfg(windows)]
75 {
76 list_interfaces_windows()
77 }
78 #[cfg(not(any(unix, windows)))]
79 {
80 vec![]
81 }
82}
83
84#[cfg(unix)]
85fn list_interfaces_unix() -> Vec<InterfaceInfo> {
86 use std::collections::HashMap;
87
88 let mut map: HashMap<String, InterfaceInfo> = HashMap::new();
89
90 unsafe {
91 let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
92 if libc::getifaddrs(&mut ifap) != 0 {
93 tracing::warn!(
94 error = %std::io::Error::last_os_error(),
95 "getifaddrs failed — interface enumeration unavailable"
96 );
97 return vec![];
98 }
99
100 let mut ifa = ifap;
101 while !ifa.is_null() {
102 let name_ptr = (*ifa).ifa_name;
103 if name_ptr.is_null() {
104 ifa = (*ifa).ifa_next;
105 continue;
106 }
107 let name = std::ffi::CStr::from_ptr(name_ptr)
108 .to_string_lossy()
109 .into_owned();
110
111 let flags = (*ifa).ifa_flags;
112 let is_up =
113 flags & (libc::IFF_UP as u32) != 0 && flags & (libc::IFF_RUNNING as u32) != 0;
114 let is_multicast = flags & (libc::IFF_MULTICAST as u32) != 0;
115 let is_loopback = flags & (libc::IFF_LOOPBACK as u32) != 0;
116
117 let entry = map.entry(name.clone()).or_insert_with(|| InterfaceInfo {
118 name: name.clone(),
119 ipv4_addrs: Vec::new(),
120 is_up,
121 is_multicast,
122 is_loopback,
123 });
124 entry.is_up = is_up;
126 entry.is_multicast = is_multicast;
127 entry.is_loopback = is_loopback;
128
129 if !(*ifa).ifa_addr.is_null() {
131 let sa_family = (*(*ifa).ifa_addr).sa_family as i32;
132 if sa_family == libc::AF_INET {
133 let sin = (*ifa).ifa_addr as *const libc::sockaddr_in;
134 let raw = u32::from_be((*sin).sin_addr.s_addr);
136 entry.ipv4_addrs.push(Ipv4Addr::from(raw));
137 }
138 }
139
140 ifa = (*ifa).ifa_next;
141 }
142
143 libc::freeifaddrs(ifap);
144 }
145
146 map.into_values().collect()
147}
148
149#[cfg(windows)]
150fn list_interfaces_windows() -> Vec<InterfaceInfo> {
151 use std::collections::HashMap;
153 use windows_sys::Win32::NetworkManagement::IpHelper::{
154 GAA_FLAG_INCLUDE_PREFIX, GetAdaptersAddresses, IP_ADAPTER_ADDRESSES_LH,
155 };
156 use windows_sys::Win32::Networking::WinSock::{AF_INET, SOCKADDR_IN};
157
158 const AF_UNSPEC: u32 = 0;
159 const ERROR_BUFFER_OVERFLOW: u32 = 111;
160 const IF_TYPE_SOFTWARE_LOOPBACK: u32 = 24;
161
162 let mut buf_len: u32 = 16 * 1024;
163 let mut buf: Vec<u8> = vec![0u8; buf_len as usize];
164
165 let rc = unsafe {
167 GetAdaptersAddresses(
168 AF_UNSPEC,
169 GAA_FLAG_INCLUDE_PREFIX,
170 std::ptr::null_mut(),
171 buf.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH,
172 &mut buf_len,
173 )
174 };
175 if rc == ERROR_BUFFER_OVERFLOW {
176 buf.resize(buf_len as usize, 0);
177 } else if rc != 0 {
178 return vec![];
179 }
180
181 let rc = unsafe {
183 GetAdaptersAddresses(
184 AF_UNSPEC,
185 GAA_FLAG_INCLUDE_PREFIX,
186 std::ptr::null_mut(),
187 buf.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH,
188 &mut buf_len,
189 )
190 };
191 if rc != 0 {
192 return vec![];
193 }
194
195 let mut map: HashMap<String, InterfaceInfo> = HashMap::new();
196 unsafe {
197 let mut adapter = buf.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH;
198 while !adapter.is_null() {
199 let friendly = if (*adapter).FriendlyName.is_null() {
200 String::new()
201 } else {
202 let mut len = 0usize;
204 let ptr = (*adapter).FriendlyName;
205 while *ptr.add(len) != 0 {
206 len += 1;
207 }
208 String::from_utf16_lossy(std::slice::from_raw_parts(ptr, len))
209 };
210
211 let is_up = (*adapter).OperStatus == 1; let is_loopback = (*adapter).IfType == IF_TYPE_SOFTWARE_LOOPBACK;
213 let is_multicast = is_up && !is_loopback;
216
217 let entry = map
218 .entry(friendly.clone())
219 .or_insert_with(|| InterfaceInfo {
220 name: friendly.clone(),
221 ipv4_addrs: Vec::new(),
222 is_up,
223 is_multicast,
224 is_loopback,
225 });
226
227 let mut ua = (*adapter).FirstUnicastAddress;
229 while !ua.is_null() {
230 let sa = (*ua).Address.lpSockaddr;
231 if !sa.is_null() && (*sa).sa_family == AF_INET as u16 {
232 let sin = sa as *const SOCKADDR_IN;
233 let raw = u32::from_be((*sin).sin_addr.S_un.S_addr);
234 entry.ipv4_addrs.push(Ipv4Addr::from(raw));
235 }
236 ua = (*ua).Next;
237 }
238
239 adapter = (*adapter).Next;
240 }
241 }
242
243 map.into_values().collect()
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn glob_exact() {
252 assert!(glob_match(b"eth0", b"eth0"));
253 assert!(!glob_match(b"eth0", b"eth1"));
254 }
255
256 #[test]
257 fn glob_star_prefix() {
258 assert!(glob_match(b"eth*", b"eth0"));
259 assert!(glob_match(b"eth*", b"eth10"));
260 assert!(!glob_match(b"eth*", b"enp3s0"));
261 }
262
263 #[test]
264 fn glob_star_all() {
265 assert!(glob_match(b"*", b"eth0"));
266 assert!(glob_match(b"*", b"lo"));
267 assert!(glob_match(b"*", b""));
268 }
269
270 #[test]
271 fn glob_question_mark() {
272 assert!(glob_match(b"eth?", b"eth0"));
273 assert!(!glob_match(b"eth?", b"eth10"));
274 }
275
276 #[test]
277 fn glob_docker_blacklist() {
278 assert!(glob_match(b"docker*", b"docker0"));
279 assert!(glob_match(b"docker*", b"docker_gwbridge"));
280 assert!(!glob_match(b"docker*", b"eth0"));
281 }
282
283 #[test]
284 fn interface_allowed_basic() {
285 let wl = vec!["eth*".to_owned(), "en*".to_owned()];
286 let bl = vec!["lo".to_owned(), "docker*".to_owned()];
287 assert!(interface_allowed("eth0", &wl, &bl));
288 assert!(interface_allowed("en0", &wl, &bl));
289 assert!(!interface_allowed("lo", &wl, &bl));
290 assert!(!interface_allowed("docker0", &wl, &bl));
291 assert!(!interface_allowed("virbr0", &wl, &bl)); }
293
294 #[test]
295 fn interface_allowed_empty_whitelist_allows_all() {
296 let bl = vec!["lo".to_owned()];
297 assert!(interface_allowed("eth0", &[], &bl));
298 assert!(!interface_allowed("lo", &[], &bl));
299 }
300}