diff --git a/DEVELOP.md b/DEVELOP.md index 0178611..6bb292f 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -5,18 +5,17 @@ ```bash # Prerequisites rustup show # Rust 1.80+ -cargo install cargo-watch -# Run locally -cargo run -- --config config/default.toml +# Run locally (DoH on :8800, DNS on :53530) +cargo run -# Run with auto-reload -cargo watch -x run +# Run with custom config +cargo run -- --config path/to/config.toml # Run tests cargo test -# Lint +# Lint & format cargo clippy -- -D warnings cargo fmt --check ``` @@ -87,7 +86,7 @@ See [docs/plan.md](docs/plan.md) for the full project plan and milestone checkli |-----------|--------| | M1 — Core Forwarding | ✅ Done | | M2 — Multiple Upstreams & Config | ✅ Done | -| M3 — Local DNS Listener | ✅ Done | +| M3 — Local DNS Listener | ✅ Done (UDP/TCP integration tests pending) | | M4 — Caching | 🔲 Not started | | M5 — Observability | 🔲 Not started | | M6 — Blocking & Filtering | 🔲 Not started | diff --git a/docs/plan.md b/docs/plan.md index b8f4b12..465eb36 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -2,15 +2,16 @@ ## Overview -A lightweight DNS-over-HTTPS (DoH) forwarder that accepts DNS queries over HTTPS (RFC 8484) and forwards them to upstream DoH resolvers, with caching, logging, and optional blocking. +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 (GET and POST per RFC 8484) -- Forward to configurable upstream DoH resolvers (Cloudflare, Google, Quad9, custom) -- In-memory DNS response cache with configurable TTL -- Query logging (stdout/file) -- Optional domain blocklist (ad-blocking / parental control) +- 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 @@ -20,11 +21,12 @@ A lightweight DNS-over-HTTPS (DoH) forwarder that accepts DNS queries over HTTPS ┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ DNS Client │─────▶│ DoH Forwarder │─────▶│ Upstream DoH │ │ (browser, │ HTTPS│ (this project) │ HTTPS│ (1.1.1.1, │ -│ OS, app) │◀─────│ │◀─────│ 8.8.8.8, …) │ -└─────────────┘ └──────────────────┘ └─────────────────┘ - │ ▲ - │ UDP/TCP :53 │ - └─────────────────────┘ +│ app) │◀─────│ /dns-query │◀─────│ 8.8.8.8, …) │ +└─────────────┘ │ /health │ └─────────────────┘ + │ └──────────────────┘ + │ UDP/TCP ▲ + │ (port 5353) │ + └──────────────────────┘ System DNS (resolv.conf) ``` @@ -32,35 +34,41 @@ A lightweight DNS-over-HTTPS (DoH) forwarder that accepts DNS queries over HTTPS - **Language**: Rust - **HTTP server**: Axum -- **DNS wire-format**: hickory-proto (formerly trust-dns-proto) +- **DNS wire-format**: hickory-proto +- **HTTP client**: reqwest (with rustls-tls) - **Async runtime**: Tokio - **Config**: TOML (config file) + env vars -- **Logging**: tracing + tracing-subscriber +- **Logging**: tracing + tracing-subscriber (env-filter) +- **Error handling**: thiserror, anyhow +- **Encoding**: base64 ## Milestones -### M1 — Core Forwarding +### M1 — Core Forwarding ✅ -- [x] Project scaffold (Cargo workspace, CI) -- [x] HTTP server with `/dns-query` endpoint (GET + POST) -- [x] Parse incoming DNS wire-format queries -- [x] Forward to a single upstream DoH resolver +- [x] Project scaffold (Cargo.toml, src layout) +- [x] HTTP server with `/dns-query` endpoint (GET + POST per RFC 8484) +- [x] Parse incoming DNS wire-format queries (hickory-proto) +- [x] Forward to upstream DoH resolver via HTTPS POST - [x] Return DNS wire-format response -- [x] Basic integration test +- [x] Health endpoint (`GET /health`) +- [x] SERVFAIL response when upstream fails +- [x] Integration tests (tests/integration.rs) -### M2 — Multiple Upstreams & Config +### M2 — Multiple Upstreams & Config ✅ -- [x] TOML config file support +- [x] TOML config file support (config/default.toml) - [x] Multiple upstream resolver support - [x] Upstream selection strategy (round-robin / failover) -- [x] Environment variable overrides -- [x] Graceful shutdown +- [x] Automatic retry with next resolver on failure +- [x] Environment variable overrides (DOH_LISTEN, DOH_UPSTREAM_STRATEGY, DOH_UPSTREAM_RESOLVERS) +- [x] Graceful shutdown (SIGINT / SIGTERM) -### M3 — Local DNS Listener +### M3 — Local DNS Listener ✅ -- [x] UDP listener on configurable port (default 53) -- [x] TCP listener on configurable port (default 53) -- [x] Receive standard DNS wire-format queries +- [x] UDP listener on configurable port (default 5353) +- [x] TCP listener on configurable port (default 5353) +- [x] DNS over TCP with 2-byte length prefix (RFC 1035 §4.2.2) - [x] Forward through DoH pipeline (shared upstream client) - [x] Return DNS wire-format response over UDP/TCP - [x] Config: `[dns]` section with `listen` address @@ -76,10 +84,8 @@ A lightweight DNS-over-HTTPS (DoH) forwarder that accepts DNS queries over HTTPS ### M5 — Observability -- [ ] Structured logging (tracing) - [ ] Request logging middleware (query name, type, latency, upstream used) -- [ ] Health endpoint (`/health`) -- [ ] Metrics endpoint (optional, Prometheus format) +- [ ] Metrics endpoint (Prometheus format) ### M6 — Blocking & Filtering @@ -91,39 +97,32 @@ A lightweight DNS-over-HTTPS (DoH) forwarder that accepts DNS queries over HTTPS ### M7 — Production Hardening - [ ] Rate limiting (per-IP) -- [ ] Request timeout for upstream queries - [ ] Dockerfile (multi-stage, distroless) - [ ] Helm chart - [ ] CI pipeline (lint, test, build, image push) -## Directory Structure (target) +## Directory Structure ``` doh-forwarder/ ├── Cargo.toml -├── src/ -│ ├── main.rs # entrypoint, server setup -│ ├── lib.rs # public API re-exports -│ ├── config.rs # config parsing -│ ├── dns/ -│ │ ├── mod.rs -│ │ ├── query.rs # parse incoming DNS queries -│ │ ├── response.rs # build/parse DNS responses -│ │ └── listener.rs # UDP/TCP DNS listener -│ ├── upstream/ -│ │ ├── mod.rs -│ │ └── doh_client.rs # DoH forwarding client -│ ├── cache.rs # DNS cache -│ ├── blocklist.rs # domain blocking -│ ├── routes.rs # HTTP route handlers -│ └── middleware.rs # logging, rate-limit ├── config/ │ └── default.toml -├── docs/ -│ └── plan.md -├── DEVELOP.md +├── 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 + └── integration.rs # HTTP integration tests ``` ## Non-Goals (for now)