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
+54 -16
View File
@@ -15,7 +15,7 @@ use lanparty_client_core::{
#[cfg(windows)]
use lanparty_client_route::{
IpInterfaceFamily, NetworkInterfaceIdentity, PinnedRelayRoute, RouteSnapshot,
ScopedInterfaceMetric,
ScopedDefaultRoutes, ScopedInterfaceMetric,
};
#[cfg(windows)]
use lanparty_client_tap::TapAdapter;
@@ -124,9 +124,9 @@ async fn main() -> Result<()> {
#[cfg(windows)]
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!(
"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! {
@@ -190,13 +190,15 @@ fn print_pinned_relay_route(route: &PinnedRelayRoute) {
#[cfg(windows)]
struct OpenedTapAdapter {
tap: TapAdapter,
_metric_guard: TapMetricGuard,
_route_guard: TapRouteProtectionGuard,
}
#[cfg(windows)]
struct TapMetricGuard {
_ipv4: ScopedInterfaceMetric,
_ipv6: Option<ScopedInterfaceMetric>,
struct TapRouteProtectionGuard {
_ipv4_metric: ScopedInterfaceMetric,
_ipv4_default_routes: ScopedDefaultRoutes,
_ipv6_metric: Option<ScopedInterfaceMetric>,
_ipv6_default_routes: Option<ScopedDefaultRoutes>,
}
#[cfg(windows)]
@@ -206,7 +208,7 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
let tap_interface =
lanparty_client_route::interface_identity_from_guid(tap.info().instance_id())
.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_mtu = tap.driver_mtu()?;
@@ -242,21 +244,29 @@ fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
Ok(OpenedTapAdapter {
tap,
_metric_guard: metric_guard,
_route_guard: route_guard,
})
}
#[cfg(windows)]
fn protect_tap_metrics(identity: NetworkInterfaceIdentity) -> Result<TapMetricGuard> {
let ipv4 = lanparty_client_route::set_scoped_interface_metric(
fn protect_tap_routes(identity: NetworkInterfaceIdentity) -> Result<TapRouteProtectionGuard> {
let ipv4_metric = lanparty_client_route::set_scoped_interface_metric(
identity,
IpInterfaceFamily::Ipv4,
TAP_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,
IpInterfaceFamily::Ipv6,
TAP_INTERFACE_METRIC,
@@ -273,9 +283,28 @@ fn protect_tap_metrics(identity: NetworkInterfaceIdentity) -> Result<TapMetricGu
}
};
Ok(TapMetricGuard {
_ipv4: ipv4,
_ipv6: ipv6,
let ipv6_default_routes = match lanparty_client_route::set_scoped_default_routes_disabled(
identity,
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)]
async fn run_tap_frame_pump(relay_io: ClientRelayIo, tap: TapAdapter) -> Result<()> {
let tap = Arc::new(tap);