Skip to main content

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}