feat(client): print runtime diagnostics snapshots
The client now builds ClientDiagnostics snapshots from the connected session, known TAP state, route-pinning status, and tunnel counters. Windows prints one snapshot after TAP and relay-route setup, then repeats snapshots while bridging so frame/datagram counters and drops are visible during manual phase-1 tests. Non-Windows builds print the same relay and QUIC diagnostics with TAP fields marked unknown before waiting for Ctrl-C. TAP IP remains unknown until a later Windows adapter IP inspection slice wires that source of truth. Test Plan: - cargo fmt --check - cargo test -p lanparty-client-win - cargo clippy -p lanparty-client-win --all-targets -- -D warnings - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md
This commit is contained in:
@@ -21,12 +21,15 @@ use lanparty_client_route::{
|
||||
#[cfg(windows)]
|
||||
use lanparty_client_tap::TapAdapter;
|
||||
use lanparty_ctrl::RoomCode;
|
||||
use lanparty_obs::{ClientDiagnostics, RelayDiagnostics, TapDiagnostics};
|
||||
use lanparty_proto::MacAddr;
|
||||
|
||||
#[cfg(windows)]
|
||||
const TAP_INTERFACE_METRIC: u32 = 9_000;
|
||||
#[cfg(windows)]
|
||||
const RELAY_ROUTE_VERIFY_INTERVAL: Duration = Duration::from_secs(5);
|
||||
#[cfg(windows)]
|
||||
const CLIENT_DIAGNOSTICS_INTERVAL: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
@@ -130,11 +133,20 @@ async fn main() -> Result<()> {
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute) -> Result<()> {
|
||||
let OpenedTapAdapter { tap, _route_guard } = open_tap_adapter(session)?;
|
||||
let OpenedTapAdapter {
|
||||
tap,
|
||||
tap_diagnostics,
|
||||
_route_guard,
|
||||
} = open_tap_adapter(session)?;
|
||||
let relay_route =
|
||||
verify_relay_route_is_pinned(session.config().relay_addr().ip(), relay_route_pin)
|
||||
.context("relay route changed after TAP activation")?;
|
||||
print_verified_relay_route(&relay_route);
|
||||
print_client_diagnostics(&client_diagnostics_snapshot(
|
||||
session,
|
||||
true,
|
||||
tap_diagnostics.clone(),
|
||||
));
|
||||
println!(
|
||||
"bridging TAP frames; relay route is pinned and TAP route policy is scoped; press Ctrl-C to stop"
|
||||
);
|
||||
@@ -148,6 +160,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
|
||||
RELAY_ROUTE_VERIFY_INTERVAL,
|
||||
);
|
||||
relay_route_check.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
let mut diagnostics_check = tokio::time::interval_at(
|
||||
tokio::time::Instant::now() + CLIENT_DIAGNOSTICS_INTERVAL,
|
||||
CLIENT_DIAGNOSTICS_INTERVAL,
|
||||
);
|
||||
diagnostics_check.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -160,6 +177,13 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
|
||||
)
|
||||
.context("relay route changed while bridging")?;
|
||||
}
|
||||
_ = diagnostics_check.tick() => {
|
||||
print_client_diagnostics(&client_diagnostics_snapshot(
|
||||
session,
|
||||
true,
|
||||
tap_diagnostics.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,6 +191,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
|
||||
#[cfg(not(windows))]
|
||||
async fn run_client(session: &ClientSession) -> Result<()> {
|
||||
open_tap_adapter(session);
|
||||
print_client_diagnostics(&client_diagnostics_snapshot(
|
||||
session,
|
||||
false,
|
||||
TapDiagnostics::new(false, None, None, None),
|
||||
));
|
||||
println!("TAP frame pump and route pinning are not wired yet; press Ctrl-C to stop");
|
||||
|
||||
tokio::signal::ctrl_c()
|
||||
@@ -272,6 +301,7 @@ fn route_next_hop_label(next_hop: Option<std::net::IpAddr>) -> String {
|
||||
#[cfg(windows)]
|
||||
struct OpenedTapAdapter {
|
||||
tap: TapAdapter,
|
||||
tap_diagnostics: TapDiagnostics,
|
||||
_route_guard: TapRouteProtectionGuard,
|
||||
}
|
||||
|
||||
@@ -312,13 +342,66 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
|
||||
tap_interface.index(),
|
||||
tap_interface.luid()
|
||||
);
|
||||
let tap_diagnostics = TapDiagnostics::new(
|
||||
true,
|
||||
Some(driver_mac),
|
||||
Some(session.welcome().effective_tap_mtu()),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(OpenedTapAdapter {
|
||||
tap,
|
||||
tap_diagnostics,
|
||||
_route_guard: route_guard,
|
||||
})
|
||||
}
|
||||
|
||||
fn client_diagnostics_snapshot(
|
||||
session: &ClientSession,
|
||||
route_pinned: bool,
|
||||
tap: TapDiagnostics,
|
||||
) -> ClientDiagnostics {
|
||||
ClientDiagnostics::new(
|
||||
RelayDiagnostics::new(true, route_pinned),
|
||||
session.quic_diagnostics(),
|
||||
tap,
|
||||
session.stats_snapshot(),
|
||||
)
|
||||
}
|
||||
|
||||
fn print_client_diagnostics(diagnostics: &ClientDiagnostics) {
|
||||
println!("{}", format_client_diagnostics(diagnostics));
|
||||
}
|
||||
|
||||
fn format_client_diagnostics(diagnostics: &ClientDiagnostics) -> String {
|
||||
let stats = diagnostics.stats();
|
||||
format!(
|
||||
"client diagnostics: relay reachable {} route pinned {}; QUIC datagrams {} max {}; TAP found {} MAC {} MTU {} IP {}; frames tx {} rx {} datagrams tx {} rx {} drops {} malformed {}",
|
||||
yes_no(diagnostics.relay().reachable()),
|
||||
yes_no(diagnostics.relay().route_pinned()),
|
||||
yes_no(diagnostics.quic().datagram_supported()),
|
||||
optional_label(diagnostics.quic().max_datagram_size()),
|
||||
yes_no(diagnostics.tap().adapter_found()),
|
||||
optional_label(diagnostics.tap().mac()),
|
||||
optional_label(diagnostics.tap().mtu()),
|
||||
optional_label(diagnostics.tap().ip()),
|
||||
stats.ethernet_frames_tx(),
|
||||
stats.ethernet_frames_rx(),
|
||||
stats.datagrams_tx(),
|
||||
stats.datagrams_rx(),
|
||||
stats.dropped_frames(),
|
||||
stats.malformed_frames()
|
||||
)
|
||||
}
|
||||
|
||||
const fn yes_no(value: bool) -> &'static str {
|
||||
if value { "yes" } else { "no" }
|
||||
}
|
||||
|
||||
fn optional_label<T: std::fmt::Display>(value: Option<T>) -> String {
|
||||
value.map_or_else(|| "unknown".to_string(), |value| value.to_string())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
fn validate_tap_driver_mac(expected_mac: MacAddr, driver_mac: MacAddr) -> Result<()> {
|
||||
if driver_mac != expected_mac {
|
||||
@@ -524,6 +607,7 @@ fn open_tap_adapter(_session: &ClientSession) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use lanparty_obs::{QuicDiagnostics, TunnelStats};
|
||||
|
||||
#[test]
|
||||
fn accepts_matching_tap_driver_mac() {
|
||||
@@ -537,6 +621,41 @@ mod tests {
|
||||
assert!(error.to_string().contains("does not match tunnel identity"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_client_diagnostics_status_line() {
|
||||
let diagnostics = ClientDiagnostics::new(
|
||||
RelayDiagnostics::new(true, true),
|
||||
QuicDiagnostics::new(true, Some(1400)),
|
||||
TapDiagnostics::new(
|
||||
true,
|
||||
Some(mac(1)),
|
||||
Some(1200),
|
||||
Some("10.73.42.51".parse().unwrap()),
|
||||
),
|
||||
TunnelStats::new(1, 2, 3, 4, 5, 6),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
format_client_diagnostics(&diagnostics),
|
||||
"client diagnostics: relay reachable yes route pinned yes; QUIC datagrams yes max 1400; TAP found yes MAC 02:00:00:00:00:01 MTU 1200 IP 10.73.42.51; frames tx 1 rx 2 datagrams tx 3 rx 4 drops 5 malformed 6"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_missing_client_diagnostics_as_unknown() {
|
||||
let diagnostics = ClientDiagnostics::new(
|
||||
RelayDiagnostics::new(true, false),
|
||||
QuicDiagnostics::new(false, None),
|
||||
TapDiagnostics::new(false, None, None, None),
|
||||
TunnelStats::default(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
format_client_diagnostics(&diagnostics),
|
||||
"client diagnostics: relay reachable yes route pinned no; QUIC datagrams no max unknown; TAP found no MAC unknown MTU unknown IP unknown; frames tx 0 rx 0 datagrams tx 0 rx 0 drops 0 malformed 0"
|
||||
);
|
||||
}
|
||||
|
||||
const fn mac(last_octet: u8) -> MacAddr {
|
||||
MacAddr::new([0x02, 0, 0, 0, 0, last_octet])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user