Files
doh-forwarder/docs/story.md
Mohammadreza Khani d28f861075 docs: add Mermaid diagrams throughout docs
- 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
2026-07-06 23:09:06 +03:30

5.9 KiB

The Story — Why This Project Exists

The Problem

In countries with internet censorship (Iran, China, Russia, and others), DNS is one of the first things controlled. ISPs intercept plain DNS queries (UDP/TCP port 53) and:

  • Return fake IP addresses for blocked domains
  • Log every domain you look up
  • Inject ads or redirect to government portals
sequenceDiagram
    participant User
    participant ISP as ISP DNS
    participant Real as Real Server

    User->>ISP: youtube.com?
    ISP-->>User: 1.2.3.4 (FAKE IP)
    User->>Real: HTTPS to 1.2.3.4
    Real-->>User: ❌ Connection failed / redirected

DNS-over-HTTPS (DoH) and DNS-over-TLS (DoT) solve this by encrypting DNS queries, making them invisible to the ISP's DNS layer.

What Already Exists

This is not a new problem. Mature tools already solve local DNS → DoH forwarding:

Tool What it does Maintained by
cloudflared Local DNS → DoH proxy (cloudflared proxy-dns) Cloudflare
dnscrypt-proxy DoH + DoT + anonymized relays, blocklists Frank Denis
stubby Local DNS → DoT proxy getdns team
systemd-resolved Built into systemd, supports DoT systemd team
AdGuard Home DNS proxy + DoH/DoT + web UI + blocklists AdGuard

For simple "local DNS → DoH" forwarding, cloudflared proxy-dns does exactly what this project does in one command:

cloudflared proxy-dns --port 5353 --upstream https://cloudflare-dns.com/dns-query

The Real Obstacle

The harder problem — and the one most relevant to censored regions — is that upstream DoH/DoT endpoints themselves get blocked:

Technique What gets blocked How
SNI inspection cloudflare-dns.com, dns.google TLS ClientHello reveals the domain
IP blocklist Known resolver IPs (1.1.1.1, 8.8.8.8) Direct IP blocking
TLS fingerprinting DoH/DoT traffic patterns JA3/JA4 fingerprint analysis
Deep packet inspection HTTPS traffic to known DNS resolvers Pattern matching on domain + path

A local forwarder that sends POST https://cloudflare-dns.com/dns-query will work in most of the world, but will likely fail in Iran because the connection to Cloudflare itself is blocked.

sequenceDiagram
    participant Client
    participant FW as Firewall / DPI
    participant CF as Cloudflare DoH

    Client->>FW: POST https://cloudflare-dns.com/dns-query
    FW->>FW: Inspect SNI: cloudflare-dns.com
    FW-->>Client: ❌ BLOCKED (connection reset)

What Actually Works in Censored Environments

Tools that solve the upstream blocking problem:

  1. Warp / WireGuard — tunnel all traffic (including DNS) through an encrypted VPN. The ISP sees WireGuard traffic but can't inspect it.

  2. GoodbyeDPI / zapret — packet manipulation to evade DPI without a VPN. Modifies TCP flags, splits packets, fakes SNI.

  3. dnscrypt-proxy with anonymized relays — DNS queries go through intermediate relays. The resolver never sees your IP, and the ISP never sees the resolver's IP.

  4. Tor — route DNS through Tor exit nodes. Slow but highly resistant.

  5. Encrypted Client Hello (ECH) — hides the SNI field in TLS handshakes. Still emerging; not widely supported.

So Is This Project Necessary?

Honestly: no, not for its current functionality.

If the goal is "use encrypted DNS from Linux in a censored region," install cloudflared or dnscrypt-proxy + a Warp/WireGuard tunnel. These are battle-tested, maintained by teams, and packaged in every distro.

Where This Project Becomes Useful

This project becomes genuinely valuable if it evolves beyond a basic forwarder. Directions that matter:

1. Upstream obfuscation (the real differentiator)

Route upstream DoH traffic through a proxy (SOCKS5, HTTP CONNECT, or WireGuard tunnel) instead of direct HTTPS. This bypasses SNI and IP blocking:

flowchart LR
    client["DNS Client"] -- "UDP/TCP" --> forwarder["doh-forwarder"]
    forwarder -- "SOCKS5/CONNECT" --> proxy["Proxy Server"]
    proxy -- "HTTPS" --> doh["DoH Resolver"]
    doh -- "DNS response" --> proxy
    proxy -- "DNS response" --> forwarder
    forwarder -- "DNS response" --> client

No existing lightweight Rust tool does this well. dnscrypt-proxy has anonymized relays but no SOCKS5 support for DoH.

2. Pluggable transport layer

Support multiple upstream transports:

  • Direct DoH (current — works in free internet)
  • DoH over SOCKS5 (works behind blocks)
  • DoH over WireGuard tunnel (works behind aggressive DPI)
  • DNS-over-TLS (for resolvers that support it)

The user picks the transport based on their environment.

3. Smart upstream selection

Don't just round-robin — probe upstreams for latency and availability. Auto-switch when a resolver becomes unreachable. This matters when resolvers get blocked intermittently.

4. Built-in blocklist support

Ad-blocking and malware-blocking at the DNS level. dnscrypt-proxy and AdGuard Home do this, but a lightweight Rust binary with this built-in is appealing for embedded/resource-constrained systems.

5. Learning

Even if you never deploy it, building this teaches:

  • DNS wire format (RFC 1035)
  • DoH protocol (RFC 8484)
  • Rust async (Tokio, Axum)
  • Network programming
  • Error handling patterns

That alone justifies the project.

Summary

Question Answer
Does this solve local DNS → DoH? Yes, but cloudflared already does this
Does this bypass censorship? No — upstream DoH endpoints get blocked
Is the project pointless? No — it's a foundation for something better
What makes it worth building? SOCKS5/proxy upstream support, pluggable transports, learning

The project is a starting point, not a finished solution. The first 80% (local DNS → DoH forwarding) is solved by existing tools. The last 20% (bypassing upstream blocking) is where this project can differentiate.