feat(client): log relay lifecycle events
The Windows client now listens for relay control events while the TAP frame pump is running and logs peer lifecycle updates as they arrive. Gateway joins get a clear LAN-gateway message, client joins include their virtual MAC, and peer leaves include the relay-provided reason. The non-Windows placeholder path also listens for the same events while waiting for Ctrl-C. That keeps lifecycle diagnostics visible in local relay/client smoke tests even before the Windows TAP path can be exercised on a real machine. 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:
@@ -165,3 +165,5 @@ 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.
|
||||
It prints client diagnostics snapshots with relay reachability, route-pinning,
|
||||
QUIC datagram budget, TAP status/IP, frame/datagram counters, and drops.
|
||||
Relay lifecycle events are logged as they arrive, including gateway joins and
|
||||
peer leaves.
|
||||
|
||||
@@ -22,7 +22,7 @@ use lanparty_client_route::{
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use lanparty_client_tap::TapAdapter;
|
||||
use lanparty_ctrl::RoomCode;
|
||||
use lanparty_ctrl::{ControlMessage, Role, RoomCode};
|
||||
use lanparty_obs::{ClientDiagnostics, RelayDiagnostics, TapDiagnostics};
|
||||
use lanparty_proto::MacAddr;
|
||||
|
||||
@@ -156,6 +156,8 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
|
||||
|
||||
let frame_pump = run_tap_frame_pump(session.relay_io(), tap);
|
||||
tokio::pin!(frame_pump);
|
||||
let control_events = run_control_event_log(session);
|
||||
tokio::pin!(control_events);
|
||||
let shutdown = tokio::signal::ctrl_c();
|
||||
tokio::pin!(shutdown);
|
||||
let mut relay_route_check = tokio::time::interval_at(
|
||||
@@ -172,6 +174,7 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = &mut frame_pump => return result,
|
||||
result = &mut control_events => return result,
|
||||
result = &mut shutdown => return result.context("failed to wait for Ctrl-C"),
|
||||
_ = relay_route_check.tick() => {
|
||||
verify_relay_route_is_pinned(
|
||||
@@ -201,9 +204,15 @@ async fn run_client(session: &ClientSession) -> Result<()> {
|
||||
));
|
||||
println!("TAP frame pump and route pinning are not wired yet; press Ctrl-C to stop");
|
||||
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.context("failed to wait for Ctrl-C")
|
||||
let control_events = run_control_event_log(session);
|
||||
tokio::pin!(control_events);
|
||||
let shutdown = tokio::signal::ctrl_c();
|
||||
tokio::pin!(shutdown);
|
||||
|
||||
tokio::select! {
|
||||
result = &mut control_events => result,
|
||||
result = &mut shutdown => result.context("failed to wait for Ctrl-C"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -406,6 +415,42 @@ fn optional_label<T: std::fmt::Display>(value: Option<T>) -> String {
|
||||
value.map_or_else(|| "unknown".to_string(), |value| value.to_string())
|
||||
}
|
||||
|
||||
async fn run_control_event_log(session: &ClientSession) -> Result<()> {
|
||||
loop {
|
||||
let event = session
|
||||
.recv_control_event()
|
||||
.await
|
||||
.context("failed to receive relay control event")?;
|
||||
println!("{}", format_control_event(&event));
|
||||
}
|
||||
}
|
||||
|
||||
fn format_control_event(event: &ControlMessage) -> String {
|
||||
match event {
|
||||
ControlMessage::PeerJoined(peer) if peer.role() == Role::Gateway => {
|
||||
format!(
|
||||
"relay event: LAN gateway connected as peer {}",
|
||||
peer.peer_id()
|
||||
)
|
||||
}
|
||||
ControlMessage::PeerJoined(peer) => {
|
||||
let mac = peer
|
||||
.mac()
|
||||
.map(|mac| mac.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
format!(
|
||||
"relay event: client peer {} joined with MAC {}",
|
||||
peer.peer_id(),
|
||||
mac
|
||||
)
|
||||
}
|
||||
ControlMessage::PeerLeft { peer_id, reason } => {
|
||||
format!("relay event: peer {peer_id} left ({reason:?})")
|
||||
}
|
||||
_ => format!("relay event: {event:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn tap_unicast_ip(identity: NetworkInterfaceIdentity) -> Option<IpAddr> {
|
||||
match lanparty_client_route::interface_unicast_addresses(identity) {
|
||||
@@ -635,6 +680,7 @@ fn open_tap_adapter(_session: &ClientSession) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use lanparty_ctrl::{DisconnectReason, PeerInfo};
|
||||
use lanparty_obs::{QuicDiagnostics, TunnelStats};
|
||||
|
||||
#[test]
|
||||
@@ -684,6 +730,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_relay_lifecycle_events() {
|
||||
let gateway = ControlMessage::PeerJoined(PeerInfo::new(1, Role::Gateway, None).unwrap());
|
||||
let client =
|
||||
ControlMessage::PeerJoined(PeerInfo::new(2, Role::Client, Some(mac(2))).unwrap());
|
||||
let left = ControlMessage::PeerLeft {
|
||||
peer_id: 2,
|
||||
reason: DisconnectReason::Normal,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
format_control_event(&gateway),
|
||||
"relay event: LAN gateway connected as peer 1"
|
||||
);
|
||||
assert_eq!(
|
||||
format_control_event(&client),
|
||||
"relay event: client peer 2 joined with MAC 02:00:00:00:00:02"
|
||||
);
|
||||
assert_eq!(
|
||||
format_control_event(&left),
|
||||
"relay event: peer 2 left (Normal)"
|
||||
);
|
||||
}
|
||||
|
||||
const fn mac(last_octet: u8) -> MacAddr {
|
||||
MacAddr::new([0x02, 0, 0, 0, 0, last_octet])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user