Commit Graph

150 Commits

Author SHA1 Message Date
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
ddidderr d1e6530829 feat(relay): add room admission state
Add a tested relay room layer before introducing QUIC socket handling. The relay
now has a focused place to enforce room membership rules instead of mixing those
rules into the future networking loop.

RoomRegistry accepts validated endpoint hellos, assigns room and peer IDs,
returns server welcome data, limits clients per room, permits only one gateway,
rejects duplicate client MACs, and keeps the room TAP MTU stable once the first
peer joins. A later peer must support the existing room MTU rather than silently
shrinking it after an earlier client may already have configured its TAP adapter.

The networking pieces still need to call this layer from the reliable control
stream and use the resulting peer metadata for datagram forwarding.

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

Refs: PLAN.md relay responsibilities and MAC identity
2026-05-21 17:21:46 +02:00
ddidderr 7aeaa0aeb9 feat(obs): add shared diagnostics models
Add the observability vocabulary needed for phase-1 frame logging and client
status reporting. Runtime crates can now emit structured events without each
binary inventing separate field names for the same tunnel state.

The new models cover frame direction, action, drop reason, parsed Ethernet frame
logs, malformed frame logs, tunnel counters, relay/QUIC/TAP client diagnostics,
and user-facing diagnostic messages. TunnelStats now lives in lanparty-obs and
is re-exported by lanparty-ctrl so stats remain one shared type whether they are
logged locally or carried over the control stream.

This still does not add logging sinks or tracing integration; those should be
wired in when the relay, gateway, and client loops exist.

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

Refs: PLAN.md Logging / diagnostics
2026-05-21 17:17:44 +02:00
ddidderr a9c143e447 feat(ctrl): define tunnel control messages
Add the reliable control-plane schema that will run over QUIC streams. This
covers the phase-1 handshake shape without mixing in relay sockets, TAP access,
or gateway packet IO.

The schema includes endpoint hello messages with role, room, MAC, and datagram
budget, plus server welcome, rejection, peer lifecycle, stats, and disconnect
messages. Constructors and validation enforce room-code syntax, client MAC
identity rules, reserved peer IDs, and effective TAP MTU limits. Decoded control
messages can be validated explicitly so serde input cannot silently bypass the
same invariants.

The actual stream codec remains future work; this commit only fixes the typed
contract the codec will carry.

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:12:56 +02:00
ddidderr f06760d1ac feat(proto): add tunnel frame primitives
Build the shared protocol contract that the client, gateway, and relay will use
for Ethernet datagrams. The MVP needs these pieces agreed on before socket or
TAP work can be reasoned about safely.

This adds strict MAC parsing and client identity validation, Ethernet header
inspection, fixed overlay datagram encoding and decoding, and MTU helpers for
the no-fragmentation QUIC datagram design. The protocol crate stays
transport-agnostic so platform and network code can depend on it without
pulling in OS-specific behavior.

Remaining work is to put these primitives behind the control-plane handshake,
relay forwarding loop, Windows TAP client, and Linux AF_PACKET gateway.

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

Refs: PLAN.md Phase 1: prove the illusion
2026-05-21 17:09:01 +02:00
ddidderr 3c395db3df chore: first project structure 2026-05-21 16:55:51 +02:00