diff --git a/Cargo.lock b/Cargo.lock index ba8029a..8eea590 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,6 +446,14 @@ dependencies = [ [[package]] name = "lanparty-client-win" version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "lanparty-client-core", + "lanparty-ctrl", + "lanparty-proto", + "tokio", +] [[package]] name = "lanparty-ctrl" diff --git a/README.md b/README.md index 385d3fa..0b79169 100644 --- a/README.md +++ b/README.md @@ -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 interface, and bridges Ethernet frames between the relay and wired LAN until 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. diff --git a/crates/lanparty-client-win/Cargo.toml b/crates/lanparty-client-win/Cargo.toml index 9371c81..96bba40 100644 --- a/crates/lanparty-client-win/Cargo.toml +++ b/crates/lanparty-client-win/Cargo.toml @@ -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 diff --git a/crates/lanparty-client-win/src/main.rs b/crates/lanparty-client-win/src/main.rs index e7a11a9..544c51f 100644 --- a/crates/lanparty-client-win/src/main.rs +++ b/crates/lanparty-client-win/src/main.rs @@ -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 { + 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(()) }