Skip to main content

rns_net/interface/
local.rs

1//! Local shared instance interface.
2//!
3//! Provides communication between the shared RNS instance and local client
4//! programs. Uses Unix abstract sockets on Linux, TCP on other platforms.
5//! HDLC framing over the connection (same as TCP interfaces).
6//!
7//! Two modes:
8//! - `LocalServer`: The shared instance binds and accepts client connections.
9//! - `LocalClient`: Connects to an existing shared instance.
10
11use 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
26/// Hardware MTU used by both sides of the local shared-instance interface.
27pub(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/// Configuration for a Local server (shared instance).
36#[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/// Configuration for a Local client (connecting to shared instance).
54#[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
75/// HDLC writer over a TCP or Unix stream.
76struct 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    // A physical keepalive is always an empty logical frame. Keeping that
170    // payload literal at the writer boundary prevents any prior application
171    // data from being captured or retransmitted by the non-polling path.
172    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    /// Try to bind a Unix abstract socket with the given instance name.
224    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    /// Try to connect to a Unix abstract socket.
230    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
236// ==================== LOCAL SERVER ====================
237
238/// Start a local server (shared instance).
239/// Tries Unix abstract socket first on Linux, falls back to TCP.
240/// Spawns an acceptor thread. Each client gets a dynamically allocated InterfaceId.
241pub 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    // Try Unix socket first on Linux
257    #[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    // Fallback: TCP on localhost
289    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
305/// TCP server accept loop for local interface.
306fn 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/// Unix socket server accept loop for local interface.
342#[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        // Convert UnixStream to a pair of read/write handles
373        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
481/// Spawn handler threads for a connected TCP local client.
482fn 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// ==================== LOCAL CLIENT ====================
599
600#[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
754/// Start a local client (connect to shared instance).
755/// Tries Unix socket first on Linux, falls back to TCP.
756/// Returns the writer for the driver.
757pub 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
774// --- Factory implementations ---
775
776use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
777use std::collections::HashMap;
778
779/// Factory for `LocalServerInterface`.
780pub 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
843/// Factory for `LocalClientInterface`.
844pub 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        // We force TCP by using a unique instance name that won't conflict
976        // with any existing Unix socket
977        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        // Connect client
1062        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        // Get server-side InterfaceUp
1074        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        // Get client-side InterfaceUp
1084        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        // Client sends to server
1091        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        // Server sends to client
1101        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        // Drain InterfaceUp
1304        let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1305
1306        // Disconnect
1307        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}