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
This commit is contained in:
2026-05-21 21:26:55 +02:00
parent 6adde91208
commit 3fa78fc935
2 changed files with 53 additions and 8 deletions
+2 -1
View File
@@ -191,7 +191,8 @@ adapter may need to be disabled/enabled or reinstalled before it reloads the
configured `NetworkAddress`. configured `NetworkAddress`.
It prints and reports client diagnostics snapshots with relay reachability, It prints and reports client diagnostics snapshots with relay reachability,
route-pinning, QUIC datagram budget, TAP status/IP, frame/datagram counters, 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 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 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 so later leave logs can identify a disconnected LAN gateway or client MAC when
+51 -7
View File
@@ -1,6 +1,9 @@
#[cfg(windows)] use std::{
use std::net::IpAddr; collections::BTreeMap,
use std::{collections::BTreeMap, fs, net::SocketAddr, path::PathBuf}; fs,
net::{IpAddr, SocketAddr},
path::PathBuf,
};
#[cfg(windows)] #[cfg(windows)]
use std::{ use std::{
sync::{Arc, mpsc}, sync::{Arc, mpsc},
@@ -139,6 +142,7 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
let OpenedTapAdapter { let OpenedTapAdapter {
tap, tap,
tap_diagnostics, tap_diagnostics,
tap_interface,
_route_guard, _route_guard,
} = open_tap_adapter(session)?; } = open_tap_adapter(session)?;
let relay_route = let relay_route =
@@ -147,7 +151,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
print_verified_relay_route(&relay_route); print_verified_relay_route(&relay_route);
print_and_report_client_diagnostics( print_and_report_client_diagnostics(
session, session,
&client_diagnostics_snapshot(session, true, tap_diagnostics.clone()), &client_diagnostics_snapshot(
session,
true,
current_tap_diagnostics(&tap_diagnostics, tap_interface),
),
) )
.await; .await;
println!( println!(
@@ -186,7 +194,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
_ = diagnostics_check.tick() => { _ = diagnostics_check.tick() => {
print_and_report_client_diagnostics( print_and_report_client_diagnostics(
session, session,
&client_diagnostics_snapshot(session, true, tap_diagnostics.clone()), &client_diagnostics_snapshot(
session,
true,
current_tap_diagnostics(&tap_diagnostics, tap_interface),
),
).await; ).await;
} }
} }
@@ -313,6 +325,7 @@ fn route_next_hop_label(next_hop: Option<std::net::IpAddr>) -> String {
struct OpenedTapAdapter { struct OpenedTapAdapter {
tap: TapAdapter, tap: TapAdapter,
tap_diagnostics: TapDiagnostics, tap_diagnostics: TapDiagnostics,
tap_interface: NetworkInterfaceIdentity,
_route_guard: TapRouteProtectionGuard, _route_guard: TapRouteProtectionGuard,
} }
@@ -353,17 +366,17 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
tap_interface.index(), tap_interface.index(),
tap_interface.luid() tap_interface.luid()
); );
let tap_ip = tap_unicast_ip(tap_interface);
let tap_diagnostics = TapDiagnostics::new( let tap_diagnostics = TapDiagnostics::new(
true, true,
Some(driver_mac), Some(driver_mac),
Some(session.welcome().effective_tap_mtu()), Some(session.welcome().effective_tap_mtu()),
tap_ip, None,
); );
Ok(OpenedTapAdapter { Ok(OpenedTapAdapter {
tap, tap,
tap_diagnostics, tap_diagnostics,
tap_interface,
_route_guard: route_guard, _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<IpAddr>) -> TapDiagnostics {
TapDiagnostics::new(base.adapter_found(), base.mac(), base.mtu(), ip)
}
fn print_client_diagnostics(diagnostics: &ClientDiagnostics) { fn print_client_diagnostics(diagnostics: &ClientDiagnostics) {
println!("{}", format_client_diagnostics(diagnostics)); 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::<IpAddr>().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] #[test]
fn formats_relay_lifecycle_events() { fn formats_relay_lifecycle_events() {
let gateway = ControlMessage::PeerJoined(PeerInfo::new(1, Role::Gateway, None).unwrap()); let gateway = ControlMessage::PeerJoined(PeerInfo::new(1, Role::Gateway, None).unwrap());