use std::{
io,
os::fd::{AsRawFd, RawFd},
};
use netlink_packet_core::{NetlinkBuffer, NlasIterator};
use netlink_sys::{Socket, SocketAddr, protocols::NETLINK_ROUTE};
use crate::{L2tpIfName, NetNs};
const RTM_NEWLINK: u16 = 16;
const RTNLGRP_LINK: u32 = 1;
const IFLA_IFNAME: u16 = 3;
const IFINFOMSG_LEN: usize = 16;
pub(crate) struct LinkMonitor {
socket: Socket,
}
impl LinkMonitor {
pub(crate) fn open(netns: &NetNs) -> io::Result<Self> {
let socket = netns.run_if_needed(|| -> io::Result<Socket> {
let mut socket = Socket::new(NETLINK_ROUTE)?;
socket.bind(&SocketAddr::new(0, 1 << (RTNLGRP_LINK - 1)))?;
socket.set_non_blocking(true)?;
Ok(socket)
})??;
Ok(Self { socket })
}
pub(crate) fn registered_index(
&self,
ifname: &L2tpIfName,
) -> io::Result<Option<u32>> {
let mut found = None;
loop {
match self.socket.recv_from_full() {
Ok((datagram, source)) => {
if source.port_number() != 0 {
continue;
}
if let Some(index) = registration_in(&datagram, ifname) {
found = Some(index);
}
}
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
return Ok(found);
}
Err(err) => return Err(err),
}
}
}
}
impl AsRawFd for LinkMonitor {
fn as_raw_fd(&self) -> RawFd {
self.socket.as_raw_fd()
}
}
fn registration_in(datagram: &[u8], ifname: &L2tpIfName) -> Option<u32> {
let mut rest = datagram;
let mut found = None;
while let Ok(message) = NetlinkBuffer::new_checked(rest) {
if message.message_type() == RTM_NEWLINK
&& let Some(index) = registration(message.payload(), ifname)
{
found = Some(index);
}
let len = (message.length() as usize).next_multiple_of(4);
if len == 0 || len > rest.len() {
break;
}
rest = &rest[len..];
}
found
}
fn registration(payload: &[u8], ifname: &L2tpIfName) -> Option<u32> {
let header = payload.get(..IFINFOMSG_LEN)?;
let index = u32::from_ne_bytes(header[4..8].try_into().ok()?);
let change = u32::from_ne_bytes(header[12..16].try_into().ok()?);
if change != u32::MAX {
return None;
}
for nla in NlasIterator::new(&payload[IFINFOMSG_LEN..]) {
let nla = nla.ok()?;
if nla.kind() != IFLA_IFNAME {
continue;
}
let value = nla.value();
let name = value.strip_suffix(&[0]).unwrap_or(value);
return (L2tpIfName::new(name).ok()? == *ifname).then_some(index);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn new_link(index: u32, name: &[u8], change: u32) -> Vec<u8> {
let mut nla = Vec::new();
let nla_len = 4 + name.len() + 1;
nla.extend_from_slice(&(nla_len as u16).to_ne_bytes());
nla.extend_from_slice(&IFLA_IFNAME.to_ne_bytes());
nla.extend_from_slice(name);
nla.push(0);
nla.resize(nla_len.next_multiple_of(4), 0);
let mut payload = Vec::new();
payload.extend_from_slice(&[libc::AF_UNSPEC as u8, 0]);
payload.extend_from_slice(&(libc::ARPHRD_ETHER).to_ne_bytes());
payload.extend_from_slice(&index.to_ne_bytes());
payload.extend_from_slice(&0u32.to_ne_bytes());
payload.extend_from_slice(&change.to_ne_bytes());
payload.extend_from_slice(&nla);
let mut message = Vec::new();
message.extend_from_slice(&((16 + payload.len()) as u32).to_ne_bytes());
message.extend_from_slice(&RTM_NEWLINK.to_ne_bytes());
message.extend_from_slice(&0u16.to_ne_bytes());
message.extend_from_slice(&0u32.to_ne_bytes());
message.extend_from_slice(&0u32.to_ne_bytes());
message.extend_from_slice(&payload);
message
}
#[test]
fn registrations_are_told_from_renames() {
let wanted = L2tpIfName::new("l2tp0").unwrap();
assert_eq!(
registration_in(&new_link(7, b"l2tp0", u32::MAX), &wanted),
Some(7)
);
assert_eq!(registration_in(&new_link(8, b"l2tp0", 0), &wanted), None);
assert_eq!(
registration_in(&new_link(9, b"l2tp1", u32::MAX), &wanted),
None
);
let mut datagram = new_link(7, b"l2tp0", u32::MAX);
datagram.extend(new_link(10, b"l2tp0", u32::MAX));
datagram.extend(new_link(11, b"l2tp0", 0));
assert_eq!(registration_in(&datagram, &wanted), Some(10));
let raw = L2tpIfName::new(b"eth\xff0").unwrap();
assert_eq!(
registration_in(&new_link(3, b"eth\xff0", u32::MAX), &raw),
Some(3)
);
assert_eq!(registration_in(&[0; 7], &wanted), None);
let mut short = new_link(7, b"l2tp0", u32::MAX);
short.truncate(20);
assert_eq!(registration_in(&short, &wanted), None);
}
#[test]
fn monitors_open_without_privileges_and_report_nothing_when_idle() {
let monitor = LinkMonitor::open(&NetNs::current().unwrap()).unwrap();
let name = L2tpIfName::new("l2tp-no-such0").unwrap();
assert_eq!(monitor.registered_index(&name).unwrap(), None);
let lo = L2tpIfName::new("lo").unwrap();
assert_eq!(
crate::socket::interface_index(monitor.as_raw_fd(), &lo).unwrap(),
crate::L2tpIfNameExt::index(&lo).unwrap()
);
assert_eq!(
crate::socket::interface_index(monitor.as_raw_fd(), &name)
.unwrap_err()
.raw_os_error(),
Some(libc::ENODEV)
);
}
}