Phase 8c hotfix: skip probe deadline when no fallback exists
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Failing after 2s
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Failing after 2s
Live smoke on tank↔architect (both LAN) failed with 200ms probe: LAN handshake takes longer than that in the wild (TLS 1.3 with full cert chain + rustls startup on fresh endpoint). The old single-addr .connect() had no deadline, so pre-8c callers never noticed. Fix: when `tailscale` is `None`, treat LAN as unlimited — the probe deadline only matters as a fall-through trigger, and there's nothing to fall through to. Callers with a real fallback addr still get the fast-path routing behavior unchanged. +1 test (connect_lan_first_lan_only_ignores_probe_deadline) using a 1-nanosecond probe budget that a real handshake could never meet — must succeed anyway because no fallback exists. 376 tests pass (+1).
This commit is contained in:
@@ -545,6 +545,17 @@ impl QuicClient {
|
||||
lan_probe: std::time::Duration,
|
||||
) -> Result<(quinn::Connection, ConnectRoute)> {
|
||||
if let Some(lan_addr) = lan {
|
||||
// No fallback path → don't apply the probe deadline.
|
||||
// Otherwise a slow-but-fine LAN handshake can spuriously
|
||||
// fail when the operator never opted into a tailnet
|
||||
// fallback in the first place.
|
||||
if tailscale.is_none() {
|
||||
let conn = self
|
||||
.connect(lan_addr, expected_server_name)
|
||||
.await
|
||||
.with_context(|| format!("dialing LAN addr {lan_addr}"))?;
|
||||
return Ok((conn, ConnectRoute::Lan(lan_addr)));
|
||||
}
|
||||
match tokio::time::timeout(
|
||||
lan_probe,
|
||||
self.connect(lan_addr, expected_server_name),
|
||||
@@ -840,6 +851,34 @@ mod tests {
|
||||
accept_task.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_lan_first_lan_only_ignores_probe_deadline() {
|
||||
// Phase 8c hotfix: when no fallback exists, LAN dial gets
|
||||
// unlimited time — otherwise a slow-but-fine handshake
|
||||
// fails a caller that never opted into a fallback path.
|
||||
let (id_a, id_b) = NodeIdentity::generate_test_pair("a", "b").unwrap();
|
||||
let server = QuicServer::bind(loopback(0), id_b).unwrap();
|
||||
let server_addr = server.local_addr().unwrap();
|
||||
let accept_task = tokio::spawn(async move {
|
||||
if let Some(res) = server.accept().await {
|
||||
let conn = res.expect("accept");
|
||||
let _ = ping_handler_loop(conn).await;
|
||||
}
|
||||
server.shutdown().await;
|
||||
});
|
||||
let client = QuicClient::new(loopback(0), id_a).unwrap();
|
||||
// Absurdly short probe. Would fail if the deadline applied.
|
||||
let (conn, route) = client
|
||||
.connect_lan_first("b", Some(server_addr), None, Duration::from_nanos(1))
|
||||
.await
|
||||
.expect("connect_lan_first with no fallback ignores deadline");
|
||||
assert!(matches!(route, ConnectRoute::Lan(a) if a == server_addr));
|
||||
conn.close(VarInt::from_u32(0), b"done");
|
||||
client.shutdown().await;
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
accept_task.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_lan_first_errors_when_both_addrs_absent() {
|
||||
let (id_a, _id_b) = NodeIdentity::generate_test_pair("a", "b").unwrap();
|
||||
|
||||
Reference in New Issue
Block a user