- plan.md: architecture flow diagram - README.md: simple forwarder overview diagram - story.md: DNS censorship and SOCKS5 proxy diagrams - intra-comparison.md: all ASCII diagrams converted to Mermaid
5.3 KiB
5.3 KiB
DoH Forwarder — Project Plan
Overview
A lightweight dual-protocol DNS forwarder written in Rust. It accepts DNS queries over both traditional DNS (UDP/TCP) and DNS-over-HTTPS (RFC 8484), then forwards them upstream via DoH (HTTPS POST) to configurable resolvers.
Goals
- Accept DoH queries over HTTP (GET and POST per RFC 8484)
- Accept traditional DNS queries over UDP and TCP
- Forward all queries to upstream DoH resolvers via HTTPS POST
- Support multiple upstream resolvers with round-robin or failover selection
- TOML configuration with environment variable overrides
- Graceful shutdown on SIGINT/SIGTERM
- Health-check endpoint
- Low resource footprint, single binary
Architecture
flowchart LR
subgraph clients["Clients"]
browser["Browser / App"]
os["OS DNS Client"]
end
subgraph forwarder["DoH Forwarder"]
doh["/dns-query (HTTP)"]
health["/health"]
dns["DNS Listener (UDP/TCP)"]
upstream["Upstream Client"]
end
subgraph upstreams["Upstream DoH Resolvers"]
cf["Cloudflare (1.1.1.1)"]
google["Google (8.8.8.8)"]
other["..."]
end
browser -- "HTTPS (RFC 8484)" --> doh
os -- "UDP/TCP :5353" --> dns
doh --> upstream
dns --> upstream
upstream -- "HTTPS POST" --> cf
upstream -- "HTTPS POST" --> google
upstream -- "HTTPS POST" --> other
cf -- "DNS response" --> upstream
google -- "DNS response" --> upstream
other -- "DNS response" --> upstream
Tech Stack
- Language: Rust
- HTTP server: Axum
- DNS wire-format: hickory-proto
- HTTP client: reqwest (with rustls-tls)
- Async runtime: Tokio
- Config: TOML (config file) + env vars
- Logging: tracing + tracing-subscriber (env-filter)
- Error handling: thiserror, anyhow
- Encoding: base64
Milestones
M1 — Core Forwarding ✅
- Project scaffold (Cargo.toml, src layout)
- HTTP server with
/dns-queryendpoint (GET + POST per RFC 8484) - Parse incoming DNS wire-format queries (hickory-proto)
- Forward to upstream DoH resolver via HTTPS POST
- Return DNS wire-format response
- Health endpoint (
GET /health) - SERVFAIL response when upstream fails
- Integration tests (tests/integration.rs)
M2 — Multiple Upstreams & Config ✅
- TOML config file support (config/default.toml)
- Multiple upstream resolver support
- Upstream selection strategy (round-robin / failover)
- Automatic retry with next resolver on failure
- Environment variable overrides (DOH_LISTEN, DOH_UPSTREAM_STRATEGY, DOH_UPSTREAM_RESOLVERS)
- Graceful shutdown (SIGINT / SIGTERM)
M3 — Local DNS Listener ✅
- UDP listener on configurable port (default 5353)
- TCP listener on configurable port (default 5353)
- DNS over TCP with 2-byte length prefix (RFC 1035 §4.2.2)
- Forward through DoH pipeline (shared upstream client)
- Return DNS wire-format response over UDP/TCP
- Config:
[dns]section withlistenaddress - Integration test with UDP/TCP clients
M4 — Caching
- In-memory DNS response cache
- Cache key = (query name, query type)
- Respect DNS response TTL (with configurable max)
- Cache eviction (TTL-based + LRU size cap)
- Cache stats endpoint (
/cache/stats)
M5 — Observability
- Request logging middleware (query name, type, latency, upstream used)
- Metrics endpoint (Prometheus format)
M6 — Blocking & Filtering
- Domain blocklist (file-based, one domain per line)
- Wildcard / suffix matching
- Return NXDOMAIN for blocked queries
- Hot-reload blocklist on SIGHUP
M7 — Production Hardening
- Rate limiting (per-IP)
- Dockerfile (multi-stage, distroless)
- Helm chart
- CI pipeline (lint, test, build, image push)
M8 — Censorship Resistance (the real differentiator)
This is where the project goes beyond what cloudflared offers. See
story.md for the full rationale.
- SOCKS5 proxy support for upstream DoH connections
- HTTP CONNECT proxy support for upstream DoH connections
- Configurable upstream transport (direct / SOCKS5 / WireGuard tunnel)
- Upstream health probing (latency + availability)
- Auto-switch away from unreachable resolvers
Directory Structure
doh-forwarder/
├── Cargo.toml
├── config/
│ └── default.toml
├── src/
│ ├── main.rs # entrypoint, server setup, graceful shutdown
│ ├── lib.rs # public API re-exports
│ ├── config.rs # TOML config parsing + env var overrides
│ ├── routes.rs # HTTP route handlers (/dns-query, /health)
│ ├── dns/
│ │ ├── mod.rs
│ │ ├── query.rs # DNS wire-format query parsing
│ │ ├── response.rs # DNS response building (SERVFAIL)
│ │ └── listener.rs # UDP/TCP DNS listener
│ └── upstream/
│ ├── mod.rs
│ └── doh_client.rs # DoH forwarding client (retry, strategies)
└── tests/
└── integration.rs # HTTP integration tests
Non-Goals (for now)
- DNS-over-TLS (DoT) listener
- DNSSEC validation
- Persistent cache (Redis / disk)
- Web UI / dashboard