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).
66 lines
2.0 KiB
TOML
66 lines
2.0 KiB
TOML
[package]
|
|
name = "numa"
|
|
version = "0.13.1"
|
|
authors = ["razvandimescu <razvan@dimescu.com>"]
|
|
edition = "2021"
|
|
description = "Portable DNS resolver in Rust — .numa local domains, ad blocking, developer overrides, DNS-over-HTTPS"
|
|
license = "MIT"
|
|
repository = "https://github.com/razvandimescu/numa"
|
|
keywords = ["dns", "dns-server", "ad-blocking", "reverse-proxy", "developer-tools"]
|
|
categories = ["network-programming", "development-tools"]
|
|
|
|
[dependencies]
|
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "time", "sync", "signal"] }
|
|
axum = "0.8"
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
toml = "1.1"
|
|
log = "0.4"
|
|
env_logger = "0.11"
|
|
reqwest = { version = "0.12", features = ["rustls-tls", "gzip", "http2"], default-features = false }
|
|
hyper = { version = "1", features = ["client", "http1", "server"] }
|
|
hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"] }
|
|
http-body-util = "0.1"
|
|
futures = "0.3"
|
|
socket2 = { version = "0.6", features = ["all"] }
|
|
rcgen = { version = "0.14", features = ["pem", "x509-parser"] }
|
|
time = "0.3"
|
|
rustls = "0.23"
|
|
tokio-rustls = "0.26"
|
|
arc-swap = "1"
|
|
ring = "0.17"
|
|
odoh-rs = "1"
|
|
# rand_core 0.9 matches the version odoh-rs (via hpke 0.13) depends on, so we
|
|
# share one RngCore trait and OsRng impl across the dep tree.
|
|
rand_core = { version = "0.9", features = ["os_rng"] }
|
|
rustls-pemfile = "2.2.0"
|
|
qrcode = { version = "0.14", default-features = false, features = ["svg"] }
|
|
webpki-roots = "1"
|
|
|
|
[target.'cfg(windows)'.dependencies]
|
|
windows-service = "0.7"
|
|
|
|
[dev-dependencies]
|
|
criterion = { version = "0.8", features = ["html_reports"] }
|
|
tower = { version = "0.5", features = ["util"] }
|
|
http = "1"
|
|
hickory-resolver = { version = "0.25", features = ["https-ring", "webpki-roots"] }
|
|
hickory-proto = "0.25"
|
|
x509-parser = "0.18"
|
|
|
|
[[bench]]
|
|
name = "hot_path"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "throughput"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "dnssec"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "recursive_compare"
|
|
harness = false
|