diff --git a/examples/list_interfaces.rs b/examples/list_interfaces.rs index 6631188..ab600f8 100644 --- a/examples/list_interfaces.rs +++ b/examples/list_interfaces.rs @@ -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 = 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); diff --git a/src/interface/interface.rs b/src/interface/interface.rs index e4f516b..50c149b 100644 --- a/src/interface/interface.rs +++ b/src/interface/interface.rs @@ -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}; @@ -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, + /// Per-address IPv6 flags, aligned with entries in `Interface::ipv6`. + pub ipv6_addr_flags: Vec, /// Raw interface flags. /// /// Bit meanings are platform-specific. @@ -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, diff --git a/src/interface/ipv6_addr_flags.rs b/src/interface/ipv6_addr_flags.rs new file mode 100644 index 0000000..680d76b --- /dev/null +++ b/src/interface/ipv6_addr_flags.rs @@ -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 [``]) +/// - **macOS/iOS**: `SIOCGIFAFLAG_IN6` ioctl (`IN6_IFF_*` from [``][xnu]) +/// - **FreeBSD/OpenBSD/NetBSD**: `SIOCGIFAFLAG_IN6` ioctl (`IN6_IFF_*` from [``][freebsd]) +/// - **Windows**: [`NL_DAD_STATE`] and [`NL_SUFFIX_ORIGIN`] from `IP_ADAPTER_UNICAST_ADDRESS` +/// +/// [``]: 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() +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 34eefb1..ecc0469 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -1,5 +1,6 @@ pub mod flags; pub mod interface; +pub mod ipv6_addr_flags; pub mod mtu; pub mod state; pub mod types; diff --git a/src/os/android/interface.rs b/src/os/android/interface.rs index f6d1996..15cb8a3 100644 --- a/src/os/android/interface.rs +++ b/src/os/android/interface.rs @@ -23,15 +23,17 @@ fn push_ipv4(v: &mut Vec, add: (Ipv4Addr, u8)) { } } -fn push_ipv6(v: &mut Vec, add: (Ipv6Addr, u8)) { +fn push_ipv6(v: &mut Vec, 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] @@ -87,6 +89,7 @@ pub fn interfaces() -> Vec { 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, @@ -104,9 +107,14 @@ pub fn interfaces() -> Vec { 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); diff --git a/src/os/android/netlink.rs b/src/os/android/netlink.rs index 6d79d74..49e54f3 100644 --- a/src/os/android/netlink.rs +++ b/src/os/android/netlink.rs @@ -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}; @@ -236,17 +236,25 @@ fn name_from_link(link: &LinkMessage) -> Option { 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 = 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")] @@ -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, pub flags: u32, pub mtu: Option, pub if_type: InterfaceType, @@ -413,6 +422,7 @@ pub fn collect_interfaces() -> io::Result> { mac, ipv4: vec![], ipv6: vec![], + ipv6_addr_flags: vec![], flags, mtu: mtu_nl, if_type, @@ -423,11 +433,14 @@ pub fn collect_interfaces() -> io::Result> { 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); + } } } } diff --git a/src/os/bsd/ipv6_addr_flags.rs b/src/os/bsd/ipv6_addr_flags.rs new file mode 100644 index 0000000..a2e6852 --- /dev/null +++ b/src/os/bsd/ipv6_addr_flags.rs @@ -0,0 +1,58 @@ +use std::net::Ipv6Addr; + +use crate::interface::ipv6_addr_flags::Ipv6AddrFlags; + +// — 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::() 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, + } + } +} diff --git a/src/os/bsd/mod.rs b/src/os/bsd/mod.rs index 679f688..866cd43 100644 --- a/src/os/bsd/mod.rs +++ b/src/os/bsd/mod.rs @@ -1,5 +1,6 @@ pub mod flags; pub mod interface; +pub mod ipv6_addr_flags; pub mod mtu; #[cfg(feature = "gateway")] pub mod route; diff --git a/src/os/darwin/ipv6_addr_flags.rs b/src/os/darwin/ipv6_addr_flags.rs new file mode 100644 index 0000000..f462a72 --- /dev/null +++ b/src/os/darwin/ipv6_addr_flags.rs @@ -0,0 +1,51 @@ +use std::net::Ipv6Addr; + +use crate::interface::ipv6_addr_flags::Ipv6AddrFlags; + +// — 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::() 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, + } + } +} diff --git a/src/os/darwin/mod.rs b/src/os/darwin/mod.rs index f5630ac..3be5605 100644 --- a/src/os/darwin/mod.rs +++ b/src/os/darwin/mod.rs @@ -1,4 +1,5 @@ pub mod flags; +pub mod ipv6_addr_flags; pub mod mtu; #[cfg(feature = "gateway")] pub mod route; diff --git a/src/os/linux/interface.rs b/src/os/linux/interface.rs index 82ed7df..9766d56 100644 --- a/src/os/linux/interface.rs +++ b/src/os/linux/interface.rs @@ -24,15 +24,17 @@ fn push_ipv4(v: &mut Vec, add: (Ipv4Addr, u8)) { } } -fn push_ipv6(v: &mut Vec, add: (Ipv6Addr, u8)) { +fn push_ipv6(v: &mut Vec, 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] @@ -63,6 +65,7 @@ pub fn interfaces() -> Vec { 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, @@ -80,9 +83,14 @@ pub fn interfaces() -> Vec { 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(super::ipv6_addr_flags::from_netlink_flags(raw)); + } } ifaces.push(iface); diff --git a/src/os/linux/ipv6_addr_flags.rs b/src/os/linux/ipv6_addr_flags.rs new file mode 100644 index 0000000..6f9c004 --- /dev/null +++ b/src/os/linux/ipv6_addr_flags.rs @@ -0,0 +1,19 @@ +use crate::interface::ipv6_addr_flags::Ipv6AddrFlags; + +/// Decode a raw `IFA_F_*` bitmask into [`Ipv6AddrFlags`]. +pub(crate) fn from_netlink_flags(raw: u32) -> Ipv6AddrFlags { + // + const IFA_F_TEMPORARY: u32 = 0x01; + const IFA_F_DADFAILED: u32 = 0x08; + const IFA_F_DEPRECATED: u32 = 0x20; + const IFA_F_TENTATIVE: u32 = 0x40; + const IFA_F_PERMANENT: u32 = 0x80; + + Ipv6AddrFlags { + deprecated: raw & IFA_F_DEPRECATED != 0, + temporary: raw & IFA_F_TEMPORARY != 0, + tentative: raw & IFA_F_TENTATIVE != 0, + duplicated: raw & IFA_F_DADFAILED != 0, + permanent: raw & IFA_F_PERMANENT != 0, + } +} diff --git a/src/os/linux/mod.rs b/src/os/linux/mod.rs index 2cf86ce..516663e 100644 --- a/src/os/linux/mod.rs +++ b/src/os/linux/mod.rs @@ -3,6 +3,7 @@ pub mod arp; pub mod flags; #[cfg(not(target_os = "android"))] pub mod interface; +pub mod ipv6_addr_flags; pub mod mtu; #[cfg(not(target_os = "android"))] pub mod netlink; diff --git a/src/os/linux/netlink.rs b/src/os/linux/netlink.rs index 65fd6a1..d1e3c1f 100644 --- a/src/os/linux/netlink.rs +++ b/src/os/linux/netlink.rs @@ -1,7 +1,7 @@ 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}; @@ -233,17 +233,25 @@ fn name_from_link(link: &LinkMessage) -> Option { 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 = 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")] @@ -334,6 +342,7 @@ pub struct IfRow { pub mac: Option<[u8; 6]>, pub ipv4: Vec<(Ipv4Addr, u8)>, pub ipv6: Vec<(Ipv6Addr, u8)>, + pub ipv6_addr_flags: Vec, pub flags: u32, pub mtu: Option, } @@ -357,6 +366,7 @@ pub fn collect_interfaces() -> io::Result> { mac, ipv4: vec![], ipv6: vec![], + ipv6_addr_flags: vec![], flags, mtu: mtu_nl, }, @@ -365,11 +375,14 @@ pub fn collect_interfaces() -> io::Result> { 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); + } } } } diff --git a/src/os/unix/interface.rs b/src/os/unix/interface.rs index b1c036b..a637f36 100644 --- a/src/os/unix/interface.rs +++ b/src/os/unix/interface.rs @@ -6,6 +6,7 @@ use std::str::from_utf8_unchecked; use super::sockaddr::{SockaddrRef, compute_sockaddr_len, netmask_ip_autolen, try_mac_from_raw}; use crate::interface::interface::Interface; +use crate::interface::ipv6_addr_flags::get_ipv6_addr_flags; use crate::interface::mtu::get_mtu; use crate::interface::state::OperState; use crate::ipnet::{Ipv4Net, Ipv6Net}; @@ -124,11 +125,17 @@ fn unix_interfaces_inner( iface.ipv4.push(ipv4_addr); } if let (Some(ipv6_addr), Some(scope_id)) = (ini_ipv6, ipv6_scope_id) { + let af = get_ipv6_addr_flags(&iface.name, &ipv6_addr.addr()); iface.ipv6.push(ipv6_addr); iface.ipv6_scope_ids.push(scope_id); + iface.ipv6_addr_flags.push(af); } } else { let mtu = get_mtu(addr_ref, &name); + let ini_ipv6_flags = ini_ipv6 + .as_ref() + .map(|net| vec![get_ipv6_addr_flags(&name, &net.addr())]) + .unwrap_or_default(); let interface: Interface = Interface { index: if_index, name, @@ -148,6 +155,7 @@ fn unix_interfaces_inner( Some(scope_id) => vec![scope_id], None => Vec::new(), }, + ipv6_addr_flags: ini_ipv6_flags, flags: addr_ref.ifa_flags, oper_state: OperState::from_if_flags(addr_ref.ifa_flags), transmit_speed: None, @@ -215,4 +223,23 @@ mod tests { assert_eq!(resolve_ipv6_scope_id(&addr, None, 7), 0); assert_eq!(resolve_ipv6_scope_id(&addr, Some(0), 7), 0); } + + #[test] + fn ipv6_addr_flags_aligned_with_addrs() { + let ifaces = super::unix_interfaces(); + for iface in &ifaces { + assert_eq!( + iface.ipv6.len(), + iface.ipv6_addr_flags.len(), + "ipv6_addr_flags length mismatch for {}", + iface.name + ); + assert_eq!( + iface.ipv6.len(), + iface.ipv6_scope_ids.len(), + "ipv6_scope_ids length mismatch for {}", + iface.name + ); + } + } } diff --git a/src/os/windows/interface.rs b/src/os/windows/interface.rs index 9097de0..a8ae1df 100644 --- a/src/os/windows/interface.rs +++ b/src/os/windows/interface.rs @@ -5,12 +5,14 @@ use windows_sys::Win32::NetworkManagement::IpHelper::{ }; use windows_sys::Win32::NetworkManagement::Ndis::NET_IF_OPER_STATUS_UP; use windows_sys::Win32::Networking::WinSock::{ - AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR_INET, SOCKET_ADDRESS, + AF_INET, AF_INET6, AF_UNSPEC, IpDadStateDeprecated, IpDadStateDuplicate, IpDadStateTentative, + IpSuffixOriginRandom, SOCKADDR_INET, SOCKET_ADDRESS, }; use super::flags; use super::macros::linked_list_iter; use crate::interface::interface::Interface; +use crate::interface::ipv6_addr_flags::Ipv6AddrFlags; use crate::interface::state::OperState; use crate::interface::types::InterfaceType; use crate::ipnet::{Ipv4Net, Ipv6Net}; @@ -180,6 +182,7 @@ pub fn interfaces() -> Vec { let mut ipv4_vec: Vec = vec![]; let mut ipv6_vec: Vec = vec![]; let mut ipv6_scope_id_vec: Vec = vec![]; + let mut ipv6_flags_vec: Vec = vec![]; // Enumerate all IPs for cur_a in unsafe { linked_list_iter!(&cur.FirstUnicastAddress) } { let (ip_addr, ipv6_scope_id) = unsafe { socket_address_to_ipaddr(&cur_a.Address) }; @@ -194,6 +197,14 @@ pub fn interfaces() -> Vec { Ok(ipv6_net) => { ipv6_vec.push(ipv6_net); ipv6_scope_id_vec.push(ipv6_scope_id.unwrap()); + + ipv6_flags_vec.push(Ipv6AddrFlags { + deprecated: cur_a.DadState == IpDadStateDeprecated, + tentative: cur_a.DadState == IpDadStateTentative, + duplicated: cur_a.DadState == IpDadStateDuplicate, + temporary: cur_a.SuffixOrigin == IpSuffixOriginRandom, + permanent: false, + }); } Err(_) => {} }, @@ -247,6 +258,7 @@ pub fn interfaces() -> Vec { ipv4: ipv4_vec, ipv6: ipv6_vec, ipv6_scope_ids: ipv6_scope_id_vec, + ipv6_addr_flags: ipv6_flags_vec, flags, oper_state, transmit_speed: sanitize_u64(cur.TransmitLinkSpeed),