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