use std::{collections::BTreeSet, io};
use rustix::{
fd::{AsFd, BorrowedFd},
ioctl::{self, Opcode, Updater},
net::{
netdevice::{index_to_name_inlined, name_to_index},
socket, AddressFamily, SocketType,
},
};
use smallvec_wrapper::TinyVec;
use smol_str::SmolStr;
use super::{netlink::netlink_addr, Flags};
use crate::{IfNet, Interface};
const IF_NAMESIZE: usize = 16;
const SIOCGIFFLAGS: Opcode = 0x8913;
const SIOCGIFMTU: Opcode = 0x8921;
#[repr(C)]
struct Ifreq {
ifr_name: [u8; IF_NAMESIZE],
ifr_ifru: [u8; 24],
}
impl Ifreq {
fn for_name(name: &str) -> Self {
let mut ifr = Ifreq {
ifr_name: [0; IF_NAMESIZE],
ifr_ifru: [0; 24],
};
let bytes = name.as_bytes();
let n = bytes.len().min(IF_NAMESIZE - 1);
ifr.ifr_name[..n].copy_from_slice(&bytes[..n]);
ifr
}
}
pub(super) fn interface_table(index: u32) -> io::Result<TinyVec<Interface>> {
let sock = socket(AddressFamily::INET, SocketType::DGRAM, None)?;
let mut out = TinyVec::new();
if index != 0 {
if let Some(ifi) = build_interface(sock.as_fd(), index)? {
out.push(ifi);
}
return Ok(out);
}
let addrs = netlink_addr::<IfNet, _>(AddressFamily::UNSPEC, 0, |_| true)?;
let mut seen = BTreeSet::new();
for net in &addrs {
let idx = net.index();
if idx != 0 && seen.insert(idx) {
if let Some(ifi) = build_interface(sock.as_fd(), idx)? {
out.push(ifi);
}
}
}
Ok(out)
}
fn build_interface(sock: BorrowedFd<'_>, index: u32) -> io::Result<Option<Interface>> {
let name = match index_to_name_inlined(sock, index) {
Ok(n) => SmolStr::new(n.as_str()),
Err(e) if vanished(e) => return Ok(None),
Err(e) if e == rustix::io::Errno::ILSEQ => return Ok(None),
Err(e) => return Err(e.into()),
};
let mut ifr = Ifreq::for_name(&name);
let mtu = match unsafe { ioctl::ioctl(sock, Updater::<SIOCGIFMTU, Ifreq>::new(&mut ifr)) } {
Ok(()) => i32::from_ne_bytes(ifr.ifr_ifru[..4].try_into().unwrap()) as u32,
Err(e) if vanished(e) => return Ok(None),
Err(e) => return Err(e.into()),
};
let flags = match unsafe { ioctl::ioctl(sock, Updater::<SIOCGIFFLAGS, Ifreq>::new(&mut ifr)) } {
Ok(()) => {
let raw = i16::from_ne_bytes(ifr.ifr_ifru[..2].try_into().unwrap()) as u16;
Flags::from_bits_truncate(raw as u32)
}
Err(e) if vanished(e) => return Ok(None),
Err(e) => return Err(e.into()),
};
match name_to_index(sock, name.as_str()) {
Ok(i) if i == index => {}
Ok(_) => return Ok(None),
Err(e) if vanished(e) => return Ok(None),
Err(e) => return Err(e.into()),
}
Ok(Some(Interface {
index,
mtu,
name,
mac_addr: None,
flags,
}))
}
fn vanished(e: rustix::io::Errno) -> bool {
e == rustix::io::Errno::NODEV || e == rustix::io::Errno::NXIO
}