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:
Generated
+1
@@ -474,6 +474,7 @@ dependencies = [
|
|||||||
"lanparty-client-route",
|
"lanparty-client-route",
|
||||||
"lanparty-client-tap",
|
"lanparty-client-tap",
|
||||||
"lanparty-ctrl",
|
"lanparty-ctrl",
|
||||||
|
"lanparty-obs",
|
||||||
"lanparty-proto",
|
"lanparty-proto",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -155,3 +155,5 @@ interface metric and disables TAP default routes while it runs, periodically
|
|||||||
rechecks that the relay route remains pinned, then restores the previous route
|
rechecks that the relay route remains pinned, then restores the previous route
|
||||||
policy on exit. Until automatic TAP MAC configuration is wired, startup fails
|
policy on exit. Until automatic TAP MAC configuration is wired, startup fails
|
||||||
before bridging if the driver-reported MAC does not match the tunnel identity.
|
before bridging if the driver-reported MAC does not match the tunnel identity.
|
||||||
|
It prints client diagnostics snapshots with relay reachability, route-pinning,
|
||||||
|
QUIC datagram budget, TAP status, frame/datagram counters, and drops.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ anyhow.workspace = true
|
|||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
lanparty-client-core = { path = "../lanparty-client-core" }
|
lanparty-client-core = { path = "../lanparty-client-core" }
|
||||||
lanparty-ctrl = { path = "../lanparty-ctrl" }
|
lanparty-ctrl = { path = "../lanparty-ctrl" }
|
||||||
|
lanparty-obs = { path = "../lanparty-obs" }
|
||||||
lanparty-proto = { path = "../lanparty-proto" }
|
lanparty-proto = { path = "../lanparty-proto" }
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ use lanparty_client_route::{
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use lanparty_client_tap::TapAdapter;
|
use lanparty_client_tap::TapAdapter;
|
||||||
use lanparty_ctrl::RoomCode;
|
use lanparty_ctrl::RoomCode;
|
||||||
|
use lanparty_obs::{ClientDiagnostics, RelayDiagnostics, TapDiagnostics};
|
||||||
use lanparty_proto::MacAddr;
|
use lanparty_proto::MacAddr;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const TAP_INTERFACE_METRIC: u32 = 9_000;
|
const TAP_INTERFACE_METRIC: u32 = 9_000;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const RELAY_ROUTE_VERIFY_INTERVAL: Duration = Duration::from_secs(5);
|
const RELAY_ROUTE_VERIFY_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
|
#[cfg(windows)]
|
||||||
|
const CLIENT_DIAGNOSTICS_INTERVAL: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
@@ -130,11 +133,20 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute) -> Result<()> {
|
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 =
|
let relay_route =
|
||||||
verify_relay_route_is_pinned(session.config().relay_addr().ip(), relay_route_pin)
|
verify_relay_route_is_pinned(session.config().relay_addr().ip(), relay_route_pin)
|
||||||
.context("relay route changed after TAP activation")?;
|
.context("relay route changed after TAP activation")?;
|
||||||
print_verified_relay_route(&relay_route);
|
print_verified_relay_route(&relay_route);
|
||||||
|
print_client_diagnostics(&client_diagnostics_snapshot(
|
||||||
|
session,
|
||||||
|
true,
|
||||||
|
tap_diagnostics.clone(),
|
||||||
|
));
|
||||||
println!(
|
println!(
|
||||||
"bridging TAP frames; relay route is pinned and TAP route policy is scoped; press Ctrl-C to stop"
|
"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_VERIFY_INTERVAL,
|
||||||
);
|
);
|
||||||
relay_route_check.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
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 {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -160,6 +177,13 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
|
|||||||
)
|
)
|
||||||
.context("relay route changed while bridging")?;
|
.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))]
|
#[cfg(not(windows))]
|
||||||
async fn run_client(session: &ClientSession) -> Result<()> {
|
async fn run_client(session: &ClientSession) -> Result<()> {
|
||||||
open_tap_adapter(session);
|
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");
|
println!("TAP frame pump and route pinning are not wired yet; press Ctrl-C to stop");
|
||||||
|
|
||||||
tokio::signal::ctrl_c()
|
tokio::signal::ctrl_c()
|
||||||
@@ -272,6 +301,7 @@ fn route_next_hop_label(next_hop: Option<std::net::IpAddr>) -> String {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
struct OpenedTapAdapter {
|
struct OpenedTapAdapter {
|
||||||
tap: TapAdapter,
|
tap: TapAdapter,
|
||||||
|
tap_diagnostics: TapDiagnostics,
|
||||||
_route_guard: TapRouteProtectionGuard,
|
_route_guard: TapRouteProtectionGuard,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,13 +342,66 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
|
|||||||
tap_interface.index(),
|
tap_interface.index(),
|
||||||
tap_interface.luid()
|
tap_interface.luid()
|
||||||
);
|
);
|
||||||
|
let tap_diagnostics = TapDiagnostics::new(
|
||||||
|
true,
|
||||||
|
Some(driver_mac),
|
||||||
|
Some(session.welcome().effective_tap_mtu()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(OpenedTapAdapter {
|
Ok(OpenedTapAdapter {
|
||||||
tap,
|
tap,
|
||||||
|
tap_diagnostics,
|
||||||
_route_guard: route_guard,
|
_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))]
|
#[cfg_attr(not(windows), allow(dead_code))]
|
||||||
fn validate_tap_driver_mac(expected_mac: MacAddr, driver_mac: MacAddr) -> Result<()> {
|
fn validate_tap_driver_mac(expected_mac: MacAddr, driver_mac: MacAddr) -> Result<()> {
|
||||||
if driver_mac != expected_mac {
|
if driver_mac != expected_mac {
|
||||||
@@ -524,6 +607,7 @@ fn open_tap_adapter(_session: &ClientSession) {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use lanparty_obs::{QuicDiagnostics, TunnelStats};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn accepts_matching_tap_driver_mac() {
|
fn accepts_matching_tap_driver_mac() {
|
||||||
@@ -537,6 +621,41 @@ mod tests {
|
|||||||
assert!(error.to_string().contains("does not match tunnel identity"));
|
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 {
|
const fn mac(last_octet: u8) -> MacAddr {
|
||||||
MacAddr::new([0x02, 0, 0, 0, 0, last_octet])
|
MacAddr::new([0x02, 0, 0, 0, 0, last_octet])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user