Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions examples/list_interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,37 @@ fn main() {
}
println!("\tIPv4: {:?}", interface.ipv4);

// Print the IPv6 addresses with the scope ID after them as a suffix
let ipv6_strs: Vec<String> = interface
.ipv6
.iter()
.zip(interface.ipv6_scope_ids)
.map(|(ipv6, scope_id)| format!("{:?}%{}", ipv6, scope_id))
.collect();
println!("\tIPv6: [{}]", ipv6_strs.join(", "));
// Print IPv6 addresses with scope ID and per-address flags
for (i, ipv6) in interface.ipv6.iter().enumerate() {
let scope_id = interface.ipv6_scope_ids.get(i).copied().unwrap_or(0);
let flags = interface
.ipv6_addr_flags
.get(i)
.copied()
.unwrap_or_default();
let mut flag_strs = Vec::new();
if flags.deprecated {
flag_strs.push("deprecated");
}
if flags.temporary {
flag_strs.push("temporary");
}
if flags.tentative {
flag_strs.push("tentative");
}
if flags.duplicated {
flag_strs.push("duplicated");
}
if flags.permanent {
flag_strs.push("permanent");
}
let flag_str = if flag_strs.is_empty() {
String::new()
} else {
format!(" [{}]", flag_strs.join(", "))
};
println!("\tIPv6: {:?}%{}{}", ipv6, scope_id, flag_str);
}

println!("\tTransmit Speed: {:?}", interface.transmit_speed);
println!("\tReceive Speed: {:?}", interface.receive_speed);
Expand Down
4 changes: 4 additions & 0 deletions src/interface/interface.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::interface::ipv6_addr_flags::Ipv6AddrFlags;
use crate::interface::state::OperState;
use crate::ipnet::{Ipv4Net, Ipv6Net};
use crate::net::ip::{is_global_ip, is_global_ipv4, is_global_ipv6};
Expand Down Expand Up @@ -60,6 +61,8 @@ pub struct Interface {
/// zone indexes. A value can be `0` when no scope is needed or when the platform did not
/// provide one.
pub ipv6_scope_ids: Vec<u32>,
/// Per-address IPv6 flags, aligned with entries in `Interface::ipv6`.
pub ipv6_addr_flags: Vec<Ipv6AddrFlags>,
/// Raw interface flags.
///
/// Bit meanings are platform-specific.
Expand Down Expand Up @@ -125,6 +128,7 @@ impl Interface {
ipv4: Vec::new(),
ipv6: Vec::new(),
ipv6_scope_ids: Vec::new(),
ipv6_addr_flags: Vec::new(),
flags: 0,
oper_state: OperState::Unknown,
transmit_speed: None,
Expand Down
62 changes: 62 additions & 0 deletions src/interface/ipv6_addr_flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! Per-address IPv6 state flags, normalized across platforms.

/// State flags for a single IPv6 address.
///
/// All fields default to `false` when the platform does not provide the
/// corresponding information.
///
/// Flags are collected from platform-specific sources:
///
/// - **Linux/Android**: netlink `IFA_FLAGS` attribute (`IFA_F_*` from [`<linux/if_addr.h>`])
/// - **macOS/iOS**: `SIOCGIFAFLAG_IN6` ioctl (`IN6_IFF_*` from [`<netinet6/in6_var.h>`][xnu])
/// - **FreeBSD/OpenBSD/NetBSD**: `SIOCGIFAFLAG_IN6` ioctl (`IN6_IFF_*` from [`<netinet6/in6_var.h>`][freebsd])
/// - **Windows**: [`NL_DAD_STATE`] and [`NL_SUFFIX_ORIGIN`] from `IP_ADAPTER_UNICAST_ADDRESS`
///
/// [`<linux/if_addr.h>`]: https://github.com/torvalds/linux/blob/master/include/uapi/linux/if_addr.h
/// [xnu]: https://github.com/apple-oss-distributions/xnu/blob/main/bsd/netinet6/in6_var.h
/// [freebsd]: https://github.com/freebsd/freebsd-src/blob/main/sys/netinet6/in6_var.h
/// [`NL_DAD_STATE`]: https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_dad_state
/// [`NL_SUFFIX_ORIGIN`]: https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_suffix_origin
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ipv6AddrFlags {
/// Preferred lifetime expired; should not be used for new connections.
///
/// Sourced from `IFA_F_DEPRECATED` (Linux), `IN6_IFF_DEPRECATED` (BSD),
/// or `IpDadStateDeprecated` (Windows).
pub deprecated: bool,
/// Privacy address ([RFC 4941](https://datatracker.ietf.org/doc/html/rfc4941)).
///
/// Sourced from `IFA_F_TEMPORARY` (Linux), `IN6_IFF_TEMPORARY` (BSD),
/// or `IpSuffixOriginRandom` (Windows).
pub temporary: bool,
/// Undergoing duplicate address detection.
///
/// Sourced from `IFA_F_TENTATIVE` (Linux), `IN6_IFF_TENTATIVE` (BSD),
/// or `IpDadStateTentative` (Windows).
pub tentative: bool,
/// Duplicate address detection failed.
///
/// Sourced from `IFA_F_DADFAILED` (Linux), `IN6_IFF_DUPLICATED` (BSD),
/// or `IpDadStateDuplicate` (Windows).
pub duplicated: bool,
/// Manually configured, not from SLAAC.
///
/// Sourced from `IFA_F_PERMANENT` (Linux). Not available on BSD or Windows.
pub permanent: bool,
}

// Platform dispatch for `get_ipv6_addr_flags`, called from `unix_interfaces()`.

#[cfg(target_vendor = "apple")]
pub(crate) use crate::os::darwin::ipv6_addr_flags::*;

#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
pub(crate) use crate::os::bsd::ipv6_addr_flags::*;

// On Linux/Android flags come from netlink; this is only reached via the
// `unix_interfaces()` fallback when netlink is unavailable.
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn get_ipv6_addr_flags(_ifname: &str, _addr: &std::net::Ipv6Addr) -> Ipv6AddrFlags {
Ipv6AddrFlags::default()
}
1 change: 1 addition & 0 deletions src/interface/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod flags;
pub mod interface;
pub mod ipv6_addr_flags;
pub mod mtu;
pub mod state;
pub mod types;
Expand Down
18 changes: 13 additions & 5 deletions src/os/android/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ fn push_ipv4(v: &mut Vec<Ipv4Net>, add: (Ipv4Addr, u8)) {
}
}

fn push_ipv6(v: &mut Vec<Ipv6Net>, add: (Ipv6Addr, u8)) {
fn push_ipv6(v: &mut Vec<Ipv6Net>, add: (Ipv6Addr, u8)) -> bool {
if v.iter()
.any(|n| n.addr() == add.0 && n.prefix_len() == add.1)
{
return;
return false;
}
if let Ok(net) = Ipv6Net::new(add.0, add.1) {
v.push(net);
return true;
}
false
}

#[inline]
Expand Down Expand Up @@ -87,6 +89,7 @@ pub fn interfaces() -> Vec<Interface> {
ipv4: Vec::new(),
ipv6: Vec::new(),
ipv6_scope_ids: Vec::new(),
ipv6_addr_flags: Vec::new(),
flags: r.flags,
oper_state: OperState::from_if_flags(r.flags),
transmit_speed: None,
Expand All @@ -104,9 +107,14 @@ pub fn interfaces() -> Vec<Interface> {
for (a, p) in r.ipv4 {
push_ipv4(&mut iface.ipv4, (a, p));
}
for (a, p) in r.ipv6 {
push_ipv6(&mut iface.ipv6, (a, p));
iface.ipv6_scope_ids.push(calc_v6_scope_id(&a, iface.index));
for (i, (a, p)) in r.ipv6.into_iter().enumerate() {
if push_ipv6(&mut iface.ipv6, (a, p)) {
iface.ipv6_scope_ids.push(calc_v6_scope_id(&a, iface.index));
let raw = r.ipv6_addr_flags.get(i).copied().unwrap_or(0);
iface
.ipv6_addr_flags
.push(crate::os::linux::ipv6_addr_flags::from_netlink_flags(raw));
}
}

ifaces.push(iface);
Expand Down
25 changes: 19 additions & 6 deletions src/os/android/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::stats::counters::InterfaceStats;
use netlink_packet_core::{NLM_F_DUMP, NLM_F_REQUEST, NetlinkMessage, NetlinkPayload};
use netlink_packet_route::{
RouteNetlinkMessage,
address::{AddressAttribute, AddressMessage},
address::{AddressAttribute, AddressFlags, AddressMessage},
link::{LinkAttribute, LinkMessage},
};
use netlink_sys::{Socket, SocketAddr, protocols::NETLINK_ROUTE};
Expand Down Expand Up @@ -236,17 +236,25 @@ fn name_from_link(link: &LinkMessage) -> Option<String> {
None
}

fn ip_from_addr(addr: &AddressMessage) -> Option<(IpAddr, u8)> {
fn ip_from_addr(addr: &AddressMessage) -> Option<(IpAddr, u8, u32)> {
let pfx = addr.header.prefix_len;
let mut ip_out = None;
let mut flags: Option<AddressFlags> = None;
for nla in &addr.attributes {
match nla {
AddressAttribute::Local(ip) | AddressAttribute::Address(ip) => {
return Some((*ip, pfx));
ip_out = Some(*ip);
}
AddressAttribute::Flags(f) => {
flags = Some(*f);
}
_ => {}
}
}
None
let addr_flags = flags
.map(|f| f.bits())
.unwrap_or(addr.header.flags.bits() as u32);
ip_out.map(|ip| (ip, pfx, addr_flags))
}

#[cfg(feature = "gateway")]
Expand Down Expand Up @@ -385,6 +393,7 @@ pub struct IfRow {
pub mac: Option<[u8; 6]>,
pub ipv4: Vec<(Ipv4Addr, u8)>,
pub ipv6: Vec<(Ipv6Addr, u8)>,
pub ipv6_addr_flags: Vec<u32>,
pub flags: u32,
pub mtu: Option<u32>,
pub if_type: InterfaceType,
Expand Down Expand Up @@ -413,6 +422,7 @@ pub fn collect_interfaces() -> io::Result<Vec<IfRow>> {
mac,
ipv4: vec![],
ipv6: vec![],
ipv6_addr_flags: vec![],
flags,
mtu: mtu_nl,
if_type,
Expand All @@ -423,11 +433,14 @@ pub fn collect_interfaces() -> io::Result<Vec<IfRow>> {

for a in addrs {
let idx = a.header.index as u32;
if let Some((ip, pfx)) = ip_from_addr(&a) {
if let Some((ip, pfx, addr_flags)) = ip_from_addr(&a) {
if let Some(row) = base.get_mut(&idx) {
match ip {
IpAddr::V4(v4) => row.ipv4.push((v4, pfx)),
IpAddr::V6(v6) => row.ipv6.push((v6, pfx)),
IpAddr::V6(v6) => {
row.ipv6.push((v6, pfx));
row.ipv6_addr_flags.push(addr_flags);
}
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/os/bsd/ipv6_addr_flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::net::Ipv6Addr;

use crate::interface::ipv6_addr_flags::Ipv6AddrFlags;

// <netinet6/in6_var.h> — not yet in `libc`.
const SIOCGIFAFLAG_IN6: libc::c_ulong = 0xC1206949;
const IN6_IFF_TENTATIVE: u32 = 0x02;
const IN6_IFF_DUPLICATED: u32 = 0x04;
const IN6_IFF_DEPRECATED: u32 = 0x10;
const IN6_IFF_TEMPORARY: u32 = 0x80;

// `libc` does not expose `in6_ifreq` on FreeBSD/OpenBSD/NetBSD.
#[repr(C)]
struct In6Ifreq {
ifr_name: [u8; libc::IFNAMSIZ],
ifr_addr: libc::sockaddr_in6,
ifr_flags: libc::c_int,
}

pub(crate) fn get_ipv6_addr_flags(ifname: &str, addr: &Ipv6Addr) -> Ipv6AddrFlags {
unsafe {
let fd = libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, 0);
if fd < 0 {
return Ipv6AddrFlags::default();
}

let mut req: In6Ifreq = std::mem::zeroed();

let name_bytes = ifname.as_bytes();
let copy_len = name_bytes.len().min(libc::IFNAMSIZ - 1);
std::ptr::copy_nonoverlapping(
name_bytes.as_ptr(),
req.ifr_name.as_mut_ptr().cast(),
copy_len,
);

req.ifr_addr.sin6_family = libc::AF_INET6 as libc::sa_family_t;
req.ifr_addr.sin6_len = std::mem::size_of::<libc::sockaddr_in6>() as u8;
req.ifr_addr.sin6_addr.s6_addr = addr.octets();

let ret = libc::ioctl(fd, SIOCGIFAFLAG_IN6, &mut req);
libc::close(fd);

if ret < 0 {
return Ipv6AddrFlags::default();
}

let raw = req.ifr_flags as u32;

Ipv6AddrFlags {
deprecated: raw & IN6_IFF_DEPRECATED != 0,
temporary: raw & IN6_IFF_TEMPORARY != 0,
tentative: raw & IN6_IFF_TENTATIVE != 0,
duplicated: raw & IN6_IFF_DUPLICATED != 0,
permanent: false,
}
}
}
1 change: 1 addition & 0 deletions src/os/bsd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod flags;
pub mod interface;
pub mod ipv6_addr_flags;
pub mod mtu;
#[cfg(feature = "gateway")]
pub mod route;
Expand Down
51 changes: 51 additions & 0 deletions src/os/darwin/ipv6_addr_flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::net::Ipv6Addr;

use crate::interface::ipv6_addr_flags::Ipv6AddrFlags;

// <netinet6/in6_var.h> — not yet in `libc`.
const SIOCGIFAFLAG_IN6: libc::c_ulong = 0xC1206949;
const IN6_IFF_TENTATIVE: u32 = 0x02;
const IN6_IFF_DUPLICATED: u32 = 0x04;
const IN6_IFF_DEPRECATED: u32 = 0x10;
const IN6_IFF_TEMPORARY: u32 = 0x80;

pub(crate) fn get_ipv6_addr_flags(ifname: &str, addr: &Ipv6Addr) -> Ipv6AddrFlags {
unsafe {
let fd = libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, 0);
if fd < 0 {
return Ipv6AddrFlags::default();
}

let mut req: libc::in6_ifreq = std::mem::zeroed();

let name_bytes = ifname.as_bytes();
let copy_len = name_bytes.len().min(libc::IFNAMSIZ - 1);
std::ptr::copy_nonoverlapping(
name_bytes.as_ptr(),
req.ifr_name.as_mut_ptr().cast(),
copy_len,
);

req.ifr_ifru.ifru_addr.sin6_family = libc::AF_INET6 as libc::sa_family_t;
req.ifr_ifru.ifru_addr.sin6_len =
std::mem::size_of::<libc::sockaddr_in6>() as libc::c_uchar;
req.ifr_ifru.ifru_addr.sin6_addr.s6_addr = addr.octets();

let ret = libc::ioctl(fd, SIOCGIFAFLAG_IN6, &mut req);
libc::close(fd);

if ret < 0 {
return Ipv6AddrFlags::default();
}

let raw = req.ifr_ifru.ifru_flags6 as u32;

Ipv6AddrFlags {
deprecated: raw & IN6_IFF_DEPRECATED != 0,
temporary: raw & IN6_IFF_TEMPORARY != 0,
tentative: raw & IN6_IFF_TENTATIVE != 0,
duplicated: raw & IN6_IFF_DUPLICATED != 0,
permanent: false,
}
}
}
1 change: 1 addition & 0 deletions src/os/darwin/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod flags;
pub mod ipv6_addr_flags;
pub mod mtu;
#[cfg(feature = "gateway")]
pub mod route;
Expand Down
Loading
Loading