diff --git a/Cargo.lock b/Cargo.lock index 0fd01cc..7da62d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -496,6 +496,7 @@ dependencies = [ "bytes", "clap", "lanparty-ctrl", + "lanparty-obs", "lanparty-proto", "libc", "quinn", diff --git a/README.md b/README.md index 85f30bc..6a1eac8 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,9 @@ control-stream hello/welcome handshake, opens an AF_PACKET socket on the LAN interface with promiscuous packet membership, and bridges Ethernet frames between the relay and wired LAN until shutdown. It tracks remote-client source MACs seen from relay traffic and periodically emits small CAM refresh frames so -the physical switch keeps those MACs associated with the gateway port. +the physical switch keeps those MACs associated with the gateway port. Gateway +frame logs include direction, peer id when present, MACs, ethertype/length, +frame length, action, and drop reason. ## Windows Client diff --git a/crates/lanparty-gateway/Cargo.toml b/crates/lanparty-gateway/Cargo.toml index a99fa8f..195db59 100644 --- a/crates/lanparty-gateway/Cargo.toml +++ b/crates/lanparty-gateway/Cargo.toml @@ -8,6 +8,7 @@ anyhow.workspace = true bytes.workspace = true clap.workspace = true lanparty-ctrl = { path = "../lanparty-ctrl" } +lanparty-obs = { path = "../lanparty-obs" } lanparty-proto = { path = "../lanparty-proto" } libc.workspace = true quinn.workspace = true diff --git a/crates/lanparty-gateway/src/lib.rs b/crates/lanparty-gateway/src/lib.rs index 3973232..32a0449 100644 --- a/crates/lanparty-gateway/src/lib.rs +++ b/crates/lanparty-gateway/src/lib.rs @@ -22,6 +22,8 @@ use lanparty_ctrl::{ CONTROL_LENGTH_PREFIX_LEN, ControlMessage, EndpointHello, MAX_CONTROL_MESSAGE_LEN, RELAY_ALPN, RoomCode, ServerWelcome, decode_control_frame, encode_control_message, }; +#[cfg(target_os = "linux")] +use lanparty_obs::{FrameAction, FrameDirection, FrameLog}; use lanparty_proto::{ EthernetFrame, FrameType, MAX_STANDARD_ETHERNET_FRAME_LEN, MacAddr, decode_datagram, encode_datagram, @@ -244,12 +246,35 @@ impl GatewayConnection { return Ok(()); } lan_frame = read_lan_ethernet(&packet_socket) => { - send_gateway_ethernet(&connection, &welcome, &lan_frame?)?; + let lan_frame = lan_frame?; + send_gateway_ethernet(&connection, &welcome, &lan_frame)?; + println!( + "{}", + gateway_frame_log_line( + packet_socket.get_ref().interface(), + FrameDirection::LanToRemote, + Some(welcome.peer_id()), + &lan_frame, + FrameAction::Forwarded, + None, + ) + ); } relay_frame = recv_gateway_ethernet(&connection, &welcome) => { let relay_frame = relay_frame?; cam_refresh.observe_remote_frame(relay_frame.payload())?; write_lan_ethernet(&packet_socket, relay_frame.payload()).await?; + println!( + "{}", + gateway_frame_log_line( + packet_socket.get_ref().interface(), + FrameDirection::RemoteToLan, + Some(relay_frame.source_peer_id()), + relay_frame.payload(), + FrameAction::Forwarded, + None, + ) + ); } _ = cam_refresh_tick.tick() => { for frame in cam_refresh.refresh_frames() { @@ -315,6 +340,54 @@ async fn recv_gateway_ethernet( } } +#[cfg(target_os = "linux")] +fn gateway_frame_log_line( + interface: &str, + direction: FrameDirection, + peer_id: Option, + frame_bytes: &[u8], + action: FrameAction, + drop_reason: Option, +) -> String { + let log = match EthernetFrame::parse(frame_bytes) { + Ok(frame) => FrameLog::from_ethernet(direction, peer_id, action, drop_reason, frame), + Err(_) => FrameLog::malformed(direction, peer_id, frame_bytes.len()), + }; + let source_mac = log + .source_mac() + .map(|mac| mac.to_string()) + .unwrap_or_else(|| "-".to_owned()); + let destination_mac = log + .destination_mac() + .map(|mac| mac.to_string()) + .unwrap_or_else(|| "-".to_owned()); + let ethertype_or_len = log + .ethertype_or_len() + .map(|value| format!("0x{value:04x}")) + .unwrap_or_else(|| "-".to_owned()); + let peer_id = log + .peer_id() + .map(|peer_id| peer_id.to_string()) + .unwrap_or_else(|| "-".to_owned()); + let drop_reason = log + .drop_reason() + .map(|reason| format!("{reason:?}")) + .unwrap_or_else(|| "-".to_owned()); + + format!( + "gateway frame interface={} direction={:?} peer_id={} src={} dst={} ethertype_or_len={} len={} action={:?} drop_reason={}", + interface, + log.direction(), + peer_id, + source_mac, + destination_mac, + ethertype_or_len, + log.frame_len(), + log.action(), + drop_reason, + ) +} + #[cfg(target_os = "linux")] async fn read_lan_ethernet(packet_socket: &AsyncFd) -> Result { loop { @@ -656,6 +729,24 @@ mod tests { assert_eq!(refresh_frame.destination(), gateway_mac); } + #[cfg(target_os = "linux")] + #[test] + fn formats_gateway_frame_log_lines() { + let line = gateway_frame_log_line( + "eth0", + FrameDirection::RemoteToLan, + Some(7), + ðernet_frame(b"payload"), + FrameAction::Forwarded, + None, + ); + + assert_eq!( + line, + "gateway frame interface=eth0 direction=RemoteToLan peer_id=7 src=02:00:00:00:00:01 dst=02:00:00:00:00:02 ethertype_or_len=0x0800 len=21 action=Forwarded drop_reason=-" + ); + } + fn test_server_config() -> (ServerConfig, CertificateDer<'static>) { let certified_key = rcgen::generate_simple_self_signed(vec!["lanparty-relay.local".into()]).unwrap();