feat(client): log filtered relay-to-TAP frames
Windows MVP debugging needs more than aggregate drop counters when LAN traffic reaches the client but is kept out of the TAP adapter. A DHCP or discovery failure is much easier to diagnose when the client log says which relayed frame was filtered and why. Expose a client receive outcome that preserves the existing accepted-frame API while allowing the Windows frame pump to log filtered RelayToTap frames with the source peer and drop reason. Document the new log signal in the README and manual MVP test guide. Test Plan: - cargo fmt --check - cargo test -p lanparty-client-core connects_to_relay_control_stream_as_client - cargo test -p lanparty-client-win formats_client_frame_log_lines - cargo test -p lanparty-client-core - cargo test -p lanparty-client-win - cargo test --workspace - cargo clippy -p lanparty-client-core --all-targets -- -D warnings - cargo clippy -p lanparty-client-win --all-targets -- -D warnings - cargo clippy --workspace --all-targets -- -D warnings - git diff --check - git diff --cached --check Windows-target check attempted: - cargo check -p lanparty-client-win --target x86_64-pc-windows-msvc The Windows-target check is still blocked on this Linux host before compiling lanparty-client-win because ring cannot find the MSVC lib.exe tool. Refs: MVP client diagnostics
This commit is contained in:
@@ -232,6 +232,19 @@ pub struct ReceivedEthernetFrame {
|
||||
payload: Bytes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FilteredRelayEthernetFrame {
|
||||
source_peer_id: u32,
|
||||
payload: Bytes,
|
||||
drop_reason: DropReason,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ClientReceiveOutcome {
|
||||
Accepted(ReceivedEthernetFrame),
|
||||
Filtered(FilteredRelayEthernetFrame),
|
||||
}
|
||||
|
||||
impl ReceivedEthernetFrame {
|
||||
#[must_use]
|
||||
pub const fn source_peer_id(&self) -> u32 {
|
||||
@@ -244,6 +257,23 @@ impl ReceivedEthernetFrame {
|
||||
}
|
||||
}
|
||||
|
||||
impl FilteredRelayEthernetFrame {
|
||||
#[must_use]
|
||||
pub const fn source_peer_id(&self) -> u32 {
|
||||
self.source_peer_id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn drop_reason(&self) -> DropReason {
|
||||
self.drop_reason
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClientSendOutcome {
|
||||
Sent,
|
||||
@@ -295,6 +325,10 @@ impl ClientSession {
|
||||
self.relay_io().recv_ethernet().await
|
||||
}
|
||||
|
||||
pub async fn recv_ethernet_outcome(&self) -> Result<ClientReceiveOutcome> {
|
||||
self.relay_io().recv_ethernet_outcome().await
|
||||
}
|
||||
|
||||
pub async fn recv_control_event(&self) -> Result<ControlMessage> {
|
||||
recv_control_event(&self.connection).await
|
||||
}
|
||||
@@ -429,6 +463,15 @@ impl ClientRelayIo {
|
||||
}
|
||||
|
||||
pub async fn recv_ethernet(&self) -> Result<ReceivedEthernetFrame> {
|
||||
loop {
|
||||
match self.recv_ethernet_outcome().await? {
|
||||
ClientReceiveOutcome::Accepted(frame) => return Ok(frame),
|
||||
ClientReceiveOutcome::Filtered(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn recv_ethernet_outcome(&self) -> Result<ClientReceiveOutcome> {
|
||||
loop {
|
||||
let datagram = self.connection.read_datagram().await?;
|
||||
self.stats.record_datagram_rx();
|
||||
@@ -453,26 +496,38 @@ impl ClientRelayIo {
|
||||
};
|
||||
|
||||
self.stats.record_ethernet_rx(ethernet_frame);
|
||||
if gateway_lan_safety_drop_reason(ethernet_frame).is_some() {
|
||||
if let Some(drop_reason) = gateway_lan_safety_drop_reason(ethernet_frame) {
|
||||
self.stats.record_dropped_frame();
|
||||
continue;
|
||||
return Ok(ClientReceiveOutcome::Filtered(FilteredRelayEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
payload: Bytes::copy_from_slice(packet.payload()),
|
||||
drop_reason: DropReason::from(drop_reason),
|
||||
}));
|
||||
}
|
||||
if ethernet_frame_exceeds_tap_mtu(
|
||||
ethernet_frame,
|
||||
usize::from(self.welcome.effective_tap_mtu()),
|
||||
) {
|
||||
self.stats.record_dropped_frame();
|
||||
continue;
|
||||
return Ok(ClientReceiveOutcome::Filtered(FilteredRelayEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
payload: Bytes::copy_from_slice(packet.payload()),
|
||||
drop_reason: DropReason::TapMtuExceeded,
|
||||
}));
|
||||
}
|
||||
if !is_accepted_relay_destination(ethernet_frame, self.virtual_mac) {
|
||||
self.stats.record_dropped_frame();
|
||||
continue;
|
||||
return Ok(ClientReceiveOutcome::Filtered(FilteredRelayEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
payload: Bytes::copy_from_slice(packet.payload()),
|
||||
drop_reason: DropReason::UnknownDestination,
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(ReceivedEthernetFrame {
|
||||
return Ok(ClientReceiveOutcome::Accepted(ReceivedEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
payload: Bytes::copy_from_slice(packet.payload()),
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,10 +998,44 @@ mod tests {
|
||||
.unwrap(),
|
||||
ClientSendOutcome::Sent
|
||||
);
|
||||
let received = tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let filtered =
|
||||
tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet_outcome())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let ClientReceiveOutcome::Filtered(filtered) = filtered else {
|
||||
panic!("expected filtered relay frame");
|
||||
};
|
||||
assert_eq!(filtered.source_peer_id(), 1);
|
||||
assert_eq!(filtered.drop_reason(), DropReason::ControlPlaneEtherType);
|
||||
assert_eq!(
|
||||
filtered.payload(),
|
||||
control_plane_ethernet_frame().as_slice()
|
||||
);
|
||||
|
||||
let filtered =
|
||||
tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet_outcome())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let ClientReceiveOutcome::Filtered(filtered) = filtered else {
|
||||
panic!("expected filtered misdirected relay frame");
|
||||
};
|
||||
assert_eq!(filtered.source_peer_id(), 1);
|
||||
assert_eq!(filtered.drop_reason(), DropReason::UnknownDestination);
|
||||
assert_eq!(
|
||||
filtered.payload(),
|
||||
misdirected_unicast_ethernet_frame().as_slice()
|
||||
);
|
||||
|
||||
let received =
|
||||
tokio::time::timeout(Duration::from_secs(5), relay_io.recv_ethernet_outcome())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let ClientReceiveOutcome::Accepted(received) = received else {
|
||||
panic!("expected accepted relay frame");
|
||||
};
|
||||
assert_eq!(received.source_peer_id(), 1);
|
||||
assert_eq!(
|
||||
received.payload(),
|
||||
|
||||
Reference in New Issue
Block a user