mirror of https://github.com/ospab/ostp.git
fix(client): more resilient UDP handshake on lossy mobile links
A mobile-network connect showed the handshake completing with rtt=3321ms — it took several attempts because handshake datagrams were being lost, and each loss cost a full 1200ms retransmit window. Sometimes all four attempts were lost and the connect failed outright. Two changes, no increase in the worst-case budget: - UDP now retransmits over 6 windows of 800ms instead of 4 of 1200ms (≈ the same 4.8s total), so a lost handshake recovers faster and there are more chances before giving up / falling back to NAT64. - Each UDP attempt sends the handshake twice. A single dropped datagram no longer costs a whole window; whichever copy lands first is processed and the server's anti-replay drops the duplicate. UoT rides reliable TCP, so it still sends one. This targets loss-driven delay and failure, which the log shows. It does not cure a carrier that deterministically holds the flow for seconds regardless of retries — that is the throttling case, which needs the mimicry work, not more retransmits.
This commit is contained in:
parent
0bb7db4f01
commit
ff8598e512
|
|
@ -1165,7 +1165,10 @@ impl Bridge {
|
||||||
let mut success = false;
|
let mut success = false;
|
||||||
|
|
||||||
let is_uot = matches!(socket, crate::transport::Transport::Uot { .. });
|
let is_uot = matches!(socket, crate::transport::Transport::Uot { .. });
|
||||||
let (attempt_limit, attempt_timeout_ms) = if is_uot { (1, 8000) } else { (4, 1200) };
|
// UDP: more, shorter retransmit windows over the SAME total budget
|
||||||
|
// (6×800ms ≈ the old 4×1200ms), so a lost handshake on a mobile link
|
||||||
|
// recovers faster and there are more chances before giving up.
|
||||||
|
let (attempt_limit, attempt_timeout_ms) = if is_uot { (1, 8000) } else { (6, 800) };
|
||||||
|
|
||||||
// TTL-desync (UDP only, opt-in): fire decoy datagrams that reach an
|
// TTL-desync (UDP only, opt-in): fire decoy datagrams that reach an
|
||||||
// on-path DPI box but expire before the server, so the box classifies
|
// on-path DPI box but expire before the server, so the box classifies
|
||||||
|
|
@ -1224,9 +1227,18 @@ impl Bridge {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
tx.send(UiEvent::Log(format!("Handshake attempt {} lost. Retransmitting...", attempt))).await.ok();
|
tx.send(UiEvent::Log(format!("Handshake attempt {} lost. Retransmitting...", attempt))).await.ok();
|
||||||
}
|
}
|
||||||
|
// Send the handshake twice on UDP: a single dropped datagram
|
||||||
|
// otherwise costs a whole retransmit window, which on a lossy
|
||||||
|
// mobile link is the gap between a sub-second connect and a
|
||||||
|
// multi-second one. The duplicate is harmless — whichever copy
|
||||||
|
// arrives first is processed, and the server's anti-replay drops
|
||||||
|
// the other. UoT rides reliable TCP, so one send there.
|
||||||
|
let sends = if is_uot { 1 } else { 2 };
|
||||||
|
for _ in 0..sends {
|
||||||
if send_datagram(&socket, &handshake_frame, self.transport_mode == "udp").await.is_ok() {
|
if send_datagram(&socket, &handshake_frame, self.transport_mode == "udp").await.is_ok() {
|
||||||
self.metrics.bytes_sent.fetch_add(handshake_frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(handshake_frame.len() as u64, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match timeout(Duration::from_millis(attempt_timeout_ms), socket.recv(&mut buf)).await {
|
match timeout(Duration::from_millis(attempt_timeout_ms), socket.recv(&mut buf)).await {
|
||||||
Ok(Ok(n)) => {
|
Ok(Ok(n)) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue