The client initialized gateway connectivity from ServerWelcome, but welcome only
exposed a boolean. If a gateway disconnected before the client saw the catch-up
PeerJoined event, the later unknown PeerLeft could not be tied to the gateway
and the status could stay connected.
Carry an optional gateway peer id in ServerWelcome. The relay fills it from the
joining gateway or the existing room gateway, and the Windows client stores it
so a matching unknown PeerLeft clears gateway connectivity. The boolean remains
for wire compatibility with older welcomes that do not carry the id.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-ctrl server_welcome
- cargo test -p lanparty-relay accepts_gateway_and_client_into_room
- cargo test -p lanparty-relay reports_missing_gateway_to_client_joining_first
- cargo test -p lanparty-client-win relay_lifecycle
- cargo test -p lanparty-client-win \
clears_gateway_status_when_welcome_gateway_leaves_before_join_event
- cargo test -p lanparty-relay bridges_real_client_and_gateway_sessions
- cargo test -p lanparty-client-core connects_to_relay_control_stream_as_client
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo build --release -p lanparty-relay -p lanparty-gateway
- git diff --check
- git diff --cached --check
Refs: MVP lifecycle cleanup
The MVP tunnel negotiates an effective TAP MTU and configures the Windows TAP IP
interface to that value, but the forwarding path only rejected frames that were
standard-Ethernet jumbo frames or exceeded the QUIC datagram budget. A frame
could therefore be larger than the negotiated TAP MTU while still fitting inside
the QUIC datagram budget.
Make the TAP-MTU frame limit an explicit shared protocol helper and enforce it
at every data-path boundary: Windows client send/receive, Linux gateway
send/receive, and relay forwarding. Such frames now produce TapMtuExceeded in
logs and counters instead of being forwarded until a later layer drops or
accepts them implicitly.
This keeps the no-fragmentation contract honest: one Ethernet frame still maps
to one QUIC datagram, but only if that frame also fits the room's negotiated TAP
MTU.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-proto tap_mtu
- 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 drops_frames_above_effective_tap_mtu
- cargo test -p lanparty-relay rate_limits_client_total_bandwidth_after_burst
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo build --release -p lanparty-relay -p lanparty-gateway
- git diff --check
- git diff --cached --check
Refs: MVP no-fragmentation tunnel MTU contract
The MVP test guide relies on --iface being both accepted and discoverable from
lanparty-gateway --help. The parser test already covered acceptance, but it did
not protect the rendered help text.
Render the Clap long help in a unit test and assert that the visible alias is
advertised. This keeps the handoff guide and binary self-documentation aligned.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway iface_alias
- cargo run -p lanparty-gateway -- --help
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: MVP gateway test handoff
The MVP test guide tells operators that --iface is accepted as a shorter alias
for --interface, but Clap hid the alias from lanparty-gateway --help. Make the
alias visible so the binary's self-documentation matches the handoff guide.
This does not change parsing behavior; --iface was already accepted and covered
by the existing gateway parser test.
Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway accepts_iface_alias_for_gateway_interface
- cargo run -p lanparty-gateway -- --help
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: MVP gateway test handoff
The client previously reported "Broadcast traffic flowing" as soon as either
broadcast TX or RX was nonzero. During the MVP DHCP/ARP proof, one-way
broadcast is useful but weaker evidence than bidirectional broadcast.
Keep the existing healthy message for two-way broadcast, but report
outbound-only broadcast as a warning that the client is still waiting for a LAN
broadcast reply, and report inbound-only broadcast separately. This makes the
Windows client status lines more precise when DHCP is stuck.
Test Plan:
- cargo test -p lanparty-obs
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: MVP Windows DHCP diagnostics
The relay and gateway already emit structured frame logs, but the Windows
client only exposed aggregate counters. During the MVP end-to-end test that
left a blind spot between TAP reads/writes and the relay datagram path.
Add client-side frame log lines for accepted TAP-to-relay sends,
relay-to-TAP writes, and local TAP-frame drops before relay send. The logs use
the shared FrameLog vocabulary with TapToRelay and RelayToTap directions so the
client, relay, and gateway logs can be correlated during DHCP, ARP, ping, and
LAN-game discovery checks.
Test Plan:
- cargo test -p lanparty-client-win formats_client_frame_log_lines
- cargo test -p lanparty-client-win
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: MVP Windows client diagnostics
The gateway already sent periodic CAM refresh frames so the physical switch
keeps remote client MACs learned on the gateway port. Those periodic refreshes
were silent, which made the manual MVP switch-learning check harder to diagnose
after the initial peer-joined refresh had passed.
Keep the peer id and MAC attached to periodic refresh work and emit the same
structured CAM refresh log line with reason=periodic. The join-triggered
refresh still logs reason=peer_joined, so the test guide can distinguish the
immediate proof from the recurring keepalive signal.
Test Plan:
- cargo test -p lanparty-gateway cam_refresh
- cargo test -p lanparty-gateway
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: MVP switch MAC-learning diagnostics
Peers advertise a datagram budget during hello, and the relay folds that into
the room MTU/no-fragmentation model. Honest clients already avoid sending
larger encoded frames, but the relay was still trusting ingress traffic to obey
that contract before forwarding it.
Drop datagrams that exceed the accepted peer's negotiated max before decode or
forwarding, and log them as datagram_budget. This keeps malformed datagram
disconnect accounting reserved for invalid overlay/ethernet bytes instead of
policy budget drops.
Test Plan:
- cargo test -p lanparty-relay ingress_budget
- cargo test -p lanparty-relay
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: MVP relay datagram budget audit
Add a relay regression test for the join ordering used by gateway MAC-state
seeding. The test sends a second client's hello but intentionally delays reading
that client's welcome until after the existing peer receives PeerJoined.
This guards the ordering from the relay admission path: existing peers are
notified before the joining peer can proceed from its welcome and begin sending
Ethernet datagrams. That matters for first DHCP/ARP frames after a Windows
client joins a room with an existing LAN gateway.
Test Plan:
- cargo test -p lanparty-relay notifies_existing_peer_before_join_welcome
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: PLAN.md MVP relay lifecycle and gateway MAC learning
The gateway authorizes remote-to-LAN frames from the relay lifecycle table. A
new client can start sending Ethernet datagrams as soon as it receives its
welcome, so the relay should notify already-present peers before returning that
welcome to the joining peer.
Register the accepted peer, send the PeerJoined event to existing peers, and
then send the welcome to the joining peer. If the welcome write fails after the
pre-notification, remove the accepted peer and send PeerLeft so existing peers
do not retain stale client MAC state.
This still is not a cross-connection delivery proof, but it puts the relay-side
ordering in the right direction for first DHCP/ARP frames after a client joins.
Test Plan:
- cargo test -p lanparty-relay
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: PLAN.md MVP relay lifecycle and gateway MAC learning
Gateway filtering and CAM refresh depend on the remote-client MAC table that
is seeded by relay lifecycle events. If a lifecycle stream and packet work are
both ready in the same loop turn, handle the lifecycle event first so the local
MAC table is as fresh as possible before deciding whether to inject or forward
frames.
This does not create a cross-stream ordering guarantee. It is a local scheduling
preference that reduces first-packet drops after client joins without weakening
source-MAC authorization or LAN-destination filtering.
Test Plan:
- cargo test -p lanparty-gateway
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- git diff --cached --check
Refs: PLAN.md MVP gateway lifecycle and L2 bridge behavior
Windows can report more than one unicast address on the TAP adapter. If an
APIPA address remains present next to the real LAN DHCP address, choosing the
first IPv4 address can make diagnostics warn about 169.254.x.x even though the
adapter already has a usable LAN address.
Prefer a non-link-local IPv4 address for TAP diagnostics, then fall back to any
IPv4 address, then to the first non-IPv4 address. This keeps the existing APIPA
warning when APIPA is the only IPv4 signal, but reports the real DHCP address
when Windows exposes both addresses.
README.md and TESTING.md now document that diagnostics prefer the real LAN IPv4
when several TAP addresses are visible.
Test Plan:
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
Refs: PLAN.md client diagnostics; TESTING.md TAP IP verification
The gateway runs in promiscuous mode so it can capture frames for remote client
MACs, but that also means it sees unrelated unicast traffic between physical
LAN machines. Before this change those frames were sent to the public relay and
only then discarded as unknown destinations.
Use the remote-client table seeded by relay lifecycle events to decide whether
a LAN frame should leave the gateway. Broadcast and multicast traffic still
flows to the relay, and unicast to a connected remote client still flows to the
relay. Unicast to any other destination is counted and logged locally as
UnknownDestination.
This keeps busy LAN traffic out of the relay data path and makes the gateway
behavior match the MVP switching model: LAN frames go to matching remote
clients, while broadcast and multicast fan out.
README.md documents the local filter, and TESTING.md explains why
LanToRemote UnknownDestination can be normal on a busy LAN.
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 switching model; TESTING.md MVP log signals
The gateway already emits periodic CAM refresh frames so the physical switch
keeps remote client MACs on the gateway port. A newly joined client previously
waited until either its own tunneled traffic reached the LAN or the first
60-second refresh tick fired.
Emit one padded CAM refresh frame immediately when a valid client PeerJoined
control event is observed. This makes the switch MAC-table check in the MVP
procedure visible sooner and keeps the periodic refresh as the aging guard.
The refresh uses the same maintenance frame shape as the periodic path and is
logged with the client peer id and MAC.
README.md and TESTING.md now document the immediate refresh behavior and the
log signal to look for during manual LAN testing.
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 CAM refresh; TESTING.md MVP switch MAC-table check
IPv4 link-local on the TAP adapter means Windows self-assigned APIPA because
LAN DHCP did not complete through the tunnel. The previous user diagnostic
only said "TAP IP detected", which made a failed DHCP path look neutral
during MVP testing.
Return a full UserDiagnostic from TAP IP classification so IPv4 link-local
can be a warning while normal IPv4 still reports the received DHCP address.
Keep IPv6 link-local neutral because it is expected on many Windows
interfaces and is not evidence of LAN DHCP success or failure.
TESTING.md now tells the operator to troubleshoot 169.254.x.x like
`Waiting for TAP IP`.
Test Plan:
- cargo test -p lanparty-obs
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
Refs: PLAN.md diagnostics; TESTING.md MVP guide
The previous startup ordering loaded the virtual MAC before touching TAP, but it
also resolved the relay endpoint before clearing stale TAP media state. That
left a post-crash TAP adapter able to influence DNS or route selection before
the client had pinned the relay path.
Split Windows client startup config into a local phase and a resolved runtime
phase. The local phase reads the certificate, room, TAP adapter selection, and
client identity without performing DNS. Windows startup now writes the TAP
NetworkAddress value and marks TAP media disconnected before resolving the relay
endpoint or opening the QUIC connection.
A regression test uses an intentionally unresolved relay hostname to prove that
building the startup config does not resolve DNS. The client still resolves the
relay before activation and still validates the driver-reported TAP MAC before
bridging.
Test Plan:
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
- cargo check -p lanparty-client-win --target x86_64-pc-windows-gnu
- blocked by missing x86_64-w64-mingw32-gcc for ring on this host
Refs: PLAN.md Windows routing / metric handling
The Windows startup path opened the TAP adapter to clear stale media state
before loading the persisted tunnel identity and writing the NetworkAddress
registry value. That made the first TAP touch happen with whatever MAC Windows
already had loaded from a previous run.
Build the client runtime config first so the virtual MAC and resolved relay
address are known before TAP activation. The pre-connect TAP preparation now
writes the selected adapter's NetworkAddress value before opening the adapter to
mark media disconnected. The relay route is still pinned only after the relay
handshake, while TAP remains media-disconnected.
This reduces the chance of a first-run or post-crash TAP MAC mismatch during
the manual Windows MVP test. The client still validates the driver-reported MAC
before bridging and still fails closed if Windows has not reloaded the registry
value.
Test Plan:
- cargo test -p lanparty-client-win
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
- cargo check -p lanparty-client-win --target x86_64-pc-windows-gnu
- blocked by missing x86_64-w64-mingw32-gcc for ring on this host
Refs: PLAN.md Windows routing / metric handling; TESTING.md MVP guide
The relay already enforces client source MAC identity before forwarding, but
this gateway bridge could still write any safety-clean relayed frame to
AF_PACKET. That left the final physical-LAN boundary depending entirely on the
relay forwarding path.
Keep a lifecycle-seeded remote client table in the gateway bridge and reject
relay frames whose datagram peer id is unknown or whose Ethernet source MAC does
not match the announced client MAC. CAM refresh now uses the same announced
table instead of learning source MACs from relay traffic.
This is conservative: if data arrives before the lifecycle event, the gateway
drops that frame with UnauthorizedSourceMac. Later packets proceed after the
control event is processed.
Test Plan:
- cargo test -p lanparty-gateway connects_to_relay_control_stream_as_gateway
- cargo test -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo fmt --check
- git diff --check
Refs: PLAN.md safety filters; TESTING.md MVP acceptance guide
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