feat(relay): log Ethernet forwarding decisions
Phase 1 needs noisy frame diagnostics while the tunnel is being proven on real LANs. The relay already had forwarding/drop decisions, but the runtime did not emit the MAC-level fields needed to understand what happened to each frame. Print one relay ingress log line for every accepted Ethernet datagram after the room registry decides whether to forward or drop it. The line includes room, peer id, source/destination MACs, ethertype or length, frame length, action, drop reason, and target count using the shared diagnostics vocabulary. This keeps logging simple stdout text for now. A later product slice can route the same `lanparty-obs` fields through tracing or JSON logs without changing the forwarding rules. Test Plan: - cargo fmt --check - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md logging diagnostics
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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<Mutex<HashMap<PeerKey, PeerSession>>>,
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user