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