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,
|
lan_probe: std::time::Duration,
|
||||||
) -> Result<(quinn::Connection, ConnectRoute)> {
|
) -> Result<(quinn::Connection, ConnectRoute)> {
|
||||||
if let Some(lan_addr) = lan {
|
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(
|
match tokio::time::timeout(
|
||||||
lan_probe,
|
lan_probe,
|
||||||
self.connect(lan_addr, expected_server_name),
|
self.connect(lan_addr, expected_server_name),
|
||||||
@@ -840,6 +851,34 @@ mod tests {
|
|||||||
accept_task.abort();
|
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]
|
#[tokio::test]
|
||||||
async fn connect_lan_first_errors_when_both_addrs_absent() {
|
async fn connect_lan_first_errors_when_both_addrs_absent() {
|
||||||
let (id_a, _id_b) = NodeIdentity::generate_test_pair("a", "b").unwrap();
|
let (id_a, _id_b) = NodeIdentity::generate_test_pair("a", "b").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user