fix(client): pin relay route before QUIC connect

The Windows client already prepared TAP before relay resolution, but it still
opened the QUIC control connection before installing the relay host route. TAP
was not active yet, so this was not the self-routing failure mode, but it left
the relay flow unpinned during the control handshake.

Resolve the relay, pin and verify the relay host route, then open the relay
connection. Keep the after-TAP verification as the guard against DHCP or TAP
route policy taking over the relay path. Update README.md and TESTING.md so the
MVP startup description and expected log order match that safer flow.

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

Windows-target check attempted:
- cargo check -p lanparty-client-win --target x86_64-pc-windows-msvc

With LLVM tools configured, that still stops inside ring on this Linux host
because the Windows C headers are unavailable, starting with assert.h.

Refs: MVP relay-route protection
This commit is contained in:
2026-05-22 08:00:17 +02:00
parent ec82cae981
commit c5523361a6
3 changed files with 10 additions and 16 deletions
+6 -6
View File
@@ -238,12 +238,12 @@ it writes the generated tunnel MAC to the selected TAP driver's
`NetworkAddress` registry setting and marks TAP media disconnected. That clears
stale connected state from a previous crashed run without letting the TAP
adapter influence relay DNS or route selection. The client then resolves the
relay endpoint, completes the control-stream hello/welcome handshake, pins a
host route for the resolved relay IP on the current pre-TAP interface, verifies
that Windows is already using that host route, verifies it again after TAP
activation, and bridges Ethernet frames between the relay and the TAP-Windows6
adapter until shutdown. `--relay` accepts a DNS name or socket address; bare
hosts default to UDP/443.
relay endpoint, pins a host route for the resolved relay IP on the current
pre-TAP interface, verifies that Windows is using that host route, completes
the control-stream hello/welcome handshake, verifies the host route again after
TAP activation, and bridges Ethernet frames between the relay and the
TAP-Windows6 adapter until shutdown. `--relay` accepts a DNS name or socket
address; bare hosts default to UDP/443.
TAP frames whose source MAC does not match that generated tunnel MAC are
dropped locally before they can consume relay bandwidth; the relay still
enforces the same source-MAC rule.
+2 -2
View File
@@ -171,10 +171,10 @@ Expected client output:
```text
prepared TAP adapter ... MAC ... configured and media disconnected before relay connect
lanparty-client-win connected as peer ...; LAN gateway connected yes (peer ...)
relay event: LAN gateway connected as peer ...
relay route pinned before TAP ...
relay route verified before TAP activation ...
lanparty-client-win connected as peer ...; LAN gateway connected yes (peer ...)
relay event: LAN gateway connected as peer ...
relay route verified after TAP activation ...
TAP driver reports MAC ... and MTU ...
client diagnostics: relay reachable yes gateway connected yes route pinned yes ...
+2 -8
View File
@@ -179,6 +179,8 @@ async fn main() -> Result<()> {
startup_config.identity.virtual_mac(),
)?;
let config = startup_config.into_runtime_config()?;
#[cfg(windows)]
let relay_route_pin = pin_relay_route_before_tap(config.session.relay_addr().ip())?;
println!(
"lanparty-client-win connecting virtual MAC {} to relay {} room {}",
config.session.virtual_mac(),
@@ -199,14 +201,6 @@ async fn main() -> Result<()> {
)
);
#[cfg(windows)]
let relay_route_pin = match pin_relay_route_before_tap(session.config().relay_addr().ip()) {
Ok(pin) => pin,
Err(error) => {
session.shutdown("client startup failed").await;
return Err(error);
}
};
#[cfg(windows)]
let run_result = run_client(
&session,
&relay_route_pin,