mx_remote/wire/netif.rs
1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! What the host's network interfaces offer: addresses, broadcast addresses
5//! and indices.
6
7use std::io;
8use std::net::Ipv4Addr;
9
10/// One IPv4 address on one interface.
11struct Interface {
12 name: String,
13 index: Option<u32>,
14 address: Ipv4Addr,
15 netmask: Ipv4Addr,
16}
17
18/// Every non-loopback IPv4 address the host has.
19fn interfaces() -> Vec<Interface> {
20 if_addrs::get_if_addrs()
21 .unwrap_or_default()
22 .into_iter()
23 .filter(|i| !i.is_loopback())
24 .filter_map(|i| match i.addr {
25 if_addrs::IfAddr::V4(ref v4) => Some(Interface {
26 name: i.name.clone(),
27 index: i.index,
28 address: v4.ip,
29 netmask: v4.netmask,
30 }),
31 if_addrs::IfAddr::V6(_) => None,
32 })
33 .collect()
34}
35
36/// The non-loopback IPv4 addresses that can be used as a local address.
37///
38/// The order is the host's own, which no interface property justifies reading
39/// as a preference; a caller with more than one address to choose from should
40/// choose.
41pub fn valid_addresses() -> Vec<Ipv4Addr> {
42 interfaces().into_iter().map(|i| i.address).collect()
43}
44
45/// The address to use when the caller named none.
46///
47/// The first address the host enumerates, which on a multi-homed machine is
48/// arbitrary. It is picked anyway rather than refused, because the single-homed
49/// case is both the common one and unambiguous.
50pub(crate) fn default_local_ip() -> io::Result<Ipv4Addr> {
51 interfaces().first().map(|i| i.address).ok_or_else(|| {
52 io::Error::new(
53 io::ErrorKind::AddrNotAvailable,
54 "the host has no non-loopback IPv4 address",
55 )
56 })
57}
58
59/// The directed broadcast address of the interface holding `local`, or of the
60/// first interface when `local` is `None`.
61///
62/// Directed rather than 255.255.255.255 so that the frame leaves by the chosen
63/// interface: a limited broadcast is routed by the host, which puts it back
64/// under exactly the decision naming an interface was meant to settle.
65pub(crate) fn broadcast_address(local: Option<Ipv4Addr>) -> io::Result<Ipv4Addr> {
66 interfaces()
67 .into_iter()
68 .find(|i| local.map_or(true, |want| i.address == want))
69 .map(|i| {
70 let (a, m) = (i.address.octets(), i.netmask.octets());
71 Ipv4Addr::new(a[0] | !m[0], a[1] | !m[1], a[2] | !m[2], a[3] | !m[3])
72 })
73 .ok_or_else(|| {
74 io::Error::new(
75 io::ErrorKind::AddrNotAvailable,
76 "no interface to derive a broadcast address from",
77 )
78 })
79}
80
81/// The kernel index of the named interface.
82pub(crate) fn index_of(name: &str) -> io::Result<u32> {
83 interfaces()
84 .into_iter()
85 .find(|i| i.name == name)
86 .and_then(|i| i.index)
87 .ok_or_else(|| {
88 io::Error::new(
89 io::ErrorKind::NotFound,
90 format!("no interface named {name:?}"),
91 )
92 })
93}
94
95/// The first non-loopback IPv4 address of the named interface.
96///
97/// An interface can carry discovery without having an address of its own, so
98/// this returns `None` rather than an error for one that has none.
99pub(crate) fn address_of(name: &str) -> Option<Ipv4Addr> {
100 interfaces()
101 .into_iter()
102 .find(|i| i.name == name)
103 .map(|i| i.address)
104}