feat(client): scope TAP interface metrics while running
The Windows client now applies a high manual metric to the TAP interface while the adapter is active. This keeps ordinary host routes preferred over TAP routes during the tunnel lifetime, and the route crate guard restores the previous metric and automatic-metric state when the client exits or startup unwinds. IPv4 metric protection is required because the tunnel depends on keeping the relay path reachable. IPv6 metric protection is attempted as a best-effort step so IPv4-only Windows setups can still run while dual-stack hosts receive similar protection when the IPv6 interface row exists. The metric guard is held for the same lifetime as the TAP frame pump. The relay host-route pin remains held through QUIC shutdown. Default-route takeover detection and automatic TAP MAC/MTU configuration are still follow-up work from PLAN.md. 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:
@@ -138,6 +138,7 @@ and then bridges Ethernet frames between the relay and the first TAP-Windows6
|
||||
adapter until shutdown.
|
||||
`--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
|
||||
forwarding frames, along with the TAP interface index/LUID. Default-route
|
||||
takeover neutralization and automatic TAP MAC/MTU configuration are not wired
|
||||
yet.
|
||||
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
|
||||
exit. Default-route takeover detection and automatic TAP MAC/MTU configuration
|
||||
are not wired yet.
|
||||
|
||||
@@ -13,12 +13,18 @@ use lanparty_client_core::{
|
||||
ClientIdentity, ClientIdentityStore, ClientSession, ClientSessionConfig, connect_client,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use lanparty_client_route::{PinnedRelayRoute, RouteSnapshot};
|
||||
use lanparty_client_route::{
|
||||
IpInterfaceFamily, NetworkInterfaceIdentity, PinnedRelayRoute, RouteSnapshot,
|
||||
ScopedInterfaceMetric,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use lanparty_client_tap::TapAdapter;
|
||||
use lanparty_ctrl::RoomCode;
|
||||
use lanparty_proto::MacAddr;
|
||||
|
||||
#[cfg(windows)]
|
||||
const TAP_INTERFACE_METRIC: u32 = 9_000;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "lanparty-client-win",
|
||||
@@ -118,9 +124,9 @@ async fn main() -> Result<()> {
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn run_client(session: &ClientSession) -> Result<()> {
|
||||
let tap = open_tap_adapter(session)?;
|
||||
let OpenedTapAdapter { tap, _metric_guard } = open_tap_adapter(session)?;
|
||||
println!(
|
||||
"bridging TAP frames; relay route is pinned; default-route neutralization is not wired yet; press Ctrl-C to stop"
|
||||
"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"
|
||||
);
|
||||
|
||||
tokio::select! {
|
||||
@@ -182,12 +188,25 @@ fn print_pinned_relay_route(route: &PinnedRelayRoute) {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn open_tap_adapter(session: &ClientSession) -> Result<lanparty_client_tap::TapAdapter> {
|
||||
struct OpenedTapAdapter {
|
||||
tap: TapAdapter,
|
||||
_metric_guard: TapMetricGuard,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
struct TapMetricGuard {
|
||||
_ipv4: ScopedInterfaceMetric,
|
||||
_ipv6: Option<ScopedInterfaceMetric>,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn open_tap_adapter(session: &ClientSession) -> Result<OpenedTapAdapter> {
|
||||
let tap = lanparty_client_tap::open_first_adapter()?;
|
||||
tap.set_media_connected(true)?;
|
||||
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 driver_mac = tap.driver_mac()?;
|
||||
let driver_mtu = tap.driver_mtu()?;
|
||||
|
||||
@@ -221,7 +240,54 @@ fn open_tap_adapter(session: &ClientSession) -> Result<lanparty_client_tap::TapA
|
||||
);
|
||||
}
|
||||
|
||||
Ok(tap)
|
||||
Ok(OpenedTapAdapter {
|
||||
tap,
|
||||
_metric_guard: metric_guard,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn protect_tap_metrics(identity: NetworkInterfaceIdentity) -> Result<TapMetricGuard> {
|
||||
let ipv4 = 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);
|
||||
|
||||
let ipv6 = match lanparty_client_route::set_scoped_interface_metric(
|
||||
identity,
|
||||
IpInterfaceFamily::Ipv6,
|
||||
TAP_INTERFACE_METRIC,
|
||||
) {
|
||||
Ok(metric) => {
|
||||
print_tap_metric_override(IpInterfaceFamily::Ipv6, &metric);
|
||||
Some(metric)
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!(
|
||||
"failed to set TAP IPv6 interface metric; IPv6 route protection may be incomplete: {error:#}"
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TapMetricGuard {
|
||||
_ipv4: ipv4,
|
||||
_ipv6: ipv6,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn print_tap_metric_override(family: IpInterfaceFamily, metric: &ScopedInterfaceMetric) {
|
||||
let previous = metric.previous();
|
||||
println!(
|
||||
"TAP {family:?} interface metric set to {TAP_INTERFACE_METRIC}; previous metric {} automatic {} default-routes-disabled {}",
|
||||
previous.metric(),
|
||||
previous.automatic_metric(),
|
||||
previous.disable_default_routes()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
Reference in New Issue
Block a user