Commit Graph
100 Commits
Author SHA1 Message Date
ddidderr 4629b3ad83 fix(client): drop malformed TAP frames without stopping
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
2026-05-22 04:52:13 +02:00
ddidderr 217469edf0 fix(gateway): reject LAN interfaces without carrier
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
2026-05-22 04:48:12 +02:00
ddidderr ac03bf1616 fix(client): clear TAP media before relay connect
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
2026-05-22 04:44:13 +02:00
ddidderr abc75831cb fix(client): reuse existing relay host route
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
2026-05-22 04:40:28 +02:00
ddidderr 0784e73f30 fix(relay): filter remote VLAN-tagged frames
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
2026-05-21 23:40:44 +02:00
ddidderr efda797ae6 fix(client): restore TAP media status on exit
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
2026-05-21 23:37:47 +02:00
ddidderr 23043dcce6 fix(relay): filter remote IPv6 fragments
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
2026-05-21 23:33:57 +02:00
ddidderr 756ba5f094 fix(relay): filter remote DHCPv6 server replies
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
2026-05-21 23:30:52 +02:00
ddidderr b310a33bb2 fix(relay): detect IPv6 RAs behind extension headers
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
2026-05-21 23:26:07 +02:00
ddidderr 47f66b7d04 fix(gateway): validate LAN interface before relay join
The gateway previously joined the relay room before opening the Linux
AF_PACKET socket. If the operator passed the wrong interface, a wireless
interface, or an interface that could not be opened, the relay could briefly
advertise a gateway that was never able to bridge LAN traffic.

Parse CLI args first, then keep platform handling in a small cfg-gated run
function. On Linux, build the config, open the AF_PACKET socket, and only then
join the relay as the room gateway. On non-Linux targets, fail before reading
relay certificate files, resolving relay names, or opening network connections.

Update the README and MVP test guide so the documented gateway startup order
matches the binary output.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: MVP gateway startup hardening
2026-05-21 23:19:26 +02:00
ddidderr 2f0802dfcf feat(client): list TAP adapters before connecting
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
2026-05-21 23:14:50 +02:00
ddidderr e619866112 feat(client): require explicit TAP selection when ambiguous
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
2026-05-21 23:11:01 +02:00
ddidderr b17b6f0683 test(relay): cover real client and gateway sessions
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
2026-05-21 23:07:20 +02:00
ddidderr 1be3f3c964 docs: add MVP end-to-end test guide
Add a dedicated manual test guide for the MVP proof. The guide keeps the
operator flow concrete: relay startup, certificate handoff, Linux gateway
startup, elevated Windows client startup, expected log signals, acceptance
checks, troubleshooting, cleanup, and the information to report back.

This is intentionally scoped to the manual MVP. It documents that installer,
GUI, production certificates, authentication, and end-to-end payload encryption
are outside this proof.

Test Plan:
- git diff --check
- reviewed TESTING.md for command flow and expected log signals

Refs: PLAN.md Phase 1 success criteria
2026-05-21 23:03:54 +02:00
ddidderr e69d41691a feat(client): report relay RTT diagnostics
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
2026-05-21 23:00:18 +02:00
ddidderr 77025e6564 fix(gateway): reject wireless LAN interfaces
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
2026-05-21 22:54:58 +02:00
ddidderr d4c96569e3 fix(gateway): account malformed LAN captures
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
2026-05-21 22:50:05 +02:00
ddidderr cd8a536771 fix(client): drop unsendable TAP frames locally
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
2026-05-21 22:47:08 +02:00
ddidderr 022e74d62b fix(gateway): capture whole LAN frames before budget checks
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
2026-05-21 22:40:16 +02:00
ddidderr d8f281e1cd feat(obs): warn on missing client readiness
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
2026-05-21 22:34:18 +02:00
ddidderr 319a1a25ad fix(gateway): drop over-budget LAN frames
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
2026-05-21 22:31:17 +02:00
ddidderr d503533a3c fix(relay): honor advertised egress budgets
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
2026-05-21 22:25:28 +02:00
ddidderr 733badd2a8 feat(client): print user-facing diagnostics
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
2026-05-21 22:22:43 +02:00
ddidderr 881dee5491 feat(relay): filter invalid source MACs
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
2026-05-21 22:17:32 +02:00
ddidderr d2cf20f597 feat(relay): log egress datagram budget skips
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
2026-05-21 22:13:18 +02:00
ddidderr 2c946ce9c2 feat(relay): log malformed datagram counts
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
2026-05-21 22:10:28 +02:00
ddidderr d4cb119b19 feat(relay): classify safety rejects as filtered
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
2026-05-21 22:06:49 +02:00
ddidderr 325e5651a2 feat(proto): validate negotiated datagram budgets
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
2026-05-21 22:03:15 +02:00
ddidderr e533131c74 feat(client): warn on TAP default routes
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
2026-05-21 21:58:46 +02:00
ddidderr 21a69626e0 feat(obs): report broadcast frame counters
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
2026-05-21 21:54:35 +02:00
ddidderr 9722adbd70 feat(client): include gateway presence in diagnostics
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
2026-05-21 21:47:53 +02:00
ddidderr 6a18daac3a feat(ctrl): report connection mode in welcome
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
2026-05-21 21:43:44 +02:00
ddidderr bdb571799a feat(net): accept relay hostnames
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
2026-05-21 21:40:00 +02:00
ddidderr 829ffe9b95 feat(gateway): accept iface CLI alias
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
2026-05-21 21:30:00 +02:00
ddidderr 3fa78fc935 feat(client): refresh TAP IP diagnostics
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
2026-05-21 21:26:55 +02:00
ddidderr 6adde91208 docs: document relay trust boundary
The plan explicitly calls out that the MVP relay sees plaintext Ethernet frames
because it terminates QUIC for every peer connection. Document that boundary in
the README so operators and future security work do not infer end-to-end payload
privacy from QUIC alone.

Also state the intended future direction: room-key payload encryption should
keep the relay routing header visible while encrypting Ethernet payload bytes
between clients and the LAN gateway.

Test Plan:
- git diff --check

Refs: PLAN.md
2026-05-21 21:23:01 +02:00
ddidderr 2d30f4ed68 feat(client): persist TAP MAC identity
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
2026-05-21 21:21:47 +02:00
ddidderr 6f3be2d4fa feat(relay): track peer last-seen times
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
2026-05-21 21:15:24 +02:00
ddidderr deec79ab22 feat(relay): close graceful disconnect peers
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
2026-05-21 21:11:36 +02:00
ddidderr 546060568b feat(ctrl): send graceful disconnects
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
2026-05-21 21:07:47 +02:00
ddidderr 66d6601d21 feat(gateway): consume lifecycle events
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
2026-05-21 21:00:43 +02:00
ddidderr e65831686c feat(gateway): report tunnel stats to relay
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
2026-05-21 20:57:02 +02:00
ddidderr 60c41471fb feat(client): send stats snapshots to relay
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
2026-05-21 20:53:37 +02:00
ddidderr dffb490afe feat(relay): receive peer stats streams
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
2026-05-21 20:50:15 +02:00
ddidderr ffdcbf8d16 feat(client): identify lifecycle leave events
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
2026-05-21 20:46:36 +02:00
ddidderr eedd03b98c feat(relay): send peer catch-up on join
The relay now sends PeerJoined catch-up events to a newly accepted peer for
peers that were already present in the room. This makes lifecycle delivery
symmetric enough for clients and gateways to learn the current room membership
after welcome, not only future joins.

The catch-up list is built from a cloned room snapshot before opening control
event streams, so room state is not locked across QUIC I/O. Delivery remains
best-effort and uses the same one-frame unidirectional control stream path as
live PeerJoined and PeerLeft notifications.

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
2026-05-21 20:42:21 +02:00
ddidderr f4f617ea01 feat(client): log relay lifecycle events
The Windows client now listens for relay control events while the TAP frame pump
is running and logs peer lifecycle updates as they arrive. Gateway joins get a
clear LAN-gateway message, client joins include their virtual MAC, and peer
leaves include the relay-provided reason.

The non-Windows placeholder path also listens for the same events while waiting
for Ctrl-C. That keeps lifecycle diagnostics visible in local relay/client smoke
tests even before the Windows TAP path can be exercised on a real machine.

Test Plan:
- cargo fmt --check
- 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
2026-05-21 20:37:56 +02:00
ddidderr 20bed4b45e feat(client): receive relay control events
ClientSession can now accept one-frame relay control events sent on reliable
unidirectional QUIC streams. This gives the Windows client a core API for the
PeerJoined and PeerLeft lifecycle messages that the relay now emits.

The implementation stays in client-core because it shares the relay connection
and control codec with the handshake. Client UI/status handling remains a
separate slice so this commit only establishes the tested transport boundary.

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 clippy -p lanparty-client-core --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 20:34:57 +02:00
ddidderr a24341c361 feat(relay): notify peers when room members leave
The relay now sends a reliable PeerLeft control event to remaining room peers
after removing the departed peer from session and room state. Normal connection
closure is reported as Normal, while malformed-datagram disconnects are reported
as ProtocolError.

This completes the first relay-side lifecycle event pair. Delivery remains
best-effort and client-side event consumption is intentionally left for a
separate slice.

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
2026-05-21 20:32:03 +02:00
ddidderr 7f4b22d5f4 feat(relay): notify peers when rooms change
The relay now sends a reliable PeerJoined control event to peers that were
already present in the room after a new peer completes the hello/welcome
handshake. Events are sent on one-frame unidirectional QUIC streams, reusing the
existing control codec without keeping the room/session lock across I/O.

Delivery is best-effort for this first lifecycle slice: a notification failure
is logged, but the newly accepted peer remains joined. PeerLeft delivery and
client-side event consumption remain separate follow-up work.

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
2026-05-21 20:28:54 +02:00
ddidderr 0824f60548 feat(ctrl): report gateway presence in welcome
ServerWelcome now carries an initial gateway_connected flag with serde defaulting
for older welcome payloads. The relay sets it from room admission state so a
gateway sees itself as connected, clients joining behind an existing gateway see
yes, and clients that arrive first see no.

The Windows client prints that handshake fact at startup. This does not replace
the later peer-event stream; it gives phase-1 diagnostics an immediate answer
for whether the relay already has a LAN gateway in the room.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-ctrl -p lanparty-relay -p lanparty-client-core \
  -p lanparty-client-win
- cargo clippy -p lanparty-ctrl -p lanparty-relay -p lanparty-client-core \
  -p lanparty-client-win --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 20:24:33 +02:00
ddidderr c6dbb78cfc feat(client): report TAP IP in diagnostics
Add InterfaceUnicastAddress snapshots to client-route, backed by the Windows
unicast IP address table. The Windows client samples the TAP interface after it
resolves the adapter identity, preferring IPv4 for diagnostics and falling back
to the first address or unknown on lookup failure.

This keeps Win32 IP table handling in the route crate and fills the existing
TapDiagnostics IP field without making bridging depend on DHCP being present.
If DHCP has not assigned an address yet, diagnostics still make that visible as
unknown.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-route
- cargo test -p lanparty-client-win
- cargo check -p lanparty-client-route --target x86_64-pc-windows-gnu
- cargo check -p lanparty-client-route --target x86_64-pc-windows-gnu --tests
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Attempted:
- cargo check -p lanparty-client-route -p lanparty-client-win
  --target x86_64-pc-windows-gnu

Blocked because ring needs missing x86_64-w64-mingw32-gcc here.

Refs: PLAN.md
2026-05-21 20:20:55 +02:00
ddidderr 2afdae44c6 feat(client): print runtime diagnostics snapshots
The client now builds ClientDiagnostics snapshots from the connected session,
known TAP state, route-pinning status, and tunnel counters. Windows prints one
snapshot after TAP and relay-route setup, then repeats snapshots while bridging
so frame/datagram counters and drops are visible during manual phase-1 tests.

Non-Windows builds print the same relay and QUIC diagnostics with TAP fields
marked unknown before waiting for Ctrl-C. TAP IP remains unknown until a later
Windows adapter IP inspection slice wires that source of truth.

Test Plan:
- cargo fmt --check
- 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
2026-05-21 20:16:32 +02:00
ddidderr e8d7cf7ff5 feat(client): retain QUIC datagram diagnostics
Client connection setup already fails when QUIC DATAGRAM is unavailable and
clamps the advertised datagram budget before sending hello. Keep that clamped
value on ClientSession and expose it through QuicDiagnostics so the Windows
client can report the negotiated budget without recomputing handshake details.

The value remains owned by client-core because it is derived from the live
quinn connection and the configured client budget during handshake. TAP and UI
code can sample the snapshot later alongside tunnel counters.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-core
- cargo clippy -p lanparty-client-core --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 20:13:52 +02:00
ddidderr 802fe3d082 feat(client): expose tunnel traffic counters
Client relay I/O now shares atomic tunnel counters across cloned
ClientRelayIo handles and the owning ClientSession. The counters cover
successful Ethernet frame rx/tx, relay datagram rx/tx, and dropped or
malformed frames so client diagnostics have the traffic totals called
out in PLAN.md.

The counters live in client-core because that crate owns relay datagram
classification and Ethernet payload validation. The Windows TAP runner can
later sample this snapshot without duplicating protocol decisions.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-core
- cargo clippy -p lanparty-client-core --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 20:11:11 +02:00
ddidderr aa9105541f feat(gateway): log bridged Ethernet frames
The gateway bridge now emits structured frame log lines for successful LAN to
relay and relay to LAN forwarding. Logs include the physical interface,
direction, peer id when one is known, MACs, ethertype or length field, frame
length, action, and drop reason.

This uses the shared `lanparty-obs` frame vocabulary instead of adding a second
ad hoc diagnostics model to the gateway. The log line stays local to the
gateway because the relay still owns its own room/target-specific formatting.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 20:06:41 +02:00
ddidderr 587b0516cd fix(relay): keep unknown unicast on gateway path
The relay models the physical LAN as the gateway port, not as another remote
client. Client-originated unknown unicast now forwards only to the gateway, and
gateway-originated unknown unicast is dropped unless it resolves to a registered
remote client. Broadcast and multicast fanout is unchanged.

This prevents promiscuous gateway capture of unrelated LAN unicast from being
flooded to every remote client. It also keeps client-to-LAN traffic from
needlessly leaking to other clients in the room.

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
2026-05-21 20:03:13 +02:00
ddidderr 3e2648abc1 feat(client): scope TAP interface MTU while running
The Windows client now sets the TAP IP-interface MTU to the relay-selected MTU
before it starts bridging frames. The override is scoped like the existing
metric and default-route guards, so the previous MTU is restored when the
client exits.

The route crate now exposes `InterfaceMtuSnapshot` and `ScopedInterfaceMtu`
around `MIB_IPINTERFACE_ROW.NlMtu`, reusing the same `GetIpInterfaceEntry` and
`SetIpInterfaceEntry` path already used for metrics and default-route policy.
IPv4 MTU setup is required for startup, while IPv6 MTU setup is best-effort to
match the existing IPv6 route-protection behavior.

This intentionally leaves TAP MAC configuration as fail-fast. TAP-Windows6 does
not expose a matching set-MAC IOCTL in the driver header, so that should remain
a separate design decision.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-route
- cargo test -p lanparty-client-win
- cargo clippy -p lanparty-client-route -p lanparty-client-win --all-targets
  -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-gnu
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check
- cargo check -p lanparty-client-win --target x86_64-pc-windows-gnu
  (fails before this crate in ring: missing x86_64-w64-mingw32-gcc)

Refs: PLAN.md
2026-05-21 20:00:58 +02:00
ddidderr 0299190c42 feat(relay): cap total client frame bandwidth
Client-originated Ethernet frames now pass through a per-peer byte token bucket
after source and safety validation and before destination-specific forwarding.
The fixed MVP default allows a 4 MiB burst with a 2 MiB/s refill, giving normal
LAN-game traffic headroom while preventing one client from filling the relay
path indefinitely.

The existing frame burst buckets now share the same `TokenBucket` type with a
unit cost of one frame. Keeping this state in `Room` preserves forwarding-policy
ownership and lets tests drive explicit instants.

This completes the relay-side abuse limit list from PLAN.md. Configurable rate
limits remain future work.

Test Plan:
- cargo fmt --check
- 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
2026-05-21 19:53:23 +02:00
ddidderr 5b12108e83 feat(relay): rate limit client unknown unicast floods
Client-originated unknown unicast now has its own relay-side token bucket.
When a client sends to an unlearned unicast MAC, the relay consumes from that
bucket before flooding the frame to the room.

Known unicast to a registered client bypasses this limit, and broadcast or
multicast traffic continues to use its separate bucket. Keeping the buckets in
room state matches the existing forwarding-policy ownership and lets unit tests
drive explicit instants.

This is the second rate-limit slice from PLAN.md. A total bandwidth cap remains
future work.

Test Plan:
- cargo fmt --check
- 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
2026-05-21 19:50:12 +02:00
ddidderr c87033f74c feat(relay): rate limit client multicast floods
The relay now applies a small token bucket to broadcast and multicast frames
originating from remote clients. Those frames are necessary for ARP, DHCP, and
LAN discovery, but they are also the easiest way for one remote peer to flood
every participant and the LAN gateway. When a client exceeds the burst budget,
the relay returns a rate-limited forwarding decision instead of forwarding the
frame.

This is intentionally only the first rate-limit slice from PLAN.md. Unknown
unicast limits and total bandwidth limits remain separate follow-up work. The
limiter lives in room state because forwarding policy already knows the ingress
role, destination MAC, and room membership, and tests can drive it with explicit
Instants without involving QUIC timing.

Test Plan:
- cargo fmt --check
- 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
2026-05-21 19:46:24 +02:00
ddidderr f29d0b755c feat(relay): disconnect peers after malformed datagrams
The relay now tracks malformed datagrams per accepted peer and closes the QUIC
connection after a small threshold. Malformed overlay bytes, datagrams with the
wrong room/peer/type header, and malformed Ethernet payloads all count toward
that threshold.

This implements the malformed-packet disconnect part of PLAN.md without mixing
in broader bandwidth or broadcast rate limiting. Ordinary safety-filter drops
still remain non-fatal; this only targets peers that repeatedly send packets the
relay cannot treat as valid tunnel Ethernet traffic.

The threshold state lives in the relay server loop, while the forwarding helper
returns a small outcome enum so malformed classification stays testable without
running a full QUIC server. The room registry remains responsible for Ethernet
policy decisions such as unauthorized source MACs, jumbo frames, and control
plane filters.

Test Plan:
- cargo fmt --check
- 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
2026-05-21 19:43:08 +02:00
ddidderr 4033b7c2d2 fix(gateway): request promiscuous AF_PACKET membership
The gateway now asks Linux for PACKET_MR_PROMISC membership on the bound
AF_PACKET socket. The tunnel depends on receiving LAN frames addressed to
remote client MACs, not only frames addressed to the gateway NIC's own MAC. The
switch may learn those remote MACs on the gateway port, but the NIC can still
filter the unicast frames unless the packet socket requests promiscuous packet
membership.

This keeps the promiscuous lifetime scoped to the packet socket. If the kernel
rejects the membership request, gateway startup fails instead of running in a
mode that cannot reliably capture remote-client replies from the LAN.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-gateway
- cargo clippy -p lanparty-gateway --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:37:14 +02:00
ddidderr 2a8e6467c4 feat(client): monitor relay route after TAP activation
The Windows client now verifies that the relay path still uses the pinned host
route after the TAP adapter is activated, and keeps checking that invariant
while frames are being bridged. If Windows starts choosing a different prefix,
next hop, or interface for the relay IP, the client exits instead of silently
letting the tunnel route itself through the TAP side.

This closes the remaining detection half of the route-protection startup flow
from PLAN.md. The previous commits installed the pre-TAP host route, scoped TAP
metrics, and disabled TAP default routes; this commit proves those protections
are still winning after TAP activation and catches later DHCP-driven route
changes during the session.

The route crate now exposes small helpers for route-interface identity and host
route matching so the client does not duplicate route-prefix semantics inline.

The full Windows client target check still cannot complete on this Linux host:
`ring` fails while compiling for `x86_64-pc-windows-msvc` because the Windows C
header `assert.h` is unavailable, before `lanparty-client-win` is typechecked.
The independent Windows-target route crate checks do pass.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:35:08 +02:00
ddidderr dc85b1dbd6 fix(client): fail fast on TAP identity mismatch
The Windows client now validates the driver-reported TAP MAC and MTU before it
marks the adapter media connected or starts bridging frames. If the TAP driver
settings do not match the tunnel identity and relay-selected MTU, startup fails
with a direct error instead of continuing into a session that would drop frames
or advertise the wrong L2 identity.

This is intentionally a correctness guard, not automatic configuration yet.
Until TAP MAC and MTU configuration are wired, the safe behavior is to fail
before traffic can flow. Route protection is still applied before validation and
restored if validation fails during startup.

The full Windows client target check still cannot complete on this Linux host:
`ring` fails while compiling for `x86_64-pc-windows-msvc` because the Windows C
header `assert.h` is unavailable, before `lanparty-client-win` is typechecked.

Test Plan:
- cargo fmt --check
- 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
2026-05-21 19:30:57 +02:00
ddidderr bbe12e851a feat(client): disable TAP default routes while running
The Windows client now holds scoped default-route suppression guards for the
TAP interface while the frame pump is active. IPv4 protection is required,
matching the relay-route safety path. IPv6 protection is still best-effort so
IPv4-only Windows TAP setups do not fail startup just because there is no IPv6
interface row to update.

This completes the current client-side route-policy wiring from PLAN.md: the
relay host route is pinned before TAP activation, TAP interface metrics are
raised while running, and TAP default routes are disabled until the client
exits or startup unwinds. Automatic TAP MAC and MTU configuration remain
follow-up work.

The full Windows client target check still cannot complete on this Linux host:
`ring` fails while compiling for `x86_64-pc-windows-msvc` because the Windows C
header `assert.h` is unavailable, before `lanparty-client-win` is typechecked.
The independent Windows-target route crate checks do pass.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:28:14 +02:00
ddidderr 9c0a974281 feat(client): add scoped default-route suppression
The Windows route helper now supports a scoped DisableDefaultRoutes override
for an IP interface family. The guard snapshots the existing interface row,
sets the requested default-route disabled state, and restores the previous
state when dropped.

This is the route-crate half of neutralizing TAP default-route takeover from
PLAN.md. It intentionally does not wire the client yet, so the public API can
be reviewed independently from the client startup behavior.

The implementation keeps metric restoration and default-route restoration
separate even though both read the same IP interface row. That avoids one guard
accidentally reverting the other guard's setting when the Windows client holds
both at the same time.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-route
- cargo clippy -p lanparty-client-route --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:26:07 +02:00
ddidderr c6a4a9da89 feat(client): scope TAP interface metrics while running
The Windows client now applies a high manual metric to the TAP interface
while the adapter is active. This keeps ordinary host routes preferred over
TAP routes during the tunnel lifetime, and the route crate guard restores the
previous metric and automatic-metric state when the client exits or startup
unwinds.

IPv4 metric protection is required because the tunnel depends on keeping the
relay path reachable. IPv6 metric protection is attempted as a best-effort
step so IPv4-only Windows setups can still run while dual-stack hosts receive
similar protection when the IPv6 interface row exists.

The metric guard is held for the same lifetime as the TAP frame pump. The
relay host-route pin remains held through QUIC shutdown. Default-route
takeover detection and automatic TAP MAC/MTU configuration are still follow-up
work from PLAN.md.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:24:01 +02:00
ddidderr 61481eaf46 feat(client): add scoped interface metric override
Add a reversible IP interface metric boundary to `lanparty-client-route`. The
crate can now read an IPv4 or IPv6 interface metric snapshot and temporarily set
a manual interface metric with an RAII guard that restores the previous metric
and automatic-metric state on drop.

This prepares the TAP metric handling without wiring policy into the Windows
client yet. Default-route disabling is captured in snapshots for diagnostics and
future decisions, but this slice deliberately changes only `UseAutomaticMetric`
and `Metric`.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
Refs: https://learn.microsoft.com/en-us/windows-hardware/drivers/network/initializeipinterfaceentry
Refs: https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-setipinterfaceentry
2026-05-21 19:20:01 +02:00
ddidderr 96bfbd0dbc feat(client): report TAP interface identity
Resolve the opened TAP adapter's NetCfgInstanceId to its Windows interface
index and LUID during startup, then print those values with the existing TAP
MAC/MTU diagnostics. This makes the interface identity visible before the next
metric-setting slice uses it for route protection.

The lookup failure is treated as startup failure because an opened TAP adapter
that cannot be resolved as a Windows network interface is not a good candidate
for metric or route management.

Verification note: I attempted to check `lanparty-client-win` for
`x86_64-pc-windows-msvc`, but this host still lacks the Windows C headers
needed by `ring`; the build stops at `assert.h` before the binary crate can be
typechecked for Windows.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:17:17 +02:00
ddidderr 432d1d08d1 feat(client): resolve Windows interface identity by GUID
Add a small route-crate API that resolves a Windows network adapter GUID into
its interface LUID and index. The TAP adapter discovery already gives us the
NetCfgInstanceId GUID, and the route/metric work needs the corresponding IP
interface identity before it can safely inspect or adjust TAP metrics.

The implementation keeps GUID parsing local and dependency-free, then delegates
the actual identity lookup to Windows IP Helper calls. Non-Windows builds expose
the same API shape with a clear unsupported-platform error.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:16:02 +02:00
ddidderr d90c06dd70 feat(client): pin relay route before opening TAP
Create the relay host-route pin in the Windows client before the TAP adapter is
opened and marked connected. The guard is held until after `ClientSession`
shutdown so both the active tunnel and the QUIC close path keep using the
pre-TAP interface.

Route inspection or route creation failure now aborts startup before TAP
activation, and the client explicitly closes the relay session in that failure
path. Once the pin is installed, the client reports both the original best-route
snapshot and the pinned route. Default-route takeover detection/neutralization
is still future work.

Verification note: I attempted to check `lanparty-client-win` for
`x86_64-pc-windows-msvc`, but this host still lacks the Windows C headers
needed by `ring`; the build stops at `assert.h` before the binary crate can be
typechecked for Windows.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:13:57 +02:00
ddidderr 4fa1c1cabb feat(client): add scoped relay host route pin
Add the first route-table mutation API to `lanparty-client-route`. Given the
pre-TAP best-route snapshot, the crate can now create a host route for the relay
IP on that same interface and return an RAII guard that deletes the route when
it is dropped.

The route row is initialized with `InitializeIpForwardEntry`, uses a /32 or
/128 destination prefix for the relay IP, preserves the pre-TAP interface
index/LUID, and uses the remembered next hop or an on-link unspecified next hop
when Windows reported no gateway. The guard tracks whether creation actually
succeeded so pure row-construction tests and failed creates do not try to delete
routes they did not install.

This remains a crate-level boundary only. The Windows client still reports the
pre-TAP route but does not yet hold a pin across TAP activation; that is the
next integration slice.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-route
- cargo clippy -p lanparty-client-route --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
Refs: https://learn.microsoft.com/en-us/windows-hardware/drivers/network/createipforwardentry2
Refs: https://learn.microsoft.com/en-us/windows-hardware/drivers/network/initializeipforwardentry
2026-05-21 19:12:06 +02:00
ddidderr f88c8a94f8 feat(client): report relay route before TAP activation
Use the new route snapshot helper in the Windows client startup path before the
TAP adapter is opened and marked connected. The client now reports the current
relay destination route: selected source address, next hop, interface index,
interface LUID, route prefix, and metric.

This is still diagnostic only. Route pinning remains unwired, and route lookup
failure is a warning so manual TAP frame-pump testing is not blocked by a route
inspection failure. Once mutation is implemented, this snapshot gives the code
the pre-TAP interface data it needs to preserve the real internet path.

Verification note: I attempted to check `lanparty-client-win` for
`x86_64-pc-windows-msvc`, but this host still lacks the Windows C headers
needed by `ring`; the build stops at `assert.h` before the binary crate can be
typechecked for Windows.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:08:07 +02:00
ddidderr 5001f3c35c feat(client): add Windows route snapshot helper
Add `lanparty-client-route` as the Win32 boundary for route-table work. The
first API is intentionally read-only: `best_route_to` wraps `GetBestRoute2`
and returns the selected source address, next hop, route prefix, interface
index/LUID, and route metric for a relay destination IP.

This keeps the route-protection work separate from the QUIC client binary, so
we can typecheck the Windows IP Helper calls on this Linux host without pulling
in the `ring` build path that currently blocks full Windows binary checks.
Actual route pinning and metric mutation remain later slices.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check -p lanparty-client-route --target x86_64-pc-windows-msvc
- cargo clippy -p lanparty-client-route --target x86_64-pc-windows-msvc -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:06:55 +02:00
ddidderr 89989c195a feat(client): bridge TAP frames in Windows client
Wire the Windows client run loop to move Ethernet frames between the relay
session and the opened TAP-Windows6 adapter. TAP reads use a named OS thread
because the current adapter API performs blocking synchronous reads; relay to
TAP writes use short `spawn_blocking` jobs so the async receive loop does not
block the Tokio worker.

The main function now always closes the relay session after the client run loop
finishes, including TAP pump errors. Ctrl-C still stops the client. The TAP
reader thread is intentionally detached in this first pump slice because a
blocking TAP read cannot yet be cancelled cleanly from the async side; process
exit tears it down after shutdown.

This still leaves route pinning and automatic TAP MAC/MTU configuration for
later. The README now reflects that frame pumping is wired while those Windows
network-configuration pieces remain outstanding.

Verification note: I attempted to check `lanparty-client-win` for
`x86_64-pc-windows-msvc`, but this Linux host still lacks the Windows C
headers needed by `ring`; the build stops at `assert.h` before the binary crate
can be typechecked for Windows.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- CC_x86_64_pc_windows_msvc=clang-cl AR_x86_64_pc_windows_msvc=llvm-lib \
  CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=lld-link cargo clippy \
  -p lanparty-client-tap --target x86_64-pc-windows-msvc -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 19:02:59 +02:00
ddidderr c0d4fdf7b4 feat(client): allow TAP adapter sharing across threads
The Windows client frame pump needs one task reading TAP frames while another
writes relay frames back to the adapter. The TAP crate already owns the raw
Windows file handle; this change makes the handle's thread-safety boundary
explicit with `Send` and `Sync` impls and documents the Windows handle
assumption next to the unsafe code.

This does not add the pump yet. It only prepares the adapter type for the
separate read/write loops that will wire TAP I/O to the relay session.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- CC_x86_64_pc_windows_msvc=clang-cl AR_x86_64_pc_windows_msvc=llvm-lib \
  CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=lld-link cargo clippy \
  -p lanparty-client-tap --target x86_64-pc-windows-msvc -- -D warnings
- git diff --check

Refs: PLAN.md
2026-05-21 18:59:58 +02:00
ddidderr 70fb23b538 feat(client): validate TAP Ethernet frame I/O
The future client pump should exchange Ethernet frames with TAP through a narrow
API, not raw byte reads and writes. Add TAP frame validation at the adapter
boundary so malformed or jumbo frames are rejected before they enter the relay
path.

Expose `TAP_FRAME_BUFFER_LEN` and `validate_tap_ethernet_frame`, then add
Windows helpers that read a TAP frame and validate it, or validate and fully
write an Ethernet frame. Raw read/write methods remain available for lower-level
adapter work.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- Windows-target cargo clippy for lanparty-client-tap with -D warnings
- git diff --check

Refs: PLAN.md one TAP Ethernet frame per datagram
2026-05-21 18:55:08 +02:00
ddidderr c5fc13d892 feat(client): expose cloneable relay I/O handle
The Windows client frame pump needs one side reading TAP frames and sending them
to the relay while another side receives relay datagrams and writes them to TAP.
That should not require the binary to reach into `ClientSession` internals.

Introduce `ClientRelayIo`, a cloneable handle around the accepted QUIC
connection and server welcome. It owns the Ethernet datagram send/receive logic
that previously lived directly on `ClientSession`, while `ClientSession` keeps
convenience forwarding methods for existing callers.

This is an enabling slice only. TAP frame pumping can now hold independent relay
I/O handles without broadening the platform-specific TAP crate.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- Windows-target cargo clippy for lanparty-client-tap with -D warnings
- git diff --check

Refs: PLAN.md Windows TAP frame pump
2026-05-21 18:53:07 +02:00
ddidderr c315add886 feat(client): open TAP adapter on Windows
The client can now reach the relay with a stable virtual MAC, and the TAP crate
can discover and open installed TAP-Windows6 adapters. Wire those pieces
together at startup so the Windows binary opens the first TAP adapter and marks
its media status connected after the relay handshake succeeds.

The binary reports the TAP device path plus the driver MAC and MTU. If those do
not match the tunnel identity or relay-selected MTU, it warns explicitly instead
of pretending configuration is complete.

Frame pumping and route protection remain separate follow-up slices. The full
Windows client binary still cannot be target-checked on this Linux host because
its QUIC/TLS stack needs Windows C headers for ring, but the TAP crate itself is
Windows-target checked and clippy-clean.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- Windows-target cargo clippy for lanparty-client-tap with -D warnings
- git diff --check

Refs: PLAN.md Windows TAP client
2026-05-21 18:50:09 +02:00
ddidderr a09852dada feat(client): add TAP-Windows adapter crate
The Windows client still needs a real Ethernet TAP boundary before it can pump
frames between the adapter and the relay session. Keep that OS-specific surface
separate from the QUIC client state by adding a `lanparty-client-tap` crate.

The new crate discovers TAP-Windows6 adapters through the Windows network
adapter registry class, validates `tap0901` component ids, constructs the
`\\.\Global\{NetCfgInstanceId}.tap` device path, opens the TAP device handle,
and exposes blocking Ethernet frame read/write helpers. It also wraps the
TAP-Windows IOCTLs for media status, driver MAC, and driver MTU.

This does not wire the TAP crate into `lanparty-client-win` yet and does not
attempt route protection. The value of this slice is the target-checkable OS
boundary that the next client pump can depend on.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- Windows-target cargo check for lanparty-client-tap with clang-cl/lld-link
- Windows-target cargo clippy for lanparty-client-tap with -D warnings
- git diff --check

Refs: PLAN.md Windows TAP client
2026-05-21 18:48:14 +02:00
ddidderr c07e49581c feat(relay): log Ethernet forwarding decisions
Phase 1 needs noisy frame diagnostics while the tunnel is being proven on real
LANs. The relay already had forwarding/drop decisions, but the runtime did not
emit the MAC-level fields needed to understand what happened to each frame.

Print one relay ingress log line for every accepted Ethernet datagram after the
room registry decides whether to forward or drop it. The line includes room,
peer id, source/destination MACs, ethertype or length, frame length, action,
drop reason, and target count using the shared diagnostics vocabulary.

This keeps logging simple stdout text for now. A later product slice can route
the same `lanparty-obs` fields through tracing or JSON logs without changing the
forwarding rules.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md logging diagnostics
2026-05-21 18:37:51 +02:00
ddidderr a3d24a1173 feat(client): persist virtual MAC identity
Remote clients need a stable locally administered MAC address so the relay,
gateway, DHCP lease, and LAN peers keep seeing the same tunnel identity across
runs. Requiring users to pass `--virtual-mac` made that responsibility manual.

Add a platform-neutral client identity store that loads a JSON identity file or
generates a new valid virtual MAC with OS randomness and persists it. The file
stores the MAC in the same string form shown by the CLI. The Windows client now
uses `lanparty-client-identity.json` by default while keeping `--virtual-mac` as
a manual test override.

TAP binding still remains future work; this slice only owns the client identity
that will be assigned to the TAP adapter.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md MAC identity
2026-05-21 18:35:20 +02:00
ddidderr 25157ad1a6 feat(gateway): refresh remote MAC learning
The LAN switch has to keep learning that remote client MAC addresses live on
the gateway port. Once the gateway injects a remote client's traffic, that
learning can age out if the client is quiet, breaking the Layer 2 illusion.

Track valid remote-client source MACs observed in relay traffic and inject a
small padded CAM refresh frame for each known MAC every 60 seconds. The refresh
frame uses the remote MAC as the Ethernet source and the gateway NIC MAC as the
destination, with a local experimental EtherType so hosts should ignore it.

PacketSocket now reads the wired interface hardware address with SIOCGIFHWADDR
when opening the AF_PACKET socket. Non-Ethernet or invalid source interfaces
fail early instead of starting a gateway that cannot emit refresh traffic.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md Linux gateway CAM-table refresh
2026-05-21 18:30:51 +02:00
ddidderr fe10f6ed37 fix(gateway): ignore self-injected packet frames
AF_PACKET sockets can report packets sent by the host as well as packets
received from the LAN. The gateway writes remote-client frames onto the wired
interface, so treating those outgoing packets as fresh LAN input can reflect
self-injected traffic back to the relay.

Read packet metadata with `recvfrom` and skip `PACKET_OUTGOING` frames before
returning a LAN frame to the bridge loop. This keeps capture scoped to inbound
LAN traffic and is a prerequisite for periodic CAM refresh frames.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md gateway AF_PACKET bridge
2026-05-21 18:27:38 +02:00
ddidderr 34ba2f2375 feat(relay): write development certificate
The gateway and Windows client now pin a relay certificate, but local relay
runs generated an ephemeral self-signed certificate only in memory. That made
the development trust flow awkward because there was no stable DER artifact to
feed into the new CLIs.

Add `--dev-cert-der-out` to write the generated development certificate before
the relay binds its endpoint. The file is DER-encoded and parent directories
are created when needed. This keeps the production certificate/key path explicit
future work while making the current pinned-trust flow usable.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md relay/client trust bootstrap
2026-05-21 18:24:47 +02:00
ddidderr 93f0a17f79 feat(client): add relay CLI for Windows binary
lanparty-client-win now has a real command-line surface for the relay-facing
client session. It accepts the relay address, expected TLS server name, pinned
DER relay certificate, room code, virtual TAP MAC, and advertised datagram
budget, then connects through lanparty-client-core as role = client.

The binary reports the assigned peer id, room id, and effective TAP MTU from the
welcome response, then waits for Ctrl-C. TAP adapter binding and Windows route
pinning remain future slices, but the executable now exercises the real relay
control-plane path instead of the starter placeholder.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md Windows client relay connection
2026-05-21 18:21:00 +02:00
ddidderr 914bd48346 feat(client): add relay session core
lanparty-client-core now owns the platform-neutral remote client relay session.
It validates relay trust input, room, virtual MAC identity, and datagram budget,
then connects to the relay, sends a client hello, and stores the assigned
welcome metadata.

The session exposes Ethernet datagram send and receive helpers that stamp
outgoing frames with the relay-assigned room and peer ids, ignore frames for
other rooms or from itself, and reject malformed Ethernet payloads before
handing them to the future TAP bridge.

The loopback Quinn test verifies the full client-side control and datagram path
without requiring Windows TAP access: pinned certificate trust, role = client
hello, announced virtual MAC, welcome parsing, and Ethernet datagrams in both
directions.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md Windows client relay connection and QUIC datagrams
2026-05-21 18:19:11 +02:00
ddidderr 63c829183f feat(gateway): bridge relay and LAN frames
The gateway now runs the actual frame bridge after relay admission. It registers
the AF_PACKET socket with Tokio using AsyncFd, reads valid LAN Ethernet frames
and forwards them as relay datagrams, and writes valid relay Ethernet datagrams
back to the LAN socket.

The packet socket is opened nonblocking so the bridge can shut down cleanly on
Ctrl-C without leaving a blocking recv thread behind. Existing send_ethernet and
recv_ethernet helpers now share the same validation and encoding helpers used by
the bridge.

This still needs a privileged LAN-host smoke test with a real wired interface,
but the compile-time and loopback coverage now include the gateway relay side of
the bridge and the non-root-safe packet-socket validation.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md gateway AF_PACKET to relay bridge loop
2026-05-21 18:16:04 +02:00
ddidderr 128903c312 feat(gateway): add relay Ethernet datagram helpers
GatewayConnection can now send and receive Ethernet frames over the admitted
relay QUIC connection. Outgoing frames are wrapped in the shared overlay format
with the gateway's assigned room id and peer id; incoming datagrams are ignored
unless they are Ethernet frames for the assigned room from another peer.

The receive helper also parses the payload as an Ethernet frame before exposing
it, which keeps the future AF_PACKET bridge from injecting malformed runt
payloads if the relay path ever misbehaves.

The loopback connector test now verifies the full post-handshake datagram path:
the gateway sends a frame to the test relay, the relay validates the overlay
metadata, and the gateway receives a relay-sent Ethernet frame back.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md gateway relay datagram send/receive
2026-05-21 18:12:07 +02:00
ddidderr 1b00deb419 feat(gateway): open AF_PACKET sockets
The gateway now has a small Linux PacketSocket wrapper for raw Ethernet frame
I/O. It resolves the configured interface with if_nametoindex, opens an
AF_PACKET/SOCK_RAW socket for ETH_P_ALL, binds it to the interface, and exposes
thin send_frame and recv_frame helpers around the owned file descriptor.

The gateway binary opens this socket after completing the relay control
handshake. The frame bridge loop is still intentionally left for a later slice,
but the process now proves the two required resources are available: relay
admission and raw L2 access on the LAN interface.

Tests cover interface-name validation and missing-interface lookup without
requiring root or CAP_NET_RAW.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md Linux AF_PACKET gateway socket
2026-05-21 18:09:03 +02:00
ddidderr 763a55bfba feat(gateway): connect to relay control plane
The gateway binary now has a real relay-facing configuration and QUIC control
handshake. It accepts a relay socket address, expected TLS server name, pinned
DER relay certificate, room code, LAN interface name, and advertised datagram
budget, then connects as role = gateway and waits for a welcome response.

The ALPN token moved into lanparty-ctrl so relay and gateway share the same
protocol identifier instead of carrying duplicate private constants. The gateway
still stops after the control-plane connection; AF_PACKET capture and injection
remain a later slice.

The connector test spins up a local Quinn server with a self-signed certificate,
trusts that certificate explicitly, verifies the outgoing gateway hello, and
checks the received welcome metadata.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md Linux gateway outbound relay connection
2026-05-21 18:06:22 +02:00
ddidderr 956650ea8a feat(relay): filter unsafe Ethernet control traffic
Relay forwarding now applies the MVP L2 safety policy before choosing output
peers. It drops jumbo frames, link-local switch-control destinations, EAPOL,
LLDP, and slow-protocol frames in both directions, and it blocks remote clients
from sending DHCP server replies or IPv6 router advertisements toward the LAN.

The filters live in the room forwarding path so the pure admission/forwarding
tests and live QUIC datagram path share the same policy. Gateway-origin DHCP
server replies remain allowed, which preserves the plan's goal that remote TAP
clients can receive LAN DHCP through the tunnel.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md L2 control-plane safety filters
2026-05-21 18:00:11 +02:00
ddidderr 756523927a feat(relay): forward live Ethernet datagrams
The relay now keeps active peer sessions alongside room admission state. After
a successful hello/welcome handshake, the connection enters a datagram loop
and stays registered until the QUIC connection closes.

Incoming datagrams are only considered for forwarding when their overlay room
id, peer id, and Ethernet frame type match the peer assigned by the relay.
The relay then reuses the existing room forwarding decision logic, clones the
matching live target sessions, and sends a relay-stamped Ethernet datagram to
each connected target that can carry the frame.

This keeps spoofable wire metadata out of the trust boundary: clients can put
whatever they want in an overlay header, but the relay forwards using the
room and peer identity established during the control handshake.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md QUIC DATAGRAM Ethernet forwarding path
2026-05-21 17:55:58 +02:00
ddidderr b8ae95a911 feat(relay): accept control handshakes
The relay now keeps a shared room registry behind the QUIC endpoint and
runs an accept loop instead of only binding the socket. Each accepted
connection must open its first bidirectional control stream with a hello
frame; the relay joins the room registry and replies with welcome or reject.

Admission clamps the hello datagram budget to Quinn's negotiated peer
datagram size before choosing the effective room MTU, so room state is based
on what the connection can actually carry. Accepted peers remain present
until the QUIC connection closes, then the relay removes them through the
existing leave cleanup path.

The development self-signed certificate helper now exposes the certificate
to tests so a loopback Quinn client can trust the relay and exercise the real
stream codec path.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md relay QUIC control-stream startup flow
2026-05-21 17:51:40 +02:00
ddidderr 81ad7abe84 feat(relay): clean up peers on leave
Add explicit room leave semantics for future relay connection tasks. Disconnect
handling will need to remove peers from room membership without reaching into the
room internals or leaving stale MAC indexes behind.

Leaving a client now removes both its peer entry and MAC mapping so the same MAC
can rejoin later. Leaving a gateway clears gateway occupancy while preserving any
remaining clients. If the last peer leaves, the room is removed from the
registry. The result reports which peer left and whether the room was removed so
the networking layer can emit lifecycle events cleanly.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md relay disconnect reason and reconnect handling groundwork
2026-05-21 17:41:16 +02:00
ddidderr 77894c4706 feat(relay): bind QUIC endpoint
Make the relay binary bind a real Quinn endpoint instead of only printing its
configuration. This is the next runtime step toward the public relay while still
keeping connection handling out of this commit.

The relay now builds a self-signed development TLS configuration, advertises the
lanparty ALPN, enables QUIC datagram buffers, binds the configured UDP address,
prints the actual local address, and waits for Ctrl-C before closing the
endpoint. The generated certificate is explicitly a development placeholder;
production certificate and client trust handling remain future work.

The rustls dependency is pinned to the ring provider to match Quinn's selected
crypto backend and avoid process-level provider ambiguity at runtime.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- timeout 2s cargo run -p lanparty-relay -- --listen 127.0.0.1:0 || test $? -eq 124

Refs: PLAN.md public relay QUIC data path
Refs: https://docs.rs/quinn/0.11.9
2026-05-21 17:38:56 +02:00
ddidderr be9596c188 feat(relay): add runtime CLI config
Replace the placeholder relay binary with a typed command-line configuration
entry point. This gives the future QUIC server loop the listen endpoint and room
limit configuration it needs without mixing command parsing into networking or
room-state code.

The relay now accepts --listen as either a socket address or an explicit UDP
shorthand such as 443/udp, defaults to 0.0.0.0:443/udp, and validates that the
per-room client limit is positive. The binary currently reports the parsed
configuration and clearly states that the QUIC server loop is not wired yet, so
this commit does not pretend to provide a working relay.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- cargo run -p lanparty-relay -- --listen 443/udp

Refs: PLAN.md public relay --listen requirement
2026-05-21 17:32:47 +02:00
ddidderr b3d1a9c046 feat(relay): add Ethernet forwarding decisions
Add socket-free relay forwarding logic for Ethernet datagrams. The future QUIC
relay loop can now ask the room registry which peer IDs should receive a frame
instead of embedding switching policy in network IO code.

Forwarding validates that the ingress peer belongs to the room, drops malformed
Ethernet frames, rejects client frames whose source MAC does not match the MAC
announced during admission, never reflects frames back to ingress, routes known
client unicasts directly, and floods broadcast/multicast or unknown unicast
frames to the other room peers. The decision reports shared observability action
and drop-reason values so the networking layer can log consistently.

This still does not send bytes over QUIC; it only defines the room-local switch
decision that the datagram loop will use.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md Switching model
2026-05-21 17:28:17 +02:00
ddidderr 879cb689a4 feat(ctrl): add control stream frame codec
Add the framing layer that turns typed control messages into bytes for reliable
QUIC streams. This keeps the codec next to the control schema while leaving the
actual QUIC read/write loops for a later relay/client/gateway slice.

The codec uses a four-byte big-endian length prefix followed by JSON. JSON is a
phase-1 choice for inspectability during manual tunnel bring-up; the explicit
length prefix keeps stream parsing deterministic and the 64 KiB cap prevents a
peer from announcing unbounded control payloads. Decoding validates the message
after deserialization so forged stream bytes cannot bypass constructor checks.

The next networking slice can use complete_control_frame_len to split a stream
buffer and decode_control_frame once a complete frame is available.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md reliable QUIC control stream requirements
2026-05-21 17:25:26 +02:00