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;
26#[cfg(feature = "iface-udp")]
27pub mod udp;
28#[cfg(all(feature = "iface-weave", target_os = "linux"))]
29pub mod weave;
30
31use std::any::Any;
32use std::collections::HashMap;
33use std::io;
34use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
35use std::sync::mpsc::{sync_channel, SyncSender, TrySendError};
36use std::sync::{Arc, Mutex, MutexGuard};
37use std::thread;
38use std::time::{Duration, Instant};
39
40use crate::event::EventSender;
41use crate::ifac::IfacState;
42use rns_core::transport::types::{InterfaceId, InterfaceInfo};
43
44#[cfg(target_os = "linux")]
48pub fn bind_to_device(fd: std::os::unix::io::RawFd, device: &str) -> io::Result<()> {
49 let dev_bytes = device.as_bytes();
50 if dev_bytes.len() >= libc::IFNAMSIZ {
51 return Err(io::Error::new(
52 io::ErrorKind::InvalidInput,
53 format!("device name too long: {}", device),
54 ));
55 }
56 let ret = unsafe {
57 libc::setsockopt(
58 fd,
59 libc::SOL_SOCKET,
60 libc::SO_BINDTODEVICE,
61 dev_bytes.as_ptr() as *const libc::c_void,
62 dev_bytes.len() as libc::socklen_t,
63 )
64 };
65 if ret != 0 {
66 return Err(io::Error::last_os_error());
67 }
68 Ok(())
69}
70
71pub trait Writer: Send {
75 fn send_frame(&mut self, data: &[u8]) -> io::Result<()>;
76
77 fn shutdown(&mut self) {}
78}
79
80pub const DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY: usize = 256;
81
82pub(crate) fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>, label: &str) -> MutexGuard<'a, T> {
83 match mutex.lock() {
84 Ok(guard) => guard,
85 Err(poisoned) => {
86 log::warn!("recovering poisoned mutex: {}", label);
87 poisoned.into_inner()
88 }
89 }
90}
91
92#[derive(Clone, Default)]
93pub struct ListenerControl {
94 stop: Arc<AtomicBool>,
95}
96
97impl ListenerControl {
98 pub fn new() -> Self {
99 Self {
100 stop: Arc::new(AtomicBool::new(false)),
101 }
102 }
103
104 pub fn request_stop(&self) {
105 self.stop.store(true, Ordering::Relaxed);
106 }
107
108 pub fn should_stop(&self) -> bool {
109 self.stop.load(Ordering::Relaxed)
110 }
111}
112
113#[derive(Clone, Default)]
114pub struct AsyncWriterMetrics {
115 queued_frames: Arc<AtomicUsize>,
116 worker_alive: Arc<AtomicBool>,
117}
118
119impl AsyncWriterMetrics {
120 pub fn queued_frames(&self) -> usize {
121 self.queued_frames.load(Ordering::Relaxed)
122 }
123
124 pub fn worker_alive(&self) -> bool {
125 self.worker_alive.load(Ordering::Relaxed)
126 }
127}
128
129struct AsyncWriter {
130 tx: SyncSender<Vec<u8>>,
131 metrics: AsyncWriterMetrics,
132}
133
134impl Writer for AsyncWriter {
135 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
136 if !self.metrics.worker_alive() {
137 return Err(io::Error::new(
138 io::ErrorKind::BrokenPipe,
139 "interface writer worker is offline",
140 ));
141 }
142
143 match self.tx.try_send(data.to_vec()) {
144 Ok(()) => {
145 self.metrics.queued_frames.fetch_add(1, Ordering::Relaxed);
146 Ok(())
147 }
148 Err(TrySendError::Full(_)) => Err(io::Error::new(
149 io::ErrorKind::WouldBlock,
150 "interface writer queue is full",
151 )),
152 Err(TrySendError::Disconnected(_)) => {
153 self.metrics.worker_alive.store(false, Ordering::Relaxed);
154 Err(io::Error::new(
155 io::ErrorKind::BrokenPipe,
156 "interface writer worker disconnected",
157 ))
158 }
159 }
160 }
161}
162
163pub fn wrap_async_writer(
164 writer: Box<dyn Writer>,
165 interface_id: InterfaceId,
166 interface_name: &str,
167 event_tx: EventSender,
168 queue_capacity: usize,
169) -> (Box<dyn Writer>, AsyncWriterMetrics) {
170 let (tx, rx) = sync_channel::<Vec<u8>>(queue_capacity.max(1));
171 let metrics = AsyncWriterMetrics {
172 queued_frames: Arc::new(AtomicUsize::new(0)),
173 worker_alive: Arc::new(AtomicBool::new(true)),
174 };
175 let metrics_thread = metrics.clone();
176 let name = interface_name.to_string();
177
178 let spawn_result = thread::Builder::new()
179 .name(format!("iface-writer-{}", interface_id.0))
180 .spawn(move || async_writer_loop(writer, rx, interface_id, name, event_tx, metrics_thread));
181
182 if let Err(err) = spawn_result {
183 metrics.worker_alive.store(false, Ordering::Relaxed);
184 log::error!(
185 "[{}:{}] failed to spawn async writer thread: {}",
186 interface_name,
187 interface_id.0,
188 err
189 );
190 return (Box::new(DirectWriterFallback), metrics);
191 }
192
193 (
194 Box::new(AsyncWriter {
195 tx,
196 metrics: metrics.clone(),
197 }),
198 metrics,
199 )
200}
201
202struct DirectWriterFallback;
203
204impl Writer for DirectWriterFallback {
205 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
206 Err(io::Error::other("interface writer worker unavailable"))
207 }
208}
209
210fn async_writer_loop(
211 mut writer: Box<dyn Writer>,
212 rx: std::sync::mpsc::Receiver<Vec<u8>>,
213 interface_id: InterfaceId,
214 interface_name: String,
215 event_tx: EventSender,
216 metrics: AsyncWriterMetrics,
217) {
218 while let Ok(frame) = rx.recv() {
219 metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
220 if let Err(err) = writer.send_frame(&frame) {
221 metrics.worker_alive.store(false, Ordering::Relaxed);
222 log::warn!(
223 "[{}:{}] async writer exiting after send failure: {}",
224 interface_name,
225 interface_id.0,
226 err
227 );
228 writer.shutdown();
229 let _ = event_tx.send(crate::event::Event::InterfaceDown(interface_id));
230 return;
231 }
232 }
233
234 metrics.worker_alive.store(false, Ordering::Relaxed);
235}
236
237pub use crate::common::interface_stats::{InterfaceStats, ANNOUNCE_SAMPLE_MAX};
238
239use crate::common::management::InterfaceStatusView;
240
241pub struct InterfaceEntry {
243 pub id: InterfaceId,
244 pub info: InterfaceInfo,
245 pub writer: Box<dyn Writer>,
246 pub async_writer_metrics: Option<AsyncWriterMetrics>,
247 pub enabled: bool,
249 pub online: bool,
250 pub dynamic: bool,
253 pub ifac: Option<IfacState>,
255 pub stats: InterfaceStats,
257 pub interface_type: String,
259 pub send_retry_at: Option<Instant>,
261 pub send_retry_backoff: Duration,
263}
264
265pub enum StartResult {
267 Simple {
269 id: InterfaceId,
270 info: InterfaceInfo,
271 writer: Box<dyn Writer>,
272 interface_type_name: String,
273 },
274 Listener { control: Option<ListenerControl> },
276 Multi(Vec<SubInterface>),
278}
279
280pub struct SubInterface {
282 pub id: InterfaceId,
283 pub info: InterfaceInfo,
284 pub writer: Box<dyn Writer>,
285 pub interface_type_name: String,
286}
287
288pub struct StartContext {
290 pub tx: EventSender,
291 pub next_dynamic_id: Arc<AtomicU64>,
292 pub mode: u8,
293 pub gravity: i64,
294 pub recursive_prs: bool,
295 pub announces_from_internal: bool,
296 pub announces_to_internal: Option<bool>,
297 pub ingress_control: rns_core::transport::types::IngressControlConfig,
298 pub ifac: Option<IfacState>,
299}
300
301pub trait InterfaceConfigData: Send + Any {
303 fn as_any(&self) -> &dyn Any;
304 fn into_any(self: Box<Self>) -> Box<dyn Any>;
305}
306
307pub struct ConfigSection<'a> {
309 pub params: &'a HashMap<String, String>,
310 pub children: &'a [crate::config::ParsedSubinterface],
311}
312
313#[derive(Debug, Clone)]
314pub struct DynamicInterfaceTemplate {
315 pub parent_id: InterfaceId,
316 pub interface_type: String,
317 pub ifac: Option<IfacState>,
318 pub mode: u8,
319 pub gravity: i64,
320 pub recursive_prs: bool,
321 pub announces_from_internal: bool,
322 pub announces_to_internal: Option<bool>,
323}
324
325impl DynamicInterfaceTemplate {
326 pub fn registration(
327 &self,
328 mut info: InterfaceInfo,
329 ) -> crate::event::DynamicInterfaceRegistration {
330 info.mode = self.mode;
331 info.gravity = self.gravity;
332 info.recursive_prs = self.recursive_prs;
333 info.announces_from_internal = self.announces_from_internal;
334 info.announces_to_internal = self.announces_to_internal;
335 crate::event::DynamicInterfaceRegistration {
336 info,
337 interface_type: self.interface_type.clone(),
338 parent_id: self.parent_id,
339 telemetry: crate::event::InterfaceTelemetry::default(),
340 ifac: self.ifac.clone(),
341 }
342 }
343}
344
345impl<T: Send + 'static> InterfaceConfigData for T {
346 fn as_any(&self) -> &dyn Any {
347 self
348 }
349
350 fn into_any(self: Box<Self>) -> Box<dyn Any> {
351 self
352 }
353}
354
355pub trait InterfaceFactory: Send + Sync {
357 fn type_name(&self) -> &str;
359
360 fn default_ifac_size(&self) -> usize {
362 16
363 }
364
365 fn parse_config(
367 &self,
368 name: &str,
369 id: InterfaceId,
370 params: &HashMap<String, String>,
371 ) -> Result<Box<dyn InterfaceConfigData>, String>;
372
373 fn parse_config_section(
376 &self,
377 name: &str,
378 id: InterfaceId,
379 section: ConfigSection<'_>,
380 ) -> Result<Box<dyn InterfaceConfigData>, String> {
381 self.parse_config(name, id, section.params)
382 }
383
384 fn start(
386 &self,
387 config: Box<dyn InterfaceConfigData>,
388 ctx: StartContext,
389 ) -> io::Result<StartResult>;
390}
391
392impl InterfaceStatusView for InterfaceEntry {
393 fn id(&self) -> InterfaceId {
394 self.id
395 }
396 fn info(&self) -> &InterfaceInfo {
397 &self.info
398 }
399 fn online(&self) -> bool {
400 self.online
401 }
402 fn stats(&self) -> &InterfaceStats {
403 &self.stats
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use crate::event::Event;
411 use rns_core::constants;
412 use std::sync::mpsc;
413
414 struct MockWriter {
415 sent: Vec<Vec<u8>>,
416 }
417
418 impl MockWriter {
419 fn new() -> Self {
420 MockWriter { sent: Vec::new() }
421 }
422 }
423
424 impl Writer for MockWriter {
425 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
426 self.sent.push(data.to_vec());
427 Ok(())
428 }
429 }
430
431 #[test]
432 fn interface_entry_construction() {
433 let entry = InterfaceEntry {
434 id: InterfaceId(1),
435 info: InterfaceInfo {
436 id: InterfaceId(1),
437 name: String::new(),
438 mode: constants::MODE_FULL,
439 gravity: 0,
440 recursive_prs: false,
441 announces_from_internal: true,
442 announces_to_internal: None,
443 out_capable: true,
444 in_capable: true,
445 bitrate: None,
446 airtime_profile: None,
447 announce_rate_target: None,
448 announce_rate_grace: 0,
449 announce_rate_penalty: 0.0,
450 announce_cap: constants::ANNOUNCE_CAP,
451 is_local_client: false,
452 wants_tunnel: false,
453 tunnel_id: None,
454 mtu: constants::MTU as u32,
455 ia_freq: 0.0,
456 ip_freq: 0.0,
457 op_freq: 0.0,
458 op_samples: 0,
459 started: 0.0,
460 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
461 },
462 writer: Box::new(MockWriter::new()),
463 async_writer_metrics: None,
464 enabled: true,
465 online: false,
466 dynamic: false,
467 ifac: None,
468 stats: InterfaceStats::default(),
469 interface_type: String::new(),
470 send_retry_at: None,
471 send_retry_backoff: Duration::ZERO,
472 };
473 assert_eq!(entry.id, InterfaceId(1));
474 assert!(!entry.online);
475 assert!(!entry.dynamic);
476 }
477
478 #[test]
479 fn dynamic_registration_inherits_announces_to_internal() {
480 let template = DynamicInterfaceTemplate {
481 parent_id: InterfaceId(7),
482 interface_type: "dynamic-test".into(),
483 ifac: None,
484 mode: constants::MODE_GATEWAY,
485 gravity: -3,
486 recursive_prs: true,
487 announces_from_internal: false,
488 announces_to_internal: Some(true),
489 };
490 let info = InterfaceInfo {
491 id: InterfaceId(8),
492 name: "child".into(),
493 mode: constants::MODE_FULL,
494 gravity: 0,
495 recursive_prs: false,
496 announces_from_internal: true,
497 announces_to_internal: None,
498 out_capable: true,
499 in_capable: true,
500 bitrate: None,
501 airtime_profile: None,
502 announce_rate_target: None,
503 announce_rate_grace: 0,
504 announce_rate_penalty: 0.0,
505 announce_cap: constants::ANNOUNCE_CAP,
506 is_local_client: false,
507 wants_tunnel: false,
508 tunnel_id: None,
509 mtu: constants::MTU as u32,
510 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
511 ia_freq: 0.0,
512 ip_freq: 0.0,
513 op_freq: 0.0,
514 op_samples: 0,
515 started: 0.0,
516 };
517
518 let registration = template.registration(info);
519
520 assert_eq!(registration.info.mode, constants::MODE_GATEWAY);
521 assert_eq!(registration.info.gravity, -3);
522 assert!(registration.info.recursive_prs);
523 assert!(!registration.info.announces_from_internal);
524 assert_eq!(registration.info.announces_to_internal, Some(true));
525 }
526
527 #[test]
528 fn mock_writer_captures_bytes() {
529 let mut writer = MockWriter::new();
530 writer.send_frame(b"hello").unwrap();
531 writer.send_frame(b"world").unwrap();
532 assert_eq!(writer.sent.len(), 2);
533 assert_eq!(writer.sent[0], b"hello");
534 assert_eq!(writer.sent[1], b"world");
535 }
536
537 #[test]
538 fn writer_send_frame_produces_output() {
539 let mut writer = MockWriter::new();
540 let data = vec![0x01, 0x02, 0x03];
541 writer.send_frame(&data).unwrap();
542 assert_eq!(writer.sent[0], data);
543 }
544
545 struct BlockingWriter {
546 entered_tx: mpsc::Sender<()>,
547 release_rx: mpsc::Receiver<()>,
548 }
549
550 impl Writer for BlockingWriter {
551 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
552 let _ = self.entered_tx.send(());
553 let _ = self.release_rx.recv();
554 Ok(())
555 }
556 }
557
558 struct FailingWriter {
559 shutdown_called: Arc<AtomicBool>,
560 }
561
562 impl Writer for FailingWriter {
563 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
564 Err(io::Error::new(io::ErrorKind::BrokenPipe, "boom"))
565 }
566
567 fn shutdown(&mut self) {
568 self.shutdown_called.store(true, Ordering::Relaxed);
569 }
570 }
571
572 #[test]
573 fn async_writer_returns_wouldblock_when_queue_is_full() {
574 let (event_tx, _event_rx) = crate::event::channel();
575 let (entered_tx, entered_rx) = mpsc::channel();
576 let (release_tx, release_rx) = mpsc::channel();
577 let (mut writer, metrics) = wrap_async_writer(
578 Box::new(BlockingWriter {
579 entered_tx,
580 release_rx,
581 }),
582 InterfaceId(7),
583 "test",
584 event_tx,
585 1,
586 );
587
588 writer.send_frame(&[1]).unwrap();
589 entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
590 writer.send_frame(&[2]).unwrap();
591 assert_eq!(metrics.queued_frames(), 1);
592 let err = writer.send_frame(&[3]).unwrap_err();
593 assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
594
595 let _ = release_tx.send(());
596 }
597
598 #[test]
599 fn async_writer_reports_interface_down_after_worker_failure() {
600 let (event_tx, event_rx) = crate::event::channel();
601 let shutdown_called = Arc::new(AtomicBool::new(false));
602 let (mut writer, metrics) = wrap_async_writer(
603 Box::new(FailingWriter {
604 shutdown_called: Arc::clone(&shutdown_called),
605 }),
606 InterfaceId(9),
607 "fail",
608 event_tx,
609 2,
610 );
611
612 writer.send_frame(&[1]).unwrap();
613 let event = event_rx.recv_timeout(Duration::from_secs(1)).unwrap();
614 assert!(matches!(event, Event::InterfaceDown(InterfaceId(9))));
615 assert!(!metrics.worker_alive());
616 assert!(shutdown_called.load(Ordering::Relaxed));
617 }
618}