1use std::net::IpAddr;
6
7use crate::error::IoError;
8
9#[derive(Debug, Clone)]
11pub struct Interface {
12 pub name: String,
14 pub friendly_name: Option<String>,
16 pub mac_address: Option<[u8; 6]>,
18 pub ips: Vec<IpAddr>,
20 pub is_loopback: bool,
22 pub is_up: bool,
24}
25
26pub fn list_interfaces() -> Result<Vec<Interface>, IoError> {
28 platform_list_interfaces()
29}
30
31#[cfg(target_os = "linux")]
36fn platform_list_interfaces() -> Result<Vec<Interface>, IoError> {
37 use std::os::fd::AsRawFd;
38 use socket2::{Domain, Socket, Type};
39
40 let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
41 let fd = sock.as_raw_fd();
42
43 let mut ifc: libc::ifconf = unsafe { std::mem::zeroed() };
45 let buf_size = 4096usize;
46 let mut buf = vec![0u8; buf_size];
47 ifc.ifc_len = buf_size as i32;
48 ifc.ifc_ifcu.ifcu_buf = buf.as_mut_ptr() as *mut libc::c_char;
49
50 if unsafe { libc::ioctl(fd, libc::SIOCGIFCONF, &mut ifc) } < 0 {
51 return Err(IoError::Socket(std::io::Error::last_os_error()));
52 }
53
54 let mut interfaces = Vec::new();
55 let mut pos = 0usize;
56 while pos < ifc.ifc_len as usize {
57 let ifr: &libc::ifreq = unsafe { &*(ifc.ifc_ifcu.ifcu_buf.add(pos) as *const libc::ifreq) };
58 pos += std::mem::size_of::<libc::ifreq>();
59 let name = cstr_to_string(&ifr.ifr_name);
60
61 let mut ifr_flags = unsafe { std::mem::zeroed::<libc::ifreq>() };
62 copy_ifname(&mut ifr_flags, &name);
63 let is_up = unsafe { libc::ioctl(fd, libc::SIOCGIFFLAGS, &mut ifr_flags) } >= 0
64 && unsafe { ifr_flags.ifr_ifru.ifru_flags & libc::IFF_UP as i16 } != 0;
65 let is_loopback = unsafe { libc::ioctl(fd, libc::SIOCGIFFLAGS, &mut ifr_flags) } >= 0
66 && unsafe { ifr_flags.ifr_ifru.ifru_flags & libc::IFF_LOOPBACK as i16 } != 0;
67
68 let mut ifr_hw = unsafe { std::mem::zeroed::<libc::ifreq>() };
70 copy_ifname(&mut ifr_hw, &name);
71 let mac = if unsafe { libc::ioctl(fd, libc::SIOCGIFHWADDR, &mut ifr_hw) } >= 0 {
72 let sa = unsafe { &ifr_hw.ifr_ifru.ifru_hwaddr };
73 Some([sa.sa_data[0] as u8, sa.sa_data[1] as u8, sa.sa_data[2] as u8,
74 sa.sa_data[3] as u8, sa.sa_data[4] as u8, sa.sa_data[5] as u8])
75 } else { None };
76
77 let mut ifr_addr = unsafe { std::mem::zeroed::<libc::ifreq>() };
79 copy_ifname(&mut ifr_addr, &name);
80 let ips = if unsafe { libc::ioctl(fd, libc::SIOCGIFADDR, &mut ifr_addr) } >= 0 {
81 let sa = unsafe { &ifr_addr.ifr_ifru.ifru_addr };
82 if sa.sa_family as i32 == libc::AF_INET {
83 let addr_bytes = unsafe { (*(sa as *const libc::sockaddr as *const libc::sockaddr_in)).sin_addr.s_addr };
84 let ip = IpAddr::V4(std::net::Ipv4Addr::from(u32::from_be(addr_bytes)));
85 vec![ip]
86 } else { vec![] }
87 } else { vec![] };
88
89 interfaces.push(Interface { name, friendly_name: None, mac_address: mac, ips, is_loopback, is_up });
90 }
91 Ok(interfaces)
92}
93
94#[cfg(target_os = "linux")]
95fn cstr_to_string(cstr: &[i8]) -> String {
96 let bytes: Vec<u8> = cstr.iter().take_while(|&&b| b != 0).map(|&b| b as u8).collect();
97 String::from_utf8_lossy(&bytes).into_owned()
98}
99
100#[cfg(target_os = "linux")]
101fn copy_ifname(ifr: &mut libc::ifreq, name: &str) {
102 let bytes = name.as_bytes();
103 let len = bytes.len().min(ifr.ifr_name.len() - 1);
104 ifr.ifr_name[..len].copy_from_slice(unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const i8, len) });
105}
106
107#[cfg(target_os = "macos")]
112fn platform_list_interfaces() -> Result<Vec<Interface>, IoError> {
113 use std::collections::HashMap;
114 use std::net::{Ipv4Addr, Ipv6Addr};
115
116 let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
117 if unsafe { libc::getifaddrs(&mut ifap) } != 0 {
118 return Err(IoError::Socket(std::io::Error::last_os_error()));
119 }
120
121 struct IfInfo {
122 name: String,
123 mac: Option<[u8; 6]>,
124 is_loopback: bool,
125 is_up: bool,
126 ips: Vec<IpAddr>,
127 }
128 let mut map: HashMap<String, IfInfo> = HashMap::new();
129
130 let mut cur = ifap;
131 while !cur.is_null() {
132 let ifa = unsafe { &*cur };
133 let name = cstr_to_string_osx(ifa.ifa_name);
134 let entry = map.entry(name.clone()).or_insert_with(|| IfInfo {
135 name,
136 mac: None,
137 is_loopback: (ifa.ifa_flags & libc::IFF_LOOPBACK as u32) != 0,
138 is_up: (ifa.ifa_flags & libc::IFF_UP as u32) != 0,
139 ips: vec![],
140 });
141
142 if !ifa.ifa_addr.is_null() {
143 let sa = unsafe { &*ifa.ifa_addr };
144 match sa.sa_family as i32 {
145 libc::AF_LINK => {
146 let sdl = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_dl) };
147 if sdl.sdl_alen == 6 {
148 let mac_addr = unsafe { *(sdl.sdl_data.as_ptr().offset(sdl.sdl_nlen as isize) as *const [u8; 6]) };
149 entry.mac = Some(mac_addr);
150 }
151 }
152 libc::AF_INET => {
153 let sin = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_in) };
154 let addr = Ipv4Addr::from(u32::from_be(sin.sin_addr.s_addr));
155 entry.ips.push(IpAddr::V4(addr));
156 }
157 libc::AF_INET6 => {
158 let sin6 = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_in6) };
159 let addr = Ipv6Addr::from(sin6.sin6_addr.s6_addr);
160 entry.ips.push(IpAddr::V6(addr));
161 }
162 _ => {}
163 }
164 }
165 cur = ifa.ifa_next;
166 }
167 unsafe { libc::freeifaddrs(ifap); }
168
169 Ok(map.into_values().map(|info| Interface {
170 name: info.name,
171 friendly_name: None,
172 mac_address: info.mac,
173 ips: info.ips,
174 is_loopback: info.is_loopback,
175 is_up: info.is_up,
176 }).collect())
177}
178
179#[cfg(target_os = "macos")]
180fn cstr_to_string_osx(ptr: *const libc::c_char) -> String {
181 if ptr.is_null() { return String::new(); }
182 let bytes = unsafe { std::ffi::CStr::from_ptr(ptr).to_bytes() };
183 String::from_utf8_lossy(bytes).into_owned()
184}
185
186#[cfg(target_os = "windows")]
191fn platform_list_interfaces() -> Result<Vec<Interface>, IoError> {
192 use std::net::{Ipv4Addr, Ipv6Addr};
193
194 const AF_UNSPEC: u32 = 0;
195 const GAA_FLAG_INCLUDE_PREFIX: u32 = 0x0010;
196 const IF_TYPE_SOFTWARE_LOOPBACK: u32 = 24;
197 const IF_OPER_STATUS_UP: u32 = 1;
198
199 #[repr(C)]
200 struct IpAdapterUnicastAddress {
201 length: u32,
202 _flags: u32,
203 next: *mut IpAdapterUnicastAddress,
204 sockaddr_ptr: *const u8, sockaddr_len: i32,
206 }
207
208 #[repr(C)]
209 struct IpAdapterAddresses {
210 length: u32,
211 if_index: u32,
212 next: *mut IpAdapterAddresses,
213 adapter_name: *const u8,
214 first_unicast_address: *mut IpAdapterUnicastAddress,
215 _pad1: [u8; 48],
217 physical_address: [u8; 8],
218 physical_address_length: u32,
219 flags: u32,
220 mtu: u32,
221 if_type: u32,
222 oper_status: u32,
223 }
224
225 extern "system" {
226 fn GetAdaptersAddresses(
227 family: u32,
228 flags: u32,
229 reserved: *const std::ffi::c_void,
230 addresses: *mut IpAdapterAddresses,
231 size: *mut u32,
232 ) -> u32;
233 }
234
235 let mut size: u32 = 0;
237 let ret = unsafe { GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, std::ptr::null(), std::ptr::null_mut(), &mut size) };
238 if ret != 111 { return Ok(vec![]); } let mut buf = vec![0u8; size as usize];
241 let ret = unsafe { GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, std::ptr::null(), buf.as_mut_ptr() as *mut IpAdapterAddresses, &mut size) };
242 if ret != 0 { return Ok(vec![]); }
243
244 let mut interfaces = Vec::new();
245 let mut cur = buf.as_ptr() as *const IpAdapterAddresses;
246
247 while !cur.is_null() {
248 let a = unsafe { &*cur };
249
250 let name = wide_to_string(a.adapter_name);
251 let friendly_name = None; let mac = if a.physical_address_length == 6 {
254 Some([a.physical_address[0], a.physical_address[1], a.physical_address[2],
255 a.physical_address[3], a.physical_address[4], a.physical_address[5]])
256 } else { None };
257
258 let mut ips = Vec::new();
259 let mut ua = a.first_unicast_address;
260 while !ua.is_null() {
261 let u = unsafe { &*ua };
262 if !u.sockaddr_ptr.is_null() {
263 let family = unsafe { *(u.sockaddr_ptr as *const u16) };
264 match family {
265 2 => { let b = unsafe { std::slice::from_raw_parts(u.sockaddr_ptr.offset(4), 4) };
267 ips.push(IpAddr::V4(Ipv4Addr::new(b[0], b[1], b[2], b[3])));
268 }
269 23 => { let b = unsafe { std::slice::from_raw_parts(u.sockaddr_ptr.offset(8), 16) };
271 let mut arr = [0u8; 16];
272 arr.copy_from_slice(b);
273 ips.push(IpAddr::V6(Ipv6Addr::from(arr)));
274 }
275 _ => {}
276 }
277 }
278 ua = u.next;
279 }
280
281 interfaces.push(Interface {
282 name,
283 friendly_name,
284 mac_address: mac,
285 ips,
286 is_loopback: a.if_type == IF_TYPE_SOFTWARE_LOOPBACK,
287 is_up: a.oper_status == IF_OPER_STATUS_UP,
288 });
289
290 cur = a.next;
291 }
292
293 Ok(interfaces)
294}
295
296#[cfg(target_os = "windows")]
297fn wide_to_string(ptr: *const u8) -> String {
298 if ptr.is_null() { return String::new(); }
299 let len = unsafe {
300 let mut end = ptr;
301 loop {
302 let w = u16::from_le_bytes([*end, *(end.offset(1))]);
303 if w == 0 { break; }
304 end = end.offset(2);
305 }
306 (end as usize - ptr as usize) / 2
307 };
308 let wide: Vec<u16> = (0..len).map(|i| unsafe {
309 u16::from_le_bytes([*ptr.add(i*2), *ptr.add(i*2+1)])
310 }).collect();
311 String::from_utf16_lossy(&wide)
312}
313
314#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn list_interfaces_returns_something() {
324 let ifaces = list_interfaces().expect("list_interfaces should succeed");
325 assert!(!ifaces.is_empty(), "should return at least one interface");
326 }
327
328 #[test]
329 fn list_interfaces_has_loopback() {
330 let ifaces = list_interfaces().expect("list_interfaces should succeed");
331 assert!(ifaces.iter().any(|i| i.is_loopback), "should contain loopback");
332 }
333}