#[cfg(windows)]
extern crate winapi;
use std::io;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[cfg(not(windows))]
#[cfg(windows)]
use std::os::windows::prelude::*;
extern crate c_linked_list;
#[cfg(not(windows))]
extern crate libc;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Interface {
pub name: String,
pub addr: IfAddr,
pub description: String,
pub ifa_flags: u32,
pub scope_id: u32,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum IfAddr {
V4(Ifv4Addr),
V6(Ifv6Addr),
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Ifv4Addr {
pub ip: Ipv4Addr,
pub netmask: Ipv4Addr,
pub broadcast: Option<Ipv4Addr>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Ifv6Addr {
pub ip: Ipv6Addr,
pub netmask: Ipv6Addr,
pub broadcast: Option<Ipv6Addr>,
}
impl Interface {
pub fn is_loopback(&self) -> bool {
self.addr.is_loopback()
}
pub fn ip(&self) -> IpAddr {
self.addr.ip()
}
}
impl IfAddr {
pub fn is_loopback(&self) -> bool {
match *self {
IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_loopback(),
IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_loopback(),
}
}
pub fn ip(&self) -> IpAddr {
match *self {
IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip),
IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip),
}
}
}
impl Ifv4Addr {
pub fn is_loopback(&self) -> bool {
self.ip.octets()[0] == 127
}
}
impl Ifv6Addr {
pub fn is_loopback(&self) -> bool {
self.ip.segments() == [0, 0, 0, 0, 0, 0, 0, 1]
}
}
#[cfg(not(windows))]
mod getifaddrs_posix {
use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface};
use c_linked_list::CLinkedListMut;
use libc::freeifaddrs as posix_freeifaddrs;
use libc::getifaddrs as posix_getifaddrs;
use libc::ifaddrs as posix_ifaddrs;
use libc::sockaddr as posix_sockaddr;
use libc::sockaddr_in as posix_sockaddr_in;
use libc::sockaddr_in6 as posix_sockaddr_in6;
use libc::{AF_INET, AF_INET6};
use std::ffi::CStr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::{io, mem};
#[allow(non_camel_case_types)]
pub enum IFFFlags {
IFF_UP = 1 << 0,
IFF_BROADCAST = 1 << 1,
IFF_DEBUG = 1 << 2,
IFF_LOOPBACK = 1 << 3,
IFF_POINTOPOINT = 1 << 4,
IFF_NOTRAILERS = 1 << 5,
IFF_RUNNING = 1 << 6,
IFF_NOARP = 1 << 7,
IFF_PROMISC = 1 << 8,
IFF_ALLMULTI = 1 << 9,
IFF_MASTER = 1 << 10,
IFF_SLAVE = 1 << 11,
IFF_MULTICAST = 1 << 12,
IFF_PORTSEL = 1 << 13,
IFF_AUTOMEDIA = 1 << 14,
IFF_DYNAMIC = 1 << 15,
IFF_LOWER_UP = 1 << 16,
IFF_DORMANT = 1 << 17,
IFF_ECHO = 1 << 18,
}
#[allow(unsafe_code)]
fn sockaddr_to_ipaddr(sockaddr: *const posix_sockaddr) -> Option<(IpAddr, u32)> {
if sockaddr.is_null() {
return None;
}
let sa_family = u32::from(unsafe { *sockaddr }.sa_family);
if sa_family == AF_INET as u32 {
#[cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
let sa = &unsafe { *(sockaddr as *const posix_sockaddr_in) };
Some((IpAddr::V4(Ipv4Addr::new(
((sa.sin_addr.s_addr) & 255) as u8,
((sa.sin_addr.s_addr >> 8) & 255) as u8,
((sa.sin_addr.s_addr >> 16) & 255) as u8,
((sa.sin_addr.s_addr >> 24) & 255) as u8,
)), 0))
} else if sa_family == AF_INET6 as u32 {
#[cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
let sa = &unsafe { *(sockaddr as *const posix_sockaddr_in6) };
Some((IpAddr::V6(Ipv6Addr::from(sa.sin6_addr.s6_addr)), sa.sin6_scope_id))
} else {
None
}
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "nacl"))]
fn do_broadcast(ifaddr: &posix_ifaddrs) -> Option<(IpAddr, u32)> {
sockaddr_to_ipaddr(ifaddr.ifa_ifu)
}
#[cfg(any(
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
fn do_broadcast(ifaddr: &posix_ifaddrs) -> Option<(IpAddr, u32)> {
sockaddr_to_ipaddr(ifaddr.ifa_addr)
}
#[allow(unsafe_code)]
#[allow(trivial_casts)]
pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
let mut ret = Vec::<Interface>::new();
let mut ifaddrs: *mut posix_ifaddrs;
unsafe {
ifaddrs = mem::MaybeUninit::uninit().assume_init();
if -1 == posix_getifaddrs(&mut ifaddrs) {
return Err(io::Error::last_os_error());
}
}
for ifaddr in unsafe { CLinkedListMut::from_ptr(ifaddrs, |a| a.ifa_next) }.iter() {
if ifaddr.ifa_addr.is_null() {
continue;
}
let name = unsafe { CStr::from_ptr(ifaddr.ifa_name as *const _) }
.to_string_lossy()
.into_owned();
if ifaddr.ifa_flags & (IFFFlags::IFF_UP as u32) == 0 {
info!("will ignore iface {}", name);
continue;
}
if (ifaddr.ifa_flags & (IFFFlags::IFF_LOOPBACK as u32) != 0)
|| (ifaddr.ifa_flags & (IFFFlags::IFF_POINTOPOINT as u32) != 0)
{
info!("will ignore iface {}", name);
continue;
}
let (addr, scope_id) = match sockaddr_to_ipaddr(ifaddr.ifa_addr) {
None => continue,
Some((IpAddr::V4(ipv4_addr), _)) => {
if ipv4_addr.is_loopback()
|| ipv4_addr.is_unspecified()
|| ipv4_addr.is_link_local()
{
continue;
}
let netmask = match sockaddr_to_ipaddr(ifaddr.ifa_netmask) {
Some((IpAddr::V4(netmask), _)) => netmask,
_ => Ipv4Addr::new(0, 0, 0, 0),
};
let broadcast = if (ifaddr.ifa_flags & IFFFlags::IFF_BROADCAST as u32) != 0 {
match do_broadcast(ifaddr) {
Some((IpAddr::V4(broadcast), _)) => Some(broadcast),
_ => None,
}
} else {
None
};
(IfAddr::V4(Ifv4Addr {
ip: ipv4_addr,
netmask,
broadcast,
}), 0)
}
Some((IpAddr::V6(ipv6_addr), scope_id)) => {
if ipv6_addr.is_loopback() || ipv6_addr.is_unspecified() {
continue;
}
let netmask = match sockaddr_to_ipaddr(ifaddr.ifa_netmask) {
Some((IpAddr::V6(netmask), _)) => netmask,
_ => Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0),
};
let broadcast = if (ifaddr.ifa_flags & IFFFlags::IFF_BROADCAST as u32) != 0 {
match do_broadcast(ifaddr) {
Some((IpAddr::V6(broadcast), _)) => Some(broadcast),
_ => None,
}
} else {
None
};
(IfAddr::V6(Ifv6Addr {
ip: ipv6_addr,
netmask,
broadcast,
}), scope_id)
}
};
ret.push(Interface {
name,
addr,
description: String::from(""),
ifa_flags: ifaddr.ifa_flags as u32,
scope_id
});
}
unsafe {
posix_freeifaddrs(ifaddrs);
}
Ok(ret)
}
}
#[cfg(not(windows))]
pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
getifaddrs_posix::get_if_addrs()
}
#[cfg(not(windows))]
pub use getifaddrs_posix::IFFFlags;
#[cfg(windows)]
mod getifaddrs_windows {
use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface};
use c_linked_list::CLinkedListConst;
use libc;
use libc::{c_char, c_int, c_ulong, size_t};
use std::ffi::{c_void, CStr, OsString};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::os::windows::prelude::*;
use std::{io, ptr};
use winapi::shared::minwindef::DWORD;
use winapi::shared::winerror::ERROR_SUCCESS;
use winapi::shared::ws2def::SOCKADDR as sockaddr;
use winapi::shared::ws2def::SOCKADDR_IN as sockaddr_in;
use winapi::shared::ws2def::{AF_INET, AF_INET6};
use winapi::shared::ws2ipdef::SOCKADDR_IN6_LH as sockaddr_in6;
#[repr(C)]
struct SocketAddress {
pub lp_socket_address: *const sockaddr,
pub i_socket_address_length: c_int,
}
#[repr(C)]
struct IpAdapterUnicastAddress {
pub length: c_ulong,
pub flags: DWORD,
pub next: *const IpAdapterUnicastAddress,
pub address: SocketAddress,
pub prefix_origin: c_ulong,
pub suffix_origin: c_ulong,
}
#[repr(C)]
struct IpAdapterPrefix {
pub length: c_ulong,
pub flags: DWORD,
pub next: *const IpAdapterPrefix,
pub address: SocketAddress,
pub prefix_length: c_ulong,
}
#[repr(C)]
struct IpAdapterAddresses {
pub length: c_ulong,
pub if_index: DWORD,
pub next: *const IpAdapterAddresses,
pub adapter_name: *const c_char,
pub first_unicast_address: *const IpAdapterUnicastAddress,
first_anycast_address: *const c_void,
first_multicast_address: *const c_void,
first_dns_server_address: *const c_void,
dns_suffix: *const c_void,
description: *const c_void,
friendly_name: *const c_void,
physical_address: [c_char; 8],
physical_address_length: DWORD,
flags: DWORD,
mtu: DWORD,
if_type: DWORD,
oper_status: c_int,
ipv6_if_index: DWORD,
zone_indices: [DWORD; 16],
pub first_prefix: *const IpAdapterPrefix,
}
#[link(name = "Iphlpapi")]
extern "system" {
fn GetAdaptersAddresses(
family: c_ulong,
flags: c_ulong,
reserved: *const c_void,
addresses: *const IpAdapterAddresses,
size: *mut c_ulong,
) -> c_ulong;
}
#[allow(unsafe_code)]
fn sockaddr_to_ipaddr(sockaddr: *const sockaddr) -> Option<(IpAddr, u32)> {
if sockaddr.is_null() {
return None;
}
if unsafe { *sockaddr }.sa_family as u32 == AF_INET as u32 {
let sa = &unsafe { *(sockaddr as *const sockaddr_in) };
if unsafe { sa.sin_addr.S_un.S_un_w().s_w1 } == 0xa9fe {
return None;
}
Some((IpAddr::V4(Ipv4Addr::new(
unsafe { sa.sin_addr.S_un.S_un_b().s_b1 },
unsafe { sa.sin_addr.S_un.S_un_b().s_b2 },
unsafe { sa.sin_addr.S_un.S_un_b().s_b3 },
unsafe { sa.sin_addr.S_un.S_un_b().s_b4 },
)), 0))
} else if unsafe { *sockaddr }.sa_family as u32 == AF_INET6 as u32 {
let sa = &unsafe { *(sockaddr as *const sockaddr_in6) };
let mut v6byte = [0_u8; 16];
v6byte.copy_from_slice(unsafe { sa.sin6_addr.u.Byte() });
Some((IpAddr::V6(Ipv6Addr::from(v6byte)), *unsafe { sa.u.sin6_scope_id() }))
} else {
None
}
}
unsafe fn u16_ptr_to_string(ptr: *const u16) -> OsString {
let len = (0..).take_while(|&i| *ptr.offset(i) != 0).count();
let slice = std::slice::from_raw_parts(ptr, len);
OsString::from_wide(slice)
}
#[allow(unsafe_code, trivial_numeric_casts)]
pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
let mut ret = Vec::<Interface>::new();
let mut ifaddrs: *const IpAdapterAddresses;
let mut buffersize: c_ulong = 15000;
loop {
unsafe {
ifaddrs = libc::malloc(buffersize as size_t) as *mut IpAdapterAddresses;
if ifaddrs.is_null() {
panic!("Failed to allocate buffer in get_if_addrs()");
}
let retcode = GetAdaptersAddresses(
0,
0x3e,
ptr::null(),
ifaddrs,
&mut buffersize,
);
match retcode {
ERROR_SUCCESS => break,
111 => {
libc::free(ifaddrs as *mut c_void);
buffersize *= 2;
continue;
}
_ => return Err(io::Error::last_os_error()),
}
}
}
for ifaddr in unsafe { CLinkedListConst::from_ptr(ifaddrs, |a| a.next) }.iter() {
if ifaddr.oper_status != 1 {
continue;
}
if ifaddr.if_type == 24 || ifaddr.if_type == 131 {
continue;
}
for addr in
unsafe { CLinkedListConst::from_ptr(ifaddr.first_unicast_address, |a| a.next) }
.iter()
{
let name = unsafe { CStr::from_ptr(ifaddr.adapter_name) }
.to_string_lossy()
.into_owned();
if name.starts_with("docker") {
info!("will ignore as Docker Virtual Ethernet Adapter: {}", name);
continue;
}
let description = unsafe { u16_ptr_to_string(ifaddr.description as *const u16) }
.to_string_lossy()
.into_owned();
if description.find("VMware").is_some() {
info!("will ignore as VMware addr: {}", description);
continue;
}
let (addr, scope_id) = match sockaddr_to_ipaddr(addr.address.lp_socket_address) {
None => continue,
Some((IpAddr::V4(ipv4_addr), _)) => {
if ipv4_addr.is_loopback()
|| ipv4_addr.is_link_local()
|| ipv4_addr.is_broadcast()
|| ipv4_addr.is_documentation()
|| ipv4_addr.is_unspecified()
{
info!("will ignore ip addr: desc={}, addr={}", description, ipv4_addr);
continue;
}
let mut item_netmask = Ipv4Addr::new(0, 0, 0, 0);
let mut item_broadcast = None;
'prefixloopv4: for prefix in
unsafe { CLinkedListConst::from_ptr(ifaddr.first_prefix, |p| p.next) }
.iter()
{
let ipprefix = sockaddr_to_ipaddr(prefix.address.lp_socket_address);
match ipprefix {
Some((IpAddr::V4(ref a), _)) => {
let mut netmask: [u8; 4] = [0; 4];
for (n, netmask_elt) in netmask
.iter_mut()
.enumerate()
.take((prefix.prefix_length as usize + 7) / 8)
{
let x_byte = ipv4_addr.octets()[n];
let y_byte = a.octets()[n];
#[cfg_attr(
feature = "cargo-clippy",
allow(needless_continue)
)]
for m in 0..8 {
if (n * 8) + m > prefix.prefix_length as usize {
break;
}
let bit = 1_u8 << m as u8;
if (x_byte & bit) == (y_byte & bit) {
*netmask_elt |= bit;
} else {
continue 'prefixloopv4;
}
}
}
item_netmask = Ipv4Addr::new(
netmask[0], netmask[1], netmask[2], netmask[3],
);
let mut broadcast: [u8; 4] = ipv4_addr.octets();
for n in 0..4 {
broadcast[n] |= !netmask[n];
}
item_broadcast = Some(Ipv4Addr::new(
broadcast[0],
broadcast[1],
broadcast[2],
broadcast[3],
));
break 'prefixloopv4;
}
_ => continue,
};
}
(IfAddr::V4(Ifv4Addr {
ip: ipv4_addr,
netmask: item_netmask,
broadcast: item_broadcast,
}), 0)
}
Some((IpAddr::V6(ipv6_addr), scope_id)) => {
let mut item_netmask = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
'prefixloopv6: for prefix in
unsafe { CLinkedListConst::from_ptr(ifaddr.first_prefix, |p| p.next) }
.iter()
{
let ipprefix = sockaddr_to_ipaddr(prefix.address.lp_socket_address);
match ipprefix {
Some((IpAddr::V6(ref a), _)) => {
let mut netmask: [u16; 8] = [0; 8];
for (n, netmask_elt) in netmask
.iter_mut()
.enumerate()
.take((prefix.prefix_length as usize + 15) / 16)
{
let x_word = ipv6_addr.segments()[n];
let y_word = a.segments()[n];
#[cfg_attr(
feature = "cargo-clippy",
allow(needless_continue)
)]
for m in 0..16 {
if (n * 16) + m > prefix.prefix_length as usize {
break;
}
let bit = 1_u16 << m as u16;
if (x_word & bit) == (y_word & bit) {
*netmask_elt |= bit;
} else {
continue 'prefixloopv6;
}
}
}
item_netmask = Ipv6Addr::new(
netmask[0], netmask[1], netmask[2], netmask[3], netmask[4],
netmask[5], netmask[6], netmask[7],
);
break 'prefixloopv6;
}
_ => continue,
};
}
(IfAddr::V6(Ifv6Addr {
ip: ipv6_addr,
netmask: item_netmask,
broadcast: None,
}), scope_id)
}
};
ret.push(Interface {
name,
addr,
description,
ifa_flags: ifaddr.flags as u32,
scope_id
});
}
}
unsafe {
libc::free(ifaddrs as *mut c_void);
}
Ok(ret)
}
}
#[cfg(windows)]
pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
getifaddrs_windows::get_if_addrs()
}
#[test]
fn test() {
let interfaces = get_if_addrs().unwrap();
for interface in interfaces {
let addr_str = match interface.addr {
IfAddr::V4(ip) => {ip.ip.to_string()}
IfAddr::V6(ip) => {ip.ip.to_string()}
};
println!("{}: {}", addr_str, interface.scope_id);
}
}