1use crate::Error;
2use libc::size_t;
3use libc::{
4 freeifaddrs, getifaddrs, if_nametoindex, ifaddrs, sockaddr_in, sockaddr_in6, AF_INET, AF_INET6,
5 IFF_BROADCAST, IFF_LOOPBACK, IFF_MULTICAST, IFF_RUNNING, IFF_UP,
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#[derive(Clone, PartialEq, Eq, Hash, Debug)]
15pub struct NetworkInterface {
16 pub index: u32,
18 pub name: String, pub addr: Vec<Addr>,
22 pub mac_addr: Option<String>,
24 pub flags: Flags,
26}
27
28#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
30pub struct Flags {
31 pub up: bool,
33 pub loopback: bool,
35 pub running: bool,
37 pub multicast: bool,
39 pub broadcast: bool,
41}
42
43#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
45pub enum Addr {
46 IPv4(IfAddrV4),
48 IPv6(IfAddrV6),
50}
51
52#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
54pub struct IfAddrV4 {
55 pub ip: Ipv4Addr,
57 pub netmask: Option<Ipv4Addr>,
59 pub broadcast: Option<Ipv4Addr>,
61}
62
63#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
65pub struct IfAddrV6 {
66 pub ip: Ipv6Addr,
68 pub netmask: Option<Ipv6Addr>,
70}
71
72impl IfAddrV6 {
73 fn is_unicast_link_local(&self) -> bool {
74 (self.ip.segments()[0] & 0xffc0) == 0xfe80
78 }
79}
80
81fn if_addr_v4(ifa: &ifaddrs, flags: &Flags) -> IfAddrV4 {
82 let mut netmask: Option<Ipv4Addr> = None;
84 if !ifa.ifa_netmask.is_null() {
85 let mask = unsafe { *(ifa.ifa_netmask as *const sockaddr_in) };
86 netmask = Some(Ipv4Addr::from(mask.sin_addr.s_addr.to_be()));
88 }
89
90 #[cfg(any(target_os = "linux", target_os = "android"))]
94 let broad_ptr = ifa.ifa_ifu;
95
96 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
97 let broad_ptr = ifa.ifa_dstaddr;
98
99 let mut broadcast: Option<Ipv4Addr> = None;
100 if !broad_ptr.is_null() && flags.broadcast {
102 let sa = unsafe { *(broad_ptr as *const sockaddr_in) };
103 broadcast = Some(Ipv4Addr::from(u32::from_be(sa.sin_addr.s_addr)));
104 }
105
106 let socket_addr = unsafe { *(ifa.ifa_addr as *const sockaddr_in) };
108 let ip = Ipv4Addr::from(socket_addr.sin_addr.s_addr.to_be());
109
110 IfAddrV4 {
111 ip,
112 netmask,
113 broadcast,
114 }
115}
116
117fn if_addr_v6(ifa: &ifaddrs) -> IfAddrV6 {
118 let mut netmask: Option<Ipv6Addr> = None;
119 if !ifa.ifa_netmask.is_null() {
120 let mask = unsafe { *(ifa.ifa_netmask as *const sockaddr_in6) };
121 netmask = Some(Ipv6Addr::from(mask.sin6_addr.s6_addr));
123 }
124
125 let socket_addr = unsafe { *(ifa.ifa_addr as *const sockaddr_in6) };
126 let ip = Ipv6Addr::from(socket_addr.sin6_addr.s6_addr);
127
128 IfAddrV6 { ip, netmask }
129}
130
131fn mac_to_string(mac: &[u8]) -> String {
132 let mac_addr = format!(
133 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
134 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
135 );
136 mac_addr
137}
138
139fn mac_addr(ifa: &ifaddrs, family: i32) -> Option<String> {
140 #[cfg(any(target_os = "linux", target_os = "android"))]
141 if family == libc::AF_PACKET {
142 let sll = unsafe { *(ifa.ifa_addr as *const libc::sockaddr_ll) };
143 let mac = sll.sll_addr;
144 let len = sll.sll_halen as usize;
145
146 if len == 6 {
148 return Some(mac_to_string(&mac));
149 }
150 }
151
152 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
153 if family == libc::AF_LINK {
154 let sdl = unsafe { *(ifa.ifa_addr as *const libc::sockaddr_dl) };
155
156 let mac_ptr = unsafe { (&sdl as *const _ as *const u8).add(8 + sdl.sdl_nlen as usize) };
159
160 if sdl.sdl_alen == 6 {
161 let mac = unsafe { std::slice::from_raw_parts(mac_ptr, 6) };
162 return Some(mac_to_string(mac));
163 }
164 }
165
166 None
167}
168
169fn update_interfaces(
172 index: u32,
173 name: String,
174 addr: Addr,
175 flags: Flags,
176 interfaces: &mut BTreeMap<u32, NetworkInterface>,
177) {
178 interfaces
179 .entry(index)
180 .and_modify(|i| {
181 i.addr.push(addr);
182 })
183 .or_insert(NetworkInterface {
184 index,
185 name,
186 addr: vec![addr],
187 mac_addr: None,
188 flags,
189 });
190}
191
192fn update_interfaces_with_mac(
195 index: u32,
196 name: String,
197 mac_addr: Option<String>,
198 flags: Flags,
199 interfaces: &mut BTreeMap<u32, NetworkInterface>,
200) {
201 interfaces
202 .entry(index)
203 .and_modify(|i| {
204 i.mac_addr = mac_addr.clone();
205 })
206 .or_insert(NetworkInterface {
207 index,
208 name,
209 addr: Vec::new(),
210 mac_addr,
211 flags,
212 });
213}
214
215pub fn get_network_interfaces() -> Result<Vec<NetworkInterface>, Error> {
218 network_interfaces()
219}
220
221pub fn network_interfaces() -> Result<Vec<NetworkInterface>, Error> {
223 let mut ifaddr_ptr: *mut ifaddrs = ptr::null_mut();
224
225 unsafe {
226 let res = getifaddrs(&mut ifaddr_ptr);
228 if res != 0 {
229 return Err(Error::FailedToGetResource(format!(
230 "getifaddrs returned {res}"
231 )));
232 }
233 }
234
235 let mut interfaces: BTreeMap<u32, NetworkInterface> = BTreeMap::new();
236
237 let mut current_ptr = ifaddr_ptr;
238 while let Some(ifa) = unsafe { current_ptr.as_ref() } {
239 let name = unsafe { CStr::from_ptr(ifa.ifa_name).to_string_lossy() };
241
242 let index = unsafe { if_nametoindex(ifa.ifa_name) };
244 if index == 0 {
245 eprint!("Interface no longer exists: {name}");
247 }
248
249 let flags = ifa.ifa_flags;
251 let flags = Flags {
252 up: (flags as i32 & IFF_UP) != 0,
253 loopback: (flags as i32 & IFF_LOOPBACK) != 0,
254 running: (flags as i32 & IFF_RUNNING) != 0,
255 multicast: (flags as i32 & IFF_MULTICAST) != 0,
256 broadcast: (flags as i32 & IFF_BROADCAST) != 0,
257 };
258
259 let ifa_addr = match unsafe { ifa.ifa_addr.as_ref() } {
261 Some(ifa_addr) => ifa_addr,
262 None => {
263 current_ptr = ifa.ifa_next;
264 continue;
265 }
266 };
267 let family = ifa_addr.sa_family as i32;
268
269 match family {
270 AF_INET => {
271 let if_addr_v4 = if_addr_v4(ifa, &flags);
272 let addr = Addr::IPv4(if_addr_v4);
273 update_interfaces(index, name.into_owned(), addr, flags, &mut interfaces);
274 }
275 AF_INET6 => {
276 let if_addr_v6 = if_addr_v6(ifa);
277 let addr = Addr::IPv6(if_addr_v6);
278 update_interfaces(index, name.into_owned(), addr, flags, &mut interfaces);
279 }
280 family => {
281 let mac_addr = mac_addr(ifa, family);
282 update_interfaces_with_mac(
283 index,
284 name.into_owned(),
285 mac_addr,
286 flags,
287 &mut interfaces,
288 );
289 }
290 }
291
292 current_ptr = ifa.ifa_next;
293 }
294
295 unsafe {
296 freeifaddrs(ifaddr_ptr);
297 }
298
299 Ok(interfaces.into_values().collect())
300}
301
302pub fn local_ipv4_addresses() -> Result<Vec<Ipv4Addr>, Error> {
304 Ok(network_interfaces()?
305 .into_iter()
306 .filter_map(|ni| {
307 if !ni.flags.loopback {
308 ni.addr.into_iter().find_map(|addr| match addr {
309 Addr::IPv4(addr) => Some(addr.ip),
310 _ => None,
311 })
312 } else {
313 None
314 }
315 })
316 .collect())
317}
318
319pub fn local_ipv6_addresses() -> Result<Vec<Ipv6Addr>, Error> {
321 Ok(network_interfaces()?
322 .into_iter()
323 .filter_map(|ni| {
324 if !ni.flags.loopback {
325 ni.addr.into_iter().find_map(|addr| match addr {
326 Addr::IPv6(addr) if !addr.is_unicast_link_local() => Some(addr.ip),
327 _ => None,
328 })
329 } else {
330 None
331 }
332 })
333 .collect())
334}
335
336pub fn get_hostname() -> Result<OsString, Error> {
339 hostname()
340}
341
342pub fn hostname() -> Result<OsString, Error> {
344 let mut buf: Vec<u8> = Vec::with_capacity(256);
345 let ptr = buf.as_mut_ptr().cast();
346 let len = buf.capacity() as size_t;
347
348 let res = unsafe { libc::gethostname(ptr, len) };
349 if res != 0 {
350 return Err(Error::FailedToGetResource(format!(
351 "gethostname returned {res}"
352 )));
353 }
354 unsafe {
355 buf.as_mut_ptr().wrapping_add(len - 1).write(0);
356 let len = CStr::from_ptr(buf.as_ptr().cast()).to_bytes().len();
358 buf.set_len(len);
359 }
360 Ok(OsString::from_vec(buf))
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_network_interfaces() {
369 let interfaces = network_interfaces().expect("Failed to get network interfaces");
370 println!("Interfaces: {interfaces:#?}");
371 assert!(interfaces.len() > 0);
372 assert!(interfaces[0].name.starts_with("lo"));
373 assert!(interfaces[0].addr.len() > 0);
374 }
375
376 #[test]
377 fn test_local_ipv4_addresses() {
378 let addresses = local_ipv4_addresses().expect("Failed to get IPv4 addresses");
379 assert!(addresses.len() >= 1);
380 }
381
382 #[test]
383 fn test_local_ipv6_addresses() {
384 let addresses = local_ipv6_addresses();
385 assert!(addresses.is_ok());
386 }
387
388 #[test]
389 fn test_hostname() {
390 let hostname = hostname().expect("Failed to get hostname");
391 println!("hostname: {hostname:#?}");
392 assert!(hostname.len() > 0);
393 }
394}