feat(client): bridge TAP frames in Windows client
Wire the Windows client run loop to move Ethernet frames between the relay session and the opened TAP-Windows6 adapter. TAP reads use a named OS thread because the current adapter API performs blocking synchronous reads; relay to TAP writes use short `spawn_blocking` jobs so the async receive loop does not block the Tokio worker. The main function now always closes the relay session after the client run loop finishes, including TAP pump errors. Ctrl-C still stops the client. The TAP reader thread is intentionally detached in this first pump slice because a blocking TAP read cannot yet be cancelled cleanly from the async side; process exit tears it down after shutdown. This still leaves route pinning and automatic TAP MAC/MTU configuration for later. The README now reflects that frame pumping is wired while those Windows network-configuration pieces remain outstanding. Verification note: I attempted to check `lanparty-client-win` for `x86_64-pc-windows-msvc`, but this Linux host still lacks the Windows C headers needed by `ring`; the build stops at `assert.h` before the binary crate can be typechecked for Windows. Test Plan: - cargo fmt --check - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - CC_x86_64_pc_windows_msvc=clang-cl AR_x86_64_pc_windows_msvc=llvm-lib \ CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=lld-link cargo clippy \ -p lanparty-client-tap --target x86_64-pc-windows-msvc -- -D warnings - git diff --check Refs: PLAN.md
This commit is contained in:
@@ -121,7 +121,8 @@ cargo run -p lanparty-client-win -- \
|
|||||||
The Windows client binary currently connects to the relay as `role = client`
|
The Windows client binary currently connects to the relay as `role = client`
|
||||||
with a generated locally administered virtual MAC persisted in
|
with a generated locally administered virtual MAC persisted in
|
||||||
`lanparty-client-identity.json`, completes the control-stream hello/welcome
|
`lanparty-client-identity.json`, completes the control-stream hello/welcome
|
||||||
handshake, and then waits for shutdown. `--virtual-mac` can still override the
|
handshake, and then bridges Ethernet frames between the relay and the first
|
||||||
stored identity for manual testing. On Windows it opens the first TAP-Windows6
|
TAP-Windows6 adapter until shutdown. `--virtual-mac` can still override the
|
||||||
adapter, marks media connected, and reports the driver MAC/MTU before waiting
|
stored identity for manual testing. On Windows it marks the TAP media
|
||||||
for shutdown. TAP frame pumping and route pinning are not wired yet.
|
connected and reports the driver MAC/MTU before forwarding frames. Route
|
||||||
|
pinning and automatic TAP MAC/MTU configuration are not wired yet.
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
use std::{fs, net::SocketAddr, path::PathBuf};
|
use std::{fs, net::SocketAddr, path::PathBuf};
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, mpsc},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use lanparty_client_core::ClientRelayIo;
|
||||||
use lanparty_client_core::{
|
use lanparty_client_core::{
|
||||||
ClientIdentity, ClientIdentityStore, ClientSession, ClientSessionConfig, connect_client,
|
ClientIdentity, ClientIdentityStore, ClientSession, ClientSessionConfig, connect_client,
|
||||||
};
|
};
|
||||||
|
#[cfg(windows)]
|
||||||
|
use lanparty_client_tap::TapAdapter;
|
||||||
use lanparty_ctrl::RoomCode;
|
use lanparty_ctrl::RoomCode;
|
||||||
use lanparty_proto::MacAddr;
|
use lanparty_proto::MacAddr;
|
||||||
|
|
||||||
@@ -89,18 +98,31 @@ async fn main() -> Result<()> {
|
|||||||
session.welcome().room_id(),
|
session.welcome().room_id(),
|
||||||
session.welcome().effective_tap_mtu()
|
session.welcome().effective_tap_mtu()
|
||||||
);
|
);
|
||||||
|
let run_result = run_client(&session).await;
|
||||||
|
session.shutdown("client shutting down").await;
|
||||||
|
|
||||||
|
run_result
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let _tap = open_tap_adapter(&session)?;
|
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))]
|
#[cfg(not(windows))]
|
||||||
open_tap_adapter(&session);
|
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");
|
println!("TAP frame pump and route pinning are not wired yet; press Ctrl-C to stop");
|
||||||
|
|
||||||
tokio::signal::ctrl_c()
|
tokio::signal::ctrl_c()
|
||||||
.await
|
.await
|
||||||
.context("failed to wait for Ctrl-C")?;
|
.context("failed to wait for Ctrl-C")
|
||||||
session.shutdown("client shutting down").await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -138,6 +160,77 @@ fn open_tap_adapter(session: &ClientSession) -> Result<lanparty_client_tap::TapA
|
|||||||
Ok(tap)
|
Ok(tap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
async fn run_tap_frame_pump(relay_io: ClientRelayIo, tap: TapAdapter) -> 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<TapAdapter>,
|
||||||
|
relay_io: ClientRelayIo,
|
||||||
|
tap_error_tx: mpsc::Sender<anyhow::Error>,
|
||||||
|
) -> 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))]
|
#[cfg(not(windows))]
|
||||||
fn open_tap_adapter(_session: &ClientSession) {
|
fn open_tap_adapter(_session: &ClientSession) {
|
||||||
println!("Windows TAP adapter opening is compiled only on Windows");
|
println!("Windows TAP adapter opening is compiled only on Windows");
|
||||||
|
|||||||
Reference in New Issue
Block a user