Skip to main content

rns_net/interface/
udp.rs

1//! UDP broadcast interface.
2//!
3//! Connectionless, no HDLC framing — each UDP datagram is one packet.
4//! Matches Python `UDPInterface` from `UDPInterface.py`.
5
6use std::io::{self};
7use std::net::{SocketAddr, UdpSocket};
8use std::os::unix::io::AsRawFd;
9use std::sync::{Arc, Mutex};
10use std::thread;
11use std::time::Duration;
12
13use rns_core::transport::types::InterfaceId;
14
15use crate::event::{Event, EventSender};
16use crate::interface::{lock_or_recover, ListenerControl, Writer};
17
18/// Configuration for a UDP interface.
19#[derive(Debug, Clone)]
20pub struct UdpConfig {
21    pub name: String,
22    pub listen_ip: Option<String>,
23    pub listen_port: Option<u16>,
24    pub forward_ip: Option<String>,
25    pub forward_port: Option<u16>,
26    pub interface_id: InterfaceId,
27    pub runtime: Arc<Mutex<UdpRuntime>>,
28}
29
30#[derive(Debug, Clone)]
31pub struct UdpRuntime {
32    pub forward_ip: Option<String>,
33    pub forward_port: Option<u16>,
34}
35
36impl UdpRuntime {
37    pub fn from_config(config: &UdpConfig) -> Self {
38        Self {
39            forward_ip: config.forward_ip.clone(),
40            forward_port: config.forward_port,
41        }
42    }
43}
44
45#[derive(Debug, Clone)]
46pub struct UdpRuntimeConfigHandle {
47    pub interface_name: String,
48    pub runtime: Arc<Mutex<UdpRuntime>>,
49    pub startup: UdpRuntime,
50}
51
52impl Default for UdpConfig {
53    fn default() -> Self {
54        let mut config = UdpConfig {
55            name: String::new(),
56            listen_ip: None,
57            listen_port: None,
58            forward_ip: None,
59            forward_port: None,
60            interface_id: InterfaceId(0),
61            runtime: Arc::new(Mutex::new(UdpRuntime {
62                forward_ip: None,
63                forward_port: None,
64            })),
65        };
66        let startup = UdpRuntime::from_config(&config);
67        config.runtime = Arc::new(Mutex::new(startup));
68        config
69    }
70}
71
72/// Writer that sends raw data via UDP to a target address.
73struct UdpWriter {
74    socket: UdpSocket,
75    runtime: Arc<Mutex<UdpRuntime>>,
76}
77
78impl Writer for UdpWriter {
79    fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
80        let runtime = lock_or_recover(&self.runtime, "udp runtime").clone();
81        let target = match (runtime.forward_ip, runtime.forward_port) {
82            (Some(ip), Some(port)) => format!("{}:{}", ip, port)
83                .parse::<SocketAddr>()
84                .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?,
85            _ => {
86                return Err(io::Error::other(
87                    "UDP interface has no forward target configured",
88                ))
89            }
90        };
91        self.socket.send_to(data, target)?;
92        Ok(())
93    }
94}
95
96/// Start a UDP interface. Spawns a reader thread if listen_ip/port are set.
97/// Returns a writer if forward_ip/port are set.
98pub fn start(config: UdpConfig, tx: EventSender) -> io::Result<Option<Box<dyn Writer>>> {
99    start_with_mark(config, tx, None).map(|(writer, _)| writer)
100}
101
102fn start_with_mark(
103    config: UdpConfig,
104    tx: EventSender,
105    underlay_mark: Option<u32>,
106) -> io::Result<(Option<Box<dyn Writer>>, ListenerControl)> {
107    let id = config.interface_id;
108    let control = ListenerControl::new();
109    {
110        let startup = UdpRuntime::from_config(&config);
111        *lock_or_recover(&config.runtime, "udp runtime") = startup;
112    }
113    let send_socket = UdpSocket::bind("0.0.0.0:0")?;
114    super::apply_underlay_mark(send_socket.as_raw_fd(), underlay_mark)?;
115    send_socket.set_broadcast(true)?;
116    let writer: Option<Box<dyn Writer>> = Some(Box::new(UdpWriter {
117        socket: send_socket,
118        runtime: Arc::clone(&config.runtime),
119    }));
120
121    // Create reader socket if listen params are set
122    if let (Some(ref bind_ip), Some(bind_port)) = (&config.listen_ip, config.listen_port) {
123        let bind_addr = format!("{}:{}", bind_ip, bind_port);
124        let recv_socket = UdpSocket::bind(&bind_addr)?;
125        super::apply_underlay_mark(recv_socket.as_raw_fd(), underlay_mark)?;
126        recv_socket.set_read_timeout(Some(Duration::from_millis(200)))?;
127
128        log::info!("[{}] UDP listening on {}", config.name, bind_addr);
129
130        // Signal interface is up
131        let _ = tx.send(Event::InterfaceUp(id, None, None));
132
133        let name = config.name.clone();
134        let reader_control = control.clone();
135        thread::Builder::new()
136            .name(format!("udp-reader-{}", id.0))
137            .spawn(move || {
138                udp_reader_loop(recv_socket, id, name, tx, reader_control);
139            })?;
140    }
141
142    Ok((writer, control))
143}
144
145/// Reader thread: receives UDP datagrams and sends them as frames.
146fn udp_reader_loop(
147    socket: UdpSocket,
148    id: InterfaceId,
149    name: String,
150    tx: EventSender,
151    control: ListenerControl,
152) {
153    let mut buf = [0u8; 2048];
154
155    loop {
156        if control.should_stop() {
157            return;
158        }
159        match socket.recv_from(&mut buf) {
160            Ok((n, _src)) => {
161                if n == 0 {
162                    continue;
163                }
164                if tx
165                    .send(Event::Frame {
166                        interface_id: id,
167                        data: buf[..n].to_vec(),
168                        rssi: None,
169                        snr: None,
170                    })
171                    .is_err()
172                {
173                    // Driver shut down
174                    return;
175                }
176            }
177            Err(e)
178                if matches!(
179                    e.kind(),
180                    io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
181                ) =>
182            {
183                continue;
184            }
185            Err(e) => {
186                if control.should_stop() {
187                    return;
188                }
189                log::warn!("[{}] recv error: {}", name, e);
190                let _ = tx.send(Event::InterfaceDown(id));
191                return;
192            }
193        }
194    }
195}
196
197// --- Factory implementation ---
198
199use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
200use rns_core::transport::types::InterfaceInfo;
201use std::collections::HashMap;
202
203/// Factory for `UDPInterface`.
204pub struct UdpFactory;
205
206impl InterfaceFactory for UdpFactory {
207    fn type_name(&self) -> &str {
208        "UDPInterface"
209    }
210
211    fn parse_config(
212        &self,
213        name: &str,
214        id: InterfaceId,
215        params: &HashMap<String, String>,
216    ) -> Result<Box<dyn InterfaceConfigData>, String> {
217        let listen_ip = params.get("listen_ip").cloned();
218
219        // 'port' is a shorthand that sets both listen_port and forward_port
220        let port_shorthand: Option<u16> = params.get("port").and_then(|v| v.parse().ok());
221
222        let listen_port: Option<u16> = params
223            .get("listen_port")
224            .and_then(|v| v.parse().ok())
225            .or(port_shorthand);
226
227        let forward_ip = params.get("forward_ip").cloned();
228
229        let forward_port: Option<u16> = params
230            .get("forward_port")
231            .and_then(|v| v.parse().ok())
232            .or(port_shorthand);
233
234        let mut config = UdpConfig {
235            name: name.to_string(),
236            listen_ip,
237            listen_port,
238            forward_ip,
239            forward_port,
240            interface_id: id,
241            runtime: Arc::new(Mutex::new(UdpRuntime {
242                forward_ip: None,
243                forward_port: None,
244            })),
245        };
246        let startup = UdpRuntime::from_config(&config);
247        config.runtime = Arc::new(Mutex::new(startup));
248        Ok(Box::new(config))
249    }
250
251    fn start(
252        &self,
253        config: Box<dyn InterfaceConfigData>,
254        ctx: StartContext,
255    ) -> io::Result<StartResult> {
256        let udp_config = *config
257            .into_any()
258            .downcast::<UdpConfig>()
259            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "wrong config type"))?;
260
261        let id = udp_config.interface_id;
262        let name = udp_config.name.clone();
263        let out_capable = udp_config.forward_ip.is_some();
264        let in_capable = udp_config.listen_ip.is_some();
265
266        let info = InterfaceInfo {
267            id,
268            name,
269            mode: ctx.mode,
270            gravity: ctx.gravity,
271            recursive_prs: ctx.recursive_prs,
272            announces_from_internal: ctx.announces_from_internal,
273            announces_to_internal: ctx.announces_to_internal,
274            out_capable,
275            in_capable,
276            bitrate: Some(10_000_000),
277            airtime_profile: None,
278            announce_rate_target: None,
279            announce_rate_grace: 0,
280            announce_rate_penalty: 0.0,
281            announce_cap: rns_core::constants::ANNOUNCE_CAP,
282            is_local_client: false,
283            wants_tunnel: false,
284            tunnel_id: None,
285            mtu: 1400,
286            ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
287            ia_freq: 0.0,
288            ip_freq: 0.0,
289            op_freq: 0.0,
290            op_samples: 0,
291            started: crate::time::now(),
292        };
293
294        let (maybe_writer, control) = start_with_mark(udp_config, ctx.tx, ctx.underlay_mark)?;
295
296        let writer: Box<dyn Writer> = maybe_writer
297            .ok_or_else(|| io::Error::other("UDPInterface did not provide a writer"))?;
298
299        Ok(StartResult::Simple {
300            control: Some(control),
301            id,
302            info,
303            writer,
304            interface_type_name: "UDPInterface".to_string(),
305        })
306    }
307}
308
309pub(crate) fn udp_runtime_handle_from_config(config: &UdpConfig) -> UdpRuntimeConfigHandle {
310    UdpRuntimeConfigHandle {
311        interface_name: config.name.clone(),
312        runtime: Arc::clone(&config.runtime),
313        startup: UdpRuntime::from_config(config),
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    #[test]
322    fn detach_stops_idle_udp_listener() {
323        let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
324        socket
325            .set_read_timeout(Some(Duration::from_millis(200)))
326            .unwrap();
327        let (tx, rx) = crate::event::channel();
328        let control = ListenerControl::new();
329        let reader_control = control.clone();
330
331        let reader = thread::spawn(move || {
332            udp_reader_loop(
333                socket,
334                InterfaceId(76),
335                "detach-test".into(),
336                tx,
337                reader_control,
338            )
339        });
340        thread::sleep(Duration::from_millis(50));
341        control.request_stop();
342        reader.join().unwrap();
343        assert!(rx.try_recv().is_err());
344    }
345    use std::net::UdpSocket;
346    use std::time::Duration;
347
348    fn find_free_port() -> u16 {
349        crate::test_support::port()
350    }
351
352    #[test]
353    fn bind_and_receive() {
354        let port = find_free_port();
355        let (tx, rx) = crate::event::channel();
356
357        let config = UdpConfig {
358            name: "test-udp".into(),
359            listen_ip: Some("127.0.0.1".into()),
360            listen_port: Some(port),
361            forward_ip: None,
362            forward_port: None,
363            interface_id: InterfaceId(10),
364            ..UdpConfig::default()
365        };
366
367        let _writer = start(config, tx).unwrap();
368
369        // Drain InterfaceUp
370        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
371
372        // Send a UDP packet to the listener
373        let sender = UdpSocket::bind("127.0.0.1:0").unwrap();
374        let payload = b"hello udp";
375        sender
376            .send_to(payload, format!("127.0.0.1:{}", port))
377            .unwrap();
378
379        // Should receive Frame event
380        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
381        match event {
382            Event::Frame {
383                interface_id,
384                data,
385                rssi: _,
386                snr: _,
387            } => {
388                assert_eq!(interface_id, InterfaceId(10));
389                assert_eq!(data, payload);
390            }
391            other => panic!("expected Frame, got {:?}", other),
392        }
393    }
394
395    #[test]
396    fn send_broadcast() {
397        let recv_port = find_free_port();
398        let (tx, _rx) = crate::event::channel();
399
400        let config = UdpConfig {
401            name: "test-udp-send".into(),
402            listen_ip: None,
403            listen_port: None,
404            forward_ip: Some("127.0.0.1".into()),
405            forward_port: Some(recv_port),
406            interface_id: InterfaceId(11),
407            ..UdpConfig::default()
408        };
409
410        let writer = start(config, tx).unwrap();
411        let mut writer = writer.unwrap();
412
413        // Bind a receiver
414        let receiver = UdpSocket::bind(format!("127.0.0.1:{}", recv_port)).unwrap();
415        receiver
416            .set_read_timeout(Some(Duration::from_secs(2)))
417            .unwrap();
418
419        // Send via writer
420        let payload = b"broadcast data";
421        writer.send_frame(payload).unwrap();
422
423        // Receive on the other socket
424        let mut buf = [0u8; 256];
425        let (n, _) = receiver.recv_from(&mut buf).unwrap();
426        assert_eq!(&buf[..n], payload);
427    }
428
429    #[test]
430    fn round_trip() {
431        let listen_port = find_free_port();
432        let forward_port = find_free_port();
433        let (tx, rx) = crate::event::channel();
434
435        let config = UdpConfig {
436            name: "test-udp-rt".into(),
437            listen_ip: Some("127.0.0.1".into()),
438            listen_port: Some(listen_port),
439            forward_ip: Some("127.0.0.1".into()),
440            forward_port: Some(forward_port),
441            interface_id: InterfaceId(12),
442            ..UdpConfig::default()
443        };
444
445        let writer = start(config, tx).unwrap();
446        assert!(writer.is_some());
447
448        // Drain InterfaceUp
449        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
450
451        // Send to the listener
452        let sender = UdpSocket::bind("127.0.0.1:0").unwrap();
453        sender
454            .send_to(b"ping", format!("127.0.0.1:{}", listen_port))
455            .unwrap();
456
457        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
458        match event {
459            Event::Frame { data, .. } => assert_eq!(data, b"ping"),
460            other => panic!("expected Frame, got {:?}", other),
461        }
462    }
463
464    #[test]
465    fn multiple_datagrams() {
466        let port = find_free_port();
467        let (tx, rx) = crate::event::channel();
468
469        let config = UdpConfig {
470            name: "test-udp-multi".into(),
471            listen_ip: Some("127.0.0.1".into()),
472            listen_port: Some(port),
473            forward_ip: None,
474            forward_port: None,
475            interface_id: InterfaceId(13),
476            ..UdpConfig::default()
477        };
478
479        let _writer = start(config, tx).unwrap();
480
481        // Drain InterfaceUp
482        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
483
484        let sender = UdpSocket::bind("127.0.0.1:0").unwrap();
485        for i in 0..5u8 {
486            sender.send_to(&[i], format!("127.0.0.1:{}", port)).unwrap();
487        }
488
489        for i in 0..5u8 {
490            let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
491            match event {
492                Event::Frame { data, .. } => assert_eq!(data, vec![i]),
493                other => panic!("expected Frame, got {:?}", other),
494            }
495        }
496    }
497
498    #[test]
499    fn empty_datagram_is_ignored_before_transport_ingress() {
500        let port = find_free_port();
501        let (tx, rx) = crate::event::channel();
502        let config = UdpConfig {
503            name: "test-udp-empty".into(),
504            listen_ip: Some("127.0.0.1".into()),
505            listen_port: Some(port),
506            interface_id: InterfaceId(14),
507            ..UdpConfig::default()
508        };
509        let _writer = start(config, tx).unwrap();
510        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
511
512        let sender = UdpSocket::bind("127.0.0.1:0").unwrap();
513        let target = format!("127.0.0.1:{port}");
514        sender.send_to(&[], &target).unwrap();
515        sender.send_to(b"after-empty", &target).unwrap();
516
517        match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
518            Event::Frame { data, .. } => assert_eq!(data, b"after-empty"),
519            other => panic!("expected non-empty Frame, got {other:?}"),
520        }
521    }
522
523    #[test]
524    fn writer_send_to() {
525        let recv_port = find_free_port();
526
527        // Bind receiver first
528        let receiver = UdpSocket::bind(format!("127.0.0.1:{}", recv_port)).unwrap();
529        receiver
530            .set_read_timeout(Some(Duration::from_secs(2)))
531            .unwrap();
532
533        // Create writer directly
534        let send_socket = UdpSocket::bind("127.0.0.1:0").unwrap();
535        send_socket.set_broadcast(true).unwrap();
536        let mut writer = UdpWriter {
537            socket: send_socket,
538            runtime: Arc::new(Mutex::new(UdpRuntime {
539                forward_ip: Some("127.0.0.1".into()),
540                forward_port: Some(recv_port),
541            })),
542        };
543
544        let payload = vec![0xAA, 0xBB, 0xCC];
545        writer.send_frame(&payload).unwrap();
546
547        let mut buf = [0u8; 256];
548        let (n, _) = receiver.recv_from(&mut buf).unwrap();
549        assert_eq!(&buf[..n], &payload);
550    }
551}