os_interface/
network.rs

1use crate::Error;
2use libc::size_t;
3use libc::{
4    AF_INET, AF_INET6, IFF_BROADCAST, IFF_LOOPBACK, IFF_MULTICAST, IFF_RUNNING, IFF_UP,
5    freeifaddrs, getifaddrs, if_nametoindex, ifaddrs, sockaddr_in, sockaddr_in6,
6};
7use std::collections::BTreeMap;
8use std::ffi::{CStr, OsString};
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 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 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 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/// This function exist for backward compatibility.
207/// Use network_interfaces() instead.
208pub fn get_network_interfaces() -> Result<Vec<NetworkInterface>, Error> {
209    network_interfaces()
210}
211
212/// Get all the network interfaces.
213pub fn network_interfaces() -> Result<Vec<NetworkInterface>, Error> {
214    let mut ifaddr_ptr: *mut ifaddrs = ptr::null_mut();
215
216    unsafe {
217        // Retrieve the linked list of interfaces
218        let res = getifaddrs(&mut ifaddr_ptr);
219        if res != 0 {
220            return Err(Error::FailedToGetResource(format!(
221                "getifaddrs returned {res}"
222            )));
223        }
224    }
225
226    let mut interfaces: BTreeMap<u32, NetworkInterface> = BTreeMap::new();
227
228    let mut current_ptr = ifaddr_ptr;
229    while let Some(ifa) = unsafe { current_ptr.as_ref() } {
230        // Extract the interface name
231        let name = unsafe { CStr::from_ptr(ifa.ifa_name).to_string_lossy() };
232
233        // Extract interface index
234        let index = unsafe { if_nametoindex(ifa.ifa_name) };
235        if index == 0 {
236            // Returns 0 on failure (e.g., interface no longer exists)
237            eprint!("Interface no longer exists: {name}");
238        }
239
240        // Extract interface flags
241        let flags = ifa.ifa_flags;
242        let flags = Flags {
243            up: (flags as i32 & IFF_UP) != 0,
244            loopback: (flags as i32 & IFF_LOOPBACK) != 0,
245            running: (flags as i32 & IFF_RUNNING) != 0,
246            multicast: (flags as i32 & IFF_MULTICAST) != 0,
247            broadcast: (flags as i32 & IFF_BROADCAST) != 0,
248        };
249
250        // Process the address if it exists
251        let Some(ifa_addr) = (unsafe { ifa.ifa_addr.as_ref() }) else {
252            current_ptr = ifa.ifa_next;
253            continue;
254        };
255        let family = ifa_addr.sa_family as i32;
256
257        match family {
258            AF_INET => {
259                let if_addr_v4 = if_addr_v4(ifa, &flags);
260                let addr = Addr::IPv4(if_addr_v4);
261                update_interfaces(index, name.into_owned(), addr, flags, &mut interfaces);
262            }
263            AF_INET6 => {
264                let if_addr_v6 = if_addr_v6(ifa);
265                let addr = Addr::IPv6(if_addr_v6);
266                update_interfaces(index, name.into_owned(), addr, flags, &mut interfaces);
267            }
268            family => {
269                let mac_addr = mac_addr(ifa, family);
270                update_interfaces_with_mac(
271                    index,
272                    name.into_owned(),
273                    mac_addr,
274                    flags,
275                    &mut interfaces,
276                );
277            }
278        }
279
280        current_ptr = ifa.ifa_next;
281    }
282
283    unsafe {
284        freeifaddrs(ifaddr_ptr);
285    }
286
287    Ok(interfaces.into_values().collect())
288}
289
290/// Gets all local IPv4 addresses that are not loopback.
291pub fn local_ipv4_addresses() -> Result<Vec<Ipv4Addr>, Error> {
292    Ok(network_interfaces()?
293        .into_iter()
294        .filter_map(|ni| {
295            if !ni.flags.loopback {
296                ni.addr.into_iter().find_map(|addr| match addr {
297                    Addr::IPv4(addr) => Some(addr.ip),
298                    _ => None,
299                })
300            } else {
301                None
302            }
303        })
304        .collect())
305}
306
307/// Gets all local IPv6 addresses that are not loopback or unicast link local.
308pub fn local_ipv6_addresses() -> Result<Vec<Ipv6Addr>, Error> {
309    Ok(network_interfaces()?
310        .into_iter()
311        .filter_map(|ni| {
312            if !ni.flags.loopback {
313                ni.addr.into_iter().find_map(|addr| match addr {
314                    Addr::IPv6(addr) if !addr.ip.is_unicast_link_local() => Some(addr.ip),
315                    _ => None,
316                })
317            } else {
318                None
319            }
320        })
321        .collect())
322}
323
324/// This function exist for backward compatibility.
325/// Use hostname() instead.
326pub fn get_hostname() -> Result<OsString, Error> {
327    hostname()
328}
329
330/// Get the hostname.
331pub fn hostname() -> Result<OsString, Error> {
332    let mut buf: Vec<u8> = Vec::with_capacity(256);
333    let ptr = buf.as_mut_ptr().cast();
334    let len = buf.capacity() as size_t;
335
336    let res = unsafe { libc::gethostname(ptr, len) };
337    if res != 0 {
338        return Err(Error::FailedToGetResource(format!(
339            "gethostname returned {res}"
340        )));
341    }
342    unsafe {
343        buf.as_mut_ptr().wrapping_add(len - 1).write(0);
344        let len = CStr::from_ptr(buf.as_ptr().cast()).count_bytes();
345        buf.set_len(len);
346    }
347    Ok(OsString::from_vec(buf))
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    #[test]
355    fn test_network_interfaces() {
356        let interfaces = network_interfaces().expect("Failed to get network interfaces");
357        println!("Interfaces: {interfaces:#?}");
358        assert!(interfaces.len() > 0);
359        assert!(interfaces[0].name.starts_with("lo"));
360        assert!(interfaces[0].addr.len() > 0);
361    }
362
363    #[test]
364    fn test_local_ipv4_addresses() {
365        let addresses = local_ipv4_addresses().expect("Failed to get IPv4 addresses");
366        assert!(addresses.len() >= 1);
367    }
368
369    #[test]
370    fn test_local_ipv6_addresses() {
371        let addresses = local_ipv6_addresses();
372        assert!(addresses.is_ok());
373    }
374
375    #[test]
376    fn test_hostname() {
377        let hostname = hostname().expect("Failed to get hostname");
378        println!("hostname: {hostname:#?}");
379        assert!(hostname.len() > 0);
380    }
381}