1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11
12#[cfg(not(windows))]
13mod posix;
14#[cfg(all(
15 not(windows),
16 not(all(
17 target_vendor = "apple",
18 any(
19 target_os = "macos",
20 target_os = "ios",
21 target_os = "tvos",
22 target_os = "watchos",
23 target_os = "visionos"
24 )
25 )),
26 not(target_os = "freebsd"),
27 not(target_os = "netbsd"),
28 not(target_os = "openbsd"),
29 not(target_os = "illumos")
30))]
31mod posix_not_apple;
32mod sockaddr;
33#[cfg(windows)]
34mod windows;
35
36use std::io;
37use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
38
39#[derive(Debug, PartialEq, Eq, Hash, Clone)]
41pub struct Interface {
42 pub name: String,
44 pub addr: IfAddr,
46 pub index: Option<u32>,
48 #[cfg(windows)]
53 pub adapter_name: String,
54}
55
56impl Interface {
57 #[must_use]
59 pub const fn is_loopback(&self) -> bool {
60 self.addr.is_loopback()
61 }
62
63 #[must_use]
65 pub const fn is_link_local(&self) -> bool {
66 self.addr.is_link_local()
67 }
68
69 #[must_use]
71 pub const fn ip(&self) -> IpAddr {
72 self.addr.ip()
73 }
74}
75
76#[derive(Debug, PartialEq, Eq, Hash, Clone)]
78pub enum IfAddr {
79 V4(Ifv4Addr),
81 V6(Ifv6Addr),
83}
84
85impl IfAddr {
86 #[must_use]
88 pub const fn is_loopback(&self) -> bool {
89 match *self {
90 IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_loopback(),
91 IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_loopback(),
92 }
93 }
94
95 #[must_use]
97 pub const fn is_link_local(&self) -> bool {
98 match *self {
99 IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_link_local(),
100 IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_link_local(),
101 }
102 }
103
104 #[must_use]
106 pub const fn ip(&self) -> IpAddr {
107 match *self {
108 IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip),
109 IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip),
110 }
111 }
112}
113
114#[derive(Debug, PartialEq, Eq, Hash, Clone)]
116pub struct Ifv4Addr {
117 pub ip: Ipv4Addr,
119 pub netmask: Ipv4Addr,
121 pub prefixlen: u8,
123 pub broadcast: Option<Ipv4Addr>,
125}
126
127impl Ifv4Addr {
128 #[must_use]
130 pub const fn is_loopback(&self) -> bool {
131 self.ip.is_loopback()
132 }
133
134 #[must_use]
136 pub const fn is_link_local(&self) -> bool {
137 self.ip.is_link_local()
138 }
139}
140
141#[derive(Debug, PartialEq, Eq, Hash, Clone)]
143pub struct Ifv6Addr {
144 pub ip: Ipv6Addr,
146 pub netmask: Ipv6Addr,
148 pub prefixlen: u8,
150 pub broadcast: Option<Ipv6Addr>,
152}
153
154impl Ifv6Addr {
155 #[must_use]
157 pub const fn is_loopback(&self) -> bool {
158 self.ip.is_loopback()
159 }
160
161 #[must_use]
163 pub const fn is_link_local(&self) -> bool {
164 let bytes = self.ip.octets();
165
166 bytes[0] == 0xfe && bytes[1] == 0x80
167 }
168}
169
170#[cfg(not(windows))]
171mod getifaddrs_posix {
172 use libc::if_nametoindex;
173
174 use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface};
175 use crate::posix::{self as ifaddrs, IfAddrs};
176 use crate::sockaddr;
177 use std::ffi::CStr;
178 use std::io;
179 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
180
181 #[allow(unsafe_code)]
183 pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
184 let mut ret = Vec::<Interface>::new();
185 let ifaddrs = IfAddrs::new()?;
186
187 for ifaddr in ifaddrs.iter() {
188 let addr = match sockaddr::to_ipaddr(ifaddr.ifa_addr) {
189 None => continue,
190 Some(IpAddr::V4(ipv4_addr)) => {
191 let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) {
192 Some(IpAddr::V4(netmask)) => netmask,
193 _ => Ipv4Addr::new(0, 0, 0, 0),
194 };
195 let broadcast = if (ifaddr.ifa_flags & 2) != 0 {
196 match ifaddrs::do_broadcast(&ifaddr) {
197 Some(IpAddr::V4(broadcast)) => Some(broadcast),
198 _ => None,
199 }
200 } else {
201 None
202 };
203 let prefixlen = if cfg!(target_endian = "little") {
204 u32::from_le_bytes(netmask.octets()).count_ones() as u8
205 } else {
206 u32::from_be_bytes(netmask.octets()).count_ones() as u8
207 };
208 IfAddr::V4(Ifv4Addr {
209 ip: ipv4_addr,
210 netmask,
211 prefixlen,
212 broadcast,
213 })
214 }
215 Some(IpAddr::V6(ipv6_addr)) => {
216 let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) {
217 Some(IpAddr::V6(netmask)) => netmask,
218 _ => Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0),
219 };
220 let broadcast = if (ifaddr.ifa_flags & 2) != 0 {
221 match ifaddrs::do_broadcast(&ifaddr) {
222 Some(IpAddr::V6(broadcast)) => Some(broadcast),
223 _ => None,
224 }
225 } else {
226 None
227 };
228 let prefixlen = if cfg!(target_endian = "little") {
229 u128::from_le_bytes(netmask.octets()).count_ones() as u8
230 } else {
231 u128::from_be_bytes(netmask.octets()).count_ones() as u8
232 };
233 IfAddr::V6(Ifv6Addr {
234 ip: ipv6_addr,
235 netmask,
236 prefixlen,
237 broadcast,
238 })
239 }
240 };
241
242 let name = unsafe { CStr::from_ptr(ifaddr.ifa_name) }
243 .to_string_lossy()
244 .into_owned();
245 let index = {
246 let index = unsafe { if_nametoindex(ifaddr.ifa_name) };
247
248 if index == 0 {
252 None
253 } else {
254 Some(index)
255 }
256 };
257 ret.push(Interface { name, addr, index });
258 }
259
260 Ok(ret)
261 }
262}
263
264#[cfg(not(windows))]
266pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
267 getifaddrs_posix::get_if_addrs()
268}
269
270#[cfg(windows)]
271mod getifaddrs_windows {
272 use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface};
273 use crate::sockaddr;
274 use crate::windows::IfAddrs;
275 use std::io;
276 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
277 use windows_sys::Win32::Networking::WinSock::IpDadStatePreferred;
278
279 pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
281 let mut ret = Vec::<Interface>::new();
282 let ifaddrs = IfAddrs::new()?;
283
284 for ifaddr in ifaddrs.iter() {
285 for addr in ifaddr.unicast_addresses() {
286 if addr.DadState != IpDadStatePreferred {
287 continue;
288 }
289 let addr = match sockaddr::to_ipaddr(addr.Address.lpSockaddr) {
290 None => continue,
291 Some(IpAddr::V4(ipv4_addr)) => {
292 let mut item_netmask = Ipv4Addr::new(0, 0, 0, 0);
293 let mut item_broadcast = None;
294 let item_prefix = addr.OnLinkPrefixLength;
295
296 'prefixloopv4: for prefix in ifaddr.prefixes() {
298 let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr);
299 match ipprefix {
300 Some(IpAddr::V4(ref a)) => {
301 let mut netmask: [u8; 4] = [0; 4];
302 for (n, netmask_elt) in netmask
303 .iter_mut()
304 .enumerate()
305 .take((prefix.PrefixLength as usize + 7) / 8)
306 {
307 let x_byte = ipv4_addr.octets()[n];
308 let y_byte = a.octets()[n];
309 for m in 0..8 {
310 if (n * 8) + m >= prefix.PrefixLength as usize {
311 break;
312 }
313 let bit = 1 << (7 - m);
314 if (x_byte & bit) == (y_byte & bit) {
315 *netmask_elt |= bit;
316 } else {
317 continue 'prefixloopv4;
318 }
319 }
320 }
321 item_netmask = Ipv4Addr::new(
322 netmask[0], netmask[1], netmask[2], netmask[3],
323 );
324 let mut broadcast: [u8; 4] = ipv4_addr.octets();
325 for n in 0..4 {
326 broadcast[n] |= !netmask[n];
327 }
328 item_broadcast = Some(Ipv4Addr::new(
329 broadcast[0],
330 broadcast[1],
331 broadcast[2],
332 broadcast[3],
333 ));
334 break 'prefixloopv4;
335 }
336 _ => continue,
337 };
338 }
339 IfAddr::V4(Ifv4Addr {
340 ip: ipv4_addr,
341 netmask: item_netmask,
342 prefixlen: item_prefix,
343 broadcast: item_broadcast,
344 })
345 }
346 Some(IpAddr::V6(ipv6_addr)) => {
347 let mut item_netmask = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
348 let item_prefix = addr.OnLinkPrefixLength;
349 'prefixloopv6: for prefix in ifaddr.prefixes() {
351 let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr);
352 match ipprefix {
353 Some(IpAddr::V6(ref a)) => {
354 let mut netmask: [u16; 8] = [0; 8];
357 for (n, netmask_elt) in netmask
358 .iter_mut()
359 .enumerate()
360 .take((prefix.PrefixLength as usize + 15) / 16)
361 {
362 let x_word = ipv6_addr.segments()[n];
363 let y_word = a.segments()[n];
364 for m in 0..16 {
365 if (n * 16) + m >= prefix.PrefixLength as usize {
366 break;
367 }
368 let bit = 1 << (15 - m);
369 if (x_word & bit) == (y_word & bit) {
370 *netmask_elt |= bit;
371 } else {
372 continue 'prefixloopv6;
373 }
374 }
375 }
376 item_netmask = Ipv6Addr::new(
377 netmask[0], netmask[1], netmask[2], netmask[3], netmask[4],
378 netmask[5], netmask[6], netmask[7],
379 );
380 break 'prefixloopv6;
381 }
382 _ => continue,
383 };
384 }
385 IfAddr::V6(Ifv6Addr {
386 ip: ipv6_addr,
387 netmask: item_netmask,
388 prefixlen: item_prefix,
389 broadcast: None,
390 })
391 }
392 };
393
394 let index = match addr {
395 IfAddr::V4(_) => ifaddr.ipv4_index(),
396 IfAddr::V6(_) => ifaddr.ipv6_index(),
397 };
398 ret.push(Interface {
399 name: ifaddr.name(),
400 addr,
401 index,
402 adapter_name: ifaddr.adapter_name(),
403 });
404 }
405 }
406
407 Ok(ret)
408 }
409}
410
411#[cfg(windows)]
413pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
414 getifaddrs_windows::get_if_addrs()
415}
416
417#[cfg(not(any(
418 all(
419 target_vendor = "apple",
420 any(
421 target_os = "macos",
422 target_os = "ios",
423 target_os = "tvos",
424 target_os = "watchos",
425 target_os = "visionos"
426 )
427 ),
428 target_os = "freebsd",
429 target_os = "netbsd",
430 target_os = "openbsd",
431 target_os = "illumos"
432)))]
433#[cfg_attr(
434 docsrs,
435 doc(cfg(any(
436 not(target_vendor = "apple"),
437 not(target_os = "freebsd"),
438 not(target_os = "netbsd"),
439 not(target_os = "openbsd"),
440 not(target_os = "illumos")
441 )))
442)]
443mod if_change_notifier {
444 use super::Interface;
445 use std::collections::HashSet;
446 use std::io;
447 use std::time::{Duration, Instant};
448
449 #[derive(Debug, PartialEq, Eq, Hash, Clone)]
450 pub enum IfChangeType {
451 Added(Interface),
452 Removed(Interface),
453 }
454
455 #[cfg(windows)]
456 type InternalIfChangeNotifier = crate::windows::WindowsIfChangeNotifier;
457 #[cfg(not(windows))]
458 type InternalIfChangeNotifier = crate::posix_not_apple::PosixIfChangeNotifier;
459
460 pub struct IfChangeNotifier {
464 inner: InternalIfChangeNotifier,
465 last_ifs: HashSet<Interface>,
466 }
467
468 impl IfChangeNotifier {
469 pub fn new() -> io::Result<Self> {
472 Ok(Self {
473 inner: InternalIfChangeNotifier::new()?,
474 last_ifs: HashSet::from_iter(super::get_if_addrs()?),
475 })
476 }
477
478 pub fn wait(&mut self, timeout: Option<Duration>) -> io::Result<Vec<IfChangeType>> {
491 let start = Instant::now();
492 loop {
493 self.inner
494 .wait(timeout.map(|t| t.saturating_sub(start.elapsed())))?;
495
496 let new_ifs = HashSet::from_iter(super::get_if_addrs()?);
498 let mut changes: Vec<IfChangeType> = new_ifs
499 .difference(&self.last_ifs)
500 .cloned()
501 .map(IfChangeType::Added)
502 .collect();
503 changes.extend(
504 self.last_ifs
505 .difference(&new_ifs)
506 .cloned()
507 .map(IfChangeType::Removed),
508 );
509 self.last_ifs = new_ifs;
510
511 if !changes.is_empty() {
512 return Ok(changes);
513 }
514 }
515 }
516 }
517}
518
519#[cfg(not(any(
520 all(
521 target_vendor = "apple",
522 any(
523 target_os = "macos",
524 target_os = "ios",
525 target_os = "tvos",
526 target_os = "watchos",
527 target_os = "visionos"
528 )
529 ),
530 target_os = "freebsd",
531 target_os = "netbsd",
532 target_os = "openbsd",
533 target_os = "illumos"
534)))]
535#[cfg_attr(
536 docsrs,
537 doc(cfg(any(
538 not(target_vendor = "apple"),
539 not(target_os = "freebsd"),
540 not(target_os = "netbsd"),
541 not(target_os = "openbsd"),
542 not(target_os = "illumos")
543 )))
544)]
545pub use if_change_notifier::{IfChangeNotifier, IfChangeType};
546
547#[cfg(test)]
548mod tests {
549 use super::{get_if_addrs, Interface};
550 use std::io::Read;
551 use std::net::{IpAddr, Ipv4Addr};
552 use std::process::{Command, Stdio};
553 use std::str::FromStr;
554 use std::thread;
555 use std::time::Duration;
556
557 fn list_system_interfaces(cmd: &str, arg: &str) -> String {
558 let start_cmd = if arg.is_empty() {
559 Command::new(cmd).stdout(Stdio::piped()).spawn()
560 } else {
561 Command::new(cmd).arg(arg).stdout(Stdio::piped()).spawn()
562 };
563 let mut process = match start_cmd {
564 Err(why) => {
565 println!("couldn't start cmd {} : {}", cmd, why);
566 return String::new();
567 }
568 Ok(process) => process,
569 };
570 thread::sleep(Duration::from_millis(1000));
571 let _ = process.kill();
572 let result: Vec<u8> = process
573 .stdout
574 .unwrap()
575 .bytes()
576 .map(|x| x.unwrap())
577 .collect();
578 String::from_utf8(result).unwrap()
579 }
580
581 #[cfg(windows)]
582 fn list_system_addrs() -> Vec<IpAddr> {
583 use std::net::Ipv6Addr;
584 list_system_interfaces("ipconfig", "")
585 .lines()
586 .filter_map(|line| {
587 println!("{}", line);
588 if line.contains("Address") && !line.contains("Link-local") {
589 let addr_s: Vec<&str> = line.split(" : ").collect();
590 if line.contains("IPv6") {
591 return Some(IpAddr::V6(Ipv6Addr::from_str(addr_s[1]).unwrap()));
592 } else if line.contains("IPv4") {
593 return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap()));
594 }
595 }
596 None
597 })
598 .collect()
599 }
600
601 #[cfg(any(target_os = "linux", target_os = "android"))]
602 fn list_system_addrs() -> Vec<IpAddr> {
603 list_system_interfaces("ip", "addr")
604 .lines()
605 .filter_map(|line| {
606 println!("{}", line);
607 if line.contains("inet ") {
608 let addr_s: Vec<&str> = line.split_whitespace().collect();
609 let addr: Vec<&str> = addr_s[1].split('/').collect();
610 return Some(IpAddr::V4(Ipv4Addr::from_str(addr[0]).unwrap()));
611 }
612 None
613 })
614 .collect()
615 }
616
617 #[cfg(any(
618 target_os = "freebsd",
619 target_os = "netbsd",
620 target_os = "openbsd",
621 target_os = "illumos",
622 all(
623 target_vendor = "apple",
624 any(
625 target_os = "macos",
626 target_os = "ios",
627 target_os = "tvos",
628 target_os = "watchos",
629 target_os = "visionos"
630 )
631 )
632 ))]
633 fn list_system_addrs() -> Vec<IpAddr> {
634 list_system_interfaces("ifconfig", "")
635 .lines()
636 .filter_map(|line| {
637 println!("{}", line);
638 if line.contains("inet ") {
639 let addr_s: Vec<&str> = line.split_whitespace().collect();
640 return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap()));
641 }
642 None
643 })
644 .collect()
645 }
646
647 #[test]
648 fn test_get_if_addrs() {
649 let ifaces = get_if_addrs().unwrap();
650 println!("Local interfaces:");
651 println!("{:#?}", ifaces);
652 assert!(
654 1 <= ifaces
655 .iter()
656 .filter(|interface| interface.is_loopback())
657 .count()
658 );
659 for interface in &ifaces {
661 if let Some(idx) = interface.index {
662 assert!(idx > 0);
663 }
664 }
665
666 let is_loopback =
668 |interface: &&Interface| interface.addr.ip() == IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
669 assert_eq!(1, ifaces.iter().filter(is_loopback).count());
670
671 let system_addrs = list_system_addrs();
673 assert!(!system_addrs.is_empty());
674 for addr in system_addrs {
675 let mut listed = false;
676 println!("\n checking whether {:?} has been properly listed \n", addr);
677 for interface in &ifaces {
678 if interface.addr.ip() == addr {
679 listed = true;
680 }
681
682 assert!(interface.index.is_some());
683 }
684 assert!(listed);
685 }
686 }
687
688 #[cfg(not(any(
689 all(
690 target_vendor = "apple",
691 any(
692 target_os = "macos",
693 target_os = "ios",
694 target_os = "tvos",
695 target_os = "watchos",
696 target_os = "visionos"
697 )
698 ),
699 target_os = "freebsd",
700 target_os = "netbsd",
701 target_os = "openbsd",
702 target_os = "illumos"
703 )))]
704 #[test]
705 fn test_if_notifier() {
706 let notifier = crate::IfChangeNotifier::new();
716 assert!(notifier.is_ok());
717 let mut notifier = notifier.unwrap();
718
719 assert!(notifier.wait(Some(Duration::ZERO)).is_err());
720 }
721}