mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 05:44:35 +03:00
feat: add block_quic config option (#213)
w0l4i has been asking for client-side QUIC block since #213. Now implemented as a small config flag. When `block_quic = true`, the SOCKS5 UDP relay drops any datagram destined for port 443 — that's HTTP/3-over-UDP. The client's QUIC stack retries a couple of times and then falls back to TCP/HTTPS through the regular CONNECT path (which goes through the relay normally). Why client-side rather than server-side udpgw block: the udpgw block in #222 is bound to Full mode + Android tun2proxy. This covers everyone — apps_script users, desktop, Full mode, all the same path. Skipping at the SOCKS5 layer rather than the tunnel-node layer also avoids paying 200–500 ms tunnel-node round-trip per QUIC datagram drop, which compounds during browser retries. Silent drop is the contractually correct shape: SOCKS5 UDP wire has no `host unreachable` reply (RFC 1928 §6 only defines that for TCP CONNECT). Browsers' QUIC stacks have a "no response → fall back" timeout, so silent drop matches what the protocol expects. Default false (opt-in) — udpgw mitigates QUIC partly via persistent sockets, and a tiny minority of sites only support HTTP/3. Will ship in v1.7.5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,33 @@ pub struct Config {
|
||||
/// Issues #39, #127.
|
||||
#[serde(default)]
|
||||
pub passthrough_hosts: Vec<String>,
|
||||
|
||||
/// Block outbound QUIC (UDP/443) at the SOCKS5 listener.
|
||||
///
|
||||
/// QUIC is HTTP/3-over-UDP. In `apps_script` mode it's hopeless —
|
||||
/// Apps Script is HTTP-only, so QUIC datagrams either get refused
|
||||
/// outright (UDP ASSOCIATE rejected) or silently fall through to
|
||||
/// `raw-tcp direct` and fail in interesting ways. In `full` mode
|
||||
/// the tunnel-node CAN carry UDP, but QUIC's congestion control
|
||||
/// stacked on top of TCP-encapsulated transport produces TCP
|
||||
/// meltdown for any non-trivial bandwidth — browsers see <1 Mbps
|
||||
/// where the same site over plain HTTPS would do >50.
|
||||
///
|
||||
/// With `block_quic = true`, the SOCKS5 UDP relay drops any
|
||||
/// datagram destined for port 443 (silent UDP — caller's stack
|
||||
/// retries a few times then falls back). Browsers then re-issue
|
||||
/// the same request as TCP/HTTPS through the regular CONNECT
|
||||
/// path, which goes through the relay normally.
|
||||
///
|
||||
/// Why this is opt-in rather than always-on: for users on Full
|
||||
/// mode + udpgw (a recent path; v1.7.0+) the QUIC TCP-meltdown
|
||||
/// is partially mitigated by udpgw's persistent-socket reuse,
|
||||
/// and a tiny minority of sites only support HTTP/3 (rare). The
|
||||
/// flag lets users who care about consistency over peak speed
|
||||
/// opt out of QUIC at the source rather than discovering its
|
||||
/// failure modes later. Issue #213.
|
||||
#[serde(default)]
|
||||
pub block_quic: bool,
|
||||
}
|
||||
|
||||
fn default_fetch_ips_from_api() -> bool { false }
|
||||
|
||||
@@ -195,6 +195,10 @@ pub struct RewriteCtx {
|
||||
/// and pass through as plain TCP (optionally via upstream_socks5).
|
||||
/// See config.rs `passthrough_hosts` for matching rules. Issues #39, #127.
|
||||
pub passthrough_hosts: Vec<String>,
|
||||
/// If true, drop SOCKS5 UDP datagrams destined for port 443 so
|
||||
/// callers fall back to TCP/HTTPS. See config.rs `block_quic` for
|
||||
/// the trade-off. Issue #213.
|
||||
pub block_quic: bool,
|
||||
}
|
||||
|
||||
/// True if `host` matches any entry in the user's passthrough list.
|
||||
@@ -263,6 +267,7 @@ impl ProxyServer {
|
||||
mode,
|
||||
youtube_via_relay: config.youtube_via_relay,
|
||||
passthrough_hosts: config.passthrough_hosts.clone(),
|
||||
block_quic: config.block_quic,
|
||||
});
|
||||
|
||||
let socks5_port = config.socks5_port.unwrap_or(config.listen_port + 1);
|
||||
@@ -864,6 +869,30 @@ async fn handle_socks5_udp_associate(
|
||||
continue;
|
||||
};
|
||||
|
||||
// Issue #213: client-side QUIC block. UDP/443 is
|
||||
// HTTP/3 — drop the datagram silently so the client
|
||||
// stack retries a couple of times and then falls back
|
||||
// to TCP/HTTPS, which goes through the regular CONNECT
|
||||
// path. Skipping this at the SOCKS5 layer (rather than
|
||||
// letting it hit the tunnel-node) avoids paying the
|
||||
// 200–500 ms tunnel-node round-trip per dropped QUIC
|
||||
// datagram, which would otherwise compound during the
|
||||
// 1–3 retries before the browser falls back.
|
||||
//
|
||||
// Silent drop instead of an explicit error reply: the
|
||||
// SOCKS5 UDP wire has no "destination unreachable"
|
||||
// datagram — `0x04` only exists in TCP CONNECT replies
|
||||
// (RFC 1928 §6). The browser's QUIC stack already has
|
||||
// a "no response → fall back" timeout, so silent drop
|
||||
// is the contractually correct shape.
|
||||
if rewrite_ctx.block_quic && target.port == 443 {
|
||||
tracing::debug!(
|
||||
"udp dropped: block_quic=true, target {}:443",
|
||||
target.host
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// RFC 1928 §6: lock to the first VALID datagram's source
|
||||
// port. Subsequent datagrams must come from the same
|
||||
// (ip, port) pair.
|
||||
|
||||
Reference in New Issue
Block a user