From a3cc64c94f6d7e53705455e4e384ac5b811eaa1e Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Mon, 20 Apr 2026 15:44:20 +0300 Subject: [PATCH] feat(odoh): relay bind-address CLI arg + dashboard Outbound Wire panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `numa relay [PORT] [BIND]` accepts an optional bind address (defaults to 127.0.0.1, matching the Caddy reverse-proxy deployment shape). Required for Docker, where the relay needs 0.0.0.0 inside the container so Caddy can reach it across the bridge network. - Dashboard now surfaces the upstream_transport dimension as an "Outbound Wire" panel alongside the existing "Inbound Wire" (renamed from "Transport" for directional clarity). Sub-headers — "apps → numa" / "numa → internet" — make the threat-model split obvious without jargon. Bars: UDP/DoH/DoT/ODoH, headline "X% encrypted outbound". The PR description's promise that "the dashboard answers how much of my DNS traffic left in cleartext honestly" is now true. --- site/dashboard.html | 35 ++++++++++++++++++++++++++++++++--- src/main.rs | 15 +++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/site/dashboard.html b/site/dashboard.html index fa2d965..710692b 100644 --- a/site/dashboard.html +++ b/site/dashboard.html @@ -228,6 +228,7 @@ body { .path-bar-fill.tcp { background: var(--violet); } .path-bar-fill.dot { background: var(--emerald); } .path-bar-fill.doh { background: var(--teal); } +.path-bar-fill.odoh { background: var(--violet-dim); } .path-pct { font-family: var(--font-mono); font-size: 0.75rem; @@ -637,16 +638,26 @@ body { - +
- Transport + Inbound Wire apps → numa
+ +
+
+ Outbound Wire numa → internet + +
+
+
+
+
@@ -992,7 +1003,24 @@ function renderTransport(transport) { renderBarChart('transportBars', TRANSPORT_DEFS, transport, total); const encPct = encryptionPct(transport); const el = document.getElementById('transportEncrypted'); - el.textContent = `${encPct}% encrypted`; + el.textContent = `${encPct}% encrypted inbound`; + el.style.color = encPct >= 80 ? 'var(--emerald)' : encPct >= 50 ? 'var(--amber)' : 'var(--rose)'; +} + +const UPSTREAM_WIRE_DEFS = [ + { key: 'udp', label: 'UDP', cls: 'udp' }, + { key: 'doh', label: 'DoH', cls: 'doh' }, + { key: 'dot', label: 'DoT', cls: 'dot' }, + { key: 'odoh', label: 'ODoH', cls: 'odoh' }, +]; + +function renderUpstreamWire(ut) { + const total = (ut.udp + ut.doh + ut.dot + ut.odoh) || 0; + renderBarChart('upstreamWireBars', UPSTREAM_WIRE_DEFS, ut, total || 1); + const encrypted = ut.doh + ut.dot + ut.odoh; + const encPct = total > 0 ? Math.round((encrypted / total) * 100) : 0; + const el = document.getElementById('upstreamWireEncrypted'); + el.textContent = total > 0 ? `${encPct}% encrypted outbound` : ''; el.style.color = encPct >= 80 ? 'var(--emerald)' : encPct >= 50 ? 'var(--amber)' : 'var(--rose)'; } @@ -1234,6 +1262,7 @@ async function refresh() { // Panels renderPaths(q); renderTransport(stats.transport); + renderUpstreamWire(stats.upstream_transport || { udp: 0, doh: 0, dot: 0, odoh: 0 }); renderQueryLog(logs); renderOverrides(overrides); renderCache(cache); diff --git a/src/main.rs b/src/main.rs index e077a2f..8f9fecf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,17 @@ fn main() -> numa::Result<()> { .as_deref() .and_then(|s| s.parse().ok()) .unwrap_or(8443); - let addr: std::net::SocketAddr = ([127, 0, 0, 1], port).into(); + let bind: std::net::IpAddr = std::env::args() + .nth(3) + .as_deref() + .map(|s| { + s.parse().unwrap_or_else(|e| { + eprintln!("invalid bind address '{}': {}", s, e); + std::process::exit(1); + }) + }) + .unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)); + let addr = std::net::SocketAddr::new(bind, port); eprintln!( "\x1b[1;38;2;192;98;58mNuma\x1b[0m — ODoH relay on {}\n", addr @@ -107,7 +117,8 @@ fn main() -> numa::Result<()> { eprintln!(" service status Check if the service is running"); eprintln!(" lan on Enable LAN service discovery (mDNS)"); eprintln!(" lan off Disable LAN service discovery"); - eprintln!(" relay [PORT] Run as an ODoH relay (RFC 9230, default port 8443)"); + eprintln!(" relay [PORT] [BIND]"); + eprintln!(" Run as an ODoH relay (RFC 9230, default 127.0.0.1:8443)"); eprintln!(" setup-phone Generate a QR code to install Numa DoT on a phone"); eprintln!(" help Show this help"); eprintln!();