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!(