Files
doh-forwarder/docs/plan.md

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

┌─────────────┐      ┌──────────────────┐      ┌─────────────────┐
│  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

  • Project scaffold (Cargo.toml, src layout)
  • HTTP server with /dns-query endpoint (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 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 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