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