1use std::io::{self, Read, Write};
6use std::net::{TcpStream, ToSocketAddrs};
7use std::os::unix::io::AsRawFd;
8use std::sync::{Arc, Mutex};
9use std::thread;
10use std::time::Duration;
11
12use rns_core::transport::types::InterfaceId;
13
14use crate::event::{Event, EventSender};
15use crate::hdlc;
16use crate::interface::{lock_or_recover, Writer};
17
18const HW_MTU: usize = 262_144;
20
21#[derive(Debug, Clone)]
23pub struct TcpClientConfig {
24 pub name: String,
25 pub target_host: String,
26 pub target_port: u16,
27 pub interface_id: InterfaceId,
28 pub reconnect_wait: Duration,
29 pub max_reconnect_tries: Option<u32>,
30 pub connect_timeout: Duration,
31 pub device: Option<String>,
33 pub runtime: Arc<Mutex<TcpClientRuntime>>,
34}
35
36#[derive(Debug, Clone)]
37pub struct TcpClientRuntime {
38 pub target_host: String,
39 pub target_port: u16,
40 pub reconnect_wait: Duration,
41 pub max_reconnect_tries: Option<u32>,
42 pub connect_timeout: Duration,
43}
44
45impl TcpClientRuntime {
46 pub fn from_config(config: &TcpClientConfig) -> Self {
47 Self {
48 target_host: config.target_host.clone(),
49 target_port: config.target_port,
50 reconnect_wait: config.reconnect_wait,
51 max_reconnect_tries: config.max_reconnect_tries,
52 connect_timeout: config.connect_timeout,
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
58pub struct TcpClientRuntimeConfigHandle {
59 pub interface_name: String,
60 pub runtime: Arc<Mutex<TcpClientRuntime>>,
61 pub startup: TcpClientRuntime,
62}
63
64impl Default for TcpClientConfig {
65 fn default() -> Self {
66 let mut config = TcpClientConfig {
67 name: String::new(),
68 target_host: "127.0.0.1".into(),
69 target_port: 4242,
70 interface_id: InterfaceId(0),
71 reconnect_wait: Duration::from_secs(5),
72 max_reconnect_tries: None,
73 connect_timeout: Duration::from_secs(5),
74 device: None,
75 runtime: Arc::new(Mutex::new(TcpClientRuntime {
76 target_host: "127.0.0.1".into(),
77 target_port: 4242,
78 reconnect_wait: Duration::from_secs(5),
79 max_reconnect_tries: None,
80 connect_timeout: Duration::from_secs(5),
81 })),
82 };
83 let startup = TcpClientRuntime::from_config(&config);
84 config.runtime = Arc::new(Mutex::new(startup));
85 config
86 }
87}
88
89struct TcpWriter {
91 stream: TcpStream,
92}
93
94impl Writer for TcpWriter {
95 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
96 self.stream.write_all(&hdlc::frame(data))
97 }
98}
99
100fn set_socket_options(stream: &TcpStream) -> io::Result<()> {
102 let fd = stream.as_raw_fd();
103 unsafe {
104 let val: libc::c_int = 1;
106 if libc::setsockopt(
107 fd,
108 libc::IPPROTO_TCP,
109 libc::TCP_NODELAY,
110 &val as *const _ as *const libc::c_void,
111 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
112 ) != 0
113 {
114 return Err(io::Error::last_os_error());
115 }
116
117 if libc::setsockopt(
119 fd,
120 libc::SOL_SOCKET,
121 libc::SO_KEEPALIVE,
122 &val as *const _ as *const libc::c_void,
123 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
124 ) != 0
125 {
126 return Err(io::Error::last_os_error());
127 }
128
129 #[cfg(target_os = "linux")]
131 {
132 let idle: libc::c_int = 5;
134 if libc::setsockopt(
135 fd,
136 libc::IPPROTO_TCP,
137 libc::TCP_KEEPIDLE,
138 &idle as *const _ as *const libc::c_void,
139 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
140 ) != 0
141 {
142 return Err(io::Error::last_os_error());
143 }
144
145 let intvl: libc::c_int = 2;
147 if libc::setsockopt(
148 fd,
149 libc::IPPROTO_TCP,
150 libc::TCP_KEEPINTVL,
151 &intvl as *const _ as *const libc::c_void,
152 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
153 ) != 0
154 {
155 return Err(io::Error::last_os_error());
156 }
157
158 let cnt: libc::c_int = 12;
160 if libc::setsockopt(
161 fd,
162 libc::IPPROTO_TCP,
163 libc::TCP_KEEPCNT,
164 &cnt as *const _ as *const libc::c_void,
165 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
166 ) != 0
167 {
168 return Err(io::Error::last_os_error());
169 }
170
171 let timeout: libc::c_int = 24_000;
173 if libc::setsockopt(
174 fd,
175 libc::IPPROTO_TCP,
176 libc::TCP_USER_TIMEOUT,
177 &timeout as *const _ as *const libc::c_void,
178 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
179 ) != 0
180 {
181 return Err(io::Error::last_os_error());
182 }
183 }
184 }
185 Ok(())
186}
187
188fn try_connect(config: &TcpClientConfig, underlay_mark: Option<u32>) -> io::Result<TcpStream> {
190 let runtime = lock_or_recover(&config.runtime, "tcp client runtime").clone();
191 let addr_str = format!("{}:{}", config.target_host, config.target_port);
192 let addr = addr_str
193 .to_socket_addrs()?
194 .next()
195 .ok_or_else(|| io::Error::new(io::ErrorKind::AddrNotAvailable, "no addresses resolved"))?;
196
197 #[cfg(target_os = "linux")]
198 let stream = if config.device.is_some() || underlay_mark.is_some() {
199 super::connect_tcp_with_options(
200 &addr,
201 config.device.as_deref(),
202 underlay_mark,
203 runtime.connect_timeout,
204 )?
205 } else {
206 TcpStream::connect_timeout(&addr, runtime.connect_timeout)?
207 };
208 #[cfg(not(target_os = "linux"))]
209 let stream = TcpStream::connect_timeout(&addr, runtime.connect_timeout)?;
210 set_socket_options(&stream)?;
211 Ok(stream)
212}
213
214pub fn start(config: TcpClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
216 start_with_ifac(config, tx, 0, None)
217}
218
219fn start_with_ifac(
220 config: TcpClientConfig,
221 tx: EventSender,
222 ifac_size: usize,
223 underlay_mark: Option<u32>,
224) -> io::Result<Box<dyn Writer>> {
225 let stream = try_connect(&config, underlay_mark)?;
226 let reader_stream = stream.try_clone()?;
227 let writer_stream = stream.try_clone()?;
228
229 let id = config.interface_id;
230 let _ = tx.send(Event::InterfaceUp(id, None, None));
232
233 let reader_config = config;
235 let reader_tx = tx;
236 thread::Builder::new()
237 .name(format!("tcp-reader-{}", id.0))
238 .spawn(move || {
239 reader_loop(
240 reader_stream,
241 reader_config,
242 reader_tx,
243 ifac_size,
244 underlay_mark,
245 );
246 })?;
247
248 Ok(Box::new(TcpWriter {
249 stream: writer_stream,
250 }))
251}
252
253fn reader_loop(
256 mut stream: TcpStream,
257 config: TcpClientConfig,
258 tx: EventSender,
259 ifac_size: usize,
260 underlay_mark: Option<u32>,
261) {
262 let id = config.interface_id;
263 let mut decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
264 let mut buf = [0u8; 4096];
265
266 loop {
267 match stream.read(&mut buf) {
268 Ok(0) => {
269 log::warn!("[{}] connection closed", config.name);
271 let _ = tx.send(Event::InterfaceDown(id));
272 match reconnect(&config, &tx, underlay_mark) {
273 Some(new_stream) => {
274 stream = new_stream;
275 decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
276 continue;
277 }
278 None => {
279 log::error!("[{}] reconnection failed, giving up", config.name);
280 return;
281 }
282 }
283 }
284 Ok(n) => {
285 let decoded = decoder.feed_with_diagnostics(&buf[..n]);
286 for frame_len in decoded.invalid_frame_lengths {
287 log::debug!(
288 "[{}] invalid HDLC frame of {} bytes received, dropping frame",
289 config.name,
290 frame_len,
291 );
292 }
293 for frame in decoded.frames {
294 if tx
295 .send(Event::Frame {
296 interface_id: id,
297 data: frame,
298 rssi: None,
299 snr: None,
300 })
301 .is_err()
302 {
303 return;
305 }
306 }
307 }
308 Err(e) => {
309 log::warn!("[{}] read error: {}", config.name, e);
310 let _ = tx.send(Event::InterfaceDown(id));
311 match reconnect(&config, &tx, underlay_mark) {
312 Some(new_stream) => {
313 stream = new_stream;
314 decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
315 continue;
316 }
317 None => {
318 log::error!("[{}] reconnection failed, giving up", config.name);
319 return;
320 }
321 }
322 }
323 }
324 }
325}
326
327fn reconnect(
330 config: &TcpClientConfig,
331 tx: &EventSender,
332 underlay_mark: Option<u32>,
333) -> Option<TcpStream> {
334 let mut attempts = 0u32;
335 loop {
336 let runtime = lock_or_recover(&config.runtime, "tcp client runtime").clone();
337 thread::sleep(runtime.reconnect_wait);
338 attempts += 1;
339
340 if let Some(max) = runtime.max_reconnect_tries {
341 if attempts > max {
342 let _ = tx.send(Event::InterfaceDown(config.interface_id));
343 return None;
344 }
345 }
346
347 log::info!("[{}] reconnect attempt {} ...", config.name, attempts);
348
349 match try_connect(config, underlay_mark) {
350 Ok(new_stream) => {
351 let writer_stream = match new_stream.try_clone() {
353 Ok(s) => s,
354 Err(e) => {
355 log::warn!("[{}] failed to clone stream: {}", config.name, e);
356 continue;
357 }
358 };
359 log::info!("[{}] reconnected", config.name);
360 let new_writer: Box<dyn Writer> = Box::new(TcpWriter {
362 stream: writer_stream,
363 });
364 let _ = tx.send(Event::InterfaceUp(
365 config.interface_id,
366 Some(new_writer),
367 None,
368 ));
369 return Some(new_stream);
370 }
371 Err(e) => {
372 log::warn!("[{}] reconnect failed: {}", config.name, e);
373 }
374 }
375 }
376}
377
378use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
381use rns_core::transport::types::InterfaceInfo;
382use std::collections::HashMap;
383
384pub struct TcpClientFactory;
386
387impl InterfaceFactory for TcpClientFactory {
388 fn type_name(&self) -> &str {
389 "TCPClientInterface"
390 }
391
392 fn parse_config(
393 &self,
394 name: &str,
395 id: InterfaceId,
396 params: &HashMap<String, String>,
397 ) -> Result<Box<dyn InterfaceConfigData>, String> {
398 let target_host = params
399 .get("target_host")
400 .cloned()
401 .unwrap_or_else(|| "127.0.0.1".into());
402 let target_port = params
403 .get("target_port")
404 .and_then(|v| v.parse().ok())
405 .unwrap_or(4242);
406
407 Ok(Box::new(TcpClientConfig {
408 name: name.to_string(),
409 target_host,
410 target_port,
411 interface_id: id,
412 device: params.get("device").cloned(),
413 ..TcpClientConfig::default()
414 }))
415 }
416
417 fn start(
418 &self,
419 config: Box<dyn InterfaceConfigData>,
420 ctx: StartContext,
421 ) -> io::Result<StartResult> {
422 let tcp_config = *config
423 .into_any()
424 .downcast::<TcpClientConfig>()
425 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "wrong config type"))?;
426
427 let id = tcp_config.interface_id;
428 let name = tcp_config.name.clone();
429 let info = InterfaceInfo {
430 id,
431 name,
432 mode: ctx.mode,
433 gravity: ctx.gravity,
434 recursive_prs: ctx.recursive_prs,
435 announces_from_internal: ctx.announces_from_internal,
436 announces_to_internal: ctx.announces_to_internal,
437 out_capable: true,
438 in_capable: true,
439 bitrate: None,
440 airtime_profile: None,
441 announce_rate_target: None,
442 announce_rate_grace: 0,
443 announce_rate_penalty: 0.0,
444 announce_cap: rns_core::constants::ANNOUNCE_CAP,
445 is_local_client: false,
446 wants_tunnel: false,
447 tunnel_id: None,
448 mtu: 65535,
449 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
450 ia_freq: 0.0,
451 ip_freq: 0.0,
452 op_freq: 0.0,
453 op_samples: 0,
454 started: crate::time::now(),
455 };
456
457 let ifac_size = ctx.ifac.as_ref().map(|ifac| ifac.size).unwrap_or(0);
458 let writer = start_with_ifac(tcp_config, ctx.tx, ifac_size, ctx.underlay_mark)?;
459
460 Ok(StartResult::Simple {
461 id,
462 info,
463 writer,
464 interface_type_name: "TCPClientInterface".to_string(),
465 })
466 }
467}
468
469pub(crate) fn tcp_client_runtime_handle_from_config(
470 config: &TcpClientConfig,
471) -> TcpClientRuntimeConfigHandle {
472 TcpClientRuntimeConfigHandle {
473 interface_name: config.name.clone(),
474 runtime: Arc::clone(&config.runtime),
475 startup: TcpClientRuntime::from_config(config),
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use std::net::TcpListener;
483 use std::time::Duration;
484
485 fn find_free_port() -> u16 {
486 crate::test_support::port()
487 }
488
489 fn make_config(port: u16) -> TcpClientConfig {
490 TcpClientConfig {
491 name: format!("test-tcp-{}", port),
492 target_host: "127.0.0.1".into(),
493 target_port: port,
494 interface_id: InterfaceId(1),
495 reconnect_wait: Duration::from_millis(100),
496 max_reconnect_tries: Some(2),
497 connect_timeout: Duration::from_secs(2),
498 runtime: Arc::new(Mutex::new(TcpClientRuntime {
499 target_host: "127.0.0.1".into(),
500 target_port: port,
501 reconnect_wait: Duration::from_millis(100),
502 max_reconnect_tries: Some(2),
503 connect_timeout: Duration::from_secs(2),
504 })),
505 device: None,
506 }
507 }
508
509 #[test]
510 fn connect_to_listener() {
511 let port = find_free_port();
512 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
513 let (tx, rx) = crate::event::channel();
514
515 let config = make_config(port);
516 let _writer = start(config, tx).unwrap();
517
518 let _server_stream = listener.accept().unwrap();
520
521 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
523 assert!(matches!(event, Event::InterfaceUp(InterfaceId(1), _, _)));
524 }
525
526 #[test]
527 fn receive_frame() {
528 let port = find_free_port();
529 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
530 let (tx, rx) = crate::event::channel();
531
532 let config = make_config(port);
533 let _writer = start(config, tx).unwrap();
534
535 let (mut server_stream, _) = listener.accept().unwrap();
536
537 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
539
540 let payload: Vec<u8> = (0..32).collect();
542 let framed = hdlc::frame(&payload);
543 server_stream.write_all(&framed).unwrap();
544
545 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
547 match event {
548 Event::Frame {
549 interface_id,
550 data,
551 rssi: _,
552 snr: _,
553 } => {
554 assert_eq!(interface_id, InterfaceId(1));
555 assert_eq!(data, payload);
556 }
557 other => panic!("expected Frame, got {:?}", other),
558 }
559 }
560
561 #[test]
562 fn reader_enforces_upstream_frame_bounds_and_ifac_allowance() {
563 let port = find_free_port();
564 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
565 let (tx, rx) = crate::event::channel();
566 let ifac_size = 8;
567
568 let config = make_config(port);
569 let _writer = start_with_ifac(config, tx, ifac_size, None).unwrap();
570 let (mut server_stream, _) = listener.accept().unwrap();
571 assert!(matches!(
572 rx.recv_timeout(Duration::from_secs(1)).unwrap(),
573 Event::InterfaceUp(_, _, _)
574 ));
575
576 server_stream
579 .write_all(&hdlc::frame(&[0x11; rns_core::constants::HEADER_MINSIZE]))
580 .unwrap();
581 let minimum_valid = vec![0x22; rns_core::constants::HEADER_MINSIZE + 1];
582 server_stream
583 .write_all(&hdlc::frame(&minimum_valid))
584 .unwrap();
585 assert!(matches!(
586 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
587 Event::Frame { data, .. } if data == minimum_valid
588 ));
589
590 let maximum_valid = vec![0x33; HW_MTU + ifac_size];
592 server_stream
593 .write_all(&hdlc::frame(&maximum_valid))
594 .unwrap();
595 assert!(matches!(
596 rx.recv_timeout(Duration::from_secs(5)).unwrap(),
597 Event::Frame { data, .. } if data == maximum_valid
598 ));
599
600 server_stream
602 .write_all(&hdlc::frame(&vec![0x44; HW_MTU + ifac_size + 1]))
603 .unwrap();
604 let recovery = vec![0x55; rns_core::constants::HEADER_MINSIZE + 1];
605 server_stream.write_all(&hdlc::frame(&recovery)).unwrap();
606 assert!(matches!(
607 rx.recv_timeout(Duration::from_secs(5)).unwrap(),
608 Event::Frame { data, .. } if data == recovery
609 ));
610 }
611
612 #[test]
613 fn send_frame() {
614 let port = find_free_port();
615 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
616 let (tx, _rx) = crate::event::channel();
617
618 let config = make_config(port);
619 let mut writer = start(config, tx).unwrap();
620
621 let (mut server_stream, _) = listener.accept().unwrap();
622 server_stream
623 .set_read_timeout(Some(Duration::from_secs(2)))
624 .unwrap();
625
626 let payload: Vec<u8> = (0..24).collect();
628 writer.send_frame(&payload).unwrap();
629
630 let mut buf = [0u8; 256];
632 let n = server_stream.read(&mut buf).unwrap();
633 let expected = hdlc::frame(&payload);
634 assert_eq!(&buf[..n], &expected[..]);
635 }
636
637 #[test]
638 fn multiple_frames() {
639 let port = find_free_port();
640 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
641 let (tx, rx) = crate::event::channel();
642
643 let config = make_config(port);
644 let _writer = start(config, tx).unwrap();
645
646 let (mut server_stream, _) = listener.accept().unwrap();
647
648 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
650
651 let payloads: Vec<Vec<u8>> = (0..3)
653 .map(|i| (0..24).map(|j| j + i * 50).collect())
654 .collect();
655 for p in &payloads {
656 server_stream.write_all(&hdlc::frame(p)).unwrap();
657 }
658
659 for expected in &payloads {
661 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
662 match event {
663 Event::Frame { data, .. } => assert_eq!(&data, expected),
664 other => panic!("expected Frame, got {:?}", other),
665 }
666 }
667 }
668
669 #[test]
670 fn split_across_reads() {
671 let port = find_free_port();
672 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
673 let (tx, rx) = crate::event::channel();
674
675 let config = make_config(port);
676 let _writer = start(config, tx).unwrap();
677
678 let (mut server_stream, _) = listener.accept().unwrap();
679
680 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
682
683 let payload: Vec<u8> = (0..32).collect();
685 let framed = hdlc::frame(&payload);
686 let mid = framed.len() / 2;
687
688 server_stream.write_all(&framed[..mid]).unwrap();
689 server_stream.flush().unwrap();
690 thread::sleep(Duration::from_millis(50));
691 server_stream.write_all(&framed[mid..]).unwrap();
692 server_stream.flush().unwrap();
693
694 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
695 match event {
696 Event::Frame { data, .. } => assert_eq!(data, payload),
697 other => panic!("expected Frame, got {:?}", other),
698 }
699 }
700
701 #[test]
702 fn reconnect_on_close() {
703 let port = find_free_port();
704 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
705 listener.set_nonblocking(false).unwrap();
706 let (tx, rx) = crate::event::channel();
707
708 let config = make_config(port);
709 let _writer = start(config, tx).unwrap();
710
711 let (server_stream, _) = listener.accept().unwrap();
713
714 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
716
717 drop(server_stream);
718
719 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
721 assert!(matches!(event, Event::InterfaceDown(InterfaceId(1))));
722
723 let _server_stream2 = listener.accept().unwrap();
725
726 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
728 assert!(matches!(event, Event::InterfaceUp(InterfaceId(1), _, _)));
729 }
730
731 #[test]
732 fn socket_options() {
733 let port = find_free_port();
734 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
735
736 let stream = try_connect(&make_config(port), None).unwrap();
737 let _server = listener.accept().unwrap();
738
739 let fd = stream.as_raw_fd();
741 let mut val: libc::c_int = 0;
742 let mut len: libc::socklen_t = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
743 unsafe {
744 libc::getsockopt(
745 fd,
746 libc::IPPROTO_TCP,
747 libc::TCP_NODELAY,
748 &mut val as *mut _ as *mut libc::c_void,
749 &mut len,
750 );
751 }
752 assert_eq!(val, 1, "TCP_NODELAY should be 1");
753 }
754
755 #[test]
756 fn connect_timeout() {
757 let config = TcpClientConfig {
759 name: "timeout-test".into(),
760 target_host: "192.0.2.1".into(), target_port: 12345,
762 interface_id: InterfaceId(99),
763 reconnect_wait: Duration::from_millis(100),
764 max_reconnect_tries: Some(0),
765 connect_timeout: Duration::from_millis(500),
766 device: None,
767 runtime: Arc::new(Mutex::new(TcpClientRuntime {
768 target_host: "192.0.2.1".into(),
769 target_port: 12345,
770 reconnect_wait: Duration::from_millis(100),
771 max_reconnect_tries: Some(0),
772 connect_timeout: Duration::from_millis(500),
773 })),
774 };
775
776 let start_time = std::time::Instant::now();
777 let result = try_connect(&config, None);
778 let elapsed = start_time.elapsed();
779
780 assert!(result.is_err());
781 assert!(elapsed < Duration::from_secs(5));
783 }
784}