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#[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
72fn if_addr_v4(ifa: &ifaddrs, flags: &Flags) -> IfAddrV4 {
73 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 = Some(Ipv4Addr::from(mask.sin_addr.s_addr.to_be()));
79 }
80
81 #[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 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 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 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 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 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
160fn 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
183fn 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
206pub fn get_network_interfaces() -> Result<Vec<NetworkInterface>, Error> {
209 network_interfaces()
210}
211
212pub fn network_interfaces() -> Result<Vec<NetworkInterface>, Error> {
214 let mut ifaddr_ptr: *mut ifaddrs = ptr::null_mut();
215
216 unsafe {
217 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 let name = unsafe { CStr::from_ptr(ifa.ifa_name).to_string_lossy() };
232
233 let index = unsafe { if_nametoindex(ifa.ifa_name) };
235 if index == 0 {
236 eprint!("Interface no longer exists: {name}");
238 }
239
240 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 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
290pub 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
307pub 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
324pub fn get_hostname() -> Result<OsString, Error> {
327 hostname()
328}
329
330pub 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}