From 0ed440ffaac62d54f01e9d1f67f6d660e4964430 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Fri, 22 May 2026 06:21:48 +0200 Subject: [PATCH] fix(client): prefer non-link-local TAP IPv4 diagnostics Windows can report more than one unicast address on the TAP adapter. If an APIPA address remains present next to the real LAN DHCP address, choosing the first IPv4 address can make diagnostics warn about 169.254.x.x even though the adapter already has a usable LAN address. Prefer a non-link-local IPv4 address for TAP diagnostics, then fall back to any IPv4 address, then to the first non-IPv4 address. This keeps the existing APIPA warning when APIPA is the only IPv4 signal, but reports the real DHCP address when Windows exposes both addresses. README.md and TESTING.md now document that diagnostics prefer the real LAN IPv4 when several TAP addresses are visible. Test Plan: - cargo test -p lanparty-client-win - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - cargo fmt --check - git diff --check Refs: PLAN.md client diagnostics; TESTING.md TAP IP verification --- README.md | 5 +-- TESTING.md | 3 ++ crates/lanparty-client-win/src/main.rs | 42 ++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1791d89..91cec8d 100644 --- a/README.md +++ b/README.md @@ -259,8 +259,9 @@ It prints and reports client diagnostics snapshots with relay reachability, LAN-gateway presence, route-pinning, QUIC datagram budget, relay RTT, TAP status/IP, broadcast frame flow, frame/datagram counters, and drops. The periodic diagnostics refresh the TAP unicast IP so DHCP results that arrive -after bridging starts become visible in later status lines. Each snapshot also -emits short user-facing lines such as relay/gateway connection status, +after bridging starts become visible in later status lines, preferring a +non-link-local IPv4 address when Windows reports several TAP addresses. Each +snapshot also emits short user-facing lines such as relay/gateway connection status, relay-route and TAP readiness warnings, DHCP address presence, relay RTT, and broadcast-flow confirmation when those signals are observed. Malformed frames read from TAP, invalid or unauthorized source-MAC frames, L2 control-plane diff --git a/TESTING.md b/TESTING.md index 70e8ad7..06eef28 100644 --- a/TESTING.md +++ b/TESTING.md @@ -138,6 +138,9 @@ line should show: DHCP received: 10.x.x.x ``` +If Windows reports both a `169.254.x.x` TAP address and a real LAN IPv4 +address, the client diagnostics should prefer the real LAN address. + ## What To Verify 1. Relay sees both peers: diff --git a/crates/lanparty-client-win/src/main.rs b/crates/lanparty-client-win/src/main.rs index b9dae9b..12f872d 100644 --- a/crates/lanparty-client-win/src/main.rs +++ b/crates/lanparty-client-win/src/main.rs @@ -856,11 +856,23 @@ fn tap_unicast_ip(identity: NetworkInterfaceIdentity) -> Option { fn preferred_tap_ip( addresses: &[lanparty_client_route::InterfaceUnicastAddress], ) -> Option { + preferred_tap_ip_address(addresses.iter().map(|address| address.address())) +} + +#[cfg_attr(not(windows), allow(dead_code))] +fn preferred_tap_ip_address(addresses: impl IntoIterator) -> Option { + let addresses: Vec<_> = addresses.into_iter().collect(); addresses .iter() - .find(|address| matches!(address.address(), IpAddr::V4(_))) - .or_else(|| addresses.first()) - .map(|address| address.address()) + .copied() + .find(|address| matches!(address, IpAddr::V4(ip) if !ip.is_link_local())) + .or_else(|| { + addresses + .iter() + .copied() + .find(|address| matches!(address, IpAddr::V4(_))) + }) + .or_else(|| addresses.first().copied()) } #[cfg_attr(not(windows), allow(dead_code))] @@ -1180,6 +1192,30 @@ mod tests { assert_eq!(refreshed.ip().unwrap().to_string(), "10.73.42.51"); } + #[test] + fn prefers_non_link_local_tap_ipv4_for_diagnostics() { + assert_eq!( + preferred_tap_ip_address([ + "169.254.10.20".parse().unwrap(), + "10.73.42.51".parse().unwrap(), + "fe80::1".parse().unwrap(), + ]), + Some("10.73.42.51".parse().unwrap()) + ); + assert_eq!( + preferred_tap_ip_address([ + "169.254.10.20".parse().unwrap(), + "fe80::1".parse().unwrap() + ]), + Some("169.254.10.20".parse().unwrap()) + ); + assert_eq!( + preferred_tap_ip_address(["fe80::1".parse().unwrap()]), + Some("fe80::1".parse().unwrap()) + ); + assert_eq!(preferred_tap_ip_address([]), None); + } + #[test] fn formats_tap_default_route_warning() { assert_eq!(