1use std::io::{self, Read, Write};
12use std::net::{TcpListener, TcpStream};
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Mutex};
15use std::thread;
16use std::time::{Duration, Instant};
17
18use rns_core::constants;
19use rns_core::transport::types::{InterfaceId, InterfaceInfo};
20
21use crate::event::{Event, EventSender};
22use crate::hdlc;
23use crate::interface::{ListenerControl, Writer};
24
25#[cfg(target_os = "android")]
26const CLIENT_SLEEP_PAUSE_TIMEOUT: Duration = Duration::from_secs(12);
27#[cfg(target_os = "android")]
28const PHY_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(5);
29
30#[derive(Debug, Clone)]
32pub struct LocalServerConfig {
33 pub instance_name: String,
34 pub port: u16,
35 pub interface_id: InterfaceId,
36}
37
38impl Default for LocalServerConfig {
39 fn default() -> Self {
40 LocalServerConfig {
41 instance_name: "default".into(),
42 port: 37428,
43 interface_id: InterfaceId(0),
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct LocalClientConfig {
51 pub name: String,
52 pub instance_name: String,
53 pub port: u16,
54 pub interface_id: InterfaceId,
55 pub reconnect_wait: Duration,
56}
57
58impl Default for LocalClientConfig {
59 fn default() -> Self {
60 LocalClientConfig {
61 name: "Local shared instance".into(),
62 instance_name: "default".into(),
63 port: 37428,
64 interface_id: InterfaceId(0),
65 reconnect_wait: Duration::from_secs(8),
66 }
67 }
68}
69
70struct LocalWriter {
72 stream: TcpStream,
73 sleep_hold: Option<ClientSleepHold>,
74}
75
76impl Writer for LocalWriter {
77 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
78 if self
79 .sleep_hold
80 .as_ref()
81 .is_some_and(ClientSleepHold::should_drop_outbound)
82 {
83 log::debug!("TX paused for LocalInterface client, dropping outbound packet");
84 return Ok(());
85 }
86 self.stream.write_all(&hdlc::frame(data))
87 }
88}
89
90#[derive(Clone)]
91struct ClientSleepHold {
92 timeout: Duration,
93 deadline: Arc<Mutex<Instant>>,
94}
95
96impl ClientSleepHold {
97 #[cfg_attr(not(any(target_os = "android", test)), allow(dead_code))]
98 fn new(timeout: Duration) -> Self {
99 Self {
100 timeout,
101 deadline: Arc::new(Mutex::new(Instant::now() + timeout)),
102 }
103 }
104
105 fn refresh(&self) {
106 *lock_or_recover(&self.deadline) = Instant::now() + self.timeout;
107 }
108
109 fn should_drop_outbound(&self) -> bool {
110 Instant::now() > *lock_or_recover(&self.deadline)
111 }
112}
113
114fn lock_or_recover<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
115 match mutex.lock() {
116 Ok(guard) => guard,
117 Err(poisoned) => poisoned.into_inner(),
118 }
119}
120
121fn android_client_sleep_hold() -> Option<ClientSleepHold> {
122 #[cfg(target_os = "android")]
123 {
124 Some(ClientSleepHold::new(CLIENT_SLEEP_PAUSE_TIMEOUT))
125 }
126 #[cfg(not(target_os = "android"))]
127 {
128 None
129 }
130}
131
132#[cfg_attr(not(any(target_os = "android", test)), allow(dead_code))]
133fn spawn_physical_keepalive_loop(
134 mut writer: Box<dyn Writer>,
135 interface_id: InterfaceId,
136 interface_name: String,
137 interval: Duration,
138) {
139 thread::Builder::new()
140 .name(format!("local-phy-keepalive-{}", interface_id.0))
141 .spawn(move || loop {
142 thread::sleep(interval);
143 if let Err(err) = writer.send_frame(&[]) {
144 log::debug!(
145 "[{}:{}] LocalInterface physical keepalive stopped: {}",
146 interface_name,
147 interface_id.0,
148 err
149 );
150 return;
151 }
152 })
153 .ok();
154}
155
156fn maybe_spawn_local_client_phy_keepalive(
157 stream: &LocalClientStream,
158 interface_id: InterfaceId,
159 interface_name: &str,
160) -> io::Result<()> {
161 #[cfg(target_os = "android")]
162 {
163 let writer = local_client_stream_writer(stream)?;
164 spawn_physical_keepalive_loop(
165 writer,
166 interface_id,
167 interface_name.to_string(),
168 PHY_KEEPALIVE_INTERVAL,
169 );
170 }
171
172 #[cfg(not(target_os = "android"))]
173 {
174 let _ = (stream, interface_id, interface_name);
175 }
176
177 Ok(())
178}
179
180#[cfg(target_os = "linux")]
181mod unix_socket {
182 use std::io;
183 use std::os::linux::net::SocketAddrExt;
184 use std::os::unix::net::{SocketAddr, UnixListener, UnixStream};
185
186 fn abstract_addr(instance_name: &str) -> io::Result<SocketAddr> {
187 SocketAddr::from_abstract_name(format!("rns/{}", instance_name))
188 }
189
190 pub fn try_bind_unix(instance_name: &str) -> io::Result<UnixListener> {
192 let addr = abstract_addr(instance_name)?;
193 UnixListener::bind_addr(&addr)
194 }
195
196 pub fn try_connect_unix(instance_name: &str) -> io::Result<UnixStream> {
198 let addr = abstract_addr(instance_name)?;
199 UnixStream::connect_addr(&addr)
200 }
201}
202
203pub fn start_server(
209 config: LocalServerConfig,
210 tx: EventSender,
211 next_id: Arc<AtomicU64>,
212) -> io::Result<ListenerControl> {
213 start_server_with_template(config, tx, next_id, None)
214}
215
216fn start_server_with_template(
217 config: LocalServerConfig,
218 tx: EventSender,
219 next_id: Arc<AtomicU64>,
220 dynamic_template: Option<super::DynamicInterfaceTemplate>,
221) -> io::Result<ListenerControl> {
222 let control = ListenerControl::new();
223 #[cfg(target_os = "linux")]
225 {
226 match unix_socket::try_bind_unix(&config.instance_name) {
227 Ok(listener) => {
228 listener.set_nonblocking(true)?;
229 log::info!(
230 "Local server using Unix socket: rns/{}",
231 config.instance_name
232 );
233 let name = format!("rns/{}", config.instance_name);
234 let listener_control = control.clone();
235 thread::Builder::new()
236 .name("local-server".into())
237 .spawn(move || {
238 unix_server_loop(
239 listener,
240 name,
241 tx,
242 next_id,
243 listener_control,
244 dynamic_template,
245 );
246 })?;
247 return Ok(control);
248 }
249 Err(e) => {
250 log::info!("Unix socket bind failed ({}), falling back to TCP", e);
251 }
252 }
253 }
254
255 let addr = format!("127.0.0.1:{}", config.port);
257 let listener = TcpListener::bind(&addr)?;
258 listener.set_nonblocking(true)?;
259
260 log::info!("Local server listening on TCP {}", addr);
261
262 let listener_control = control.clone();
263 thread::Builder::new()
264 .name("local-server".into())
265 .spawn(move || {
266 tcp_server_loop(listener, tx, next_id, listener_control, dynamic_template);
267 })?;
268
269 Ok(control)
270}
271
272fn tcp_server_loop(
274 listener: TcpListener,
275 tx: EventSender,
276 next_id: Arc<AtomicU64>,
277 control: ListenerControl,
278 dynamic_template: Option<super::DynamicInterfaceTemplate>,
279) {
280 loop {
281 if control.should_stop() {
282 log::info!("Local TCP listener stopping");
283 return;
284 }
285
286 let stream_result = listener.accept().map(|(stream, _)| stream);
287 let stream = match stream_result {
288 Ok(s) => s,
289 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
290 thread::sleep(Duration::from_millis(50));
291 continue;
292 }
293 Err(e) => {
294 log::warn!("Local server accept failed: {}", e);
295 continue;
296 }
297 };
298
299 if let Err(e) = stream.set_nodelay(true) {
300 log::warn!("Local server set_nodelay failed: {}", e);
301 }
302
303 let client_id = InterfaceId(next_id.fetch_add(1, Ordering::Relaxed));
304 spawn_local_client_handler(stream, client_id, tx.clone(), dynamic_template.as_ref());
305 }
306}
307
308#[cfg(target_os = "linux")]
310fn unix_server_loop(
311 listener: std::os::unix::net::UnixListener,
312 name: String,
313 tx: EventSender,
314 next_id: Arc<AtomicU64>,
315 control: ListenerControl,
316 dynamic_template: Option<super::DynamicInterfaceTemplate>,
317) {
318 loop {
319 if control.should_stop() {
320 log::info!("[{}] Local Unix listener stopping", name);
321 return;
322 }
323
324 let stream_result = listener.accept().map(|(stream, _)| stream);
325 let stream = match stream_result {
326 Ok(s) => s,
327 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
328 thread::sleep(Duration::from_millis(50));
329 continue;
330 }
331 Err(e) => {
332 log::warn!("[{}] Local server accept failed: {}", name, e);
333 continue;
334 }
335 };
336
337 let client_id = InterfaceId(next_id.fetch_add(1, Ordering::Relaxed));
338
339 let writer_stream = match stream.try_clone() {
341 Ok(s) => s,
342 Err(e) => {
343 log::warn!("Local server clone failed: {}", e);
344 continue;
345 }
346 };
347
348 let sleep_hold = android_client_sleep_hold();
349 let info = make_local_interface_info(client_id);
350 let writer: Box<dyn Writer> = Box::new(UnixLocalWriter {
351 stream: writer_stream,
352 sleep_hold: sleep_hold.clone(),
353 });
354
355 let event = if let Some(template) = &dynamic_template {
356 Event::DynamicInterfaceUp {
357 id: client_id,
358 writer,
359 registration: template.registration(info),
360 }
361 } else {
362 Event::InterfaceUp(client_id, Some(writer), Some(info))
363 };
364 if tx.send(event).is_err() {
365 return;
366 }
367
368 let client_tx = tx.clone();
369 thread::Builder::new()
370 .name(format!("local-unix-reader-{}", client_id.0))
371 .spawn(move || {
372 unix_reader_loop(stream, client_id, client_tx, sleep_hold);
373 })
374 .ok();
375 }
376}
377
378#[cfg(target_os = "linux")]
379struct UnixLocalWriter {
380 stream: std::os::unix::net::UnixStream,
381 sleep_hold: Option<ClientSleepHold>,
382}
383
384#[cfg(target_os = "linux")]
385impl Writer for UnixLocalWriter {
386 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
387 use std::io::Write;
388 if self
389 .sleep_hold
390 .as_ref()
391 .is_some_and(ClientSleepHold::should_drop_outbound)
392 {
393 log::debug!("TX paused for LocalInterface client, dropping outbound packet");
394 return Ok(());
395 }
396 self.stream.write_all(&hdlc::frame(data))
397 }
398}
399
400#[cfg(target_os = "linux")]
401fn unix_reader_loop(
402 mut stream: std::os::unix::net::UnixStream,
403 id: InterfaceId,
404 tx: EventSender,
405 sleep_hold: Option<ClientSleepHold>,
406) {
407 use std::io::Read;
408 let mut decoder = hdlc::Decoder::new();
409 let mut buf = [0u8; 4096];
410
411 loop {
412 match stream.read(&mut buf) {
413 Ok(0) => {
414 let _ = tx.send(Event::InterfaceDown(id));
415 return;
416 }
417 Ok(n) => {
418 if let Some(ref sleep_hold) = sleep_hold {
419 sleep_hold.refresh();
420 }
421 for frame in decoder.feed(&buf[..n]) {
422 if tx
423 .send(Event::Frame {
424 interface_id: id,
425 data: frame,
426 rssi: None,
427 snr: None,
428 })
429 .is_err()
430 {
431 return;
432 }
433 }
434 }
435 Err(_) => {
436 let _ = tx.send(Event::InterfaceDown(id));
437 return;
438 }
439 }
440 }
441}
442
443fn spawn_local_client_handler(
445 stream: TcpStream,
446 client_id: InterfaceId,
447 tx: EventSender,
448 dynamic_template: Option<&super::DynamicInterfaceTemplate>,
449) {
450 let writer_stream = match stream.try_clone() {
451 Ok(s) => s,
452 Err(e) => {
453 log::warn!("Local server clone failed: {}", e);
454 return;
455 }
456 };
457
458 let sleep_hold = android_client_sleep_hold();
459 let info = make_local_interface_info(client_id);
460 let writer: Box<dyn Writer> = Box::new(LocalWriter {
461 stream: writer_stream,
462 sleep_hold: sleep_hold.clone(),
463 });
464
465 let event = if let Some(template) = dynamic_template {
466 Event::DynamicInterfaceUp {
467 id: client_id,
468 writer,
469 registration: template.registration(info),
470 }
471 } else {
472 Event::InterfaceUp(client_id, Some(writer), Some(info))
473 };
474 if tx.send(event).is_err() {
475 return;
476 }
477
478 thread::Builder::new()
479 .name(format!("local-reader-{}", client_id.0))
480 .spawn(move || {
481 tcp_reader_loop(stream, client_id, tx, sleep_hold);
482 })
483 .ok();
484}
485
486fn tcp_reader_loop(
487 mut stream: TcpStream,
488 id: InterfaceId,
489 tx: EventSender,
490 sleep_hold: Option<ClientSleepHold>,
491) {
492 let mut decoder = hdlc::Decoder::new();
493 let mut buf = [0u8; 4096];
494
495 loop {
496 match stream.read(&mut buf) {
497 Ok(0) => {
498 log::info!("Local client {} disconnected", id.0);
499 let _ = tx.send(Event::InterfaceDown(id));
500 return;
501 }
502 Ok(n) => {
503 if let Some(ref sleep_hold) = sleep_hold {
504 sleep_hold.refresh();
505 }
506 for frame in decoder.feed(&buf[..n]) {
507 if tx
508 .send(Event::Frame {
509 interface_id: id,
510 data: frame,
511 rssi: None,
512 snr: None,
513 })
514 .is_err()
515 {
516 return;
517 }
518 }
519 }
520 Err(e) => {
521 log::warn!("Local client {} read error: {}", id.0, e);
522 let _ = tx.send(Event::InterfaceDown(id));
523 return;
524 }
525 }
526 }
527}
528
529fn make_local_interface_info(id: InterfaceId) -> InterfaceInfo {
530 InterfaceInfo {
531 id,
532 name: String::from("LocalInterface"),
533 mode: constants::MODE_FULL,
534 gravity: 0,
535 recursive_prs: false,
536 announces_from_internal: true,
537 announces_to_internal: None,
538 out_capable: true,
539 in_capable: true,
540 bitrate: Some(1_000_000_000), airtime_profile: None,
542 announce_rate_target: None,
543 announce_rate_grace: 0,
544 announce_rate_penalty: 0.0,
545 announce_cap: constants::ANNOUNCE_CAP,
546 is_local_client: true,
547 wants_tunnel: false,
548 tunnel_id: None,
549 mtu: 65535,
550 ia_freq: 0.0,
551 ip_freq: 0.0,
552 op_freq: 0.0,
553 op_samples: 0,
554 started: 0.0,
555 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
556 }
557}
558
559#[cfg(target_os = "linux")]
562enum LocalClientStream {
563 Unix(std::os::unix::net::UnixStream),
564 Tcp(TcpStream),
565}
566
567#[cfg(target_os = "linux")]
568impl LocalClientStream {
569 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
570 match self {
571 LocalClientStream::Unix(stream) => stream.read(buf),
572 LocalClientStream::Tcp(stream) => stream.read(buf),
573 }
574 }
575
576 fn writer(&self) -> io::Result<Box<dyn Writer>> {
577 match self {
578 LocalClientStream::Unix(stream) => Ok(Box::new(UnixLocalWriter {
579 stream: stream.try_clone()?,
580 sleep_hold: None,
581 })),
582 LocalClientStream::Tcp(stream) => Ok(Box::new(LocalWriter {
583 stream: stream.try_clone()?,
584 sleep_hold: None,
585 })),
586 }
587 }
588}
589
590#[cfg(not(target_os = "linux"))]
591type LocalClientStream = TcpStream;
592
593#[cfg(not(target_os = "linux"))]
594fn local_client_stream_writer(stream: &LocalClientStream) -> io::Result<Box<dyn Writer>> {
595 Ok(Box::new(LocalWriter {
596 stream: stream.try_clone()?,
597 sleep_hold: None,
598 }))
599}
600
601#[cfg(target_os = "linux")]
602fn local_client_stream_writer(stream: &LocalClientStream) -> io::Result<Box<dyn Writer>> {
603 stream.writer()
604}
605
606fn try_connect_tcp(config: &LocalClientConfig) -> io::Result<TcpStream> {
607 let addr = format!("127.0.0.1:{}", config.port);
608 let stream = TcpStream::connect(&addr)?;
609 stream.set_nodelay(true)?;
610 log::info!(
611 "[{}] Connected to shared instance via TCP {}",
612 config.name,
613 addr
614 );
615 Ok(stream)
616}
617
618#[cfg(target_os = "linux")]
619fn try_connect_local_client(config: &LocalClientConfig) -> io::Result<LocalClientStream> {
620 match unix_socket::try_connect_unix(&config.instance_name) {
621 Ok(stream) => {
622 log::info!(
623 "[{}] Connected to shared instance via Unix socket: rns/{}",
624 config.name,
625 config.instance_name
626 );
627 Ok(LocalClientStream::Unix(stream))
628 }
629 Err(e) => {
630 log::info!(
631 "[{}] Unix socket connect failed ({}), trying TCP",
632 config.name,
633 e
634 );
635 try_connect_tcp(config).map(LocalClientStream::Tcp)
636 }
637 }
638}
639
640#[cfg(not(target_os = "linux"))]
641fn try_connect_local_client(config: &LocalClientConfig) -> io::Result<LocalClientStream> {
642 try_connect_tcp(config)
643}
644
645fn reconnect_local_client(config: &LocalClientConfig, tx: &EventSender) -> LocalClientStream {
646 loop {
647 thread::sleep(config.reconnect_wait);
648 match try_connect_local_client(config) {
649 Ok(stream) => match local_client_stream_writer(&stream) {
650 Ok(writer) => {
651 let _ = maybe_spawn_local_client_phy_keepalive(
652 &stream,
653 config.interface_id,
654 &config.name,
655 );
656 let _ = tx.send(Event::InterfaceUp(config.interface_id, Some(writer), None));
657 return stream;
658 }
659 Err(e) => {
660 log::warn!("[{}] failed to clone reconnect writer: {}", config.name, e);
661 }
662 },
663 Err(e) => {
664 log::warn!("[{}] reconnect failed: {}", config.name, e);
665 }
666 }
667 }
668}
669
670fn local_client_reader_loop(
671 mut stream: LocalClientStream,
672 config: LocalClientConfig,
673 tx: EventSender,
674) {
675 let id = config.interface_id;
676 let mut decoder = hdlc::Decoder::new();
677 let mut buf = [0u8; 4096];
678
679 loop {
680 match stream.read(&mut buf) {
681 Ok(0) => {
682 log::warn!("[{}] shared connection closed", config.name);
683 let _ = tx.send(Event::InterfaceDown(id));
684 stream = reconnect_local_client(&config, &tx);
685 decoder = hdlc::Decoder::new();
686 }
687 Ok(n) => {
688 for frame in decoder.feed(&buf[..n]) {
689 if tx
690 .send(Event::Frame {
691 interface_id: id,
692 data: frame,
693 rssi: None,
694 snr: None,
695 })
696 .is_err()
697 {
698 return;
699 }
700 }
701 }
702 Err(e) => {
703 log::warn!("[{}] shared read error: {}", config.name, e);
704 let _ = tx.send(Event::InterfaceDown(id));
705 stream = reconnect_local_client(&config, &tx);
706 decoder = hdlc::Decoder::new();
707 }
708 }
709 }
710}
711
712pub fn start_client(config: LocalClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
716 let id = config.interface_id;
717 let stream = try_connect_local_client(&config)?;
718 let writer = local_client_stream_writer(&stream)?;
719 maybe_spawn_local_client_phy_keepalive(&stream, id, &config.name)?;
720
721 let _ = tx.send(Event::InterfaceUp(id, None, None));
722
723 thread::Builder::new()
724 .name(format!("local-client-reader-{}", id.0))
725 .spawn(move || {
726 local_client_reader_loop(stream, config, tx);
727 })?;
728
729 Ok(writer)
730}
731
732use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
735use std::collections::HashMap;
736
737pub struct LocalServerFactory;
739
740impl InterfaceFactory for LocalServerFactory {
741 fn type_name(&self) -> &str {
742 "LocalServerInterface"
743 }
744
745 fn parse_config(
746 &self,
747 _name: &str,
748 id: InterfaceId,
749 params: &HashMap<String, String>,
750 ) -> Result<Box<dyn InterfaceConfigData>, String> {
751 let instance_name = params
752 .get("instance_name")
753 .cloned()
754 .unwrap_or_else(|| "default".into());
755 let port = params
756 .get("port")
757 .and_then(|v| v.parse().ok())
758 .unwrap_or(37428);
759
760 Ok(Box::new(LocalServerConfig {
761 instance_name,
762 port,
763 interface_id: id,
764 }))
765 }
766
767 fn start(
768 &self,
769 config: Box<dyn InterfaceConfigData>,
770 ctx: StartContext,
771 ) -> std::io::Result<StartResult> {
772 let server_config = *config
773 .into_any()
774 .downcast::<LocalServerConfig>()
775 .map_err(|_| {
776 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
777 })?;
778
779 let parent_id = server_config.interface_id;
780 let control = start_server_with_template(
781 server_config,
782 ctx.tx,
783 ctx.next_dynamic_id,
784 Some(super::DynamicInterfaceTemplate {
785 parent_id,
786 interface_type: "LocalClientInterface".into(),
787 ifac: ctx.ifac,
788 mode: ctx.mode,
789 gravity: ctx.gravity,
790 recursive_prs: ctx.recursive_prs,
791 announces_from_internal: ctx.announces_from_internal,
792 announces_to_internal: ctx.announces_to_internal,
793 }),
794 )?;
795 Ok(StartResult::Listener {
796 control: Some(control),
797 })
798 }
799}
800
801pub struct LocalClientFactory;
803
804impl InterfaceFactory for LocalClientFactory {
805 fn type_name(&self) -> &str {
806 "LocalClientInterface"
807 }
808
809 fn parse_config(
810 &self,
811 _name: &str,
812 id: InterfaceId,
813 params: &HashMap<String, String>,
814 ) -> Result<Box<dyn InterfaceConfigData>, String> {
815 let instance_name = params
816 .get("instance_name")
817 .cloned()
818 .unwrap_or_else(|| "default".into());
819 let port = params
820 .get("port")
821 .and_then(|v| v.parse().ok())
822 .unwrap_or(37428);
823
824 Ok(Box::new(LocalClientConfig {
825 instance_name,
826 port,
827 interface_id: id,
828 ..LocalClientConfig::default()
829 }))
830 }
831
832 fn start(
833 &self,
834 config: Box<dyn InterfaceConfigData>,
835 ctx: StartContext,
836 ) -> std::io::Result<StartResult> {
837 let client_config = *config
838 .into_any()
839 .downcast::<LocalClientConfig>()
840 .map_err(|_| {
841 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
842 })?;
843
844 let id = client_config.interface_id;
845 let name = client_config.name.clone();
846 let info = InterfaceInfo {
847 id,
848 name,
849 mode: ctx.mode,
850 gravity: ctx.gravity,
851 recursive_prs: ctx.recursive_prs,
852 announces_from_internal: ctx.announces_from_internal,
853 announces_to_internal: ctx.announces_to_internal,
854 out_capable: true,
855 in_capable: true,
856 bitrate: Some(1_000_000_000),
857 airtime_profile: None,
858 announce_rate_target: None,
859 announce_rate_grace: 0,
860 announce_rate_penalty: 0.0,
861 announce_cap: rns_core::constants::ANNOUNCE_CAP,
862 is_local_client: false,
863 wants_tunnel: false,
864 tunnel_id: None,
865 mtu: 65535,
866 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
867 ia_freq: 0.0,
868 ip_freq: 0.0,
869 op_freq: 0.0,
870 op_samples: 0,
871 started: crate::time::now(),
872 };
873
874 let writer = start_client(client_config, ctx.tx)?;
875
876 Ok(StartResult::Simple {
877 id,
878 info,
879 writer,
880 interface_type_name: "LocalInterface".to_string(),
881 })
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use super::*;
888 use std::sync::mpsc;
889 use std::sync::mpsc::RecvTimeoutError;
890
891 #[cfg(target_os = "linux")]
892 type TestClient = std::os::unix::net::UnixStream;
893
894 #[cfg(not(target_os = "linux"))]
895 type TestClient = TcpStream;
896
897 fn connect_test_client(instance_name: &str, _port: u16) -> TestClient {
898 #[cfg(target_os = "linux")]
899 {
900 unix_socket::try_connect_unix(instance_name).unwrap()
901 }
902
903 #[cfg(not(target_os = "linux"))]
904 {
905 TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap()
906 }
907 }
908
909 fn find_free_port() -> u16 {
910 TcpListener::bind("127.0.0.1:0")
911 .unwrap()
912 .local_addr()
913 .unwrap()
914 .port()
915 }
916
917 #[test]
918 fn server_bind_tcp() {
919 let port = find_free_port();
920 let instance_name = "test-bind".to_string();
921 let (tx, _rx) = crate::event::channel();
922 let next_id = Arc::new(AtomicU64::new(7000));
923
924 let config = LocalServerConfig {
925 instance_name: instance_name.clone(),
926 port,
927 interface_id: InterfaceId(70),
928 };
929
930 start_server(config, tx, next_id).unwrap();
933 thread::sleep(Duration::from_millis(50));
934
935 connect_test_client(&instance_name, port);
936 }
937
938 #[test]
939 fn server_accept_client() {
940 let port = find_free_port();
941 let instance_name = "test-accept".to_string();
942 let (tx, rx) = crate::event::channel();
943 let next_id = Arc::new(AtomicU64::new(7100));
944
945 let config = LocalServerConfig {
946 instance_name: instance_name.clone(),
947 port,
948 interface_id: InterfaceId(71),
949 };
950
951 start_server(config, tx, next_id).unwrap();
952 thread::sleep(Duration::from_millis(50));
953
954 connect_test_client(&instance_name, port);
955
956 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
957 match event {
958 Event::InterfaceUp(id, writer, info) => {
959 assert_eq!(id, InterfaceId(7100));
960 assert!(writer.is_some());
961 assert!(info.is_some());
962 }
963 other => panic!("expected InterfaceUp, got {:?}", other),
964 }
965 }
966
967 #[test]
968 fn server_stop_prevents_new_accepts() {
969 let port = find_free_port();
970 let instance_name = "test-stop".to_string();
971 let (tx, rx) = crate::event::channel();
972 let next_id = Arc::new(AtomicU64::new(7150));
973
974 let config = LocalServerConfig {
975 instance_name: instance_name.clone(),
976 port,
977 interface_id: InterfaceId(71),
978 };
979
980 let control = start_server(config, tx, next_id).unwrap();
981 thread::sleep(Duration::from_millis(50));
982 control.request_stop();
983 thread::sleep(Duration::from_millis(120));
984
985 #[cfg(target_os = "linux")]
986 let connect_result = unix_socket::try_connect_unix(&instance_name);
987
988 #[cfg(not(target_os = "linux"))]
989 let connect_result = TcpStream::connect(format!("127.0.0.1:{}", port));
990
991 if let Ok(stream) = connect_result {
992 drop(stream);
993 }
994
995 match rx.recv_timeout(Duration::from_millis(200)) {
996 Err(RecvTimeoutError::Timeout) | Err(RecvTimeoutError::Disconnected) => {}
997 other => panic!("expected no InterfaceUp after server stop, got {:?}", other),
998 }
999 }
1000
1001 #[test]
1002 fn client_send_receive() {
1003 let port = find_free_port();
1004 let (server_tx, server_rx) = crate::event::channel();
1005 let next_id = Arc::new(AtomicU64::new(7200));
1006
1007 let server_config = LocalServerConfig {
1008 instance_name: "test-sr".into(),
1009 port,
1010 interface_id: InterfaceId(72),
1011 };
1012
1013 start_server(server_config, server_tx, next_id).unwrap();
1014 thread::sleep(Duration::from_millis(50));
1015
1016 let (client_tx, client_rx) = crate::event::channel();
1018 let client_config = LocalClientConfig {
1019 name: "test-client".into(),
1020 instance_name: "test-sr".into(),
1021 port,
1022 interface_id: InterfaceId(73),
1023 reconnect_wait: Duration::from_secs(1),
1024 };
1025
1026 let mut client_writer = start_client(client_config, client_tx).unwrap();
1027
1028 let event = server_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1030 let mut server_writer = match event {
1031 Event::InterfaceUp(_, Some(w), Some(info)) => {
1032 assert!(info.is_local_client);
1033 w
1034 }
1035 other => panic!("expected InterfaceUp with writer, got {:?}", other),
1036 };
1037
1038 let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1040 match event {
1041 Event::InterfaceUp(id, _, _) => assert_eq!(id, InterfaceId(73)),
1042 other => panic!("expected InterfaceUp, got {:?}", other),
1043 }
1044
1045 let payload: Vec<u8> = (0..32).collect();
1047 client_writer.send_frame(&payload).unwrap();
1048
1049 let event = server_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1050 match event {
1051 Event::Frame { data, .. } => assert_eq!(data, payload),
1052 other => panic!("expected Frame, got {:?}", other),
1053 }
1054
1055 let payload2: Vec<u8> = (100..132).collect();
1057 server_writer.send_frame(&payload2).unwrap();
1058
1059 let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1060 match event {
1061 Event::Frame { data, .. } => assert_eq!(data, payload2),
1062 other => panic!("expected Frame, got {:?}", other),
1063 }
1064 }
1065
1066 #[test]
1067 fn sleep_hold_drops_outbound_after_timeout_and_refresh_restores() {
1068 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1069 let addr = listener.local_addr().unwrap();
1070 let mut client = TcpStream::connect(addr).unwrap();
1071 let (server_stream, _) = listener.accept().unwrap();
1072 client
1073 .set_read_timeout(Some(Duration::from_millis(200)))
1074 .unwrap();
1075
1076 let sleep_hold = ClientSleepHold::new(Duration::from_millis(30));
1077 let mut writer = LocalWriter {
1078 stream: server_stream,
1079 sleep_hold: Some(sleep_hold.clone()),
1080 };
1081
1082 writer.send_frame(b"live").unwrap();
1083 let mut buf = [0u8; 16];
1084 let n = client.read(&mut buf).unwrap();
1085 assert!(n > 0);
1086
1087 thread::sleep(Duration::from_millis(50));
1088 writer.send_frame(b"drop").unwrap();
1089 let err = client.read(&mut buf).unwrap_err();
1090 assert!(
1091 matches!(
1092 err.kind(),
1093 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
1094 ),
1095 "paused writer should not emit bytes, got {err:?}"
1096 );
1097
1098 sleep_hold.refresh();
1099 writer.send_frame(b"again").unwrap();
1100 let n = client.read(&mut buf).unwrap();
1101 assert!(n > 0);
1102 }
1103
1104 #[test]
1105 fn tcp_reader_refreshes_sleep_hold_on_inbound_data() {
1106 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1107 let addr = listener.local_addr().unwrap();
1108 let mut client = TcpStream::connect(addr).unwrap();
1109 let (server_stream, _) = listener.accept().unwrap();
1110 let writer_stream = server_stream.try_clone().unwrap();
1111 client
1112 .set_read_timeout(Some(Duration::from_millis(200)))
1113 .unwrap();
1114
1115 let sleep_hold = ClientSleepHold::new(Duration::from_millis(30));
1116 let reader_sleep_hold = sleep_hold.clone();
1117 let (tx, rx) = crate::event::channel();
1118 thread::spawn(move || {
1119 tcp_reader_loop(server_stream, InterfaceId(98), tx, Some(reader_sleep_hold));
1120 });
1121
1122 let mut writer = LocalWriter {
1123 stream: writer_stream,
1124 sleep_hold: Some(sleep_hold),
1125 };
1126
1127 thread::sleep(Duration::from_millis(50));
1128 writer.send_frame(b"drop").unwrap();
1129 let mut buf = [0u8; 32];
1130 let err = client.read(&mut buf).unwrap_err();
1131 assert!(
1132 matches!(
1133 err.kind(),
1134 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
1135 ),
1136 "paused writer should not emit bytes, got {err:?}"
1137 );
1138
1139 let inbound = vec![0x42; constants::HEADER_MINSIZE];
1140 client.write_all(&hdlc::frame(&inbound)).unwrap();
1141 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1142 match event {
1143 Event::Frame {
1144 interface_id,
1145 data,
1146 rssi: _,
1147 snr: _,
1148 } => {
1149 assert_eq!(interface_id, InterfaceId(98));
1150 assert_eq!(data, inbound);
1151 }
1152 other => panic!("expected Frame, got {:?}", other),
1153 }
1154
1155 writer.send_frame(b"again").unwrap();
1156 let n = client.read(&mut buf).unwrap();
1157 assert!(n > 0);
1158 }
1159
1160 #[test]
1161 fn physical_keepalive_loop_sends_empty_hdlc_frame() {
1162 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1163 let addr = listener.local_addr().unwrap();
1164 let mut client = TcpStream::connect(addr).unwrap();
1165 let (server_stream, _) = listener.accept().unwrap();
1166 client
1167 .set_read_timeout(Some(Duration::from_millis(500)))
1168 .unwrap();
1169
1170 let writer: Box<dyn Writer> = Box::new(LocalWriter {
1171 stream: server_stream,
1172 sleep_hold: None,
1173 });
1174 spawn_physical_keepalive_loop(
1175 writer,
1176 InterfaceId(99),
1177 "test-local".into(),
1178 Duration::from_millis(10),
1179 );
1180
1181 let mut buf = [0u8; 2];
1182 client.read_exact(&mut buf).unwrap();
1183 assert_eq!(buf, [0x7E, 0x7E]);
1184 }
1185
1186 #[test]
1187 fn multiple_local_clients() {
1188 let port = find_free_port();
1189 let instance_name = "test-multi".to_string();
1190 let (tx, rx) = crate::event::channel();
1191 let next_id = Arc::new(AtomicU64::new(7300));
1192
1193 let config = LocalServerConfig {
1194 instance_name: instance_name.clone(),
1195 port,
1196 interface_id: InterfaceId(74),
1197 };
1198
1199 start_server(config, tx, next_id).unwrap();
1200 thread::sleep(Duration::from_millis(50));
1201
1202 let _client1 = connect_test_client(&instance_name, port);
1203 let _client2 = connect_test_client(&instance_name, port);
1204
1205 let mut ids = Vec::new();
1206 for _ in 0..2 {
1207 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1208 match event {
1209 Event::InterfaceUp(id, _, _) => ids.push(id),
1210 other => panic!("expected InterfaceUp, got {:?}", other),
1211 }
1212 }
1213
1214 assert_eq!(ids.len(), 2);
1215 assert_ne!(ids[0], ids[1]);
1216 }
1217
1218 #[test]
1219 fn client_disconnect_detected() {
1220 let port = find_free_port();
1221 let instance_name = "test-dc".to_string();
1222 let (tx, rx) = crate::event::channel();
1223 let next_id = Arc::new(AtomicU64::new(7400));
1224
1225 let config = LocalServerConfig {
1226 instance_name: instance_name.clone(),
1227 port,
1228 interface_id: InterfaceId(75),
1229 };
1230
1231 start_server(config, tx, next_id).unwrap();
1232 thread::sleep(Duration::from_millis(50));
1233
1234 #[cfg(target_os = "linux")]
1235 let client = unix_socket::try_connect_unix(&instance_name).unwrap();
1236
1237 #[cfg(not(target_os = "linux"))]
1238 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1239
1240 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1242
1243 drop(client);
1245
1246 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1247 assert!(
1248 matches!(event, Event::InterfaceDown(_)),
1249 "expected InterfaceDown, got {:?}",
1250 event
1251 );
1252 }
1253
1254 #[test]
1255 fn client_reconnects_after_tcp_restart() {
1256 let port = find_free_port();
1257 let addr = format!("127.0.0.1:{}", port);
1258 let instance_name = format!("test-reconnect-{}", port);
1259
1260 let listener1 = TcpListener::bind(&addr).unwrap();
1261 let (accepted1_tx, accepted1_rx) = mpsc::channel();
1262 thread::spawn(move || {
1263 let (stream, _) = listener1.accept().unwrap();
1264 accepted1_tx.send(stream).unwrap();
1265 });
1266
1267 let (client_tx, client_rx) = crate::event::channel();
1268 let client_config = LocalClientConfig {
1269 name: "test-client".into(),
1270 instance_name,
1271 port,
1272 interface_id: InterfaceId(76),
1273 reconnect_wait: Duration::from_millis(50),
1274 };
1275
1276 let _writer = start_client(client_config, client_tx).unwrap();
1277 let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1278 assert!(matches!(
1279 event,
1280 Event::InterfaceUp(InterfaceId(76), None, None)
1281 ));
1282
1283 let stream1 = accepted1_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1284 drop(stream1);
1285
1286 let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1287 assert!(matches!(event, Event::InterfaceDown(InterfaceId(76))));
1288
1289 let listener2 = TcpListener::bind(&addr).unwrap();
1290 let (accepted2_tx, accepted2_rx) = mpsc::channel();
1291 thread::spawn(move || {
1292 let (stream, _) = listener2.accept().unwrap();
1293 accepted2_tx.send(stream).unwrap();
1294 });
1295
1296 let mut reconnected_writer = None;
1297 for _ in 0..10 {
1298 let event = client_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1299 match event {
1300 Event::InterfaceUp(InterfaceId(76), writer, None) if writer.is_some() => {
1301 reconnected_writer = writer;
1302 break;
1303 }
1304 _ => {}
1305 }
1306 }
1307
1308 let mut reconnected_writer = reconnected_writer.expect("missing reconnect writer");
1309 let mut stream2 = accepted2_rx.recv_timeout(Duration::from_secs(2)).unwrap();
1310 reconnected_writer.send_frame(b"client->server").unwrap();
1311 stream2
1312 .set_read_timeout(Some(Duration::from_secs(2)))
1313 .unwrap();
1314 let mut buf = [0u8; 64];
1315 let n = stream2.read(&mut buf).unwrap();
1316 assert!(n > 0, "expected bytes from refreshed writer");
1317 }
1318
1319 #[cfg(target_os = "linux")]
1320 #[test]
1321 fn unix_abstract_socket_helpers_work() {
1322 let instance_name = format!(
1323 "test-abstract-{}",
1324 std::time::SystemTime::now()
1325 .duration_since(std::time::UNIX_EPOCH)
1326 .unwrap()
1327 .as_nanos()
1328 );
1329
1330 let listener = unix_socket::try_bind_unix(&instance_name).unwrap();
1331 let accept_thread = thread::spawn(move || listener.accept().unwrap().0);
1332
1333 let mut client = unix_socket::try_connect_unix(&instance_name).unwrap();
1334 let mut server = accept_thread.join().unwrap();
1335
1336 client.write_all(b"ping").unwrap();
1337 let mut buf = [0u8; 4];
1338 server.read_exact(&mut buf).unwrap();
1339 assert_eq!(&buf, b"ping");
1340 }
1341}