@@ -4,7 +4,7 @@ use std::fs;
44use std:: net:: { IpAddr , Ipv4Addr , Ipv6Addr } ;
55use std:: path:: Path ;
66
7- use super :: { ConnectionState , NetstatEntry , SocketType } ;
7+ use super :: { ConnectionState , InterfaceEntry , NetstatEntry , SocketType } ;
88
99pub fn netstat ( ) -> Result < Vec < NetstatEntry > > {
1010 let mut entries = Vec :: new ( ) ;
@@ -294,6 +294,82 @@ fn read_process_name(pid: u32) -> Result<String> {
294294 ) )
295295}
296296
297+ struct IfaceData {
298+ mac : [ u8 ; 6 ] ,
299+ ipv4 : Option < IpAddr > ,
300+ ipv6 : Option < IpAddr > ,
301+ }
302+
303+ pub fn list_interfaces ( ) -> Result < Vec < InterfaceEntry > > {
304+ let mut ifaddrs_ptr: * mut libc:: ifaddrs = std:: ptr:: null_mut ( ) ;
305+
306+ unsafe {
307+ if libc:: getifaddrs ( & mut ifaddrs_ptr) != 0 {
308+ return Err ( anyhow:: anyhow!(
309+ "getifaddrs failed: {}" ,
310+ std:: io:: Error :: last_os_error( )
311+ ) ) ;
312+ }
313+ }
314+
315+ let mut ifaces: HashMap < String , IfaceData > = HashMap :: new ( ) ;
316+
317+ unsafe {
318+ let mut cur = ifaddrs_ptr;
319+ while !cur. is_null ( ) {
320+ let ifa = & * cur;
321+
322+ if !ifa. ifa_addr . is_null ( ) {
323+ let name = std:: ffi:: CStr :: from_ptr ( ifa. ifa_name )
324+ . to_string_lossy ( )
325+ . to_string ( ) ;
326+ let family = ( * ifa. ifa_addr ) . sa_family as i32 ;
327+ let entry = ifaces. entry ( name) . or_insert ( IfaceData {
328+ mac : [ 0u8 ; 6 ] ,
329+ ipv4 : None ,
330+ ipv6 : None ,
331+ } ) ;
332+
333+ match family {
334+ libc:: AF_PACKET => {
335+ let sll = & * ( ifa. ifa_addr as * const libc:: sockaddr_ll ) ;
336+ if sll. sll_halen == 6 {
337+ entry. mac . copy_from_slice ( & sll. sll_addr [ ..6 ] ) ;
338+ }
339+ }
340+ libc:: AF_INET => {
341+ if entry. ipv4 . is_none ( ) {
342+ let sin = & * ( ifa. ifa_addr as * const libc:: sockaddr_in ) ;
343+ let bytes = sin. sin_addr . s_addr . to_ne_bytes ( ) ;
344+ entry. ipv4 = Some ( IpAddr :: V4 ( Ipv4Addr :: from ( bytes) ) ) ;
345+ }
346+ }
347+ libc:: AF_INET6 => {
348+ if entry. ipv6 . is_none ( ) {
349+ let sin6 = & * ( ifa. ifa_addr as * const libc:: sockaddr_in6 ) ;
350+ entry. ipv6 = Some ( IpAddr :: V6 ( Ipv6Addr :: from ( sin6. sin6_addr . s6_addr ) ) ) ;
351+ }
352+ }
353+ _ => { }
354+ }
355+ }
356+
357+ cur = ifa. ifa_next ;
358+ }
359+
360+ libc:: freeifaddrs ( ifaddrs_ptr) ;
361+ }
362+
363+ Ok ( ifaces
364+ . into_iter ( )
365+ . map ( |( name, data) | InterfaceEntry {
366+ iface_name : name,
367+ mac_address : data. mac ,
368+ ip_address : data. ipv4 . or ( data. ipv6 ) ,
369+ } )
370+ . collect ( ) )
371+ }
372+
297373#[ cfg( test) ]
298374mod tests {
299375 use super :: * ;
@@ -389,4 +465,23 @@ mod tests {
389465 assert ! ( found, "Our test socket should appear in netstat results" ) ;
390466 Ok ( ( ) )
391467 }
468+
469+ #[ test]
470+ fn test_list_interfaces ( ) {
471+ let result = list_interfaces ( ) ;
472+ assert ! ( result. is_ok( ) ) ;
473+
474+ let interfaces = result. unwrap ( ) ;
475+ assert ! ( !interfaces. is_empty( ) , "Should have at least one interface" ) ;
476+
477+ // Loopback interface should exist on Linux
478+ let has_lo = interfaces. iter ( ) . any ( |i| i. iface_name == "lo" ) ;
479+ assert ! ( has_lo, "Loopback interface 'lo' should be present" ) ;
480+ }
481+
482+ #[ test]
483+ fn print_interfaces ( ) {
484+ let result = list_interfaces ( ) ;
485+ println ! ( "debug {:?}" , result)
486+ }
392487}
0 commit comments