mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-17 21:24:48 +03:00
fix: block DoH by default + fix Android tunnel_doh config mismatch (#763)
Problem: PR #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: yyoyoian-pixel <279225925+yyoyoian-pixel@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -118,7 +118,7 @@ data class MhrvConfig(
|
||||
* per name lookup with no real privacy gain. Set this to true to
|
||||
* keep DoH inside the tunnel. See `src/config.rs` `tunnel_doh`.
|
||||
*/
|
||||
val tunnelDoh: Boolean = false,
|
||||
val tunnelDoh: Boolean = true,
|
||||
|
||||
/**
|
||||
* Extra hostnames added to the built-in DoH default list. Same
|
||||
@@ -127,6 +127,13 @@ data class MhrvConfig(
|
||||
*/
|
||||
val bypassDohHosts: List<String> = emptyList(),
|
||||
|
||||
/**
|
||||
* When true, reject all connections to known DoH endpoints.
|
||||
* Browsers fall back to system DNS (tun2proxy virtual DNS — instant).
|
||||
* Takes priority over tunnel_doh / bypass_doh.
|
||||
*/
|
||||
val blockDoh: Boolean = true,
|
||||
|
||||
/** VPN_TUN (everything routed) vs PROXY_ONLY (user configures per-app). */
|
||||
val connectionMode: ConnectionMode = ConnectionMode.VPN_TUN,
|
||||
|
||||
@@ -218,7 +225,8 @@ data class MhrvConfig(
|
||||
if (passthroughHosts.isNotEmpty()) {
|
||||
put("passthrough_hosts", JSONArray().apply { passthroughHosts.forEach { put(it) } })
|
||||
}
|
||||
if (tunnelDoh) put("tunnel_doh", true)
|
||||
put("tunnel_doh", tunnelDoh)
|
||||
put("block_doh", blockDoh)
|
||||
if (youtubeViaRelay) put("youtube_via_relay", true)
|
||||
// Trim/drop-empty/dedupe before serializing — symmetric with the
|
||||
// read-side normalization in loadFromJson(), so a user typing
|
||||
@@ -325,6 +333,7 @@ object ConfigStore {
|
||||
if (cfg.upstreamSocks5.isNotBlank()) obj.put("upstream_socks5", cfg.upstreamSocks5)
|
||||
if (cfg.passthroughHosts.isNotEmpty()) obj.put("passthrough_hosts", JSONArray().apply { cfg.passthroughHosts.forEach { put(it) } })
|
||||
if (cfg.tunnelDoh != defaults.tunnelDoh) obj.put("tunnel_doh", cfg.tunnelDoh)
|
||||
if (cfg.blockDoh != defaults.blockDoh) obj.put("block_doh", cfg.blockDoh)
|
||||
if (cfg.youtubeViaRelay != defaults.youtubeViaRelay) obj.put("youtube_via_relay", cfg.youtubeViaRelay)
|
||||
val cleanBypassDohHosts = cfg.bypassDohHosts
|
||||
.map { it.trim() }
|
||||
@@ -428,7 +437,8 @@ object ConfigStore {
|
||||
passthroughHosts = obj.optJSONArray("passthrough_hosts")?.let { arr ->
|
||||
buildList { for (i in 0 until arr.length()) add(arr.optString(i)) }
|
||||
}?.filter { it.isNotBlank() }.orEmpty(),
|
||||
tunnelDoh = obj.optBoolean("tunnel_doh", false),
|
||||
tunnelDoh = obj.optBoolean("tunnel_doh", true),
|
||||
blockDoh = obj.optBoolean("block_doh", true),
|
||||
youtubeViaRelay = obj.optBoolean("youtube_via_relay", false),
|
||||
bypassDohHosts = obj.optJSONArray("bypass_doh_hosts")?.let { arr ->
|
||||
buildList { for (i in 0 until arr.length()) add(arr.optString(i)) }
|
||||
|
||||
@@ -1265,6 +1265,51 @@ private fun AdvancedSettings(
|
||||
)
|
||||
}
|
||||
|
||||
// Block DoH toggle
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
"Block DoH",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Text(
|
||||
"Reject browser DoH — forces instant system DNS via tun2proxy. Saves ~1.5s per domain lookup.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = cfg.blockDoh,
|
||||
onCheckedChange = { onChange(cfg.copy(blockDoh = it)) },
|
||||
)
|
||||
}
|
||||
|
||||
// Bypass DoH toggle
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
"Bypass DoH",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Text(
|
||||
"Send browser DoH direct, not through tunnel. Faster DNS — queries are still encrypted.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = !cfg.tunnelDoh,
|
||||
onCheckedChange = { onChange(cfg.copy(tunnelDoh = !it)) },
|
||||
enabled = !cfg.blockDoh,
|
||||
)
|
||||
}
|
||||
|
||||
// Batch coalesce step slider
|
||||
Column {
|
||||
Text(
|
||||
|
||||
@@ -269,6 +269,22 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub bypass_doh_hosts: Vec<String>,
|
||||
|
||||
/// When true, immediately reject (close) any CONNECT to a known DoH
|
||||
/// endpoint. Takes priority over `tunnel_doh` — the connection is
|
||||
/// never established in either direction. Browsers fall back to system
|
||||
/// DNS, which tun2proxy handles via virtual DNS (instant, no tunnel
|
||||
/// round-trip). This eliminates the ~1.5s per-domain DoH overhead
|
||||
/// that #468's `tunnel_doh: true` default introduced.
|
||||
///
|
||||
/// Background: #468 changed `tunnel_doh` from false (bypass) to true
|
||||
/// (tunnel) because Iranian ISPs block direct DoH endpoints. But
|
||||
/// tunneling DoH costs an extra ~1.5s Apps Script round-trip per DNS
|
||||
/// lookup, which made every page load noticeably slower. Blocking
|
||||
/// DoH entirely avoids both problems: no ISP-visible DoH connection,
|
||||
/// no tunnel round-trip — browsers use the system DNS path instead.
|
||||
#[serde(default)]
|
||||
pub block_doh: bool,
|
||||
|
||||
/// Multi-edge domain-fronting groups. Each group is a triple of
|
||||
/// (edge IP, front SNI, member domains): when a CONNECT to one of
|
||||
/// the member domains arrives, the proxy MITMs at the local CA
|
||||
|
||||
@@ -246,6 +246,9 @@ pub struct RewriteCtx {
|
||||
/// `matches_doh_host` for matching, and config.rs `tunnel_doh` for
|
||||
/// the trade-off.
|
||||
pub bypass_doh: bool,
|
||||
/// When true, immediately reject connections to known DoH hosts.
|
||||
/// Takes priority over bypass_doh.
|
||||
pub block_doh: bool,
|
||||
/// User-supplied DoH hostnames added to the built-in default list.
|
||||
/// Same matching semantics as `passthrough_hosts`.
|
||||
pub bypass_doh_hosts: Vec<String>,
|
||||
@@ -504,6 +507,7 @@ impl ProxyServer {
|
||||
passthrough_hosts: config.passthrough_hosts.clone(),
|
||||
block_quic: config.block_quic,
|
||||
bypass_doh: !config.tunnel_doh,
|
||||
block_doh: config.block_doh,
|
||||
bypass_doh_hosts: config.bypass_doh_hosts.clone(),
|
||||
fronting_groups,
|
||||
});
|
||||
@@ -1581,6 +1585,18 @@ async fn dispatch_tunnel(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 0.4. DoH block. Reject connections to known DoH endpoints so browsers
|
||||
// fall back to system DNS (tun2proxy virtual DNS — instant).
|
||||
// Takes priority over bypass_doh.
|
||||
if rewrite_ctx.block_doh
|
||||
&& port == 443
|
||||
&& matches_doh_host(&host, &rewrite_ctx.bypass_doh_hosts)
|
||||
{
|
||||
tracing::info!("dispatch {}:{} -> blocked (block_doh)", host, port);
|
||||
drop(sock);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 0.5. DoH bypass. DNS-over-HTTPS is the dominant per-flow DNS cost
|
||||
// in Full mode (every browser name lookup costs a ~2 s Apps
|
||||
// Script round-trip), and the tunnel adds no privacy beyond
|
||||
|
||||
Reference in New Issue
Block a user