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
Generated
+8
View File
@@ -446,6 +446,14 @@ dependencies = [
[[package]] [[package]]
name = "lanparty-client-win" name = "lanparty-client-win"
version = "0.1.0" version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"lanparty-client-core",
"lanparty-ctrl",
"lanparty-proto",
"tokio",
]
[[package]] [[package]]
name = "lanparty-ctrl" name = "lanparty-ctrl"
+16
View File
@@ -93,3 +93,19 @@ The gateway connects to the relay as `role = gateway`, completes the
control-stream hello/welcome handshake, opens an AF_PACKET socket on the LAN control-stream hello/welcome handshake, opens an AF_PACKET socket on the LAN
interface, and bridges Ethernet frames between the relay and wired LAN until interface, and bridges Ethernet frames between the relay and wired LAN until
shutdown. shutdown.
## Windows Client
```bash
cargo run -p lanparty-client-win -- \
--relay 203.0.113.10:443 \
--server-name lanparty-relay.local \
--relay-ca-cert relay-cert.der \
--room ROOM1 \
--virtual-mac 02:00:00:00:00:51
```
The Windows client binary currently connects to the relay as `role = client`
with the configured virtual MAC, completes the control-stream hello/welcome
handshake, and then waits for shutdown. TAP adapter binding and route pinning
are not wired yet.
+6
View File
@@ -4,3 +4,9 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [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() { use std::{fs, net::SocketAddr, path::PathBuf};
println!("Hello, world!");
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(())
} }