mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-05 23:51:04 +03:00
fix: give the UDP NAT test a real window when the TCP clock is faked
The punch request carries udp_port only if the rendezvous server's TestNatResponse has arrived, and the wait for it was bounded by rtt / 2 — half the TCP connect time, on the assumption that TCP and UDP round trips are comparable and the test, started earlier, has already answered. A transparent TCP proxy breaks that assumption: a TUN-mode VPN on the host, or a redirect-mode proxy on the LAN gateway serving every device behind it, completes the handshake locally in ~3ms while the real UDP round trip is hundreds of ms. Log-confirmed against 5.161.65.208: ping 341ms, TCP connect 3.7ms, connect to a dead port there "succeeds" just as fast. The window collapsed to ~1.5ms, udp_port stayed 0 on every attempt, and UDP punch was never even requested — although UDP itself passes such gateways untouched. So use the TCP clock only when it is believable: below a plausible WAN round trip it says nothing about the UDP path, and a flat ceiling applies instead. The loop still exits the moment the port arrives, so a genuinely nearby server pays nothing and only a UDP-dead network waits out the ceiling — on the udp-carrying round alone, while the parallel pure-TCP round is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016HV43uh1ztv6Wm5qi3Y1ne
This commit is contained in:
@@ -595,6 +595,28 @@ impl Client {
|
||||
/// links; short enough that UDP-blocked networks settle on relay without a noticeable wait.
|
||||
const WEBRTC_PREFER_WINDOW_MS: u64 = 2500;
|
||||
|
||||
/// UDP-NAT-test wait when the TCP clock is implausible (see TCP_RTT_PLAUSIBLE_MIN). The
|
||||
/// normal bound is `rtt / 2`: the test has been running since before the TCP connect, so on
|
||||
/// a network where TCP RTT ~ UDP RTT its response has already landed. A transparent TCP
|
||||
/// proxy — a TUN-mode VPN on the host, or a redirect-mode proxy on the LAN gateway (soft
|
||||
/// router), which fakes the handshake for every device behind it — breaks that by answering
|
||||
/// in ~3ms while the real UDP round trip is hundreds of ms: the window collapsed to ~1.5ms,
|
||||
/// udp_port stayed 0, and UDP punch never ran on such networks. The wait still exits the instant the port
|
||||
/// arrives, so a genuinely nearby server (LAN hbbs) pays nothing; only UDP-dead networks
|
||||
/// wait out the full grace, and only on this round — the pure-TCP fallback round never
|
||||
/// waits. Sized as a ceiling on real-world rendezvous RTTs plus one 20ms retransmit
|
||||
/// (intercontinental ~300ms); paths slower than that lose UDP punch under a proxy, which
|
||||
/// is today's behavior, not a regression.
|
||||
const UDP_NAT_TEST_GRACE: Duration = Duration::from_millis(400);
|
||||
|
||||
/// Below this, the measured TCP connect time is not a believable WAN round trip — it was
|
||||
/// answered inside the LAN (host TUN proxy, gateway transparent proxy, or a genuinely local
|
||||
/// server) — and must not be used to size the UDP window.
|
||||
/// Generous on purpose: over-triggering costs nothing (the wait exits on arrival, and a
|
||||
/// UDP-dead round loses to the racing fallback anyway), while a tight bound would let a
|
||||
/// busy proxy's occasional ~80ms handshake slip through and silently drop UDP punch.
|
||||
const TCP_RTT_PLAUSIBLE_MIN: Duration = Duration::from_millis(100);
|
||||
|
||||
/// Delay before re-sending an ICE candidate over the rendezvous route once. The hop to the
|
||||
/// peer can be UDP (the controlled side's mediator channel), so a candidate can be lost in
|
||||
/// flight; the remote ICE agent dedups repeats, so the second copy is free.
|
||||
@@ -788,13 +810,20 @@ impl Client {
|
||||
.map_err(|e| anyhow!("Failed to secure tcp: {}", e))?;
|
||||
} else if let Some(udp) = udp.1.as_ref() {
|
||||
let tm = Instant::now();
|
||||
// rtt is the TCP connect time. When it is too short to be a real WAN round trip it
|
||||
// says nothing about the UDP path (a TUN VPN or the LAN gateway answered the
|
||||
// handshake, not the server), so fall back to the flat grace; otherwise trust it.
|
||||
let udp_nat_wait = if rtt < Self::TCP_RTT_PLAUSIBLE_MIN {
|
||||
Self::UDP_NAT_TEST_GRACE
|
||||
} else {
|
||||
rtt / 2
|
||||
};
|
||||
loop {
|
||||
let port = *udp.lock().unwrap();
|
||||
if port > 0 {
|
||||
break;
|
||||
}
|
||||
// await for 0.5 RTT
|
||||
if tm.elapsed() > rtt / 2 {
|
||||
if tm.elapsed() > udp_nat_wait {
|
||||
break;
|
||||
}
|
||||
hbb_common::sleep(0.001).await;
|
||||
|
||||
Reference in New Issue
Block a user