The relay should only send a remote client unicast traffic for that client's
virtual MAC, while broadcast and multicast traffic fan out to every relevant
peer. The Windows client is still the last boundary before TAP, so it should not
write a relayed unicast frame for some other MAC into the adapter if the relay
or a future code path misroutes it.
Add a receive-side destination guard in client-core. Relayed frames now reach
TAP only when their destination is the client's virtual MAC or a
broadcast/multicast address; other unicast frames are counted and skipped.
Extend the client relay-session test with a wrong-client unicast before the
valid frame, and document the client-side skip in the README.
Test Plan:
- cargo test -p lanparty-client-core
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
Refs: PLAN.md LAN frames to matching remote clients
The MVP overlay reserves its flags field for later features such as
fragmentation or payload encryption, but version 1 does not define any flag
semantics. Accepting nonzero flags would let unknown behavior silently traverse
the relay and reach the tunnel endpoints.
Make zero the only valid v1 flag value. Overlay encoding and decoding now reject
reserved nonzero flags, production send paths use the explicit
OVERLAY_FLAGS_NONE constant, and the relay emits forwarded datagrams with the
same zero-flag policy instead of preserving peer-supplied bits.
Document the reserved-flag rule in the protocol crate overview.
Test Plan:
- cargo test -p lanparty-proto overlay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
Refs: PLAN.md no-fragmentation MVP overlay format
LAN-to-remote switch-control filtering is enforced by the gateway and relay,
but the Windows client is the final boundary before frames enter the TAP
adapter. A malformed or buggy relay path should not be able to make the client
write LAN control traffic, invalid-source frames, or jumbo frames into TAP.
Reuse the shared gateway/LAN safety classifier on received relay Ethernet
frames. Filtered frames are counted and skipped, and recv_ethernet only returns
frames that are safe to hand to the platform TAP writer.
Extend the client relay-session test so the mock relay sends a filtered frame
before the valid one, then document the receive-side TAP boundary in the
README.
Test Plan:
- cargo test -p lanparty-client-core
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
Refs: PLAN.md LAN-to-remote control-plane filtering
The relay already filters unsafe remote-client traffic, but the gateway is the
last process before the physical LAN. Treating a relayed Ethernet frame as safe
just because it came from the relay leaves the LAN boundary dependent on one
upstream check.
Add a gateway-local remote-to-LAN safety decision before AF_PACKET writes. The
gateway now skips and logs relayed frames with invalid source MACs, L2 control
traffic, remote VLAN tags, DHCP-server replies, IPv6 Router Advertisements,
IPv6 fragments, or jumbo payloads. The public receive helper also loops past
filtered frames so callers only receive frames that can be injected.
Document the final gateway boundary check in the README and extend the gateway
relay integration test so an unsafe relayed frame is filtered before the valid
frame is delivered.
Test Plan:
- cargo test -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
Refs: PLAN.md remote-to-LAN safety filters
Safety filtering now applies at several tunnel boundaries. The relay remains
the trust boundary, while the client and gateway also drop unsafe frames before
spending relay bandwidth. Duplicating EtherType and IPv4/IPv6 parsers across
crates would make those rules drift as the MVP grows.
Move the Ethernet safety classifiers into lanparty-proto, expose typed safety
drop reasons, and map them back into the existing DropReason vocabulary. The
relay now uses the shared client and gateway classifiers, the gateway keeps its
local LAN-send drops through the shared classifier, and the client drops the
same remote-to-LAN safety cases before QUIC DATAGRAM encoding.
Document the client-side local drops and list the additional suspicious drop
reasons in the manual MVP test guide.
Test Plan:
- cargo test -p lanparty-proto safety
- cargo test -p lanparty-client-core connects_to_relay_control_stream_as_client
- cargo test -p lanparty-gateway connects_to_relay_control_stream_as_gateway
- cargo test -p lanparty-relay
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-tap --target x86_64-pc-windows-gnu --tests
- cargo check -p lanparty-client-route --target x86_64-pc-windows-gnu --tests
- cargo check -p lanparty-client-tap --target x86_64-pc-windows-msvc --tests
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc --tests
- git diff --check
Refs: PLAN.md safety filters and client source-MAC isolation
The relay already filters invalid source MACs, jumbo frames, and L2 control
plane traffic. The gateway bridge was still encoding those LAN frames and
sending them to the relay first, so gateway logs could say forwarded even when
the relay would later drop the frame.
Classify that same local LAN-send subset before QUIC DATAGRAM encoding. The
gateway now records and reports these frames as local drops, keeps the relay as
the trust boundary, and avoids spending relay bandwidth on frames that can never
reach remote clients.
Document that gateway-side local drops cover invalid source MACs, L2 control
plane traffic, jumbo frames, and datagram-budget failures.
Test Plan:
- cargo test -p lanparty-gateway
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md LAN-to-remote control-plane filtering
The relay already rejects client frames whose source MAC does not match the
announced virtual MAC. The Windows bridge can still see those frames from TAP,
though, and sending them to the relay wastes datagram budget and makes the
client-side counters less useful during manual tests.
Carry the configured virtual MAC into ClientRelayIo and drop invalid or
unauthorized TAP source MACs before QUIC DATAGRAM encoding. The relay keeps the
same checks as the trust boundary, but client diagnostics now account for these
drops locally.
Document the local source-MAC check and list InvalidSourceMac as a suspicious
manual-test drop reason.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-core connects_to_relay_control_stream_as_client
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md source-MAC authorization and safety filters
The Windows TAP reader used the validating read helper, so a malformed or runt
TAP frame would surface as a read error and stop the whole client bridge. The
client relay send path already has the right semantics for bad local frames: it
counts malformed, jumbo, and over-budget frames, reports the drop reason, and
keeps the bridge running.
Read raw TAP frames in the Windows pump and pass the bytes through
send_ethernet_with_outcome. Device read errors still stop the bridge, but frame
validation failures now follow the documented local-drop path instead of tearing
down the session.
Test Plan:
All cargo commands used these environment variables:
RUSTUP_HOME=/tmp/softlan-vpn-rustup
CARGO_HOME=/tmp/softlan-vpn-cargo
- cargo test -p lanparty-client-core connects_to_relay_control_stream_as_client
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-tap --tests
--target x86_64-pc-windows-gnu
- cargo check -p lanparty-client-tap --tests
--target x86_64-pc-windows-msvc
- cargo check -p lanparty-client-route --tests
--target x86_64-pc-windows-gnu
- cargo check -p lanparty-client-route --tests
--target x86_64-pc-windows-msvc
- git diff --check
Known limitation: full lanparty-client-win Windows cross-check remains blocked
on this host by ring requiring x86_64-w64-mingw32-gcc for the GNU target.
Refs: PLAN.md better malformed-frame handling
A gateway with the selected Ethernet cable unplugged can still open an
AF_PACKET socket and join the relay room. That makes clients see a connected LAN
gateway even though DHCP and LAN discovery cannot make the physical round trip.
Check the selected Linux interface's sysfs carrier file before creating the raw
socket. If sysfs reports carrier 0, fail before the gateway joins the relay.
Missing or unrecognized carrier files remain allowed so this does not reject
interfaces where the kernel cannot expose link state in that form. README and
TESTING now document the preflight and the operator fix.
Test Plan:
All cargo commands used these environment variables:
RUSTUP_HOME=/tmp/softlan-vpn-rustup
CARGO_HOME=/tmp/softlan-vpn-cargo
- cargo test -p lanparty-gateway \
packet::tests::detects_disconnected_interfaces_from_sysfs_carrier
- cargo test -p lanparty-gateway
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md wired Ethernet gateway requirement
If the Windows client is killed hard, the TAP adapter can be left in a connected
media state. A retry should not resolve or connect to the relay while stale TAP
state might still influence Windows routing.
Select and open the intended TAP adapter before relay endpoint resolution, force
its media state to disconnected, then proceed with the existing relay connect,
route pin, TAP route protection, and bridge startup flow. This also makes
missing or ambiguous TAP adapters fail before the client joins the relay room.
The README and MVP test guide now show the new startup line and the early TAP
preflight troubleshooting checks.
Test Plan:
All cargo commands used these environment variables:
RUSTUP_HOME=/tmp/softlan-vpn-rustup
CARGO_HOME=/tmp/softlan-vpn-cargo
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-tap --tests
--target x86_64-pc-windows-gnu
- cargo check -p lanparty-client-tap --tests
--target x86_64-pc-windows-msvc
- cargo check -p lanparty-client-route --tests
--target x86_64-pc-windows-gnu
- cargo check -p lanparty-client-route --tests
--target x86_64-pc-windows-msvc
- git diff --check
Known limitation: full lanparty-client-win Windows cross-check is still blocked
on this Linux host by the external ring toolchain setup. The default GNU target
lacks x86_64-w64-mingw32-gcc, the default MSVC target lacks lib.exe, and the
LLVM MSVC attempt gets as far as ring C compilation but lacks Windows CRT
headers such as assert.h.
Refs: PLAN.md route-protection startup requirement
A crashed or forcibly killed Windows client can leave the scoped relay host
route behind. The next run should still be allowed to start when Windows says
that the exact route row already exists, because that route already protects the
relay path from TAP default-route takeover.
Handle ERROR_OBJECT_ALREADY_EXISTS from CreateIpForwardEntry2 as a successful
borrowed pin. Routes created by the current client are still deleted on Drop;
pre-existing routes are left alone so we do not remove administrator-managed or
stale routes that this process did not create. The client startup log now marks
whether the route was created or already existed, and the README and MVP test
guide explain the behavior.
Test Plan:
All cargo commands used these environment variables:
RUSTUP_HOME=/tmp/softlan-vpn-rustup
CARGO_HOME=/tmp/softlan-vpn-cargo
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --tests
--target x86_64-pc-windows-gnu
- cargo check -p lanparty-client-route --tests
--target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --tests
--target x86_64-pc-windows-gnu -- -D warnings
- cargo clippy -p lanparty-client-route --tests
--target x86_64-pc-windows-msvc -- -D warnings
- git diff --check
Known limitation: full lanparty-client-win Windows cross-check is still blocked
on this Linux host by the external ring toolchain setup. The GNU target lacks
x86_64-w64-mingw32-gcc, and the MSVC target lacks lib.exe/MSVC environment.
Refs: PLAN.md route-protection requirement
The MVP bridge treats each remote player as a normal host on the LAN, not as a
trunk port. Allowing client-origin VLAN-tagged frames would let a remote client
send traffic outside the simple untagged Ethernet model, and could also hide
IPv4/IPv6 control traffic behind an outer VLAN EtherType that the existing
safety filters do not parse.
Filter 802.1Q, 802.1ad, and common QinQ-tagged frames from remote clients before
they can reach the physical LAN. LAN-origin tagged frames are still allowed back
toward clients so the gateway remains a transparent receiver for whatever the
local wired network emits. Add a dedicated drop reason so relay logs make the
policy clear.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay -p lanparty-obs
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: MVP relay L2 safety filters
The Windows client already treats TAP route, metric, and MTU changes as scoped
runtime state that is restored when the process exits. TAP media status was the
odd one out: startup marked the adapter connected, but there was no matching
cleanup path to mark it disconnected again.
Add a small TAP media guard that owns an Arc to the opened adapter and marks
media disconnected on drop. The frame pump now receives an Arc<TapAdapter>, so
the guard can run on normal Ctrl-C, startup errors after TAP open, route-check
failures, or frame-pump failures without fighting ownership of the TAP reader
thread. Cleanup errors are logged because Drop cannot return them.
Update the README and MVP test guide so the documented cleanup behavior matches
the client runtime.
The full Windows client cross-check is still blocked on this host before project
code by ring needing x86_64-w64-mingw32-gcc. The TAP and route helper crates
still check for the Windows GNU target.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-win -p lanparty-client-tap -p lanparty-client-route
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check --target x86_64-pc-windows-gnu -p lanparty-client-route -p lanparty-client-tap
- git diff --check
Refs: MVP Windows TAP cleanup
The relay now looks through ordinary IPv6 extension headers to catch remote
DHCPv6 server replies and Router Advertisements. IPv6 fragments are still an
evasion risk because later fragments may not contain the upper-layer ports or
ICMPv6 type that the relay safety policy checks.
For the MVP, make that boundary conservative: remote-client IPv6 fragments are
filtered before they can reach the physical LAN. LAN-origin fragments are still
allowed to flow back to remote clients, so this does not block ordinary LAN
traffic returning through the gateway. Add a dedicated diagnostics drop reason
so logs explain the policy clearly.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay -p lanparty-obs
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: MVP relay L2 safety filters
The MVP safety policy says remote clients must not inject DHCP server replies
onto the LAN. The relay enforced that for IPv4 DHCP, but DHCPv6 uses UDP
547 -> 546 over IPv6 and was not covered by the existing check.
Split the DHCP server-reply predicate into IPv4 and IPv6 paths. The IPv6 path
reuses the extension-header walker so server replies hidden behind ordinary IPv6
extension headers are still treated as unsafe. Keep client DHCPv6 requests
allowed, and continue allowing LAN-origin DHCPv6 replies to flow back to remote
clients.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: MVP relay L2 safety filters
Remote clients must not be able to inject IPv6 Router Advertisements onto the
LAN. The relay already filtered direct ICMPv6 RA packets, but the check only
looked at the IPv6 base header's immediate next-header value. A client could
hide the ICMPv6 RA behind ordinary IPv6 extension headers and bypass that MVP
safety policy.
Walk the IPv6 extension-header chain for hop-by-hop, routing, destination
options, fragment, and AH headers before checking the ICMPv6 message type. This
keeps non-RA ICMPv6 traffic eligible for normal relay forwarding while closing
the obvious RA evasion path. Document the stronger relay safety boundary in the
README.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: MVP relay L2 safety filters
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
Add a --list-tap-adapters mode to the Windows client. The command prints the
TAP-Windows6 adapter instance ids and exits without requiring relay, room, or
certificate arguments.
This makes the manual MVP test smoother on machines with multiple TAP adapters:
the operator can ask the binary for the exact ids, then rerun with
--tap-instance-id instead of relying on a separate PowerShell query or waiting
for the ambiguous-adapter startup error.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md Windows client TAP adapter responsibility
Add a Windows client --tap-instance-id option for selecting a specific
TAP-Windows6 adapter by NetCfgInstanceId / InterfaceGuid. The client still opens
the only installed TAP adapter automatically, but now refuses to choose an
arbitrary adapter when multiple TAP-Windows6 adapters are present.
This keeps the MVP test run from silently configuring and opening the wrong TAP
adapter on machines that already have VPN or test TAP devices installed. The
error lists available adapter instance ids so the operator can rerun with the
intended value.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md Windows client TAP adapter responsibility
Add a relay-server test that connects the production client-core session and the
production gateway session to the same in-process relay. The test verifies room
join catch-up, negotiated peer identities, Ethernet datagrams in both
directions, stats reporting, and room/session cleanup after graceful shutdown.
The existing relay tests covered forwarding with raw Quinn peers. This adds a
higher-level proof that the three MVP components agree on the same control and
datagram contracts before the final Windows TAP and physical LAN test.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay bridges_real_client_and_gateway_sessions -- --nocapture
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md Phase 1 relay/client/gateway proof
Expose the active QUIC connection RTT in shared client diagnostics and in the
Windows client status line. This gives operators a live relay-path latency
signal without pretending to measure end-to-end gateway or LAN latency.
The new diagnostics field defaults to unknown when older JSON snapshots omit it,
so consumers can read pre-change snapshots without special migration code. The
user-facing diagnostics now print Relay RTT only when the client has an active
QUIC measurement.
Test Plan:
- cargo fmt --check
- git diff --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md logging and diagnostics section
The plan explicitly keeps the physical LAN gateway wired-only for the MVP.
Managed Wi-Fi adapters are not reliable for arbitrary source-MAC injection, but
the gateway previously accepted any interface that could be opened as an
Ethernet-like packet socket.
Reject Linux interfaces that sysfs marks as wireless before opening the raw
packet socket. The check looks for the common `wireless` and `phy80211` markers
under `/sys/class/net/<iface>`, and keeps path separators out of interface names
so the sysfs lookup stays scoped to a single netdev name.
Document the wired-only enforcement in the gateway README section.
Test Plan:
- cargo fmt --check
- git diff --check
- cargo test -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md
The Linux AF_PACKET read helper discarded malformed or runt LAN captures before
the bridge loop saw them. That made those frames invisible to gateway drop
counters and frame logs, which is not great for the phase-one heavy diagnostics
called for in the plan.
Return raw inbound capture bytes from the read helper and let the bridge loop
make the drop decision. Malformed LAN frames are now counted as malformed drops,
logged with the normal gateway frame log shape, and skipped without stopping the
bridge. Valid LAN frames still flow through the existing send path and budget
checks.
Document the accounting behavior in the gateway README section.
Test Plan:
- cargo fmt --check
- git diff --check
- cargo test -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md
The Windows TAP pump used the same send helper as direct callers, so malformed,
jumbo, or over-budget TAP frames were reported as errors that stopped the
bridge. That is too brittle for the no-fragmentation MVP: local frames that
cannot fit the tunnel should be counted and dropped, while real QUIC send
failures should still surface as fatal.
Add a client send outcome that reports whether a frame was sent or locally
dropped. The existing send_ethernet API still returns an error for direct
callers when the outcome is a local drop. The live TAP pump uses the outcome API
so it can log the drop reason and keep forwarding later frames.
Document the new client behavior in the README.
Test Plan:
- cargo fmt --check
- git diff --check
- cargo test -p lanparty-client-core
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Attempted:
- cargo check -p lanparty-client-win --target x86_64-pc-windows-msvc
(blocked by missing MSVC lib.exe on this Linux host)
Refs: PLAN.md
The gateway AF_PACKET read path used the standard 1514 byte Ethernet frame
length as its receive buffer. VLAN-tagged or jumbo LAN frames could therefore
be truncated before the bridge reached the encoded-datagram budget check, so
logs and drop accounting saw a corrupted shorter frame.
Use an overlay payload-sized capture buffer instead. This lets the Linux
gateway observe the whole frame that the kernel reports, then leave the
existing Ethernet parsing and negotiated QUIC datagram budget checks to decide
whether the frame can cross the tunnel. The bridge still never fragments
Ethernet frames.
Document the behavior in the gateway README section and add a compile-time
guard so the capture buffer stays above the standard Ethernet frame size.
Test Plan:
- cargo fmt --check
- git diff --check
- cargo test -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
Refs: PLAN.md
The friendly client diagnostics reported relay/gateway/IP/broadcast signals,
but they still hid two important readiness failures in the dense status line:
the relay route was not pinned, or no TAP adapter was available. Those are the
kind of states a CLI user or future GUI needs surfaced immediately.
Add user diagnostics for an unpinned relay route, a missing TAP adapter, and a
TAP adapter that is present but still has no IP address. The existing healthy
path stays concise: once the TAP has an address, the diagnostics continue to
report that address instead of also saying that the adapter is ready.
README now mentions that the short diagnostics include route and TAP readiness
warnings.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-obs -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: PLAN.md Logging / diagnostics
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
Peers announce a QUIC datagram budget in their hello, and the relay clamps that
value against the transport's negotiated max before room admission. The relay
used that clamped value for MTU selection, but stored the raw transport budget
in the live peer session. A peer that intentionally advertised a smaller budget
could therefore receive egress datagrams larger than it promised to accept.
Store the post-clamp hello budget in AcceptedPeer and PeerSession instead. That
keeps the existing relay egress skip path tied to the same negotiated size used
for room MTU decisions.
The handshake regression now advertises a budget below the QUIC transport
budget and asserts that the accepted peer records the advertised value. The
README decomposition also calls out the per-peer egress-budget invariant.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: PLAN.md No fragmentation for MVP
The PLAN calls out client diagnostics that a user can read directly, not only
machine-shaped counters. The Windows client already built ClientDiagnostics
snapshots, but it printed a dense status line and left the UserDiagnostic model
unused.
Derive user-facing diagnostics from ClientDiagnostics in lanparty-obs so a
future GUI and the current CLI can share the same status vocabulary. The
messages report the states the runtime actually observes: relay reachability,
LAN gateway presence, TAP IP presence, and observed broadcast traffic. TAP IPs
are only described as DHCP when they are non-link-local IPv4 addresses, because
link-local IPv4 and IPv6 addresses do not prove DHCP success.
The client now prints those user-facing lines after the existing detailed
counter line. Gateway latency is intentionally not reported here; the current
protocol does not measure gateway RTT.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-obs -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: PLAN.md Logging / diagnostics
PLAN.md requires rejecting broadcast, multicast, and otherwise invalid source
MACs. Client ingress already had a forged-source check against the registered
client identity, but gateway ingress could still forward Ethernet frames whose
source MAC was not valid unicast.
Add an explicit `InvalidSourceMac` drop reason and filter invalid Ethernet
source MACs before client source authorization and last-seen refresh. This keeps
invalid source addresses out of both remote-client and gateway forwarding paths
while preserving `UnauthorizedSourceMac` for valid unicast sources that simply
belong to another client identity.
Document the invalid-source filter in the relay README decomposition.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-obs -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
PLAN.md keeps MVP forwarding to one Ethernet frame per QUIC datagram with no
fragmentation. The relay already skipped targets whose negotiated datagram
budget could not carry an encoded forwarded frame, but that skip was silent.
Retain the no-fragmentation behavior and log the skipped egress target with the
room, ingress peer, target peer, encoded length, and target datagram budget.
Store the peer id in relay sessions so the diagnostic can identify the skipped
target directly.
Document the egress budget-skip diagnostic in the relay README section.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
PLAN.md calls for heavy diagnostics and better malformed-frame handling. The
relay already disconnected peers after repeated malformed datagrams, but overlay
or header-level malformed datagrams had no operator-facing count until the peer
was closed.
Log each malformed peer datagram with room, peer id, role, current count,
threshold, and whether the threshold is disconnecting the peer. Keep the
existing threshold behavior unchanged; this only makes the counter visible.
Document the malformed-datagram count in the relay README section.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
PLAN.md describes relay frame logs with separate forwarded, dropped, filtered,
and rate-limited actions. The relay had the shared `Filtered` action in its log
vocabulary, but safety-policy rejects were still reported as generic drops.
Classify forged client source MACs and L2 safety-filter matches as filtered
forwarding decisions. Malformed frames and unknown destinations remain drops,
while rate limits continue to use the rate-limited action.
Document the distinction in the relay README section.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
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 for a user-facing warning when TAP routing could steal the
relay path. The client already disables TAP default routes under a scoped guard,
but the startup output only reported the previous raw flag value.
Format the route-protection message as a warning whenever default routes were
enabled before the scoped override. Keep the already-disabled case quiet and
explicit, and cover both messages with tests that run on non-Windows builds.
Document the startup warning alongside the existing route-protection behavior.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-win -p lanparty-client-route
- 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 for user-facing diagnostics that can say whether the remote
client is connected to the LAN gateway. Startup already reported the initial
welcome bit, but periodic diagnostics only carried relay reachability and route
pinning.
Add gateway_connected to RelayDiagnostics and seed the client status from the
welcome. The client control-event logger now updates that status when gateway
join and leave events arrive, so later diagnostics reflect relay lifecycle
changes while the tunnel is running.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-obs -p lanparty-client-win
- 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 Windows client sampled TAP diagnostics once when opening the adapter. That
can miss the useful DHCP result because the TAP interface may not receive a LAN
address until after frame bridging starts.
Keep the stable TAP diagnostics fields from startup, but retain the interface
identity and re-read the TAP unicast IP whenever the periodic diagnostics tick
prints and reports a snapshot. Later status lines can now show the DHCP address
that arrives after the tunnel is already moving traffic.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The Windows client already generates and announces a stable locally administered
MAC, but it only rejected the TAP adapter when the driver reported a different
address. Persist the tunnel MAC to tap-windows6's NetworkAddress registry value
before opening the adapter so the driver can load the intended current address.
The TAP crate now keeps the driver registry key name from discovery, formats the
NetworkAddress value as the 12-digit hex string expected by NDIS, and rejects
invalid multicast, broadcast, or globally administered MACs before writing.
Runtime validation stays in place. tap-windows6 reads NetworkAddress during
adapter initialization, so an adapter that Windows already initialized with an
old value may still need disable/enable or reinstall on the real Windows test
machine before the GET_MAC ioctl reports the new identity.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-tap
- cargo test -p lanparty-client-win
- cargo clippy -p lanparty-client-tap --all-targets -- -D warnings
- cargo check -p lanparty-client-tap --target x86_64-pc-windows-gnu
- cargo check -p lanparty-client-tap --target x86_64-pc-windows-msvc
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The plan calls for relay switch state to remember when peers were last seen.
Wrap room peers in a small entry that keeps PeerInfo alongside a monotonic
Instant, and expose the timestamp through room snapshots for relay diagnostics.
Joining initializes last_seen. Valid Ethernet frames with the peer's authorized
source refresh the timestamp before safety filters or rate limits run, so
filtered but well-formed traffic still proves the peer is active. Malformed
frames and forged client source MACs do not refresh it.
Room leave now removes the associated timestamp with the peer entry. That keeps
membership, MAC indexes, rate-limit buckets, and diagnostics state aligned.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay last_seen -- --nocapture
- cargo test -p lanparty-relay
- cargo clippy -p lanparty-relay --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
Peers can now end their relay session with a post-handshake Disconnect
control message, but the relay previously only returned from the peer loop.
Explicitly close that peer connection before leaving the room so graceful
disconnects are finite from both sides.
The relay still runs the normal leave path after the close. That keeps room
cleanup, session removal, and PeerLeft notification centralized, while the
PeerLeft reason now comes from the peer's Disconnect message.
The integration test covers two connected clients. One sends a TimedOut
Disconnect, the relay closes that client connection, and the remaining peer
receives PeerLeft with the TimedOut reason.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay \
forwards_graceful_disconnect_reason_to_remaining_peers \
-- --nocapture
- cargo test -p lanparty-relay
- cargo clippy -p lanparty-relay --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
Client counters were only local diagnostics even though the control protocol and
relay now understand Stats messages. Add a client-core sender that opens a
peer-to-relay unidirectional stream with the current TunnelStats snapshot.
The Windows client reports stats whenever it prints its diagnostics snapshot.
Failures are logged instead of stopping the frame pump because stats reporting
is diagnostic and should not be the reason a live tunnel goes down.
The client-core integration test now has the server decode the stats stream
before shutdown so the send path is covered without a timing race.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-core \
connects_to_relay_control_stream_as_client -- --nocapture
- cargo test -p lanparty-client-core
- cargo test -p lanparty-client-win
- cargo clippy -p lanparty-client-core --all-targets -- -D warnings
- cargo clippy -p lanparty-client-win --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
Peers can now send post-handshake Stats messages over unidirectional control
streams. The relay listens for those streams alongside Ethernet datagrams,
stores the latest snapshot on the peer session, and logs the counters with
room, peer, and role context.
Unexpected post-handshake control messages now close the peer as a protocol
error. Peer-requested Disconnect messages leave the room through the normal
cleanup path, which keeps lifecycle notifications centralized.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay \
forwards_ethernet_datagrams_between_joined_peers \
-- --nocapture
- cargo test -p lanparty-relay
- cargo clippy -p lanparty-relay --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md
The relay now sends enough PeerJoined events for the client to learn the peers
already in the room, but the client was still formatting PeerLeft with only the
bare peer id. That made a gateway disconnect look the same as any other peer
leaving.
Keep a small in-memory peer map in the control-event logger. PeerJoined records
or refreshes the peer identity, and PeerLeft removes it before formatting the
message. Unknown leaves still use the generic fallback, so out-of-order or
missed events remain understandable.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-win formats_relay_lifecycle_events \
-- --nocapture
- cargo test -p lanparty-client-win
- cargo clippy -p lanparty-client-win --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
Refs: PLAN.md