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
This commit is contained in:
2026-05-21 19:30:57 +02:00
parent bbe12e851a
commit dc85b1dbd6
2 changed files with 66 additions and 18 deletions
+63 -16
View File
@@ -5,7 +5,7 @@ use std::{
thread,
};
use anyhow::{Context, Result};
use anyhow::{Context, Result, bail};
use clap::Parser;
#[cfg(windows)]
use lanparty_client_core::ClientRelayIo;
@@ -204,13 +204,19 @@ struct TapRouteProtectionGuard {
#[cfg(windows)]
fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
let tap = lanparty_client_tap::open_first_adapter()?;
tap.set_media_connected(true)?;
let tap_interface =
lanparty_client_route::interface_identity_from_guid(tap.info().instance_id())
.context("failed to resolve TAP interface identity")?;
let route_guard = protect_tap_routes(tap_interface)?;
let driver_mac = tap.driver_mac()?;
let driver_mtu = tap.driver_mtu()?;
validate_tap_driver_settings(
session.config().virtual_mac(),
session.welcome().effective_tap_mtu(),
driver_mac,
driver_mtu,
)?;
tap.set_media_connected(true)?;
println!(
"lanparty-client-win opened TAP adapter {}",
@@ -227,20 +233,6 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
tap_interface.index(),
tap_interface.luid()
);
if driver_mac != session.config().virtual_mac() {
eprintln!(
"TAP driver MAC {} does not match tunnel identity {}; MAC configuration is not wired yet",
driver_mac,
session.config().virtual_mac()
);
}
if driver_mtu != u32::from(session.welcome().effective_tap_mtu()) {
eprintln!(
"TAP driver MTU {} does not match relay-selected MTU {}; MTU configuration is not wired yet",
driver_mtu,
session.welcome().effective_tap_mtu()
);
}
Ok(OpenedTapAdapter {
tap,
@@ -248,6 +240,29 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
})
}
#[cfg_attr(not(windows), allow(dead_code))]
fn validate_tap_driver_settings(
expected_mac: MacAddr,
expected_mtu: u16,
driver_mac: MacAddr,
driver_mtu: u32,
) -> Result<()> {
if driver_mac != expected_mac {
bail!(
"TAP driver MAC {driver_mac} does not match tunnel identity {expected_mac}; automatic MAC configuration is not wired yet"
);
}
let expected_mtu = u32::from(expected_mtu);
if driver_mtu != expected_mtu {
bail!(
"TAP driver MTU {driver_mtu} does not match relay-selected MTU {expected_mtu}; automatic MTU configuration is not wired yet"
);
}
Ok(())
}
#[cfg(windows)]
fn protect_tap_routes(identity: NetworkInterfaceIdentity) -> Result<TapRouteProtectionGuard> {
let ipv4_metric = lanparty_client_route::set_scoped_interface_metric(
@@ -403,3 +418,35 @@ fn read_and_relay_tap_frame(
fn open_tap_adapter(_session: &ClientSession) {
println!("Windows TAP adapter opening is compiled only on Windows");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_matching_tap_driver_settings() {
assert!(validate_tap_driver_settings(mac(1), 1200, mac(1), 1200).is_ok());
}
#[test]
fn rejects_tap_driver_mac_mismatch() {
let error = validate_tap_driver_settings(mac(1), 1200, mac(2), 1200).unwrap_err();
assert!(error.to_string().contains("does not match tunnel identity"));
}
#[test]
fn rejects_tap_driver_mtu_mismatch() {
let error = validate_tap_driver_settings(mac(1), 1200, mac(1), 1500).unwrap_err();
assert!(
error
.to_string()
.contains("does not match relay-selected MTU")
);
}
const fn mac(last_octet: u8) -> MacAddr {
MacAddr::new([0x02, 0, 0, 0, 0, last_octet])
}
}