Commit Graph

77 Commits

Author SHA1 Message Date
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