fix(client): clear TAP before resolving relay
The previous startup ordering loaded the virtual MAC before touching TAP, but it also resolved the relay endpoint before clearing stale TAP media state. That left a post-crash TAP adapter able to influence DNS or route selection before the client had pinned the relay path. Split Windows client startup config into a local phase and a resolved runtime phase. The local phase reads the certificate, room, TAP adapter selection, and client identity without performing DNS. Windows startup now writes the TAP NetworkAddress value and marks TAP media disconnected before resolving the relay endpoint or opening the QUIC connection. A regression test uses an intentionally unresolved relay hostname to prove that building the startup config does not resolve DNS. The client still resolves the relay before activation and still validates the driver-reported TAP MAC before bridging. Test Plan: - cargo test -p lanparty-client-win - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - cargo fmt --check - git diff --check - cargo check -p lanparty-client-win --target x86_64-pc-windows-gnu - blocked by missing x86_64-w64-mingw32-gcc for ring on this host Refs: PLAN.md Windows routing / metric handling
This commit is contained in:
@@ -90,6 +90,17 @@ struct ClientArgs {
|
||||
max_datagram_size: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ClientStartupConfig {
|
||||
relay: RelayEndpoint,
|
||||
server_name: String,
|
||||
relay_ca_cert: Vec<u8>,
|
||||
room: RoomCode,
|
||||
identity: ClientIdentity,
|
||||
tap_instance_id: Option<String>,
|
||||
max_datagram_size: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ClientRuntimeConfig {
|
||||
session: ClientSessionConfig,
|
||||
@@ -97,7 +108,7 @@ struct ClientRuntimeConfig {
|
||||
}
|
||||
|
||||
impl ClientArgs {
|
||||
fn into_config(self) -> Result<ClientRuntimeConfig> {
|
||||
fn into_startup_config(self) -> Result<ClientStartupConfig> {
|
||||
if self.list_tap_adapters {
|
||||
bail!("--list-tap-adapters exits before building a tunnel config");
|
||||
}
|
||||
@@ -117,16 +128,31 @@ impl ClientArgs {
|
||||
None => ClientIdentityStore::new(self.identity_file)?.load_or_create()?,
|
||||
};
|
||||
|
||||
let relay_addr = relay
|
||||
Ok(ClientStartupConfig {
|
||||
relay,
|
||||
server_name: self.server_name,
|
||||
relay_ca_cert,
|
||||
room,
|
||||
identity,
|
||||
tap_instance_id: self.tap_instance_id,
|
||||
max_datagram_size: self.max_datagram_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientStartupConfig {
|
||||
fn into_runtime_config(self) -> Result<ClientRuntimeConfig> {
|
||||
let relay_addr = self
|
||||
.relay
|
||||
.resolve()
|
||||
.context("failed to resolve relay endpoint")?;
|
||||
|
||||
let session = ClientSessionConfig::new(
|
||||
relay_addr,
|
||||
self.server_name,
|
||||
relay_ca_cert,
|
||||
room,
|
||||
identity.virtual_mac(),
|
||||
self.relay_ca_cert,
|
||||
self.room,
|
||||
self.identity.virtual_mac(),
|
||||
self.max_datagram_size,
|
||||
)?;
|
||||
|
||||
@@ -145,12 +171,13 @@ async fn main() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let config = args.into_config()?;
|
||||
let startup_config = args.into_startup_config()?;
|
||||
#[cfg(windows)]
|
||||
prepare_tap_before_relay_connect(
|
||||
config.tap_instance_id.as_deref(),
|
||||
config.session.virtual_mac(),
|
||||
startup_config.tap_instance_id.as_deref(),
|
||||
startup_config.identity.virtual_mac(),
|
||||
)?;
|
||||
let config = startup_config.into_runtime_config()?;
|
||||
println!(
|
||||
"lanparty-client-win connecting virtual MAC {} to relay {} room {}",
|
||||
config.session.virtual_mac(),
|
||||
@@ -1053,6 +1080,11 @@ fn open_tap_adapter(_session: &ClientSession) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
fs,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use lanparty_ctrl::{DisconnectReason, PeerInfo};
|
||||
use lanparty_net::DEFAULT_RELAY_PORT;
|
||||
@@ -1181,6 +1213,38 @@ mod tests {
|
||||
assert_eq!(relay.port(), DEFAULT_RELAY_PORT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_startup_config_without_resolving_relay() {
|
||||
let cert_path = unique_temp_file("lanparty-client-cert");
|
||||
fs::write(&cert_path, [1_u8, 2, 3]).unwrap();
|
||||
let cert_path_string = cert_path.display().to_string();
|
||||
let args = ClientArgs::parse_from([
|
||||
"lanparty-client-win",
|
||||
"--relay",
|
||||
"unresolved.invalid",
|
||||
"--relay-ca-cert",
|
||||
&cert_path_string,
|
||||
"--room",
|
||||
"ROOM1",
|
||||
"--virtual-mac",
|
||||
"02:00:00:00:00:09",
|
||||
"--tap-instance-id",
|
||||
"{01234567-89AB-CDEF-0123-456789ABCDEF}",
|
||||
]);
|
||||
|
||||
let startup = args.into_startup_config().unwrap();
|
||||
fs::remove_file(cert_path).unwrap();
|
||||
|
||||
assert_eq!(startup.relay.host(), "unresolved.invalid");
|
||||
assert_eq!(startup.relay_ca_cert, vec![1, 2, 3]);
|
||||
assert_eq!(startup.room.as_str(), "ROOM1");
|
||||
assert_eq!(startup.identity.virtual_mac(), mac(9));
|
||||
assert_eq!(
|
||||
startup.tap_instance_id.as_deref(),
|
||||
Some("{01234567-89AB-CDEF-0123-456789ABCDEF}")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_tap_adapter_listing_without_tunnel_args() {
|
||||
let args = ClientArgs::parse_from(["lanparty-client-win", "--list-tap-adapters"]);
|
||||
@@ -1323,4 +1387,13 @@ mod tests {
|
||||
fn tap_info(instance_id: &str) -> TapAdapterInfo {
|
||||
TapAdapterInfo::new(instance_id, "tap0901").unwrap()
|
||||
}
|
||||
|
||||
fn unique_temp_file(prefix: &str) -> std::path::PathBuf {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
|
||||
std::env::temp_dir().join(format!("{prefix}-{}-{nanos}", std::process::id()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user