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;
15#[cfg(feature = "iface-pipe")]
16pub mod pipe;
17pub mod registry;
18#[cfg(feature = "iface-rnode")]
19pub mod rnode;
20#[cfg(feature = "iface-serial")]
21pub mod serial_iface;
22#[cfg(feature = "iface-tcp")]
23pub mod tcp;
24#[cfg(feature = "iface-tcp")]
25pub mod tcp_server;
26pub(crate) mod transmit_buffer;
27#[cfg(feature = "iface-udp")]
28pub mod udp;
29#[cfg(all(feature = "iface-weave", target_os = "linux"))]
30pub mod weave;
31
32use std::any::Any;
33use std::collections::HashMap;
34use std::io;
35use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
36use std::sync::mpsc::{sync_channel, SyncSender, TrySendError};
37use std::sync::{Arc, Mutex, MutexGuard};
38use std::thread;
39use std::time::{Duration, Instant};
40
41use crate::event::EventSender;
42use crate::ifac::IfacState;
43use rns_core::transport::types::{InterfaceId, InterfaceInfo};
44
45pub trait SocketProtector: Send + Sync {
48 fn protect(&self, fd: std::os::unix::io::RawFd) -> io::Result<()>;
49}
50
51fn socket_protector_slot() -> &'static std::sync::RwLock<Option<Arc<dyn SocketProtector>>> {
52 static SLOT: std::sync::OnceLock<std::sync::RwLock<Option<Arc<dyn SocketProtector>>>> =
53 std::sync::OnceLock::new();
54 SLOT.get_or_init(|| std::sync::RwLock::new(None))
55}
56
57pub struct SocketProtectorGuard {
63 protector: Arc<dyn SocketProtector>,
64}
65
66pub fn install_socket_protector(
67 protector: Arc<dyn SocketProtector>,
68) -> io::Result<SocketProtectorGuard> {
69 let mut slot = socket_protector_slot()
70 .write()
71 .unwrap_or_else(|poisoned| poisoned.into_inner());
72 if slot.is_some() {
73 return Err(io::Error::new(
74 io::ErrorKind::AlreadyExists,
75 "an underlay socket protector is already installed",
76 ));
77 }
78 *slot = Some(Arc::clone(&protector));
79 Ok(SocketProtectorGuard { protector })
80}
81
82#[cfg(test)]
83mod socket_protector_tests {
84 use super::*;
85 use std::sync::atomic::{AtomicUsize, Ordering};
86
87 static TEST_LOCK: Mutex<()> = Mutex::new(());
88 struct CountingProtector(Arc<AtomicUsize>);
89 impl SocketProtector for CountingProtector {
90 fn protect(&self, _fd: std::os::unix::io::RawFd) -> io::Result<()> {
91 self.0.fetch_add(1, Ordering::Relaxed);
92 Ok(())
93 }
94 }
95
96 #[test]
97 fn underlay_helper_invokes_installed_protector_without_mark() {
98 use std::os::fd::AsRawFd;
99 let _lock = TEST_LOCK.lock().unwrap();
100 let calls = Arc::new(AtomicUsize::new(0));
101 let _guard =
102 install_socket_protector(Arc::new(CountingProtector(Arc::clone(&calls)))).unwrap();
103 let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
104 apply_underlay_mark(socket.as_raw_fd(), None).unwrap();
105 assert!(calls.load(Ordering::Relaxed) >= 1);
106 }
107}
108
109impl Drop for SocketProtectorGuard {
110 fn drop(&mut self) {
111 let mut slot = socket_protector_slot()
112 .write()
113 .unwrap_or_else(|poisoned| poisoned.into_inner());
114 if slot
115 .as_ref()
116 .is_some_and(|installed| Arc::ptr_eq(installed, &self.protector))
117 {
118 *slot = None;
119 }
120 }
121}
122
123fn protect_underlay_socket(fd: std::os::unix::io::RawFd) -> io::Result<()> {
124 let protector = socket_protector_slot()
125 .read()
126 .unwrap_or_else(|poisoned| poisoned.into_inner())
127 .clone();
128 if let Some(protector) = protector {
129 protector.protect(fd)?;
130 }
131 Ok(())
132}
133
134#[cfg(target_os = "linux")]
138pub fn bind_to_device(fd: std::os::unix::io::RawFd, device: &str) -> io::Result<()> {
139 let dev_bytes = device.as_bytes();
140 if dev_bytes.len() >= libc::IFNAMSIZ {
141 return Err(io::Error::new(
142 io::ErrorKind::InvalidInput,
143 format!("device name too long: {}", device),
144 ));
145 }
146 let ret = unsafe {
147 libc::setsockopt(
148 fd,
149 libc::SOL_SOCKET,
150 libc::SO_BINDTODEVICE,
151 dev_bytes.as_ptr() as *const libc::c_void,
152 dev_bytes.len() as libc::socklen_t,
153 )
154 };
155 if ret != 0 {
156 return Err(io::Error::last_os_error());
157 }
158 Ok(())
159}
160
161#[cfg(target_os = "linux")]
168pub fn apply_underlay_mark(fd: std::os::unix::io::RawFd, mark: Option<u32>) -> io::Result<()> {
169 protect_underlay_socket(fd)?;
170 let Some(mark) = mark else {
171 return Ok(());
172 };
173 let ret = unsafe {
174 libc::setsockopt(
175 fd,
176 libc::SOL_SOCKET,
177 libc::SO_MARK,
178 &mark as *const _ as *const libc::c_void,
179 std::mem::size_of::<u32>() as libc::socklen_t,
180 )
181 };
182 if ret != 0 {
183 return Err(io::Error::last_os_error());
184 }
185 Ok(())
186}
187
188#[cfg(not(target_os = "linux"))]
189pub fn apply_underlay_mark(fd: std::os::unix::io::RawFd, mark: Option<u32>) -> io::Result<()> {
190 protect_underlay_socket(fd)?;
191 if mark.is_some() {
192 return Err(io::Error::new(
193 io::ErrorKind::Unsupported,
194 "underlay socket marks are only supported on Linux",
195 ));
196 }
197 Ok(())
198}
199
200#[cfg(target_os = "linux")]
202pub fn connect_tcp_with_options(
203 addr: &std::net::SocketAddr,
204 device: Option<&str>,
205 underlay_mark: Option<u32>,
206 timeout: Duration,
207) -> io::Result<std::net::TcpStream> {
208 use std::os::unix::io::AsRawFd;
209
210 let socket = socket2::Socket::new(
211 socket2::Domain::for_address(*addr),
212 socket2::Type::STREAM,
213 Some(socket2::Protocol::TCP),
214 )?;
215 apply_underlay_mark(socket.as_raw_fd(), underlay_mark)?;
216 if let Some(device) = device {
217 bind_to_device(socket.as_raw_fd(), device)?;
218 }
219 socket.connect_timeout(&(*addr).into(), timeout)?;
220 Ok(socket.into())
221}
222
223pub fn validate_underlay_mark(mark: Option<u32>) -> io::Result<()> {
225 let Some(_) = mark else {
226 return Ok(());
227 };
228 let socket = socket2::Socket::new(
229 socket2::Domain::IPV4,
230 socket2::Type::DGRAM,
231 Some(socket2::Protocol::UDP),
232 )?;
233 use std::os::unix::io::AsRawFd;
234 apply_underlay_mark(socket.as_raw_fd(), mark)
235}
236
237pub trait Writer: Send {
241 fn send_frame(&mut self, data: &[u8]) -> io::Result<()>;
242
243 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
247 self.send_frame(data)
248 }
249
250 #[doc(hidden)]
252 fn enqueue_confirmed(
253 &mut self,
254 _data: &[u8],
255 _completion: &mut Option<crate::link_send::Completion>,
256 ) -> io::Result<()> {
257 Err(io::Error::new(
258 io::ErrorKind::Unsupported,
259 "confirmed sends require an async interface writer",
260 ))
261 }
262
263 fn send_frames(&mut self, frames: &[Vec<u8>]) -> io::Result<()> {
264 for frame in frames {
265 self.send_frame(frame)?;
266 }
267 Ok(())
268 }
269
270 fn flush(&mut self) -> io::Result<()> {
272 Ok(())
273 }
274
275 fn egress_control(&self) -> Option<EgressControl> {
278 None
279 }
280
281 fn shutdown(&mut self) {}
282}
283
284pub const DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY: usize = 256;
285
286#[derive(Clone)]
287#[doc(hidden)]
288pub struct EgressControl {
289 byte_limit: usize,
290 reserved_bytes: Arc<AtomicUsize>,
291 stalled: Arc<AtomicBool>,
292 dropped_frames: Arc<AtomicU64>,
293 dropped_bytes: Arc<AtomicU64>,
294}
295
296impl EgressControl {
297 pub(crate) fn new(byte_limit: usize) -> Self {
298 Self {
299 byte_limit,
300 reserved_bytes: Arc::new(AtomicUsize::new(0)),
301 stalled: Arc::new(AtomicBool::new(false)),
302 dropped_frames: Arc::new(AtomicU64::new(0)),
303 dropped_bytes: Arc::new(AtomicU64::new(0)),
304 }
305 }
306
307 fn try_reserve(&self, bytes: usize) -> bool {
308 if self.stalled.load(Ordering::Relaxed) {
309 return false;
310 }
311 let mut current = self.reserved_bytes.load(Ordering::Relaxed);
312 loop {
313 let Some(next) = current.checked_add(bytes) else {
314 return false;
315 };
316 if next > self.byte_limit {
317 return false;
318 }
319 match self.reserved_bytes.compare_exchange_weak(
320 current,
321 next,
322 Ordering::Relaxed,
323 Ordering::Relaxed,
324 ) {
325 Ok(_) => return true,
326 Err(observed) => current = observed,
327 }
328 }
329 }
330
331 fn release(&self, bytes: usize) {
332 self.reserved_bytes.fetch_sub(bytes, Ordering::Relaxed);
333 }
334
335 pub(crate) fn stalled(&self) -> bool {
336 self.stalled.load(Ordering::Relaxed)
337 }
338
339 pub(crate) fn set_stalled(&self, stalled: bool) {
340 self.stalled.store(stalled, Ordering::Relaxed);
341 }
342
343 pub(crate) fn record_drop(&self, bytes: usize) {
344 self.dropped_frames.fetch_add(1, Ordering::Relaxed);
345 self.dropped_bytes
346 .fetch_add(bytes as u64, Ordering::Relaxed);
347 }
348}
349
350pub fn shared_medium_hint(interface_type: &str) -> bool {
356 matches!(
357 interface_type,
358 "AX25KISSInterface"
359 | "KISSInterface"
360 | "PipeInterface"
361 | "RNodeInterface"
362 | "RNodeMultiInterface"
363 | "SerialInterface"
364 | "UDPInterface"
365 )
366}
367
368pub(crate) fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>, label: &str) -> MutexGuard<'a, T> {
369 match mutex.lock() {
370 Ok(guard) => guard,
371 Err(poisoned) => {
372 log::warn!("recovering poisoned mutex: {}", label);
373 poisoned.into_inner()
374 }
375 }
376}
377
378#[derive(Clone, Default)]
379pub struct ListenerControl {
380 stop: Arc<AtomicBool>,
381 running: Option<Arc<AtomicBool>>,
382}
383
384impl ListenerControl {
385 pub fn new() -> Self {
386 Self {
387 stop: Arc::new(AtomicBool::new(false)),
388 running: None,
389 }
390 }
391
392 pub(crate) fn with_running_flag(running: Arc<AtomicBool>) -> Self {
393 Self {
394 stop: Arc::new(AtomicBool::new(false)),
395 running: Some(running),
396 }
397 }
398
399 pub fn request_stop(&self) {
400 self.stop.store(true, Ordering::Relaxed);
401 if let Some(running) = &self.running {
402 running.store(false, Ordering::Relaxed);
403 }
404 }
405
406 pub fn should_stop(&self) -> bool {
407 self.stop.load(Ordering::Relaxed)
408 }
409
410 pub(crate) fn same_instance(&self, other: &Self) -> bool {
411 Arc::ptr_eq(&self.stop, &other.stop)
412 }
413}
414
415pub(crate) fn poll_readable(fd: std::os::fd::RawFd, timeout: Duration) -> io::Result<bool> {
417 let mut pfd = libc::pollfd {
418 fd,
419 events: libc::POLLIN,
420 revents: 0,
421 };
422 let timeout_ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
423 let result = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
424 if result < 0 {
425 let error = io::Error::last_os_error();
426 return if error.kind() == io::ErrorKind::Interrupted {
427 Ok(false)
428 } else {
429 Err(error)
430 };
431 }
432 if pfd.revents & libc::POLLNVAL != 0 {
433 return Err(io::Error::new(
434 io::ErrorKind::BrokenPipe,
435 "serial port closed",
436 ));
437 }
438 Ok(result > 0)
439}
440
441pub(crate) fn wait_while_active(control: &ListenerControl, duration: Duration) -> bool {
442 let deadline = Instant::now() + duration;
443 while !control.should_stop() {
444 let remaining = deadline.saturating_duration_since(Instant::now());
445 if remaining.is_zero() {
446 return true;
447 }
448 thread::sleep(remaining.min(Duration::from_millis(100)));
449 }
450 false
451}
452
453pub(crate) struct UnavailableWriter;
454
455impl Writer for UnavailableWriter {
456 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
457 Err(io::Error::new(
458 io::ErrorKind::NotConnected,
459 "interface is waiting for its port",
460 ))
461 }
462}
463
464#[derive(Clone, Default)]
465pub struct AsyncWriterMetrics {
466 queued_frames: Arc<AtomicUsize>,
467 worker_alive: Arc<AtomicBool>,
468 egress_control: Option<EgressControl>,
469}
470
471impl AsyncWriterMetrics {
472 pub fn queued_frames(&self) -> usize {
473 self.queued_frames.load(Ordering::Relaxed)
474 }
475
476 pub fn worker_alive(&self) -> bool {
477 self.worker_alive.load(Ordering::Relaxed)
478 }
479
480 pub fn tx_buffered(&self) -> usize {
482 self.egress_control
483 .as_ref()
484 .map(|control| control.reserved_bytes.load(Ordering::Relaxed))
485 .unwrap_or(0)
486 }
487
488 pub fn tx_drops(&self) -> u64 {
489 self.egress_control
490 .as_ref()
491 .map(|control| control.dropped_frames.load(Ordering::Relaxed))
492 .unwrap_or(0)
493 }
494
495 pub fn tx_dropped_bytes(&self) -> u64 {
496 self.egress_control
497 .as_ref()
498 .map(|control| control.dropped_bytes.load(Ordering::Relaxed))
499 .unwrap_or(0)
500 }
501
502 pub fn tx_stalled(&self) -> bool {
503 self.egress_control
504 .as_ref()
505 .is_some_and(EgressControl::stalled)
506 }
507}
508
509struct AsyncWriter {
510 tx: SyncSender<QueuedFrame>,
511 metrics: AsyncWriterMetrics,
512}
513
514struct QueuedFrame {
515 data: Vec<u8>,
516 reserved_bytes: usize,
517 completion: Option<crate::link_send::Completion>,
518}
519
520impl Writer for AsyncWriter {
521 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
522 self.enqueue(data, &mut None)
523 }
524
525 fn enqueue_confirmed(
526 &mut self,
527 data: &[u8],
528 completion: &mut Option<crate::link_send::Completion>,
529 ) -> io::Result<()> {
530 self.enqueue(data, completion)
531 }
532}
533
534impl AsyncWriter {
535 fn enqueue(
536 &mut self,
537 data: &[u8],
538 completion: &mut Option<crate::link_send::Completion>,
539 ) -> io::Result<()> {
540 if !self.metrics.worker_alive() {
541 return Err(io::Error::new(
542 io::ErrorKind::BrokenPipe,
543 "interface writer worker is offline",
544 ));
545 }
546
547 let reserved_bytes = if let Some(control) = &self.metrics.egress_control {
548 let framed_bytes = crate::hdlc::framed_len(data);
549 if !control.try_reserve(framed_bytes) {
550 if completion.is_some() {
551 if framed_bytes > control.byte_limit {
552 return Err(io::Error::new(
553 io::ErrorKind::InvalidInput,
554 "frame exceeds interface byte budget",
555 ));
556 }
557 return Err(io::Error::new(
558 io::ErrorKind::WouldBlock,
559 "interface egress budget is full",
560 ));
561 }
562 control.record_drop(framed_bytes);
563 return Ok(());
564 }
565 framed_bytes
566 } else {
567 0
568 };
569
570 self.metrics.queued_frames.fetch_add(1, Ordering::Relaxed);
572 match self.tx.try_send(QueuedFrame {
573 data: data.to_vec(),
574 reserved_bytes,
575 completion: completion.take(),
576 }) {
577 Ok(()) => Ok(()),
578 Err(TrySendError::Full(frame)) => {
579 *completion = frame.completion;
580 self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
581 if let Some(control) = &self.metrics.egress_control {
582 control.release(frame.reserved_bytes);
583 }
584 Err(io::Error::new(
585 io::ErrorKind::WouldBlock,
586 "interface writer queue is full",
587 ))
588 }
589 Err(TrySendError::Disconnected(frame)) => {
590 *completion = frame.completion;
591 self.metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
592 if let Some(control) = &self.metrics.egress_control {
593 control.release(frame.reserved_bytes);
594 }
595 self.metrics.worker_alive.store(false, Ordering::Relaxed);
596 Err(io::Error::new(
597 io::ErrorKind::BrokenPipe,
598 "interface writer worker disconnected",
599 ))
600 }
601 }
602 }
603}
604
605pub fn wrap_async_writer(
606 writer: Box<dyn Writer>,
607 interface_id: InterfaceId,
608 interface_name: &str,
609 event_tx: EventSender,
610 queue_capacity: usize,
611) -> (Box<dyn Writer>, AsyncWriterMetrics) {
612 let egress_control = writer.egress_control();
613 let (tx, rx) = sync_channel::<QueuedFrame>(queue_capacity.max(1));
614 let metrics = AsyncWriterMetrics {
615 queued_frames: Arc::new(AtomicUsize::new(0)),
616 worker_alive: Arc::new(AtomicBool::new(true)),
617 egress_control,
618 };
619 let metrics_thread = metrics.clone();
620 let name = interface_name.to_string();
621
622 let spawn_result = thread::Builder::new()
623 .name(format!("iface-writer-{}", interface_id.0))
624 .spawn(move || async_writer_loop(writer, rx, interface_id, name, event_tx, metrics_thread));
625
626 if let Err(err) = spawn_result {
627 metrics.worker_alive.store(false, Ordering::Relaxed);
628 log::error!(
629 "[{}:{}] failed to spawn async writer thread: {}",
630 interface_name,
631 interface_id.0,
632 err
633 );
634 return (Box::new(DirectWriterFallback), metrics);
635 }
636
637 (
638 Box::new(AsyncWriter {
639 tx,
640 metrics: metrics.clone(),
641 }),
642 metrics,
643 )
644}
645
646struct DirectWriterFallback;
647
648impl Writer for DirectWriterFallback {
649 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
650 Err(io::Error::other("interface writer worker unavailable"))
651 }
652}
653
654fn async_writer_loop(
655 mut writer: Box<dyn Writer>,
656 rx: std::sync::mpsc::Receiver<QueuedFrame>,
657 interface_id: InterfaceId,
658 interface_name: String,
659 event_tx: EventSender,
660 metrics: AsyncWriterMetrics,
661) {
662 while let Ok(first) = rx.recv() {
663 let mut queued = vec![first];
664 queued.extend(rx.try_iter());
665 let reserved_bytes = queued.iter().map(|frame| frame.reserved_bytes).sum();
666 metrics
667 .queued_frames
668 .fetch_sub(queued.len(), Ordering::Relaxed);
669 if event_tx.link_send_pool().in_flight() > 0 {
670 let _ = event_tx.try_send(crate::event::Event::LinkWriterReady);
671 }
672
673 let mut result = Ok(());
674 if queued.iter().any(|frame| frame.completion.is_some()) {
675 for mut frame in queued {
676 let confirmed = frame.completion.is_some();
677 if frame.completion.as_ref().is_some_and(|c| c.is_finished()) {
678 result = Err(io::Error::new(
679 io::ErrorKind::Interrupted,
680 "transmission cancelled by shutdown",
681 ));
682 }
683 if result.is_ok() {
684 result = if frame.completion.is_some() {
685 writer.send_frame_confirmed(&frame.data)
686 } else {
687 writer.send_frame(&frame.data)
688 };
689 while result
690 .as_ref()
691 .is_err_and(|e| e.kind() == io::ErrorKind::WouldBlock)
692 {
693 if frame.completion.as_ref().is_some_and(|c| c.is_finished()) {
694 result = Err(io::Error::new(
695 io::ErrorKind::Interrupted,
696 "transmission cancelled by shutdown",
697 ));
698 break;
699 }
700 thread::sleep(Duration::from_millis(1));
701 result = writer.flush();
702 }
703 }
704 if let Some(completion) = frame.completion.take() {
705 completion.finish(
706 result.as_ref().map(|_| ()).map_err(|e| {
707 crate::link_send::LinkSendError::WriteFailed(e.to_string())
708 }),
709 );
710 }
711 if confirmed
714 && result.as_ref().is_err_and(|error| {
715 matches!(
716 error.kind(),
717 io::ErrorKind::PermissionDenied
718 | io::ErrorKind::InvalidInput
719 | io::ErrorKind::Unsupported
720 )
721 })
722 {
723 result = Ok(());
724 }
725 }
726 } else {
727 let frames: Vec<_> = queued.into_iter().map(|frame| frame.data).collect();
728 result = writer.send_frames(&frames);
729 }
730 while result
731 .as_ref()
732 .is_err_and(|error| error.kind() == io::ErrorKind::WouldBlock)
733 {
734 thread::sleep(Duration::from_millis(1));
735 result = writer.flush();
736 }
737
738 if let Some(control) = &metrics.egress_control {
739 control.release(reserved_bytes);
740 }
741 if event_tx.link_send_pool().in_flight() > 0 {
742 let _ = event_tx.try_send(crate::event::Event::LinkWriterReady);
743 }
744
745 if let Err(err) = result {
746 metrics.worker_alive.store(false, Ordering::Relaxed);
747 log::warn!(
748 "[{}:{}] async writer exiting after send failure: {}",
749 interface_name,
750 interface_id.0,
751 err
752 );
753 writer.shutdown();
754 let _ = event_tx.send(crate::event::Event::InterfaceDown(interface_id));
755 return;
756 }
757 }
758
759 metrics.worker_alive.store(false, Ordering::Relaxed);
760}
761
762pub use crate::common::interface_stats::{InterfaceStats, TrafficRates, ANNOUNCE_SAMPLE_MAX};
763
764use crate::common::management::InterfaceStatusView;
765
766pub struct InterfaceEntry {
768 pub id: InterfaceId,
769 pub info: InterfaceInfo,
770 pub writer: Box<dyn Writer>,
771 pub async_writer_metrics: Option<AsyncWriterMetrics>,
772 pub enabled: bool,
774 pub online: bool,
775 pub dynamic: bool,
778 pub ifac: Option<IfacState>,
780 pub stats: InterfaceStats,
782 pub interface_type: String,
784 pub send_retry_at: Option<Instant>,
786 pub send_retry_backoff: Duration,
788}
789
790#[allow(clippy::large_enum_variant)]
793pub enum StartResult {
794 Simple {
796 id: InterfaceId,
797 info: InterfaceInfo,
798 writer: Box<dyn Writer>,
799 interface_type_name: String,
800 control: Option<ListenerControl>,
801 },
802 Listener { control: Option<ListenerControl> },
804 Multi {
806 subinterfaces: Vec<SubInterface>,
807 control: Option<ListenerControl>,
808 },
809}
810
811pub struct SubInterface {
813 pub id: InterfaceId,
814 pub info: InterfaceInfo,
815 pub writer: Box<dyn Writer>,
816 pub interface_type_name: String,
817}
818
819pub struct StartContext {
821 pub tx: EventSender,
822 pub next_dynamic_id: Arc<AtomicU64>,
823 pub mode: u8,
824 pub gravity: i64,
825 pub recursive_prs: bool,
826 pub announces_from_internal: bool,
827 pub announces_to_internal: Option<bool>,
828 pub ingress_control: rns_core::transport::types::IngressControlConfig,
829 pub ifac: Option<IfacState>,
830 pub underlay_mark: Option<u32>,
832}
833
834pub trait InterfaceConfigData: Send + Any {
836 fn as_any(&self) -> &dyn Any;
837 fn into_any(self: Box<Self>) -> Box<dyn Any>;
838}
839
840pub struct ConfigSection<'a> {
842 pub params: &'a HashMap<String, String>,
843 pub children: &'a [crate::config::ParsedSubinterface],
844}
845
846#[derive(Debug, Clone)]
847pub struct DynamicInterfaceTemplate {
848 pub parent_id: InterfaceId,
849 pub interface_type: String,
850 pub ifac: Option<IfacState>,
851 pub mode: u8,
852 pub gravity: i64,
853 pub recursive_prs: bool,
854 pub announces_from_internal: bool,
855 pub announces_to_internal: Option<bool>,
856}
857
858impl DynamicInterfaceTemplate {
859 pub fn registration(
860 &self,
861 mut info: InterfaceInfo,
862 ) -> crate::event::DynamicInterfaceRegistration {
863 info.mode = self.mode;
864 info.gravity = self.gravity;
865 info.recursive_prs = self.recursive_prs;
866 info.announces_from_internal = self.announces_from_internal;
867 info.announces_to_internal = self.announces_to_internal;
868 crate::event::DynamicInterfaceRegistration {
869 info,
870 interface_type: self.interface_type.clone(),
871 parent_id: self.parent_id,
872 telemetry: crate::event::InterfaceTelemetry::default(),
873 ifac: self.ifac.clone(),
874 }
875 }
876}
877
878impl<T: Send + 'static> InterfaceConfigData for T {
879 fn as_any(&self) -> &dyn Any {
880 self
881 }
882
883 fn into_any(self: Box<Self>) -> Box<dyn Any> {
884 self
885 }
886}
887
888pub trait InterfaceFactory: Send + Sync {
890 fn type_name(&self) -> &str;
892
893 fn default_ifac_size(&self) -> usize {
895 16
896 }
897
898 fn parse_config(
900 &self,
901 name: &str,
902 id: InterfaceId,
903 params: &HashMap<String, String>,
904 ) -> Result<Box<dyn InterfaceConfigData>, String>;
905
906 fn parse_config_section(
909 &self,
910 name: &str,
911 id: InterfaceId,
912 section: ConfigSection<'_>,
913 ) -> Result<Box<dyn InterfaceConfigData>, String> {
914 self.parse_config(name, id, section.params)
915 }
916
917 fn start(
919 &self,
920 config: Box<dyn InterfaceConfigData>,
921 ctx: StartContext,
922 ) -> io::Result<StartResult>;
923}
924
925impl InterfaceStatusView for InterfaceEntry {
926 fn id(&self) -> InterfaceId {
927 self.id
928 }
929 fn info(&self) -> &InterfaceInfo {
930 &self.info
931 }
932 fn online(&self) -> bool {
933 self.online
934 }
935 fn stats(&self) -> &InterfaceStats {
936 &self.stats
937 }
938 fn tx_drops(&self) -> u64 {
939 self.async_writer_metrics
940 .as_ref()
941 .map(AsyncWriterMetrics::tx_drops)
942 .unwrap_or(0)
943 }
944 fn tx_dropped_bytes(&self) -> u64 {
945 self.async_writer_metrics
946 .as_ref()
947 .map(AsyncWriterMetrics::tx_dropped_bytes)
948 .unwrap_or(0)
949 }
950 fn tx_stalled(&self) -> bool {
951 self.async_writer_metrics
952 .as_ref()
953 .is_some_and(AsyncWriterMetrics::tx_stalled)
954 }
955 fn tx_buffered(&self) -> usize {
956 self.async_writer_metrics
957 .as_ref()
958 .map(AsyncWriterMetrics::tx_buffered)
959 .unwrap_or(0)
960 }
961}
962
963#[cfg(test)]
964mod tests {
965 use super::*;
966
967 #[test]
968 fn shared_medium_hint_matches_interface_class_defaults() {
969 for interface_type in [
970 "AX25KISSInterface",
971 "KISSInterface",
972 "PipeInterface",
973 "RNodeInterface",
974 "RNodeMultiInterface",
975 "SerialInterface",
976 "UDPInterface",
977 ] {
978 assert!(shared_medium_hint(interface_type), "{interface_type}");
979 }
980 for interface_type in [
981 "AutoInterface",
982 "BackboneInterface",
983 "I2PInterface",
984 "LocalInterface",
985 "TCPClientInterface",
986 "TCPServerInterface",
987 ] {
988 assert!(!shared_medium_hint(interface_type), "{interface_type}");
989 }
990 }
991 use crate::event::Event;
992 use rns_core::constants;
993 use std::sync::mpsc;
994
995 struct MockWriter {
996 sent: Vec<Vec<u8>>,
997 }
998
999 impl MockWriter {
1000 fn new() -> Self {
1001 MockWriter { sent: Vec::new() }
1002 }
1003 }
1004
1005 impl Writer for MockWriter {
1006 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1007 self.sent.push(data.to_vec());
1008 Ok(())
1009 }
1010 }
1011
1012 #[test]
1013 fn interface_entry_construction() {
1014 let entry = InterfaceEntry {
1015 id: InterfaceId(1),
1016 info: InterfaceInfo {
1017 id: InterfaceId(1),
1018 name: String::new(),
1019 mode: constants::MODE_FULL,
1020 gravity: 0,
1021 recursive_prs: false,
1022 announces_from_internal: true,
1023 announces_to_internal: None,
1024 out_capable: true,
1025 in_capable: true,
1026 bitrate: None,
1027 airtime_profile: None,
1028 announce_rate_target: None,
1029 announce_rate_grace: 0,
1030 announce_rate_penalty: 0.0,
1031 announce_cap: constants::ANNOUNCE_CAP,
1032 is_local_client: false,
1033 wants_tunnel: false,
1034 tunnel_id: None,
1035 mtu: constants::MTU as u32,
1036 ia_freq: 0.0,
1037 ip_freq: 0.0,
1038 op_freq: 0.0,
1039 op_samples: 0,
1040 started: 0.0,
1041 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1042 },
1043 writer: Box::new(MockWriter::new()),
1044 async_writer_metrics: None,
1045 enabled: true,
1046 online: false,
1047 dynamic: false,
1048 ifac: None,
1049 stats: InterfaceStats::default(),
1050 interface_type: String::new(),
1051 send_retry_at: None,
1052 send_retry_backoff: Duration::ZERO,
1053 };
1054 assert_eq!(entry.id, InterfaceId(1));
1055 assert!(!entry.online);
1056 assert!(!entry.dynamic);
1057 }
1058
1059 #[test]
1060 fn dynamic_registration_inherits_announces_to_internal() {
1061 let template = DynamicInterfaceTemplate {
1062 parent_id: InterfaceId(7),
1063 interface_type: "dynamic-test".into(),
1064 ifac: None,
1065 mode: constants::MODE_GATEWAY,
1066 gravity: -3,
1067 recursive_prs: true,
1068 announces_from_internal: false,
1069 announces_to_internal: Some(true),
1070 };
1071 let info = InterfaceInfo {
1072 id: InterfaceId(8),
1073 name: "child".into(),
1074 mode: constants::MODE_FULL,
1075 gravity: 0,
1076 recursive_prs: false,
1077 announces_from_internal: true,
1078 announces_to_internal: None,
1079 out_capable: true,
1080 in_capable: true,
1081 bitrate: None,
1082 airtime_profile: None,
1083 announce_rate_target: None,
1084 announce_rate_grace: 0,
1085 announce_rate_penalty: 0.0,
1086 announce_cap: constants::ANNOUNCE_CAP,
1087 is_local_client: false,
1088 wants_tunnel: false,
1089 tunnel_id: None,
1090 mtu: constants::MTU as u32,
1091 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
1092 ia_freq: 0.0,
1093 ip_freq: 0.0,
1094 op_freq: 0.0,
1095 op_samples: 0,
1096 started: 0.0,
1097 };
1098
1099 let registration = template.registration(info);
1100
1101 assert_eq!(registration.info.mode, constants::MODE_GATEWAY);
1102 assert_eq!(registration.info.gravity, -3);
1103 assert!(registration.info.recursive_prs);
1104 assert!(!registration.info.announces_from_internal);
1105 assert_eq!(registration.info.announces_to_internal, Some(true));
1106 }
1107
1108 #[test]
1109 fn mock_writer_captures_bytes() {
1110 let mut writer = MockWriter::new();
1111 writer.send_frame(b"hello").unwrap();
1112 writer.send_frame(b"world").unwrap();
1113 assert_eq!(writer.sent.len(), 2);
1114 assert_eq!(writer.sent[0], b"hello");
1115 assert_eq!(writer.sent[1], b"world");
1116 }
1117
1118 #[test]
1119 fn writer_send_frame_produces_output() {
1120 let mut writer = MockWriter::new();
1121 let data = vec![0x01, 0x02, 0x03];
1122 writer.send_frame(&data).unwrap();
1123 assert_eq!(writer.sent[0], data);
1124 }
1125
1126 struct BlockingWriter {
1127 entered_tx: mpsc::Sender<()>,
1128 release_rx: mpsc::Receiver<()>,
1129 }
1130
1131 #[test]
1132 fn confirmed_writer_waits_for_flush_and_reports_write_failures() {
1133 use crate::link_send::{Completion, LinkSendError, SendPool};
1134 use futures::FutureExt;
1135 struct FlushWriter {
1136 entered: mpsc::Sender<()>,
1137 release: mpsc::Receiver<()>,
1138 writes: Arc<AtomicUsize>,
1139 }
1140 impl Writer for FlushWriter {
1141 fn send_frame(&mut self, _: &[u8]) -> io::Result<()> {
1142 self.writes.fetch_add(1, Ordering::SeqCst);
1143 Err(io::ErrorKind::WouldBlock.into())
1144 }
1145 fn flush(&mut self) -> io::Result<()> {
1146 self.entered.send(()).unwrap();
1147 self.release.recv().unwrap();
1148 Ok(())
1149 }
1150 }
1151 let (event_tx, _rx) = crate::event::channel();
1152 let (entered, entered_rx) = mpsc::channel();
1153 let (release_tx, release) = mpsc::channel();
1154 let writes = Arc::new(AtomicUsize::new(0));
1155 let pool = SendPool::new(1);
1156 let (completion, mut receipt) = Completion::new(pool.try_acquire().unwrap());
1157 let (mut writer, _) = wrap_async_writer(
1158 Box::new(FlushWriter {
1159 entered,
1160 release,
1161 writes: writes.clone(),
1162 }),
1163 InterfaceId(999),
1164 "flush test",
1165 event_tx.clone(),
1166 1,
1167 );
1168 writer
1169 .enqueue_confirmed(b"once", &mut Some(completion))
1170 .unwrap();
1171 entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1172 assert!((&mut receipt).now_or_never().is_none());
1173 release_tx.send(()).unwrap();
1174 receipt.wait().unwrap();
1175 assert_eq!(
1176 writes.load(Ordering::SeqCst),
1177 1,
1178 "flush must not resubmit the frame"
1179 );
1180
1181 let (completion, receipt) = Completion::new(pool.try_acquire().unwrap());
1182 let (mut writer, _) = wrap_async_writer(
1183 Box::new(FailingWriter {
1184 shutdown_called: Arc::new(AtomicBool::new(false)),
1185 }),
1186 InterfaceId(998),
1187 "failure test",
1188 event_tx,
1189 1,
1190 );
1191 writer
1192 .enqueue_confirmed(b"fail", &mut Some(completion))
1193 .unwrap();
1194 assert_eq!(
1195 receipt.wait(),
1196 Err(LinkSendError::WriteFailed("boom".into()))
1197 );
1198 }
1199
1200 impl Writer for BlockingWriter {
1201 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1202 let _ = self.entered_tx.send(());
1203 let _ = self.release_rx.recv();
1204 Ok(())
1205 }
1206 }
1207
1208 struct EgressControlledBlockingWriter {
1209 inner: BlockingWriter,
1210 control: EgressControl,
1211 }
1212
1213 impl Writer for EgressControlledBlockingWriter {
1214 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1215 self.inner.send_frame(data)
1216 }
1217
1218 fn egress_control(&self) -> Option<EgressControl> {
1219 Some(self.control.clone())
1220 }
1221 }
1222
1223 struct FailingWriter {
1224 shutdown_called: Arc<AtomicBool>,
1225 }
1226
1227 struct RecordingWriter {
1228 sent_tx: mpsc::Sender<Vec<u8>>,
1229 }
1230
1231 impl Writer for RecordingWriter {
1232 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1233 self.sent_tx
1234 .send(data.to_vec())
1235 .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "test receiver closed"))
1236 }
1237 }
1238
1239 struct BatchRecordingWriter {
1240 batch_tx: mpsc::Sender<usize>,
1241 first_release: Option<mpsc::Receiver<()>>,
1242 }
1243
1244 impl Writer for BatchRecordingWriter {
1245 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1246 unreachable!("the async worker must use batch delivery")
1247 }
1248
1249 fn send_frames(&mut self, frames: &[Vec<u8>]) -> io::Result<()> {
1250 self.batch_tx.send(frames.len()).unwrap();
1251 if let Some(release) = self.first_release.take() {
1252 release.recv().unwrap();
1253 }
1254 Ok(())
1255 }
1256 }
1257
1258 impl Writer for FailingWriter {
1259 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
1260 Err(io::Error::new(io::ErrorKind::BrokenPipe, "boom"))
1261 }
1262
1263 fn shutdown(&mut self) {
1264 self.shutdown_called.store(true, Ordering::Relaxed);
1265 }
1266 }
1267
1268 #[test]
1269 fn async_writer_returns_wouldblock_when_queue_is_full() {
1270 let (event_tx, _event_rx) = crate::event::channel();
1271 let (entered_tx, entered_rx) = mpsc::channel();
1272 let (release_tx, release_rx) = mpsc::channel();
1273 let (mut writer, metrics) = wrap_async_writer(
1274 Box::new(BlockingWriter {
1275 entered_tx,
1276 release_rx,
1277 }),
1278 InterfaceId(7),
1279 "test",
1280 event_tx,
1281 1,
1282 );
1283
1284 writer.send_frame(&[1]).unwrap();
1285 entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1286 writer.send_frame(&[2]).unwrap();
1287 assert_eq!(metrics.queued_frames(), 1);
1288 let err = writer.send_frame(&[3]).unwrap_err();
1289 assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
1290
1291 release_tx.send(()).unwrap();
1292 entered_rx
1293 .recv_timeout(Duration::from_secs(1))
1294 .expect("the queued frame must wake the writer without another send trigger");
1295 assert_eq!(metrics.queued_frames(), 0);
1296 release_tx.send(()).unwrap();
1297 }
1298
1299 #[test]
1300 fn async_writer_shares_exact_egress_byte_valve_and_stall_gate() {
1301 let (event_tx, _event_rx) = crate::event::channel();
1302 let (entered_tx, entered_rx) = mpsc::channel();
1303 let (release_tx, release_rx) = mpsc::channel();
1304 let control = EgressControl::new(4);
1305 let (mut writer, metrics) = wrap_async_writer(
1306 Box::new(EgressControlledBlockingWriter {
1307 inner: BlockingWriter {
1308 entered_tx,
1309 release_rx,
1310 },
1311 control: control.clone(),
1312 }),
1313 InterfaceId(8),
1314 "egress-test",
1315 event_tx,
1316 8,
1317 );
1318
1319 writer.send_frame(&[1, 2]).unwrap();
1321 entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1322 assert_eq!(control.reserved_bytes.load(Ordering::Relaxed), 4);
1323 assert_eq!(metrics.tx_buffered(), 4);
1324
1325 writer.send_frame(&[3, 4]).unwrap();
1327 assert_eq!(control.reserved_bytes.load(Ordering::Relaxed), 4);
1328 assert_eq!(control.dropped_frames.load(Ordering::Relaxed), 1);
1329 assert_eq!(control.dropped_bytes.load(Ordering::Relaxed), 4);
1330 assert_eq!(metrics.tx_drops(), 1);
1331 assert_eq!(metrics.tx_dropped_bytes(), 4);
1332
1333 release_tx.send(()).unwrap();
1334 let deadline = Instant::now() + Duration::from_secs(1);
1335 while control.reserved_bytes.load(Ordering::Relaxed) != 0 {
1336 assert!(
1337 Instant::now() < deadline,
1338 "egress reservation did not drain"
1339 );
1340 thread::yield_now();
1341 }
1342
1343 control.set_stalled(true);
1344 assert!(metrics.tx_stalled());
1345 writer.send_frame(&[5]).unwrap();
1346 assert_eq!(control.dropped_frames.load(Ordering::Relaxed), 2);
1347 assert_eq!(control.dropped_bytes.load(Ordering::Relaxed), 7);
1348 assert_eq!(metrics.queued_frames(), 0);
1349 }
1350
1351 #[test]
1352 fn async_writer_reports_interface_down_after_worker_failure() {
1353 let (event_tx, event_rx) = crate::event::channel();
1354 let shutdown_called = Arc::new(AtomicBool::new(false));
1355 let (mut writer, metrics) = wrap_async_writer(
1356 Box::new(FailingWriter {
1357 shutdown_called: Arc::clone(&shutdown_called),
1358 }),
1359 InterfaceId(9),
1360 "fail",
1361 event_tx,
1362 2,
1363 );
1364
1365 writer.send_frame(&[1]).unwrap();
1366 let event = event_rx.recv_timeout(Duration::from_secs(1)).unwrap();
1367 assert!(matches!(event, Event::InterfaceDown(InterfaceId(9))));
1368 assert!(!metrics.worker_alive());
1369 assert!(shutdown_called.load(Ordering::Relaxed));
1370 }
1371
1372 #[test]
1373 fn async_writer_preserves_order_through_fifty_thousand_frame_burst() {
1374 const FRAME_COUNT: usize = 50_000;
1375 let (event_tx, _event_rx) = crate::event::channel();
1376 let (sent_tx, sent_rx) = mpsc::channel();
1377 let (mut writer, metrics) = wrap_async_writer(
1378 Box::new(RecordingWriter { sent_tx }),
1379 InterfaceId(11),
1380 "burst",
1381 event_tx,
1382 FRAME_COUNT,
1383 );
1384
1385 for index in 0..FRAME_COUNT {
1386 let payload = (index as u64).to_be_bytes();
1387 writer.send_frame(&payload).unwrap();
1388 }
1389
1390 for index in 0..FRAME_COUNT {
1391 let received = sent_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1392 assert_eq!(received, (index as u64).to_be_bytes());
1393 }
1394 assert!(metrics.worker_alive());
1395 }
1396
1397 #[test]
1398 fn async_writer_coalesces_frames_waiting_behind_an_active_batch() {
1399 let (event_tx, _event_rx) = crate::event::channel();
1400 let (batch_tx, batch_rx) = mpsc::channel();
1401 let (release_tx, release_rx) = mpsc::channel();
1402 let (mut writer, _) = wrap_async_writer(
1403 Box::new(BatchRecordingWriter {
1404 batch_tx,
1405 first_release: Some(release_rx),
1406 }),
1407 InterfaceId(12),
1408 "batch",
1409 event_tx,
1410 16,
1411 );
1412
1413 writer.send_frame(&[0]).unwrap();
1414 assert_eq!(batch_rx.recv_timeout(Duration::from_secs(1)).unwrap(), 1);
1415 for index in 1..=10 {
1416 writer.send_frame(&[index]).unwrap();
1417 }
1418 release_tx.send(()).unwrap();
1419
1420 assert_eq!(batch_rx.recv_timeout(Duration::from_secs(1)).unwrap(), 10);
1421 }
1422}