From 3fa78fc935c7d40554422795b40ef0be6e40dad3 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 21 May 2026 21:26:55 +0200 Subject: [PATCH] feat(client): refresh TAP IP diagnostics The Windows client sampled TAP diagnostics once when opening the adapter. That can miss the useful DHCP result because the TAP interface may not receive a LAN address until after frame bridging starts. Keep the stable TAP diagnostics fields from startup, but retain the interface identity and re-read the TAP unicast IP whenever the periodic diagnostics tick prints and reports a snapshot. Later status lines can now show the DHCP address that arrives after the tunnel is already moving traffic. Test Plan: - cargo fmt --check - cargo test -p lanparty-client-win - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md --- README.md | 3 +- crates/lanparty-client-win/src/main.rs | 58 ++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dcf22df..ae1a6bc 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,8 @@ adapter may need to be disabled/enabled or reinstalled before it reloads the configured `NetworkAddress`. It prints and reports client diagnostics snapshots with relay reachability, route-pinning, QUIC datagram budget, TAP status/IP, frame/datagram counters, -and drops. +and drops. The periodic diagnostics refresh the TAP unicast IP so DHCP results +that arrive after bridging starts become visible in later status lines. Relay lifecycle events are logged as they arrive, including gateway joins and peer leaves. The client remembers peer identities from join and catch-up events so later leave logs can identify a disconnected LAN gateway or client MAC when diff --git a/crates/lanparty-client-win/src/main.rs b/crates/lanparty-client-win/src/main.rs index 09c8e70..3505990 100644 --- a/crates/lanparty-client-win/src/main.rs +++ b/crates/lanparty-client-win/src/main.rs @@ -1,6 +1,9 @@ -#[cfg(windows)] -use std::net::IpAddr; -use std::{collections::BTreeMap, fs, net::SocketAddr, path::PathBuf}; +use std::{ + collections::BTreeMap, + fs, + net::{IpAddr, SocketAddr}, + path::PathBuf, +}; #[cfg(windows)] use std::{ sync::{Arc, mpsc}, @@ -139,6 +142,7 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute) let OpenedTapAdapter { tap, tap_diagnostics, + tap_interface, _route_guard, } = open_tap_adapter(session)?; let relay_route = @@ -147,7 +151,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute) print_verified_relay_route(&relay_route); print_and_report_client_diagnostics( session, - &client_diagnostics_snapshot(session, true, tap_diagnostics.clone()), + &client_diagnostics_snapshot( + session, + true, + current_tap_diagnostics(&tap_diagnostics, tap_interface), + ), ) .await; println!( @@ -186,7 +194,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute) _ = diagnostics_check.tick() => { print_and_report_client_diagnostics( session, - &client_diagnostics_snapshot(session, true, tap_diagnostics.clone()), + &client_diagnostics_snapshot( + session, + true, + current_tap_diagnostics(&tap_diagnostics, tap_interface), + ), ).await; } } @@ -313,6 +325,7 @@ fn route_next_hop_label(next_hop: Option) -> String { struct OpenedTapAdapter { tap: TapAdapter, tap_diagnostics: TapDiagnostics, + tap_interface: NetworkInterfaceIdentity, _route_guard: TapRouteProtectionGuard, } @@ -353,17 +366,17 @@ fn open_tap_adapter(session: &ClientSession) -> Result { tap_interface.index(), tap_interface.luid() ); - let tap_ip = tap_unicast_ip(tap_interface); let tap_diagnostics = TapDiagnostics::new( true, Some(driver_mac), Some(session.welcome().effective_tap_mtu()), - tap_ip, + None, ); Ok(OpenedTapAdapter { tap, tap_diagnostics, + tap_interface, _route_guard: route_guard, }) } @@ -398,6 +411,19 @@ fn client_diagnostics_snapshot( ) } +#[cfg(windows)] +fn current_tap_diagnostics( + base: &TapDiagnostics, + identity: NetworkInterfaceIdentity, +) -> TapDiagnostics { + tap_diagnostics_with_ip(base, tap_unicast_ip(identity)) +} + +#[cfg_attr(not(windows), allow(dead_code))] +fn tap_diagnostics_with_ip(base: &TapDiagnostics, ip: Option) -> TapDiagnostics { + TapDiagnostics::new(base.adapter_found(), base.mac(), base.mtu(), ip) +} + fn print_client_diagnostics(diagnostics: &ClientDiagnostics) { println!("{}", format_client_diagnostics(diagnostics)); } @@ -801,6 +827,24 @@ mod tests { ); } + #[test] + fn refreshes_tap_diagnostics_ip_without_losing_static_fields() { + let base = TapDiagnostics::new( + true, + Some(mac(1)), + Some(1200), + Some("169.254.10.20".parse().unwrap()), + ); + + let refreshed = + tap_diagnostics_with_ip(&base, Some("10.73.42.51".parse::().unwrap())); + + assert!(refreshed.adapter_found()); + assert_eq!(refreshed.mac(), Some(mac(1))); + assert_eq!(refreshed.mtu(), Some(1200)); + assert_eq!(refreshed.ip().unwrap().to_string(), "10.73.42.51"); + } + #[test] fn formats_relay_lifecycle_events() { let gateway = ControlMessage::PeerJoined(PeerInfo::new(1, Role::Gateway, None).unwrap());