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