diff --git a/crates/wzp-relay/src/config.rs b/crates/wzp-relay/src/config.rs index a771b42..abf73d8 100644 --- a/crates/wzp-relay/src/config.rs +++ b/crates/wzp-relay/src/config.rs @@ -63,6 +63,9 @@ pub struct RelayConfig { /// Federation peer relays. #[serde(default)] pub peers: Vec, + /// Debug tap: log packet headers for matching rooms ("*" = all rooms). + /// Activated via --debug-tap or debug_tap = "room" in TOML. + pub debug_tap: Option, } impl Default for RelayConfig { @@ -82,6 +85,7 @@ impl Default for RelayConfig { ws_port: None, static_dir: None, peers: Vec::new(), + debug_tap: None, } } } diff --git a/crates/wzp-relay/src/main.rs b/crates/wzp-relay/src/main.rs index ab8cce1..5b43880 100644 --- a/crates/wzp-relay/src/main.rs +++ b/crates/wzp-relay/src/main.rs @@ -104,6 +104,12 @@ fn parse_args() -> RelayConfig { args.get(i).expect("--static-dir requires a directory path").to_string(), ); } + "--debug-tap" => { + i += 1; + config.debug_tap = Some( + args.get(i).expect("--debug-tap requires a room name (or '*' for all)").to_string(), + ); + } "--mesh-status" => { // Print mesh table from a fresh registry and exit. // In practice this is useful after the relay has been running; @@ -126,6 +132,7 @@ fn parse_args() -> RelayConfig { eprintln!(" --probe-mesh Enable mesh mode (mark config flag, probes all --probe targets)."); eprintln!(" --mesh-status Print mesh health table and exit (diagnostic)."); eprintln!(" --trunking Enable trunk batching for outgoing media in room mode."); + eprintln!(" --debug-tap Log packet headers for a room ('*' for all rooms)."); eprintln!(" --ws-port WebSocket listener port for browser clients (e.g., 8080)."); eprintln!(" --static-dir Directory to serve static files from (HTML/JS/WASM)."); eprintln!(); @@ -372,6 +379,9 @@ async fn main() -> anyhow::Result<()> { } else { info!("auth disabled — any client can connect (use --auth-url to enable)"); } + if let Some(ref tap) = config.debug_tap { + info!(filter = %tap, "debug tap enabled — logging packet headers"); + } info!("Listening for connections..."); @@ -388,6 +398,7 @@ async fn main() -> anyhow::Result<()> { let relay_seed_bytes = relay_seed.0; let metrics = metrics.clone(); let trunking_enabled = config.trunking_enabled; + let debug_tap = config.debug_tap.as_ref().map(|filter| room::DebugTap { room_filter: filter.clone() }); let presence = presence.clone(); let route_resolver = route_resolver.clone(); let federation_mgr = federation_mgr.clone(); @@ -675,6 +686,7 @@ async fn main() -> anyhow::Result<()> { metrics.clone(), &session_id_str, trunking_enabled, + debug_tap, ).await; // Participant disconnected — clean up presence + per-session metrics diff --git a/crates/wzp-relay/src/room.rs b/crates/wzp-relay/src/room.rs index 2bfaab0..7ffeb70 100644 --- a/crates/wzp-relay/src/room.rs +++ b/crates/wzp-relay/src/room.rs @@ -18,6 +18,38 @@ use wzp_proto::MediaTransport; use crate::metrics::RelayMetrics; use crate::trunk::TrunkBatcher; +/// Debug tap: logs packet metadata for matching rooms. +#[derive(Clone)] +pub struct DebugTap { + /// Room name filter ("*" = all rooms, or specific room name/hash). + pub room_filter: String, +} + +impl DebugTap { + pub fn matches(&self, room_name: &str) -> bool { + self.room_filter == "*" || self.room_filter == room_name + } + + pub fn log_packet(&self, room: &str, dir: &str, addr: &std::net::SocketAddr, pkt: &wzp_proto::MediaPacket, fan_out: usize) { + let h = &pkt.header; + info!( + target: "debug_tap", + room = %room, + dir = dir, + addr = %addr, + seq = h.seq, + codec = ?h.codec_id, + ts = h.timestamp, + fec_block = h.fec_block, + fec_sym = h.fec_symbol, + repair = h.is_repair, + len = pkt.payload.len(), + fan_out, + "TAP" + ); + } +} + /// Unique participant ID within a room. pub type ParticipantId = u64; @@ -477,6 +509,7 @@ pub async fn run_participant( metrics: Arc, session_id: &str, trunking_enabled: bool, + debug_tap: Option, ) { if trunking_enabled { run_participant_trunked( @@ -485,7 +518,7 @@ pub async fn run_participant( .await; } else { run_participant_plain( - room_mgr, room_name, participant_id, transport, metrics, session_id, + room_mgr, room_name, participant_id, transport, metrics, session_id, debug_tap, ) .await; } @@ -499,6 +532,7 @@ async fn run_participant_plain( transport: Arc, metrics: Arc, session_id: &str, + debug_tap: Option, ) { let addr = transport.connection().remote_address(); let mut packets_forwarded = 0u64; @@ -572,6 +606,13 @@ async fn run_participant_plain( ); } + // Debug tap: log packet metadata + if let Some(ref tap) = debug_tap { + if tap.matches(&room_name) { + tap.log_packet(&room_name, "in", &addr, &pkt, others.len()); + } + } + // Forward to all others let fwd_start = std::time::Instant::now(); let pkt_bytes = pkt.payload.len() as u64;