use {
crate::{
netlink::{NetlinkMessage, NetlinkSocket, parse_rtm_newneigh, parse_rtm_newroute},
route::{RouteTable, Router, RoutingTables},
},
arc_swap::ArcSwap,
libc::{
self, POLLERR, POLLHUP, POLLIN, POLLNVAL, RTM_DELLINK, RTM_DELNEIGH, RTM_DELROUTE,
RTM_NEWLINK, RTM_NEWNEIGH, RTM_NEWROUTE, RTMGRP_IPV4_ROUTE, RTMGRP_LINK, RTMGRP_NEIGH,
pollfd,
},
log::*,
std::{
io::{Error, ErrorKind},
net::IpAddr,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
time::{Duration, Instant},
},
};
pub struct RouteMonitor;
impl RouteMonitor {
pub fn start<F: FnOnce() + Send + Sync + 'static>(
atomic_router: Arc<ArcSwap<Router>>,
route_table: RouteTable,
exit: Arc<AtomicBool>,
update_interval: Duration,
on_thread_start: F,
) -> thread::JoinHandle<()> {
thread::Builder::new()
.name("solRouteMon".to_string())
.spawn(move || {
on_thread_start();
let mut state = RouteMonitorState::new(route_table);
let timeout = Duration::from_millis(10);
while !exit.load(Ordering::Relaxed) {
state.publish_if_needed(&atomic_router, update_interval);
let mut pfd = pollfd {
fd: state.sock.as_raw_fd(),
events: POLLIN,
revents: 0,
};
let ev = match poll(&mut pfd, timeout) {
Ok(0) => continue,
Ok(_) => pfd.revents,
Err(e) => {
error!("netlink poll error: {e}");
state.reset(&atomic_router);
continue;
}
};
debug_assert!(ev & POLLNVAL == 0);
if (ev & (POLLHUP | POLLERR)) != 0 {
error!(
"netlink poll error (revents={}{})",
if ev & POLLERR != 0 { "POLLERR " } else { "" },
if ev & POLLHUP != 0 { "POLLHUP" } else { "" },
);
state.reset(&atomic_router);
continue;
}
if (ev & POLLIN) == 0 {
continue;
}
loop {
match state.sock.recv_nonblocking() {
Ok(Some(msgs)) => {
if msgs.is_empty() {
warn!("netlink recv returned empty message list");
continue;
}
state.update(&msgs);
}
Ok(None) => break,
Err(e) => {
error!("netlink recv error: {e}");
state.reset(&atomic_router);
break;
}
}
}
}
})
.unwrap()
}
}
struct RouteMonitorState {
sock: NetlinkSocket,
route_table: RouteTable,
pending_events: PendingEvents,
last_publish: Instant,
}
#[derive(Default)]
struct PendingEvents {
routes: usize,
neighbors: usize,
links: usize,
errors: usize,
}
impl PendingEvents {
fn is_empty(&self) -> bool {
self.routes == 0 && self.neighbors == 0 && self.links == 0 && self.errors == 0
}
}
impl RouteMonitorState {
fn new(route_table: RouteTable) -> Self {
Self {
sock: bind_socket(),
route_table,
pending_events: PendingEvents::default(),
last_publish: Instant::now(),
}
}
#[inline]
fn update(&mut self, msgs: &[NetlinkMessage]) {
for message in msgs {
match message.header.nlmsg_type {
RTM_NEWROUTE | RTM_DELROUTE => {
let Some(route) = parse_rtm_newroute(message) else {
continue;
};
if !route
.table
.is_some_and(|table| self.route_table == table.into())
{
continue;
}
self.pending_events.routes = self.pending_events.routes.saturating_add(1);
debug!(
"route monitor update {} table {} dst={:?}/{} gateway={:?} oif={:?} \
priority={:?}",
nlmsg_type_name(message.header.nlmsg_type),
self.route_table,
route.destination,
route.dst_len,
route.gateway,
route.out_if_index,
route.priority,
);
}
RTM_NEWNEIGH | RTM_DELNEIGH => {
let Some(neighbor) = parse_rtm_newneigh(message, None) else {
continue;
};
if !matches!(neighbor.destination, Some(IpAddr::V4(_))) {
continue;
}
self.pending_events.neighbors = self.pending_events.neighbors.saturating_add(1);
debug!(
"route monitor update {} neighbor={:?} ifindex={} state={} lladdr={:?}",
nlmsg_type_name(message.header.nlmsg_type),
neighbor.destination,
neighbor.ifindex,
neighbor.state,
neighbor.lladdr,
);
}
RTM_NEWLINK | RTM_DELLINK => {
self.pending_events.links = self.pending_events.links.saturating_add(1);
debug!(
"route monitor update {}",
nlmsg_type_name(message.header.nlmsg_type)
);
}
_ => {}
}
}
}
fn reset(&mut self, atomic_router: &Arc<ArcSwap<Router>>) {
self.sock = bind_socket();
self.pending_events.errors = self.pending_events.errors.saturating_add(1);
log_router_rebuild(self.route_table, &self.pending_events);
let router = match rebuild_router(self.route_table) {
Ok(router) => router,
Err(e) => {
warn!("failed to rebuild router from netlink during reset: {e}");
return;
}
};
log_router_publish(self.route_table, &router);
atomic_router.store(Arc::new(router));
self.pending_events = PendingEvents::default();
self.last_publish = Instant::now();
}
fn publish_if_needed(
&mut self,
atomic_router: &Arc<ArcSwap<Router>>,
update_interval: Duration,
) {
if !self.pending_events.is_empty() && self.last_publish.elapsed() >= update_interval {
log_router_rebuild(self.route_table, &self.pending_events);
match rebuild_router(self.route_table) {
Ok(router) => {
log_router_publish(self.route_table, &router);
atomic_router.store(Arc::new(router));
self.pending_events = PendingEvents::default();
}
Err(e) => warn!("failed to rebuild router from netlink: {e}"),
}
self.last_publish = Instant::now();
}
}
}
fn log_router_publish(route_table: RouteTable, router: &Router) {
debug!(
"published router table {route_table}:\n{}",
router.routing_table()
);
}
fn log_router_rebuild(route_table: RouteTable, pending_rebuild: &PendingEvents) {
info!(
"rebuilding router table {route_table}: route_events={} neighbor_events={} link_events={} \
error_events={}",
pending_rebuild.routes,
pending_rebuild.neighbors,
pending_rebuild.links,
pending_rebuild.errors,
);
}
fn nlmsg_type_name(nlmsg_type: u16) -> &'static str {
match nlmsg_type {
RTM_NEWROUTE => "RTM_NEWROUTE",
RTM_DELROUTE => "RTM_DELROUTE",
RTM_NEWNEIGH => "RTM_NEWNEIGH",
RTM_DELNEIGH => "RTM_DELNEIGH",
RTM_NEWLINK => "RTM_NEWLINK",
RTM_DELLINK => "RTM_DELLINK",
_ => "RTM_UNKNOWN",
}
}
fn bind_socket() -> NetlinkSocket {
NetlinkSocket::bind((RTMGRP_IPV4_ROUTE | RTMGRP_NEIGH | RTMGRP_LINK) as u32)
.expect("failed to bind netlink socket")
}
fn rebuild_router(route_table: RouteTable) -> Result<Router, Error> {
let mut retries = 0u8;
loop {
if retries == 10 {
return Err(Error::new(
ErrorKind::Interrupted,
"failed to build routing table after 10 attempts",
));
}
match RoutingTables::from_netlink(route_table) {
Ok(tables) => return Router::from_tables(tables),
Err(e) if e.kind() == ErrorKind::Interrupted => {
warn!("interrupted while building routing table, retrying");
thread::sleep(Duration::from_secs(1));
retries = retries.saturating_add(1);
}
Err(e) => return Err(e),
}
}
}
#[inline]
fn poll(pfd: &mut pollfd, timeout: Duration) -> Result<i32, Error> {
let rc = loop {
let rc = unsafe { libc::poll(pfd as *mut pollfd, 1, timeout.as_millis() as i32) };
if rc < 0 && Error::last_os_error().kind() == ErrorKind::Interrupted {
continue;
}
break rc;
};
if rc < 0 {
return Err(Error::last_os_error());
}
Ok(rc)
}