feat(client): disable TAP default routes while running

The Windows client now holds scoped default-route suppression guards for the
TAP interface while the frame pump is active. IPv4 protection is required,
matching the relay-route safety path. IPv6 protection is still best-effort so
IPv4-only Windows TAP setups do not fail startup just because there is no IPv6
interface row to update.

This completes the current client-side route-policy wiring from PLAN.md: the
relay host route is pinned before TAP activation, TAP interface metrics are
raised while running, and TAP default routes are disabled until the client
exits or startup unwinds. Automatic TAP MAC and MTU configuration remain
follow-up work.

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.
The independent Windows-target route crate checks do pass.

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

Refs: PLAN.md
This commit is contained in:
2026-05-21 19:28:14 +02:00
parent 9c0a974281
commit bbe12e851a
2 changed files with 57 additions and 19 deletions
+3 -3
View File
@@ -140,6 +140,6 @@ adapter until shutdown.
`--virtual-mac` can still override the stored identity for manual testing. On `--virtual-mac` can still override the stored identity for manual testing. On
Windows it marks the TAP media connected and reports the driver MAC/MTU before Windows it marks the TAP media connected and reports the driver MAC/MTU before
forwarding frames, along with the TAP interface index/LUID. The client applies forwarding frames, along with the TAP interface index/LUID. The client applies
a scoped TAP interface metric while it runs and restores the previous metric on a scoped TAP interface metric and disables TAP default routes while it runs,
exit. Default-route takeover detection and automatic TAP MAC/MTU configuration then restores the previous route policy on exit. Automatic TAP MAC/MTU
are not wired yet. configuration is not wired yet.
+54 -16
View File
@@ -15,7 +15,7 @@ use lanparty_client_core::{
#[cfg(windows)] #[cfg(windows)]
use lanparty_client_route::{ use lanparty_client_route::{
IpInterfaceFamily, NetworkInterfaceIdentity, PinnedRelayRoute, RouteSnapshot, IpInterfaceFamily, NetworkInterfaceIdentity, PinnedRelayRoute, RouteSnapshot,
ScopedInterfaceMetric, ScopedDefaultRoutes, ScopedInterfaceMetric,
}; };
#[cfg(windows)] #[cfg(windows)]
use lanparty_client_tap::TapAdapter; use lanparty_client_tap::TapAdapter;
@@ -124,9 +124,9 @@ async fn main() -> Result<()> {
#[cfg(windows)] #[cfg(windows)]
async fn run_client(session: &ClientSession) -> Result<()> { async fn run_client(session: &ClientSession) -> Result<()> {
let OpenedTapAdapter { tap, _metric_guard } = open_tap_adapter(session)?; let OpenedTapAdapter { tap, _route_guard } = open_tap_adapter(session)?;
println!( println!(
"bridging TAP frames; relay route is pinned and TAP metrics are scoped; default-route takeover detection is not wired yet; press Ctrl-C to stop" "bridging TAP frames; relay route is pinned and TAP route policy is scoped; press Ctrl-C to stop"
); );
tokio::select! { tokio::select! {
@@ -190,13 +190,15 @@ fn print_pinned_relay_route(route: &PinnedRelayRoute) {
#[cfg(windows)] #[cfg(windows)]
struct OpenedTapAdapter { struct OpenedTapAdapter {
tap: TapAdapter, tap: TapAdapter,
_metric_guard: TapMetricGuard, _route_guard: TapRouteProtectionGuard,
} }
#[cfg(windows)] #[cfg(windows)]
struct TapMetricGuard { struct TapRouteProtectionGuard {
_ipv4: ScopedInterfaceMetric, _ipv4_metric: ScopedInterfaceMetric,
_ipv6: Option<ScopedInterfaceMetric>, _ipv4_default_routes: ScopedDefaultRoutes,
_ipv6_metric: Option<ScopedInterfaceMetric>,
_ipv6_default_routes: Option<ScopedDefaultRoutes>,
} }
#[cfg(windows)] #[cfg(windows)]
@@ -206,7 +208,7 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
let tap_interface = let tap_interface =
lanparty_client_route::interface_identity_from_guid(tap.info().instance_id()) lanparty_client_route::interface_identity_from_guid(tap.info().instance_id())
.context("failed to resolve TAP interface identity")?; .context("failed to resolve TAP interface identity")?;
let metric_guard = protect_tap_metrics(tap_interface)?; let route_guard = protect_tap_routes(tap_interface)?;
let driver_mac = tap.driver_mac()?; let driver_mac = tap.driver_mac()?;
let driver_mtu = tap.driver_mtu()?; let driver_mtu = tap.driver_mtu()?;
@@ -242,21 +244,29 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
Ok(OpenedTapAdapter { Ok(OpenedTapAdapter {
tap, tap,
_metric_guard: metric_guard, _route_guard: route_guard,
}) })
} }
#[cfg(windows)] #[cfg(windows)]
fn protect_tap_metrics(identity: NetworkInterfaceIdentity) -> Result<TapMetricGuard> { fn protect_tap_routes(identity: NetworkInterfaceIdentity) -> Result<TapRouteProtectionGuard> {
let ipv4 = lanparty_client_route::set_scoped_interface_metric( let ipv4_metric = lanparty_client_route::set_scoped_interface_metric(
identity, identity,
IpInterfaceFamily::Ipv4, IpInterfaceFamily::Ipv4,
TAP_INTERFACE_METRIC, TAP_INTERFACE_METRIC,
) )
.context("failed to set TAP IPv4 interface metric")?; .context("failed to set TAP IPv4 interface metric")?;
print_tap_metric_override(IpInterfaceFamily::Ipv4, &ipv4); print_tap_metric_override(IpInterfaceFamily::Ipv4, &ipv4_metric);
let ipv6 = match lanparty_client_route::set_scoped_interface_metric( let ipv4_default_routes = lanparty_client_route::set_scoped_default_routes_disabled(
identity,
IpInterfaceFamily::Ipv4,
true,
)
.context("failed to disable TAP IPv4 default routes")?;
print_tap_default_routes_override(IpInterfaceFamily::Ipv4, &ipv4_default_routes);
let ipv6_metric = match lanparty_client_route::set_scoped_interface_metric(
identity, identity,
IpInterfaceFamily::Ipv6, IpInterfaceFamily::Ipv6,
TAP_INTERFACE_METRIC, TAP_INTERFACE_METRIC,
@@ -273,9 +283,28 @@ fn protect_tap_metrics(identity: NetworkInterfaceIdentity) -> Result<TapMetricGu
} }
}; };
Ok(TapMetricGuard { let ipv6_default_routes = match lanparty_client_route::set_scoped_default_routes_disabled(
_ipv4: ipv4, identity,
_ipv6: ipv6, IpInterfaceFamily::Ipv6,
true,
) {
Ok(default_routes) => {
print_tap_default_routes_override(IpInterfaceFamily::Ipv6, &default_routes);
Some(default_routes)
}
Err(error) => {
eprintln!(
"failed to disable TAP IPv6 default routes; IPv6 route protection may be incomplete: {error:#}"
);
None
}
};
Ok(TapRouteProtectionGuard {
_ipv4_metric: ipv4_metric,
_ipv4_default_routes: ipv4_default_routes,
_ipv6_metric: ipv6_metric,
_ipv6_default_routes: ipv6_default_routes,
}) })
} }
@@ -290,6 +319,15 @@ fn print_tap_metric_override(family: IpInterfaceFamily, metric: &ScopedInterface
); );
} }
#[cfg(windows)]
fn print_tap_default_routes_override(family: IpInterfaceFamily, routes: &ScopedDefaultRoutes) {
let previous = routes.previous();
println!(
"TAP {family:?} default routes disabled; previous default-routes-disabled {}",
previous.disable_default_routes()
);
}
#[cfg(windows)] #[cfg(windows)]
async fn run_tap_frame_pump(relay_io: ClientRelayIo, tap: TapAdapter) -> Result<()> { async fn run_tap_frame_pump(relay_io: ClientRelayIo, tap: TapAdapter) -> Result<()> {
let tap = Arc::new(tap); let tap = Arc::new(tap);