diff --git a/crates/wzp-client/src/cli.rs b/crates/wzp-client/src/cli.rs index c983029..7928e9c 100644 --- a/crates/wzp-client/src/cli.rs +++ b/crates/wzp-client/src/cli.rs @@ -47,6 +47,7 @@ struct CliArgs { room: Option, token: Option, _metrics_file: Option, + version_check: bool, } impl CliArgs { @@ -88,6 +89,7 @@ fn parse_args() -> CliArgs { let mut room = None; let mut token = None; let mut metrics_file = None; + let mut version_check = false; let mut relay_str = None; let mut i = 1; @@ -169,6 +171,7 @@ fn parse_args() -> CliArgs { ); } "--sweep" => sweep = true, + "--version-check" => { version_check = true; } "--help" | "-h" => { eprintln!("Usage: wzp-client [options] [relay-addr]"); eprintln!(); @@ -221,6 +224,7 @@ fn parse_args() -> CliArgs { room, token, _metrics_file: metrics_file, + version_check, } } @@ -239,6 +243,26 @@ async fn main() -> anyhow::Result<()> { return Ok(()); } + // --version-check: query relay version over QUIC and exit + if cli.version_check { + let client_config = wzp_transport::client_config(); + let bind_addr: SocketAddr = "0.0.0.0:0".parse()?; + let endpoint = wzp_transport::create_endpoint(bind_addr, None)?; + let conn = wzp_transport::connect(&endpoint, cli.relay_addr, "version", client_config).await?; + match conn.accept_uni().await { + Ok(mut recv) => { + let data = recv.read_to_end(256).await.unwrap_or_default(); + let version = String::from_utf8_lossy(&data); + println!("{} {}", cli.relay_addr, version.trim()); + } + Err(e) => { + eprintln!("relay {} does not support version query: {e}", cli.relay_addr); + } + } + endpoint.close(0u32.into(), b"done"); + return Ok(()); + } + let seed = cli.resolve_seed(); info!( diff --git a/crates/wzp-relay/src/main.rs b/crates/wzp-relay/src/main.rs index 89fe7b0..134942f 100644 --- a/crates/wzp-relay/src/main.rs +++ b/crates/wzp-relay/src/main.rs @@ -490,12 +490,22 @@ async fn main() -> anyhow::Result<()> { let transport = Arc::new(wzp_transport::QuinnTransport::new(connection)); // Ping connections: client just measures QUIC connect RTT. - // No handshake, no streams — client closes immediately after connecting. if room_name == "ping" { info!(%addr, "ping connection (RTT probe)"); return; } + // Version query: respond with build hash over a uni stream. + if room_name == "version" { + if let Ok(mut send) = transport.connection().open_uni().await { + let _ = send.write_all(BUILD_GIT_HASH.as_bytes()).await; + let _ = send.finish(); + // Wait for client to read before closing + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + return; + } + // Probe connections use SNI "_probe" to identify themselves. // They skip auth + handshake and just do Ping->Pong + presence gossip. if room_name == "_probe" {