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