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
This commit is contained in:
2026-05-21 21:21:47 +02:00
parent 6f3be2d4fa
commit 2d30f4ed68
4 changed files with 124 additions and 15 deletions
+19 -2
View File
@@ -328,7 +328,7 @@ struct TapRouteProtectionGuard {
#[cfg(windows)]
fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
let tap = lanparty_client_tap::open_first_adapter()?;
let tap = open_configured_tap_adapter(session.config().virtual_mac())?;
let tap_interface =
lanparty_client_route::interface_identity_from_guid(tap.info().instance_id())
.context("failed to resolve TAP interface identity")?;
@@ -368,6 +368,23 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
})
}
#[cfg(windows)]
fn open_configured_tap_adapter(virtual_mac: MacAddr) -> Result<TapAdapter> {
let mut adapters = lanparty_client_tap::available_adapters()?;
let info = adapters
.drain(..)
.next()
.context("no TAP-Windows6 adapters found")?;
lanparty_client_tap::configure_adapter_mac(&info, virtual_mac).with_context(|| {
format!(
"failed to persist TAP MAC {virtual_mac} for adapter {}",
info.instance_id()
)
})?;
TapAdapter::open(info)
}
fn client_diagnostics_snapshot(
session: &ClientSession,
route_pinned: bool,
@@ -533,7 +550,7 @@ fn preferred_tap_ip(
fn validate_tap_driver_mac(expected_mac: MacAddr, driver_mac: MacAddr) -> 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"
"TAP driver MAC {driver_mac} does not match tunnel identity {expected_mac}; the NetworkAddress registry value was written before opening the adapter, but Windows may need the TAP adapter disabled/enabled or reinstalled before the driver reloads it"
);
}