1use std::io::{self, 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, LoopContext, LoopSource, SourceStatus};
15use crate::hdlc;
16use crate::interface::{lock_or_recover, wait_while_active, ListenerControl, 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 scratch: Vec<u8>,
94}
95
96impl Writer for TcpWriter {
97 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
98 self.stream.write_all(&hdlc::frame(data))
99 }
100
101 fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
102 hdlc::write_frames(&mut self.stream, frames, &mut self.scratch)
103 }
104
105 fn loop_socket(&self) -> Option<std::os::fd::RawFd> {
106 Some(self.stream.as_raw_fd())
107 }
108}
109
110pub(crate) fn set_socket_options(stream: &TcpStream) -> io::Result<()> {
114 let fd = stream.as_raw_fd();
115 unsafe {
116 let val: libc::c_int = 1;
118 if libc::setsockopt(
119 fd,
120 libc::IPPROTO_TCP,
121 libc::TCP_NODELAY,
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 if libc::setsockopt(
131 fd,
132 libc::SOL_SOCKET,
133 libc::SO_KEEPALIVE,
134 &val as *const _ as *const libc::c_void,
135 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
136 ) != 0
137 {
138 return Err(io::Error::last_os_error());
139 }
140
141 #[cfg(target_os = "linux")]
143 {
144 let idle: libc::c_int = 5;
146 if libc::setsockopt(
147 fd,
148 libc::IPPROTO_TCP,
149 libc::TCP_KEEPIDLE,
150 &idle as *const _ as *const libc::c_void,
151 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
152 ) != 0
153 {
154 return Err(io::Error::last_os_error());
155 }
156
157 let intvl: libc::c_int = 2;
159 if libc::setsockopt(
160 fd,
161 libc::IPPROTO_TCP,
162 libc::TCP_KEEPINTVL,
163 &intvl as *const _ as *const libc::c_void,
164 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
165 ) != 0
166 {
167 return Err(io::Error::last_os_error());
168 }
169
170 let cnt: libc::c_int = 12;
172 if libc::setsockopt(
173 fd,
174 libc::IPPROTO_TCP,
175 libc::TCP_KEEPCNT,
176 &cnt as *const _ as *const libc::c_void,
177 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
178 ) != 0
179 {
180 return Err(io::Error::last_os_error());
181 }
182
183 let timeout: libc::c_int = 24_000;
185 if libc::setsockopt(
186 fd,
187 libc::IPPROTO_TCP,
188 libc::TCP_USER_TIMEOUT,
189 &timeout as *const _ as *const libc::c_void,
190 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
191 ) != 0
192 {
193 return Err(io::Error::last_os_error());
194 }
195 }
196 }
197 Ok(())
198}
199
200fn try_connect(config: &TcpClientConfig, underlay_mark: Option<u32>) -> io::Result<TcpStream> {
202 let runtime = lock_or_recover(&config.runtime, "tcp client runtime").clone();
203 let addr_str = format!("{}:{}", config.target_host, config.target_port);
204 let addr = addr_str
205 .to_socket_addrs()?
206 .next()
207 .ok_or_else(|| io::Error::new(io::ErrorKind::AddrNotAvailable, "no addresses resolved"))?;
208
209 #[cfg(target_os = "linux")]
210 let stream = if config.device.is_some() || underlay_mark.is_some() {
211 super::connect_tcp_with_options(
212 &addr,
213 config.device.as_deref(),
214 underlay_mark,
215 runtime.connect_timeout,
216 )?
217 } else {
218 TcpStream::connect_timeout(&addr, runtime.connect_timeout)?
219 };
220 #[cfg(not(target_os = "linux"))]
221 let stream = TcpStream::connect_timeout(&addr, runtime.connect_timeout)?;
222 set_socket_options(&stream)?;
223 Ok(stream)
224}
225
226pub fn start(config: TcpClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
228 start_with_ifac(config, tx, 0, None)
229}
230
231fn start_with_ifac(
232 config: TcpClientConfig,
233 tx: EventSender,
234 ifac_size: usize,
235 underlay_mark: Option<u32>,
236) -> io::Result<Box<dyn Writer>> {
237 start_with_ifac_control(config, tx, ifac_size, underlay_mark).map(|(writer, _)| writer)
238}
239
240fn start_with_ifac_control(
241 config: TcpClientConfig,
242 tx: EventSender,
243 ifac_size: usize,
244 underlay_mark: Option<u32>,
245) -> io::Result<(Box<dyn Writer>, ListenerControl)> {
246 let stream = try_connect(&config, underlay_mark)?;
247 let writer_stream = stream.try_clone()?;
248
249 let id = config.interface_id;
250 let _ = tx.send(Event::InterfaceUp(id, None, None));
252
253 let control = ListenerControl::new();
254 let link = Arc::new(ClientLink {
255 config,
256 tx,
257 ifac_size,
258 underlay_mark,
259 control: control.clone(),
260 });
261 if let Err(error) = link.serve(stream) {
262 let _ = link.tx.send(Event::InterfaceDown(id));
263 return Err(error);
264 }
265
266 Ok((
267 Box::new(TcpWriter {
268 stream: writer_stream,
269 scratch: Vec::new(),
270 }),
271 control,
272 ))
273}
274
275pub(crate) const STREAM_READ_BUDGET: usize = 256 * 1024;
278
279pub(crate) enum StreamRead {
281 Rearm,
283 Suspend,
286 Closed,
288 Failed(io::Error),
289 DriverGone,
291}
292
293pub(crate) fn read_stream_frames(
299 fd: std::os::fd::RawFd,
300 decoder: &mut hdlc::Decoder,
301 cx: &mut LoopContext<'_>,
302 tx: &EventSender,
303 id: InterfaceId,
304 name: &str,
305 mut on_read: impl FnMut(usize, usize),
306) -> StreamRead {
307 if !tx.inbound_data_has_room() {
308 return StreamRead::Suspend;
309 }
310 let mut read = 0;
311 while read < STREAM_READ_BUDGET {
312 let n = unsafe {
315 libc::recv(
316 fd,
317 cx.buf.as_mut_ptr().cast(),
318 cx.buf.len(),
319 libc::MSG_DONTWAIT,
320 )
321 };
322 if n < 0 {
323 let e = io::Error::last_os_error();
324 match e.kind() {
325 io::ErrorKind::WouldBlock => return StreamRead::Rearm,
326 io::ErrorKind::Interrupted => continue,
327 _ => return StreamRead::Failed(e),
328 }
329 }
330 if n == 0 {
331 return StreamRead::Closed;
332 }
333 let n = n as usize;
334 read += n;
335 let decoded = decoder.feed_with_diagnostics(&cx.buf[..n]);
336 for frame_len in decoded.invalid_frame_lengths {
337 log::debug!(
338 "[{}] invalid HDLC frame of {} bytes received on interface {}, dropping frame",
339 name,
340 frame_len,
341 id.0,
342 );
343 }
344 on_read(n, decoded.frames.len());
345 let frames = decoded.frames.into_iter().map(|frame| Event::Frame {
346 interface_id: id,
347 data: frame,
348 rssi: None,
349 snr: None,
350 });
351 match tx.push_from_loop(frames) {
352 Ok(true) => {}
353 Ok(false) => return StreamRead::Suspend,
354 Err(_) => return StreamRead::DriverGone,
355 }
356 }
357 StreamRead::Rearm
358}
359
360struct ClientLink {
362 config: TcpClientConfig,
363 tx: EventSender,
364 ifac_size: usize,
365 underlay_mark: Option<u32>,
366 control: ListenerControl,
367}
368
369impl ClientLink {
370 fn serve(self: &Arc<Self>, stream: TcpStream) -> io::Result<()> {
372 let source = ReaderSource {
373 stream,
374 decoder: hdlc::Decoder::reticulum(HW_MTU, self.ifac_size),
375 link: Arc::clone(self),
376 };
377 self.tx.register_source(
378 crate::interface::loop_writer::read_key(self.config.interface_id),
379 Box::new(source),
380 )
381 }
382
383 fn connection_lost(self: &Arc<Self>) {
387 let id = self.config.interface_id;
388 let _ = self.tx.push_from_loop([Event::InterfaceDown(id)]);
389 let link = Arc::clone(self);
390 let spawned = thread::Builder::new()
391 .name(format!("tcp-reconn-{}", id.0))
392 .spawn(move || link.reconnect_loop());
393 if let Err(error) = spawned {
394 log::error!(
395 "[{}] cannot start reconnecting, giving up: {}",
396 self.config.name,
397 error
398 );
399 }
400 }
401
402 fn reconnect_loop(self: Arc<Self>) {
403 loop {
404 let Some(stream) = reconnect(&self.config, &self.tx, self.underlay_mark, &self.control)
405 else {
406 if !self.control.should_stop() {
407 log::error!("[{}] reconnection failed, giving up", self.config.name);
408 }
409 return;
410 };
411 match self.serve(stream) {
412 Ok(()) => return,
413 Err(error) => {
414 log::warn!(
415 "[{}] cannot read the new connection: {}",
416 self.config.name,
417 error
418 );
419 let _ = self.tx.send(Event::InterfaceDown(self.config.interface_id));
420 }
421 }
422 }
423 }
424}
425
426struct ReaderSource {
428 stream: TcpStream,
429 decoder: hdlc::Decoder,
430 link: Arc<ClientLink>,
431}
432
433impl LoopSource for ReaderSource {
434 fn fd(&self) -> std::os::fd::RawFd {
435 self.stream.as_raw_fd()
436 }
437
438 fn stopped(&self) -> bool {
439 self.link.control.should_stop()
440 }
441
442 fn on_ready(&mut self, cx: &mut LoopContext<'_>) -> SourceStatus {
443 let link = &self.link;
444 if link.control.should_stop() {
445 return SourceStatus::Remove;
446 }
447 let name = &link.config.name;
448 match read_stream_frames(
449 self.stream.as_raw_fd(),
450 &mut self.decoder,
451 cx,
452 &link.tx,
453 link.config.interface_id,
454 name,
455 |_, _| {},
456 ) {
457 StreamRead::Rearm => SourceStatus::Rearm,
458 StreamRead::Suspend => SourceStatus::Suspend,
459 StreamRead::Closed => {
460 log::warn!("[{}] connection closed", name);
461 link.connection_lost();
462 SourceStatus::Remove
463 }
464 StreamRead::Failed(error) => {
465 log::warn!("[{}] read error: {}", name, error);
466 link.connection_lost();
467 SourceStatus::Remove
468 }
469 StreamRead::DriverGone => SourceStatus::Remove,
470 }
471 }
472}
473
474fn reconnect(
477 config: &TcpClientConfig,
478 tx: &EventSender,
479 underlay_mark: Option<u32>,
480 control: &ListenerControl,
481) -> Option<TcpStream> {
482 let mut attempts = 0u32;
483 loop {
484 let runtime = lock_or_recover(&config.runtime, "tcp client runtime").clone();
485 if !wait_while_active(control, runtime.reconnect_wait) {
486 return None;
487 }
488 attempts += 1;
489
490 if let Some(max) = runtime.max_reconnect_tries {
491 if attempts > max {
492 let _ = tx.send(Event::InterfaceDown(config.interface_id));
493 return None;
494 }
495 }
496
497 log::info!("[{}] reconnect attempt {} ...", config.name, attempts);
498
499 match try_connect(config, underlay_mark) {
500 Ok(new_stream) => {
501 if control.should_stop() {
502 return None;
503 }
504 let writer_stream = match new_stream.try_clone() {
506 Ok(s) => s,
507 Err(e) => {
508 log::warn!("[{}] failed to clone stream: {}", config.name, e);
509 continue;
510 }
511 };
512 log::info!("[{}] reconnected", config.name);
513 let new_writer: Box<dyn Writer> = Box::new(TcpWriter {
515 stream: writer_stream,
516 scratch: Vec::new(),
517 });
518 let _ = tx.send(Event::InterfaceUp(
519 config.interface_id,
520 Some(new_writer),
521 None,
522 ));
523 return Some(new_stream);
524 }
525 Err(e) => {
526 log::warn!("[{}] reconnect failed: {}", config.name, e);
527 }
528 }
529 }
530}
531
532use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
535use rns_core::transport::types::InterfaceInfo;
536use std::collections::HashMap;
537
538pub struct TcpClientFactory;
540
541impl InterfaceFactory for TcpClientFactory {
542 fn type_name(&self) -> &str {
543 "TCPClientInterface"
544 }
545
546 fn parse_config(
547 &self,
548 name: &str,
549 id: InterfaceId,
550 params: &HashMap<String, String>,
551 ) -> Result<Box<dyn InterfaceConfigData>, String> {
552 let target_host = params
553 .get("target_host")
554 .cloned()
555 .unwrap_or_else(|| "127.0.0.1".into());
556 let target_port = params
557 .get("target_port")
558 .and_then(|v| v.parse().ok())
559 .unwrap_or(4242);
560
561 Ok(Box::new(TcpClientConfig {
562 name: name.to_string(),
563 target_host,
564 target_port,
565 interface_id: id,
566 device: params.get("device").cloned(),
567 ..TcpClientConfig::default()
568 }))
569 }
570
571 fn start(
572 &self,
573 config: Box<dyn InterfaceConfigData>,
574 ctx: StartContext,
575 ) -> io::Result<StartResult> {
576 let tcp_config = *config
577 .into_any()
578 .downcast::<TcpClientConfig>()
579 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "wrong config type"))?;
580
581 let id = tcp_config.interface_id;
582 let name = tcp_config.name.clone();
583 let info = InterfaceInfo {
584 id,
585 name,
586 mode: ctx.mode,
587 gravity: ctx.gravity,
588 recursive_prs: ctx.recursive_prs,
589 announces_from_internal: ctx.announces_from_internal,
590 announces_to_internal: ctx.announces_to_internal,
591 out_capable: true,
592 in_capable: true,
593 bitrate: None,
594 airtime_profile: None,
595 announce_rate_target: None,
596 announce_rate_grace: 0,
597 announce_rate_penalty: 0.0,
598 announce_cap: rns_core::constants::ANNOUNCE_CAP,
599 is_local_client: false,
600 wants_tunnel: false,
601 tunnel_id: None,
602 mtu: 65535,
603 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
604 ia_freq: 0.0,
605 ip_freq: 0.0,
606 op_freq: 0.0,
607 op_samples: 0,
608 started: crate::time::now(),
609 };
610
611 let ifac_size = ctx.ifac.as_ref().map(|ifac| ifac.size).unwrap_or(0);
612 let (writer, control) =
613 start_with_ifac_control(tcp_config, ctx.tx, ifac_size, ctx.underlay_mark)?;
614
615 Ok(StartResult::Simple {
616 control: Some(control),
617 id,
618 info,
619 writer,
620 interface_type_name: "TCPClientInterface".to_string(),
621 })
622 }
623}
624
625pub(crate) fn tcp_client_runtime_handle_from_config(
626 config: &TcpClientConfig,
627) -> TcpClientRuntimeConfigHandle {
628 TcpClientRuntimeConfigHandle {
629 interface_name: config.name.clone(),
630 runtime: Arc::clone(&config.runtime),
631 startup: TcpClientRuntime::from_config(config),
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use std::io::Read;
639
640 #[test]
641 fn detach_stops_idle_tcp_reader_without_reconnect() {
642 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
643 let mut client = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
644 let (server, _) = listener.accept().unwrap();
645 let (tx, rx) = crate::event::channel();
646 let control = ListenerControl::new();
647 let link = Arc::new(ClientLink {
648 config: TcpClientConfig {
649 name: "detach-test".into(),
650 interface_id: InterfaceId(75),
651 ..Default::default()
652 },
653 tx,
654 ifac_size: 0,
655 underlay_mark: None,
656 control: control.clone(),
657 });
658 link.serve(server).unwrap();
659 control.request_stop();
660 rx.sweep_stopped_sources();
661
662 client
664 .set_read_timeout(Some(Duration::from_secs(2)))
665 .unwrap();
666 let mut buf = [0u8; 1];
667 assert_eq!(std::io::Read::read(&mut client, &mut buf).unwrap(), 0);
668 assert!(rx.try_recv().is_err());
669 drop(link);
670 }
671
672 #[test]
673 fn a_reconnected_client_is_read_on_the_receiving_thread() {
674 let port = find_free_port();
675 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
676 let (tx, rx) = crate::event::channel();
677 let config = TcpClientConfig {
678 interface_id: InterfaceId(76),
679 ..make_config(port)
680 };
681 let _writer = start(config, tx).unwrap();
682 let (first, _) = listener.accept().unwrap();
683 assert!(matches!(
684 rx.recv_timeout(Duration::from_secs(1)).unwrap(),
685 Event::InterfaceUp(InterfaceId(76), None, _)
686 ));
687 drop(first);
688 assert!(matches!(
689 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
690 Event::InterfaceDown(InterfaceId(76))
691 ));
692 let (mut second, _) = listener.accept().unwrap();
693 assert!(matches!(
694 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
695 Event::InterfaceUp(InterfaceId(76), Some(_), _)
696 ));
697 let payload: Vec<u8> = (0..32).collect();
698 second.write_all(&hdlc::frame(&payload)).unwrap();
699 match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
700 Event::Frame { data, .. } => assert_eq!(data, payload),
701 other => panic!("expected Frame, got {:?}", other),
702 }
703 let reconnecting = || {
705 std::fs::read_dir("/proc/self/task")
706 .unwrap()
707 .filter_map(|task| std::fs::read_to_string(task.ok()?.path().join("comm")).ok())
708 .any(|name| name.trim_end() == "tcp-reconn-76")
709 };
710 let deadline = std::time::Instant::now() + Duration::from_secs(2);
711 while reconnecting() && std::time::Instant::now() < deadline {
712 thread::sleep(Duration::from_millis(10));
713 }
714 assert!(!reconnecting());
715 }
716
717 use std::net::TcpListener;
718 use std::time::Duration;
719
720 fn find_free_port() -> u16 {
721 crate::test_support::port()
722 }
723
724 fn make_config(port: u16) -> TcpClientConfig {
725 TcpClientConfig {
726 name: format!("test-tcp-{}", port),
727 target_host: "127.0.0.1".into(),
728 target_port: port,
729 interface_id: InterfaceId(1),
730 reconnect_wait: Duration::from_millis(100),
731 max_reconnect_tries: Some(2),
732 connect_timeout: Duration::from_secs(2),
733 runtime: Arc::new(Mutex::new(TcpClientRuntime {
734 target_host: "127.0.0.1".into(),
735 target_port: port,
736 reconnect_wait: Duration::from_millis(100),
737 max_reconnect_tries: Some(2),
738 connect_timeout: Duration::from_secs(2),
739 })),
740 device: None,
741 }
742 }
743
744 #[test]
745 fn connect_to_listener() {
746 let port = find_free_port();
747 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
748 let (tx, rx) = crate::event::channel();
749
750 let config = make_config(port);
751 let _writer = start(config, tx).unwrap();
752
753 let _server_stream = listener.accept().unwrap();
755
756 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
758 assert!(matches!(event, Event::InterfaceUp(InterfaceId(1), _, _)));
759 }
760
761 #[test]
762 fn receive_frame() {
763 let port = find_free_port();
764 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
765 let (tx, rx) = crate::event::channel();
766
767 let config = make_config(port);
768 let _writer = start(config, tx).unwrap();
769
770 let (mut server_stream, _) = listener.accept().unwrap();
771
772 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
774
775 let payload: Vec<u8> = (0..32).collect();
777 let framed = hdlc::frame(&payload);
778 server_stream.write_all(&framed).unwrap();
779
780 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
782 match event {
783 Event::Frame {
784 interface_id,
785 data,
786 rssi: _,
787 snr: _,
788 } => {
789 assert_eq!(interface_id, InterfaceId(1));
790 assert_eq!(data, payload);
791 }
792 other => panic!("expected Frame, got {:?}", other),
793 }
794 }
795
796 #[test]
797 fn reader_enforces_upstream_frame_bounds_and_ifac_allowance() {
798 let port = find_free_port();
799 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
800 let (tx, rx) = crate::event::channel();
801 let ifac_size = 8;
802
803 let config = make_config(port);
804 let _writer = start_with_ifac(config, tx, ifac_size, None).unwrap();
805 let (mut server_stream, _) = listener.accept().unwrap();
806 assert!(matches!(
807 rx.recv_timeout(Duration::from_secs(1)).unwrap(),
808 Event::InterfaceUp(_, _, _)
809 ));
810
811 server_stream
814 .write_all(&hdlc::frame(&[0x11; rns_core::constants::HEADER_MINSIZE]))
815 .unwrap();
816 let minimum_valid = vec![0x22; rns_core::constants::HEADER_MINSIZE + 1];
817 server_stream
818 .write_all(&hdlc::frame(&minimum_valid))
819 .unwrap();
820 assert!(matches!(
821 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
822 Event::Frame { data, .. } if data == minimum_valid
823 ));
824
825 let maximum_valid = vec![0x33; HW_MTU + ifac_size];
827 server_stream
828 .write_all(&hdlc::frame(&maximum_valid))
829 .unwrap();
830 assert!(matches!(
831 rx.recv_timeout(Duration::from_secs(5)).unwrap(),
832 Event::Frame { data, .. } if data == maximum_valid
833 ));
834
835 server_stream
837 .write_all(&hdlc::frame(&vec![0x44; HW_MTU + ifac_size + 1]))
838 .unwrap();
839 let recovery = vec![0x55; rns_core::constants::HEADER_MINSIZE + 1];
840 server_stream.write_all(&hdlc::frame(&recovery)).unwrap();
841 assert!(matches!(
842 rx.recv_timeout(Duration::from_secs(5)).unwrap(),
843 Event::Frame { data, .. } if data == recovery
844 ));
845 }
846
847 #[test]
848 fn send_frame() {
849 let port = find_free_port();
850 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
851 let (tx, _rx) = crate::event::channel();
852
853 let config = make_config(port);
854 let mut writer = start(config, tx).unwrap();
855
856 let (mut server_stream, _) = listener.accept().unwrap();
857 server_stream
858 .set_read_timeout(Some(Duration::from_secs(2)))
859 .unwrap();
860
861 let payload: Vec<u8> = (0..24).collect();
863 writer.send_frame(&payload).unwrap();
864
865 let mut buf = [0u8; 256];
867 let n = server_stream.read(&mut buf).unwrap();
868 let expected = hdlc::frame(&payload);
869 assert_eq!(&buf[..n], &expected[..]);
870 }
871
872 #[test]
873 fn multiple_frames() {
874 let port = find_free_port();
875 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
876 let (tx, rx) = crate::event::channel();
877
878 let config = make_config(port);
879 let _writer = start(config, tx).unwrap();
880
881 let (mut server_stream, _) = listener.accept().unwrap();
882
883 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
885
886 let payloads: Vec<Vec<u8>> = (0..3)
888 .map(|i| (0..24).map(|j| j + i * 50).collect())
889 .collect();
890 for p in &payloads {
891 server_stream.write_all(&hdlc::frame(p)).unwrap();
892 }
893
894 for expected in &payloads {
896 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
897 match event {
898 Event::Frame { data, .. } => assert_eq!(&data, expected),
899 other => panic!("expected Frame, got {:?}", other),
900 }
901 }
902 }
903
904 #[test]
905 fn split_across_reads() {
906 let port = find_free_port();
907 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
908 let (tx, rx) = crate::event::channel();
909
910 let config = make_config(port);
911 let _writer = start(config, tx).unwrap();
912
913 let (mut server_stream, _) = listener.accept().unwrap();
914
915 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
917
918 let payload: Vec<u8> = (0..32).collect();
920 let framed = hdlc::frame(&payload);
921 let mid = framed.len() / 2;
922
923 server_stream.write_all(&framed[..mid]).unwrap();
924 server_stream.flush().unwrap();
925 thread::sleep(Duration::from_millis(50));
926 server_stream.write_all(&framed[mid..]).unwrap();
927 server_stream.flush().unwrap();
928
929 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
930 match event {
931 Event::Frame { data, .. } => assert_eq!(data, payload),
932 other => panic!("expected Frame, got {:?}", other),
933 }
934 }
935
936 #[test]
937 fn reconnect_on_close() {
938 let port = find_free_port();
939 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
940 listener.set_nonblocking(false).unwrap();
941 let (tx, rx) = crate::event::channel();
942
943 let config = make_config(port);
944 let _writer = start(config, tx).unwrap();
945
946 let (server_stream, _) = listener.accept().unwrap();
948
949 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
951
952 drop(server_stream);
953
954 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
956 assert!(matches!(event, Event::InterfaceDown(InterfaceId(1))));
957
958 let _server_stream2 = listener.accept().unwrap();
960
961 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
963 assert!(matches!(event, Event::InterfaceUp(InterfaceId(1), _, _)));
964 }
965
966 #[test]
967 fn socket_options() {
968 let port = find_free_port();
969 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
970
971 let stream = try_connect(&make_config(port), None).unwrap();
972 let _server = listener.accept().unwrap();
973
974 let fd = stream.as_raw_fd();
976 let mut val: libc::c_int = 0;
977 let mut len: libc::socklen_t = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
978 unsafe {
979 libc::getsockopt(
980 fd,
981 libc::IPPROTO_TCP,
982 libc::TCP_NODELAY,
983 &mut val as *mut _ as *mut libc::c_void,
984 &mut len,
985 );
986 }
987 assert_eq!(val, 1, "TCP_NODELAY should be 1");
988 }
989
990 #[test]
991 fn connect_timeout() {
992 let config = TcpClientConfig {
994 name: "timeout-test".into(),
995 target_host: "192.0.2.1".into(), target_port: 12345,
997 interface_id: InterfaceId(99),
998 reconnect_wait: Duration::from_millis(100),
999 max_reconnect_tries: Some(0),
1000 connect_timeout: Duration::from_millis(500),
1001 device: None,
1002 runtime: Arc::new(Mutex::new(TcpClientRuntime {
1003 target_host: "192.0.2.1".into(),
1004 target_port: 12345,
1005 reconnect_wait: Duration::from_millis(100),
1006 max_reconnect_tries: Some(0),
1007 connect_timeout: Duration::from_millis(500),
1008 })),
1009 };
1010
1011 let start_time = std::time::Instant::now();
1012 let result = try_connect(&config, None);
1013 let elapsed = start_time.elapsed();
1014
1015 assert!(result.is_err());
1016 assert!(elapsed < Duration::from_secs(5));
1018 }
1019}