feat(client): add relay CLI for Windows binary

lanparty-client-win now has a real command-line surface for the relay-facing
client session. It accepts the relay address, expected TLS server name, pinned
DER relay certificate, room code, virtual TAP MAC, and advertised datagram
budget, then connects through lanparty-client-core as role = client.

The binary reports the assigned peer id, room id, and effective TAP MTU from the
welcome response, then waits for Ctrl-C. TAP adapter binding and Windows route
pinning remain future slices, but the executable now exercises the real relay
control-plane path instead of the starter placeholder.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings

Refs: PLAN.md Windows client relay connection
This commit is contained in:
2026-05-21 18:21:00 +02:00
parent 914bd48346
commit 93f0a17f79
4 changed files with 114 additions and 2 deletions
+6
View File
@@ -4,3 +4,9 @@ version.workspace = true
edition.workspace = true
[dependencies]
anyhow.workspace = true
clap.workspace = true
lanparty-client-core = { path = "../lanparty-client-core" }
lanparty-ctrl = { path = "../lanparty-ctrl" }
lanparty-proto = { path = "../lanparty-proto" }
tokio.workspace = true
+84 -2
View File
@@ -1,3 +1,85 @@
fn main() {
println!("Hello, world!");
use std::{fs, net::SocketAddr, path::PathBuf};
use anyhow::{Context, Result};
use clap::Parser;
use lanparty_client_core::{ClientSessionConfig, connect_client};
use lanparty_ctrl::RoomCode;
use lanparty_proto::MacAddr;
#[derive(Debug, Parser)]
#[command(
name = "lanparty-client-win",
about = "Windows TAP client for the LAN party L2 tunnel"
)]
struct ClientArgs {
/// Relay UDP socket address, for example 203.0.113.10:443.
#[arg(long)]
relay: SocketAddr,
/// 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,
/// Room code to join as a remote client.
#[arg(long)]
room: RoomCode,
/// Locally administered unicast MAC address assigned to the TAP adapter.
#[arg(long)]
virtual_mac: MacAddr,
/// Client's advertised QUIC datagram budget before relay clamping.
#[arg(long, default_value_t = 1400)]
max_datagram_size: u16,
}
impl ClientArgs {
fn into_config(self) -> Result<ClientSessionConfig> {
let relay_ca_cert = fs::read(&self.relay_ca_cert).with_context(|| {
format!(
"failed to read relay CA certificate {}",
self.relay_ca_cert.display()
)
})?;
ClientSessionConfig::new(
self.relay,
self.server_name,
relay_ca_cert,
self.room,
self.virtual_mac,
self.max_datagram_size,
)
}
}
#[tokio::main]
async fn main() -> Result<()> {
let config = ClientArgs::parse().into_config()?;
println!(
"lanparty-client-win connecting virtual MAC {} to relay {} room {}",
config.virtual_mac(),
config.relay_addr(),
config.room()
);
let session = connect_client(config).await?;
println!(
"lanparty-client-win connected as peer {} in room id {} with TAP MTU {}",
session.welcome().peer_id(),
session.welcome().room_id(),
session.welcome().effective_tap_mtu()
);
println!("Windows TAP binding and route pinning are not wired yet; press Ctrl-C to stop");
tokio::signal::ctrl_c()
.await
.context("failed to wait for Ctrl-C")?;
session.shutdown("client shutting down").await;
Ok(())
}