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