Skip to main content

rns_net/interface/
mod.rs

1//! Network interface abstractions.
2
3#[cfg(feature = "iface-auto")]
4pub mod auto;
5#[cfg(feature = "iface-kiss")]
6pub mod ax25_kiss;
7#[cfg(feature = "iface-backbone")]
8pub mod backbone;
9#[cfg(feature = "iface-i2p")]
10pub mod i2p;
11#[cfg(feature = "iface-kiss")]
12pub mod kiss_iface;
13#[cfg(feature = "iface-local")]
14pub mod local;
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/// Bind a socket to a specific network interface using `SO_BINDTODEVICE`.
45///
46/// Requires `CAP_NET_RAW` or root on Linux.
47#[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
71/// Writable end of an interface. Held by the driver.
72///
73/// Each implementation wraps a socket + framing.
74pub 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
241/// Everything the driver tracks per interface.
242pub struct InterfaceEntry {
243    pub id: InterfaceId,
244    pub info: InterfaceInfo,
245    pub writer: Box<dyn Writer>,
246    pub async_writer_metrics: Option<AsyncWriterMetrics>,
247    /// Administrative enable/disable state.
248    pub enabled: bool,
249    pub online: bool,
250    /// True for dynamically spawned interfaces (e.g. TCP server clients).
251    /// These are fully removed on InterfaceDown rather than just marked offline.
252    pub dynamic: bool,
253    /// IFAC state for this interface, if access codes are enabled.
254    pub ifac: Option<IfacState>,
255    /// Traffic statistics.
256    pub stats: InterfaceStats,
257    /// Human-readable interface type string (e.g. "TCPClientInterface").
258    pub interface_type: String,
259    /// Next time a send should be retried after a transient WouldBlock.
260    pub send_retry_at: Option<Instant>,
261    /// Current retry backoff for transient send failures.
262    pub send_retry_backoff: Duration,
263}
264
265/// Result of starting an interface via a factory.
266pub enum StartResult {
267    /// One writer, registered immediately (TcpClient, Udp, Serial, etc.)
268    Simple {
269        id: InterfaceId,
270        info: InterfaceInfo,
271        writer: Box<dyn Writer>,
272        interface_type_name: String,
273    },
274    /// Spawns a listener; dynamic interfaces arrive via Event::InterfaceUp (TcpServer, Auto, I2P, etc.)
275    Listener { control: Option<ListenerControl> },
276    /// Multiple subinterfaces from one config (RNode).
277    Multi(Vec<SubInterface>),
278}
279
280/// A single subinterface returned from a multi-interface factory.
281pub struct SubInterface {
282    pub id: InterfaceId,
283    pub info: InterfaceInfo,
284    pub writer: Box<dyn Writer>,
285    pub interface_type_name: String,
286}
287
288/// Context passed to [`InterfaceFactory::start()`].
289pub 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
301/// Opaque interface config data. Each factory downcasts to its concrete type.
302pub trait InterfaceConfigData: Send + Any {
303    fn as_any(&self) -> &dyn Any;
304    fn into_any(self: Box<Self>) -> Box<dyn Any>;
305}
306
307/// ConfigObj section passed to factories that need nested `[[[sections]]]`.
308pub 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
355/// Factory that can parse config and start an interface type.
356pub trait InterfaceFactory: Send + Sync {
357    /// Config-file type name, e.g. "TCPClientInterface".
358    fn type_name(&self) -> &str;
359
360    /// Default IFAC size (bytes). 8 for serial/kiss/rnode, 16 for others.
361    fn default_ifac_size(&self) -> usize {
362        16
363    }
364
365    /// Parse from key-value params (config file or external).
366    fn parse_config(
367        &self,
368        name: &str,
369        id: InterfaceId,
370        params: &HashMap<String, String>,
371    ) -> Result<Box<dyn InterfaceConfigData>, String>;
372
373    /// Parse a complete ConfigObj interface section. Flat factories inherit
374    /// the compatibility default; multi-interface factories can inspect children.
375    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    /// Start the interface from parsed config.
385    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}