diff --git a/README.md b/README.md index 7c84464..05ff834 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,8 @@ cargo run -p lanparty-client-win -- \ The Windows client binary currently connects to the relay as `role = client` with a generated locally administered virtual MAC persisted in `lanparty-client-identity.json`, completes the control-stream hello/welcome -handshake, and then waits for shutdown. `--virtual-mac` can still override the -stored identity for manual testing. On Windows it opens the first TAP-Windows6 -adapter, marks media connected, and reports the driver MAC/MTU before waiting -for shutdown. TAP frame pumping and route pinning are not wired yet. +handshake, and then bridges Ethernet frames between the relay and the first +TAP-Windows6 adapter until shutdown. `--virtual-mac` can still override the +stored identity for manual testing. On Windows it marks the TAP media +connected and reports the driver MAC/MTU before forwarding frames. Route +pinning and automatic TAP MAC/MTU configuration are not wired yet. diff --git a/crates/lanparty-client-win/src/main.rs b/crates/lanparty-client-win/src/main.rs index 923138e..a8b389a 100644 --- a/crates/lanparty-client-win/src/main.rs +++ b/crates/lanparty-client-win/src/main.rs @@ -1,10 +1,19 @@ use std::{fs, net::SocketAddr, path::PathBuf}; +#[cfg(windows)] +use std::{ + sync::{Arc, mpsc}, + thread, +}; use anyhow::{Context, Result}; use clap::Parser; +#[cfg(windows)] +use lanparty_client_core::ClientRelayIo; use lanparty_client_core::{ ClientIdentity, ClientIdentityStore, ClientSession, ClientSessionConfig, connect_client, }; +#[cfg(windows)] +use lanparty_client_tap::TapAdapter; use lanparty_ctrl::RoomCode; use lanparty_proto::MacAddr; @@ -89,18 +98,31 @@ async fn main() -> Result<()> { session.welcome().room_id(), session.welcome().effective_tap_mtu() ); - #[cfg(windows)] - let _tap = open_tap_adapter(&session)?; - #[cfg(not(windows))] - open_tap_adapter(&session); + let run_result = run_client(&session).await; + session.shutdown("client shutting down").await; + + run_result +} + +#[cfg(windows)] +async fn run_client(session: &ClientSession) -> Result<()> { + let tap = open_tap_adapter(session)?; + println!("bridging TAP frames; route pinning is not wired yet; press Ctrl-C to stop"); + + tokio::select! { + result = run_tap_frame_pump(session.relay_io(), tap) => result, + result = tokio::signal::ctrl_c() => result.context("failed to wait for Ctrl-C"), + } +} + +#[cfg(not(windows))] +async fn run_client(session: &ClientSession) -> Result<()> { + open_tap_adapter(session); println!("TAP frame pump 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(()) + .context("failed to wait for Ctrl-C") } #[cfg(windows)] @@ -138,6 +160,77 @@ fn open_tap_adapter(session: &ClientSession) -> Result Result<()> { + let tap = Arc::new(tap); + let (tap_error_tx, tap_error_rx) = mpsc::channel(); + spawn_tap_reader(Arc::clone(&tap), relay_io.clone(), tap_error_tx)?; + + let tap_reader_error = tokio::task::spawn_blocking(move || tap_error_rx.recv()); + tokio::pin!(tap_reader_error); + + loop { + tokio::select! { + reader_result = &mut tap_reader_error => { + let reader_result = reader_result.context("TAP reader error watcher panicked")?; + let error = reader_result + .context("TAP reader thread stopped without reporting an error")?; + return Err::<(), _>(error).context("TAP reader thread stopped"); + } + relay_frame = relay_io.recv_ethernet() => { + let relay_frame = relay_frame.context("failed to receive relay Ethernet frame")?; + let tap = Arc::clone(&tap); + let payload = relay_frame.payload().to_vec(); + tokio::task::spawn_blocking(move || { + tap.write_ethernet_frame(&payload) + .context("failed to write relay Ethernet frame to TAP") + }) + .await + .context("TAP writer task panicked")??; + } + } + } +} + +#[cfg(windows)] +fn spawn_tap_reader( + tap: Arc, + relay_io: ClientRelayIo, + tap_error_tx: mpsc::Sender, +) -> Result<()> { + thread::Builder::new() + .name("lanparty-tap-reader".to_string()) + .spawn(move || { + let mut buffer = vec![0_u8; lanparty_client_tap::TAP_FRAME_BUFFER_LEN]; + loop { + let result = read_and_relay_tap_frame(&tap, &relay_io, &mut buffer); + if let Err(error) = result { + let _ = tap_error_tx.send(error); + return; + } + } + }) + .context("failed to spawn TAP reader thread")?; + + Ok(()) +} + +#[cfg(windows)] +fn read_and_relay_tap_frame( + tap: &TapAdapter, + relay_io: &ClientRelayIo, + buffer: &mut [u8], +) -> Result<()> { + let len = tap + .read_ethernet_frame(buffer) + .context("failed to read TAP Ethernet frame")?; + relay_io + .send_ethernet(&buffer[..len]) + .context("failed to send TAP Ethernet frame to relay")?; + + Ok(()) +} + #[cfg(not(windows))] fn open_tap_adapter(_session: &ClientSession) { println!("Windows TAP adapter opening is compiled only on Windows");