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) -> 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 let Some(ref device) = config.device {
199 connect_with_device(&addr, device, runtime.connect_timeout)?
200 } else {
201 TcpStream::connect_timeout(&addr, runtime.connect_timeout)?
202 };
203 #[cfg(not(target_os = "linux"))]
204 let stream = TcpStream::connect_timeout(&addr, runtime.connect_timeout)?;
205 set_socket_options(&stream)?;
206 Ok(stream)
207}
208
209#[cfg(target_os = "linux")]
211fn connect_with_device(
212 addr: &std::net::SocketAddr,
213 device: &str,
214 timeout: Duration,
215) -> io::Result<TcpStream> {
216 use std::os::unix::io::{FromRawFd, RawFd};
217
218 let domain = if addr.is_ipv4() {
219 libc::AF_INET
220 } else {
221 libc::AF_INET6
222 };
223 let fd: RawFd = unsafe { libc::socket(domain, libc::SOCK_STREAM, 0) };
224 if fd < 0 {
225 return Err(io::Error::last_os_error());
226 }
227
228 let stream = unsafe { TcpStream::from_raw_fd(fd) };
230
231 super::bind_to_device(stream.as_raw_fd(), device)?;
232
233 stream.set_nonblocking(true)?;
235
236 let (sockaddr, socklen) = socket_addr_to_raw(addr);
237 let ret = unsafe {
238 libc::connect(
239 stream.as_raw_fd(),
240 &sockaddr as *const libc::sockaddr_storage as *const libc::sockaddr,
241 socklen,
242 )
243 };
244
245 if ret != 0 {
246 let err = io::Error::last_os_error();
247 if err.raw_os_error() != Some(libc::EINPROGRESS) {
248 return Err(err);
249 }
250 }
251
252 let mut pollfd = libc::pollfd {
254 fd: stream.as_raw_fd(),
255 events: libc::POLLOUT,
256 revents: 0,
257 };
258 let timeout_ms = timeout.as_millis() as libc::c_int;
259 let poll_ret = unsafe { libc::poll(&mut pollfd, 1, timeout_ms) };
260
261 if poll_ret == 0 {
262 return Err(io::Error::new(io::ErrorKind::TimedOut, "connect timed out"));
263 }
264 if poll_ret < 0 {
265 return Err(io::Error::last_os_error());
266 }
267
268 let mut err_val: libc::c_int = 0;
270 let mut err_len: libc::socklen_t = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
271 let ret = unsafe {
272 libc::getsockopt(
273 stream.as_raw_fd(),
274 libc::SOL_SOCKET,
275 libc::SO_ERROR,
276 &mut err_val as *mut _ as *mut libc::c_void,
277 &mut err_len,
278 )
279 };
280 if ret != 0 {
281 return Err(io::Error::last_os_error());
282 }
283 if err_val != 0 {
284 return Err(io::Error::from_raw_os_error(err_val));
285 }
286
287 stream.set_nonblocking(false)?;
289
290 Ok(stream)
291}
292
293#[cfg(target_os = "linux")]
295fn socket_addr_to_raw(addr: &std::net::SocketAddr) -> (libc::sockaddr_storage, libc::socklen_t) {
296 let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
297 match addr {
298 std::net::SocketAddr::V4(v4) => {
299 let sin: &mut libc::sockaddr_in = unsafe {
300 &mut *(&mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_in)
301 };
302 sin.sin_family = libc::AF_INET as libc::sa_family_t;
303 sin.sin_port = v4.port().to_be();
304 sin.sin_addr = libc::in_addr {
305 s_addr: u32::from_ne_bytes(v4.ip().octets()),
306 };
307 (
308 storage,
309 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
310 )
311 }
312 std::net::SocketAddr::V6(v6) => {
313 let sin6: &mut libc::sockaddr_in6 = unsafe {
314 &mut *(&mut storage as *mut libc::sockaddr_storage as *mut libc::sockaddr_in6)
315 };
316 sin6.sin6_family = libc::AF_INET6 as libc::sa_family_t;
317 sin6.sin6_port = v6.port().to_be();
318 sin6.sin6_addr = libc::in6_addr {
319 s6_addr: v6.ip().octets(),
320 };
321 sin6.sin6_flowinfo = v6.flowinfo();
322 sin6.sin6_scope_id = v6.scope_id();
323 (
324 storage,
325 std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t,
326 )
327 }
328 }
329}
330
331pub fn start(config: TcpClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
333 start_with_ifac(config, tx, 0)
334}
335
336fn start_with_ifac(
337 config: TcpClientConfig,
338 tx: EventSender,
339 ifac_size: usize,
340) -> io::Result<Box<dyn Writer>> {
341 let stream = try_connect(&config)?;
342 let reader_stream = stream.try_clone()?;
343 let writer_stream = stream.try_clone()?;
344
345 let id = config.interface_id;
346 let _ = tx.send(Event::InterfaceUp(id, None, None));
348
349 let reader_config = config;
351 let reader_tx = tx;
352 thread::Builder::new()
353 .name(format!("tcp-reader-{}", id.0))
354 .spawn(move || {
355 reader_loop(reader_stream, reader_config, reader_tx, ifac_size);
356 })?;
357
358 Ok(Box::new(TcpWriter {
359 stream: writer_stream,
360 }))
361}
362
363fn reader_loop(mut stream: TcpStream, config: TcpClientConfig, tx: EventSender, ifac_size: usize) {
366 let id = config.interface_id;
367 let mut decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
368 let mut buf = [0u8; 4096];
369
370 loop {
371 match stream.read(&mut buf) {
372 Ok(0) => {
373 log::warn!("[{}] connection closed", config.name);
375 let _ = tx.send(Event::InterfaceDown(id));
376 match reconnect(&config, &tx) {
377 Some(new_stream) => {
378 stream = new_stream;
379 decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
380 continue;
381 }
382 None => {
383 log::error!("[{}] reconnection failed, giving up", config.name);
384 return;
385 }
386 }
387 }
388 Ok(n) => {
389 let decoded = decoder.feed_with_diagnostics(&buf[..n]);
390 for frame_len in decoded.invalid_frame_lengths {
391 log::debug!(
392 "[{}] invalid HDLC frame of {} bytes received, dropping frame",
393 config.name,
394 frame_len,
395 );
396 }
397 for frame in decoded.frames {
398 if tx
399 .send(Event::Frame {
400 interface_id: id,
401 data: frame,
402 rssi: None,
403 snr: None,
404 })
405 .is_err()
406 {
407 return;
409 }
410 }
411 }
412 Err(e) => {
413 log::warn!("[{}] read error: {}", config.name, e);
414 let _ = tx.send(Event::InterfaceDown(id));
415 match reconnect(&config, &tx) {
416 Some(new_stream) => {
417 stream = new_stream;
418 decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
419 continue;
420 }
421 None => {
422 log::error!("[{}] reconnection failed, giving up", config.name);
423 return;
424 }
425 }
426 }
427 }
428 }
429}
430
431fn reconnect(config: &TcpClientConfig, tx: &EventSender) -> Option<TcpStream> {
434 let mut attempts = 0u32;
435 loop {
436 let runtime = lock_or_recover(&config.runtime, "tcp client runtime").clone();
437 thread::sleep(runtime.reconnect_wait);
438 attempts += 1;
439
440 if let Some(max) = runtime.max_reconnect_tries {
441 if attempts > max {
442 let _ = tx.send(Event::InterfaceDown(config.interface_id));
443 return None;
444 }
445 }
446
447 log::info!("[{}] reconnect attempt {} ...", config.name, attempts);
448
449 match try_connect(config) {
450 Ok(new_stream) => {
451 let writer_stream = match new_stream.try_clone() {
453 Ok(s) => s,
454 Err(e) => {
455 log::warn!("[{}] failed to clone stream: {}", config.name, e);
456 continue;
457 }
458 };
459 log::info!("[{}] reconnected", config.name);
460 let new_writer: Box<dyn Writer> = Box::new(TcpWriter {
462 stream: writer_stream,
463 });
464 let _ = tx.send(Event::InterfaceUp(
465 config.interface_id,
466 Some(new_writer),
467 None,
468 ));
469 return Some(new_stream);
470 }
471 Err(e) => {
472 log::warn!("[{}] reconnect failed: {}", config.name, e);
473 }
474 }
475 }
476}
477
478use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
481use rns_core::transport::types::InterfaceInfo;
482use std::collections::HashMap;
483
484pub struct TcpClientFactory;
486
487impl InterfaceFactory for TcpClientFactory {
488 fn type_name(&self) -> &str {
489 "TCPClientInterface"
490 }
491
492 fn parse_config(
493 &self,
494 name: &str,
495 id: InterfaceId,
496 params: &HashMap<String, String>,
497 ) -> Result<Box<dyn InterfaceConfigData>, String> {
498 let target_host = params
499 .get("target_host")
500 .cloned()
501 .unwrap_or_else(|| "127.0.0.1".into());
502 let target_port = params
503 .get("target_port")
504 .and_then(|v| v.parse().ok())
505 .unwrap_or(4242);
506
507 Ok(Box::new(TcpClientConfig {
508 name: name.to_string(),
509 target_host,
510 target_port,
511 interface_id: id,
512 device: params.get("device").cloned(),
513 ..TcpClientConfig::default()
514 }))
515 }
516
517 fn start(
518 &self,
519 config: Box<dyn InterfaceConfigData>,
520 ctx: StartContext,
521 ) -> io::Result<StartResult> {
522 let tcp_config = *config
523 .into_any()
524 .downcast::<TcpClientConfig>()
525 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "wrong config type"))?;
526
527 let id = tcp_config.interface_id;
528 let name = tcp_config.name.clone();
529 let info = InterfaceInfo {
530 id,
531 name,
532 mode: ctx.mode,
533 gravity: ctx.gravity,
534 recursive_prs: ctx.recursive_prs,
535 announces_from_internal: ctx.announces_from_internal,
536 announces_to_internal: ctx.announces_to_internal,
537 out_capable: true,
538 in_capable: true,
539 bitrate: None,
540 airtime_profile: None,
541 announce_rate_target: None,
542 announce_rate_grace: 0,
543 announce_rate_penalty: 0.0,
544 announce_cap: rns_core::constants::ANNOUNCE_CAP,
545 is_local_client: false,
546 wants_tunnel: false,
547 tunnel_id: None,
548 mtu: 65535,
549 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
550 ia_freq: 0.0,
551 ip_freq: 0.0,
552 op_freq: 0.0,
553 op_samples: 0,
554 started: crate::time::now(),
555 };
556
557 let ifac_size = ctx.ifac.as_ref().map(|ifac| ifac.size).unwrap_or(0);
558 let writer = start_with_ifac(tcp_config, ctx.tx, ifac_size)?;
559
560 Ok(StartResult::Simple {
561 id,
562 info,
563 writer,
564 interface_type_name: "TCPClientInterface".to_string(),
565 })
566 }
567}
568
569pub(crate) fn tcp_client_runtime_handle_from_config(
570 config: &TcpClientConfig,
571) -> TcpClientRuntimeConfigHandle {
572 TcpClientRuntimeConfigHandle {
573 interface_name: config.name.clone(),
574 runtime: Arc::clone(&config.runtime),
575 startup: TcpClientRuntime::from_config(config),
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 use std::net::TcpListener;
583 use std::time::Duration;
584
585 fn find_free_port() -> u16 {
586 TcpListener::bind("127.0.0.1:0")
587 .unwrap()
588 .local_addr()
589 .unwrap()
590 .port()
591 }
592
593 fn make_config(port: u16) -> TcpClientConfig {
594 TcpClientConfig {
595 name: format!("test-tcp-{}", port),
596 target_host: "127.0.0.1".into(),
597 target_port: port,
598 interface_id: InterfaceId(1),
599 reconnect_wait: Duration::from_millis(100),
600 max_reconnect_tries: Some(2),
601 connect_timeout: Duration::from_secs(2),
602 runtime: Arc::new(Mutex::new(TcpClientRuntime {
603 target_host: "127.0.0.1".into(),
604 target_port: port,
605 reconnect_wait: Duration::from_millis(100),
606 max_reconnect_tries: Some(2),
607 connect_timeout: Duration::from_secs(2),
608 })),
609 device: None,
610 }
611 }
612
613 #[test]
614 fn connect_to_listener() {
615 let port = find_free_port();
616 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
617 let (tx, rx) = crate::event::channel();
618
619 let config = make_config(port);
620 let _writer = start(config, tx).unwrap();
621
622 let _server_stream = listener.accept().unwrap();
624
625 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
627 assert!(matches!(event, Event::InterfaceUp(InterfaceId(1), _, _)));
628 }
629
630 #[test]
631 fn receive_frame() {
632 let port = find_free_port();
633 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
634 let (tx, rx) = crate::event::channel();
635
636 let config = make_config(port);
637 let _writer = start(config, tx).unwrap();
638
639 let (mut server_stream, _) = listener.accept().unwrap();
640
641 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
643
644 let payload: Vec<u8> = (0..32).collect();
646 let framed = hdlc::frame(&payload);
647 server_stream.write_all(&framed).unwrap();
648
649 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
651 match event {
652 Event::Frame {
653 interface_id,
654 data,
655 rssi: _,
656 snr: _,
657 } => {
658 assert_eq!(interface_id, InterfaceId(1));
659 assert_eq!(data, payload);
660 }
661 other => panic!("expected Frame, got {:?}", other),
662 }
663 }
664
665 #[test]
666 fn reader_enforces_upstream_frame_bounds_and_ifac_allowance() {
667 let port = find_free_port();
668 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
669 let (tx, rx) = crate::event::channel();
670 let ifac_size = 8;
671
672 let config = make_config(port);
673 let _writer = start_with_ifac(config, tx, ifac_size).unwrap();
674 let (mut server_stream, _) = listener.accept().unwrap();
675 assert!(matches!(
676 rx.recv_timeout(Duration::from_secs(1)).unwrap(),
677 Event::InterfaceUp(_, _, _)
678 ));
679
680 server_stream
683 .write_all(&hdlc::frame(&vec![
684 0x11;
685 rns_core::constants::HEADER_MINSIZE
686 ]))
687 .unwrap();
688 let minimum_valid = vec![0x22; rns_core::constants::HEADER_MINSIZE + 1];
689 server_stream
690 .write_all(&hdlc::frame(&minimum_valid))
691 .unwrap();
692 assert!(matches!(
693 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
694 Event::Frame { data, .. } if data == minimum_valid
695 ));
696
697 let maximum_valid = vec![0x33; HW_MTU + ifac_size];
699 server_stream
700 .write_all(&hdlc::frame(&maximum_valid))
701 .unwrap();
702 assert!(matches!(
703 rx.recv_timeout(Duration::from_secs(5)).unwrap(),
704 Event::Frame { data, .. } if data == maximum_valid
705 ));
706
707 server_stream
709 .write_all(&hdlc::frame(&vec![0x44; HW_MTU + ifac_size + 1]))
710 .unwrap();
711 let recovery = vec![0x55; rns_core::constants::HEADER_MINSIZE + 1];
712 server_stream.write_all(&hdlc::frame(&recovery)).unwrap();
713 assert!(matches!(
714 rx.recv_timeout(Duration::from_secs(5)).unwrap(),
715 Event::Frame { data, .. } if data == recovery
716 ));
717 }
718
719 #[test]
720 fn send_frame() {
721 let port = find_free_port();
722 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
723 let (tx, _rx) = crate::event::channel();
724
725 let config = make_config(port);
726 let mut writer = start(config, tx).unwrap();
727
728 let (mut server_stream, _) = listener.accept().unwrap();
729 server_stream
730 .set_read_timeout(Some(Duration::from_secs(2)))
731 .unwrap();
732
733 let payload: Vec<u8> = (0..24).collect();
735 writer.send_frame(&payload).unwrap();
736
737 let mut buf = [0u8; 256];
739 let n = server_stream.read(&mut buf).unwrap();
740 let expected = hdlc::frame(&payload);
741 assert_eq!(&buf[..n], &expected[..]);
742 }
743
744 #[test]
745 fn multiple_frames() {
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 (mut server_stream, _) = listener.accept().unwrap();
754
755 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
757
758 let payloads: Vec<Vec<u8>> = (0..3)
760 .map(|i| (0..24).map(|j| j + i * 50).collect())
761 .collect();
762 for p in &payloads {
763 server_stream.write_all(&hdlc::frame(p)).unwrap();
764 }
765
766 for expected in &payloads {
768 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
769 match event {
770 Event::Frame { data, .. } => assert_eq!(&data, expected),
771 other => panic!("expected Frame, got {:?}", other),
772 }
773 }
774 }
775
776 #[test]
777 fn split_across_reads() {
778 let port = find_free_port();
779 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
780 let (tx, rx) = crate::event::channel();
781
782 let config = make_config(port);
783 let _writer = start(config, tx).unwrap();
784
785 let (mut server_stream, _) = listener.accept().unwrap();
786
787 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
789
790 let payload: Vec<u8> = (0..32).collect();
792 let framed = hdlc::frame(&payload);
793 let mid = framed.len() / 2;
794
795 server_stream.write_all(&framed[..mid]).unwrap();
796 server_stream.flush().unwrap();
797 thread::sleep(Duration::from_millis(50));
798 server_stream.write_all(&framed[mid..]).unwrap();
799 server_stream.flush().unwrap();
800
801 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
802 match event {
803 Event::Frame { data, .. } => assert_eq!(data, payload),
804 other => panic!("expected Frame, got {:?}", other),
805 }
806 }
807
808 #[test]
809 fn reconnect_on_close() {
810 let port = find_free_port();
811 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
812 listener.set_nonblocking(false).unwrap();
813 let (tx, rx) = crate::event::channel();
814
815 let config = make_config(port);
816 let _writer = start(config, tx).unwrap();
817
818 let (server_stream, _) = listener.accept().unwrap();
820
821 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
823
824 drop(server_stream);
825
826 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
828 assert!(matches!(event, Event::InterfaceDown(InterfaceId(1))));
829
830 let _server_stream2 = listener.accept().unwrap();
832
833 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
835 assert!(matches!(event, Event::InterfaceUp(InterfaceId(1), _, _)));
836 }
837
838 #[test]
839 fn socket_options() {
840 let port = find_free_port();
841 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
842
843 let stream = try_connect(&make_config(port)).unwrap();
844 let _server = listener.accept().unwrap();
845
846 let fd = stream.as_raw_fd();
848 let mut val: libc::c_int = 0;
849 let mut len: libc::socklen_t = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
850 unsafe {
851 libc::getsockopt(
852 fd,
853 libc::IPPROTO_TCP,
854 libc::TCP_NODELAY,
855 &mut val as *mut _ as *mut libc::c_void,
856 &mut len,
857 );
858 }
859 assert_eq!(val, 1, "TCP_NODELAY should be 1");
860 }
861
862 #[test]
863 fn connect_timeout() {
864 let config = TcpClientConfig {
866 name: "timeout-test".into(),
867 target_host: "192.0.2.1".into(), target_port: 12345,
869 interface_id: InterfaceId(99),
870 reconnect_wait: Duration::from_millis(100),
871 max_reconnect_tries: Some(0),
872 connect_timeout: Duration::from_millis(500),
873 device: None,
874 runtime: Arc::new(Mutex::new(TcpClientRuntime {
875 target_host: "192.0.2.1".into(),
876 target_port: 12345,
877 reconnect_wait: Duration::from_millis(100),
878 max_reconnect_tries: Some(0),
879 connect_timeout: Duration::from_millis(500),
880 })),
881 ..TcpClientConfig::default()
882 };
883
884 let start_time = std::time::Instant::now();
885 let result = try_connect(&config);
886 let elapsed = start_time.elapsed();
887
888 assert!(result.is_err());
889 assert!(elapsed < Duration::from_secs(5));
891 }
892}