145 lines
5.3 KiB
Markdown
145 lines
5.3 KiB
Markdown
# 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
|
|
|
|
```
|
|
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ DNS Client │─────▶│ DoH Forwarder │─────▶│ Upstream DoH │
|
|
│ (browser, │ HTTPS│ (this project) │ HTTPS│ (1.1.1.1, │
|
|
│ app) │◀─────│ /dns-query │◀─────│ 8.8.8.8, …) │
|
|
└─────────────┘ │ /health │ └─────────────────┘
|
|
│ └──────────────────┘
|
|
│ UDP/TCP ▲
|
|
│ (port 5353) │
|
|
└──────────────────────┘
|
|
System DNS (resolv.conf)
|
|
```
|
|
|
|
## 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 ✅
|
|
|
|
- [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] Health endpoint (`GET /health`)
|
|
- [x] SERVFAIL response when upstream fails
|
|
- [x] Integration tests (tests/integration.rs)
|
|
|
|
### M2 — Multiple Upstreams & Config ✅
|
|
|
|
- [x] TOML config file support (config/default.toml)
|
|
- [x] Multiple upstream resolver support
|
|
- [x] Upstream selection strategy (round-robin / failover)
|
|
- [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 ✅
|
|
|
|
- [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
|
|
- [ ] 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](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
|