Skip to content
3RadiancePublic

About

Censorship-resistant DoH stub in Rust with randomized TLS fingerprints, HPACK padding, direct IP connections, and provider fallback.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

doh-rs

Читать на русском →

Cross-platform local DNS-over-HTTPS (DoH) resolver written in Rust. Converts standard local UDP DNS queries into encrypted HTTPS POST requests to a DoH provider.

Connects directly to DoH servers by IP, bypassing system DNS. Discovers and caches new providers in a local JSON config.

sequenceDiagram
    participant OS as System (UDP)
    participant Stub as doh-rs
    participant JSON as bootstrap.json
    participant DoH as DoH Provider (HTTPS)

    OS->>Stub: Standard DNS query (UDP:53/5300)
    Note over Stub: Check bootstrap.json / Resolve IP / Fallback
    Stub->>DoH: HTTPS POST (Direct IP + Random TLS)
    DoH->>Stub: Response (application/dns-message)
    Stub->>OS: Standard DNS response (UDP)
    Note over Stub: Background: hourly IP refresh & client hot-swap
Loading

Features

  • Direct IP connection — Does not depend on system DNS to resolve the DoH server itself.
  • Automatic provider discovery — Detects whether the --doh argument is an IP or a domain, resolves it via bootstrap providers, and validates the URL.
  • Provider fallback — If the primary provider fails (timeout, connection error, HTTP error), automatically retries with the next provider from bootstrap.json.
  • Bootstrap caching (bootstrap.json) — New DoH URLs passed via -d are automatically resolved and appended to the config for instant startup next time.
  • Random TLS fingerprints — Randomizes TLS Client Hello fingerprints (Emulation::random()) on every connection to evade DPI and censorship detection.
  • Background IP auto-updater — A background Tokio task automatically resolves and updates the DoH provider's IP address every hour.
  • Transparent client hot-swap — Replaces the underlying wreq HTTP client transparently when IPs change, without dropping active queries or connections.
  • Request timeouts — 10s total timeout, 5s connect timeout, 8s per-request timeout to prevent hanging on dead connections.
  • HPACK-aware traffic padding (-P) — Adds random-byte padding to requests, sized to account for HPACK/QPACK header compression (ported from Xray's XHTTP padding logic), so the padded size actually survives on the wire instead of being compressed away. Disabled by default (-P 0), a fixed size (-P 500), or a random size picked from a range (-P 100-500) on every request.

Build

Native / Linux (Any OS/distro except NixOS)

cargo build --release

Windows/Android/Static Linux binary/NixOS

If you are using NixOS, you can cross-compile:

Windows

mv flake.windows.nix flake.nix

nix develop
cargo xwin build --release --target x86_64-pc-windows-msvc

Android

mv flake.android.nix flake.nix

nix develop
cargo build --release --target aarch64-linux-android

Static Linux binary

mv flake.linux.nix flake.nix

nix develop
cargo build --release --target x86_64-unknown-linux-musl

NixOS

mv flake.nixos.nix flake.nix

nix develop
cargo build --release

Usage

Default settings (port 5300, Cloudflare primary with Google fallback):

./target/release/doh-rs

Custom port and provider:

./target/release/doh-rs -p 53 -P 0-50 -d https://dns.google/dns-query

Using a direct IP (no domain resolution needed):

./target/release/doh-rs -p 53 -P 100-500 -d https://1.1.1.1/dns-query

CLI Flags

Flag Description Default
-p Local UDP port for incoming DNS queries 5300
-d DoH provider URL https://cloudflare-dns.com/dns-query
-P Padding size: 0 (disabled), a fixed value (500), or a range (100-500) 0

Provider Validation

When a new provider is passed via -d, the stub validates it before use:

  • Scheme must be https — http is rejected.
  • Path must contain dns-query — standard DoH endpoint required.
  • Host is automatically detected as either an IP or a domain. Domains are resolved via existing bootstrap providers.

Fallback Behavior

Providers are tried in order until one succeeds:

  1. The provider from -d (if it exists in bootstrap.json, it is moved to first position).
  2. Remaining providers from bootstrap.json in their stored order.

If a provider fails, the error is logged and the next one is attempted immediately. If all providers fail, the query is dropped with a fatal log entry.

Anti-Censorship & DPI Evasion

To resist deep-packet inspection and blocking, doh-rs employs multiple layers:

  1. Random TLS fingerprints — Every HTTPS connection uses a randomized TLS Client Hello fingerprint (Emulation::random()), making it harder for DPI systems to fingerprint or block the stub based on static TLS signatures.
  2. Browser SNI emulation — The TLS handshake mimics a real browser, blending into normal HTTPS traffic.
  3. Direct IP connection — Bypasses system DNS entirely, preventing local DNS-based blocking from affecting the DoH path.
  4. HPACK-aware padding (-P) — Randomizes request size to defeat length-based traffic analysis. Naive padding gets mostly eaten by HPACK/QPACK header compression, so the padding size is calculated to survive compression and still land close to the requested size on the wire.

Configuration (bootstrap.json)

Created automatically on first run:

{
  "primary": "dns",
  "providers": [
    {
      "name": "cloudflare-dns",
      "domain": "cloudflare-dns.com",
      "url": "https://cloudflare-dns.com/dns-query",
      "ips": ["104.16.249.249", "104.16.248.249"]
    },
    {
      "name": "google-dns",
      "domain": "dns.google",
      "url": "https://dns.google/dns-query",
      "ips": ["8.8.8.8", "8.8.4.4"]
    }
  ]
}

New providers discovered via -d are appended to this file automatically.

System Integration (NixOS / systemd)

To intercept all system DNS traffic, bind to port 53 and set 127.0.0.1 as the system resolver.

Example NixOS service:

systemd.services.doh-rs = {
  description = "Custom Rust DoH Resolver";
  after = [ "network.target" ];
  wantedBy = [ "multi-user.target" ];
  serviceConfig = {
    ExecStart = "/path/to/doh-rs -p 53 -P 100-200 -d https://your-doh-provider.com/dns-query";
    WorkingDirectory = "/path/to/doh-dir";
    Restart = "always";
    RestartSec = "3s";
    User = "root";
  };
};

License

MIT

About

Censorship-resistant DoH stub in Rust with randomized TLS fingerprints, HPACK padding, direct IP connections, and provider fallback.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages