feat(odoh): ship ODoH client + self-hosted relay (RFC 9230)
Client (mode = "odoh"): URL-query target routing per RFC 9230 §5,
/.well-known/odohconfigs TTL cache with 60s backoff on failure, HPKE
seal/open via odoh-rs, strict-mode default that SERVFAILs on relay
failure instead of silently downgrading. Host-equality config
validation rejects same-operator relay/target pairs.
Relay (`numa relay [PORT]`): axum server with /relay + /health.
SSRF-hardened hostname validator (RFC 1035 ASCII + dot + dash),
4 KiB body cap at the axum layer, 5s full-transaction timeout, and
static 502 on target failure (reqwest internals logged, not leaked).
Aggregate counters only — no per-request logs.
Observability: new `UpstreamTransport { Udp, Doh, Dot, Odoh }`
orthogonal to `QueryPath`, so /stats can tally wire protocols
symmetrically. Recursive mode records `Some(Udp)` for honest
"bytes egressing in cleartext" accounting.
Tests: Suite 8 exercises the client end-to-end via Frank Denis's
public relay + Cloudflare target; Suite 9 exercises `numa relay`
forwarding + guards against Cloudflare as the real far end. Full
probe script at tests/probe-odoh-ecosystem.sh verifies the entire
public ODoH ecosystem (4 targets + 1 relay per DNSCrypt's curated
list — confirms deploying Numa's relay doubles global supply).
This commit is contained in:
17
src/main.rs
17
src/main.rs
@@ -60,6 +60,22 @@ fn main() -> numa::Result<()> {
|
||||
.block_on(numa::setup_phone::run())
|
||||
.map_err(|e| e.into());
|
||||
}
|
||||
"relay" => {
|
||||
let port: u16 = std::env::args()
|
||||
.nth(2)
|
||||
.as_deref()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(8443);
|
||||
let addr: std::net::SocketAddr = ([127, 0, 0, 1], port).into();
|
||||
eprintln!(
|
||||
"\x1b[1;38;2;192;98;58mNuma\x1b[0m — ODoH relay on {}\n",
|
||||
addr
|
||||
);
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
return runtime.block_on(numa::relay::run(addr));
|
||||
}
|
||||
"lan" => {
|
||||
let sub = std::env::args().nth(2).unwrap_or_default();
|
||||
let config_path = std::env::args()
|
||||
@@ -91,6 +107,7 @@ 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!(" setup-phone Generate a QR code to install Numa DoT on a phone");
|
||||
eprintln!(" help Show this help");
|
||||
eprintln!();
|
||||
|
||||
Reference in New Issue
Block a user