The gateway may see full-sized frames from the physical LAN even when the room
uses a smaller tunnel MTU. With no overlay fragmentation, those frames cannot be
encoded into the negotiated QUIC datagram budget. The live bridge previously
propagated that budget error and stopped, which made one oversized LAN frame a
fatal condition.
Teach the gateway send path to return a send outcome. Normal direct callers
still get an error for over-budget sends, but the Linux bridge loop now records,
logs, and drops those frames with a DatagramBudget drop reason before continuing
with later traffic. Malformed local Ethernet still remains an error because that
indicates a broken local boundary rather than ordinary LAN traffic.
The gateway stats test now covers the extra drop, and the frame-log test covers
the new drop reason. README now documents that over-budget LAN frames are
counted, dropped, and logged instead of fragmented or killing the bridge.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-obs -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: PLAN.md No fragmentation for MVP
PLAN.md keeps MVP traffic to one Ethernet frame per QUIC datagram with no
fragmentation. The relay already negotiates a datagram budget, but the client
and gateway send paths still relied on Quinn to reject oversized encoded
Ethernet datagrams.
Add a shared protocol validation helper for encoded datagram length versus the
negotiated QUIC budget. Thread the negotiated budget into client and gateway
send boundaries, reject oversized datagrams before send, and count those valid
but unsent frames as local drops.
Document the budget check in the workspace decomposition and gateway behavior.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-proto -p lanparty-client-core -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
PLAN.md calls out "Broadcast traffic flowing" as a user-facing diagnostic.
The tunnel stats only reported total Ethernet frame counts, so the client
could not distinguish whether broadcast traffic was actually crossing the
tunnel.
Add defaulted broadcast tx/rx counters to TunnelStats while preserving the
existing constructor and old JSON compatibility. Client and gateway accounting
now increments those counters from validated Ethernet frames, and the client
diagnostics line reports the broadcast flow next to total frame counts.
Relay peer stats logs include the new counters so operators can see broadcast
activity from forwarded stats snapshots too.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-obs -p lanparty-client-core -p lanparty-gateway \
-p lanparty-client-win -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
PLAN.md calls out room modes such as relay, direct-p2p, and relay fallback so
future transport choices can fit the protocol. Add an explicit ConnectionMode
field to ServerWelcome and default it to relay for existing decoded welcomes.
The relay still operates only in relay mode today. Client and gateway startup
logs now print the selected mode, which makes the current relay path visible
without changing routing or forwarding behavior.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-ctrl server_welcome -- --nocapture
- cargo test -p lanparty-client-win -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
PLAN.md describes the first client flow as entering a relay domain and room
code, but the client and gateway CLIs only accepted socket-address literals.
Add a small shared RelayEndpoint parser so bare hosts default to UDP/443 while
IP literals and explicit host:port values stay supported.
The runtime configs still store resolved SocketAddr values. That keeps the
Windows route-pinning path on a concrete relay IP before TAP activation while
avoiding duplicated endpoint grammar between client and gateway. The relay
listen config reuses the same default port constant so UDP/443 has one source.
README examples now use lanparty-relay.local and document the shared endpoint
syntax.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-net
- cargo test -p lanparty-client-win \
accepts_relay_domain_with_default_port -- --nocapture
- cargo test -p lanparty-gateway \
accepts_iface_alias_for_gateway_interface -- --nocapture
- cargo test -p lanparty-net -p lanparty-client-win -p lanparty-gateway
- cargo clippy -p lanparty-net -p lanparty-client-win -p lanparty-gateway \
--all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
PLAN.md tells LAN hosts to start the gateway with --iface, while the binary
only accepted --interface. Add --iface as a Clap alias so the documented
Phase 1 command works without changing the canonical config field name.
The README gateway example now uses the shorthand from the plan, and a focused
parse test covers the alias mapping to the same interface field.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway \
accepts_iface_alias_for_gateway_interface -- --nocapture
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The relay already accepts post-handshake Disconnect control messages, but the
client and gateway shutdown paths only sent a QUIC application close. That made
normal shutdown indistinguishable from transport closure until the relay
inferred a generic Normal leave.
Client and gateway shutdown now send a best-effort Disconnect message with the
human-readable shutdown reason before closing QUIC. The client-core drain uses
Quinn's runtime timer instead of taking a Tokio runtime dependency. The gateway
uses its existing Tokio runtime and applies the same short drain window on both
explicit shutdown and Ctrl-C in the Linux bridge loop.
The endpoint integration tests now assert that the server receives Disconnect
after the stats stream, which also protects against closing too quickly and
aborting the control stream.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-core \
connects_to_relay_control_stream_as_client -- --nocapture
- cargo test -p lanparty-gateway \
connects_to_relay_control_stream_as_gateway -- --nocapture
- cargo test -p lanparty-client-core
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-client-core --all-targets -- -D warnings
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The relay now sends room lifecycle events to gateways, but the gateway was only
learning remote MACs after seeing relay traffic. That delayed CAM refresh for a
silent remote client and left PeerLeft unable to retire stale refresh entries.
Add a gateway control-event receive path and consume it inside the Linux bridge
loop. Client PeerJoined events seed the CAM refresh table by peer id and MAC,
and PeerLeft removes that peer. Relay traffic can still refresh or correct the
same table from observed source MACs.
The bridge loop selects on accepting a control stream, then reads the selected
stream inside the branch. That avoids dropping an already accepted control
stream if another select branch wins while the stream body is still pending.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway \
connects_to_relay_control_stream_as_gateway -- --nocapture
- cargo test -p lanparty-gateway updates_cam_refresh_from_lifecycle_events \
-- --nocapture
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
Gateway traffic now contributes to the shared TunnelStats model. The gateway
counts Ethernet frames sent to the relay, Ethernet frames received from the
relay, incoming relay datagrams, dropped datagrams, and malformed frames.
Expose a gateway stats snapshot and send it to the relay over the same reliable
post-handshake Stats control stream used by clients. The Linux bridge loop sends
periodic stats snapshots as diagnostics; send failures are logged instead of
bringing down the bridge because stats are observational.
The gateway relay-session test now has the server decode a Stats event before
shutdown, so the stats control path is covered without a close race.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway \
connects_to_relay_control_stream_as_gateway -- --nocapture
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The gateway bridge now emits structured frame log lines for successful LAN to
relay and relay to LAN forwarding. Logs include the physical interface,
direction, peer id when one is known, MACs, ethertype or length field, frame
length, action, and drop reason.
This uses the shared `lanparty-obs` frame vocabulary instead of adding a second
ad hoc diagnostics model to the gateway. The log line stays local to the
gateway because the relay still owns its own room/target-specific formatting.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The gateway now asks Linux for PACKET_MR_PROMISC membership on the bound
AF_PACKET socket. The tunnel depends on receiving LAN frames addressed to
remote client MACs, not only frames addressed to the gateway NIC's own MAC. The
switch may learn those remote MACs on the gateway port, but the NIC can still
filter the unicast frames unless the packet socket requests promiscuous packet
membership.
This keeps the promiscuous lifetime scoped to the packet socket. If the kernel
rejects the membership request, gateway startup fails instead of running in a
mode that cannot reliably capture remote-client replies from the LAN.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The LAN switch has to keep learning that remote client MAC addresses live on
the gateway port. Once the gateway injects a remote client's traffic, that
learning can age out if the client is quiet, breaking the Layer 2 illusion.
Track valid remote-client source MACs observed in relay traffic and inject a
small padded CAM refresh frame for each known MAC every 60 seconds. The refresh
frame uses the remote MAC as the Ethernet source and the gateway NIC MAC as the
destination, with a local experimental EtherType so hosts should ignore it.
PacketSocket now reads the wired interface hardware address with SIOCGIFHWADDR
when opening the AF_PACKET socket. Non-Ethernet or invalid source interfaces
fail early instead of starting a gateway that cannot emit refresh traffic.
Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md Linux gateway CAM-table refresh
AF_PACKET sockets can report packets sent by the host as well as packets
received from the LAN. The gateway writes remote-client frames onto the wired
interface, so treating those outgoing packets as fresh LAN input can reflect
self-injected traffic back to the relay.
Read packet metadata with `recvfrom` and skip `PACKET_OUTGOING` frames before
returning a LAN frame to the bridge loop. This keeps capture scoped to inbound
LAN traffic and is a prerequisite for periodic CAM refresh frames.
Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md gateway AF_PACKET bridge
The gateway now runs the actual frame bridge after relay admission. It registers
the AF_PACKET socket with Tokio using AsyncFd, reads valid LAN Ethernet frames
and forwards them as relay datagrams, and writes valid relay Ethernet datagrams
back to the LAN socket.
The packet socket is opened nonblocking so the bridge can shut down cleanly on
Ctrl-C without leaving a blocking recv thread behind. Existing send_ethernet and
recv_ethernet helpers now share the same validation and encoding helpers used by
the bridge.
This still needs a privileged LAN-host smoke test with a real wired interface,
but the compile-time and loopback coverage now include the gateway relay side of
the bridge and the non-root-safe packet-socket validation.
Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md gateway AF_PACKET to relay bridge loop
GatewayConnection can now send and receive Ethernet frames over the admitted
relay QUIC connection. Outgoing frames are wrapped in the shared overlay format
with the gateway's assigned room id and peer id; incoming datagrams are ignored
unless they are Ethernet frames for the assigned room from another peer.
The receive helper also parses the payload as an Ethernet frame before exposing
it, which keeps the future AF_PACKET bridge from injecting malformed runt
payloads if the relay path ever misbehaves.
The loopback connector test now verifies the full post-handshake datagram path:
the gateway sends a frame to the test relay, the relay validates the overlay
metadata, and the gateway receives a relay-sent Ethernet frame back.
Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md gateway relay datagram send/receive
The gateway now has a small Linux PacketSocket wrapper for raw Ethernet frame
I/O. It resolves the configured interface with if_nametoindex, opens an
AF_PACKET/SOCK_RAW socket for ETH_P_ALL, binds it to the interface, and exposes
thin send_frame and recv_frame helpers around the owned file descriptor.
The gateway binary opens this socket after completing the relay control
handshake. The frame bridge loop is still intentionally left for a later slice,
but the process now proves the two required resources are available: relay
admission and raw L2 access on the LAN interface.
Tests cover interface-name validation and missing-interface lookup without
requiring root or CAP_NET_RAW.
Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md Linux AF_PACKET gateway socket
The gateway binary now has a real relay-facing configuration and QUIC control
handshake. It accepts a relay socket address, expected TLS server name, pinned
DER relay certificate, room code, LAN interface name, and advertised datagram
budget, then connects as role = gateway and waits for a welcome response.
The ALPN token moved into lanparty-ctrl so relay and gateway share the same
protocol identifier instead of carrying duplicate private constants. The gateway
still stops after the control-plane connection; AF_PACKET capture and injection
remain a later slice.
The connector test spins up a local Quinn server with a self-signed certificate,
trusts that certificate explicitly, verifies the outgoing gateway hello, and
checks the received welcome metadata.
Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md Linux gateway outbound relay connection