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    /// Administrative enable/disable state.
81    pub enabled: bool,
82    pub online: bool,
83    /// True for dynamically spawned interfaces (e.g. TCP server clients).
84    /// These are fully removed on InterfaceDown rather than just marked offline.
85    pub dynamic: bool,
86    /// IFAC state for this interface, if access codes are enabled.
87    pub ifac: Option<IfacState>,
88    /// Traffic statistics.
89    pub stats: InterfaceStats,
90    /// Human-readable interface type string (e.g. "TCPClientInterface").
91    pub interface_type: String,
92}
93
94/// Result of starting an interface via a factory.
95pub enum StartResult {
96    /// One writer, registered immediately (TcpClient, Udp, Serial, etc.)
97    Simple {
98        id: InterfaceId,
99        info: InterfaceInfo,
100        writer: Box<dyn Writer>,
101        interface_type_name: String,
102    },
103    /// Spawns a listener; dynamic interfaces arrive via Event::InterfaceUp (TcpServer, Auto, I2P, etc.)
104    Listener,
105    /// Multiple subinterfaces from one config (RNode).
106    Multi(Vec<SubInterface>),
107}
108
109/// A single subinterface returned from a multi-interface factory.
110pub struct SubInterface {
111    pub id: InterfaceId,
112    pub info: InterfaceInfo,
113    pub writer: Box<dyn Writer>,
114    pub interface_type_name: String,
115}
116
117/// Context passed to [`InterfaceFactory::start()`].
118pub struct StartContext {
119    pub tx: EventSender,
120    pub next_dynamic_id: Arc<AtomicU64>,
121    pub mode: u8,
122}
123
124/// Opaque interface config data. Each factory downcasts to its concrete type.
125pub trait InterfaceConfigData: Send + Any {
126    fn as_any(&self) -> &dyn Any;
127    fn into_any(self: Box<Self>) -> Box<dyn Any>;
128}
129
130impl<T: Send + 'static> InterfaceConfigData for T {
131    fn as_any(&self) -> &dyn Any {
132        self
133    }
134
135    fn into_any(self: Box<Self>) -> Box<dyn Any> {
136        self
137    }
138}
139
140/// Factory that can parse config and start an interface type.
141pub trait InterfaceFactory: Send + Sync {
142    /// Config-file type name, e.g. "TCPClientInterface".
143    fn type_name(&self) -> &str;
144
145    /// Default IFAC size (bytes). 8 for serial/kiss/rnode, 16 for others.
146    fn default_ifac_size(&self) -> usize {
147        16
148    }
149
150    /// Parse from key-value params (config file or external).
151    fn parse_config(
152        &self,
153        name: &str,
154        id: InterfaceId,
155        params: &HashMap<String, String>,
156    ) -> Result<Box<dyn InterfaceConfigData>, String>;
157
158    /// Start the interface from parsed config.
159    fn start(
160        &self,
161        config: Box<dyn InterfaceConfigData>,
162        ctx: StartContext,
163    ) -> io::Result<StartResult>;
164}
165
166impl InterfaceStatusView for InterfaceEntry {
167    fn id(&self) -> InterfaceId {
168        self.id
169    }
170    fn info(&self) -> &InterfaceInfo {
171        &self.info
172    }
173    fn online(&self) -> bool {
174        self.online
175    }
176    fn stats(&self) -> &InterfaceStats {
177        &self.stats
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use rns_core::constants;
185
186    struct MockWriter {
187        sent: Vec<Vec<u8>>,
188    }
189
190    impl MockWriter {
191        fn new() -> Self {
192            MockWriter { sent: Vec::new() }
193        }
194    }
195
196    impl Writer for MockWriter {
197        fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
198            self.sent.push(data.to_vec());
199            Ok(())
200        }
201    }
202
203    #[test]
204    fn interface_entry_construction() {
205        let entry = InterfaceEntry {
206            id: InterfaceId(1),
207            info: InterfaceInfo {
208                id: InterfaceId(1),
209                name: String::new(),
210                mode: constants::MODE_FULL,
211                out_capable: true,
212                in_capable: true,
213                bitrate: None,
214                announce_rate_target: None,
215                announce_rate_grace: 0,
216                announce_rate_penalty: 0.0,
217                announce_cap: constants::ANNOUNCE_CAP,
218                is_local_client: false,
219                wants_tunnel: false,
220                tunnel_id: None,
221                mtu: constants::MTU as u32,
222                ia_freq: 0.0,
223                started: 0.0,
224                ingress_control: false,
225            },
226            writer: Box::new(MockWriter::new()),
227            enabled: true,
228            online: false,
229            dynamic: false,
230            ifac: None,
231            stats: InterfaceStats::default(),
232            interface_type: String::new(),
233        };
234        assert_eq!(entry.id, InterfaceId(1));
235        assert!(!entry.online);
236        assert!(!entry.dynamic);
237    }
238
239    #[test]
240    fn mock_writer_captures_bytes() {
241        let mut writer = MockWriter::new();
242        writer.send_frame(b"hello").unwrap();
243        writer.send_frame(b"world").unwrap();
244        assert_eq!(writer.sent.len(), 2);
245        assert_eq!(writer.sent[0], b"hello");
246        assert_eq!(writer.sent[1], b"world");
247    }
248
249    #[test]
250    fn writer_send_frame_produces_output() {
251        let mut writer = MockWriter::new();
252        let data = vec![0x01, 0x02, 0x03];
253        writer.send_frame(&data).unwrap();
254        assert_eq!(writer.sent[0], data);
255    }
256}