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
This commit is contained in:
2026-05-21 18:50:09 +02:00
parent a09852dada
commit c315add886
4 changed files with 53 additions and 4 deletions
+46 -2
View File
@@ -3,7 +3,7 @@ use std::{fs, net::SocketAddr, path::PathBuf};
use anyhow::{Context, Result};
use clap::Parser;
use lanparty_client_core::{
ClientIdentity, ClientIdentityStore, ClientSessionConfig, connect_client,
ClientIdentity, ClientIdentityStore, ClientSession, ClientSessionConfig, connect_client,
};
use lanparty_ctrl::RoomCode;
use lanparty_proto::MacAddr;
@@ -89,7 +89,11 @@ async fn main() -> Result<()> {
session.welcome().room_id(),
session.welcome().effective_tap_mtu()
);
println!("Windows TAP binding and route pinning are not wired yet; press Ctrl-C to stop");
#[cfg(windows)]
let _tap = open_tap_adapter(&session)?;
#[cfg(not(windows))]
open_tap_adapter(&session);
println!("TAP frame pump and route pinning are not wired yet; press Ctrl-C to stop");
tokio::signal::ctrl_c()
.await
@@ -98,3 +102,43 @@ async fn main() -> Result<()> {
Ok(())
}
#[cfg(windows)]
fn open_tap_adapter(session: &ClientSession) -> Result<lanparty_client_tap::TapAdapter> {
let tap = lanparty_client_tap::open_first_adapter()?;
tap.set_media_connected(true)?;
let driver_mac = tap.driver_mac()?;
let driver_mtu = tap.driver_mtu()?;
println!(
"lanparty-client-win opened TAP adapter {}",
tap.info().device_path()
);
println!(
"TAP driver reports MAC {} and MTU {}; relay selected TAP MTU {}",
driver_mac,
driver_mtu,
session.welcome().effective_tap_mtu()
);
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(tap)
}
#[cfg(not(windows))]
fn open_tap_adapter(_session: &ClientSession) {
println!("Windows TAP adapter opening is compiled only on Windows");
}