use std::{
collections::HashMap,
sync::{Arc, Mutex, OnceLock, RwLock},
};
use tokio::sync::{Notify, broadcast, mpsc};
use super::{message::MessageIter, resync::ResyncMarker, socket::NetlinkSocket};
use crate::Error;
pub const DEFAULT_MULTICAST_CAPACITY: usize = 1024;
#[derive(Clone)]
pub struct Dispatcher {
inner: Arc<DispatcherInner>,
}
struct DispatcherInner {
subscribers: RwLock<HashMap<u32, broadcast::Sender<DispatcherEvent>>>,
pending: Mutex<HashMap<u32, mpsc::UnboundedSender<Arc<Vec<u8>>>>>,
driver: OnceLock<tokio::task::JoinHandle<()>>,
shutdown: Notify,
fatal: Mutex<Option<String>>,
event_listeners: Mutex<HashMap<u64, mpsc::UnboundedSender<Arc<Vec<u8>>>>>,
next_listener_id: std::sync::atomic::AtomicU64,
}
#[derive(Debug, Clone)]
pub enum DispatcherEvent {
Frame(Arc<Vec<u8>>),
Resync(ResyncMarker),
}
impl Dispatcher {
pub(crate) fn new() -> Self {
Self {
inner: Arc::new(DispatcherInner {
subscribers: RwLock::new(HashMap::new()),
pending: Mutex::new(HashMap::new()),
driver: OnceLock::new(),
shutdown: Notify::new(),
fatal: Mutex::new(None),
event_listeners: Mutex::new(HashMap::new()),
next_listener_id: std::sync::atomic::AtomicU64::new(0),
}),
}
}
pub fn subscribe_multicast(&self, group: u32) -> broadcast::Receiver<DispatcherEvent> {
let mut subs = self
.inner
.subscribers
.write()
.unwrap_or_else(|p| p.into_inner());
subs.entry(group)
.or_insert_with(|| broadcast::channel(DEFAULT_MULTICAST_CAPACITY).0)
.subscribe()
}
pub fn emit_enobufs(&self) {
let subs = self
.inner
.subscribers
.read()
.unwrap_or_else(|p| p.into_inner());
let ev = DispatcherEvent::Resync(ResyncMarker::ResyncStart);
for (group, sender) in subs.iter() {
let n = sender.receiver_count();
if n == 0 {
continue;
}
match sender.send(ev.clone()) {
Ok(_) => {
tracing::debug!(group, subs = n, "dispatcher: ENOBUFS resync emitted");
}
Err(_) => {
tracing::trace!(group, "dispatcher: ENOBUFS resync had no live receivers");
}
}
}
}
pub fn fan_out(&self, group: u32, frame: Arc<Vec<u8>>) -> usize {
let subs = self
.inner
.subscribers
.read()
.unwrap_or_else(|p| p.into_inner());
let Some(sender) = subs.get(&group) else {
return 0;
};
let n = sender.receiver_count();
if n == 0 {
return 0;
}
let _ = sender.send(DispatcherEvent::Frame(frame));
n
}
pub(crate) fn register(&self, seq: u32) -> PendingGuard {
let (tx, rx) = mpsc::unbounded_channel();
self.inner
.pending
.lock()
.unwrap_or_else(|p| p.into_inner())
.insert(seq, tx);
PendingGuard {
dispatcher: self.clone(),
seqs: vec![seq],
rx,
}
}
pub(crate) fn register_many(&self, seqs: &[u32]) -> PendingGuard {
let (tx, rx) = mpsc::unbounded_channel();
let mut pending = self.inner.pending.lock().unwrap_or_else(|p| p.into_inner());
for &seq in seqs {
pending.insert(seq, tx.clone());
}
PendingGuard {
dispatcher: self.clone(),
seqs: seqs.to_vec(),
rx,
}
}
pub(crate) fn deregister(&self, seq: u32) {
self.inner
.pending
.lock()
.unwrap_or_else(|p| p.into_inner())
.remove(&seq);
}
pub(crate) fn route_buffer(&self, buf: Arc<Vec<u8>>) {
let Some((header, _)) = MessageIter::new(&buf).flatten().next() else {
return;
};
let seq = header.nlmsg_seq;
if seq == 0 {
self.fan_out_all(buf.clone());
self.fan_out_events(buf);
return;
}
let pending = self.inner.pending.lock().unwrap_or_else(|p| p.into_inner());
if let Some(tx) = pending.get(&seq) {
let _ = tx.send(buf);
} else {
tracing::trace!(seq, "dispatcher: dropping frame for unregistered seq");
}
}
fn fan_out_all(&self, frame: Arc<Vec<u8>>) -> usize {
let subs = self
.inner
.subscribers
.read()
.unwrap_or_else(|p| p.into_inner());
let mut delivered = 0;
for sender in subs.values() {
if sender.receiver_count() == 0 {
continue;
}
if sender.send(DispatcherEvent::Frame(frame.clone())).is_ok() {
delivered += 1;
}
}
delivered
}
pub(crate) fn subscribe_events(&self) -> EventGuard {
let id = self
.inner
.next_listener_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let (tx, rx) = mpsc::unbounded_channel();
self.inner
.event_listeners
.lock()
.unwrap_or_else(|p| p.into_inner())
.insert(id, tx);
EventGuard {
dispatcher: self.clone(),
id,
rx,
}
}
pub(crate) fn deregister_event_listener(&self, id: u64) {
self.inner
.event_listeners
.lock()
.unwrap_or_else(|p| p.into_inner())
.remove(&id);
}
fn fan_out_events(&self, frame: Arc<Vec<u8>>) {
let mut listeners = self
.inner
.event_listeners
.lock()
.unwrap_or_else(|p| p.into_inner());
listeners.retain(|_, tx| tx.send(frame.clone()).is_ok());
}
#[cfg(any(test, feature = "lab"))]
pub fn event_listener_count(&self) -> usize {
self.inner
.event_listeners
.lock()
.unwrap_or_else(|p| p.into_inner())
.len()
}
fn fail_all(&self, message: String) {
*self.inner.fatal.lock().unwrap_or_else(|p| p.into_inner()) = Some(message);
self.inner
.pending
.lock()
.unwrap_or_else(|p| p.into_inner())
.clear();
}
pub(crate) fn take_fatal_error(&self) -> Error {
let msg = self
.inner
.fatal
.lock()
.unwrap_or_else(|p| p.into_inner())
.clone();
match msg {
Some(m) => Error::Io(std::io::Error::other(format!(
"netlink dispatcher driver stopped: {m}"
))),
None => Error::Io(std::io::Error::other(
"netlink dispatcher driver stopped before the response arrived",
)),
}
}
pub(crate) fn ensure_driver(&self, socket: Arc<NetlinkSocket>) {
self.inner.driver.get_or_init(|| {
let dispatcher = self.clone();
tokio::spawn(async move { run_driver(socket, dispatcher).await })
});
}
pub(crate) fn shutdown(&self) {
self.inner.shutdown.notify_waiters();
}
pub(crate) fn driver_started(&self) -> bool {
self.inner.driver.get().is_some()
}
#[cfg(any(test, feature = "lab"))]
pub fn pending_count(&self) -> usize {
self.inner
.pending
.lock()
.unwrap_or_else(|p| p.into_inner())
.len()
}
pub fn active_group_count(&self) -> usize {
self.inner
.subscribers
.read()
.unwrap_or_else(|p| p.into_inner())
.len()
}
#[cfg(test)]
pub fn gc(&self) {
let mut subs = self
.inner
.subscribers
.write()
.unwrap_or_else(|p| p.into_inner());
subs.retain(|_, sender| sender.receiver_count() > 0);
}
}
pub(crate) struct PendingGuard {
dispatcher: Dispatcher,
seqs: Vec<u32>,
pub rx: mpsc::UnboundedReceiver<Arc<Vec<u8>>>,
}
impl Drop for PendingGuard {
fn drop(&mut self) {
for &seq in &self.seqs {
self.dispatcher.deregister(seq);
}
}
}
pub(crate) struct EventGuard {
dispatcher: Dispatcher,
id: u64,
pub rx: mpsc::UnboundedReceiver<Arc<Vec<u8>>>,
}
impl Drop for EventGuard {
fn drop(&mut self) {
self.dispatcher.deregister_event_listener(self.id);
}
}
async fn run_driver(socket: Arc<NetlinkSocket>, dispatcher: Dispatcher) {
loop {
tokio::select! {
biased;
_ = dispatcher.inner.shutdown.notified() => {
tracing::trace!("dispatcher driver: shutdown");
break;
}
res = socket.recv_msg() => match res {
Ok(buf) => dispatcher.route_buffer(Arc::new(buf)),
Err(e) if e.is_no_buffer_space() => {
tracing::debug!("dispatcher driver: ENOBUFS, continuing");
continue;
}
Err(e) => {
tracing::warn!(error = %e, "dispatcher driver: fatal recv error");
dispatcher.fail_all(e.to_string());
break;
}
}
}
}
}
impl Default for Dispatcher {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Dispatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let n = self.active_group_count();
f.debug_struct("Dispatcher")
.field("active_groups", &n)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn dispatcher_starts_empty() {
let d = Dispatcher::new();
assert_eq!(d.active_group_count(), 0);
}
#[tokio::test]
async fn subscribe_multicast_registers_group() {
let d = Dispatcher::new();
let _rx = d.subscribe_multicast(1);
assert_eq!(d.active_group_count(), 1);
}
#[tokio::test]
async fn subscribe_multicast_same_group_shares_sender() {
let d = Dispatcher::new();
let _rx1 = d.subscribe_multicast(7);
let _rx2 = d.subscribe_multicast(7);
assert_eq!(d.active_group_count(), 1);
}
#[tokio::test]
async fn fan_out_delivers_to_subscriber() {
let d = Dispatcher::new();
let mut rx = d.subscribe_multicast(42);
let frame = Arc::new(vec![1u8, 2, 3, 4]);
let n = d.fan_out(42, frame.clone());
assert_eq!(n, 1);
match rx.recv().await {
Ok(DispatcherEvent::Frame(f)) => assert_eq!(&*f, &[1u8, 2, 3, 4]),
other => panic!("expected Frame, got {:?}", other),
}
}
#[tokio::test]
async fn fan_out_skips_other_groups() {
let d = Dispatcher::new();
let mut rx1 = d.subscribe_multicast(1);
let _rx2 = d.subscribe_multicast(2); let frame = Arc::new(vec![9u8]);
let n = d.fan_out(2, frame);
assert_eq!(n, 1, "fan_out reaches the group-2 subscriber");
assert!(
matches!(rx1.try_recv(), Err(broadcast::error::TryRecvError::Empty)),
"group-1 receiver must not see group-2 frame"
);
}
#[tokio::test]
async fn fan_out_no_subscribers_returns_zero() {
let d = Dispatcher::new();
let frame = Arc::new(vec![1u8]);
assert_eq!(d.fan_out(1, frame), 0);
}
#[tokio::test]
async fn enobufs_fans_out_resync_start_to_all_subscribers() {
let d = Dispatcher::new();
let mut rx1 = d.subscribe_multicast(1);
let mut rx2 = d.subscribe_multicast(2);
let mut rx3 = d.subscribe_multicast(3);
d.emit_enobufs();
for (i, rx) in [&mut rx1, &mut rx2, &mut rx3].iter_mut().enumerate() {
match rx.recv().await {
Ok(DispatcherEvent::Resync(ResyncMarker::ResyncStart)) => {
}
other => panic!("subscriber {i} expected ResyncStart, got {:?}", other),
}
}
}
#[tokio::test]
async fn enobufs_with_no_subscribers_is_noop() {
let d = Dispatcher::new();
d.emit_enobufs();
let mut rx = d.subscribe_multicast(99);
assert!(matches!(
rx.try_recv(),
Err(broadcast::error::TryRecvError::Empty)
));
}
#[tokio::test]
async fn enobufs_with_dropped_receiver_does_not_panic() {
let d = Dispatcher::new();
let rx = d.subscribe_multicast(5);
drop(rx);
d.emit_enobufs();
}
#[tokio::test]
async fn gc_reclaims_dropped_groups() {
let d = Dispatcher::new();
let rx = d.subscribe_multicast(11);
assert_eq!(d.active_group_count(), 1);
drop(rx);
d.gc();
assert_eq!(d.active_group_count(), 0);
}
#[tokio::test]
async fn fan_out_with_many_subscribers() {
let d = Dispatcher::new();
let mut receivers: Vec<_> = (0..32).map(|_| d.subscribe_multicast(7)).collect();
let frame = Arc::new(vec![0xAB; 16]);
let delivered = d.fan_out(7, frame.clone());
assert_eq!(delivered, 32);
for rx in receivers.iter_mut() {
match rx.try_recv() {
Ok(DispatcherEvent::Frame(f)) => assert_eq!(f.len(), 16),
other => panic!("expected Frame, got {:?}", other),
}
}
}
fn synth_frame(seq: u32) -> Arc<Vec<u8>> {
use crate::netlink::message::{NLMSG_HDRLEN, NlMsgHdr, NlMsgType};
let mut hdr = NlMsgHdr::new(NlMsgType::RTM_NEWLINK, 0);
hdr.nlmsg_len = NLMSG_HDRLEN as u32;
hdr.nlmsg_seq = seq;
Arc::new(hdr.as_bytes().to_vec())
}
#[tokio::test]
async fn register_then_route_delivers_to_matching_seq() {
let d = Dispatcher::new();
let mut guard = d.register(7);
assert_eq!(d.pending_count(), 1);
d.route_buffer(synth_frame(7));
let buf = guard.rx.recv().await.expect("frame routed to seq 7");
assert!(!buf.is_empty());
}
#[tokio::test]
async fn route_unregistered_seq_is_dropped() {
let d = Dispatcher::new();
let mut guard = d.register(1);
d.route_buffer(synth_frame(999));
assert!(matches!(
guard.rx.try_recv(),
Err(mpsc::error::TryRecvError::Empty)
));
}
#[tokio::test]
async fn dropping_guard_deregisters_seq() {
let d = Dispatcher::new();
let guard = d.register(3);
assert_eq!(d.pending_count(), 1);
drop(guard);
assert_eq!(d.pending_count(), 0);
d.route_buffer(synth_frame(3));
assert_eq!(d.pending_count(), 0);
}
#[tokio::test]
async fn seq_zero_frame_fans_out_to_multicast() {
let d = Dispatcher::new();
let mut sub = d.subscribe_multicast(1);
let mut unicast = d.register(5);
d.route_buffer(synth_frame(0));
match sub.recv().await {
Ok(DispatcherEvent::Frame(_)) => {}
other => panic!("expected multicast Frame, got {:?}", other),
}
assert!(matches!(
unicast.rx.try_recv(),
Err(mpsc::error::TryRecvError::Empty)
));
}
#[tokio::test]
async fn fail_all_closes_pending_and_records_error() {
let d = Dispatcher::new();
let mut guard = d.register(2);
d.fail_all("synthetic recv failure".to_string());
assert!(guard.rx.recv().await.is_none());
assert_eq!(d.pending_count(), 0);
let err = d.take_fatal_error();
assert!(
err.to_string().contains("synthetic recv failure"),
"got {err}"
);
}
#[tokio::test]
async fn take_fatal_error_without_record_is_generic() {
let d = Dispatcher::new();
let err = d.take_fatal_error();
assert!(err.to_string().contains("driver stopped"), "got {err}");
}
#[tokio::test]
async fn event_listener_receives_seq_zero_frames() {
let d = Dispatcher::new();
let mut guard = d.subscribe_events();
assert_eq!(d.event_listener_count(), 1);
d.route_buffer(synth_frame(0));
let buf = guard.rx.recv().await.expect("event listener got the frame");
assert!(!buf.is_empty());
}
#[tokio::test]
async fn event_listener_ignores_unicast_frames() {
let d = Dispatcher::new();
let mut ev = d.subscribe_events();
let mut req = d.register(5);
d.route_buffer(synth_frame(5));
assert!(req.rx.recv().await.is_some(), "unicast routed to seq 5");
assert!(matches!(
ev.rx.try_recv(),
Err(mpsc::error::TryRecvError::Empty)
));
}
#[tokio::test]
async fn dropping_event_guard_deregisters() {
let d = Dispatcher::new();
let g = d.subscribe_events();
assert_eq!(d.event_listener_count(), 1);
drop(g);
assert_eq!(d.event_listener_count(), 0);
d.route_buffer(synth_frame(0));
}
#[tokio::test]
async fn multiple_event_listeners_all_receive() {
let d = Dispatcher::new();
let mut a = d.subscribe_events();
let mut b = d.subscribe_events();
assert_eq!(d.event_listener_count(), 2);
d.route_buffer(synth_frame(0));
assert!(a.rx.recv().await.is_some());
assert!(b.rx.recv().await.is_some());
}
#[tokio::test]
async fn register_many_routes_all_seqs_to_one_channel() {
let d = Dispatcher::new();
let mut guard = d.register_many(&[10, 20, 30]);
assert_eq!(d.pending_count(), 3);
d.route_buffer(synth_frame(20));
d.route_buffer(synth_frame(10));
d.route_buffer(synth_frame(30));
for _ in 0..3 {
assert!(guard.rx.recv().await.is_some());
}
drop(guard);
assert_eq!(d.pending_count(), 0);
}
#[tokio::test]
async fn concurrent_registrations_are_independent() {
let d = Dispatcher::new();
let mut g1 = d.register(10);
let mut g2 = d.register(20);
assert_eq!(d.pending_count(), 2);
d.route_buffer(synth_frame(20));
d.route_buffer(synth_frame(10));
assert!(g1.rx.recv().await.is_some(), "seq 10 delivered");
assert!(g2.rx.recv().await.is_some(), "seq 20 delivered");
}
#[tokio::test]
async fn debug_impl_exposes_active_group_count() {
let d = Dispatcher::new();
let _ = d.subscribe_multicast(1);
let _ = d.subscribe_multicast(2);
let s = format!("{:?}", d);
assert!(s.contains("active_groups: 2"), "got {s}");
}
#[tokio::test]
async fn connection_install_propagates_enobufs_to_subscribers() {
use crate::netlink::{Connection, Route};
let conn = Connection::<Route>::new().unwrap();
let dispatcher = conn.dispatcher();
let mut rx = dispatcher.subscribe_multicast(1);
let installed = conn
.socket()
.dispatcher_for_test()
.expect("Connection::new must install_dispatcher");
assert_eq!(installed.active_group_count(), 1);
conn.socket().synth_enobufs_for_test();
match rx.recv().await {
Ok(DispatcherEvent::Resync(ResyncMarker::ResyncStart)) => {}
other => panic!("expected ResyncStart, got {:?}", other),
}
}
}