fix(client): restore TAP media status on exit

The Windows client already treats TAP route, metric, and MTU changes as scoped
runtime state that is restored when the process exits. TAP media status was the
odd one out: startup marked the adapter connected, but there was no matching
cleanup path to mark it disconnected again.

Add a small TAP media guard that owns an Arc to the opened adapter and marks
media disconnected on drop. The frame pump now receives an Arc<TapAdapter>, so
the guard can run on normal Ctrl-C, startup errors after TAP open, route-check
failures, or frame-pump failures without fighting ownership of the TAP reader
thread. Cleanup errors are logged because Drop cannot return them.

Update the README and MVP test guide so the documented cleanup behavior matches
the client runtime.

The full Windows client cross-check is still blocked on this host before project
code by ring needing x86_64-w64-mingw32-gcc. The TAP and route helper crates
still check for the Windows GNU target.

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

Refs: MVP Windows TAP cleanup
This commit is contained in:
2026-05-21 23:37:47 +02:00
parent 23043dcce6
commit efda797ae6
3 changed files with 40 additions and 11 deletions
+33 -5
View File
@@ -208,6 +208,7 @@ async fn run_client(
tap,
tap_diagnostics,
tap_interface,
_media_guard,
_route_guard,
} = open_tap_adapter(session, tap_instance_id)?;
let relay_route =
@@ -396,9 +397,10 @@ fn route_next_hop_label(next_hop: Option<std::net::IpAddr>) -> String {
#[cfg(windows)]
struct OpenedTapAdapter {
tap: TapAdapter,
tap: Arc<TapAdapter>,
tap_diagnostics: TapDiagnostics,
tap_interface: NetworkInterfaceIdentity,
_media_guard: TapMediaStatusGuard,
_route_guard: TapRouteProtectionGuard,
}
@@ -412,12 +414,38 @@ struct TapRouteProtectionGuard {
_ipv6_mtu: Option<ScopedInterfaceMtu>,
}
#[cfg(windows)]
struct TapMediaStatusGuard {
tap: Arc<TapAdapter>,
}
#[cfg(windows)]
impl TapMediaStatusGuard {
fn connected(tap: Arc<TapAdapter>) -> Result<Self> {
tap.set_media_connected(true)?;
Ok(Self { tap })
}
}
#[cfg(windows)]
impl Drop for TapMediaStatusGuard {
fn drop(&mut self) {
if let Err(error) = self.tap.set_media_connected(false) {
eprintln!("failed to mark TAP media disconnected during cleanup: {error:#}");
}
}
}
#[cfg(windows)]
fn open_tap_adapter(
session: &ClientSession,
tap_instance_id: Option<&str>,
) -> Result<OpenedTapAdapter> {
let tap = open_configured_tap_adapter(session.config().virtual_mac(), tap_instance_id)?;
let tap = Arc::new(open_configured_tap_adapter(
session.config().virtual_mac(),
tap_instance_id,
)?);
let tap_interface =
lanparty_client_route::interface_identity_from_guid(tap.info().instance_id())
.context("failed to resolve TAP interface identity")?;
@@ -425,7 +453,7 @@ fn open_tap_adapter(
let driver_mac = tap.driver_mac()?;
let driver_mtu = tap.driver_mtu()?;
validate_tap_driver_mac(session.config().virtual_mac(), driver_mac)?;
tap.set_media_connected(true)?;
let media_guard = TapMediaStatusGuard::connected(Arc::clone(&tap))?;
println!(
"lanparty-client-win opened TAP adapter {}",
@@ -453,6 +481,7 @@ fn open_tap_adapter(
tap,
tap_diagnostics,
tap_interface,
_media_guard: media_guard,
_route_guard: route_guard,
})
}
@@ -908,8 +937,7 @@ fn tap_default_routes_override_message(family: &str, previous_disabled: bool) ->
}
#[cfg(windows)]
async fn run_tap_frame_pump(relay_io: ClientRelayIo, tap: TapAdapter) -> Result<()> {
let tap = Arc::new(tap);
async fn run_tap_frame_pump(relay_io: ClientRelayIo, tap: Arc<TapAdapter>) -> Result<()> {
let (tap_error_tx, tap_error_rx) = mpsc::channel();
spawn_tap_reader(Arc::clone(&tap), relay_io.clone(), tap_error_tx)?;