docs/ is gitignored; references to docs/implementation/*.md from public
source, configs, and packaging were dead links outside the maintainer
machine. Adds four recipes (README, dnsdist-front, doh-on-lan,
odoh-upstream) under top-level recipes/ and repoints existing pointers.
- numa.toml, packaging/client/{README.md,numa.toml}: point to
recipes/odoh-upstream.md.
- src/{bootstrap_resolver,forward,serve}.rs: reference issue #122
directly (module scope is broader than the ODoH-specific recipe).
- src/health.rs: drop the §-ref; iOS HealthInfo remains named as the
canonical consumer.
60 lines
2.7 KiB
Markdown
60 lines
2.7 KiB
Markdown
# ODoH upstream with bootstrap pinning
|
|
|
|
Numa can run as an Oblivious DoH (RFC 9230) client: the relay sees your IP but not the question, the target sees the question but not your IP. Neither party alone can re-identify a query. This recipe covers the minimal config and the bootstrap leak that `relay_ip` / `target_ip` close.
|
|
|
|
## When to use this
|
|
|
|
- You want split-trust encrypted DNS without a single provider seeing both who you are and what you asked.
|
|
- Numa is your system resolver (so there's no "other" DNS to ask).
|
|
|
|
## Minimal config
|
|
|
|
```toml
|
|
[upstream]
|
|
mode = "odoh"
|
|
relay = "https://odoh-relay.numa.rs/relay"
|
|
target = "https://odoh.cloudflare-dns.com/dns-query"
|
|
strict = true # refuse to fall back to a non-oblivious path on relay failure
|
|
```
|
|
|
|
`strict = true` means a relay-level HTTPS failure returns SERVFAIL instead of silently downgrading. Set it to `false` and configure `[upstream].fallback` if you'd rather keep resolving (at the cost of the oblivious property).
|
|
|
|
## The bootstrap leak
|
|
|
|
When Numa is the system resolver and needs to reach the relay/target, *something* has to translate `odoh-relay.numa.rs` → IP. If Numa asks itself, you deadlock. If Numa asks a bootstrap resolver (1.1.1.1, 9.9.9.9), that resolver learns which ODoH endpoint you use in cleartext — it can't see your questions, but it sees the destination. That's the leak ODoH was supposed to close.
|
|
|
|
`relay_ip` and `target_ip` tell Numa the IPs directly, so it never asks anyone:
|
|
|
|
```toml
|
|
[upstream]
|
|
mode = "odoh"
|
|
relay = "https://odoh-relay.numa.rs/relay"
|
|
target = "https://odoh.cloudflare-dns.com/dns-query"
|
|
relay_ip = "178.104.229.30" # pin the relay — no hostname lookup
|
|
target_ip = "104.16.249.249" # pin the target — no hostname lookup
|
|
```
|
|
|
|
Numa still validates TLS against the hostnames in `relay` / `target`, so a hijacked IP can't masquerade — pinning skips only the DNS step.
|
|
|
|
## Finding current IPs
|
|
|
|
```bash
|
|
dig +short odoh-relay.numa.rs
|
|
dig +short odoh.cloudflare-dns.com
|
|
```
|
|
|
|
Re-pin when an operator rotates. The community-maintained list at <https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v3/odoh-relays.md> is a useful cross-reference.
|
|
|
|
## Verify
|
|
|
|
```bash
|
|
kdig @127.0.0.1 example.com
|
|
```
|
|
|
|
Numa's `/queries` API and startup banner should label the upstream as `odoh://`. Look for `ODoH relay returned ...` errors in the logs if routing fails.
|
|
|
|
## Known gotchas
|
|
|
|
- **Same-operator refused.** Numa's eTLD+1 check blocks configs where the relay and target belong to the same operator (pointless — same party sees both sides). Override only when testing.
|
|
- **Single relay.** Current config accepts one relay and one target. Multi-entry rotation/failover is tracked in #140.
|