diff --git a/README.md b/README.md index 135d1f7..9575270 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,8 @@ replies with `welcome` or `reject`, and forwards live Ethernet QUIC datagrams between accepted peers in the same room. It currently uses a generated self-signed development certificate; `--dev-cert-der-out` writes that certificate so the gateway and client can pin it in development. Production -certificate handling remains future work. +certificate handling remains future work. Ethernet forwarding decisions are +logged with room, peer, MAC, ethertype, action, drop reason, and target count. ## Gateway diff --git a/crates/lanparty-relay/src/server.rs b/crates/lanparty-relay/src/server.rs index c284059..1eb7411 100644 --- a/crates/lanparty-relay/src/server.rs +++ b/crates/lanparty-relay/src/server.rs @@ -7,14 +7,15 @@ use lanparty_ctrl::{ MAX_CONTROL_MESSAGE_LEN, PeerInfo, RELAY_ALPN, Reject, RejectReason, Role, RoomCode, ServerWelcome, decode_control_frame, encode_control_message, }; -use lanparty_proto::{FrameType, decode_datagram, encode_datagram}; +use lanparty_obs::{FrameDirection, FrameLog}; +use lanparty_proto::{EthernetFrame, FrameType, decode_datagram, encode_datagram}; use quinn::crypto::rustls::QuicServerConfig; use quinn::{Endpoint, Incoming, SendStream, ServerConfig, TransportConfig}; use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; use std::collections::HashMap; use tokio::sync::Mutex; -use crate::{RelayConfig, RoomRegistry}; +use crate::{ForwardingDecision, RelayConfig, RoomRegistry}; const DATAGRAM_BUFFER_BYTES: usize = 4 * 1024 * 1024; const MAX_CONTROL_FRAME_LEN: usize = CONTROL_LENGTH_PREFIX_LEN + MAX_CONTROL_MESSAGE_LEN; @@ -278,6 +279,15 @@ async fn forward_peer_datagram( accepted.peer.peer_id(), packet.payload(), )?; + println!( + "{}", + relay_frame_log_line( + &accepted.room, + accepted.peer.peer_id(), + packet.payload(), + &decision + ) + ); let target_peer_ids = decision.targets().to_vec(); if target_peer_ids.is_empty() { return Ok(()); @@ -312,6 +322,58 @@ async fn forward_peer_datagram( Ok(()) } +fn relay_frame_log_line( + room: &RoomCode, + ingress_peer_id: u32, + frame_bytes: &[u8], + decision: &ForwardingDecision, +) -> String { + let log = match EthernetFrame::parse(frame_bytes) { + Ok(frame) => FrameLog::from_ethernet( + FrameDirection::RelayIngress, + Some(ingress_peer_id), + decision.action(), + decision.drop_reason(), + frame, + ), + Err(_) => FrameLog::malformed( + FrameDirection::RelayIngress, + Some(ingress_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 drop_reason = log + .drop_reason() + .map(|reason| format!("{reason:?}")) + .unwrap_or_else(|| "-".to_owned()); + + format!( + "relay frame room={} direction={:?} peer_id={} src={} dst={} ethertype_or_len={} len={} action={:?} drop_reason={} targets={}", + room, + log.direction(), + log.peer_id().unwrap_or(ingress_peer_id), + source_mac, + destination_mac, + ethertype_or_len, + log.frame_len(), + log.action(), + drop_reason, + decision.targets().len() + ) +} + async fn collect_target_sessions( sessions: &Arc>>, room: &RoomCode, @@ -592,6 +654,27 @@ mod tests { std::fs::remove_file(cert_path).unwrap(); } + #[test] + fn formats_relay_forwarding_log_line() { + let decision = ForwardingDecision::forwarded(vec![2, 3]); + let line = relay_frame_log_line( + &RoomCode::new("TESTROOM").unwrap(), + 1, + ðernet_frame(client_mac(2), client_mac(1)), + &decision, + ); + + assert!(line.contains("room=TESTROOM")); + assert!(line.contains("direction=RelayIngress")); + assert!(line.contains("peer_id=1")); + assert!(line.contains("src=02:00:00:00:00:01")); + assert!(line.contains("dst=02:00:00:00:00:02")); + assert!(line.contains("ethertype_or_len=0x0800")); + assert!(line.contains("action=Forwarded")); + assert!(line.contains("drop_reason=-")); + assert!(line.contains("targets=2")); + } + #[tokio::test] async fn forwards_ethernet_datagrams_between_joined_peers() { let (server, certificate) = bind_test_server(DEFAULT_MAX_CLIENTS_PER_ROOM);