1#[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::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
31use std::sync::mpsc::{sync_channel, SyncSender, TrySendError};
32use std::sync::{Arc, Mutex, MutexGuard};
33use std::thread;
34use std::time::{Duration, Instant};
35
36use crate::event::EventSender;
37use crate::ifac::IfacState;
38use rns_core::transport::types::{InterfaceId, InterfaceInfo};
39
40#[cfg(target_os = "linux")]
44pub fn bind_to_device(fd: std::os::unix::io::RawFd, device: &str) -> io::Result<()> {
45 let dev_bytes = device.as_bytes();
46 if dev_bytes.len() >= libc::IFNAMSIZ {
47 return Err(io::Error::new(
48 io::ErrorKind::InvalidInput,
49 format!("device name too long: {}", device),
50 ));
51 }
52 let ret = unsafe {
53 libc::setsockopt(
54 fd,
55 libc::SOL_SOCKET,
56 libc::SO_BINDTODEVICE,
57 dev_bytes.as_ptr() as *const libc::c_void,
58 dev_bytes.len() as libc::socklen_t,
59 )
60 };
61 if ret != 0 {
62 return Err(io::Error::last_os_error());
63 }
64 Ok(())
65}
66
67pub trait Writer: Send {
71 fn send_frame(&mut self, data: &[u8]) -> io::Result<()>;
72}
73
74pub const DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY: usize = 256;
75
76pub(crate) fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>, label: &str) -> MutexGuard<'a, T> {
77 match mutex.lock() {
78 Ok(guard) => guard,
79 Err(poisoned) => {
80 log::warn!("recovering poisoned mutex: {}", label);
81 poisoned.into_inner()
82 }
83 }
84}
85
86#[derive(Clone, Default)]
87pub struct ListenerControl {
88 stop: Arc<AtomicBool>,
89}
90
91impl ListenerControl {
92 pub fn new() -> Self {
93 Self {
94 stop: Arc::new(AtomicBool::new(false)),
95 }
96 }
97
98 pub fn request_stop(&self) {
99 self.stop.store(true, Ordering::Relaxed);
100 }
101
102 pub fn should_stop(&self) -> bool {
103 self.stop.load(Ordering::Relaxed)
104 }
105}
106
107#[derive(Clone, Default)]
108pub struct AsyncWriterMetrics {
109 queued_frames: Arc<AtomicUsize>,
110 worker_alive: Arc<AtomicBool>,
111}
112
113impl AsyncWriterMetrics {
114 pub fn queued_frames(&self) -> usize {
115 self.queued_frames.load(Ordering::Relaxed)
116 }
117
118 pub fn worker_alive(&self) -> bool {
119 self.worker_alive.load(Ordering::Relaxed)
120 }
121}
122
123struct AsyncWriter {
124 tx: SyncSender<Vec<u8>>,
125 metrics: AsyncWriterMetrics,
126}
127
128impl Writer for AsyncWriter {
129 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
130 if !self.metrics.worker_alive() {
131 return Err(io::Error::new(
132 io::ErrorKind::BrokenPipe,
133 "interface writer worker is offline",
134 ));
135 }
136
137 match self.tx.try_send(data.to_vec()) {
138 Ok(()) => {
139 self.metrics.queued_frames.fetch_add(1, Ordering::Relaxed);
140 Ok(())
141 }
142 Err(TrySendError::Full(_)) => Err(io::Error::new(
143 io::ErrorKind::WouldBlock,
144 "interface writer queue is full",
145 )),
146 Err(TrySendError::Disconnected(_)) => {
147 self.metrics.worker_alive.store(false, Ordering::Relaxed);
148 Err(io::Error::new(
149 io::ErrorKind::BrokenPipe,
150 "interface writer worker disconnected",
151 ))
152 }
153 }
154 }
155}
156
157pub fn wrap_async_writer(
158 writer: Box<dyn Writer>,
159 interface_id: InterfaceId,
160 interface_name: &str,
161 event_tx: EventSender,
162 queue_capacity: usize,
163) -> (Box<dyn Writer>, AsyncWriterMetrics) {
164 let (tx, rx) = sync_channel::<Vec<u8>>(queue_capacity.max(1));
165 let metrics = AsyncWriterMetrics {
166 queued_frames: Arc::new(AtomicUsize::new(0)),
167 worker_alive: Arc::new(AtomicBool::new(true)),
168 };
169 let metrics_thread = metrics.clone();
170 let name = interface_name.to_string();
171
172 let spawn_result = thread::Builder::new()
173 .name(format!("iface-writer-{}", interface_id.0))
174 .spawn(move || async_writer_loop(writer, rx, interface_id, name, event_tx, metrics_thread));
175
176 if let Err(err) = spawn_result {
177 metrics.worker_alive.store(false, Ordering::Relaxed);
178 log::error!(
179 "[{}:{}] failed to spawn async writer thread: {}",
180 interface_name,
181 interface_id.0,
182 err
183 );
184 return (Box::new(DirectWriterFallback), metrics);
185 }
186
187 (
188 Box::new(AsyncWriter {
189 tx,
190 metrics: metrics.clone(),
191 }),
192 metrics,
193 )
194}
195
196struct DirectWriterFallback;
197
198impl Writer for DirectWriterFallback {
199 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
200 Err(io::Error::other("interface writer worker unavailable"))
201 }
202}
203
204fn async_writer_loop(
205 mut writer: Box<dyn Writer>,
206 rx: std::sync::mpsc::Receiver<Vec<u8>>,
207 interface_id: InterfaceId,
208 interface_name: String,
209 event_tx: EventSender,
210 metrics: AsyncWriterMetrics,
211) {
212 while let Ok(frame) = rx.recv() {
213 metrics.queued_frames.fetch_sub(1, Ordering::Relaxed);
214 if let Err(err) = writer.send_frame(&frame) {
215 metrics.worker_alive.store(false, Ordering::Relaxed);
216 log::warn!(
217 "[{}:{}] async writer exiting after send failure: {}",
218 interface_name,
219 interface_id.0,
220 err
221 );
222 let _ = event_tx.send(crate::event::Event::InterfaceDown(interface_id));
223 return;
224 }
225 }
226
227 metrics.worker_alive.store(false, Ordering::Relaxed);
228}
229
230pub use crate::common::interface_stats::{InterfaceStats, ANNOUNCE_SAMPLE_MAX};
231
232use crate::common::management::InterfaceStatusView;
233
234pub struct InterfaceEntry {
236 pub id: InterfaceId,
237 pub info: InterfaceInfo,
238 pub writer: Box<dyn Writer>,
239 pub async_writer_metrics: Option<AsyncWriterMetrics>,
240 pub enabled: bool,
242 pub online: bool,
243 pub dynamic: bool,
246 pub ifac: Option<IfacState>,
248 pub stats: InterfaceStats,
250 pub interface_type: String,
252 pub send_retry_at: Option<Instant>,
254 pub send_retry_backoff: Duration,
256}
257
258pub enum StartResult {
260 Simple {
262 id: InterfaceId,
263 info: InterfaceInfo,
264 writer: Box<dyn Writer>,
265 interface_type_name: String,
266 },
267 Listener { control: Option<ListenerControl> },
269 Multi(Vec<SubInterface>),
271}
272
273pub struct SubInterface {
275 pub id: InterfaceId,
276 pub info: InterfaceInfo,
277 pub writer: Box<dyn Writer>,
278 pub interface_type_name: String,
279}
280
281pub struct StartContext {
283 pub tx: EventSender,
284 pub next_dynamic_id: Arc<AtomicU64>,
285 pub mode: u8,
286 pub recursive_prs: bool,
287 pub announces_from_internal: bool,
288 pub ingress_control: rns_core::transport::types::IngressControlConfig,
289}
290
291pub trait InterfaceConfigData: Send + Any {
293 fn as_any(&self) -> &dyn Any;
294 fn into_any(self: Box<Self>) -> Box<dyn Any>;
295}
296
297impl<T: Send + 'static> InterfaceConfigData for T {
298 fn as_any(&self) -> &dyn Any {
299 self
300 }
301
302 fn into_any(self: Box<Self>) -> Box<dyn Any> {
303 self
304 }
305}
306
307pub trait InterfaceFactory: Send + Sync {
309 fn type_name(&self) -> &str;
311
312 fn default_ifac_size(&self) -> usize {
314 16
315 }
316
317 fn parse_config(
319 &self,
320 name: &str,
321 id: InterfaceId,
322 params: &HashMap<String, String>,
323 ) -> Result<Box<dyn InterfaceConfigData>, String>;
324
325 fn start(
327 &self,
328 config: Box<dyn InterfaceConfigData>,
329 ctx: StartContext,
330 ) -> io::Result<StartResult>;
331}
332
333impl InterfaceStatusView for InterfaceEntry {
334 fn id(&self) -> InterfaceId {
335 self.id
336 }
337 fn info(&self) -> &InterfaceInfo {
338 &self.info
339 }
340 fn online(&self) -> bool {
341 self.online
342 }
343 fn stats(&self) -> &InterfaceStats {
344 &self.stats
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::event::Event;
352 use rns_core::constants;
353 use std::sync::mpsc;
354
355 struct MockWriter {
356 sent: Vec<Vec<u8>>,
357 }
358
359 impl MockWriter {
360 fn new() -> Self {
361 MockWriter { sent: Vec::new() }
362 }
363 }
364
365 impl Writer for MockWriter {
366 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
367 self.sent.push(data.to_vec());
368 Ok(())
369 }
370 }
371
372 #[test]
373 fn interface_entry_construction() {
374 let entry = InterfaceEntry {
375 id: InterfaceId(1),
376 info: InterfaceInfo {
377 id: InterfaceId(1),
378 name: String::new(),
379 mode: constants::MODE_FULL,
380 recursive_prs: false,
381 announces_from_internal: true,
382 out_capable: true,
383 in_capable: true,
384 bitrate: None,
385 airtime_profile: None,
386 announce_rate_target: None,
387 announce_rate_grace: 0,
388 announce_rate_penalty: 0.0,
389 announce_cap: constants::ANNOUNCE_CAP,
390 is_local_client: false,
391 wants_tunnel: false,
392 tunnel_id: None,
393 mtu: constants::MTU as u32,
394 ia_freq: 0.0,
395 ip_freq: 0.0,
396 op_freq: 0.0,
397 op_samples: 0,
398 started: 0.0,
399 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
400 },
401 writer: Box::new(MockWriter::new()),
402 async_writer_metrics: None,
403 enabled: true,
404 online: false,
405 dynamic: false,
406 ifac: None,
407 stats: InterfaceStats::default(),
408 interface_type: String::new(),
409 send_retry_at: None,
410 send_retry_backoff: Duration::ZERO,
411 };
412 assert_eq!(entry.id, InterfaceId(1));
413 assert!(!entry.online);
414 assert!(!entry.dynamic);
415 }
416
417 #[test]
418 fn mock_writer_captures_bytes() {
419 let mut writer = MockWriter::new();
420 writer.send_frame(b"hello").unwrap();
421 writer.send_frame(b"world").unwrap();
422 assert_eq!(writer.sent.len(), 2);
423 assert_eq!(writer.sent[0], b"hello");
424 assert_eq!(writer.sent[1], b"world");
425 }
426
427 #[test]
428 fn writer_send_frame_produces_output() {
429 let mut writer = MockWriter::new();
430 let data = vec![0x01, 0x02, 0x03];
431 writer.send_frame(&data).unwrap();
432 assert_eq!(writer.sent[0], data);
433 }
434
435 struct BlockingWriter {
436 entered_tx: mpsc::Sender<()>,
437 release_rx: mpsc::Receiver<()>,
438 }
439
440 impl Writer for BlockingWriter {
441 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
442 let _ = self.entered_tx.send(());
443 let _ = self.release_rx.recv();
444 Ok(())
445 }
446 }
447
448 struct FailingWriter;
449
450 impl Writer for FailingWriter {
451 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
452 Err(io::Error::new(io::ErrorKind::BrokenPipe, "boom"))
453 }
454 }
455
456 #[test]
457 fn async_writer_returns_wouldblock_when_queue_is_full() {
458 let (event_tx, _event_rx) = crate::event::channel();
459 let (entered_tx, entered_rx) = mpsc::channel();
460 let (release_tx, release_rx) = mpsc::channel();
461 let (mut writer, metrics) = wrap_async_writer(
462 Box::new(BlockingWriter {
463 entered_tx,
464 release_rx,
465 }),
466 InterfaceId(7),
467 "test",
468 event_tx,
469 1,
470 );
471
472 writer.send_frame(&[1]).unwrap();
473 entered_rx.recv_timeout(Duration::from_secs(1)).unwrap();
474 writer.send_frame(&[2]).unwrap();
475 assert_eq!(metrics.queued_frames(), 1);
476 let err = writer.send_frame(&[3]).unwrap_err();
477 assert_eq!(err.kind(), io::ErrorKind::WouldBlock);
478
479 let _ = release_tx.send(());
480 }
481
482 #[test]
483 fn async_writer_reports_interface_down_after_worker_failure() {
484 let (event_tx, event_rx) = crate::event::channel();
485 let (mut writer, metrics) =
486 wrap_async_writer(Box::new(FailingWriter), InterfaceId(9), "fail", event_tx, 2);
487
488 writer.send_frame(&[1]).unwrap();
489 let event = event_rx.recv_timeout(Duration::from_secs(1)).unwrap();
490 assert!(matches!(event, Event::InterfaceDown(InterfaceId(9))));
491 assert!(!metrics.worker_alive());
492 }
493}