Skip to main content

rns_net/interface/
mod.rs

1//! Network interface abstractions.
2
3#[cfg(feature = "iface-auto")]
4pub mod auto;
5#[cfg(feature = "iface-kiss")]
6pub mod ax25_kiss;
7#[cfg(feature = "iface-backbone")]
8pub mod backbone;
9#[cfg(feature = "iface-i2p")]
10pub mod i2p;
11#[cfg(feature = "iface-kiss")]
12pub mod kiss_iface;
13#[cfg(feature = "iface-local")]
14pub mod local;
15pub(crate) mod loop_writer;
16#[cfg(feature = "iface-pipe")]
17pub mod pipe;
18pub mod registry;
19#[cfg(feature = "iface-rnode")]
20pub mod rnode;
21#[cfg(feature = "iface-serial")]
22pub mod serial_iface;
23#[cfg(feature = "iface-tcp")]
24pub mod tcp;
25#[cfg(feature = "iface-tcp")]
26pub mod tcp_server;
27pub(crate) mod transmit_buffer;
28#[cfg(feature = "iface-udp")]
29pub mod udp;
30#[cfg(all(feature = "iface-weave", target_os = "linux"))]
31pub mod weave;
32
33use std::any::Any;
34use std::collections::HashMap;
35use std::io;
36use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
37use std::sync::mpsc::TrySendError;
38use std::sync::{Arc, Condvar, Mutex, MutexGuard};
39use std::thread;
40use std::time::{Duration, Instant};
41
42use crate::event::EventSender;
43use crate::ifac::IfacState;
44use rns_core::transport::types::{InterfaceId, InterfaceInfo};
45
46/// Host callback used where every Reticulum underlay socket must be explicitly
47/// excluded from an application VPN, notably Android `VpnService.protect()`.
48pub trait SocketProtector: Send + Sync {
49    fn protect(&self, fd: std::os::unix::io::RawFd) -> io::Result<()>;
50}
51
52fn socket_protector_slot() -> &'static std::sync::RwLock<Option<Arc<dyn SocketProtector>>> {
53    static SLOT: std::sync::OnceLock<std::sync::RwLock<Option<Arc<dyn SocketProtector>>>> =
54        std::sync::OnceLock::new();
55    SLOT.get_or_init(|| std::sync::RwLock::new(None))
56}
57
58/// Exclusive process-scoped socket protection registration.
59///
60/// Android VPN services should run their private node in a dedicated process.
61/// A second registration fails instead of replacing a callback used by live
62/// sockets.
63pub struct SocketProtectorGuard {
64    protector: Arc<dyn SocketProtector>,
65}
66
67pub fn install_socket_protector(
68    protector: Arc<dyn SocketProtector>,
69) -> io::Result<SocketProtectorGuard> {
70    let mut slot = socket_protector_slot()
71        .write()
72        .unwrap_or_else(|poisoned| poisoned.into_inner());
73    if slot.is_some() {
74        return Err(io::Error::new(
75            io::ErrorKind::AlreadyExists,
76            "an underlay socket protector is already installed",
77        ));
78    }
79    *slot = Some(Arc::clone(&protector));
80    Ok(SocketProtectorGuard { protector })
81}
82
83#[cfg(test)]
84mod socket_protector_tests {
85    use super::*;
86    use std::sync::atomic::{AtomicUsize, Ordering};
87
88    static TEST_LOCK: Mutex<()> = Mutex::new(());
89    struct CountingProtector(Arc<AtomicUsize>);
90    impl SocketProtector for CountingProtector {
91        fn protect(&self, _fd: std::os::unix::io::RawFd) -> io::Result<()> {
92            self.0.fetch_add(1, Ordering::Relaxed);
93            Ok(())
94        }
95    }
96
97    #[test]
98    fn underlay_helper_invokes_installed_protector_without_mark() {
99        use std::os::fd::AsRawFd;
100        let _lock = TEST_LOCK.lock().unwrap();
101        let calls = Arc::new(AtomicUsize::new(0));
102        let _guard =
103            install_socket_protector(Arc::new(CountingProtector(Arc::clone(&calls)))).unwrap();
104        let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
105        apply_underlay_mark(socket.as_raw_fd(), None).unwrap();
106        assert!(calls.load(Ordering::Relaxed) >= 1);
107    }
108}
109
110impl Drop for SocketProtectorGuard {
111    fn drop(&mut self) {
112        let mut slot = socket_protector_slot()
113            .write()
114            .unwrap_or_else(|poisoned| poisoned.into_inner());
115        if slot
116            .as_ref()
117            .is_some_and(|installed| Arc::ptr_eq(installed, &self.protector))
118        {
119            *slot = None;
120        }
121    }
122}
123
124fn protect_underlay_socket(fd: std::os::unix::io::RawFd) -> io::Result<()> {
125    let protector = socket_protector_slot()
126        .read()
127        .unwrap_or_else(|poisoned| poisoned.into_inner())
128        .clone();
129    if let Some(protector) = protector {
130        protector.protect(fd)?;
131    }
132    Ok(())
133}
134
135/// Bind a socket to a specific network interface using `SO_BINDTODEVICE`.
136///
137/// Requires `CAP_NET_RAW` or root on Linux.
138#[cfg(target_os = "linux")]
139pub fn bind_to_device(fd: std::os::unix::io::RawFd, device: &str) -> io::Result<()> {
140    let dev_bytes = device.as_bytes();
141    if dev_bytes.len() >= libc::IFNAMSIZ {
142        return Err(io::Error::new(
143            io::ErrorKind::InvalidInput,
144            format!("device name too long: {}", device),
145        ));
146    }
147    let ret = unsafe {
148        libc::setsockopt(
149            fd,
150            libc::SOL_SOCKET,
151            libc::SO_BINDTODEVICE,
152            dev_bytes.as_ptr() as *const libc::c_void,
153            dev_bytes.len() as libc::socklen_t,
154        )
155    };
156    if ret != 0 {
157        return Err(io::Error::last_os_error());
158    }
159    Ok(())
160}
161
162/// Apply a Linux firewall mark to an IP underlay socket.
163///
164/// The mark must be set before `connect()` so policy routing can keep Reticulum
165/// underlay traffic outside application-level full tunnels. Configuring a mark
166/// is fail-closed: callers must not silently create an unmarked socket when the
167/// operation is not permitted.
168#[cfg(target_os = "linux")]
169pub fn apply_underlay_mark(fd: std::os::unix::io::RawFd, mark: Option<u32>) -> io::Result<()> {
170    protect_underlay_socket(fd)?;
171    let Some(mark) = mark else {
172        return Ok(());
173    };
174    let ret = unsafe {
175        libc::setsockopt(
176            fd,
177            libc::SOL_SOCKET,
178            libc::SO_MARK,
179            &mark as *const _ as *const libc::c_void,
180            std::mem::size_of::<u32>() as libc::socklen_t,
181        )
182    };
183    if ret != 0 {
184        return Err(io::Error::last_os_error());
185    }
186    Ok(())
187}
188
189#[cfg(not(target_os = "linux"))]
190pub fn apply_underlay_mark(fd: std::os::unix::io::RawFd, mark: Option<u32>) -> io::Result<()> {
191    protect_underlay_socket(fd)?;
192    if mark.is_some() {
193        return Err(io::Error::new(
194            io::ErrorKind::Unsupported,
195            "underlay socket marks are only supported on Linux",
196        ));
197    }
198    Ok(())
199}
200
201/// Connect a TCP socket after applying underlay routing options.
202#[cfg(target_os = "linux")]
203pub fn connect_tcp_with_options(
204    addr: &std::net::SocketAddr,
205    device: Option<&str>,
206    underlay_mark: Option<u32>,
207    timeout: Duration,
208) -> io::Result<std::net::TcpStream> {
209    use std::os::unix::io::AsRawFd;
210
211    let socket = socket2::Socket::new(
212        socket2::Domain::for_address(*addr),
213        socket2::Type::STREAM,
214        Some(socket2::Protocol::TCP),
215    )?;
216    apply_underlay_mark(socket.as_raw_fd(), underlay_mark)?;
217    if let Some(device) = device {
218        bind_to_device(socket.as_raw_fd(), device)?;
219    }
220    socket.connect_timeout(&(*addr).into(), timeout)?;
221    Ok(socket.into())
222}
223
224/// Validate at node startup that the configured mark can actually be applied.
225pub fn validate_underlay_mark(mark: Option<u32>) -> io::Result<()> {
226    let Some(_) = mark else {
227        return Ok(());
228    };
229    let socket = socket2::Socket::new(
230        socket2::Domain::IPV4,
231        socket2::Type::DGRAM,
232        Some(socket2::Protocol::UDP),
233    )?;
234    use std::os::unix::io::AsRawFd;
235    apply_underlay_mark(socket.as_raw_fd(), mark)
236}
237
238/// Writable end of an interface. Held by the driver.
239///
240/// Each implementation wraps a socket + framing.
241pub trait Writer: Send {
242    fn send_frame(&mut self, data: &[u8]) -> io::Result<()>;
243
244    /// Write one frame without silently dropping it. WouldBlock means the
245    /// frame was accepted and `flush` must finish it before success is reported.
246    /// Writers with intentional drop policies must override this method.
247    fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
248        self.send_frame(data)
249    }
250
251    /// Internal async-writer admission. Leaves completion untouched on failure.
252    #[doc(hidden)]
253    fn enqueue_confirmed(
254        &mut self,
255        _data: &[u8],
256        _completion: &mut Option<crate::link_send::Completion>,
257    ) -> io::Result<()> {
258        Err(io::Error::new(
259            io::ErrorKind::Unsupported,
260            "confirmed sends require an async interface writer",
261        ))
262    }
263
264    /// Like [`Writer::send_frame`], but a writer with its own worker may
265    /// postpone waking it until [`Writer::wake`]. The driver uses this for
266    /// forwarded frames and wakes each touched writer once per event batch.
267    fn send_frame_deferred(&mut self, data: &[u8]) -> io::Result<()> {
268        self.send_frame(data)
269    }
270
271    /// Deferred send of shared frame bytes; writers with a worker queue may
272    /// retain the shared buffer instead of copying it.
273    fn send_shared_deferred(
274        &mut self,
275        data: &rns_core::transport::types::PacketBytes,
276    ) -> io::Result<()> {
277        self.send_frame_deferred(data)
278    }
279
280    /// Wake a worker postponed by [`Writer::send_frame_deferred`].
281    fn wake(&mut self) {}
282
283    /// Write out what a driver-run writer has buffered (see `loop_writer`).
284    /// The driver calls this on every writer before it waits for more
285    /// input, so frames from one pass leave in as few writes as possible.
286    fn flush_pending(&mut self) {}
287
288    fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
289        for frame in frames {
290            self.send_frame(frame)?;
291        }
292        Ok(())
293    }
294
295    /// Resume bytes accepted by a previous call that returned `WouldBlock`.
296    fn flush(&mut self) -> io::Result<()> {
297        Ok(())
298    }
299
300    /// Share an exact byte admission budget with the async producer when this
301    /// writer uses dataplane egress control.
302    fn egress_control(&self) -> Option<EgressControl> {
303        None
304    }
305
306    /// A stream socket the driver may write directly, without a worker
307    /// thread (see `loop_writer`). Writers returning one must only frame
308    /// with HDLC and write the result to this descriptor.
309    fn loop_socket(&self) -> Option<std::os::fd::RawFd> {
310        None
311    }
312
313    /// Byte-budget egress rules for a [`Writer::loop_socket`] writer, taken
314    /// once when the driver starts writing it. Without them the driver
315    /// applies its default backpressure.
316    #[doc(hidden)]
317    fn loop_egress(&mut self) -> Option<loop_writer::LoopEgress> {
318        None
319    }
320
321    /// Called by the driver once per tick, so driver-run writers can detect
322    /// a peer that stopped reading even when nothing new is sent.
323    #[doc(hidden)]
324    fn tick(&mut self) {}
325
326    fn shutdown(&mut self) {}
327}
328
329/// Hasher for maps keyed by [`InterfaceId`], looked up several times per
330/// forwarded packet. IDs are small trusted integers, so a multiplicative mix
331/// replaces SipHash, whose per-lookup cost showed up in forwarding profiles.
332#[derive(Default, Clone, Copy)]
333pub struct InterfaceIdHasher(u64);
334
335impl std::hash::Hasher for InterfaceIdHasher {
336    fn write(&mut self, bytes: &[u8]) {
337        for byte in bytes {
338            self.write_u64(u64::from(*byte));
339        }
340    }
341
342    fn write_u64(&mut self, value: u64) {
343        self.0 = (self.0 ^ value).wrapping_mul(0x9E37_79B9_7F4A_7C15);
344    }
345
346    fn finish(&self) -> u64 {
347        self.0 ^ (self.0 >> 29)
348    }
349}
350
351/// Map keyed by interface ID using [`InterfaceIdHasher`].
352pub type InterfaceMap<V> =
353    HashMap<InterfaceId, V, std::hash::BuildHasherDefault<InterfaceIdHasher>>;
354
355pub const DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY: usize = 256;
356
357/// Longest a sleeping writer worker goes without rechecking its queue, a
358/// backstop for wakeups postponed by `send_frame_deferred`.
359const WRITER_RECHECK: Duration = Duration::from_millis(5);
360
361/// Longest time a producer waits for a busy async writer to free queue space
362/// or byte budget before treating the frame as undeliverable.
363///
364/// A healthy writer drains its queue in well under a millisecond, so this
365/// bound converts momentary bursts into backpressure (the producer pauses and
366/// the inbound queue fills) instead of silent loss. A writer that stays full
367/// for the whole bound is treated as stalled: the frame is refused and the
368/// driver's per-interface send deferral protects other interfaces.
369pub const ASYNC_WRITER_BACKPRESSURE_WAIT: Duration = Duration::from_millis(20);
370
371#[derive(Clone)]
372#[doc(hidden)]
373pub struct EgressControl {
374    byte_limit: usize,
375    reserved_bytes: Arc<AtomicUsize>,
376    stalled: Arc<AtomicBool>,
377    dropped_frames: Arc<AtomicU64>,
378    dropped_bytes: Arc<AtomicU64>,
379}
380
381impl EgressControl {
382    pub(crate) fn new(byte_limit: usize) -> Self {
383        Self {
384            byte_limit,
385            reserved_bytes: Arc::new(AtomicUsize::new(0)),
386            stalled: Arc::new(AtomicBool::new(false)),
387            dropped_frames: Arc::new(AtomicU64::new(0)),
388            dropped_bytes: Arc::new(AtomicU64::new(0)),
389        }
390    }
391
392    fn try_reserve(&self, bytes: usize) -> bool {
393        if self.stalled.load(Ordering::Relaxed) {
394            return false;
395        }
396        let mut current = self.reserved_bytes.load(Ordering::Relaxed);
397        loop {
398            let Some(next) = current.checked_add(bytes) else {
399                return false;
400            };
401            if next > self.byte_limit {
402                return false;
403            }
404            match self.reserved_bytes.compare_exchange_weak(
405                current,
406                next,
407                Ordering::Relaxed,
408                Ordering::Relaxed,
409            ) {
410                Ok(_) => return true,
411                Err(observed) => current = observed,
412            }
413        }
414    }
415
416    fn release(&self, bytes: usize) {
417        self.reserved_bytes.fetch_sub(bytes, Ordering::Relaxed);
418    }
419
420    pub(crate) fn stalled(&self) -> bool {
421        self.stalled.load(Ordering::Relaxed)
422    }
423
424    pub(crate) fn set_stalled(&self, stalled: bool) {
425        self.stalled.store(stalled, Ordering::Relaxed);
426    }
427
428    pub(crate) fn record_drop(&self, bytes: usize) {
429        self.dropped_frames.fetch_add(1, Ordering::Relaxed);
430        self.dropped_bytes
431            .fetch_add(bytes as u64, Ordering::Relaxed);
432    }
433}
434
435/// Return whether an interface type normally operates on a shared medium.
436///
437/// This is a type-level hint: runtime configuration can still describe links
438/// with different physical properties. It mirrors Reticulum's interface-class
439/// defaults and is suitable for discovery/capability metadata.
440pub fn shared_medium_hint(interface_type: &str) -> bool {
441    matches!(
442        interface_type,
443        "AX25KISSInterface"
444            | "KISSInterface"
445            | "PipeInterface"
446            | "RNodeInterface"
447            | "RNodeMultiInterface"
448            | "SerialInterface"
449            | "UDPInterface"
450    )
451}
452
453pub(crate) fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>, label: &str) -> MutexGuard<'a, T> {
454    match mutex.lock() {
455        Ok(guard) => guard,
456        Err(poisoned) => {
457            log::warn!("recovering poisoned mutex: {}", label);
458            poisoned.into_inner()
459        }
460    }
461}
462
463#[derive(Clone, Default)]
464pub struct ListenerControl {
465    stop: Arc<AtomicBool>,
466    running: Option<Arc<AtomicBool>>,
467}
468
469impl ListenerControl {
470    pub fn new() -> Self {
471        Self {
472            stop: Arc::new(AtomicBool::new(false)),
473            running: None,
474        }
475    }
476
477    pub(crate) fn with_running_flag(running: Arc<AtomicBool>) -> Self {
478        Self {
479            stop: Arc::new(AtomicBool::new(false)),
480            running: Some(running),
481        }
482    }
483
484    pub fn request_stop(&self) {
485        self.stop.store(true, Ordering::Relaxed);
486        if let Some(running) = &self.running {
487            running.store(false, Ordering::Relaxed);
488        }
489    }
490
491    pub fn should_stop(&self) -> bool {
492        self.stop.load(Ordering::Relaxed)
493    }
494
495    pub(crate) fn same_instance(&self, other: &Self) -> bool {
496        Arc::ptr_eq(&self.stop, &other.stop)
497    }
498}
499
500/// Wait for serial input without keeping a detached reader blocked forever.
501pub(crate) fn poll_readable(fd: std::os::fd::RawFd, timeout: Duration) -> io::Result<bool> {
502    let mut pfd = libc::pollfd {
503        fd,
504        events: libc::POLLIN,
505        revents: 0,
506    };
507    let timeout_ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
508    let result = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
509    if result < 0 {
510        let error = io::Error::last_os_error();
511        return if error.kind() == io::ErrorKind::Interrupted {
512            Ok(false)
513        } else {
514            Err(error)
515        };
516    }
517    if pfd.revents & libc::POLLNVAL != 0 {
518        return Err(io::Error::new(
519            io::ErrorKind::BrokenPipe,
520            "serial port closed",
521        ));
522    }
523    Ok(result > 0)
524}
525
526pub(crate) fn wait_while_active(control: &ListenerControl, duration: Duration) -> bool {
527    let deadline = Instant::now() + duration;
528    while !control.should_stop() {
529        let remaining = deadline.saturating_duration_since(Instant::now());
530        if remaining.is_zero() {
531            return true;
532        }
533        thread::sleep(remaining.min(Duration::from_millis(100)));
534    }
535    false
536}
537
538pub(crate) struct UnavailableWriter;
539
540impl Writer for UnavailableWriter {
541    fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
542        Err(io::Error::new(
543            io::ErrorKind::NotConnected,
544            "interface is waiting for its port",
545        ))
546    }
547}
548
549#[derive(Clone, Default)]
550pub struct AsyncWriterMetrics {
551    queued_frames: Arc<AtomicUsize>,
552    worker_alive: Arc<AtomicBool>,
553    egress_control: Option<EgressControl>,
554    /// Signalled by the worker whenever it frees queue slots or byte budget.
555    space: Arc<(Mutex<()>, Condvar)>,
556    /// Producers inside a backpressure wait. Incremented under the `space`
557    /// lock before the producer's queue attempt, so a worker that frees space
558    /// after a failed attempt always observes it and notifies.
559    space_waiters: Arc<AtomicUsize>,
560}
561
562impl AsyncWriterMetrics {
563    fn notify_space(&self) {
564        if self.space_waiters.load(Ordering::SeqCst) == 0 {
565            return;
566        }
567        let (lock, condvar) = &*self.space;
568        let _guard = lock_or_recover(lock, "async writer space");
569        condvar.notify_all();
570    }
571
572    pub fn queued_frames(&self) -> usize {
573        self.queued_frames.load(Ordering::Relaxed)
574    }
575
576    pub fn worker_alive(&self) -> bool {
577        self.worker_alive.load(Ordering::Relaxed)
578    }
579
580    /// Exact encoded bytes waiting in the async queue or concrete writer.
581    pub fn tx_buffered(&self) -> usize {
582        self.egress_control
583            .as_ref()
584            .map(|control| control.reserved_bytes.load(Ordering::Relaxed))
585            .unwrap_or(0)
586    }
587
588    pub fn tx_drops(&self) -> u64 {
589        self.egress_control
590            .as_ref()
591            .map(|control| control.dropped_frames.load(Ordering::Relaxed))
592            .unwrap_or(0)
593    }
594
595    pub fn tx_dropped_bytes(&self) -> u64 {
596        self.egress_control
597            .as_ref()
598            .map(|control| control.dropped_bytes.load(Ordering::Relaxed))
599            .unwrap_or(0)
600    }
601
602    pub fn tx_stalled(&self) -> bool {
603        self.egress_control
604            .as_ref()
605            .is_some_and(EgressControl::stalled)
606    }
607}
608
609/// Bounded frame queue between the driver and one interface writer thread.
610///
611/// The worker drains every queued frame in one lock acquisition. This
612/// replaces a std sync channel whose receiver spins while it observes a send
613/// in progress, which on a single CPU burned the rest of the time slice.
614struct WriterQueue {
615    state: Mutex<WriterQueueState>,
616    ready: Condvar,
617    capacity: usize,
618}
619
620struct WriterQueueState {
621    frames: std::collections::VecDeque<QueuedFrame>,
622    sender_closed: bool,
623    receiver_closed: bool,
624    receiver_waiting: bool,
625}
626
627impl WriterQueue {
628    fn new(capacity: usize) -> Arc<Self> {
629        Arc::new(Self {
630            state: Mutex::new(WriterQueueState {
631                frames: std::collections::VecDeque::new(),
632                sender_closed: false,
633                receiver_closed: false,
634                receiver_waiting: false,
635            }),
636            ready: Condvar::new(),
637            capacity: capacity.max(1),
638        })
639    }
640
641    /// Wait until frames are queued, then move all of them into `out`.
642    /// Returns false once the sender is gone and nothing remains.
643    fn recv_all(&self, out: &mut Vec<QueuedFrame>) -> bool {
644        let mut state = lock_or_recover(&self.state, "async writer queue");
645        while state.frames.is_empty() {
646            if state.sender_closed {
647                return false;
648            }
649            state.receiver_waiting = true;
650            // Producers may postpone the wakeup (see send_frame_deferred);
651            // the periodic recheck bounds any delay if one is never sent.
652            state = match self.ready.wait_timeout(state, WRITER_RECHECK) {
653                Ok((state, _)) => state,
654                Err(poisoned) => poisoned.into_inner().0,
655            };
656            state.receiver_waiting = false;
657        }
658        out.extend(state.frames.drain(..));
659        true
660    }
661
662    /// Wake the worker if it sleeps while frames are queued.
663    fn kick(&self) {
664        let state = lock_or_recover(&self.state, "async writer queue");
665        let wake = state.receiver_waiting && !state.frames.is_empty();
666        drop(state);
667        if wake {
668            self.ready.notify_one();
669        }
670    }
671
672    fn close_receiver(&self) {
673        lock_or_recover(&self.state, "async writer queue").receiver_closed = true;
674    }
675}
676
677/// Producer handle; dropping it lets the worker exit after draining.
678struct WriterTx(Arc<WriterQueue>);
679
680impl WriterTx {
681    /// Queue a frame. Returns whether the worker still needs waking: false
682    /// when it was woken here (`notify`) or was not waiting.
683    fn try_send(
684        &self,
685        frame: QueuedFrame,
686        notify: bool,
687    ) -> Result<bool, TrySendError<QueuedFrame>> {
688        let mut state = lock_or_recover(&self.0.state, "async writer queue");
689        if state.receiver_closed {
690            return Err(TrySendError::Disconnected(frame));
691        }
692        if state.frames.len() >= self.0.capacity {
693            return Err(TrySendError::Full(frame));
694        }
695        state.frames.push_back(frame);
696        let waiting = state.receiver_waiting;
697        drop(state);
698        if waiting && notify {
699            self.0.ready.notify_one();
700        }
701        Ok(waiting && !notify)
702    }
703}
704
705impl Drop for WriterTx {
706    fn drop(&mut self) {
707        lock_or_recover(&self.0.state, "async writer queue").sender_closed = true;
708        self.0.ready.notify_one();
709    }
710}
711
712struct AsyncWriter {
713    tx: WriterTx,
714    metrics: AsyncWriterMetrics,
715    /// Set after a backpressure wait times out. Until a frame is admitted
716    /// again, unconfirmed sends make a single attempt instead of waiting,
717    /// so a stalled peer costs the producer at most one wait.
718    skip_wait: bool,
719    /// Set while a deferred send is in progress.
720    defer_wake: bool,
721    /// A deferred send left the worker asleep with frames queued.
722    wake_pending: bool,
723}
724
725enum Admission {
726    Done,
727    Refused(QueuedFrame),
728}
729
730/// Frame bytes queued for a writer: owned, or shared with the driver's
731/// forwarding buffer so queueing a forwarded frame does not copy it.
732pub enum FrameBytes {
733    Owned(Vec<u8>),
734    Shared(rns_core::transport::types::PacketBytes),
735}
736
737impl std::ops::Deref for FrameBytes {
738    type Target = [u8];
739
740    fn deref(&self) -> &[u8] {
741        match self {
742            FrameBytes::Owned(bytes) => bytes,
743            FrameBytes::Shared(bytes) => bytes,
744        }
745    }
746}
747
748struct QueuedFrame {
749    data: FrameBytes,
750    reserved_bytes: usize,
751    completion: Option<crate::link_send::Completion>,
752}
753
754impl Writer for AsyncWriter {
755    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
756        self.enqueue(data, &mut None)
757    }
758
759    fn send_frame_deferred(&mut self, data: &[u8]) -> io::Result<()> {
760        self.defer_wake = true;
761        let result = self.enqueue(data, &mut None);
762        self.defer_wake = false;
763        result
764    }
765
766    fn send_shared_deferred(
767        &mut self,
768        data: &rns_core::transport::types::PacketBytes,
769    ) -> io::Result<()> {
770        self.defer_wake = true;
771        let result = self.enqueue_unconfirmed(FrameBytes::Shared(data.clone()));
772        self.defer_wake = false;
773        result
774    }
775
776    fn wake(&mut self) {
777        if std::mem::take(&mut self.wake_pending) {
778            self.tx.0.kick();
779        }
780    }
781
782    fn enqueue_confirmed(
783        &mut self,
784        data: &[u8],
785        completion: &mut Option<crate::link_send::Completion>,
786    ) -> io::Result<()> {
787        self.enqueue(data, completion)
788    }
789}
790
791impl AsyncWriter {
792    /// Admit an unconfirmed frame, waiting up to
793    /// [`ASYNC_WRITER_BACKPRESSURE_WAIT`] while the worker is busy.
794    ///
795    /// A stalled peer (as reported by the byte-budget controller) is refused
796    /// immediately so that one dead connection cannot repeatedly pause the
797    /// driver. Over-budget frames that can never fit are dropped as before.
798    fn enqueue_unconfirmed(&mut self, data: FrameBytes) -> io::Result<()> {
799        let mut frame = QueuedFrame {
800            data,
801            reserved_bytes: 0,
802            completion: None,
803        };
804        // Fast path without the space lock: almost every frame fits.
805        match self.try_admit(frame)? {
806            Admission::Done => return Ok(()),
807            Admission::Refused(returned) => frame = returned,
808        }
809        if self.skip_wait {
810            return self.refuse(frame);
811        }
812        // The worker must run to free space; wake it if a deferred send left
813        // it asleep.
814        self.wake();
815        let space = Arc::clone(&self.metrics.space);
816        let (lock, condvar) = &*space;
817        let mut guard = lock_or_recover(lock, "async writer space");
818        // Register before retrying: a worker that frees space after this
819        // retry fails observes the waiter and must take the lock to notify,
820        // which it can only do once this thread is waiting.
821        self.metrics.space_waiters.fetch_add(1, Ordering::SeqCst);
822        let deadline = Instant::now() + ASYNC_WRITER_BACKPRESSURE_WAIT;
823        let result = loop {
824            match self.try_admit(frame) {
825                Err(error) => break Err(error),
826                Ok(Admission::Done) => break Ok(()),
827                Ok(Admission::Refused(returned)) => frame = returned,
828            }
829            let now = Instant::now();
830            if now >= deadline {
831                self.skip_wait = true;
832                break self.refuse(frame);
833            }
834            guard = match condvar.wait_timeout(guard, deadline - now) {
835                Ok((guard, _)) => guard,
836                Err(poisoned) => poisoned.into_inner().0,
837            };
838        };
839        self.metrics.space_waiters.fetch_sub(1, Ordering::SeqCst);
840        drop(guard);
841        result
842    }
843
844    /// One admission attempt. Frames that can never be admitted (over the
845    /// byte limit, or a stalled peer) are dropped and counted, as before.
846    fn try_admit(&mut self, mut frame: QueuedFrame) -> io::Result<Admission> {
847        if !self.metrics.worker_alive() {
848            return Err(io::Error::new(
849                io::ErrorKind::BrokenPipe,
850                "interface writer worker is offline",
851            ));
852        }
853        if let Some(control) = &self.metrics.egress_control {
854            let bytes = crate::hdlc::framed_len(&frame.data);
855            if bytes > control.byte_limit || control.stalled() {
856                control.record_drop(bytes);
857                return Ok(Admission::Done);
858            }
859            if !control.try_reserve(bytes) {
860                return Ok(Admission::Refused(frame));
861            }
862            frame.reserved_bytes = bytes;
863        }
864        self.metrics.queued_frames.fetch_add(1, Ordering::Relaxed);
865        match self.tx.try_send(frame, !self.defer_wake) {
866            Ok(needs_wake) => {
867                self.skip_wait = false;
868                self.wake_pending |= needs_wake;
869                Ok(Admission::Done)
870            }
871            Err(TrySendError::Full(mut returned)) => {
872                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
873                if let Some(control) = &self.metrics.egress_control {
874                    control.release(returned.reserved_bytes);
875                }
876                returned.reserved_bytes = 0;
877                Ok(Admission::Refused(returned))
878            }
879            Err(TrySendError::Disconnected(returned)) => {
880                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
881                if let Some(control) = &self.metrics.egress_control {
882                    control.release(returned.reserved_bytes);
883                }
884                self.metrics.worker_alive.store(false, Ordering::Relaxed);
885                Err(io::Error::new(
886                    io::ErrorKind::BrokenPipe,
887                    "interface writer worker disconnected",
888                ))
889            }
890        }
891    }
892
893    /// Give up on a frame that did not fit. A frame refused for byte budget
894    /// is dropped and counted (the pre-existing egress-control policy); a
895    /// frame refused for queue slots is reported as would-block.
896    fn refuse(&self, frame: QueuedFrame) -> io::Result<()> {
897        if let Some(control) = &self.metrics.egress_control {
898            let bytes = crate::hdlc::framed_len(&frame.data);
899            if self.metrics.queued_frames() < self.tx.0.capacity {
900                control.record_drop(bytes);
901                return Ok(());
902            }
903        }
904        Err(io::Error::new(
905            io::ErrorKind::WouldBlock,
906            "interface writer queue is full",
907        ))
908    }
909
910    fn enqueue(
911        &mut self,
912        data: &[u8],
913        completion: &mut Option<crate::link_send::Completion>,
914    ) -> io::Result<()> {
915        if !self.metrics.worker_alive() {
916            return Err(io::Error::new(
917                io::ErrorKind::BrokenPipe,
918                "interface writer worker is offline",
919            ));
920        }
921
922        // Unconfirmed frames (transit forwarding, announces) wait briefly for a
923        // busy worker instead of being refused at the first full queue.
924        // Confirmed sends keep their immediate WouldBlock contract: their
925        // callers already retry through the completion machinery.
926        if completion.is_none() {
927            return self.enqueue_unconfirmed(FrameBytes::Owned(data.to_vec()));
928        }
929
930        let reserved_bytes = if let Some(control) = &self.metrics.egress_control {
931            let framed_bytes = crate::hdlc::framed_len(data);
932            if !control.try_reserve(framed_bytes) {
933                if completion.is_some() {
934                    if framed_bytes > control.byte_limit {
935                        return Err(io::Error::new(
936                            io::ErrorKind::InvalidInput,
937                            "frame exceeds interface byte budget",
938                        ));
939                    }
940                    return Err(io::Error::new(
941                        io::ErrorKind::WouldBlock,
942                        "interface egress budget is full",
943                    ));
944                }
945                control.record_drop(framed_bytes);
946                return Ok(());
947            }
948            framed_bytes
949        } else {
950            0
951        };
952
953        // Publish accounting before the frame becomes visible to the worker.
954        self.metrics.queued_frames.fetch_add(1, Ordering::Relaxed);
955        match self.tx.try_send(
956            QueuedFrame {
957                data: FrameBytes::Owned(data.to_vec()),
958                reserved_bytes,
959                completion: completion.take(),
960            },
961            true,
962        ) {
963            Ok(_) => Ok(()),
964            Err(TrySendError::Full(frame)) => {
965                *completion = frame.completion;
966                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
967                if let Some(control) = &self.metrics.egress_control {
968                    control.release(frame.reserved_bytes);
969                }
970                Err(io::Error::new(
971                    io::ErrorKind::WouldBlock,
972                    "interface writer queue is full",
973                ))
974            }
975            Err(TrySendError::Disconnected(frame)) => {
976                *completion = frame.completion;
977                self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
978                if let Some(control) = &self.metrics.egress_control {
979                    control.release(frame.reserved_bytes);
980                }
981                self.metrics.worker_alive.store(false, Ordering::Relaxed);
982                Err(io::Error::new(
983                    io::ErrorKind::BrokenPipe,
984                    "interface writer worker disconnected",
985                ))
986            }
987        }
988    }
989}
990
991pub fn wrap_async_writer(
992    writer: Box<dyn Writer>,
993    interface_id: InterfaceId,
994    interface_name: &str,
995    event_tx: EventSender,
996    queue_capacity: usize,
997) -> (Box<dyn Writer>, AsyncWriterMetrics) {
998    if let Some(fd) = writer.loop_socket() {
999        return loop_writer::wrap(writer, fd, interface_id, interface_name, event_tx);
1000    }
1001    let egress_control = writer.egress_control();
1002    let queue = WriterQueue::new(queue_capacity);
1003    let (tx, rx) = (WriterTx(Arc::clone(&queue)), queue);
1004    let metrics = AsyncWriterMetrics {
1005        queued_frames: Arc::new(AtomicUsize::new(0)),
1006        worker_alive: Arc::new(AtomicBool::new(true)),
1007        egress_control,
1008        space: Arc::default(),
1009        space_waiters: Arc::default(),
1010    };
1011    let metrics_thread = metrics.clone();
1012    let name = interface_name.to_string();
1013
1014    let spawn_result = thread::Builder::new()
1015        .name(format!("iface-writer-{}", interface_id.0))
1016        .spawn(move || async_writer_loop(writer, rx, interface_id, name, event_tx, metrics_thread));
1017
1018    if let Err(err) = spawn_result {
1019        metrics.worker_alive.store(false, Ordering::Relaxed);
1020        log::error!(
1021            "[{}:{}] failed to spawn async writer thread: {}",
1022            interface_name,
1023            interface_id.0,
1024            err
1025        );
1026        return (Box::new(DirectWriterFallback), metrics);
1027    }
1028
1029    (
1030        Box::new(AsyncWriter {
1031            tx,
1032            metrics: metrics.clone(),
1033            skip_wait: false,
1034            defer_wake: false,
1035            wake_pending: false,
1036        }),
1037        metrics,
1038    )
1039}
1040
1041struct DirectWriterFallback;
1042
1043impl Writer for DirectWriterFallback {
1044    fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1045        Err(io::Error::other("interface writer worker unavailable"))
1046    }
1047}
1048
1049fn async_writer_loop(
1050    mut writer: Box<dyn Writer>,
1051    rx: Arc<WriterQueue>,
1052    interface_id: InterfaceId,
1053    interface_name: String,
1054    event_tx: EventSender,
1055    metrics: AsyncWriterMetrics,
1056) {
1057    let mut batch = Vec::new();
1058    while rx.recv_all(&mut batch) {
1059        let queued = std::mem::take(&mut batch);
1060        let reserved_bytes = queued.iter().map(|frame| frame.reserved_bytes).sum();
1061        metrics
1062            .queued_frames
1063            .fetch_sub(queued.len(), Ordering::Relaxed);
1064        metrics.notify_space();
1065        if event_tx.link_send_pool().in_flight() > 0 {
1066            let _ = event_tx.try_send(crate::event::Event::LinkWriterReady);
1067        }
1068
1069        let mut result = Ok(());
1070        if queued.iter().any(|frame| frame.completion.is_some()) {
1071            for mut frame in queued {
1072                let confirmed = frame.completion.is_some();
1073                if frame.completion.as_ref().is_some_and(|c| c.is_finished()) {
1074                    result = Err(io::Error::new(
1075                        io::ErrorKind::Interrupted,
1076                        "transmission cancelled by shutdown",
1077                    ));
1078                }
1079                if result.is_ok() {
1080                    result = if frame.completion.is_some() {
1081                        writer.send_frame_confirmed(&frame.data)
1082                    } else {
1083                        writer.send_frame(&frame.data)
1084                    };
1085                    while result
1086                        .as_ref()
1087                        .is_err_and(|e| e.kind() == io::ErrorKind::WouldBlock)
1088                    {
1089                        if frame.completion.as_ref().is_some_and(|c| c.is_finished()) {
1090                            result = Err(io::Error::new(
1091                                io::ErrorKind::Interrupted,
1092                                "transmission cancelled by shutdown",
1093                            ));
1094                            break;
1095                        }
1096                        thread::sleep(Duration::from_millis(1));
1097                        result = writer.flush();
1098                    }
1099                }
1100                if let Some(completion) = frame.completion.take() {
1101                    completion.finish(
1102                        result.as_ref().map(|_| ()).map_err(|e| {
1103                            crate::link_send::LinkSendError::WriteFailed(e.to_string())
1104                        }),
1105                    );
1106                }
1107                // Packet-level policy/validation errors do not imply a broken
1108                // connection (e.g. a Local client that temporarily holds TX).
1109                if confirmed
1110                    && result.as_ref().is_err_and(|error| {
1111                        matches!(
1112                            error.kind(),
1113                            io::ErrorKind::PermissionDenied
1114                                | io::ErrorKind::InvalidInput
1115                                | io::ErrorKind::Unsupported
1116                        )
1117                    })
1118                {
1119                    result = Ok(());
1120                }
1121            }
1122        } else {
1123            let frames: Vec<_> = queued.into_iter().map(|frame| frame.data).collect();
1124            result = writer.send_frames(&frames);
1125        }
1126        while result
1127            .as_ref()
1128            .is_err_and(|error| error.kind() == io::ErrorKind::WouldBlock)
1129        {
1130            thread::sleep(Duration::from_millis(1));
1131            result = writer.flush();
1132        }
1133
1134        if let Some(control) = &metrics.egress_control {
1135            control.release(reserved_bytes);
1136        }
1137        metrics.notify_space();
1138        if event_tx.link_send_pool().in_flight() > 0 {
1139            let _ = event_tx.try_send(crate::event::Event::LinkWriterReady);
1140        }
1141
1142        if let Err(err) = result {
1143            metrics.worker_alive.store(false, Ordering::Relaxed);
1144            log::warn!(
1145                "[{}:{}] async writer exiting after send failure: {}",
1146                interface_name,
1147                interface_id.0,
1148                err
1149            );
1150            writer.shutdown();
1151            rx.close_receiver();
1152            metrics.notify_space();
1153            let _ = event_tx.send(crate::event::Event::InterfaceDown(interface_id));
1154            return;
1155        }
1156    }
1157
1158    rx.close_receiver();
1159    metrics.worker_alive.store(false, Ordering::Relaxed);
1160    metrics.notify_space();
1161}
1162
1163pub use crate::common::interface_stats::{InterfaceStats, TrafficRates, ANNOUNCE_SAMPLE_MAX};
1164
1165use crate::common::management::InterfaceStatusView;
1166
1167/// Everything the driver tracks per interface.
1168pub struct InterfaceEntry {
1169    pub id: InterfaceId,
1170    pub info: InterfaceInfo,
1171    pub writer: Box<dyn Writer>,
1172    pub async_writer_metrics: Option<AsyncWriterMetrics>,
1173    /// Administrative enable/disable state.
1174    pub enabled: bool,
1175    pub online: bool,
1176    /// True for dynamically spawned interfaces (e.g. TCP server clients).
1177    /// These are fully removed on InterfaceDown rather than just marked offline.
1178    pub dynamic: bool,
1179    /// IFAC state for this interface, if access codes are enabled.
1180    pub ifac: Option<IfacState>,
1181    /// Traffic statistics.
1182    pub stats: InterfaceStats,
1183    /// Human-readable interface type string (e.g. "TCPClientInterface").
1184    pub interface_type: String,
1185    /// Next time a send should be retried after a transient WouldBlock.
1186    pub send_retry_at: Option<Instant>,
1187    /// Current retry backoff for transient send failures.
1188    pub send_retry_backoff: Duration,
1189}
1190
1191/// Result of starting an interface via a factory.
1192// Boxing `Simple` would change the published factory result representation.
1193#[allow(clippy::large_enum_variant)]
1194pub enum StartResult {
1195    /// One writer, registered immediately (TcpClient, Udp, Serial, etc.)
1196    Simple {
1197        id: InterfaceId,
1198        info: InterfaceInfo,
1199        writer: Box<dyn Writer>,
1200        interface_type_name: String,
1201        control: Option<ListenerControl>,
1202    },
1203    /// Spawns a listener; dynamic interfaces arrive via Event::InterfaceUp (TcpServer, Auto, I2P, etc.)
1204    Listener { control: Option<ListenerControl> },
1205    /// Multiple subinterfaces from one config (RNode).
1206    Multi {
1207        subinterfaces: Vec<SubInterface>,
1208        control: Option<ListenerControl>,
1209    },
1210}
1211
1212/// A single subinterface returned from a multi-interface factory.
1213pub struct SubInterface {
1214    pub id: InterfaceId,
1215    pub info: InterfaceInfo,
1216    pub writer: Box<dyn Writer>,
1217    pub interface_type_name: String,
1218}
1219
1220/// Context passed to [`InterfaceFactory::start()`].
1221pub struct StartContext {
1222    pub tx: EventSender,
1223    pub next_dynamic_id: Arc<AtomicU64>,
1224    pub mode: u8,
1225    pub gravity: i64,
1226    pub recursive_prs: bool,
1227    pub announces_from_internal: bool,
1228    pub announces_to_internal: Option<bool>,
1229    pub ingress_control: rns_core::transport::types::IngressControlConfig,
1230    pub ifac: Option<IfacState>,
1231    /// Linux `SO_MARK` value applied to IP underlay sockets before connect.
1232    pub underlay_mark: Option<u32>,
1233}
1234
1235/// Opaque interface config data. Each factory downcasts to its concrete type.
1236pub trait InterfaceConfigData: Send + Any {
1237    fn as_any(&self) -> &dyn Any;
1238    fn into_any(self: Box<Self>) -> Box<dyn Any>;
1239}
1240
1241/// ConfigObj section passed to factories that need nested `[[[sections]]]`.
1242pub struct ConfigSection<'a> {
1243    pub params: &'a HashMap<String, String>,
1244    pub children: &'a [crate::config::ParsedSubinterface],
1245}
1246
1247#[derive(Debug, Clone)]
1248pub struct DynamicInterfaceTemplate {
1249    pub parent_id: InterfaceId,
1250    pub interface_type: String,
1251    pub ifac: Option<IfacState>,
1252    pub mode: u8,
1253    pub gravity: i64,
1254    pub recursive_prs: bool,
1255    pub announces_from_internal: bool,
1256    pub announces_to_internal: Option<bool>,
1257}
1258
1259impl DynamicInterfaceTemplate {
1260    pub fn registration(
1261        &self,
1262        mut info: InterfaceInfo,
1263    ) -> crate::event::DynamicInterfaceRegistration {
1264        info.mode = self.mode;
1265        info.gravity = self.gravity;
1266        info.recursive_prs = self.recursive_prs;
1267        info.announces_from_internal = self.announces_from_internal;
1268        info.announces_to_internal = self.announces_to_internal;
1269        crate::event::DynamicInterfaceRegistration {
1270            info,
1271            interface_type: self.interface_type.clone(),
1272            parent_id: self.parent_id,
1273            telemetry: crate::event::InterfaceTelemetry::default(),
1274            ifac: self.ifac.clone(),
1275        }
1276    }
1277}
1278
1279impl<T: Send + 'static> InterfaceConfigData for T {
1280    fn as_any(&self) -> &dyn Any {
1281        self
1282    }
1283
1284    fn into_any(self: Box<Self>) -> Box<dyn Any> {
1285        self
1286    }
1287}
1288
1289/// Factory that can parse config and start an interface type.
1290pub trait InterfaceFactory: Send + Sync {
1291    /// Config-file type name, e.g. "TCPClientInterface".
1292    fn type_name(&self) -> &str;
1293
1294    /// Default IFAC size (bytes). 8 for serial/kiss/rnode, 16 for others.
1295    fn default_ifac_size(&self) -> usize {
1296        16
1297    }
1298
1299    /// Parse from key-value params (config file or external).
1300    fn parse_config(
1301        &self,
1302        name: &str,
1303        id: InterfaceId,
1304        params: &HashMap<String, String>,
1305    ) -> Result<Box<dyn InterfaceConfigData>, String>;
1306
1307    /// Parse a complete ConfigObj interface section. Flat factories inherit
1308    /// the compatibility default; multi-interface factories can inspect children.
1309    fn parse_config_section(
1310        &self,
1311        name: &str,
1312        id: InterfaceId,
1313        section: ConfigSection<'_>,
1314    ) -> Result<Box<dyn InterfaceConfigData>, String> {
1315        self.parse_config(name, id, section.params)
1316    }
1317
1318    /// Start the interface from parsed config.
1319    fn start(
1320        &self,
1321        config: Box<dyn InterfaceConfigData>,
1322        ctx: StartContext,
1323    ) -> io::Result<StartResult>;
1324}
1325
1326impl InterfaceStatusView for InterfaceEntry {
1327    fn id(&self) -> InterfaceId {
1328        self.id
1329    }
1330    fn info(&self) -> &InterfaceInfo {
1331        &self.info
1332    }
1333    fn online(&self) -> bool {
1334        self.online
1335    }
1336    fn stats(&self) -> &InterfaceStats {
1337        &self.stats
1338    }
1339    fn tx_drops(&self) -> u64 {
1340        self.async_writer_metrics
1341            .as_ref()
1342            .map(AsyncWriterMetrics::tx_drops)
1343            .unwrap_or(0)
1344    }
1345    fn tx_dropped_bytes(&self) -> u64 {
1346        self.async_writer_metrics
1347            .as_ref()
1348            .map(AsyncWriterMetrics::tx_dropped_bytes)
1349            .unwrap_or(0)
1350    }
1351    fn tx_stalled(&self) -> bool {
1352        self.async_writer_metrics
1353            .as_ref()
1354            .is_some_and(AsyncWriterMetrics::tx_stalled)
1355    }
1356    fn tx_buffered(&self) -> usize {
1357        self.async_writer_metrics
1358            .as_ref()
1359            .map(AsyncWriterMetrics::tx_buffered)
1360            .unwrap_or(0)
1361    }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366    use super::*;
1367
1368    #[test]
1369    fn shared_medium_hint_matches_interface_class_defaults() {
1370        for interface_type in [
1371            "AX25KISSInterface",
1372            "KISSInterface",
1373            "PipeInterface",
1374            "RNodeInterface",
1375            "RNodeMultiInterface",
1376            "SerialInterface",
1377            "UDPInterface",
1378        ] {
1379            assert!(shared_medium_hint(interface_type), "{interface_type}");
1380        }
1381        for interface_type in [
1382            "AutoInterface",
1383            "BackboneInterface",
1384            "I2PInterface",
1385            "LocalInterface",
1386            "TCPClientInterface",
1387            "TCPServerInterface",
1388        ] {
1389            assert!(!shared_medium_hint(interface_type), "{interface_type}");
1390        }
1391    }
1392    use crate::event::Event;
1393    use rns_core::constants;
1394    use std::sync::mpsc;
1395
1396    struct MockWriter {
1397        sent: Vec<Vec<u8>>,
1398    }
1399
1400    impl MockWriter {
1401        fn new() -> Self {
1402            MockWriter { sent: Vec::new() }
1403        }
1404    }
1405
1406    impl Writer for MockWriter {
1407        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1408            self.sent.push(data.to_vec());
1409            Ok(())
1410        }
1411    }
1412
1413    #[test]
1414    fn interface_entry_construction() {
1415        let entry = InterfaceEntry {
1416            id: InterfaceId(1),
1417            info: InterfaceInfo {
1418                id: InterfaceId(1),
1419                name: String::new(),
1420                mode: constants::MODE_FULL,
1421                gravity: 0,
1422                recursive_prs: false,
1423                announces_from_internal: true,
1424                announces_to_internal: None,
1425                out_capable: true,
1426                in_capable: true,
1427                bitrate: None,
1428                airtime_profile: None,
1429                announce_rate_target: None,
1430                announce_rate_grace: 0,
1431                announce_rate_penalty: 0.0,
1432                announce_cap: constants::ANNOUNCE_CAP,
1433                is_local_client: false,
1434                wants_tunnel: false,
1435                tunnel_id: None,
1436                mtu: constants::MTU as u32,
1437                ia_freq: 0.0,
1438                ip_freq: 0.0,
1439                op_freq: 0.0,
1440                op_samples: 0,
1441                started: 0.0,
1442                ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1443            },
1444            writer: Box::new(MockWriter::new()),
1445            async_writer_metrics: None,
1446            enabled: true,
1447            online: false,
1448            dynamic: false,
1449            ifac: None,
1450            stats: InterfaceStats::default(),
1451            interface_type: String::new(),
1452            send_retry_at: None,
1453            send_retry_backoff: Duration::ZERO,
1454        };
1455        assert_eq!(entry.id, InterfaceId(1));
1456        assert!(!entry.online);
1457        assert!(!entry.dynamic);
1458    }
1459
1460    #[test]
1461    fn dynamic_registration_inherits_announces_to_internal() {
1462        let template = DynamicInterfaceTemplate {
1463            parent_id: InterfaceId(7),
1464            interface_type: "dynamic-test".into(),
1465            ifac: None,
1466            mode: constants::MODE_GATEWAY,
1467            gravity: -3,
1468            recursive_prs: true,
1469            announces_from_internal: false,
1470            announces_to_internal: Some(true),
1471        };
1472        let info = InterfaceInfo {
1473            id: InterfaceId(8),
1474            name: "child".into(),
1475            mode: constants::MODE_FULL,
1476            gravity: 0,
1477            recursive_prs: false,
1478            announces_from_internal: true,
1479            announces_to_internal: None,
1480            out_capable: true,
1481            in_capable: true,
1482            bitrate: None,
1483            airtime_profile: None,
1484            announce_rate_target: None,
1485            announce_rate_grace: 0,
1486            announce_rate_penalty: 0.0,
1487            announce_cap: constants::ANNOUNCE_CAP,
1488            is_local_client: false,
1489            wants_tunnel: false,
1490            tunnel_id: None,
1491            mtu: constants::MTU as u32,
1492            ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1493            ia_freq: 0.0,
1494            ip_freq: 0.0,
1495            op_freq: 0.0,
1496            op_samples: 0,
1497            started: 0.0,
1498        };
1499
1500        let registration = template.registration(info);
1501
1502        assert_eq!(registration.info.mode, constants::MODE_GATEWAY);
1503        assert_eq!(registration.info.gravity, -3);
1504        assert!(registration.info.recursive_prs);
1505        assert!(!registration.info.announces_from_internal);
1506        assert_eq!(registration.info.announces_to_internal, Some(true));
1507    }
1508
1509    #[test]
1510    fn mock_writer_captures_bytes() {
1511        let mut writer = MockWriter::new();
1512        writer.send_frame(b"hello").unwrap();
1513        writer.send_frame(b"world").unwrap();
1514        assert_eq!(writer.sent.len(), 2);
1515        assert_eq!(writer.sent[0], b"hello");
1516        assert_eq!(writer.sent[1], b"world");
1517    }
1518
1519    #[test]
1520    fn writer_send_frame_produces_output() {
1521        let mut writer = MockWriter::new();
1522        let data = vec![0x01, 0x02, 0x03];
1523        writer.send_frame(&data).unwrap();
1524        assert_eq!(writer.sent[0], data);
1525    }
1526
1527    struct BlockingWriter {
1528        entered_tx: mpsc::Sender<()>,
1529        release_rx: mpsc::Receiver<()>,
1530    }
1531
1532    #[test]
1533    fn confirmed_writer_waits_for_flush_and_reports_write_failures() {
1534        use crate::link_send::{Completion, LinkSendError, SendPool};
1535        use futures::FutureExt;
1536        struct FlushWriter {
1537            entered: mpsc::Sender<()>,
1538            release: mpsc::Receiver<()>,
1539            writes: Arc<AtomicUsize>,
1540        }
1541        impl Writer for FlushWriter {
1542            fn send_frame(&mut self, _: &[u8]) -> io::Result<()> {
1543                self.writes.fetch_add(1, Ordering::SeqCst);
1544                Err(io::ErrorKind::WouldBlock.into())
1545            }
1546            fn flush(&mut self) -> io::Result<()> {
1547                self.entered.send(()).unwrap();
1548                self.release.recv().unwrap();
1549                Ok(())
1550            }
1551        }
1552        let (event_tx, _rx) = crate::event::channel();
1553        let (entered, entered_rx) = mpsc::channel();
1554        let (release_tx, release) = mpsc::channel();
1555        let writes = Arc::new(AtomicUsize::new(0));
1556        let pool = SendPool::new(1);
1557        let (completion, mut receipt) = Completion::new(pool.try_acquire().unwrap());
1558        let (mut writer, _) = wrap_async_writer(
1559            Box::new(FlushWriter {
1560                entered,
1561                release,
1562                writes: writes.clone(),
1563            }),
1564            InterfaceId(999),
1565            "flush test",
1566            event_tx.clone(),
1567            1,
1568        );
1569        writer
1570            .enqueue_confirmed(b"once", &mut Some(completion))
1571            .unwrap();
1572        entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1573        assert!((&mut receipt).now_or_never().is_none());
1574        release_tx.send(()).unwrap();
1575        receipt.wait().unwrap();
1576        assert_eq!(
1577            writes.load(Ordering::SeqCst),
1578            1,
1579            "flush must not resubmit the frame"
1580        );
1581
1582        let (completion, receipt) = Completion::new(pool.try_acquire().unwrap());
1583        let (mut writer, _) = wrap_async_writer(
1584            Box::new(FailingWriter {
1585                shutdown_called: Arc::new(AtomicBool::new(false)),
1586            }),
1587            InterfaceId(998),
1588            "failure test",
1589            event_tx,
1590            1,
1591        );
1592        writer
1593            .enqueue_confirmed(b"fail", &mut Some(completion))
1594            .unwrap();
1595        assert_eq!(
1596            receipt.wait(),
1597            Err(LinkSendError::WriteFailed("boom".into()))
1598        );
1599    }
1600
1601    impl Writer for BlockingWriter {
1602        fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1603            let _ = self.entered_tx.send(());
1604            let _ = self.release_rx.recv();
1605            Ok(())
1606        }
1607    }
1608
1609    struct EgressControlledBlockingWriter {
1610        inner: BlockingWriter,
1611        control: EgressControl,
1612    }
1613
1614    impl Writer for EgressControlledBlockingWriter {
1615        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1616            self.inner.send_frame(data)
1617        }
1618
1619        fn egress_control(&self) -> Option<EgressControl> {
1620            Some(self.control.clone())
1621        }
1622    }
1623
1624    struct FailingWriter {
1625        shutdown_called: Arc<AtomicBool>,
1626    }
1627
1628    struct RecordingWriter {
1629        sent_tx: mpsc::Sender<Vec<u8>>,
1630    }
1631
1632    impl Writer for RecordingWriter {
1633        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1634            self.sent_tx
1635                .send(data.to_vec())
1636                .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "test receiver closed"))
1637        }
1638    }
1639
1640    struct BatchRecordingWriter {
1641        batch_tx: mpsc::Sender<usize>,
1642        first_release: Option<mpsc::Receiver<()>>,
1643    }
1644
1645    impl Writer for BatchRecordingWriter {
1646        fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1647            unreachable!("the async worker must use batch delivery")
1648        }
1649
1650        fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
1651            self.batch_tx.send(frames.len()).unwrap();
1652            if let Some(release) = self.first_release.take() {
1653                release.recv().unwrap();
1654            }
1655            Ok(())
1656        }
1657    }
1658
1659    impl Writer for FailingWriter {
1660        fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1661            Err(io::Error::new(io::ErrorKind::BrokenPipe, "boom"))
1662        }
1663
1664        fn shutdown(&mut self) {
1665            self.shutdown_called.store(true, Ordering::Relaxed);
1666        }
1667    }
1668
1669    #[test]
1670    fn async_writer_returns_wouldblock_when_queue_is_full() {
1671        let (event_tx, _event_rx) = crate::event::channel();
1672        let (entered_tx, entered_rx) = mpsc::channel();
1673        let (release_tx, release_rx) = mpsc::channel();
1674        let (mut writer, metrics) = wrap_async_writer(
1675            Box::new(BlockingWriter {
1676                entered_tx,
1677                release_rx,
1678            }),
1679            InterfaceId(7),
1680            "test",
1681            event_tx,
1682            1,
1683        );
1684
1685        writer.send_frame(&[1]).unwrap();
1686        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1687        writer.send_frame(&[2]).unwrap();
1688        assert_eq!(metrics.queued_frames(), 1);
1689        let err = writer.send_frame(&[3]).unwrap_err();
1690        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
1691
1692        release_tx.send(()).unwrap();
1693        entered_rx
1694            .recv_timeout(Duration::from_secs(1))
1695            .expect("the queued frame must wake the writer without another send trigger");
1696        assert_eq!(metrics.queued_frames(), 0);
1697        release_tx.send(()).unwrap();
1698    }
1699
1700    #[test]
1701    fn deferred_sends_are_delivered_on_wake_or_by_the_recheck_backstop() {
1702        struct Recorder(mpsc::Sender<Vec<u8>>);
1703        impl Writer for Recorder {
1704            fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1705                self.0.send(data.to_vec()).unwrap();
1706                Ok(())
1707            }
1708        }
1709        let (event_tx, _event_rx) = crate::event::channel();
1710        let (sent_tx, sent_rx) = mpsc::channel();
1711        let (mut writer, _metrics) = wrap_async_writer(
1712            Box::new(Recorder(sent_tx)),
1713            InterfaceId(9),
1714            "test",
1715            event_tx,
1716            8,
1717        );
1718        // Let the worker go to sleep on the empty queue.
1719        std::thread::sleep(Duration::from_millis(20));
1720        writer.send_frame_deferred(&[1]).unwrap();
1721        writer.send_frame_deferred(&[2]).unwrap();
1722        writer.wake();
1723        assert_eq!(
1724            sent_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
1725            vec![1]
1726        );
1727        assert_eq!(
1728            sent_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
1729            vec![2]
1730        );
1731
1732        std::thread::sleep(Duration::from_millis(20));
1733        writer.send_frame_deferred(&[3]).unwrap();
1734        // No wake: the periodic recheck still delivers the frame.
1735        assert_eq!(
1736            sent_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
1737            vec![3]
1738        );
1739    }
1740
1741    #[test]
1742    fn async_writer_waits_briefly_for_space_then_fails_fast_while_stalled() {
1743        let (event_tx, _event_rx) = crate::event::channel();
1744        let (entered_tx, entered_rx) = mpsc::channel();
1745        let (release_tx, release_rx) = mpsc::channel();
1746        let (mut writer, metrics) = wrap_async_writer(
1747            Box::new(BlockingWriter {
1748                entered_tx,
1749                release_rx,
1750            }),
1751            InterfaceId(8),
1752            "test",
1753            event_tx,
1754            1,
1755        );
1756        writer.send_frame(&[1]).unwrap();
1757        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1758        writer.send_frame(&[2]).unwrap();
1759
1760        // The worker frees the slot while the producer is waiting: the frame
1761        // is admitted instead of being refused.
1762        let release = release_tx.clone();
1763        let releaser = std::thread::spawn(move || {
1764            std::thread::sleep(Duration::from_millis(5));
1765            release.send(()).unwrap();
1766        });
1767        writer.send_frame(&[3]).unwrap();
1768        releaser.join().unwrap();
1769        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1770        assert_eq!(metrics.queued_frames(), 1);
1771
1772        // A writer that stays full costs one bounded wait; later sends do not
1773        // wait again until a frame is admitted.
1774        let started = Instant::now();
1775        let err = writer.send_frame(&[4]).unwrap_err();
1776        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
1777        assert!(started.elapsed() >= ASYNC_WRITER_BACKPRESSURE_WAIT);
1778        let started = Instant::now();
1779        let err = writer.send_frame(&[5]).unwrap_err();
1780        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
1781        assert!(started.elapsed() < ASYNC_WRITER_BACKPRESSURE_WAIT);
1782
1783        for _ in 0..3 {
1784            let _ = release_tx.send(());
1785        }
1786    }
1787
1788    #[test]
1789    fn async_writer_shares_exact_egress_byte_valve_and_stall_gate() {
1790        let (event_tx, _event_rx) = crate::event::channel();
1791        let (entered_tx, entered_rx) = mpsc::channel();
1792        let (release_tx, release_rx) = mpsc::channel();
1793        let control = EgressControl::new(4);
1794        let (mut writer, metrics) = wrap_async_writer(
1795            Box::new(EgressControlledBlockingWriter {
1796                inner: BlockingWriter {
1797                    entered_tx,
1798                    release_rx,
1799                },
1800                control: control.clone(),
1801            }),
1802            InterfaceId(8),
1803            "egress-test",
1804            event_tx,
1805            8,
1806        );
1807
1808        // Two ordinary bytes occupy exactly four HDLC bytes on wire.
1809        writer.send_frame(&[1, 2]).unwrap();
1810        entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1811        assert_eq!(control.reserved_bytes.load(Ordering::Relaxed), 4);
1812        assert_eq!(metrics.tx_buffered(), 4);
1813
1814        // The hard valve drops instead of retaining another four-byte frame.
1815        writer.send_frame(&[3, 4]).unwrap();
1816        assert_eq!(control.reserved_bytes.load(Ordering::Relaxed), 4);
1817        assert_eq!(control.dropped_frames.load(Ordering::Relaxed), 1);
1818        assert_eq!(control.dropped_bytes.load(Ordering::Relaxed), 4);
1819        assert_eq!(metrics.tx_drops(), 1);
1820        assert_eq!(metrics.tx_dropped_bytes(), 4);
1821
1822        release_tx.send(()).unwrap();
1823        let deadline = Instant::now() + Duration::from_secs(1);
1824        while control.reserved_bytes.load(Ordering::Relaxed) != 0 {
1825            assert!(
1826                Instant::now() < deadline,
1827                "egress reservation did not drain"
1828            );
1829            thread::yield_now();
1830        }
1831
1832        control.set_stalled(true);
1833        assert!(metrics.tx_stalled());
1834        writer.send_frame(&[5]).unwrap();
1835        assert_eq!(control.dropped_frames.load(Ordering::Relaxed), 2);
1836        assert_eq!(control.dropped_bytes.load(Ordering::Relaxed), 7);
1837        assert_eq!(metrics.queued_frames(), 0);
1838    }
1839
1840    #[test]
1841    fn async_writer_reports_interface_down_after_worker_failure() {
1842        let (event_tx, event_rx) = crate::event::channel();
1843        let shutdown_called = Arc::new(AtomicBool::new(false));
1844        let (mut writer, metrics) = wrap_async_writer(
1845            Box::new(FailingWriter {
1846                shutdown_called: Arc::clone(&shutdown_called),
1847            }),
1848            InterfaceId(9),
1849            "fail",
1850            event_tx,
1851            2,
1852        );
1853
1854        writer.send_frame(&[1]).unwrap();
1855        let event = event_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1856        assert!(matches!(event, Event::InterfaceDown(InterfaceId(9))));
1857        assert!(!metrics.worker_alive());
1858        assert!(shutdown_called.load(Ordering::Relaxed));
1859    }
1860
1861    #[test]
1862    fn async_writer_preserves_order_through_fifty_thousand_frame_burst() {
1863        const FRAME_COUNT: usize = 50_000;
1864        let (event_tx, _event_rx) = crate::event::channel();
1865        let (sent_tx, sent_rx) = mpsc::channel();
1866        let (mut writer, metrics) = wrap_async_writer(
1867            Box::new(RecordingWriter { sent_tx }),
1868            InterfaceId(11),
1869            "burst",
1870            event_tx,
1871            FRAME_COUNT,
1872        );
1873
1874        for index in 0..FRAME_COUNT {
1875            let payload = (index as u64).to_be_bytes();
1876            writer.send_frame(&payload).unwrap();
1877        }
1878
1879        for index in 0..FRAME_COUNT {
1880            let received = sent_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1881            assert_eq!(received, (index as u64).to_be_bytes());
1882        }
1883        assert!(metrics.worker_alive());
1884    }
1885
1886    #[test]
1887    fn async_writer_coalesces_frames_waiting_behind_an_active_batch() {
1888        let (event_tx, _event_rx) = crate::event::channel();
1889        let (batch_tx, batch_rx) = mpsc::channel();
1890        let (release_tx, release_rx) = mpsc::channel();
1891        let (mut writer, _) = wrap_async_writer(
1892            Box::new(BatchRecordingWriter {
1893                batch_tx,
1894                first_release: Some(release_rx),
1895            }),
1896            InterfaceId(12),
1897            "batch",
1898            event_tx,
1899            16,
1900        );
1901
1902        writer.send_frame(&[0]).unwrap();
1903        assert_eq!(batch_rx.recv_timeout(Duration::from_secs(1)).unwrap(), 1);
1904        for index in 1..=10 {
1905            writer.send_frame(&[index]).unwrap();
1906        }
1907        release_tx.send(()).unwrap();
1908
1909        assert_eq!(batch_rx.recv_timeout(Duration::from_secs(1)).unwrap(), 10);
1910    }
1911}