EstebanBorai / network-interface

Retrieve system's Network Interfaces on Linux, macOS and Windows on a standardized manner
https://crates.io/crates/network-interface
Apache License 2.0
62 stars 27 forks source link

Check if a network interface is a loopback interface #39

Open alexkirsz opened 1 year ago

alexkirsz commented 1 year ago

Hey!

I'm looking for the same functionality as the is_loopback() method of the pnet_datalink crate.

FWIW, here's what GPT-4 generated to do this:

#[cfg(unix)]
fn main() {
    use libc::{getifaddrs, freeifaddrs, ifaddrs, IFF_LOOPBACK};
    use std::ffi::CStr;
    use std::ptr;

    unsafe {
        let mut ifap: *mut ifaddrs = ptr::null_mut();
        if getifaddrs(&mut ifap) == 0 {
            let mut ifa = ifap;
            while !ifa.is_null() {
                let iface = &*ifa;
                if iface.ifa_flags & IFF_LOOPBACK == IFF_LOOPBACK {
                    let name = CStr::from_ptr(iface.ifa_name).to_string_lossy().into_owned();
                    println!("Loopback Interface: {}", name);
                }
                ifa = iface.ifa_next;
            }
            freeifaddrs(ifap);
        } else {
            eprintln!("Error getting network interfaces");
        }
    }
}

#[cfg(windows)]
fn main() {
    use winapi::shared::minwindef::ULONG;
    use winapi::shared::ntdef::NULL;
    use winapi::shared::ws2def::AF_UNSPEC;
    use winapi::um::iphlpapi::{GetAdaptersAddresses, PIP_ADAPTER_ADDRESSES};
    use winapi::um::winsock2::SOCKET_ERROR;

    use std::mem::size_of_val;
    use std::ptr;

    unsafe {
        let mut buf_len: ULONG = 0;
        if GetAdaptersAddresses(AF_UNSPEC as u32, 0, ptr::null_mut(), ptr::null_mut(), &mut buf_len) == SOCKET_ERROR {
            let mut buffer = vec![0u8; buf_len as usize];
            let p_addresses = buffer.as_mut_ptr() as PIP_ADAPTER_ADDRESSES;
            if GetAdaptersAddresses(AF_UNSPEC as u32, 0, ptr::null_mut(), p_addresses, &mut buf_len) == 0 {
                let mut current_address = p_addresses;
                while !current_address.is_null() {
                    let iface = &*current_address;
                    if iface.IfType == winapi::shared::ifdef::IF_TYPE_SOFTWARE_LOOPBACK {
                        let name = String::from_utf16_lossy(std::slice::from_raw_parts(iface.FriendlyName.as_ptr(), iface.FriendlyName.iter().position(|&c| c == 0).unwrap_or(0)));
                        println!("Loopback Interface: {}", name);
                    }
                    current_address = iface.Next;
                }
            } else {
                eprintln!("Error getting network interfaces");
            }
        } else {
            eprintln!("Error getting network interfaces");
        }
    }
}
EstebanBorai commented 1 year ago

Hi @alexkirsz thanks for openning this feature request!

Are you planning to work on this?

nvandamme commented 7 months ago

Code borrowed from if_addrs:

fn is_loopback_ipv6(ip: Ipv6Addr) -> bool {
    ip.segments() == [0, 0, 0, 0, 0, 0, 0, 1]
}

fn is_loopback_ipv4(ip: Ipv4Addr) -> bool {
    ip.octets()[0] == 127
}
utkarshgupta137 commented 7 months ago

We use this:

    let network_interfaces = NetworkInterface::show()?;
    let network_interfaces = network_interfaces
        .into_iter()
        .filter(|itf| !itf.name.starts_with("lo"))
        .collect::<Vec<NetworkInterface>>();
podarcis commented 7 months ago

@nvandamme I think it would be nice to get a flag from the OS whether the IF is loopback or not and don't decide on its address.

@utkarshgupta137 Your app code seems to be platform specific (Linux/UNIX) and might not work on Windows. Also I think it's risky to just test for IF name to start with "lo".

By the way: Despite not having a solution (I guess @alexkirsz might go in the right direction?) either, here is my workaround I'm using in my app code:

  let network_interfaces = NetworkInterface::show().unwrap();

  for itf in network_interfaces.iter() {
      if let Some(mac_addr) = &itf.mac_addr {
          if mac_addr == "00:00:00:00:00:00" {            // <- not strictly lo related, but I'd like to skip those anyway
              continue;
          }

          for addr in &itf.addr {
              let ip = addr.ip();
              if ip.is_loopback() {          // <- HERE; checks both IPv4 and IPv6
                  continue;
              }
              // ...
          }
      };
  }
nvandamme commented 7 months ago

@podarcis Why ? Either way, local interfaces are following RFC's adresses allocations.... And if the OS allows otherwise, it is another level of problems awaiting network stack usage anyway...

podarcis commented 7 months ago

@nvandamme My initial thinking was that the OS knows best about whether it's a loopback or not. And I thought about other interfaces besides AF_PACKET, AF_INET and AF_INET6 might have the concept of loopback interfaces, however, it's the only types that are supported with this library (at least for now and Linux). So there might be no point in going the extra mile.

So what about NetworkInterface implements a function is_loopback() that simply iterates over the containing IPv4/IPv6 address vector and returns true on the first addr.ip().is_loopback()?

nvandamme commented 7 months ago

@nvandamme My initial thinking was that the OS knows best about whether it's a loopback or not. And I thought about other interfaces besides AF_PACKET, AF_INET and AF_INET6 might have the concept of loopback interfaces, however, it's the only types that are supported with this library (at least for now and Linux). So there might be no point in going the extra mile.

So what about NetworkInterface implements a function is_loopback() that simply iterates over the containing IPv4/IPv6 address vector and returns true on the first addr.ip().is_loopback()?

@podarcis, ok, for example cases using lo aliases with routed IPs, like management IPs on router and switches (linux/unix sure can handle this, but windows, I'm not sure) ? And in this case, the allocated IPs are fully routed to all network routes, so not really acting as a pure loopback IP anymore.

The only other case would be others layer 2 ethernet protocoles that might use a form of loopback (profinet, 6lowpan...)

podarcis commented 7 months ago

@EstebanBorai What do you think? Expose a function is_loopback() on the interface checking on its adresses for loopback? Or just add a recipe to the README (see my example above)?