feat(client): list TAP adapters before connecting
Add a --list-tap-adapters mode to the Windows client. The command prints the TAP-Windows6 adapter instance ids and exits without requiring relay, room, or certificate arguments. This makes the manual MVP test smoother on machines with multiple TAP adapters: the operator can ask the binary for the exact ids, then rerun with --tap-instance-id instead of relying on a separate PowerShell query or waiting for the ambiguous-adapter startup error. Test Plan: - cargo fmt --check - cargo test -p lanparty-client-win - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md Windows client TAP adapter responsibility
This commit is contained in:
@@ -227,7 +227,8 @@ reinstalled before it reloads the configured `NetworkAddress`.
|
||||
If exactly one TAP-Windows6 adapter is installed, the client opens it
|
||||
automatically. If multiple TAP-Windows6 adapters are installed, startup fails
|
||||
until `--tap-instance-id` selects the intended adapter by NetCfgInstanceId /
|
||||
InterfaceGuid.
|
||||
InterfaceGuid. `--list-tap-adapters` prints the TAP adapter ids and exits
|
||||
without connecting.
|
||||
It prints and reports client diagnostics snapshots with relay reachability,
|
||||
LAN-gateway presence, route-pinning, QUIC datagram budget, relay RTT, TAP
|
||||
status/IP, broadcast frame flow, frame/datagram counters, and drops. The
|
||||
|
||||
+1
-2
@@ -104,8 +104,7 @@ If the Windows machine has multiple TAP-Windows6 adapters, select the intended
|
||||
one explicitly:
|
||||
|
||||
```powershell
|
||||
Get-NetAdapter | Where-Object InterfaceDescription -Like "*TAP*" |
|
||||
Select-Object Name, InterfaceGuid, InterfaceDescription
|
||||
.\target\release\lanparty-client-win.exe --list-tap-adapters
|
||||
|
||||
.\target\release\lanparty-client-win.exe `
|
||||
--relay relay.example.net:8443 `
|
||||
|
||||
@@ -20,7 +20,6 @@ use lanparty_client_route::{
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use lanparty_client_tap::TapAdapter;
|
||||
#[cfg(any(windows, test))]
|
||||
use lanparty_client_tap::TapAdapterInfo;
|
||||
use lanparty_ctrl::{ControlMessage, PeerInfo, Role, RoomCode};
|
||||
use lanparty_net::RelayEndpoint;
|
||||
@@ -42,21 +41,33 @@ const CLIENT_DIAGNOSTICS_INTERVAL: Duration = Duration::from_secs(10);
|
||||
about = "Windows TAP client for the LAN party L2 tunnel"
|
||||
)]
|
||||
struct ClientArgs {
|
||||
/// List TAP-Windows6 adapter ids and exit without connecting.
|
||||
#[arg(long)]
|
||||
list_tap_adapters: bool,
|
||||
|
||||
/// Relay DNS name or UDP socket address; bare hosts default to UDP/443.
|
||||
#[arg(long, value_name = "HOST[:PORT]")]
|
||||
relay: RelayEndpoint,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "HOST[:PORT]",
|
||||
required_unless_present = "list_tap_adapters"
|
||||
)]
|
||||
relay: Option<RelayEndpoint>,
|
||||
|
||||
/// TLS server name expected in the relay certificate.
|
||||
#[arg(long, default_value = "lanparty-relay.local")]
|
||||
server_name: String,
|
||||
|
||||
/// DER-encoded relay CA/certificate to trust.
|
||||
#[arg(long, value_name = "PATH")]
|
||||
relay_ca_cert: PathBuf,
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PATH",
|
||||
required_unless_present = "list_tap_adapters"
|
||||
)]
|
||||
relay_ca_cert: Option<PathBuf>,
|
||||
|
||||
/// Room code to join as a remote client.
|
||||
#[arg(long)]
|
||||
room: RoomCode,
|
||||
#[arg(long, required_unless_present = "list_tap_adapters")]
|
||||
room: Option<RoomCode>,
|
||||
|
||||
/// Identity JSON file used to persist the generated virtual MAC.
|
||||
#[arg(
|
||||
@@ -87,10 +98,17 @@ struct ClientRuntimeConfig {
|
||||
|
||||
impl ClientArgs {
|
||||
fn into_config(self) -> Result<ClientRuntimeConfig> {
|
||||
let relay_ca_cert = fs::read(&self.relay_ca_cert).with_context(|| {
|
||||
if self.list_tap_adapters {
|
||||
bail!("--list-tap-adapters exits before building a tunnel config");
|
||||
}
|
||||
|
||||
let relay = self.relay.context("--relay is required")?;
|
||||
let relay_ca_cert_path = self.relay_ca_cert.context("--relay-ca-cert is required")?;
|
||||
let room = self.room.context("--room is required")?;
|
||||
let relay_ca_cert = fs::read(&relay_ca_cert_path).with_context(|| {
|
||||
format!(
|
||||
"failed to read relay CA certificate {}",
|
||||
self.relay_ca_cert.display()
|
||||
relay_ca_cert_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -99,16 +117,15 @@ impl ClientArgs {
|
||||
None => ClientIdentityStore::new(self.identity_file)?.load_or_create()?,
|
||||
};
|
||||
|
||||
let relay_addr = self
|
||||
.relay
|
||||
let relay_addr = relay
|
||||
.resolve()
|
||||
.with_context(|| format!("failed to resolve relay endpoint {}", self.relay))?;
|
||||
.context("failed to resolve relay endpoint")?;
|
||||
|
||||
let session = ClientSessionConfig::new(
|
||||
relay_addr,
|
||||
self.server_name,
|
||||
relay_ca_cert,
|
||||
self.room,
|
||||
room,
|
||||
identity.virtual_mac(),
|
||||
self.max_datagram_size,
|
||||
)?;
|
||||
@@ -122,7 +139,13 @@ impl ClientArgs {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let config = ClientArgs::parse().into_config()?;
|
||||
let args = ClientArgs::parse();
|
||||
if args.list_tap_adapters {
|
||||
print_available_tap_adapters()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let config = args.into_config()?;
|
||||
println!(
|
||||
"lanparty-client-win connecting virtual MAC {} to relay {} room {}",
|
||||
config.session.virtual_mac(),
|
||||
@@ -165,6 +188,15 @@ async fn main() -> Result<()> {
|
||||
run_result
|
||||
}
|
||||
|
||||
fn print_available_tap_adapters() -> Result<()> {
|
||||
let adapters = lanparty_client_tap::available_adapters()?;
|
||||
for line in format_tap_adapter_list(&adapters) {
|
||||
println!("{line}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn run_client(
|
||||
session: &ClientSession,
|
||||
@@ -488,6 +520,29 @@ fn available_tap_adapter_label(adapters: &[TapAdapterInfo]) -> String {
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
fn format_tap_adapter_list(adapters: &[TapAdapterInfo]) -> Vec<String> {
|
||||
if adapters.is_empty() {
|
||||
return vec!["No TAP-Windows6 adapters found".to_owned()];
|
||||
}
|
||||
|
||||
let mut lines = Vec::with_capacity(adapters.len() + 1);
|
||||
lines.push("TAP-Windows6 adapters:".to_owned());
|
||||
lines.extend(adapters.iter().map(|adapter| {
|
||||
let driver_key = adapter
|
||||
.driver_key_name()
|
||||
.map(|driver_key| format!(" driver-key={driver_key}"))
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
" {} component={}{}",
|
||||
adapter.instance_id(),
|
||||
adapter.component_id(),
|
||||
driver_key
|
||||
)
|
||||
}));
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
fn client_diagnostics_snapshot(
|
||||
session: &ClientSession,
|
||||
route_pinned: bool,
|
||||
@@ -1059,8 +1114,19 @@ mod tests {
|
||||
"ROOM1",
|
||||
]);
|
||||
|
||||
assert_eq!(args.relay.host(), "relay.example.test");
|
||||
assert_eq!(args.relay.port(), DEFAULT_RELAY_PORT);
|
||||
let relay = args.relay.unwrap();
|
||||
assert_eq!(relay.host(), "relay.example.test");
|
||||
assert_eq!(relay.port(), DEFAULT_RELAY_PORT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_tap_adapter_listing_without_tunnel_args() {
|
||||
let args = ClientArgs::parse_from(["lanparty-client-win", "--list-tap-adapters"]);
|
||||
|
||||
assert!(args.list_tap_adapters);
|
||||
assert!(args.relay.is_none());
|
||||
assert!(args.relay_ca_cert.is_none());
|
||||
assert!(args.room.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1129,6 +1195,21 @@ mod tests {
|
||||
assert!(error.contains("tap-two"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_tap_adapter_listing() {
|
||||
assert_eq!(
|
||||
format_tap_adapter_list(&[]),
|
||||
vec!["No TAP-Windows6 adapters found".to_owned()]
|
||||
);
|
||||
assert_eq!(
|
||||
format_tap_adapter_list(&[tap_info("tap-one")]),
|
||||
vec![
|
||||
"TAP-Windows6 adapters:".to_owned(),
|
||||
" tap-one component=tap0901".to_owned(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_relay_lifecycle_events() {
|
||||
let gateway = ControlMessage::PeerJoined(PeerInfo::new(1, Role::Gateway, None).unwrap());
|
||||
|
||||
Reference in New Issue
Block a user