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-backbone")]
6pub mod backbone;
7#[cfg(feature = "iface-i2p")]
8pub mod i2p;
9#[cfg(feature = "iface-kiss")]
10pub mod kiss_iface;
11#[cfg(feature = "iface-local")]
12pub mod local;
13#[cfg(feature = "iface-pipe")]
14pub mod pipe;
15pub mod registry;
16#[cfg(feature = "iface-rnode")]
17pub mod rnode;
18#[cfg(feature = "iface-serial")]
19pub mod serial_iface;
20#[cfg(feature = "iface-tcp")]
21pub mod tcp;
22#[cfg(feature = "iface-tcp")]
23pub mod tcp_server;
24#[cfg(feature = "iface-udp")]
25pub mod udp;
26
27use std::any::Any;
28use std::collections::HashMap;
29use std::io;
30use std::sync::atomic::AtomicU64;
31use std::sync::Arc;
32
33use crate::event::EventSender;
34use crate::ifac::IfacState;
35use rns_core::transport::types::{InterfaceId, InterfaceInfo};
36
37/// Bind a socket to a specific network interface using `SO_BINDTODEVICE`.
38///
39/// Requires `CAP_NET_RAW` or root on Linux.
40#[cfg(target_os = "linux")]
41pub fn bind_to_device(fd: std::os::unix::io::RawFd, device: &str) -> io::Result<()> {
42    let dev_bytes = device.as_bytes();
43    if dev_bytes.len() >= libc::IFNAMSIZ {
44        return Err(io::Error::new(
45            io::ErrorKind::InvalidInput,
46            format!("device name too long: {}", device),
47        ));
48    }
49    let ret = unsafe {
50        libc::setsockopt(
51            fd,
52            libc::SOL_SOCKET,
53            libc::SO_BINDTODEVICE,
54            dev_bytes.as_ptr() as *const libc::c_void,
55            dev_bytes.len() as libc::socklen_t,
56        )
57    };
58    if ret != 0 {
59        return Err(io::Error::last_os_error());
60    }
61    Ok(())
62}
63
64/// Writable end of an interface. Held by the driver.
65///
66/// Each implementation wraps a socket + framing.
67pub trait Writer: Send {
68    fn send_frame(&mut self, data: &[u8]) -> io::Result<()>;
69}
70
71pub use crate::common::interface_stats::{InterfaceStats, ANNOUNCE_SAMPLE_MAX};
72
73use crate::common::management::InterfaceStatusView;
74
75/// Everything the driver tracks per interface.
76pub struct InterfaceEntry {
77    pub id: InterfaceId,
78    pub info: InterfaceInfo,
79    pub writer: Box<dyn Writer>,
80    pub online: bool,
81    /// True for dynamically spawned interfaces (e.g. TCP server clients).
82    /// These are fully removed on InterfaceDown rather than just marked offline.
83    pub dynamic: bool,
84    /// IFAC state for this interface, if access codes are enabled.
85    pub ifac: Option<IfacState>,
86    /// Traffic statistics.
87    pub stats: InterfaceStats,
88    /// Human-readable interface type string (e.g. "TCPClientInterface").
89    pub interface_type: String,
90}
91
92/// Result of starting an interface via a factory.
93pub enum StartResult {
94    /// One writer, registered immediately (TcpClient, Udp, Serial, etc.)
95    Simple {
96        id: InterfaceId,
97        info: InterfaceInfo,
98        writer: Box<dyn Writer>,
99        interface_type_name: String,
100    },
101    /// Spawns a listener; dynamic interfaces arrive via Event::InterfaceUp (TcpServer, Auto, I2P, etc.)
102    Listener,
103    /// Multiple subinterfaces from one config (RNode).
104    Multi(Vec<SubInterface>),
105}
106
107/// A single subinterface returned from a multi-interface factory.
108pub struct SubInterface {
109    pub id: InterfaceId,
110    pub info: InterfaceInfo,
111    pub writer: Box<dyn Writer>,
112    pub interface_type_name: String,
113}
114
115/// Context passed to [`InterfaceFactory::start()`].
116pub struct StartContext {
117    pub tx: EventSender,
118    pub next_dynamic_id: Arc<AtomicU64>,
119    pub mode: u8,
120}
121
122/// Opaque interface config data. Each factory downcasts to its concrete type.
123pub trait InterfaceConfigData: Send + Any {
124    fn into_any(self: Box<Self>) -> Box<dyn Any>;
125}
126
127impl<T: Send + 'static> InterfaceConfigData for T {
128    fn into_any(self: Box<Self>) -> Box<dyn Any> {
129        self
130    }
131}
132
133/// Factory that can parse config and start an interface type.
134pub trait InterfaceFactory: Send + Sync {
135    /// Config-file type name, e.g. "TCPClientInterface".
136    fn type_name(&self) -> &str;
137
138    /// Default IFAC size (bytes). 8 for serial/kiss/rnode, 16 for others.
139    fn default_ifac_size(&self) -> usize {
140        16
141    }
142
143    /// Parse from key-value params (config file or external).
144    fn parse_config(
145        &self,
146        name: &str,
147        id: InterfaceId,
148        params: &HashMap<String, String>,
149    ) -> Result<Box<dyn InterfaceConfigData>, String>;
150
151    /// Start the interface from parsed config.
152    fn start(
153        &self,
154        config: Box<dyn InterfaceConfigData>,
155        ctx: StartContext,
156    ) -> io::Result<StartResult>;
157}
158
159impl InterfaceStatusView for InterfaceEntry {
160    fn id(&self) -> InterfaceId {
161        self.id
162    }
163    fn info(&self) -> &InterfaceInfo {
164        &self.info
165    }
166    fn online(&self) -> bool {
167        self.online
168    }
169    fn stats(&self) -> &InterfaceStats {
170        &self.stats
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use rns_core::constants;
178
179    struct MockWriter {
180        sent: Vec<Vec<u8>>,
181    }
182
183    impl MockWriter {
184        fn new() -> Self {
185            MockWriter { sent: Vec::new() }
186        }
187    }
188
189    impl Writer for MockWriter {
190        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
191            self.sent.push(data.to_vec());
192            Ok(())
193        }
194    }
195
196    #[test]
197    fn interface_entry_construction() {
198        let entry = InterfaceEntry {
199            id: InterfaceId(1),
200            info: InterfaceInfo {
201                id: InterfaceId(1),
202                name: String::new(),
203                mode: constants::MODE_FULL,
204                out_capable: true,
205                in_capable: true,
206                bitrate: None,
207                announce_rate_target: None,
208                announce_rate_grace: 0,
209                announce_rate_penalty: 0.0,
210                announce_cap: constants::ANNOUNCE_CAP,
211                is_local_client: false,
212                wants_tunnel: false,
213                tunnel_id: None,
214                mtu: constants::MTU as u32,
215                ia_freq: 0.0,
216                started: 0.0,
217                ingress_control: false,
218            },
219            writer: Box::new(MockWriter::new()),
220            online: false,
221            dynamic: false,
222            ifac: None,
223            stats: InterfaceStats::default(),
224            interface_type: String::new(),
225        };
226        assert_eq!(entry.id, InterfaceId(1));
227        assert!(!entry.online);
228        assert!(!entry.dynamic);
229    }
230
231    #[test]
232    fn mock_writer_captures_bytes() {
233        let mut writer = MockWriter::new();
234        writer.send_frame(b"hello").unwrap();
235        writer.send_frame(b"world").unwrap();
236        assert_eq!(writer.sent.len(), 2);
237        assert_eq!(writer.sent[0], b"hello");
238        assert_eq!(writer.sent[1], b"world");
239    }
240
241    #[test]
242    fn writer_send_frame_produces_output() {
243        let mut writer = MockWriter::new();
244        let data = vec![0x01, 0x02, 0x03];
245        writer.send_frame(&data).unwrap();
246        assert_eq!(writer.sent[0], data);
247    }
248}