1#[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
46pub 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
58pub 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#[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#[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#[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
224pub 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
238pub trait Writer: Send {
242 fn send_frame(&mut self, data: &[u8]) -> io::Result<()>;
243
244 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
248 self.send_frame(data)
249 }
250
251 #[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 fn send_frame_deferred(&mut self, data: &[u8]) -> io::Result<()> {
268 self.send_frame(data)
269 }
270
271 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 fn wake(&mut self) {}
282
283 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 fn flush(&mut self) -> io::Result<()> {
297 Ok(())
298 }
299
300 fn egress_control(&self) -> Option<EgressControl> {
303 None
304 }
305
306 fn loop_socket(&self) -> Option<std::os::fd::RawFd> {
310 None
311 }
312
313 #[doc(hidden)]
317 fn loop_egress(&mut self) -> Option<loop_writer::LoopEgress> {
318 None
319 }
320
321 #[doc(hidden)]
324 fn tick(&mut self) {}
325
326 fn shutdown(&mut self) {}
327}
328
329#[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
351pub type InterfaceMap<V> =
353 HashMap<InterfaceId, V, std::hash::BuildHasherDefault<InterfaceIdHasher>>;
354
355pub const DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY: usize = 256;
356
357const WRITER_RECHECK: Duration = Duration::from_millis(5);
360
361pub 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
435pub 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
500pub(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 space: Arc<(Mutex<()>, Condvar)>,
556 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 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
609struct 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 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 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 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
677struct WriterTx(Arc<WriterQueue>);
679
680impl WriterTx {
681 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 skip_wait: bool,
719 defer_wake: bool,
721 wake_pending: bool,
723}
724
725enum Admission {
726 Done,
727 Refused(QueuedFrame),
728}
729
730pub 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 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 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 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 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 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 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 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 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 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
1167pub struct InterfaceEntry {
1169 pub id: InterfaceId,
1170 pub info: InterfaceInfo,
1171 pub writer: Box<dyn Writer>,
1172 pub async_writer_metrics: Option<AsyncWriterMetrics>,
1173 pub enabled: bool,
1175 pub online: bool,
1176 pub dynamic: bool,
1179 pub ifac: Option<IfacState>,
1181 pub stats: InterfaceStats,
1183 pub interface_type: String,
1185 pub send_retry_at: Option<Instant>,
1187 pub send_retry_backoff: Duration,
1189}
1190
1191#[allow(clippy::large_enum_variant)]
1194pub enum StartResult {
1195 Simple {
1197 id: InterfaceId,
1198 info: InterfaceInfo,
1199 writer: Box<dyn Writer>,
1200 interface_type_name: String,
1201 control: Option<ListenerControl>,
1202 },
1203 Listener { control: Option<ListenerControl> },
1205 Multi {
1207 subinterfaces: Vec<SubInterface>,
1208 control: Option<ListenerControl>,
1209 },
1210}
1211
1212pub struct SubInterface {
1214 pub id: InterfaceId,
1215 pub info: InterfaceInfo,
1216 pub writer: Box<dyn Writer>,
1217 pub interface_type_name: String,
1218}
1219
1220pub 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 pub underlay_mark: Option<u32>,
1233}
1234
1235pub trait InterfaceConfigData: Send + Any {
1237 fn as_any(&self) -> &dyn Any;
1238 fn into_any(self: Box<Self>) -> Box<dyn Any>;
1239}
1240
1241pub 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
1289pub trait InterfaceFactory: Send + Sync {
1291 fn type_name(&self) -> &str;
1293
1294 fn default_ifac_size(&self) -> usize {
1296 16
1297 }
1298
1299 fn parse_config(
1301 &self,
1302 name: &str,
1303 id: InterfaceId,
1304 params: &HashMap<String, String>,
1305 ) -> Result<Box<dyn InterfaceConfigData>, String>;
1306
1307 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 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 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 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 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 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 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 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}