Skip to content

fix: block DoH by default + fix Android tunnel_doh config mismatch#763

Merged
therealaleph merged 1 commit intotherealaleph:mainfrom
yyoyoian-pixel:fix/block-doh-and-android-defaults
May 5, 2026
Merged

fix: block DoH by default + fix Android tunnel_doh config mismatch#763
therealaleph merged 1 commit intotherealaleph:mainfrom
yyoyoian-pixel:fix/block-doh-and-android-defaults

Conversation

@yyoyoian-pixel
Copy link
Copy Markdown
Contributor

Summary

  • Block DoH (block_doh: true): immediately reject connections to known DoH endpoints (chrome.cloudflare-dns.com, dns.google, etc.). Browsers fall back to system DNS, which tun2proxy handles via virtual DNS — instant, zero tunnel cost. Saves ~1.5s per domain lookup vs tunneling DoH through Apps Script.
  • Fix Android config mismatch: tunnelDoh defaulted to false on Android but true on Rust. When Android omitted the field (default = don't emit), Rust used its own default (true) — so bypass_doh was silently broken on Android. Now Android always emits tunnel_doh explicitly and defaults match Rust.
  • Android UI toggles: Block DoH and Bypass DoH in Advanced settings. Block takes priority over Bypass.

Context

PR #468 changed tunnel_doh to true because Iranian ISPs block direct DoH endpoints. This was the right call for censorship resistance, but it made every page load ~1.5s slower per domain (Chrome's DoH connections now traversed the full Apps Script tunnel). block_doh solves both problems: no ISP-visible DoH connection (the connection never leaves the device), no tunnel round-trip (system DNS is instant via tun2proxy virtual DNS).

Test plan

  • Tested on Pixel 6 Pro with full-mode tunnel
  • Zero chrome.cloudflare-dns.com tunnel sessions with block_doh=true
  • Chrome/Brave fall back to virtual DNS correctly
  • Pages load without DoH overhead
  • Block DoH toggle works in Android Advanced UI
  • Bypass DoH is disabled (grayed out) when Block DoH is on

🤖 Generated with Claude Code

Problem:
PR therealaleph#468 changed `tunnel_doh` default to `true` (tunnel DoH through
Apps Script) to avoid ISP-blocked DoH on censored networks. But this
added ~1.5s of Apps Script round-trip per DNS lookup — every page
load got noticeably slower because Chrome's DoH connections had to
traverse the full tunnel path before the page could even start
connecting.

The Android side had a separate bug: `tunnelDoh` defaulted to
`false` but only emitted `tunnel_doh` to JSON when `true`. Since
the Rust default is `true`, omitting the field meant Rust always
tunneled DoH regardless of the Android UI setting — bypass_doh was
silently broken on Android.

Fix:
- Add `block_doh` config option: immediately reject (RST) connections
  to known DoH endpoints. Browsers fall back to system DNS, which
  tun2proxy handles via virtual DNS (instant, zero tunnel cost).
  Eliminates the DoH round-trip without exposing DoH connections to
  the ISP (unlike bypass_doh which sends DoH direct).
- Default `block_doh: true` on Android — tested on Chrome/Brave,
  falls back to virtual DNS correctly.
- Fix Android `tunnelDoh` default to `true` (matches Rust).
- Always emit `tunnel_doh` and `block_doh` explicitly in Android
  JSON serialization — no more default-mismatch bugs.
- Add Block DoH and Bypass DoH toggles in Android Advanced UI.
  Block DoH takes priority; Bypass DoH is disabled when Block is on.

Tested on Pixel 6 Pro: zero chrome.cloudflare-dns.com tunnel
sessions with block_doh=true. All DNS resolves instantly via
tun2proxy virtual DNS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the type: fix fix: PR — auto-applied by release-drafter label May 5, 2026
@therealaleph
Copy link
Copy Markdown
Owner

@yyoyoian-pixel — reviewed locally (179 lib + 35 tunnel-node tests still pass). Merging.

The DoH block is the right call — DoH-via-Apps-Script was wasting tunnel quota on a use case (encrypted DNS) where browsers will happily fall back to system DNS, which tun2proxy resolves via virtual DNS at zero cost. The Block > Bypass priority + the disabled Bypass switch when Block is on is a good UX pattern.

The Android tunnelDoh mismatch fix is a real bug: defaulting false Android-side meant the field never serialized, so Rust used its own true default and bypass_doh_hosts matching was broken on every fresh Android install. Glad you caught it.

Will ship in the next release.


[reply via Anthropic Claude | reviewed by @therealaleph]

@therealaleph therealaleph merged commit e13bca8 into therealaleph:main May 5, 2026
1 check passed
therealaleph added a commit that referenced this pull request May 5, 2026
Wraps three already-merged PRs into a release:
- PR #763 (@yyoyoian-pixel): block_doh: true default; rejects browser DoH at SOCKS5 listener so it falls back to system DNS via tun2proxy virtual DNS instead of paying ~1.5s tunnel round-trip per name lookup. Also fixes the Android tunnel_doh config mismatch (was false on Android, true on Rust — silently broke bypass_doh_hosts).
- PR #751 (@yyoyoian-pixel): TLS pool refill loop keeping ≥8 ready connections, freshest-first acquire, pool TTL 45→60s, coalesce step 10→200ms (more conservative revert from v1.9.8 for full-mode batch packing).
- PR #747 (@Shjpr9): added github.io to Fastly fronting group example.

Tests: 179 lib + 35 tunnel-node green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
therealaleph added a commit that referenced this pull request May 5, 2026
…doh field

Both v1.9.11 and v1.9.12 release CI runs failed because PR #763 added a new `block_doh: bool` field to `Config` but didn't update `src/bin/ui.rs::FormState::to_config()` which builds Config via a struct literal — caught by `cargo build --features ui --bin mhrv-rs-ui` only, not by the lib `cargo test` I'd run during PR review. Added the field to FormState (round-trip from Config), to ConfigWire (skip_serializing_if = "is_true" so default-true configs stay clean), and a new is_true helper. Verified mhrv-rs-ui release build green locally before pushing.

Net effect: v1.9.13 ships everything v1.9.11 and v1.9.12 were supposed to ship (DoH block by default, TLS pool refill loop, github.io fronting group, parallel_relay safe-method gating) plus this UI fix. No additional behavior change.

Tests: 180 lib + 35 tunnel-node + UI release-mode build all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
therealaleph added a commit that referenced this pull request May 5, 2026
PR #763 added `block_doh: bool` with `#[serde(default)]`, which resolves to Rust's `Default::default() = false` for bool, not the `true` PR #763's docs intended. Existing configs upgrading from v1.9.10 → v1.9.13 had no block_doh field, so they got `false` paired with `tunnel_doh: true` (new default from #468) — every browser DoH lookup got tunneled through Apps Script, adding ~1.5s overhead per page load. User-perceived as "v1.9.13 is slower than v1.9.10" in #773.

Switched to a named-default function `default_block_doh() -> bool { true }` so the upgrade path actually delivers the fast block-then-system-DNS behaviour PR #763 advertised. Power users who specifically want browser DoH (with the latency cost) can still opt in with explicit `block_doh: false`.

Tests: 180 lib + 35 tunnel-node + UI release-mode build all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix fix: PR — auto-applied by release-drafter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants