feat(client): add Windows route snapshot helper
Add `lanparty-client-route` as the Win32 boundary for route-table work. The first API is intentionally read-only: `best_route_to` wraps `GetBestRoute2` and returns the selected source address, next hop, route prefix, interface index/LUID, and route metric for a relay destination IP. This keeps the route-protection work separate from the QUIC client binary, so we can typecheck the Windows IP Helper calls on this Linux host without pulling in the `ring` build path that currently blocks full Windows binary checks. Actual route pinning and metric mutation remain later slices. 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 -- -D warnings - git diff --check Refs: PLAN.md
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
//! Windows route-table inspection for protecting the relay path.
|
||||
//!
|
||||
//! The client binary uses this crate to keep Win32 route/metric calls out of
|
||||
//! the relay session code. This crate starts with read-only route snapshots;
|
||||
//! later route pinning can build on the same typed boundary.
|
||||
|
||||
use std::net::IpAddr;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RouteSnapshot {
|
||||
destination: IpAddr,
|
||||
source: IpAddr,
|
||||
next_hop: Option<IpAddr>,
|
||||
route_prefix: IpAddr,
|
||||
route_prefix_len: u8,
|
||||
interface_index: u32,
|
||||
interface_luid: u64,
|
||||
metric: u32,
|
||||
}
|
||||
|
||||
impl RouteSnapshot {
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
const fn new(
|
||||
destination: IpAddr,
|
||||
source: IpAddr,
|
||||
next_hop: Option<IpAddr>,
|
||||
route_prefix: IpAddr,
|
||||
route_prefix_len: u8,
|
||||
interface_index: u32,
|
||||
interface_luid: u64,
|
||||
metric: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
destination,
|
||||
source,
|
||||
next_hop,
|
||||
route_prefix,
|
||||
route_prefix_len,
|
||||
interface_index,
|
||||
interface_luid,
|
||||
metric,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn destination(&self) -> IpAddr {
|
||||
self.destination
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn source(&self) -> IpAddr {
|
||||
self.source
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn next_hop(&self) -> Option<IpAddr> {
|
||||
self.next_hop
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn route_prefix(&self) -> IpAddr {
|
||||
self.route_prefix
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn route_prefix_len(&self) -> u8 {
|
||||
self.route_prefix_len
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn interface_index(&self) -> u32 {
|
||||
self.interface_index
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn interface_luid(&self) -> u64 {
|
||||
self.interface_luid
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn metric(&self) -> u32 {
|
||||
self.metric
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use windows::best_route_to;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn best_route_to(_destination: IpAddr) -> Result<RouteSnapshot> {
|
||||
bail!("Windows route inspection is only available on Windows");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn exposes_route_snapshot_fields() {
|
||||
let snapshot = RouteSnapshot::new(
|
||||
ip("203.0.113.10"),
|
||||
ip("192.0.2.44"),
|
||||
Some(ip("192.0.2.1")),
|
||||
ip("0.0.0.0"),
|
||||
0,
|
||||
12,
|
||||
34,
|
||||
25,
|
||||
);
|
||||
|
||||
assert_eq!(snapshot.destination(), ip("203.0.113.10"));
|
||||
assert_eq!(snapshot.source(), ip("192.0.2.44"));
|
||||
assert_eq!(snapshot.next_hop(), Some(ip("192.0.2.1")));
|
||||
assert_eq!(snapshot.route_prefix(), ip("0.0.0.0"));
|
||||
assert_eq!(snapshot.route_prefix_len(), 0);
|
||||
assert_eq!(snapshot.interface_index(), 12);
|
||||
assert_eq!(snapshot.interface_luid(), 34);
|
||||
assert_eq!(snapshot.metric(), 25);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn rejects_route_inspection_on_non_windows() {
|
||||
assert!(best_route_to(ip("203.0.113.10")).is_err());
|
||||
}
|
||||
|
||||
fn ip(value: &str) -> IpAddr {
|
||||
value.parse().unwrap()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user