1use std::fmt;
6use std::io;
7use std::net::IpAddr;
8use std::str::FromStr;
9
10use rama_core::error::{BoxError, BoxErrorExt as _, ErrorContext as _, ErrorExt as _};
11use rama_utils::macros::serde_str::impl_serde_str;
12use rama_utils::str::{eq_ignore_ascii_kebab_case, smol_str::SmolStr};
13
14use crate::address::ip::{IpScopes, ip_scope, ipnet::IpNet};
15
16#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
17mod unix;
18
19#[cfg(target_os = "windows")]
20mod windows;
21
22bitflags::bitflags! {
23 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37 pub struct InterfaceFlags: u8 {
38 const UP = 1 << 0;
44 const RUNNING = 1 << 1;
46 const LOOPBACK = 1 << 2;
48 const POINT_TO_POINT = 1 << 3;
50 const BROADCAST = 1 << 4;
52 const MULTICAST = 1 << 5;
54 }
55}
56
57const FLAG_NAMES: &[(&str, InterfaceFlags)] = &[
59 ("up", InterfaceFlags::UP),
60 ("running", InterfaceFlags::RUNNING),
61 ("loopback", InterfaceFlags::LOOPBACK),
62 ("point-to-point", InterfaceFlags::POINT_TO_POINT),
63 ("broadcast", InterfaceFlags::BROADCAST),
64 ("multicast", InterfaceFlags::MULTICAST),
65];
66
67impl fmt::Display for InterfaceFlags {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 let mut first = true;
70 for (name, flag) in FLAG_NAMES {
71 if self.contains(*flag) {
72 if !first {
73 f.write_str(",")?;
74 }
75 first = false;
76 f.write_str(name)?;
77 }
78 }
79 Ok(())
80 }
81}
82
83impl FromStr for InterfaceFlags {
84 type Err = BoxError;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 let mut flags = Self::empty();
88 for token in s.split([',', '|']) {
89 let token = token.trim();
90 if token.is_empty() {
91 continue;
92 }
93 flags |= FLAG_NAMES
94 .iter()
95 .find_map(|(name, flag)| {
96 eq_ignore_ascii_kebab_case(token.as_bytes(), name.as_bytes()).then_some(*flag)
97 })
98 .ok_or_else(|| {
99 BoxError::from_static_str("unknown interface flag")
100 .context_str_field("flag", token)
101 })?;
102 }
103 Ok(flags)
104 }
105}
106
107impl_serde_str!(display InterfaceFlags);
108
109#[derive(Clone, Copy, PartialEq, Eq, Hash)]
117pub struct HardwareAddress {
118 bytes: [u8; Self::MAX_LEN],
119 len: u8,
120}
121
122impl HardwareAddress {
123 const MAX_LEN: usize = 8;
124
125 fn try_new(bytes: &[u8]) -> Option<Self> {
128 if bytes.is_empty() || bytes.len() > Self::MAX_LEN || bytes.iter().all(|b| *b == 0) {
129 return None;
130 }
131 let len = u8::try_from(bytes.len()).ok()?;
132 let mut buf = [0u8; Self::MAX_LEN];
133 buf.get_mut(..bytes.len())?.copy_from_slice(bytes);
134 Some(Self { bytes: buf, len })
135 }
136
137 #[must_use]
139 pub fn as_bytes(&self) -> &[u8] {
140 self.bytes.get(..usize::from(self.len)).unwrap_or_default()
141 }
142}
143
144impl TryFrom<&[u8]> for HardwareAddress {
145 type Error = BoxError;
146
147 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
148 Self::try_new(bytes).ok_or_else(|| {
149 BoxError::from_static_str("hardware address must be 1..=8 bytes and not all-zero")
150 })
151 }
152}
153
154impl FromStr for HardwareAddress {
155 type Err = BoxError;
156
157 fn from_str(s: &str) -> Result<Self, Self::Err> {
158 let mut bytes = [0u8; Self::MAX_LEN];
159 let mut len = 0usize;
160 for group in s.split([':', '-']) {
161 if group.len() != 2 || !group.bytes().all(|b| b.is_ascii_hexdigit()) {
162 return Err(BoxError::from_static_str(
163 "hardware address groups must be exactly two hex digits",
164 ));
165 }
166 let Some(slot) = bytes.get_mut(len) else {
167 return Err(BoxError::from_static_str(
168 "hardware address is too long (max 8 bytes)",
169 ));
170 };
171 *slot = u8::from_str_radix(group, 16).context("parse hardware address hex group")?;
172 len += 1;
173 }
174 bytes.get(..len).and_then(Self::try_new).ok_or_else(|| {
175 BoxError::from_static_str("hardware address must be 1..=8 bytes and not all-zero")
176 })
177 }
178}
179
180impl TryFrom<&str> for HardwareAddress {
181 type Error = BoxError;
182
183 fn try_from(s: &str) -> Result<Self, Self::Error> {
184 s.parse()
185 }
186}
187
188impl_serde_str!(display HardwareAddress);
189
190impl fmt::Display for HardwareAddress {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 for (i, b) in self.as_bytes().iter().enumerate() {
193 if i > 0 {
194 write!(f, ":")?;
195 }
196 write!(f, "{b:02x}")?;
197 }
198 Ok(())
199 }
200}
201
202impl fmt::Debug for HardwareAddress {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 write!(f, "HardwareAddress({self})")
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
216pub struct InterfaceAddress {
217 address: IpAddr,
218 prefix_len: Option<u8>,
219 scope_id: Option<u32>,
220}
221
222impl InterfaceAddress {
223 #[cfg(any(
224 target_os = "linux",
225 target_os = "android",
226 target_vendor = "apple",
227 target_os = "windows",
228 test
229 ))]
230 fn new(address: IpAddr, prefix_len: Option<u8>, scope_id: Option<u32>) -> Self {
231 let max = if address.is_ipv4() { 32 } else { 128 };
232 Self {
233 address,
234 prefix_len: prefix_len.filter(|prefix| *prefix <= max),
235 scope_id: scope_id.filter(|id| *id != 0 && address.is_ipv6()),
236 }
237 }
238
239 #[must_use]
241 pub fn address(&self) -> IpAddr {
242 self.address
243 }
244
245 #[must_use]
248 pub fn prefix_len(&self) -> Option<u8> {
249 self.prefix_len
250 }
251
252 #[must_use]
259 pub fn scope_id(&self) -> Option<u32> {
260 self.scope_id
261 }
262
263 #[must_use]
267 pub fn ip_net(&self) -> Option<IpNet> {
268 IpNet::new(self.address, self.prefix_len?).ok()
269 }
270}
271
272impl fmt::Display for InterfaceAddress {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 self.address.fmt(f)?;
275 if let Some(zone) = self.scope_id {
276 write!(f, "%{zone}")?;
277 }
278 if let Some(prefix) = self.prefix_len {
279 write!(f, "/{prefix}")?;
280 }
281 Ok(())
282 }
283}
284
285impl FromStr for InterfaceAddress {
286 type Err = BoxError;
287
288 fn from_str(s: &str) -> Result<Self, Self::Err> {
289 let (s, prefix_len) = match s.split_once('/') {
290 Some((s, prefix)) => (
291 s,
292 Some(
293 prefix
294 .parse::<u8>()
295 .context("parse interface address prefix length")?,
296 ),
297 ),
298 None => (s, None),
299 };
300 let (s, scope_id) = match s.split_once('%') {
301 Some((s, zone)) => (
302 s,
303 Some(
304 zone.parse::<u32>()
305 .context("parse interface address zone (scope id)")?,
306 ),
307 ),
308 None => (s, None),
309 };
310 let address: IpAddr = s.parse().context("parse interface ip address")?;
311
312 if scope_id.is_some() && address.is_ipv4() {
313 return Err(BoxError::from_static_str(
314 "a zone (scope id) is only valid for an ipv6 interface address",
315 )
316 .context_field("address", address));
317 }
318 let max = if address.is_ipv4() { 32 } else { 128 };
319 if let Some(prefix) = prefix_len
320 && prefix > max
321 {
322 return Err(
323 BoxError::from_static_str("interface address prefix length out of range")
324 .context_field("prefix", prefix)
325 .context_field("address", address),
326 );
327 }
328
329 Ok(Self {
330 address,
331 prefix_len,
332 scope_id: scope_id.filter(|id| *id != 0),
333 })
334 }
335}
336
337impl_serde_str!(display InterfaceAddress);
338
339#[derive(Clone, Debug)]
341pub struct Interface {
342 name: SmolStr,
343 index: Option<u32>,
344 flags: InterfaceFlags,
345 hw_address: Option<HardwareAddress>,
346 description: Option<SmolStr>,
347 addresses: Vec<InterfaceAddress>,
348}
349
350impl Interface {
351 #[must_use]
354 pub fn name(&self) -> &str {
355 self.name.as_str()
356 }
357
358 #[must_use]
364 pub fn index(&self) -> Option<u32> {
365 self.index
366 }
367
368 #[must_use]
370 pub fn flags(&self) -> InterfaceFlags {
371 self.flags
372 }
373
374 #[must_use]
376 pub fn is_up(&self) -> bool {
377 self.flags
378 .contains(InterfaceFlags::UP | InterfaceFlags::RUNNING)
379 }
380
381 #[must_use]
383 pub fn hardware_address(&self) -> Option<&HardwareAddress> {
384 self.hw_address.as_ref()
385 }
386
387 #[must_use]
389 pub fn description(&self) -> Option<&str> {
390 self.description.as_deref()
391 }
392
393 #[must_use]
395 pub fn addresses(&self) -> &[InterfaceAddress] {
396 &self.addresses
397 }
398
399 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
400 #[cfg_attr(
401 docsrs,
402 doc(cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))
403 )]
404 #[must_use]
412 pub fn device_name(&self) -> Option<super::DeviceName> {
413 super::DeviceName::try_from(self.name.as_str()).ok()
414 }
415}
416
417#[inline(always)]
428pub fn interfaces() -> io::Result<Vec<Interface>> {
429 #[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
430 {
431 unix::interfaces()
432 }
433
434 #[cfg(target_os = "windows")]
435 {
436 windows::interfaces()
437 }
438
439 #[cfg(not(any(
440 target_os = "linux",
441 target_os = "android",
442 target_vendor = "apple",
443 target_os = "windows"
444 )))]
445 {
446 Err(io::Error::new(
447 io::ErrorKind::Unsupported,
448 "network interface enumeration is not supported on this platform",
449 ))
450 }
451}
452
453pub fn local_addresses(scopes: IpScopes) -> io::Result<Vec<IpAddr>> {
460 Ok(collect_local_addresses(&interfaces()?, scopes))
461}
462
463pub fn route_source_address(destination: std::net::SocketAddr) -> Option<IpAddr> {
470 let bind: std::net::SocketAddr = if destination.is_ipv6() {
471 (std::net::Ipv6Addr::UNSPECIFIED, 0).into()
472 } else {
473 (std::net::Ipv4Addr::UNSPECIFIED, 0).into()
474 };
475
476 let socket = std::net::UdpSocket::bind(bind).ok()?;
477 socket.connect(destination).ok()?;
478 let address = socket.local_addr().ok()?.ip();
479 (!address.is_unspecified()).then_some(address)
480}
481
482fn collect_local_addresses(interfaces: &[Interface], scopes: IpScopes) -> Vec<IpAddr> {
483 let mut out = Vec::new();
484 for interface in interfaces {
485 if !interface.is_up() {
486 continue;
487 }
488 for address in interface.addresses() {
489 let ip = address.address();
490 if scopes.intersects(ip_scope(ip)) && !out.contains(&ip) {
491 out.push(ip);
492 }
493 }
494 }
495 out
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501 use std::net::{Ipv4Addr, Ipv6Addr};
502
503 fn interface(name: &str, flags: InterfaceFlags, addresses: &[IpAddr]) -> Interface {
504 Interface {
505 name: SmolStr::new(name),
506 index: None,
507 flags,
508 hw_address: None,
509 description: None,
510 addresses: addresses
511 .iter()
512 .map(|addr| InterfaceAddress::new(*addr, None, None))
513 .collect(),
514 }
515 }
516
517 const UP: InterfaceFlags = InterfaceFlags::UP.union(InterfaceFlags::RUNNING);
518
519 #[test]
520 fn collect_skips_interfaces_that_are_not_up() {
521 let ip: IpAddr = Ipv4Addr::new(192, 168, 1, 7).into();
522 let ifaces = [
523 interface("down0", InterfaceFlags::UP, &[ip]),
524 interface("off0", InterfaceFlags::empty(), &[ip]),
525 ];
526 assert!(collect_local_addresses(&ifaces, IpScopes::all()).is_empty());
527 }
528
529 #[test]
530 fn collect_filters_by_scope() {
531 let loopback: IpAddr = Ipv4Addr::LOCALHOST.into();
532 let private: IpAddr = Ipv4Addr::new(10, 0, 0, 1).into();
533 let global: IpAddr = Ipv4Addr::new(1, 1, 1, 1).into();
534 let ifaces = [interface("eth0", UP, &[loopback, private, global])];
535
536 assert_eq!(
537 collect_local_addresses(&ifaces, IpScopes::GLOBAL),
538 vec![global]
539 );
540 assert_eq!(
541 collect_local_addresses(&ifaces, IpScopes::LOOPBACK | IpScopes::PRIVATE),
542 vec![loopback, private]
543 );
544 }
545
546 #[test]
547 fn collect_dedupes_preserving_first_seen_order() {
548 let a: IpAddr = Ipv4Addr::new(10, 0, 0, 1).into();
549 let b: IpAddr = Ipv6Addr::LOCALHOST.into();
550 let ifaces = [
551 interface("eth0", UP, &[a, b]),
552 interface("eth1", UP, &[b, a]),
553 ];
554 assert_eq!(
555 collect_local_addresses(&ifaces, IpScopes::all()),
556 vec![a, b]
557 );
558 }
559
560 #[test]
561 fn hardware_address_validation_and_display() {
562 let _ = HardwareAddress::try_from(&[][..]).unwrap_err();
563 let _ = HardwareAddress::try_from(&[0u8; 6][..]).unwrap_err();
564 let _ = HardwareAddress::try_from(&[1u8; 9][..]).unwrap_err();
565
566 let mac = HardwareAddress::try_from(&[0xaa, 0xbb, 0xcc, 0x0d, 0xee, 0xff][..]).unwrap();
567 assert_eq!(mac.to_string(), "aa:bb:cc:0d:ee:ff");
568 assert_eq!(mac.as_bytes(), &[0xaa, 0xbb, 0xcc, 0x0d, 0xee, 0xff]);
569 }
570
571 #[test]
572 fn interface_address_prefix_and_display() {
573 let addr = InterfaceAddress::new(Ipv4Addr::new(192, 168, 1, 7).into(), Some(24), None);
574 assert_eq!(addr.prefix_len(), Some(24));
575 assert_eq!(addr.to_string(), "192.168.1.7/24");
576 let net = addr.ip_net().unwrap();
577 assert!(net.contains(&addr.address()));
578 assert_eq!(net.trunc().to_string(), "192.168.1.0/24");
579
580 let addr = InterfaceAddress::new(Ipv4Addr::new(192, 168, 1, 7).into(), Some(33), None);
582 assert_eq!(addr.prefix_len(), None);
583 assert!(addr.ip_net().is_none());
584 assert_eq!(addr.to_string(), "192.168.1.7");
585 }
586
587 #[test]
588 fn interface_address_scope_id() {
589 let link_local: IpAddr = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1).into();
590 let addr = InterfaceAddress::new(link_local, Some(64), Some(3));
591 assert_eq!(addr.scope_id(), Some(3));
592 assert_eq!(addr.to_string(), "fe80::1%3/64");
593 assert_eq!(addr.to_string().parse::<InterfaceAddress>().unwrap(), addr);
594
595 assert_eq!(
597 InterfaceAddress::new(link_local, None, Some(0)).scope_id(),
598 None
599 );
600 assert_eq!(
601 InterfaceAddress::new(Ipv4Addr::new(10, 0, 0, 1).into(), None, Some(3)).scope_id(),
602 None
603 );
604 }
605
606 #[test]
607 fn interface_getters() {
608 let mac: HardwareAddress = "aa:bb:cc:0d:ee:ff".parse().unwrap();
609 let addr: InterfaceAddress = "192.168.1.7/24".parse().unwrap();
610 let iface = Interface {
611 name: SmolStr::new("eth0"),
612 index: Some(3),
613 flags: UP | InterfaceFlags::MULTICAST,
614 hw_address: Some(mac),
615 description: Some(SmolStr::new("some adapter")),
616 addresses: vec![addr],
617 };
618
619 assert_eq!(iface.name(), "eth0");
620 assert_eq!(iface.index(), Some(3));
621 assert_eq!(iface.flags(), UP | InterfaceFlags::MULTICAST);
622 assert!(iface.is_up());
623 assert_eq!(iface.hardware_address(), Some(&mac));
624 assert_eq!(iface.description(), Some("some adapter"));
625 assert_eq!(iface.addresses(), &[addr]);
626
627 let down = Interface {
628 flags: InterfaceFlags::UP,
629 ..iface
630 };
631 assert!(!down.is_up());
632 }
633
634 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
635 #[test]
636 fn interface_device_name() {
637 let mut iface = Interface {
638 name: SmolStr::new("eth0"),
639 index: None,
640 flags: InterfaceFlags::empty(),
641 hw_address: None,
642 description: None,
643 addresses: Vec::new(),
644 };
645 assert_eq!(
646 iface.device_name().map(|name| name.as_str().to_owned()),
647 Some("eth0".to_owned())
648 );
649
650 iface.name = SmolStr::new("6in4-wan");
652 assert!(iface.device_name().is_none());
653 }
654
655 #[test]
656 fn hardware_address_boundaries_and_debug() {
657 let mac = HardwareAddress::try_from(&[1u8; 8][..]).unwrap();
659 assert_eq!(mac.as_bytes(), &[1u8; 8]);
660 assert_eq!(mac.to_string().parse::<HardwareAddress>().unwrap(), mac);
661
662 let mac: HardwareAddress = "aa:bb:cc:0d:ee:ff".parse().unwrap();
663 assert_eq!(format!("{mac:?}"), "HardwareAddress(aa:bb:cc:0d:ee:ff)");
664 }
665
666 #[test]
667 fn interface_address_parse_boundary_prefixes() {
668 assert_eq!(
669 "1.2.3.4/32"
670 .parse::<InterfaceAddress>()
671 .unwrap()
672 .prefix_len(),
673 Some(32)
674 );
675 assert_eq!(
676 "::1/128".parse::<InterfaceAddress>().unwrap().prefix_len(),
677 Some(128)
678 );
679 }
680
681 #[test]
682 fn interface_flags_display_from_str_roundtrip() {
683 for (name, flag) in FLAG_NAMES {
684 assert_eq!(flag.to_string(), *name);
685 assert_eq!(name.parse::<InterfaceFlags>().unwrap(), *flag);
686 }
687
688 let flags = InterfaceFlags::UP | InterfaceFlags::RUNNING | InterfaceFlags::POINT_TO_POINT;
689 assert_eq!(flags.to_string(), "up,running,point-to-point");
690 assert_eq!(flags.to_string().parse::<InterfaceFlags>().unwrap(), flags);
691
692 assert_eq!(
693 "UP|POINT_TO_POINT".parse::<InterfaceFlags>().unwrap(),
694 InterfaceFlags::UP | InterfaceFlags::POINT_TO_POINT
695 );
696 assert_eq!(
697 "".parse::<InterfaceFlags>().unwrap(),
698 InterfaceFlags::empty()
699 );
700 let err = "bogus".parse::<InterfaceFlags>().unwrap_err();
701 assert!(err.to_string().contains("bogus"), "err: {err}");
702 }
703
704 #[test]
705 fn hardware_address_from_str() {
706 let mac: HardwareAddress = "aa:bb:cc:0d:ee:ff".parse().unwrap();
707 assert_eq!(mac.as_bytes(), &[0xaa, 0xbb, 0xcc, 0x0d, 0xee, 0xff]);
708 assert_eq!(mac.to_string().parse::<HardwareAddress>().unwrap(), mac);
709
710 assert_eq!("AA-BB-CC-0D-EE-FF".parse::<HardwareAddress>().unwrap(), mac);
712
713 let _ = "".parse::<HardwareAddress>().unwrap_err();
714 let _ = "aa:b:cc".parse::<HardwareAddress>().unwrap_err();
715 let _ = "aa:+f:cc".parse::<HardwareAddress>().unwrap_err();
716 let _ = "00:00:00:00:00:00".parse::<HardwareAddress>().unwrap_err();
717 let _ = "aa:bb:cc:dd:ee:ff:00:11:22"
718 .parse::<HardwareAddress>()
719 .unwrap_err();
720 }
721
722 #[test]
723 fn interface_address_from_str() {
724 let addr: InterfaceAddress = "192.168.1.7/24".parse().unwrap();
725 assert_eq!(addr.address(), IpAddr::from(Ipv4Addr::new(192, 168, 1, 7)));
726 assert_eq!(addr.prefix_len(), Some(24));
727 assert_eq!(addr.to_string().parse::<InterfaceAddress>().unwrap(), addr);
728
729 let addr: InterfaceAddress = "fe80::1".parse().unwrap();
730 assert_eq!(addr.prefix_len(), None);
731
732 let addr: InterfaceAddress = "fe80::1%3/64".parse().unwrap();
733 assert_eq!(addr.scope_id(), Some(3));
734 assert_eq!(addr.prefix_len(), Some(64));
735 let addr: InterfaceAddress = "fe80::1%0".parse().unwrap();
736 assert_eq!(addr.scope_id(), None);
737
738 let _ = "192.168.1.7/33".parse::<InterfaceAddress>().unwrap_err();
739 let _ = "192.168.1.7/x".parse::<InterfaceAddress>().unwrap_err();
740 let _ = "192.168.1.7%3".parse::<InterfaceAddress>().unwrap_err();
741 let _ = "fe80::1%x".parse::<InterfaceAddress>().unwrap_err();
742 let _ = "not-an-ip".parse::<InterfaceAddress>().unwrap_err();
743 }
744
745 #[test]
746 fn serde_string_roundtrips() {
747 let flags = InterfaceFlags::UP | InterfaceFlags::MULTICAST;
748 let json = serde_json::to_string(&flags).unwrap();
749 assert_eq!(json, "\"up,multicast\"");
750 assert_eq!(
751 serde_json::from_str::<InterfaceFlags>(&json).unwrap(),
752 flags
753 );
754
755 let mac: HardwareAddress = "aa:bb:cc:0d:ee:ff".parse().unwrap();
756 let json = serde_json::to_string(&mac).unwrap();
757 assert_eq!(json, "\"aa:bb:cc:0d:ee:ff\"");
758 assert_eq!(serde_json::from_str::<HardwareAddress>(&json).unwrap(), mac);
759
760 let addr: InterfaceAddress = "10.0.0.1/8".parse().unwrap();
761 let json = serde_json::to_string(&addr).unwrap();
762 assert_eq!(json, "\"10.0.0.1/8\"");
763 assert_eq!(
764 serde_json::from_str::<InterfaceAddress>(&json).unwrap(),
765 addr
766 );
767
768 let addr: InterfaceAddress = "fe80::1%3/64".parse().unwrap();
769 let json = serde_json::to_string(&addr).unwrap();
770 assert_eq!(json, "\"fe80::1%3/64\"");
771 assert_eq!(
772 serde_json::from_str::<InterfaceAddress>(&json).unwrap(),
773 addr
774 );
775 }
776
777 #[cfg(all(
779 any(
780 target_os = "linux",
781 target_os = "android",
782 target_vendor = "apple",
783 target_os = "windows"
784 ),
785 not(miri)
786 ))]
787 mod live {
788 use super::*;
789
790 #[test]
791 fn enumerates_loopback() {
792 let ifaces = interfaces().unwrap();
793 assert!(!ifaces.is_empty());
794 assert!(
795 ifaces
796 .iter()
797 .any(|i| i.flags().contains(InterfaceFlags::LOOPBACK))
798 );
799 assert!(
802 ifaces
803 .iter()
804 .flat_map(Interface::addresses)
805 .any(|a| a.address().is_loopback())
806 );
807 assert!(ifaces.iter().any(Interface::is_up));
808 }
809
810 #[test]
811 fn local_addresses_scope_invariants() {
812 let all = local_addresses(IpScopes::all()).unwrap();
813 let global = local_addresses(IpScopes::GLOBAL).unwrap();
814
815 assert!(all.iter().any(|ip| ip.is_loopback()));
816 for ip in &global {
817 assert!(all.contains(ip));
818 assert!(
819 !ip_scope(*ip).intersects(IpScopes::LOOPBACK | IpScopes::LINK_LOCAL),
820 "global result contains non-global address: {ip}"
821 );
822 }
823 for (i, ip) in all.iter().enumerate() {
825 assert!(!all[..i].contains(ip), "duplicate address: {ip}");
826 }
827 }
828 }
829
830 #[test]
831 fn route_source_address_answers_for_loopback() {
832 let source = route_source_address((std::net::Ipv4Addr::LOCALHOST, 53).into());
834 assert_eq!(source, Some(std::net::Ipv4Addr::LOCALHOST.into()));
835
836 if let Some(source) = route_source_address((std::net::Ipv6Addr::LOCALHOST, 53).into()) {
838 assert!(!source.is_unspecified(), "{source}");
839 assert!(source.is_ipv6(), "{source}");
840 }
841 }
842}