diff --git a/README.md b/README.md index 677bee9..4d0a345 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/crates/lanparty-client-win/src/main.rs b/crates/lanparty-client-win/src/main.rs index 070de86..4b59279 100644 --- a/crates/lanparty-client-win/src/main.rs +++ b/crates/lanparty-client-win/src/main.rs @@ -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(value: Option) -> 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 { 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]) }