From 47f66b7d0445f2a4e868c7d4ae925080cf7c75fa Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 21 May 2026 23:19:26 +0200 Subject: [PATCH] fix(gateway): validate LAN interface before relay join The gateway previously joined the relay room before opening the Linux AF_PACKET socket. If the operator passed the wrong interface, a wireless interface, or an interface that could not be opened, the relay could briefly advertise a gateway that was never able to bridge LAN traffic. Parse CLI args first, then keep platform handling in a small cfg-gated run function. On Linux, build the config, open the AF_PACKET socket, and only then join the relay as the room gateway. On non-Linux targets, fail before reading relay certificate files, resolving relay names, or opening network connections. Update the README and MVP test guide so the documented gateway startup order matches the binary output. Test Plan: - cargo fmt --check - cargo test -p lanparty-gateway - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: MVP gateway startup hardening --- README.md | 18 +++++++------ TESTING.md | 3 ++- crates/lanparty-gateway/src/main.rs | 41 +++++++++++++++++------------ 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index b1bb208..db5ce79 100644 --- a/README.md +++ b/README.md @@ -168,14 +168,16 @@ cargo run -p lanparty-gateway -- \ --iface eth0 ``` -The gateway connects to the relay as `role = gateway`, completes the -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 captures whole LAN frames up -to the overlay payload-length ceiling before deciding whether they fit the -tunnel. It never fragments Ethernet frames; LAN frames whose encoded datagrams -exceed the negotiated QUIC budget are counted, dropped, and logged instead of -stopping the bridge. +The gateway first opens the wired LAN interface as an AF_PACKET socket with +promiscuous packet membership, then connects to the relay as `role = gateway` +and completes the control-stream hello/welcome handshake. That startup order +keeps an invalid or wireless interface from briefly advertising a gateway that +cannot bridge. Once both sides are ready, it bridges Ethernet frames between the +relay and wired LAN until shutdown. It captures whole LAN frames up to the +overlay payload-length ceiling before deciding whether they fit the tunnel. It +never fragments Ethernet frames; LAN frames whose encoded datagrams exceed the +negotiated QUIC budget are counted, dropped, and logged instead of stopping the +bridge. `--relay` accepts a DNS name or socket address; bare hosts default to UDP/443. The gateway rejects Linux interfaces that sysfs identifies as Wi-Fi; managed wireless NICs are not supported for the physical LAN bridge. diff --git a/TESTING.md b/TESTING.md index e145adc..1c06ae5 100644 --- a/TESTING.md +++ b/TESTING.md @@ -77,8 +77,9 @@ Use the real wired LAN interface name for `--iface`. Do not use Wi-Fi. Expected gateway output: ```text -lanparty-gateway connected as peer ... +lanparty-gateway opening interface eth0 and connecting to relay ... lanparty-gateway opened AF_PACKET socket on eth0 ... +lanparty-gateway connected as peer ... lanparty-gateway bridging frames; press Ctrl-C to stop ``` diff --git a/crates/lanparty-gateway/src/main.rs b/crates/lanparty-gateway/src/main.rs index 0bbd10c..7783c3f 100644 --- a/crates/lanparty-gateway/src/main.rs +++ b/crates/lanparty-gateway/src/main.rs @@ -1,18 +1,31 @@ use clap::Parser; +use lanparty_gateway::GatewayArgs; #[cfg(target_os = "linux")] -use lanparty_gateway::PacketSocket; -use lanparty_gateway::{GatewayArgs, connect_gateway}; +use lanparty_gateway::{PacketSocket, connect_gateway}; #[tokio::main] async fn main() -> anyhow::Result<()> { - let config = GatewayArgs::parse().into_config()?; + let args = GatewayArgs::parse(); + run(args).await +} + +#[cfg(target_os = "linux")] +async fn run(args: GatewayArgs) -> anyhow::Result<()> { + let config = args.into_config()?; println!( - "lanparty-gateway connecting interface {} to relay {} room {}", + "lanparty-gateway opening interface {} and connecting to relay {} room {}", config.interface(), config.relay_addr(), config.room() ); + let socket = PacketSocket::open(config.interface())?; + println!( + "lanparty-gateway opened AF_PACKET socket on {} (ifindex {})", + socket.interface(), + socket.interface_index() + ); + let gateway = connect_gateway(config).await?; println!( "lanparty-gateway connected as peer {} in room id {} with TAP MTU {} over {}", @@ -21,19 +34,13 @@ async fn main() -> anyhow::Result<()> { gateway.welcome().effective_tap_mtu(), gateway.welcome().mode() ); - #[cfg(target_os = "linux")] - { - let socket = PacketSocket::open(gateway.config().interface())?; - println!( - "lanparty-gateway opened AF_PACKET socket on {} (ifindex {})", - socket.interface(), - socket.interface_index() - ); - println!("lanparty-gateway bridging frames; press Ctrl-C to stop"); - gateway.bridge_until_shutdown(socket).await?; - } - #[cfg(not(target_os = "linux"))] - anyhow::bail!("lanparty-gateway requires Linux AF_PACKET support"); + println!("lanparty-gateway bridging frames; press Ctrl-C to stop"); + gateway.bridge_until_shutdown(socket).await?; Ok(()) } + +#[cfg(not(target_os = "linux"))] +async fn run(_args: GatewayArgs) -> anyhow::Result<()> { + anyhow::bail!("lanparty-gateway requires Linux AF_PACKET support"); +}