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