use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use hardware_address::xtoi2;
use ipnet::{Ipv4Net, Ipv6Net};
use rustix::net::AddressFamily;
use smallvec_wrapper::{SmallVec, TinyVec};
use smol_str::SmolStr;
use super::{
IfAddr, IfNet, Ifv4Addr, Ifv4Net, Ifv6Addr, Ifv6Net, Interface, IpRoute, Ipv4Route, Ipv6Route,
MacAddr, Net, MAC_ADDRESS_SIZE,
};
pub(super) use local_addr::*;
#[path = "linux/netlink.rs"]
mod netlink;
#[path = "linux/local_addr.rs"]
mod local_addr;
use netlink::{netlink_addr, netlink_interface, netlink_walk_routes};
macro_rules! rt_generic_mod {
($($name:ident($rta:expr, $rtn:expr)), +$(,)?) => {
$(
paste::paste! {
pub(super) use [< rt_ $name >]::*;
mod [< rt_ $name >] {
use rustix::net::AddressFamily;
use smallvec_wrapper::SmallVec;
use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use crate::{ipv4_filter_to_ip_filter, ipv6_filter_to_ip_filter};
use super::{
super::{IfAddr, Ifv4Addr, Ifv6Addr},
netlink::rt_generic_addrs,
};
pub(crate) fn [< $name _addrs >]() -> io::Result<SmallVec<IfAddr>> {
rt_generic_addrs(AddressFamily::UNSPEC, $rta, $rtn, |_| true)
}
pub(crate) fn [< $name _ipv4_addrs >]() -> io::Result<SmallVec<Ifv4Addr>> {
rt_generic_addrs(AddressFamily::INET, $rta, $rtn, |_| true)
}
pub(crate) fn [< $name _ipv6_addrs >]() -> io::Result<SmallVec<Ifv6Addr>> {
rt_generic_addrs(AddressFamily::INET6, $rta, $rtn, |_| true)
}
pub(crate) fn [< $name _addrs_by_filter >]<F>(f: F) -> io::Result<SmallVec<IfAddr>>
where
F: FnMut(&IpAddr) -> bool,
{
rt_generic_addrs(AddressFamily::UNSPEC, $rta, $rtn, f)
}
pub(crate) fn [< $name _ipv4_addrs_by_filter >]<F>(f: F) -> io::Result<SmallVec<Ifv4Addr>>
where
F: FnMut(&Ipv4Addr) -> bool,
{
rt_generic_addrs(AddressFamily::INET, $rta, $rtn, ipv4_filter_to_ip_filter(f))
}
pub(crate) fn [< $name _ipv6_addrs_by_filter >]<F>(f: F) -> io::Result<SmallVec<Ifv6Addr>>
where
F: FnMut(&Ipv6Addr) -> bool,
{
rt_generic_addrs(AddressFamily::INET6, $rta, $rtn, ipv6_filter_to_ip_filter(f))
}
}
}
)*
};
}
rt_generic_mod!(gateway(
linux_raw_sys::netlink::rtattr_type_t::RTA_GATEWAY as u16,
None
),);
#[inline]
fn route_v4_from_raw(
oif: u32,
dst_len: u8,
dst: Option<IpAddr>,
gw: Option<IpAddr>,
) -> Option<Ipv4Route> {
if dst_len > 32 {
return None;
}
let dst_ip = match dst {
Some(IpAddr::V4(ip)) => ip,
Some(_) => return None,
None if dst_len == 0 => Ipv4Addr::UNSPECIFIED,
None => return None,
};
let net = Ipv4Net::new(dst_ip, dst_len).ok()?;
let gw = match gw {
Some(IpAddr::V4(ip)) => Some(ip),
Some(_) => return None,
None => None,
};
Some(Ipv4Route::new(oif, net, gw))
}
#[inline]
fn route_v6_from_raw(
oif: u32,
dst_len: u8,
dst: Option<IpAddr>,
gw: Option<IpAddr>,
) -> Option<Ipv6Route> {
if dst_len > 128 {
return None;
}
let dst_ip = match dst {
Some(IpAddr::V6(ip)) => ip,
Some(_) => return None,
None if dst_len == 0 => Ipv6Addr::UNSPECIFIED,
None => return None,
};
let net = Ipv6Net::new(dst_ip, dst_len).ok()?;
let gw = match gw {
Some(IpAddr::V6(ip)) => Some(ip),
Some(_) => return None,
None => None,
};
Some(Ipv6Route::new(oif, net, gw))
}
pub(super) fn route_table_by_filter<F>(mut f: F) -> io::Result<SmallVec<IpRoute>>
where
F: FnMut(&IpRoute) -> bool,
{
let mut out: SmallVec<IpRoute> = SmallVec::new();
netlink_walk_routes(AddressFamily::INET, |fam, oif, dst_len, dst, gw| {
if fam as u16 == AddressFamily::INET.as_raw() {
if let Some(r) = route_v4_from_raw(oif, dst_len, dst, gw).map(IpRoute::V4) {
if f(&r) {
out.push(r);
}
}
}
})?;
netlink_walk_routes(AddressFamily::INET6, |fam, oif, dst_len, dst, gw| {
if fam as u16 == AddressFamily::INET6.as_raw() {
if let Some(r) = route_v6_from_raw(oif, dst_len, dst, gw).map(IpRoute::V6) {
if f(&r) {
out.push(r);
}
}
}
})?;
Ok(out)
}
pub(super) fn route_ipv4_table_by_filter<F>(mut f: F) -> io::Result<SmallVec<Ipv4Route>>
where
F: FnMut(&Ipv4Route) -> bool,
{
let mut out: SmallVec<Ipv4Route> = SmallVec::new();
netlink_walk_routes(AddressFamily::INET, |fam, oif, dst_len, dst, gw| {
if fam as u16 != AddressFamily::INET.as_raw() {
return;
}
if let Some(r) = route_v4_from_raw(oif, dst_len, dst, gw) {
if f(&r) {
out.push(r);
}
}
})?;
Ok(out)
}
pub(super) fn route_ipv6_table_by_filter<F>(mut f: F) -> io::Result<SmallVec<Ipv6Route>>
where
F: FnMut(&Ipv6Route) -> bool,
{
let mut out: SmallVec<Ipv6Route> = SmallVec::new();
netlink_walk_routes(AddressFamily::INET6, |fam, oif, dst_len, dst, gw| {
if fam as u16 != AddressFamily::INET6.as_raw() {
return;
}
if let Some(r) = route_v6_from_raw(oif, dst_len, dst, gw) {
if f(&r) {
out.push(r);
}
}
})?;
Ok(out)
}
impl Interface {
#[inline]
fn new(index: u32, flags: Flags) -> Self {
Self {
index,
mtu: 0,
name: SmolStr::default(),
mac_addr: None,
flags,
}
}
}
bitflags::bitflags! {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Flags: u32 {
const UP = 0x1;
const BROADCAST = 0x2;
const DEBUG = 0x4;
const LOOPBACK = 0x8;
const POINTOPOINT = 0x10;
const NOTRAILERS = 0x20;
const RUNNING = 0x40;
const NOARP = 0x80;
const PROMISC = 0x100;
const ALLMULTI = 0x200;
const MASTER = 0x400;
const SLAVE = 0x800;
const MULTICAST = 0x1000;
const PORTSEL = 0x2000;
const AUTOMEDIA = 0x4000;
const DYNAMIC = 0x8000;
}
}
pub(super) fn interface_table(index: u32) -> io::Result<TinyVec<Interface>> {
netlink_interface(AddressFamily::UNSPEC, index)
}
pub(super) fn interface_ipv4_addresses<F>(index: u32, f: F) -> io::Result<SmallVec<Ifv4Net>>
where
F: FnMut(&IpAddr) -> bool,
{
netlink_addr(AddressFamily::INET, index, f)
}
pub(super) fn interface_ipv6_addresses<F>(index: u32, f: F) -> io::Result<SmallVec<Ifv6Net>>
where
F: FnMut(&IpAddr) -> bool,
{
netlink_addr(AddressFamily::INET6, index, f)
}
pub(super) fn interface_addresses<F>(index: u32, f: F) -> io::Result<SmallVec<IfNet>>
where
F: FnMut(&IpAddr) -> bool,
{
netlink_addr(AddressFamily::UNSPEC, index, f)
}
const IGMP_PATH: &str = "/proc/net/igmp";
const IGMP6_PATH: &str = "/proc/net/igmp6";
pub(super) fn interface_multicast_ipv4_addresses<F>(
ifi: u32,
f: F,
) -> io::Result<SmallVec<Ifv4Addr>>
where
F: FnMut(&Ipv4Addr) -> bool,
{
parse_proc_net_igmp(IGMP_PATH, ifi, f)
}
pub(super) fn interface_multicast_ipv6_addresses<F>(
ifi: u32,
f: F,
) -> io::Result<SmallVec<Ifv6Addr>>
where
F: FnMut(&Ipv6Addr) -> bool,
{
parse_proc_net_igmp6(IGMP6_PATH, ifi, f)
}
pub(super) fn interface_multicast_addresses<F>(ifi: u32, mut f: F) -> io::Result<SmallVec<IfAddr>>
where
F: FnMut(&IpAddr) -> bool,
{
let ifmat4 = parse_proc_net_igmp("/proc/net/igmp", ifi, |addr| f(&(*addr).into()))?;
let ifmat6 = parse_proc_net_igmp6("/proc/net/igmp6", ifi, |addr| f(&(*addr).into()))?;
Ok(
ifmat4
.into_iter()
.map(From::from)
.chain(ifmat6.into_iter().map(From::from))
.collect(),
)
}
fn parse_proc_net_igmp<F>(path: &str, ifi: u32, mut f: F) -> std::io::Result<SmallVec<Ifv4Addr>>
where
F: FnMut(&Ipv4Addr) -> bool,
{
use std::io::BufRead;
let file = std::fs::File::open(path)?;
let reader = std::io::BufReader::new(file);
let mut ifmat = SmallVec::new();
let mut idx = 0;
let mut lines = reader.lines();
lines.next();
for line in lines {
let line = line?;
let mut it = line.split_ascii_whitespace();
let field0 = match it.next() {
Some(s) => s,
None => continue,
};
if it.nth(2).is_none() {
continue;
}
if !line.starts_with(' ') && !line.starts_with('\t') {
match field0.parse() {
Ok(res) => idx = res,
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
}
} else if field0.len() == 8 {
if ifi == 0 || ifi == idx {
let src = field0.as_bytes();
let mut b = [0u8; 4];
for i in (0..src.len()).step_by(2) {
b[i / 2] = xtoi2(&src[i..i + 2], 0).unwrap_or(0);
}
b.reverse();
let ip = b.into();
if f(&ip) {
ifmat.push(Ifv4Addr::new(idx, ip));
}
}
}
}
Ok(ifmat)
}
fn parse_proc_net_igmp6<F>(path: &str, ifi: u32, mut f: F) -> io::Result<SmallVec<Ifv6Addr>>
where
F: FnMut(&Ipv6Addr) -> bool,
{
use std::io::BufRead;
let file = std::fs::File::open(path)?;
let reader = std::io::BufReader::new(file);
let mut ifmat = SmallVec::new();
for line in reader.lines() {
let line = line?;
let mut it = line.split_ascii_whitespace();
let field0 = match it.next() {
Some(s) => s,
None => continue,
};
if it.next().is_none() {
continue;
}
let field2 = match it.next() {
Some(s) => s,
None => continue,
};
if it.nth(2).is_none() {
continue;
}
let idx = match field0.parse() {
Ok(res) => res,
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
};
if ifi == 0 || ifi == idx {
let mut i = 0;
let src = field2.as_bytes();
let mut data = [0u8; 16];
while i + 1 < src.len() {
data[i / 2] = xtoi2(&src[i..i + 2], 0).unwrap_or(0);
i += 2;
}
let ip = data.into();
if f(&ip) {
ifmat.push(Ifv6Addr::new(idx, ip));
}
}
}
Ok(ifmat)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_v4_from_raw_rejects_oversize_prefix() {
assert!(route_v4_from_raw(1, 33, Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), None).is_none());
}
#[test]
fn route_v4_from_raw_rejects_wrong_family_dst() {
assert!(route_v4_from_raw(1, 0, Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED)), None).is_none());
}
#[test]
fn route_v4_from_raw_treats_absent_dst_as_default() {
let r = route_v4_from_raw(1, 0, None, None).unwrap();
assert_eq!(r.destination().addr(), Ipv4Addr::UNSPECIFIED);
}
#[test]
fn route_v4_from_raw_rejects_absent_dst_with_nonzero_prefix() {
assert!(route_v4_from_raw(1, 8, None, None).is_none());
}
#[test]
fn route_v4_from_raw_rejects_wrong_family_gateway() {
let dst = Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
let gw_v6 = Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED));
assert!(route_v4_from_raw(1, 0, dst, gw_v6).is_none());
}
#[test]
fn route_v4_from_raw_accepts_absent_gateway() {
let dst = Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 0)));
let r = route_v4_from_raw(1, 8, dst, None).unwrap();
assert!(r.gateway().is_none());
}
#[test]
fn route_v6_from_raw_rejects_oversize_prefix() {
assert!(route_v6_from_raw(1, 129, Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED)), None).is_none());
}
#[test]
fn route_v6_from_raw_rejects_wrong_family_dst() {
assert!(route_v6_from_raw(1, 0, Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), None).is_none());
}
#[test]
fn route_v6_from_raw_treats_absent_dst_as_default() {
let r = route_v6_from_raw(1, 0, None, None).unwrap();
assert_eq!(r.destination().addr(), Ipv6Addr::UNSPECIFIED);
}
#[test]
fn route_v6_from_raw_rejects_absent_dst_with_nonzero_prefix() {
assert!(route_v6_from_raw(1, 64, None, None).is_none());
}
#[test]
fn route_v6_from_raw_rejects_wrong_family_gateway() {
let dst = Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED));
let gw_v4 = Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
assert!(route_v6_from_raw(1, 0, dst, gw_v4).is_none());
}
#[test]
fn route_v6_from_raw_accepts_absent_gateway() {
let dst = Some(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)));
let r = route_v6_from_raw(1, 32, dst, None).unwrap();
assert!(r.gateway().is_none());
}
}