1use std::net::IpAddr;
2
3#[cfg(test)]
4mod test;
5
6#[derive(Clone, Debug, Eq, Hash, PartialEq)]
7pub struct Interface {
8 name: String,
9 flags: u64,
10 mac: [u8; 6],
11 address: IpAddr,
12 scope_id: Option<u32>,
13 netmask: IpAddr,
14}
15
16impl Interface {
17 pub fn name(&self) -> &str {
19 &self.name
20 }
21
22 pub fn flags(&self) -> u64 {
24 self.flags
25 }
26
27 pub fn mac(&self) -> [u8; 6] {
29 self.mac
30 }
31
32 pub fn address(&self) -> &IpAddr {
38 &self.address
39 }
40
41 pub fn scope_id(&self) -> Option<u32> {
43 self.scope_id
44 }
45
46 pub fn netmask(&self) -> &IpAddr {
47 &self.netmask
48 }
49
50 pub fn cidr(&self) -> (&IpAddr, u8) {
53 let range = match self.netmask {
54 IpAddr::V4(addr) => u32::from_be_bytes(addr.octets()).count_ones(),
55 IpAddr::V6(addr) => u128::from_be_bytes(addr.octets()).count_ones(),
56 };
57 (&self.address, range as u8)
58 }
59}
60
61#[cfg(target_os = "windows")]
62pub use windows::*;
63
64#[cfg(not(target_os = "windows"))]
65pub use unix::*;
66
67#[cfg(target_os = "windows")]
68mod windows {
69 use super::Interface;
70 use std::io;
71 use std::net::IpAddr;
72 use std::net::Ipv4Addr;
73 use std::net::Ipv6Addr;
74 use std::ptr::null_mut;
75 use std::ptr::NonNull;
76 use winapi::shared::ifdef::IfOperStatusUp;
77 use winapi::shared::ws2def::SOCKADDR;
78 use winapi::shared::ws2def::SOCKADDR_IN;
79 use winapi::shared::ws2ipdef::SOCKADDR_IN6;
80 use winapi::um::iphlpapi::GetAdaptersAddresses;
81 use winapi::um::iptypes::GAA_FLAG_SKIP_ANYCAST;
82 use winapi::um::iptypes::GAA_FLAG_SKIP_DNS_SERVER;
83 use winapi::um::iptypes::GAA_FLAG_SKIP_MULTICAST;
84 use winapi::um::iptypes::IP_ADAPTER_ADDRESSES;
85 use winapi::um::iptypes::IP_ADAPTER_UNICAST_ADDRESS;
86 use winapi::um::winsock2::PF_INET;
87 use winapi::um::winsock2::PF_INET6;
88 use winapi::um::winsock2::PF_UNSPEC;
89
90 pub fn up() -> io::Result<Up> {
93 let mut len = 0;
94
95 let flags = GAA_FLAG_SKIP_ANYCAST
96 + GAA_FLAG_SKIP_DNS_SERVER
97 + GAA_FLAG_SKIP_MULTICAST;
98
99 unsafe {
101 GetAdaptersAddresses(
102 PF_UNSPEC as _,
103 flags,
104 null_mut(),
105 null_mut(),
106 &mut len,
107 );
108 }
109
110 let mut buf = vec![0usize; len as _];
112
113 let result = unsafe {
114 GetAdaptersAddresses(
115 PF_UNSPEC as _,
116 flags,
117 null_mut(),
118 buf.as_mut_ptr() as *mut _,
119 &mut len,
120 )
121 };
122
123 if result != 0 {
124 return Err(io::Error::from_raw_os_error(result as _));
125 }
126
127 let adapter =
128 NonNull::new(buf.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES);
129
130 let address = adapter.and_then(|adapter| {
131 let adapter = unsafe { adapter.as_ref() };
132 NonNull::new(adapter.FirstUnicastAddress)
133 });
134
135 let iter = Iter { adapter, address };
136
137 Ok(Up { _buf: buf, iter })
138 }
139
140 pub struct Up {
141 _buf: Vec<usize>, iter: Iter,
143 }
144
145 impl Iterator for Up {
146 type Item = Interface;
147
148 fn next(&mut self) -> Option<Self::Item> {
149 self.iter.find_map(to_interface)
150 }
151 }
152
153 impl Drop for Up {
154 fn drop(&mut self) {}
155 }
156
157 struct Iter {
158 adapter: Option<NonNull<IP_ADAPTER_ADDRESSES>>,
159 address: Option<NonNull<IP_ADAPTER_UNICAST_ADDRESS>>,
160 }
161
162 impl Iterator for Iter {
163 type Item = (
164 NonNull<IP_ADAPTER_ADDRESSES>,
165 NonNull<IP_ADAPTER_UNICAST_ADDRESS>,
166 );
167
168 fn next(&mut self) -> Option<Self::Item> {
169 loop {
170 let adapter = self.adapter?;
171
172 if let Some(address) = self.address.take() {
173 self.address =
174 NonNull::new(unsafe { address.as_ref().Next });
175 return Some((adapter, address));
176 }
177
178 self.adapter = NonNull::new(unsafe { adapter.as_ref().Next });
179
180 self.address = self.adapter.and_then(|adapter| {
181 let adapter = unsafe { adapter.as_ref() };
182 NonNull::new(adapter.FirstUnicastAddress)
183 });
184 }
185 }
186 }
187
188 fn ip(addr: NonNull<SOCKADDR>) -> Option<IpAddr> {
189 let family = unsafe { addr.as_ref().sa_family };
190
191 match family as _ {
192 PF_INET => {
193 let addr = addr.as_ptr() as *mut SOCKADDR_IN;
194 let addr = unsafe { *(*addr).sin_addr.S_un.S_addr() };
195 let addr = Ipv4Addr::from(u32::from_be(addr));
196 Some(IpAddr::V4(addr))
197 }
198 PF_INET6 => {
199 let addr = addr.as_ptr() as *mut SOCKADDR_IN6;
200 let [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15] =
201 *unsafe { (*addr).sin6_addr.u.Byte() };
202 let s0 = 256 * b0 as u16 + b1 as u16;
203 let s1 = 256 * b2 as u16 + b3 as u16;
204 let s2 = 256 * b4 as u16 + b5 as u16;
205 let s3 = 256 * b6 as u16 + b7 as u16;
206 let s4 = 256 * b8 as u16 + b9 as u16;
207 let s5 = 256 * b10 as u16 + b11 as u16;
208 let s6 = 256 * b12 as u16 + b13 as u16;
209 let s7 = 256 * b14 as u16 + b15 as u16;
210 let addr = Ipv6Addr::new(s0, s1, s2, s3, s4, s5, s6, s7);
211 Some(IpAddr::V6(addr))
212 }
213 _ => None,
214 }
215 }
216
217 fn to_interface(
218 (adapter, addr): (
219 NonNull<IP_ADAPTER_ADDRESSES>,
220 NonNull<IP_ADAPTER_UNICAST_ADDRESS>,
221 ),
222 ) -> Option<Interface> {
223 let adapter = unsafe { adapter.as_ref() };
224
225 if adapter.OperStatus != IfOperStatusUp {
226 return None;
227 }
228
229 let addr = unsafe { addr.as_ref() };
230 let sockaddr = NonNull::new(addr.Address.lpSockaddr)?;
231 let prefixlen = addr.OnLinkPrefixLength as _;
232
233 let address = ip(sockaddr)?;
234
235 let netmask = match address {
236 IpAddr::V4(_) => {
237 let ones = !0u32;
238 let mask = ones & !ones.checked_shr(prefixlen).unwrap_or(0);
239 IpAddr::V4(Ipv4Addr::from(mask))
240 }
241 IpAddr::V6(_) => {
242 let ones = !0u128;
243 let mask = ones & !ones.checked_shr(prefixlen).unwrap_or(0);
244 IpAddr::V6(Ipv6Addr::from(mask))
245 }
246 };
247
248 let name =
249 unsafe { std::slice::from_raw_parts(adapter.FriendlyName, 256) };
250 let len = name.iter().position(|&b| b == 0).unwrap_or(name.len());
251 let name = String::from_utf16_lossy(&name[..len]);
252
253 let scope_id = address.is_ipv6().then(|| {
254 let addr = addr.Address.lpSockaddr as *const SOCKADDR_IN6;
255 unsafe { *(*addr).u.sin6_scope_id() }
256 });
257
258 let [b0, b1, b2, b3, b4, b5, _, _] = adapter.PhysicalAddress;
259 let mac = [b0, b1, b2, b3, b4, b5];
260
261 let flags = 0;
262
263 Some(Interface {
264 name,
265 flags,
266 mac,
267 address,
268 scope_id,
269 netmask,
270 })
271 }
272}
273
274#[cfg(not(target_os = "windows"))]
275mod unix {
276 use super::Interface;
277 use libc as c;
278 use std::ffi::CStr;
279 use std::io;
280 use std::mem;
281 use std::net::IpAddr;
282 use std::net::Ipv4Addr;
283 use std::net::Ipv6Addr;
284 use std::ptr;
285 use std::ptr::NonNull;
286
287 #[cfg(any(target_os = "android", target_os = "linux"))]
288 use crate::linux::*;
289
290 #[cfg(not(any(target_os = "android", target_os = "linux")))]
292 use crate::bsd::*;
293
294 pub fn up() -> io::Result<Up> {
297 let mut base = ptr::null_mut();
298
299 if 0 != unsafe { c::getifaddrs(&mut base) } {
300 return Err(io::Error::last_os_error());
301 }
302
303 let base = NonNull::new(base);
304 let iter = Iter(base);
305
306 Ok(Up { base, iter })
307 }
308
309 pub struct Up {
310 base: Option<NonNull<c::ifaddrs>>,
311 iter: Iter,
312 }
313
314 impl Iterator for Up {
315 type Item = Interface;
316
317 fn next(&mut self) -> Option<Self::Item> {
318 self.iter.find_map(|curr| to_interface(self.base, curr))
319 }
320 }
321
322 impl Drop for Up {
323 fn drop(&mut self) {
324 if let Some(mut base) = self.base {
325 unsafe { c::freeifaddrs(base.as_mut()) };
326 }
327 }
328 }
329
330 struct Iter(Option<NonNull<c::ifaddrs>>);
331
332 impl Iterator for Iter {
333 type Item = NonNull<c::ifaddrs>;
334
335 fn next(&mut self) -> Option<Self::Item> {
336 let curr = self.0?;
337 let next = unsafe { curr.as_ref().ifa_next };
338 mem::replace(&mut self.0, NonNull::new(next))
339 }
340 }
341
342 fn ip(addr: NonNull<c::sockaddr>) -> Option<IpAddr> {
343 let family = unsafe { addr.as_ref().sa_family };
344
345 match family as _ {
346 c::AF_INET => {
347 let addr = unsafe { &*(addr.as_ptr() as *mut c::sockaddr_in) };
348 let addr = Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr));
349 Some(IpAddr::V4(addr))
350 }
351 c::AF_INET6 => {
352 let addr = unsafe { &*(addr.as_ptr() as *mut c::sockaddr_in6) };
353 let [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15] =
354 addr.sin6_addr.s6_addr;
355 let s0 = 256 * b0 as u16 + b1 as u16;
356 let s1 = 256 * b2 as u16 + b3 as u16;
357 let s2 = 256 * b4 as u16 + b5 as u16;
358 let s3 = 256 * b6 as u16 + b7 as u16;
359 let s4 = 256 * b8 as u16 + b9 as u16;
360 let s5 = 256 * b10 as u16 + b11 as u16;
361 let s6 = 256 * b12 as u16 + b13 as u16;
362 let s7 = 256 * b14 as u16 + b15 as u16;
363 let addr = Ipv6Addr::new(s0, s1, s2, s3, s4, s5, s6, s7);
364 Some(IpAddr::V6(addr))
365 }
366 _ => None,
367 }
368 }
369
370 fn to_interface(
371 base: Option<NonNull<c::ifaddrs>>,
372 curr: NonNull<c::ifaddrs>,
373 ) -> Option<Interface> {
374 let curr = unsafe { curr.as_ref() };
375 let addr = NonNull::new(curr.ifa_addr)?;
376
377 if is_link(addr) {
378 return None;
379 }
380
381 let address = ip(addr)?;
382 let netmask = NonNull::new(curr.ifa_netmask).and_then(ip)?;
383
384 let name = unsafe { CStr::from_ptr(curr.ifa_name) };
385 let mac = Iter(base)
386 .find_map(|link| mac_of(name, link))
387 .unwrap_or_default();
388 let name = name.to_string_lossy().into_owned();
389
390 let flags = From::from(curr.ifa_flags);
391
392 let scope_id = address.is_ipv6().then(|| {
393 let addr = addr.as_ptr() as *const c::sockaddr_in6;
394 unsafe { (*addr).sin6_scope_id }
395 });
396
397 Some(Interface {
398 name,
399 flags,
400 mac,
401 address,
402 scope_id,
403 netmask,
404 })
405 }
406}
407
408#[cfg(any(target_os = "android", target_os = "linux"))]
409mod linux {
410 use libc as c;
411 use std::ffi::CStr;
412 use std::ptr::NonNull;
413
414 pub(crate) fn is_link(addr: NonNull<c::sockaddr>) -> bool {
415 c::AF_PACKET == unsafe { addr.as_ref().sa_family } as _
416 }
417
418 pub(crate) fn mac_of(
419 name: &CStr,
420 link: NonNull<c::ifaddrs>,
421 ) -> Option<[u8; 6]> {
422 let link = unsafe { link.as_ref() };
423 let addr = NonNull::new(link.ifa_addr)?;
424
425 if !is_link(addr) {
426 return None;
427 }
428
429 let ok = unsafe { CStr::from_ptr(link.ifa_name) }
430 .to_bytes()
431 .strip_prefix(name.to_bytes())
432 .filter(|suffix| suffix.is_empty() || suffix.starts_with(b":"))
433 .is_some();
434
435 if !ok {
436 return None;
437 }
438
439 let addr = link.ifa_addr as *const _ as *const c::sockaddr_ll;
440 let addr = unsafe { &*addr };
441
442 if addr.sll_halen != 6 {
443 return None;
444 }
445
446 let [b0, b1, b2, b3, b4, b5, _, _] = addr.sll_addr;
447
448 Some([b0, b1, b2, b3, b4, b5])
449 }
450}
451
452#[cfg(all(unix, not(any(target_os = "android", target_os = "linux"))))]
453mod bsd {
454 use libc as c;
455 use std::ffi::CStr;
456 use std::ptr::NonNull;
457
458 pub(crate) fn is_link(addr: NonNull<c::sockaddr>) -> bool {
459 c::AF_LINK == unsafe { addr.as_ref().sa_family } as _
460 }
461
462 pub(crate) fn mac_of(
463 name: &CStr,
464 link: NonNull<c::ifaddrs>,
465 ) -> Option<[u8; 6]> {
466 let link = unsafe { link.as_ref() };
467 let addr = NonNull::new(link.ifa_addr)?;
468
469 if !is_link(addr) {
470 return None;
471 }
472
473 let ok = unsafe { CStr::from_ptr(link.ifa_name) }
474 .to_bytes()
475 .strip_prefix(name.to_bytes())
476 .filter(|suffix| suffix.is_empty() || suffix.starts_with(b":"))
477 .is_some();
478
479 if !ok {
480 return None;
481 }
482
483 let addr = link.ifa_addr as *const _ as *const c::sockaddr_dl;
484 let addr = unsafe { &*addr };
485
486 if addr.sdl_alen != 6 {
487 return None;
488 }
489
490 let start = addr.sdl_nlen as usize; let end = start + addr.sdl_alen as usize;
494 let data = unsafe {
495 std::slice::from_raw_parts(
496 &addr.sdl_data as *const _ as *const u8,
497 end,
498 )
499 };
500
501 if let [b0, b1, b2, b3, b4, b5] = data[start..end] {
502 Some([b0, b1, b2, b3, b4, b5])
503 } else {
504 None
505 }
506 }
507}
508
509#[test]
510fn basic() {
511 for ifa in up().unwrap() {
512 println!("{:?} {:?}", ifa, ifa.cidr());
513
514 assert!(!ifa.name().is_empty());
515 assert!(ifa.address().is_ipv4() ^ ifa.scope_id().is_some());
516 assert_eq!(ifa.address().is_ipv4(), ifa.netmask().is_ipv4());
517
518 let link_local = "fe80::1" == &format!("{:?}", ifa.address());
519
520 if link_local || ifa.address().is_loopback() {
521 let (address, range) = ifa.cidr();
522 assert_eq!(address, ifa.address());
523 match address {
524 IpAddr::V6(_) if link_local => assert_eq!(range, 64),
525 IpAddr::V6(_) => assert_eq!(range, 128),
526 IpAddr::V4(_) => assert_eq!(range, 8),
527 }
528 }
529 }
530}