feat(client): add scoped interface metric override
Add a reversible IP interface metric boundary to `lanparty-client-route`. The crate can now read an IPv4 or IPv6 interface metric snapshot and temporarily set a manual interface metric with an RAII guard that restores the previous metric and automatic-metric state on drop. This prepares the TAP metric handling without wiring policy into the Windows client yet. Default-route disabling is captured in snapshots for diagnostics and future decisions, but this slice deliberately changes only `UseAutomaticMetric` and `Metric`. 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 Refs: https://learn.microsoft.com/en-us/windows-hardware/drivers/network/initializeipinterfaceentry Refs: https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-setipinterfaceentry
This commit is contained in:
@@ -44,6 +44,65 @@ impl NetworkInterfaceIdentity {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum IpInterfaceFamily {
|
||||
Ipv4,
|
||||
Ipv6,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InterfaceMetricSnapshot {
|
||||
identity: NetworkInterfaceIdentity,
|
||||
family: IpInterfaceFamily,
|
||||
automatic_metric: bool,
|
||||
metric: u32,
|
||||
disable_default_routes: bool,
|
||||
}
|
||||
|
||||
impl InterfaceMetricSnapshot {
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
const fn new(
|
||||
identity: NetworkInterfaceIdentity,
|
||||
family: IpInterfaceFamily,
|
||||
automatic_metric: bool,
|
||||
metric: u32,
|
||||
disable_default_routes: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
identity,
|
||||
family,
|
||||
automatic_metric,
|
||||
metric,
|
||||
disable_default_routes,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn identity(self) -> NetworkInterfaceIdentity {
|
||||
self.identity
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn family(self) -> IpInterfaceFamily {
|
||||
self.family
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn automatic_metric(self) -> bool {
|
||||
self.automatic_metric
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn metric(self) -> u32 {
|
||||
self.metric
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn disable_default_routes(self) -> bool {
|
||||
self.disable_default_routes
|
||||
}
|
||||
}
|
||||
|
||||
impl RouteSnapshot {
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -115,6 +174,8 @@ mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use windows::{PinnedRelayRoute, best_route_to, interface_identity_from_guid, pin_relay_route};
|
||||
#[cfg(windows)]
|
||||
pub use windows::{ScopedInterfaceMetric, interface_metric, set_scoped_interface_metric};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn best_route_to(_destination: IpAddr) -> Result<RouteSnapshot> {
|
||||
@@ -137,6 +198,29 @@ pub fn interface_identity_from_guid(_interface_guid: &str) -> Result<NetworkInte
|
||||
bail!("Windows interface identity lookup is only available on Windows");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[derive(Debug)]
|
||||
pub struct ScopedInterfaceMetric {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn interface_metric(
|
||||
_identity: NetworkInterfaceIdentity,
|
||||
_family: IpInterfaceFamily,
|
||||
) -> Result<InterfaceMetricSnapshot> {
|
||||
bail!("Windows interface metric lookup is only available on Windows");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn set_scoped_interface_metric(
|
||||
_identity: NetworkInterfaceIdentity,
|
||||
_family: IpInterfaceFamily,
|
||||
_metric: u32,
|
||||
) -> Result<ScopedInterfaceMetric> {
|
||||
bail!("Windows interface metric updates are only available on Windows");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -172,6 +256,19 @@ mod tests {
|
||||
assert_eq!(identity.luid(), 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exposes_interface_metric_snapshot_fields() {
|
||||
let identity = NetworkInterfaceIdentity::new(12, 34);
|
||||
let snapshot =
|
||||
InterfaceMetricSnapshot::new(identity, IpInterfaceFamily::Ipv4, true, 25, false);
|
||||
|
||||
assert_eq!(snapshot.identity(), identity);
|
||||
assert_eq!(snapshot.family(), IpInterfaceFamily::Ipv4);
|
||||
assert!(snapshot.automatic_metric());
|
||||
assert_eq!(snapshot.metric(), 25);
|
||||
assert!(!snapshot.disable_default_routes());
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn rejects_route_inspection_on_non_windows() {
|
||||
@@ -201,6 +298,15 @@ mod tests {
|
||||
assert!(interface_identity_from_guid("{00112233-4455-6677-8899-AABBCCDDEEFF}").is_err());
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn rejects_interface_metric_operations_on_non_windows() {
|
||||
let identity = NetworkInterfaceIdentity::new(12, 34);
|
||||
|
||||
assert!(interface_metric(identity, IpInterfaceFamily::Ipv4).is_err());
|
||||
assert!(set_scoped_interface_metric(identity, IpInterfaceFamily::Ipv4, 500).is_err());
|
||||
}
|
||||
|
||||
fn ip(value: &str) -> IpAddr {
|
||||
value.parse().unwrap()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user