diff --git a/README.md b/README.md index 52203af..3b6a7d2 100644 --- a/README.md +++ b/README.md @@ -167,4 +167,6 @@ 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. +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 +that peer was known. diff --git a/crates/lanparty-client-win/src/main.rs b/crates/lanparty-client-win/src/main.rs index 4b59279..efab30d 100644 --- a/crates/lanparty-client-win/src/main.rs +++ b/crates/lanparty-client-win/src/main.rs @@ -1,6 +1,6 @@ #[cfg(windows)] use std::net::IpAddr; -use std::{fs, net::SocketAddr, path::PathBuf}; +use std::{collections::BTreeMap, fs, net::SocketAddr, path::PathBuf}; #[cfg(windows)] use std::{ sync::{Arc, mpsc}, @@ -22,7 +22,7 @@ use lanparty_client_route::{ }; #[cfg(windows)] use lanparty_client_tap::TapAdapter; -use lanparty_ctrl::{ControlMessage, Role, RoomCode}; +use lanparty_ctrl::{ControlMessage, PeerInfo, Role, RoomCode}; use lanparty_obs::{ClientDiagnostics, RelayDiagnostics, TapDiagnostics}; use lanparty_proto::MacAddr; @@ -416,24 +416,73 @@ fn optional_label(value: Option) -> String { } async fn run_control_event_log(session: &ClientSession) -> Result<()> { + let mut formatter = ControlEventFormatter::default(); + loop { let event = session .recv_control_event() .await .context("failed to receive relay control event")?; - println!("{}", format_control_event(&event)); + println!("{}", formatter.format(&event)); } } +#[cfg(test)] fn format_control_event(event: &ControlMessage) -> String { - match event { - ControlMessage::PeerJoined(peer) if peer.role() == Role::Gateway => { + ControlEventFormatter::default().format(event) +} + +#[derive(Debug, Default)] +struct ControlEventFormatter { + peers: BTreeMap, +} + +impl ControlEventFormatter { + fn format(&mut self, event: &ControlMessage) -> String { + match event { + ControlMessage::PeerJoined(peer) => { + self.peers.insert(peer.peer_id(), peer.clone()); + format_peer_joined(peer) + } + ControlMessage::PeerLeft { peer_id, reason } => { + let Some(peer) = self.peers.remove(peer_id) else { + return format!("relay event: peer {peer_id} left ({reason:?})"); + }; + + match peer.role() { + Role::Gateway => { + format!( + "relay event: LAN gateway disconnected (peer {}, {reason:?})", + peer.peer_id() + ) + } + Role::Client => { + let mac = peer + .mac() + .map(|mac| mac.to_string()) + .unwrap_or_else(|| "unknown".to_string()); + format!( + "relay event: client peer {} with MAC {} left ({reason:?})", + peer.peer_id(), + mac + ) + } + } + } + _ => format!("relay event: {event:?}"), + } + } +} + +fn format_peer_joined(peer: &PeerInfo) -> String { + match peer.role() { + Role::Gateway => { format!( "relay event: LAN gateway connected as peer {}", peer.peer_id() ) } - ControlMessage::PeerJoined(peer) => { + Role::Client => { let mac = peer .mac() .map(|mac| mac.to_string()) @@ -444,10 +493,6 @@ fn format_control_event(event: &ControlMessage) -> String { mac ) } - ControlMessage::PeerLeft { peer_id, reason } => { - format!("relay event: peer {peer_id} left ({reason:?})") - } - _ => format!("relay event: {event:?}"), } } @@ -735,22 +780,39 @@ mod tests { 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 { + let unknown_left = ControlMessage::PeerLeft { + peer_id: 3, + reason: DisconnectReason::Normal, + }; + let client_left = ControlMessage::PeerLeft { peer_id: 2, reason: DisconnectReason::Normal, }; + let gateway_left = ControlMessage::PeerLeft { + peer_id: 1, + reason: DisconnectReason::Normal, + }; + let mut formatter = ControlEventFormatter::default(); assert_eq!( - format_control_event(&gateway), + formatter.format(&gateway), "relay event: LAN gateway connected as peer 1" ); assert_eq!( - format_control_event(&client), + formatter.format(&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)" + format_control_event(&unknown_left), + "relay event: peer 3 left (Normal)" + ); + assert_eq!( + formatter.format(&client_left), + "relay event: client peer 2 with MAC 02:00:00:00:00:02 left (Normal)" + ); + assert_eq!( + formatter.format(&gateway_left), + "relay event: LAN gateway disconnected (peer 1, Normal)" ); }