use linux_raw_sys::{
if_arp::{self, ARPHRD_IPGRE, ARPHRD_TUNNEL, ARPHRD_TUNNEL6},
netlink::{self, NLM_F_DUMP, NLM_F_DUMP_INTR, NLM_F_REQUEST},
};
use rustix::net::{
bind, getsockname, netlink::SocketAddrNetlink, recvfrom, sendto, socket, AddressFamily,
RecvFlags, SendFlags, SocketType,
};
use smallvec_wrapper::{SmallVec, TinyVec};
use std::{collections::HashSet, io, mem, net::IpAddr, os::fd::OwnedFd};
use crate::local_ip_filter;
use super::{super::Address, Flags, Interface, MacAddr, Net, MAC_ADDRESS_SIZE};
const NLMSG_HDRLEN: usize = mem::size_of::<MessageHeader>();
const NLMSG_ALIGNTO: u32 = netlink::NLMSG_ALIGNTO;
const NLMSG_DONE: u32 = netlink::NLMSG_DONE;
const NLMSG_ERROR: u32 = netlink::NLMSG_ERROR;
const RTM_GETLINK: u32 = netlink::RTM_GETLINK as u32;
const RTM_GETADDR: u32 = netlink::RTM_GETADDR as u32;
const RTM_GETROUTE: u32 = netlink::RTM_GETROUTE as u32;
const RTM_NEWLINK: u32 = netlink::RTM_NEWLINK as u32;
const RTM_NEWADDR: u32 = netlink::RTM_NEWADDR as u32;
const RTM_NEWROUTE: u32 = netlink::RTM_NEWROUTE as u32;
const RTM_GETNEXTHOP: u32 = netlink::RTM_GETNEXTHOP as u32;
const RTM_NEWNEXTHOP: u32 = netlink::RTM_NEWNEXTHOP as u32;
const NHA_ID: u16 = 1;
const NHA_GROUP: u16 = 2;
const NHA_BLACKHOLE: u16 = 4;
const NHA_OIF: u16 = 5;
const NHA_GATEWAY: u16 = 6;
const RTA_DST: u16 = netlink::rtattr_type_t::RTA_DST as u16;
const RTA_GATEWAY: u16 = netlink::rtattr_type_t::RTA_GATEWAY as u16;
const RTA_OIF: u16 = netlink::rtattr_type_t::RTA_OIF as u16;
const RTA_PRIORITY: u16 = netlink::rtattr_type_t::RTA_PRIORITY as u16;
const RTA_MULTIPATH: u16 = netlink::rtattr_type_t::RTA_MULTIPATH as u16;
const RTA_SRC: u16 = netlink::rtattr_type_t::RTA_SRC as u16;
const RTA_TABLE: u16 = netlink::rtattr_type_t::RTA_TABLE as u16;
const RTA_NH_ID: u16 = netlink::rtattr_type_t::RTA_NH_ID as u16;
const RTA_VIA: u16 = netlink::rtattr_type_t::RTA_VIA as u16;
const RTA_PREF: u16 = netlink::rtattr_type_t::RTA_PREF as u16;
const RTNH_F_DEAD: u8 = 1;
const RTNH_F_LINKDOWN: u8 = 16;
const RTNH_F_UNRESOLVED: u8 = 32;
const RTN_UNICAST: u8 = 1;
const RTN_LOCAL: u8 = 2;
const RT_TABLE_MAIN: u16 = netlink::rt_class_t::RT_TABLE_MAIN as u16;
const RT_TABLE_LOCAL: u32 = netlink::rt_class_t::RT_TABLE_LOCAL as u32;
const RT_TABLE_DEFAULT: u32 = netlink::rt_class_t::RT_TABLE_DEFAULT as u32;
#[inline]
fn table_rank_for(table_id: u32) -> u8 {
if table_id == RT_TABLE_LOCAL {
0
} else if table_id == RT_TABLE_MAIN as u32 {
1
} else if table_id == RT_TABLE_DEFAULT {
2
} else {
u8::MAX
}
}
#[inline]
fn pref_rank_for(pref: u8) -> u8 {
const ICMPV6_ROUTER_PREF_HIGH: u8 = 0x1;
const ICMPV6_ROUTER_PREF_LOW: u8 = 0x3;
match pref {
ICMPV6_ROUTER_PREF_HIGH => 0,
ICMPV6_ROUTER_PREF_LOW => 2,
_ => 1,
}
}
const IFA_LOCAL: u32 = netlink::IFA_LOCAL as u32;
const IFA_ADDRESS: u32 = netlink::IFA_ADDRESS as u32;
const IFLA_MTU: u32 = if_arp::IFLA_MTU as u32;
const IFLA_IFNAME: u32 = if_arp::IFLA_IFNAME as u32;
const IFLA_ADDRESS: u32 = if_arp::IFLA_ADDRESS as u32;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct MessageHeader {
nlmsg_len: u32,
nlmsg_type: u16,
nlmsg_flags: u16,
nlmsg_seq: u32,
nlmsg_pid: u32,
}
struct Handle {
fd: OwnedFd,
sa: SocketAddrNetlink,
}
impl Handle {
unsafe fn new() -> io::Result<Self> {
let sock = socket(AddressFamily::NETLINK, SocketType::RAW, None)?;
let sa = SocketAddrNetlink::new(0, 0);
bind(&sock, &sa)?;
Ok(Self { fd: sock, sa })
}
unsafe fn send(&self, req: &NetlinkRouteRequest) -> io::Result<usize> {
self.send_bytes(req.as_bytes())
}
unsafe fn send_bytes(&self, bytes: &[u8]) -> io::Result<usize> {
sendto(&self.fd, bytes, SendFlags::empty(), &self.sa).map_err(Into::into)
}
unsafe fn sock(&self) -> io::Result<SocketAddrNetlink> {
getsockname(&self.fd)
.and_then(|addr| addr.try_into())
.map_err(Into::into)
}
unsafe fn recv(&self, dst: &mut [u8]) -> io::Result<usize> {
let (nr, _, _) = recvfrom(&self.fd, dst, RecvFlags::empty())?;
if nr < NLMSG_HDRLEN {
return Err(rustix::io::Errno::INVAL.into());
}
Ok(nr)
}
}
const ROUTE_RECV_BUF_SIZE: usize = 32 * 1024;
pub(super) fn netlink_interface(family: AddressFamily, ifi: u32) -> io::Result<TinyVec<Interface>> {
unsafe {
let handle = Handle::new()?;
let req = NetlinkRouteRequest::new(RTM_GETLINK as u16, 1, family.as_raw() as u8, ifi);
handle.send(&req)?;
let lsa = handle.sock()?;
let page_size = rustix::param::page_size();
let mut rb = vec![0u8; page_size];
let mut interfaces = TinyVec::new();
'outer: loop {
let nr = handle.recv(&mut rb)?;
let mut received = &rb[..nr];
while received.len() >= NLMSG_HDRLEN {
let h = decode_nlmsghdr(received);
let hlen = h.nlmsg_len as usize;
let l = nlm_align_of(hlen);
if hlen < NLMSG_HDRLEN || l > received.len() {
return Err(rustix::io::Errno::INVAL.into());
}
if h.nlmsg_seq != 1 || h.nlmsg_pid != lsa.pid() {
return Err(rustix::io::Errno::INVAL.into());
}
let msg_buf = &received[NLMSG_HDRLEN..hlen];
match h.nlmsg_type as u32 {
NLMSG_DONE => break 'outer,
NLMSG_ERROR => return Err(rustix::io::Errno::INVAL.into()),
val if val == RTM_NEWLINK => {
let info_hdr = IfInfoMessageHeader::parse(msg_buf)?;
let mut info_data = &msg_buf[IfInfoMessageHeader::SIZE..];
if ifi != 0 && ifi != info_hdr.index as u32 {
received = &received[l..];
continue;
}
let mut interface = Interface::new(
info_hdr.index as u32,
Flags::from_bits_truncate(info_hdr.flags),
);
while info_data.len() >= RtAttr::SIZE {
let attr = RtAttr {
len: u16::from_ne_bytes(info_data[..2].try_into().unwrap()),
ty: u16::from_ne_bytes(info_data[2..4].try_into().unwrap()),
};
let attrlen = attr.len as usize;
if attrlen < RtAttr::SIZE || attrlen > info_data.len() {
return Err(rustix::io::Errno::INVAL.into());
}
let data = &info_data[RtAttr::SIZE..attrlen];
let alen = rta_align_of(attrlen).min(info_data.len());
match attr.ty as u32 {
IFLA_MTU if data.len() >= 4 => {
interface.mtu = u32::from_ne_bytes(data[..4].try_into().unwrap());
}
IFLA_IFNAME => {
let nul = data.iter().position(|&b| b == 0).unwrap_or(data.len());
interface.name = String::from_utf8_lossy(&data[..nul]).as_ref().into();
}
IFLA_ADDRESS => match data.len() {
4 if info_hdr.ty == ARPHRD_IPGRE as u16
|| info_hdr.ty == ARPHRD_TUNNEL as u16 =>
{
info_data = &info_data[alen..];
continue;
}
16 if info_hdr.ty == ARPHRD_TUNNEL6 as u16 || info_hdr.ty == 823 => {
info_data = &info_data[alen..];
continue;
} _ => {
let mut nonzero = false;
for b in data {
if *b != 0 {
nonzero = true;
break;
}
}
if nonzero {
let mut buf = [0; MAC_ADDRESS_SIZE];
let len = data.len().min(MAC_ADDRESS_SIZE);
buf[..len].copy_from_slice(&data[..len]);
interface.mac_addr = Some(MacAddr::from_raw(buf));
}
}
},
_ => {}
}
info_data = &info_data[alen..];
}
interfaces.push(interface);
}
_ => {}
}
received = &received[l..];
}
}
Ok(interfaces)
}
}
pub(super) fn netlink_addr<N, F>(family: AddressFamily, ifi: u32, f: F) -> io::Result<SmallVec<N>>
where
N: Net,
F: FnMut(&IpAddr) -> bool,
{
let mut out = SmallVec::new();
netlink_addr_into(family, ifi, f, &mut out)?;
Ok(out)
}
pub(super) fn netlink_addr_into<N, F>(
family: AddressFamily,
ifi: u32,
mut f: F,
addrs: &mut SmallVec<N>,
) -> io::Result<()>
where
N: Net,
F: FnMut(&IpAddr) -> bool,
{
unsafe {
let handle = Handle::new()?;
let req = NetlinkRouteRequest::new(RTM_GETADDR as u16, 1, family.as_raw() as u8, ifi);
handle.send(&req)?;
let lsa = handle.sock()?;
let page_size = rustix::param::page_size();
let mut rb = vec![0u8; page_size];
'outer: loop {
let nr = handle.recv(&mut rb)?;
let mut received = &rb[..nr];
while received.len() >= NLMSG_HDRLEN {
let h = decode_nlmsghdr(received);
let hlen = h.nlmsg_len as usize;
let l = nlm_align_of(hlen);
if hlen < NLMSG_HDRLEN || l > received.len() {
return Err(rustix::io::Errno::INVAL.into());
}
if h.nlmsg_seq != 1 || h.nlmsg_pid != lsa.pid() {
return Err(rustix::io::Errno::INVAL.into());
}
let msg_buf = &received[NLMSG_HDRLEN..hlen];
match h.nlmsg_type as u32 {
NLMSG_DONE => break 'outer,
NLMSG_ERROR => return Err(rustix::io::Errno::INVAL.into()),
val if val == RTM_NEWADDR => {
let ifam = IfNetMessageHeader::parse(msg_buf)?;
let mut ifa_msg_data = &msg_buf[IfNetMessageHeader::SIZE..];
let mut point_to_point = false;
let mut attrs = SmallVec::new();
while ifa_msg_data.len() >= RtAttr::SIZE {
let attr = RtAttr {
len: u16::from_ne_bytes(ifa_msg_data[..2].try_into().unwrap()),
ty: u16::from_ne_bytes(ifa_msg_data[2..4].try_into().unwrap()),
};
let attrlen = attr.len as usize;
if attrlen < RtAttr::SIZE || attrlen > ifa_msg_data.len() {
return Err(rustix::io::Errno::INVAL.into());
}
let data = &ifa_msg_data[RtAttr::SIZE..attrlen];
let alen = rta_align_of(attrlen).min(ifa_msg_data.len());
if ifi == 0 || ifi == ifam.index {
attrs.push((attr, data));
}
ifa_msg_data = &ifa_msg_data[alen..];
}
for (attr, _) in attrs.iter() {
if attr.ty == IFA_LOCAL as u16 {
point_to_point = true;
break;
}
}
for (attr, data) in attrs.iter() {
if point_to_point && attr.ty == IFA_ADDRESS as u16 {
continue;
}
match AddressFamily::from_raw(ifam.family as u16) {
AddressFamily::INET if data.len() >= 4 => {
let ip: [u8; 4] = data[..4].try_into().unwrap();
if attr.ty == IFA_ADDRESS as u16 || attr.ty == IFA_LOCAL as u16 {
if let Some(addr) =
N::try_from_with_filter(ifam.index, ip.into(), ifam.prefix_len, |addr| {
f(addr)
})
{
addrs.push(addr);
}
}
}
AddressFamily::INET6 if data.len() >= 16 => {
let ip: [u8; 16] = data[..16].try_into().unwrap();
if attr.ty == IFA_ADDRESS as u16 || attr.ty == IFA_LOCAL as u16 {
if let Some(addr) =
N::try_from_with_filter(ifam.index, ip.into(), ifam.prefix_len, |addr| {
f(addr)
})
{
addrs.push(addr);
}
}
}
_ => {}
}
}
}
_ => {}
}
received = &received[l..];
}
}
Ok(())
}
}
pub fn netlink_best_local_addrs<N>(family: AddressFamily) -> io::Result<SmallVec<N>>
where
N: Net,
{
let mut out = SmallVec::new();
netlink_best_local_addrs_into(family, &mut out)?;
Ok(out)
}
pub fn netlink_best_local_addrs_into<N>(
family: AddressFamily,
out: &mut SmallVec<N>,
) -> io::Result<()>
where
N: Net,
{
unsafe {
let mut deferred_best: Vec<(u8, u32, u8, u32)> = Vec::new();
let handle = Handle::new()?;
let req = NetlinkRouteRequest::new(RTM_GETROUTE as u16, 1, family.as_raw() as u8, 0);
handle.send(&req)?;
let lsa = handle.sock()?;
let mut rb = vec![0u8; ROUTE_RECV_BUF_SIZE];
let mut best_oifs: SmallVec<u32> = SmallVec::new();
let mut best_rank: u8 = u8::MAX;
let mut best_metric: u32 = u32::MAX;
let mut best_pref_rank: u8 = u8::MAX;
'outer: loop {
let nr = handle.recv(&mut rb)?;
let mut received = &rb[..nr];
while received.len() >= NLMSG_HDRLEN {
let h = decode_nlmsghdr(received);
let hlen = h.nlmsg_len as usize;
let l = nlm_align_of(hlen);
if hlen < NLMSG_HDRLEN || l > received.len() {
return Err(rustix::io::Errno::INVAL.into());
}
if h.nlmsg_seq != 1 || h.nlmsg_pid != lsa.pid() {
return Err(rustix::io::Errno::INVAL.into());
}
match h.nlmsg_type as u32 {
NLMSG_DONE => {
if h.nlmsg_flags as u32 & NLM_F_DUMP_INTR != 0 {
return Err(rustix::io::Errno::INTR.into());
}
break 'outer;
}
NLMSG_ERROR => match decode_nlmsgerr(received, hlen)? {
NlmsgErrOutcome::Ack => {
received = &received[l..];
continue;
}
NlmsgErrOutcome::FamilyUnavailable => return Ok(()),
},
val if val == RTM_NEWROUTE => {
let rtm = &received[NLMSG_HDRLEN..hlen];
let rtm_header = RtmMessageHeader::parse(rtm)?;
if rtm_header.rtm_type != RTN_UNICAST && rtm_header.rtm_type != RTN_LOCAL {
received = &received[l..];
continue;
}
if rtm_header.rtm_tos != 0 || rtm_header.rtm_src_len != 0 {
received = &received[l..];
continue;
}
if rtm_header.rtm_dst_len != 0 {
received = &received[l..];
continue;
}
let mut rtattr_buf = &rtm[RtmMessageHeader::SIZE..];
let mut current_metric = None;
let mut current_oifs: SmallVec<u32> = SmallVec::new();
let mut have_top_oif = false;
let mut multipath: Option<&[u8]> = None;
let mut nh_id: Option<u32> = None;
let mut table_id: u32 = rtm_header.rtm_table as u32;
let mut has_src_constraint = false;
let mut dst_specific = false;
let mut dst_malformed = false;
let mut current_pref: u8 = 0;
while rtattr_buf.len() >= RtAttr::SIZE {
let attr = RtAttr {
len: u16::from_ne_bytes(rtattr_buf[..2].try_into().unwrap()),
ty: u16::from_ne_bytes(rtattr_buf[2..4].try_into().unwrap()),
};
let attrlen = attr.len as usize;
if attrlen < RtAttr::SIZE || attrlen > rtattr_buf.len() {
return Err(rustix::io::Errno::INVAL.into());
}
let data = &rtattr_buf[RtAttr::SIZE..attrlen];
let alen = rta_align_of(attrlen).min(rtattr_buf.len());
match attr.ty {
RTA_PRIORITY if data.len() >= 4 => {
current_metric = Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
RTA_OIF if data.len() >= 4 => {
let idx = u32::from_ne_bytes(data[..4].try_into().unwrap());
if idx != 0 {
current_oifs.push(idx);
}
have_top_oif = true;
}
RTA_DST => match parse_rta_ipaddr(rtm_header.rtm_family, data) {
Some(addr) if !addr.is_unspecified() => {
dst_specific = true;
}
Some(_) => {}
None => {
dst_malformed = true;
}
},
RTA_MULTIPATH => {
multipath = Some(data);
}
RTA_NH_ID if data.len() >= 4 => {
nh_id = Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
RTA_TABLE if data.len() >= 4 => {
table_id = u32::from_ne_bytes(data[..4].try_into().unwrap());
}
RTA_SRC => {
has_src_constraint = true;
}
RTA_PREF if !data.is_empty() => {
current_pref = data[0];
}
_ => {}
}
rtattr_buf = &rtattr_buf[alen..];
}
if has_src_constraint
|| dst_specific
|| dst_malformed
|| (table_id != RT_TABLE_MAIN as u32
&& table_id != RT_TABLE_LOCAL
&& table_id != RT_TABLE_DEFAULT)
{
received = &received[l..];
continue;
}
if !have_top_oif {
if let Some(mp) = multipath {
multipath_oifs_into(mp, &mut current_oifs);
} else if let Some(id) = nh_id {
let metric = current_metric.unwrap_or(0);
let rank = table_rank_for(table_id);
let pref_rank = pref_rank_for(current_pref);
deferred_best.push((rank, metric, pref_rank, id));
received = &received[l..];
continue;
}
}
if !current_oifs.is_empty() {
let metric = current_metric.unwrap_or(0);
let rank = table_rank_for(table_id);
let pref_rank = pref_rank_for(current_pref);
let cur_key = (rank, metric, pref_rank);
let best_key = (best_rank, best_metric, best_pref_rank);
if cur_key < best_key {
best_rank = rank;
best_metric = metric;
best_pref_rank = pref_rank;
best_oifs.clear();
best_oifs.extend(current_oifs.iter().copied());
} else if cur_key == best_key {
best_oifs.extend(current_oifs.iter().copied());
}
}
}
_ => {}
}
received = &received[l..];
}
}
if !deferred_best.is_empty() {
let nh_map = dump_nexthops()?;
for (rank, metric, pref_rank, id) in deferred_best {
match resolve_nh_id(&nh_map, id) {
None => return Err(rustix::io::Errno::INTR.into()),
Some(resolved) => {
let oifs: SmallVec<u32> = resolved
.iter()
.filter_map(|(oif, _)| if *oif != 0 { Some(*oif) } else { None })
.collect();
if oifs.is_empty() {
continue;
}
let cur_key = (rank, metric, pref_rank);
let best_key = (best_rank, best_metric, best_pref_rank);
if cur_key < best_key {
best_rank = rank;
best_metric = metric;
best_pref_rank = pref_rank;
best_oifs.clear();
best_oifs.extend(oifs);
} else if cur_key == best_key {
best_oifs.extend(oifs);
}
}
}
}
}
best_oifs.sort_unstable();
best_oifs.dedup();
for idx in best_oifs {
netlink_addr_into(family, idx, local_ip_filter, out)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
struct NexthopInfo {
oif: u32,
gw: Option<IpAddr>,
group: Option<SmallVec<u32>>,
filtered: bool,
}
fn build_nh_dump_request(seq: u32) -> [u8; 24] {
let mut bytes = [0u8; 24];
bytes[0..4].copy_from_slice(&24u32.to_ne_bytes());
bytes[4..6].copy_from_slice(&(RTM_GETNEXTHOP as u16).to_ne_bytes());
bytes[6..8].copy_from_slice(&((NLM_F_DUMP | NLM_F_REQUEST) as u16).to_ne_bytes());
bytes[8..12].copy_from_slice(&seq.to_ne_bytes());
bytes[12..16].copy_from_slice(&std::process::id().to_ne_bytes());
bytes
}
fn dump_nexthops() -> io::Result<std::collections::HashMap<u32, NexthopInfo>> {
use std::collections::HashMap;
unsafe {
let handle = Handle::new()?;
let req = build_nh_dump_request(1);
handle.send_bytes(&req)?;
let lsa = handle.sock()?;
let mut rb = vec![0u8; ROUTE_RECV_BUF_SIZE];
let mut map: HashMap<u32, NexthopInfo> = HashMap::new();
'outer: loop {
let nr = handle.recv(&mut rb)?;
let mut received = &rb[..nr];
while received.len() >= NLMSG_HDRLEN {
let h = decode_nlmsghdr(received);
let hlen = h.nlmsg_len as usize;
let l = nlm_align_of(hlen);
if hlen < NLMSG_HDRLEN || l > received.len() {
return Err(rustix::io::Errno::INVAL.into());
}
if h.nlmsg_seq != 1 || h.nlmsg_pid != lsa.pid() {
return Err(rustix::io::Errno::INVAL.into());
}
match h.nlmsg_type as u32 {
NLMSG_DONE => {
if h.nlmsg_flags as u32 & NLM_F_DUMP_INTR != 0 {
return Err(rustix::io::Errno::INTR.into());
}
break 'outer;
}
NLMSG_ERROR => match decode_nlmsgerr(received, hlen)? {
NlmsgErrOutcome::Ack => {
received = &received[l..];
continue;
}
NlmsgErrOutcome::FamilyUnavailable => return Ok(map),
},
val if val == RTM_NEWNEXTHOP => {
if hlen < NLMSG_HDRLEN + 8 {
received = &received[l..];
continue;
}
let nh_family = received[NLMSG_HDRLEN];
let nh_flags = u32::from_ne_bytes(
received[NLMSG_HDRLEN + 4..NLMSG_HDRLEN + 8]
.try_into()
.unwrap(),
);
let unusable = (RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_UNRESOLVED) as u32;
let nh_unusable = nh_flags & unusable != 0;
let mut attr_buf = &received[NLMSG_HDRLEN + 8..hlen];
let mut id: u32 = 0;
let mut oif: u32 = 0;
let mut gw: Option<IpAddr> = None;
let mut gw_malformed = false;
let mut group: Option<SmallVec<u32>> = None;
let mut blackhole = false;
let mut attr_malformed = false;
while attr_buf.len() >= RtAttr::SIZE {
let attr = RtAttr {
len: u16::from_ne_bytes(attr_buf[..2].try_into().unwrap()),
ty: u16::from_ne_bytes(attr_buf[2..4].try_into().unwrap()),
};
let attrlen = attr.len as usize;
if attrlen < RtAttr::SIZE || attrlen > attr_buf.len() {
attr_malformed = true;
break;
}
let data = &attr_buf[RtAttr::SIZE..attrlen];
let alen = rta_align_of(attrlen).min(attr_buf.len());
match attr.ty {
NHA_ID if data.len() >= 4 => {
id = u32::from_ne_bytes(data[..4].try_into().unwrap());
}
NHA_OIF if data.len() >= 4 => {
oif = u32::from_ne_bytes(data[..4].try_into().unwrap());
}
NHA_GATEWAY => {
gw = parse_rta_ipaddr(nh_family, data);
if gw.is_none() {
gw_malformed = true;
}
}
NHA_GROUP => {
let mut members: SmallVec<u32> = SmallVec::new();
let mut p = data;
while p.len() >= 8 {
members.push(u32::from_ne_bytes(p[..4].try_into().unwrap()));
p = &p[8..];
}
group = Some(members);
}
NHA_BLACKHOLE => {
blackhole = true;
}
_ => {}
}
attr_buf = &attr_buf[alen..];
}
if id != 0 {
let filtered = blackhole || nh_unusable || gw_malformed || attr_malformed;
map.insert(
id,
NexthopInfo {
oif,
gw,
group,
filtered,
},
);
}
}
_ => {}
}
received = &received[l..];
}
}
Ok(map)
}
}
fn resolve_nh_id(
map: &std::collections::HashMap<u32, NexthopInfo>,
id: u32,
) -> Option<SmallVec<(u32, Option<IpAddr>)>> {
let nh = map.get(&id)?;
let mut out: SmallVec<(u32, Option<IpAddr>)> = SmallVec::new();
if nh.filtered {
return Some(out);
}
if let Some(members) = &nh.group {
for member_id in members {
match map.get(member_id) {
Some(member) => {
if member.group.is_none() && !member.filtered && member.oif != 0 {
out.push((member.oif, member.gw));
}
}
None => {
return None;
}
}
}
} else if nh.oif != 0 {
out.push((nh.oif, nh.gw));
}
Some(out)
}
pub(super) fn netlink_walk_routes<F>(family: AddressFamily, mut on_route: F) -> io::Result<()>
where
F: FnMut(u8, u32, u8, Option<IpAddr>, Option<IpAddr>),
{
unsafe {
let mut deferred_nh: Vec<(u8, u8, Option<IpAddr>, u32)> = Vec::new();
let handle = Handle::new()?;
let req = NetlinkRouteRequest::new(RTM_GETROUTE as u16, 1, family.as_raw() as u8, 0);
handle.send(&req)?;
let lsa = handle.sock()?;
let mut rb = vec![0u8; ROUTE_RECV_BUF_SIZE];
'outer: loop {
let nr = handle.recv(&mut rb)?;
let mut received = &rb[..nr];
while received.len() >= NLMSG_HDRLEN {
let h = decode_nlmsghdr(received);
let hlen = h.nlmsg_len as usize;
let l = nlm_align_of(hlen);
if hlen < NLMSG_HDRLEN || l > received.len() {
return Err(rustix::io::Errno::INVAL.into());
}
if h.nlmsg_seq != 1 || h.nlmsg_pid != lsa.pid() {
return Err(rustix::io::Errno::INVAL.into());
}
match h.nlmsg_type as u32 {
NLMSG_DONE => {
if h.nlmsg_flags as u32 & NLM_F_DUMP_INTR != 0 {
return Err(rustix::io::Errno::INTR.into());
}
break 'outer;
}
NLMSG_ERROR => match decode_nlmsgerr(received, hlen)? {
NlmsgErrOutcome::Ack => {
received = &received[l..];
continue;
}
NlmsgErrOutcome::FamilyUnavailable => return Ok(()),
},
val if val == RTM_NEWROUTE => {
let rtm = &received[NLMSG_HDRLEN..hlen];
let rtm_header = RtmMessageHeader::parse(rtm)?;
if rtm_header.rtm_type != RTN_UNICAST && rtm_header.rtm_type != RTN_LOCAL {
received = &received[l..];
continue;
}
if rtm_header.rtm_src_len != 0 {
received = &received[l..];
continue;
}
if rtm_header.rtm_tos != 0 {
received = &received[l..];
continue;
}
let mut rtattr_buf = &rtm[RtmMessageHeader::SIZE..];
let mut oif: u32 = 0;
let mut dst: Option<IpAddr> = None;
let mut gw: Option<IpAddr> = None;
let mut has_src_constraint = false;
let mut dst_present = false;
let mut dst_malformed = false;
let mut gw_malformed = false;
let mut has_via = false;
let mut table_id: u32 = rtm_header.rtm_table as u32;
let mut nh_id: Option<u32> = None;
let mut multipath: Option<&[u8]> = None;
while rtattr_buf.len() >= RtAttr::SIZE {
let attr = RtAttr {
len: u16::from_ne_bytes(rtattr_buf[..2].try_into().unwrap()),
ty: u16::from_ne_bytes(rtattr_buf[2..4].try_into().unwrap()),
};
let attrlen = attr.len as usize;
if attrlen < RtAttr::SIZE || attrlen > rtattr_buf.len() {
return Err(rustix::io::Errno::INVAL.into());
}
let data = &rtattr_buf[RtAttr::SIZE..attrlen];
let alen = rta_align_of(attrlen).min(rtattr_buf.len());
match attr.ty {
RTA_OIF if data.len() >= 4 => {
oif = u32::from_ne_bytes(data[..4].try_into().unwrap());
}
RTA_DST => {
dst_present = true;
dst = parse_rta_ipaddr(rtm_header.rtm_family, data);
if dst.is_none() {
dst_malformed = true;
}
}
RTA_GATEWAY => {
gw = parse_rta_ipaddr(rtm_header.rtm_family, data);
if gw.is_none() {
gw_malformed = true;
}
}
RTA_VIA => {
has_via = true;
}
RTA_MULTIPATH => {
multipath = Some(data);
}
RTA_SRC => {
has_src_constraint = true;
}
RTA_TABLE if data.len() >= 4 => {
table_id = u32::from_ne_bytes(data[..4].try_into().unwrap());
}
RTA_NH_ID if data.len() >= 4 => {
nh_id = Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
_ => {}
}
rtattr_buf = &rtattr_buf[alen..];
}
if dst_malformed
|| gw_malformed
|| has_via
|| (dst.is_none() && rtm_header.rtm_dst_len != 0)
{
received = &received[l..];
continue;
}
let _ = dst_present;
if has_src_constraint {
received = &received[l..];
continue;
}
if table_id != RT_TABLE_MAIN as u32
&& table_id != RT_TABLE_LOCAL
&& table_id != RT_TABLE_DEFAULT
{
received = &received[l..];
continue;
}
if let Some(id) = nh_id {
deferred_nh.push((rtm_header.rtm_family, rtm_header.rtm_dst_len, dst, id));
received = &received[l..];
continue;
}
if let Some(mp) = multipath {
walk_multipath(
rtm_header.rtm_family,
rtm_header.rtm_dst_len,
dst,
mp,
&mut on_route,
);
received = &received[l..];
continue;
}
if oif == 0 {
received = &received[l..];
continue;
}
on_route(rtm_header.rtm_family, oif, rtm_header.rtm_dst_len, dst, gw);
}
_ => {}
}
received = &received[l..];
}
}
if !deferred_nh.is_empty() {
let nh_map = dump_nexthops()?;
for (rfamily, dst_len, dst, id) in deferred_nh {
match resolve_nh_id(&nh_map, id) {
None => return Err(rustix::io::Errno::INTR.into()),
Some(resolved) => {
for (nh_oif, nh_gw) in resolved {
on_route(rfamily, nh_oif, dst_len, dst, nh_gw);
}
}
}
}
}
Ok(())
}
}
fn walk_multipath<F>(
rtm_family: u8,
dst_len: u8,
dst: Option<IpAddr>,
mut buf: &[u8],
on_route: &mut F,
) where
F: FnMut(u8, u32, u8, Option<IpAddr>, Option<IpAddr>),
{
const RTNH_SIZE: usize = 8;
while buf.len() >= RTNH_SIZE {
let nh_len = u16::from_ne_bytes(buf[..2].try_into().unwrap()) as usize;
if nh_len < RTNH_SIZE || nh_len > buf.len() {
break;
}
let nh_flags = buf[2];
let nh_ifindex = i32::from_ne_bytes(buf[4..8].try_into().unwrap()) as u32;
let unusable = RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_UNRESOLVED;
if nh_flags & unusable != 0 {
let nh_aligned = rta_align_of(nh_len).min(buf.len());
if nh_aligned == 0 {
break;
}
buf = &buf[nh_aligned..];
continue;
}
let mut nh_gw: Option<IpAddr> = None;
let mut nh_gw_malformed = false;
let mut nh_has_via = false;
let mut nh_truncated = false;
let mut sub = &buf[RTNH_SIZE..nh_len];
while sub.len() >= RtAttr::SIZE {
let attr_len = u16::from_ne_bytes(sub[..2].try_into().unwrap()) as usize;
let attr_ty = u16::from_ne_bytes(sub[2..4].try_into().unwrap());
if attr_len < RtAttr::SIZE || attr_len > sub.len() {
nh_truncated = true;
break;
}
if attr_ty == RTA_GATEWAY {
nh_gw = parse_rta_ipaddr(rtm_family, &sub[RtAttr::SIZE..attr_len]);
if nh_gw.is_none() {
nh_gw_malformed = true;
}
} else if attr_ty == RTA_VIA {
nh_has_via = true;
}
let alen = rta_align_of(attr_len).min(sub.len());
sub = &sub[alen..];
}
if nh_ifindex != 0 && !nh_gw_malformed && !nh_has_via && !nh_truncated {
on_route(rtm_family, nh_ifindex, dst_len, dst, nh_gw);
}
let nh_aligned = rta_align_of(nh_len).min(buf.len());
if nh_aligned == 0 {
break;
}
buf = &buf[nh_aligned..];
}
}
#[derive(Debug)]
enum NlmsgErrOutcome {
Ack,
FamilyUnavailable,
}
fn decode_nlmsgerr(received: &[u8], hlen: usize) -> io::Result<NlmsgErrOutcome> {
use rustix::io::Errno;
if hlen < NLMSG_HDRLEN + 4 {
return Err(Errno::INVAL.into());
}
let errno = i32::from_ne_bytes(received[NLMSG_HDRLEN..NLMSG_HDRLEN + 4].try_into().unwrap());
if errno == 0 {
return Ok(NlmsgErrOutcome::Ack);
}
let raw = errno.unsigned_abs() as i32;
if raw == Errno::OPNOTSUPP.raw_os_error()
|| raw == Errno::PROTONOSUPPORT.raw_os_error()
|| raw == Errno::AFNOSUPPORT.raw_os_error()
{
return Ok(NlmsgErrOutcome::FamilyUnavailable);
}
Err(io::Error::from_raw_os_error(raw))
}
fn multipath_oifs_into(mut buf: &[u8], out: &mut SmallVec<u32>) {
const RTNH_SIZE: usize = 8;
let unusable = RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_UNRESOLVED;
while buf.len() >= RTNH_SIZE {
let nh_len = u16::from_ne_bytes(buf[..2].try_into().unwrap()) as usize;
if nh_len < RTNH_SIZE || nh_len > buf.len() {
break;
}
let nh_flags = buf[2];
let nh_ifindex = i32::from_ne_bytes(buf[4..8].try_into().unwrap()) as u32;
if nh_flags & unusable == 0 && nh_ifindex != 0 {
out.push(nh_ifindex);
}
let nh_aligned = rta_align_of(nh_len).min(buf.len());
if nh_aligned == 0 {
break;
}
buf = &buf[nh_aligned..];
}
}
#[inline]
fn parse_rta_ipaddr(rtm_family: u8, data: &[u8]) -> Option<IpAddr> {
match AddressFamily::from_raw(rtm_family as u16) {
AddressFamily::INET if data.len() >= 4 => {
let bytes: [u8; 4] = data[..4].try_into().unwrap();
Some(IpAddr::V4(bytes.into()))
}
AddressFamily::INET6 if data.len() >= 16 => {
let bytes: [u8; 16] = data[..16].try_into().unwrap();
Some(IpAddr::V6(bytes.into()))
}
_ => None,
}
}
pub(super) fn rt_generic_addrs<A, F>(
family: AddressFamily,
rta: u16,
rtn: Option<u8>,
mut f: F,
) -> io::Result<SmallVec<A>>
where
A: Address + Eq,
F: FnMut(&IpAddr) -> bool,
{
unsafe {
let mut deferred_nh: SmallVec<u32> = SmallVec::new();
let handle = Handle::new()?;
let req = NetlinkRouteRequest::new(RTM_GETROUTE as u16, 1, family.as_raw() as u8, 0);
handle.send(&req)?;
let lsa = handle.sock()?;
let mut rb = vec![0u8; ROUTE_RECV_BUF_SIZE];
let mut gateways = SmallVec::new();
let mut seen: HashSet<(u32, IpAddr)> = HashSet::new();
'outer: loop {
let nr = handle.recv(&mut rb)?;
let mut received = &rb[..nr];
while received.len() >= NLMSG_HDRLEN {
let h = decode_nlmsghdr(received);
let hlen = h.nlmsg_len as usize;
let l = nlm_align_of(hlen);
if hlen < NLMSG_HDRLEN || l > received.len() {
return Err(rustix::io::Errno::INVAL.into());
}
if h.nlmsg_seq != 1 || h.nlmsg_pid != lsa.pid() {
return Err(rustix::io::Errno::INVAL.into());
}
match h.nlmsg_type as u32 {
NLMSG_DONE => {
if h.nlmsg_flags as u32 & NLM_F_DUMP_INTR != 0 {
return Err(rustix::io::Errno::INTR.into());
}
break 'outer;
}
NLMSG_ERROR => match decode_nlmsgerr(received, hlen)? {
NlmsgErrOutcome::Ack => {
received = &received[l..];
continue;
}
NlmsgErrOutcome::FamilyUnavailable => return Ok(SmallVec::new()),
},
val if val == RTM_NEWROUTE => {
let rtm = &received[NLMSG_HDRLEN..hlen];
let rtm_header = RtmMessageHeader::parse(rtm)?;
if let Some(rtn) = rtn {
if rtm_header.rtm_type != rtn {
received = &received[l..];
continue;
}
}
let mut rtattr_buf = &rtm[RtmMessageHeader::SIZE..];
let mut tmp_addrs: SmallVec<IpAddr> = SmallVec::new();
let mut current_ifi = 0;
let mut multipath: Option<&[u8]> = None;
let mut nh_id: Option<u32> = None;
while rtattr_buf.len() >= RtAttr::SIZE {
let attr = RtAttr {
len: u16::from_ne_bytes(rtattr_buf[..2].try_into().unwrap()),
ty: u16::from_ne_bytes(rtattr_buf[2..4].try_into().unwrap()),
};
let attrlen = attr.len as usize;
if attrlen < RtAttr::SIZE || attrlen > rtattr_buf.len() {
return Err(rustix::io::Errno::INVAL.into());
}
let data = &rtattr_buf[RtAttr::SIZE..attrlen];
let alen = rta_align_of(attrlen).min(rtattr_buf.len());
match attr.ty {
val if val == rta => match (
family,
AddressFamily::from_raw(rtm_header.rtm_family as u16),
) {
(AddressFamily::INET, AddressFamily::INET)
| (AddressFamily::UNSPEC, AddressFamily::INET)
if data.len() >= 4 =>
{
let bytes: [u8; 4] = data[..4].try_into().unwrap();
let addr = IpAddr::V4(bytes.into());
if f(&addr) {
tmp_addrs.push(addr);
}
}
(AddressFamily::INET6, AddressFamily::INET6)
| (AddressFamily::UNSPEC, AddressFamily::INET6)
if data.len() >= 16 =>
{
let bytes: [u8; 16] = data[..16].try_into().unwrap();
let addr = IpAddr::V6(bytes.into());
if f(&addr) {
tmp_addrs.push(addr);
}
}
_ => {}
},
RTA_OIF => {
if data.len() >= 4 {
let idx = u32::from_ne_bytes(data[..4].try_into().unwrap());
current_ifi = idx;
}
}
RTA_MULTIPATH => {
multipath = Some(data);
}
RTA_NH_ID if data.len() >= 4 => {
nh_id = Some(u32::from_ne_bytes(data[..4].try_into().unwrap()));
}
_ => {}
}
rtattr_buf = &rtattr_buf[alen..];
}
let mut emit = |idx: u32, raw: IpAddr| {
if let Some(addr) = A::try_from(idx, raw) {
if seen.insert((addr.index(), addr.addr())) {
gateways.push(addr);
}
}
};
for raw in tmp_addrs.drain(..) {
emit(current_ifi, raw);
}
if rta == RTA_GATEWAY {
if let Some(mp) = multipath {
multipath_gateways_into(rtm_header.rtm_family, mp, &mut |idx, gw| {
if f(&gw) {
emit(idx, gw);
}
});
}
if let Some(id) = nh_id {
deferred_nh.push(id);
}
}
}
_ => {}
}
received = &received[l..];
}
}
if !deferred_nh.is_empty() {
let nh_map = dump_nexthops()?;
for id in deferred_nh {
if let Some(resolved) = resolve_nh_id(&nh_map, id) {
for (oif, maybe_gw) in resolved {
if let Some(gw) = maybe_gw {
if f(&gw) {
if let Some(addr) = A::try_from(oif, gw) {
if seen.insert((addr.index(), addr.addr())) {
gateways.push(addr);
}
}
}
}
}
}
}
}
Ok(gateways)
}
}
fn multipath_gateways_into<F>(rtm_family: u8, mut buf: &[u8], sink: &mut F)
where
F: FnMut(u32, IpAddr),
{
const RTNH_SIZE: usize = 8;
let unusable = RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_UNRESOLVED;
while buf.len() >= RTNH_SIZE {
let nh_len = u16::from_ne_bytes(buf[..2].try_into().unwrap()) as usize;
if nh_len < RTNH_SIZE || nh_len > buf.len() {
break;
}
let nh_flags = buf[2];
let nh_ifindex = i32::from_ne_bytes(buf[4..8].try_into().unwrap()) as u32;
if nh_flags & unusable == 0 && nh_ifindex != 0 {
let mut sub = &buf[RTNH_SIZE..nh_len];
while sub.len() >= RtAttr::SIZE {
let attr_len = u16::from_ne_bytes(sub[..2].try_into().unwrap()) as usize;
let attr_ty = u16::from_ne_bytes(sub[2..4].try_into().unwrap());
if attr_len < RtAttr::SIZE || attr_len > sub.len() {
break;
}
if attr_ty == RTA_GATEWAY {
if let Some(gw) = parse_rta_ipaddr(rtm_family, &sub[RtAttr::SIZE..attr_len]) {
sink(nh_ifindex, gw);
}
}
let alen = rta_align_of(attr_len).min(sub.len());
sub = &sub[alen..];
}
}
let nh_aligned = rta_align_of(nh_len).min(buf.len());
if nh_aligned == 0 {
break;
}
buf = &buf[nh_aligned..];
}
}
#[repr(C)]
#[derive(Debug)]
struct RtmMessageHeader {
rtm_family: u8,
rtm_dst_len: u8,
rtm_src_len: u8,
rtm_tos: u8,
rtm_table: u8,
rtm_protocol: u8,
rtm_scope: u8,
rtm_type: u8,
rtm_flags: u32,
}
impl RtmMessageHeader {
const SIZE: usize = std::mem::size_of::<Self>();
#[inline]
fn parse(src: &[u8]) -> io::Result<Self> {
if src.len() < Self::SIZE {
return Err(rustix::io::Errno::INVAL.into());
}
Ok(Self {
rtm_family: src[0],
rtm_dst_len: src[1],
rtm_src_len: src[2],
rtm_tos: src[3],
rtm_table: src[4],
rtm_protocol: src[5],
rtm_scope: src[6],
rtm_type: src[7],
rtm_flags: u32::from_ne_bytes(src[8..12].try_into().unwrap()),
})
}
}
#[inline]
const fn nlm_align_of(msg_len: usize) -> usize {
((msg_len as u32 + NLMSG_ALIGNTO - 1) & !(NLMSG_ALIGNTO - 1)) as usize
}
#[inline]
const fn rta_align_of(attrlen: usize) -> usize {
const RTA_ALIGNTO: usize = 0x4;
(attrlen + RTA_ALIGNTO - 1) & !(RTA_ALIGNTO - 1)
}
struct NetlinkRouteRequest {
bytes: [u8; Self::SIZE],
}
impl NetlinkRouteRequest {
const SIZE: usize = (mem::size_of::<MessageHeader>()
+ mem::size_of::<u8>() + (NLMSG_ALIGNTO as usize - 1))
& !(NLMSG_ALIGNTO as usize - 1);
#[inline]
fn new(proto: u16, seq: u32, family: u8, _ifi: u32) -> Self {
let mut bytes = [0u8; Self::SIZE];
bytes[0..4].copy_from_slice(&(Self::SIZE as u32).to_ne_bytes());
bytes[4..6].copy_from_slice(&proto.to_ne_bytes());
bytes[6..8].copy_from_slice(&((NLM_F_DUMP | NLM_F_REQUEST) as u16).to_ne_bytes());
bytes[8..12].copy_from_slice(&seq.to_ne_bytes());
bytes[12..16].copy_from_slice(&std::process::id().to_ne_bytes());
bytes[16] = family;
Self { bytes }
}
#[inline]
fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
#[repr(C)]
#[derive(Debug)]
struct IfInfoMessageHeader {
family: u8,
x_ifi_pad: u8,
ty: u16,
index: i32,
flags: u32,
change: u32,
}
impl IfInfoMessageHeader {
const SIZE: usize = mem::size_of::<Self>();
#[inline]
fn parse(src: &[u8]) -> io::Result<Self> {
if src.len() < Self::SIZE {
return Err(rustix::io::Errno::INVAL.into());
}
Ok(Self {
family: src[0],
x_ifi_pad: src[1],
ty: u16::from_ne_bytes(src[2..4].try_into().unwrap()),
index: i32::from_ne_bytes(src[4..8].try_into().unwrap()),
flags: u32::from_ne_bytes(src[8..12].try_into().unwrap()),
change: u32::from_ne_bytes(src[12..16].try_into().unwrap()),
})
}
}
#[repr(C)]
struct RtAttr {
len: u16,
ty: u16,
}
impl RtAttr {
const SIZE: usize = mem::size_of::<Self>();
}
#[repr(C)]
#[derive(Debug)]
struct IfNetMessageHeader {
family: u8,
prefix_len: u8,
flags: u8,
scope: u8,
index: u32,
}
impl IfNetMessageHeader {
const SIZE: usize = mem::size_of::<Self>();
#[inline]
fn parse(src: &[u8]) -> io::Result<Self> {
if src.len() < Self::SIZE {
return Err(rustix::io::Errno::INVAL.into());
}
Ok(Self {
family: src[0],
prefix_len: src[1],
flags: src[2],
scope: src[3],
index: u32::from_ne_bytes(src[4..8].try_into().unwrap()),
})
}
}
#[inline]
fn decode_nlmsghdr(src: &[u8]) -> MessageHeader {
let hlen = u32::from_ne_bytes(src[..4].try_into().unwrap());
let hty = u16::from_ne_bytes(src[4..6].try_into().unwrap());
let hflags = u16::from_ne_bytes(src[6..8].try_into().unwrap());
let hseq = u32::from_ne_bytes(src[8..12].try_into().unwrap());
let hpid = u32::from_ne_bytes(src[12..16].try_into().unwrap());
MessageHeader {
nlmsg_len: hlen,
nlmsg_type: hty,
nlmsg_flags: hflags,
nlmsg_seq: hseq,
nlmsg_pid: hpid,
}
}