os_interface/
network.rs

1use libc::size_t;
2use libc::{
3    AF_INET, AF_INET6, IFF_BROADCAST, IFF_LOOPBACK, IFF_MULTICAST, IFF_RUNNING, IFF_UP,
4    freeifaddrs, getifaddrs, if_nametoindex, ifaddrs, sockaddr_in, sockaddr_in6,
5};
6use std::collections::BTreeMap;
7use std::ffi::{CStr, OsString};
8use std::io::{Error, ErrorKind};
9use std::net::{Ipv4Addr, Ipv6Addr};
10use std::os::unix::ffi::OsStringExt;
11use std::ptr;
12
13/// System network interface
14#[derive(Clone, PartialEq, Eq, Hash, Debug)]
15pub struct NetworkInterface {
16    /// Index
17    pub index: u32,
18    /// Name
19    pub name: String, // e.g. eth0
20    /// Address
21    pub addr: Vec<Addr>,
22    /// MAC address
23    pub mac_addr: Option<String>,
24    /// Interface flags
25    pub flags: Flags,
26}
27
28/// Interface flags
29#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
30pub struct Flags {
31    /// Interface is administratively up
32    pub up: bool,
33    /// Interface is a loopback device
34    pub loopback: bool,
35    /// Interface has resources allocated (operational)
36    pub running: bool,
37    /// Interface supports multicasting
38    pub multicast: bool,
39    /// Interface supports broadcast
40    pub broadcast: bool,
41}
42
43/// Network interface address
44#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
45pub enum Addr {
46    /// IPv4, AFINET address Family Internet Protocol version 4
47    IPv4(IfAddrV4),
48    /// IPv6, AFINET6 address Family Internet Protocol version 6
49    IPv6(IfAddrV6),
50}
51
52/// IPv4 Interface from the AFINET network interface family
53#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
54pub struct IfAddrV4 {
55    /// The IP address for this network interface
56    pub ip: Ipv4Addr,
57    /// The netmask for this interface
58    pub netmask: Option<Ipv4Addr>,
59    /// The broadcast address for this interface
60    pub broadcast: Option<Ipv4Addr>,
61}
62
63/// IPv6 Interface from the AFINET6 network interface family
64#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
65pub struct IfAddrV6 {
66    /// The IP address for this network interface
67    pub ip: Ipv6Addr,
68    /// The netmask for this interface
69    pub netmask: Option<Ipv6Addr>,
70}
71
72fn get_if_addr_v4(ifa: &ifaddrs, flags: &Flags) -> IfAddrV4 {
73    // Get Netmask
74    let mut netmask: Option<Ipv4Addr> = None;
75    if !ifa.ifa_netmask.is_null() {
76        let mask = unsafe { *(ifa.ifa_netmask as *const sockaddr_in) };
77        // Netmask in network byte order (Big Endian)
78        netmask = Some(Ipv4Addr::from(mask.sin_addr.s_addr.to_be()));
79    }
80
81    // Access the broadcast address from the union field
82    // Note: In some libc versions, this is called ifa_ifu; in others, ifa_dstaddr
83    // Use cfg to handle the field name difference
84    #[cfg(any(target_os = "linux", target_os = "android"))]
85    let broad_ptr = ifa.ifa_ifu;
86
87    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
88    let broad_ptr = ifa.ifa_dstaddr;
89
90    let mut broadcast: Option<Ipv4Addr> = None;
91    // Check if the interface supports broadcasting
92    if !broad_ptr.is_null() && flags.broadcast {
93        let sa = unsafe { *(broad_ptr as *const sockaddr_in) };
94        broadcast = Some(Ipv4Addr::from(u32::from_be(sa.sin_addr.s_addr)));
95    }
96
97    // Get address
98    let socket_addr = unsafe { *(ifa.ifa_addr as *const sockaddr_in) };
99    let ip = Ipv4Addr::from(socket_addr.sin_addr.s_addr.to_be());
100
101    IfAddrV4 {
102        ip,
103        netmask,
104        broadcast,
105    }
106}
107
108fn get_if_addr_v6(ifa: &ifaddrs) -> IfAddrV6 {
109    let mut netmask: Option<Ipv6Addr> = None;
110    if !ifa.ifa_netmask.is_null() {
111        let mask = unsafe { *(ifa.ifa_netmask as *const sockaddr_in6) };
112        // Access 16-byte array for IPv6 netmask
113        netmask = Some(Ipv6Addr::from(mask.sin6_addr.s6_addr));
114    }
115
116    let socket_addr = unsafe { *(ifa.ifa_addr as *const sockaddr_in6) };
117    let ip = Ipv6Addr::from(socket_addr.sin6_addr.s6_addr);
118
119    IfAddrV6 { ip, netmask }
120}
121
122fn mac_to_string(mac: &[u8]) -> String {
123    let mac_addr = format!(
124        "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
125        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
126    );
127    mac_addr
128}
129
130fn get_mac_addr(ifa: &ifaddrs, family: i32) -> Option<String> {
131    #[cfg(any(target_os = "linux", target_os = "android"))]
132    if family == libc::AF_PACKET {
133        let sll = unsafe { *(ifa.ifa_addr as *const libc::sockaddr_ll) };
134        let mac = sll.sll_addr;
135        let len = sll.sll_halen as usize;
136
137        // MAC addresses are usually 6 bytes (Ethernet)
138        if len == 6 {
139            return Some(mac_to_string(&mac));
140        }
141    }
142
143    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
144    if family == libc::AF_LINK {
145        let sdl = unsafe { *(ifa.ifa_addr as *const libc::sockaddr_dl) };
146
147        // Use LLADDR macro logic: it skips the name (sdl_nlen)
148        // to find the start of the hardware address
149        let mac_ptr = unsafe { (&sdl as *const _ as *const u8).add(8 + sdl.sdl_nlen as usize) };
150
151        if sdl.sdl_alen == 6 {
152            let mac = unsafe { std::slice::from_raw_parts(mac_ptr, 6) };
153            return Some(mac_to_string(mac));
154        }
155    }
156
157    None
158}
159
160/// Inserts a new NetworkInterface into the BTreeMap
161/// or updates with another address in the addr list.
162fn update_interfaces(
163    index: u32,
164    name: String,
165    addr: Addr,
166    flags: Flags,
167    interfaces: &mut BTreeMap<u32, NetworkInterface>,
168) {
169    interfaces
170        .entry(index)
171        .and_modify(|i| {
172            i.addr.push(addr);
173        })
174        .or_insert(NetworkInterface {
175            index,
176            name,
177            addr: vec![addr],
178            mac_addr: None,
179            flags,
180        });
181}
182
183/// Inserts a new NetworkInterface into the BTreeMap
184/// or updates the mac address for the given NetworkInterface.
185fn update_interfaces_with_mac(
186    index: u32,
187    name: String,
188    mac_addr: Option<String>,
189    flags: Flags,
190    interfaces: &mut BTreeMap<u32, NetworkInterface>,
191) {
192    interfaces
193        .entry(index)
194        .and_modify(|i| {
195            i.mac_addr = mac_addr.clone();
196        })
197        .or_insert(NetworkInterface {
198            index,
199            name,
200            addr: Vec::new(),
201            mac_addr,
202            flags,
203        });
204}
205
206/// Get the network interfaces
207pub fn get_network_interfaces() -> Result<Vec<NetworkInterface>, Error> {
208    let mut ifaddr_ptr: *mut ifaddrs = ptr::null_mut();
209
210    unsafe {
211        // Retrieve the linked list of interfaces
212        if getifaddrs(&mut ifaddr_ptr) != 0 {
213            return Err(Error::new(
214                ErrorKind::Other,
215                "Failed to get network interfaces",
216            ));
217        }
218    }
219
220    let mut interfaces: BTreeMap<u32, NetworkInterface> = BTreeMap::new();
221
222    let mut current_ptr = ifaddr_ptr;
223    while let Some(ifa) = unsafe { current_ptr.as_ref() } {
224        // Extract the interface name
225        let name = unsafe { CStr::from_ptr(ifa.ifa_name).to_string_lossy() };
226
227        // Extract interface index
228        let index = unsafe { if_nametoindex(ifa.ifa_name) };
229        if index == 0 {
230            // Returns 0 on failure (e.g., interface no longer exists)
231            eprint!("Interface no longer exists: {name}");
232        }
233
234        // Extract interface flags
235        let flags = ifa.ifa_flags;
236        let flags = Flags {
237            up: (flags as i32 & IFF_UP) != 0,
238            loopback: (flags as i32 & IFF_LOOPBACK) != 0,
239            running: (flags as i32 & IFF_RUNNING) != 0,
240            multicast: (flags as i32 & IFF_MULTICAST) != 0,
241            broadcast: (flags as i32 & IFF_BROADCAST) != 0,
242        };
243
244        // Process the address if it exists
245        let Some(ifa_addr) = (unsafe { ifa.ifa_addr.as_ref() }) else {
246            current_ptr = ifa.ifa_next;
247            continue;
248        };
249        let family = ifa_addr.sa_family as i32;
250
251        match family {
252            AF_INET => {
253                let if_addr_v4 = get_if_addr_v4(ifa, &flags);
254                let addr = Addr::IPv4(if_addr_v4);
255                update_interfaces(index, name.into_owned(), addr, flags, &mut interfaces);
256            }
257            AF_INET6 => {
258                let if_addr_v6 = get_if_addr_v6(ifa);
259                let addr = Addr::IPv6(if_addr_v6);
260                update_interfaces(index, name.into_owned(), addr, flags, &mut interfaces);
261            }
262            family => {
263                let mac_addr = get_mac_addr(ifa, family);
264                update_interfaces_with_mac(
265                    index,
266                    name.into_owned(),
267                    mac_addr,
268                    flags,
269                    &mut interfaces,
270                );
271            }
272        }
273
274        current_ptr = ifa.ifa_next;
275    }
276
277    unsafe {
278        freeifaddrs(ifaddr_ptr);
279    }
280
281    Ok(interfaces.into_values().collect())
282}
283
284/// Get the hostname.
285pub fn get_hostname() -> Result<OsString, Error> {
286    let mut buf: Vec<u8> = Vec::with_capacity(256);
287    let ptr = buf.as_mut_ptr().cast();
288    let len = buf.capacity() as size_t;
289
290    let res = unsafe { libc::gethostname(ptr, len) };
291    if res != 0 {
292        return Err(Error::new(ErrorKind::Other, "Failed to get hostname"));
293    }
294    unsafe {
295        buf.as_mut_ptr().wrapping_add(len - 1).write(0);
296        let len = CStr::from_ptr(buf.as_ptr().cast()).count_bytes();
297        buf.set_len(len);
298    }
299    Ok(OsString::from_vec(buf))
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_get_network_interfaces() {
308        let interfaces = get_network_interfaces().expect("Should give network interfaces");
309        println!("Interfaces: {interfaces:#?}");
310        assert!(interfaces.len() > 0);
311        assert!(interfaces[0].name.starts_with("lo"));
312        assert!(interfaces[0].addr.len() > 0);
313    }
314
315    #[test]
316    fn test_get_hostname() {
317        let hostname = get_hostname().expect("Should give hostname");
318        println!("hostname: {hostname:#?}");
319        assert!(hostname.len() > 0);
320    }
321}