# doh-forwarder A lightweight DNS-over-HTTPS (DoH) forwarder written in Rust. Accepts DNS queries over traditional DNS (UDP/TCP) and HTTP DoH ([RFC 8484](https://www.rfc-editor.org/rfc/rfc8484)), then forwards them upstream via DoH (HTTPS POST) to configurable resolvers. ```mermaid flowchart LR client["DNS Client"] -- "UDP/TCP or HTTPS" --> forwarder["doh-forwarder"] forwarder -- "HTTPS POST (DoH)" --> upstream["Upstream Resolvers\n(Cloudflare, Google, ...)"] upstream -- "DNS response" --> forwarder forwarder -- "DNS response" --> client ``` ## Why? See [docs/story.md](docs/story.md) for the full context — why this exists, what already works, and where this project is headed. ## Features - **Dual-protocol listener** — traditional DNS (UDP/TCP) + HTTP DoH endpoints - **RFC 8484 compliant** — GET (`?dns=base64url`) and POST (`application/dns-message`) - **Multiple upstreams** — configure several DoH resolvers - **Selection strategies** — round-robin or failover with automatic retry - **Environment overrides** — override config via env vars - **Graceful shutdown** — handles SIGINT and SIGTERM - **Health endpoint** — `GET /health` ## Quick Start ```bash # Build cargo build --release # Run with default config (DoH on :8800, DNS on :53530) ./target/release/doh-forwarder # Run with a custom config ./target/release/doh-forwarder --config /path/to/config.toml ``` ## Configuration See [config/default.toml](config/default.toml): ```toml [server] listen = "0.0.0.0:8800" [dns] listen = "0.0.0.0:53530" [upstream] resolvers = [ { name = "cloudflare", url = "https://cloudflare-dns.com/dns-query" }, ] strategy = "round-robin" # or "failover" ``` ### Environment Variable Overrides | Variable | Description | Example | |---|---|---| | `DOH_LISTEN` | Override DoH server listen address | `0.0.0.0:8080` | | `DOH_UPSTREAM_STRATEGY` | Override upstream strategy | `failover` | | `DOH_UPSTREAM_RESOLVERS` | Comma-separated `name=url` pairs | `google=https://dns.google/dns-query,cf=https://cloudflare-dns.com/dns-query` | | `RUST_LOG` | Log level filter | `debug`, `doh_forwarder=trace` | ## Usage ### Point your system DNS at the forwarder ```bash # /etc/resolv.conf nameserver 127.0.0.1 # Then run the forwarder with [dns] listen = "127.0.0.1:53" ``` Or use `resolvectl` with systemd-resolved: ```bash resolvectl dns eth0 127.0.0.1 resolvectl dns-over-tls no # we handle encryption upstream ``` ### DoH API **POST `/dns-query`** — raw DNS wire-format in body: ```bash curl -X POST http://localhost:8800/dns-query \ -H "Content-Type: application/dns-message" \ -H "Accept: application/dns-message" \ --data-binary @query.bin ``` **GET `/dns-query`** — base64url-encoded query parameter: ```bash curl "http://localhost:8800/dns-query?dns=" ``` **GET `/health`** — returns `ok`: ```bash curl http://localhost:8800/health ``` ### Upstream Strategies | Strategy | Behavior | |---|---| | `round-robin` | Distributes queries across resolvers in rotation | | `failover` | Tries resolvers in order; moves to the next on failure | ## Project Structure ``` doh-forwarder/ ├── Cargo.toml ├── config/ │ └── default.toml # Default configuration ├── docs/ │ ├── plan.md # Project plan & milestones │ └── story.md # Why this project exists ├── src/ │ ├── main.rs # Entrypoint, server setup, graceful shutdown │ ├── lib.rs # Public API re-exports │ ├── config.rs # TOML config + env overrides │ ├── routes.rs # HTTP route handlers (/dns-query, /health) │ ├── dns/ │ │ ├── mod.rs │ │ ├── query.rs # DNS 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 └── DEVELOP.md # Development rules & milestone tracking ``` ## Development See [DEVELOP.md](DEVELOP.md) for development rules, coding standards, and milestone tracking. ```bash # Run with auto-reload cargo install cargo-watch cargo watch -x run # Run tests cargo test # Lint & format cargo clippy -- -D warnings cargo fmt --check ``` ## License MIT