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#[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 get_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 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 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 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> {
208 let mut ifaddr_ptr: *mut ifaddrs = ptr::null_mut();
209
210 unsafe {
211 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 let name = unsafe { CStr::from_ptr(ifa.ifa_name).to_string_lossy() };
226
227 let index = unsafe { if_nametoindex(ifa.ifa_name) };
229 if index == 0 {
230 eprint!("Interface no longer exists: {name}");
232 }
233
234 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 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
284pub 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}