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            recursive_prs: ctx.recursive_prs,
234            announces_from_internal: ctx.announces_from_internal,
235            out_capable,
236            in_capable,
237            bitrate: Some(10_000_000),
238            airtime_profile: None,
239            announce_rate_target: None,
240            announce_rate_grace: 0,
241            announce_rate_penalty: 0.0,
242            announce_cap: rns_core::constants::ANNOUNCE_CAP,
243            is_local_client: false,
244            wants_tunnel: false,
245            tunnel_id: None,
246            mtu: 1400,
247            ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
248            ia_freq: 0.0,
249            ip_freq: 0.0,
250            op_freq: 0.0,
251            op_samples: 0,
252            started: crate::time::now(),
253        };
254
255        let maybe_writer = start(udp_config, ctx.tx)?;
256
257        let writer: Box<dyn Writer> = maybe_writer
258            .ok_or_else(|| io::Error::other("UDPInterface did not provide a writer"))?;
259
260        Ok(StartResult::Simple {
261            id,
262            info,
263            writer,
264            interface_type_name: "UDPInterface".to_string(),
265        })
266    }
267}
268
269pub(crate) fn udp_runtime_handle_from_config(config: &UdpConfig) -> UdpRuntimeConfigHandle {
270    UdpRuntimeConfigHandle {
271        interface_name: config.name.clone(),
272        runtime: Arc::clone(&config.runtime),
273        startup: UdpRuntime::from_config(config),
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use std::net::UdpSocket;
281    use std::time::Duration;
282
283    fn find_free_port() -> u16 {
284        UdpSocket::bind("127.0.0.1:0")
285            .unwrap()
286            .local_addr()
287            .unwrap()
288            .port()
289    }
290
291    #[test]
292    fn bind_and_receive() {
293        let port = find_free_port();
294        let (tx, rx) = crate::event::channel();
295
296        let config = UdpConfig {
297            name: "test-udp".into(),
298            listen_ip: Some("127.0.0.1".into()),
299            listen_port: Some(port),
300            forward_ip: None,
301            forward_port: None,
302            interface_id: InterfaceId(10),
303            ..UdpConfig::default()
304        };
305
306        let _writer = start(config, tx).unwrap();
307
308        // Drain InterfaceUp
309        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
310
311        // Send a UDP packet to the listener
312        let sender = UdpSocket::bind("127.0.0.1:0").unwrap();
313        let payload = b"hello udp";
314        sender
315            .send_to(payload, format!("127.0.0.1:{}", port))
316            .unwrap();
317
318        // Should receive Frame event
319        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
320        match event {
321            Event::Frame {
322                interface_id,
323                data,
324                rssi: _,
325                snr: _,
326            } => {
327                assert_eq!(interface_id, InterfaceId(10));
328                assert_eq!(data, payload);
329            }
330            other => panic!("expected Frame, got {:?}", other),
331        }
332    }
333
334    #[test]
335    fn send_broadcast() {
336        let recv_port = find_free_port();
337        let (tx, _rx) = crate::event::channel();
338
339        let config = UdpConfig {
340            name: "test-udp-send".into(),
341            listen_ip: None,
342            listen_port: None,
343            forward_ip: Some("127.0.0.1".into()),
344            forward_port: Some(recv_port),
345            interface_id: InterfaceId(11),
346            ..UdpConfig::default()
347        };
348
349        let writer = start(config, tx).unwrap();
350        let mut writer = writer.unwrap();
351
352        // Bind a receiver
353        let receiver = UdpSocket::bind(format!("127.0.0.1:{}", recv_port)).unwrap();
354        receiver
355            .set_read_timeout(Some(Duration::from_secs(2)))
356            .unwrap();
357
358        // Send via writer
359        let payload = b"broadcast data";
360        writer.send_frame(payload).unwrap();
361
362        // Receive on the other socket
363        let mut buf = [0u8; 256];
364        let (n, _) = receiver.recv_from(&mut buf).unwrap();
365        assert_eq!(&buf[..n], payload);
366    }
367
368    #[test]
369    fn round_trip() {
370        let listen_port = find_free_port();
371        let forward_port = find_free_port();
372        let (tx, rx) = crate::event::channel();
373
374        let config = UdpConfig {
375            name: "test-udp-rt".into(),
376            listen_ip: Some("127.0.0.1".into()),
377            listen_port: Some(listen_port),
378            forward_ip: Some("127.0.0.1".into()),
379            forward_port: Some(forward_port),
380            interface_id: InterfaceId(12),
381            ..UdpConfig::default()
382        };
383
384        let writer = start(config, tx).unwrap();
385        assert!(writer.is_some());
386
387        // Drain InterfaceUp
388        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
389
390        // Send to the listener
391        let sender = UdpSocket::bind("127.0.0.1:0").unwrap();
392        sender
393            .send_to(b"ping", format!("127.0.0.1:{}", listen_port))
394            .unwrap();
395
396        let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
397        match event {
398            Event::Frame { data, .. } => assert_eq!(data, b"ping"),
399            other => panic!("expected Frame, got {:?}", other),
400        }
401    }
402
403    #[test]
404    fn multiple_datagrams() {
405        let port = find_free_port();
406        let (tx, rx) = crate::event::channel();
407
408        let config = UdpConfig {
409            name: "test-udp-multi".into(),
410            listen_ip: Some("127.0.0.1".into()),
411            listen_port: Some(port),
412            forward_ip: None,
413            forward_port: None,
414            interface_id: InterfaceId(13),
415            ..UdpConfig::default()
416        };
417
418        let _writer = start(config, tx).unwrap();
419
420        // Drain InterfaceUp
421        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
422
423        let sender = UdpSocket::bind("127.0.0.1:0").unwrap();
424        for i in 0..5u8 {
425            sender.send_to(&[i], format!("127.0.0.1:{}", port)).unwrap();
426        }
427
428        for i in 0..5u8 {
429            let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
430            match event {
431                Event::Frame { data, .. } => assert_eq!(data, vec![i]),
432                other => panic!("expected Frame, got {:?}", other),
433            }
434        }
435    }
436
437    #[test]
438    fn writer_send_to() {
439        let recv_port = find_free_port();
440
441        // Bind receiver first
442        let receiver = UdpSocket::bind(format!("127.0.0.1:{}", recv_port)).unwrap();
443        receiver
444            .set_read_timeout(Some(Duration::from_secs(2)))
445            .unwrap();
446
447        // Create writer directly
448        let send_socket = UdpSocket::bind("127.0.0.1:0").unwrap();
449        send_socket.set_broadcast(true).unwrap();
450        let mut writer = UdpWriter {
451            socket: send_socket,
452            runtime: Arc::new(Mutex::new(UdpRuntime {
453                forward_ip: Some("127.0.0.1".into()),
454                forward_port: Some(recv_port),
455            })),
456        };
457
458        let payload = vec![0xAA, 0xBB, 0xCC];
459        writer.send_frame(&payload).unwrap();
460
461        let mut buf = [0u8; 256];
462        let (n, _) = receiver.recv_from(&mut buf).unwrap();
463        assert_eq!(&buf[..n], &payload);
464    }
465}