heldar_kernel/services/remote_access.rs
1//! Remote-access overlay awareness (open kernel platform feature; see `docs/REMOTE-ACCESS.md`).
2//!
3//! A Heldar deployment is typically behind CGNAT (no inbound port-forward, DDNS useless). The
4//! supported way to reach it remotely is a **WireGuard overlay** — Tailscale for personal/dev use,
5//! NetBird self-hosted for shipped products — running as an EXTERNAL daemon on the host. The overlay
6//! is deliberately *orthogonal* to the media stack: MediaMTX and this API keep serving on their
7//! normal ports, now reachable over the overlay's interface by authorized peers. Connections are
8//! P2P-first (direct hole-punched / IPv6 when possible) and end-to-end encrypted, so no relay can
9//! ever read camera video.
10//!
11//! The kernel does **not** embed or manage WireGuard — that would duplicate mature daemons. It only
12//! *observes* the configured overlay interface and reports whether remote access is currently
13//! functional, so the dashboard/health surface can show it without log-diving. This keeps the
14//! capability fully open (Apache-2.0) and transport-agnostic.
15
16use serde::Serialize;
17
18use crate::config::Config;
19
20/// Health of the remote-access overlay, surfaced via `/api/v1/system`.
21#[derive(Debug, Clone, Serialize)]
22pub struct OverlayStatus {
23 /// Whether remote access via an overlay is configured at all (else the deployment is LAN-only).
24 pub enabled: bool,
25 /// `tailscale` | `netbird` | `wireguard` | `none`.
26 pub kind: String,
27 /// The probed interface (e.g. `tailscale0`), if configured.
28 pub iface: Option<String>,
29 /// Whether that interface currently exists on the host (i.e. the overlay daemon created it).
30 pub present: bool,
31 /// Raw `operstate` of the interface, if readable.
32 pub operstate: Option<String>,
33 /// Whether remote access is considered functional right now.
34 pub up: bool,
35 /// Human-readable explanation for the dashboard.
36 pub note: String,
37}
38
39/// A TUN/overlay interface is "up" when it exists and its operstate is `up` or `unknown`. WireGuard
40/// and Tailscale TUN devices commonly report `unknown` even when fully functional (the kernel does
41/// not track carrier on a point-to-point TUN), so `unknown` must NOT be treated as down.
42fn is_up(present: bool, operstate: Option<&str>) -> bool {
43 present && matches!(operstate, None | Some("up") | Some("unknown"))
44}
45
46/// Probe the configured overlay interface and report remote-access health. Dependency-free: reads
47/// `/sys/class/net/<iface>` (Linux), so it is cheap enough to call per `/system` request.
48pub fn status(cfg: &Config) -> OverlayStatus {
49 if !cfg.overlay_enabled {
50 return OverlayStatus {
51 enabled: false,
52 kind: cfg.overlay_kind.clone(),
53 iface: cfg.overlay_iface.clone(),
54 present: false,
55 operstate: None,
56 up: false,
57 note: "Remote-access overlay disabled (HELDAR_OVERLAY_ENABLED=false); reachable on the LAN only."
58 .into(),
59 };
60 }
61 let Some(iface) = cfg.overlay_iface.clone() else {
62 return OverlayStatus {
63 enabled: true,
64 kind: cfg.overlay_kind.clone(),
65 iface: None,
66 present: false,
67 operstate: None,
68 up: false,
69 note: "Overlay enabled but HELDAR_OVERLAY_IFACE is unset; cannot determine status."
70 .into(),
71 };
72 };
73
74 let base = std::path::Path::new("/sys/class/net").join(&iface);
75 let present = base.exists();
76 let operstate = std::fs::read_to_string(base.join("operstate"))
77 .ok()
78 .map(|s| s.trim().to_string());
79 let up = is_up(present, operstate.as_deref());
80
81 let note = if !present {
82 format!(
83 "Overlay interface '{iface}' not found — is the {} daemon running and connected?",
84 cfg.overlay_kind
85 )
86 } else if up {
87 format!(
88 "Overlay '{}' up on '{iface}'; deployment reachable to authorized peers (P2P-first, end-to-end encrypted).",
89 cfg.overlay_kind
90 )
91 } else {
92 format!(
93 "Overlay interface '{iface}' present but not up (operstate={}).",
94 operstate.as_deref().unwrap_or("?")
95 )
96 };
97
98 OverlayStatus {
99 enabled: true,
100 kind: cfg.overlay_kind.clone(),
101 iface: Some(iface),
102 present,
103 operstate,
104 up,
105 note,
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn tun_unknown_counts_as_up() {
115 // TUN devices report "unknown" when functional — must be treated as up.
116 assert!(is_up(true, Some("unknown")));
117 assert!(is_up(true, Some("up")));
118 assert!(is_up(true, None));
119 }
120
121 #[test]
122 fn down_or_absent_is_not_up() {
123 assert!(!is_up(true, Some("down")));
124 assert!(!is_up(false, Some("up")));
125 assert!(!is_up(false, None));
126 }
127}