fix(port): handle stale macOS wildcard guards

Co-authored-by: wonfen <eiwonfen@gmail.com>
This commit is contained in:
Tunglies
2026-08-22 04:17:15 +08:00
co-authored by wonfen
parent 2717aaadad
commit 629c00dc4a
+12 -5
View File
@@ -352,9 +352,8 @@ fn claims_overlap(left: &BindClaim, right: &BindClaim) -> bool {
/// rebind it at once (measured on macOS 26). "Is anything answering?" survives that asymmetry; /// rebind it at once (measured on macOS 26). "Is anything answering?" survives that asymmetry;
/// "can I bind?" does not. /// "can I bind?" does not.
/// ///
/// Restrictions, both deliberate: a wildcard claim can be blocked by a listener on any address, so /// Restrictions, both deliberate: every concrete guard for a wildcard claim must be checked before
/// one refused connection cannot clear it — leaving `allow-lan` setups exposed to the same false /// it can be cleared. UDP has no lingering state, so a refused UDP bind is always a live socket.
/// positive. UDP has no lingering state, so a refused UDP bind is always a live socket.
fn nothing_is_serving(claim: &BindClaim) -> bool { fn nothing_is_serving(claim: &BindClaim) -> bool {
const ANSWER_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(200); const ANSWER_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(200);
@@ -477,12 +476,17 @@ fn bind_claim(claim: &BindClaim) -> io::Result<Vec<Socket>> {
} }
guard_addresses.sort_unstable(); guard_addresses.sort_unstable();
guard_addresses.dedup(); guard_addresses.dedup();
let mut stale_guard = false;
for address in guard_addresses { for address in guard_addresses {
match bind_socket(&BindClaim::new(claim.name, address, claim.port, claim.transport)) { let guard_claim = BindClaim::new(claim.name, address, claim.port, claim.transport);
match bind_socket(&guard_claim) {
Ok(socket) => sockets.push(socket), Ok(socket) => sockets.push(socket),
// Only a conflict proves the port is taken; a vanished guard address proves nothing. // Only a conflict proves the port is taken; a vanished guard address proves nothing.
Err(error) if is_bind_conflict(&error) => { Err(error) if is_bind_conflict(&error) => {
// The caller only sees the claim, so name the address that actually lost. if nothing_is_serving(&guard_claim) {
stale_guard = true;
continue;
}
logging!( logging!(
warn, warn,
LogType::Network, LogType::Network,
@@ -497,6 +501,9 @@ fn bind_claim(claim: &BindClaim) -> io::Result<Vec<Socket>> {
Err(_) => {} Err(_) => {}
} }
} }
if stale_guard {
return Ok(sockets);
}
} }
let socket = bind_socket(claim)?; let socket = bind_socket(claim)?;