diff --git a/README.md b/README.md index 2c27a31..7a5ff7e 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ btest -c 192.168.88.1 -r -a admin -p password ## Known Limitations +- **IPv6 support is experimental** (`--listen6`). TCP over IPv6 works fully. UDP over IPv6 has issues on macOS due to kernel ENOBUFS limitations with `send_to()`. On Linux, IPv6 UDP works fine. IPv6 is disabled by default. - **Multi-connection UDP** is supported. MikroTik's multi-connection mode sends from multiple source ports which are all accepted by the server. ## Testing diff --git a/src/main.rs b/src/main.rs index 2633640..9237901 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,9 +54,9 @@ struct Cli { #[arg(long = "listen", default_value = "0.0.0.0")] listen_addr: String, - /// Listen address for IPv6 (default: ::, use "none" to disable) - #[arg(long = "listen6", default_value = "::")] - listen6_addr: String, + /// Enable IPv6 listener (experimental — TCP works, UDP has issues on macOS) + #[arg(long = "listen6", default_missing_value = "::", num_args = 0..=1)] + listen6_addr: Option, /// Authentication username #[arg(short = 'a', long = "authuser")] @@ -110,7 +110,7 @@ async fn main() -> anyhow::Result<()> { if cli.server { // Server mode let v4 = if cli.listen_addr.eq_ignore_ascii_case("none") { None } else { Some(cli.listen_addr) }; - let v6 = if cli.listen6_addr.eq_ignore_ascii_case("none") { None } else { Some(cli.listen6_addr) }; + let v6 = cli.listen6_addr; // None unless --listen6 is passed tracing::info!("Starting btest server on port {}", cli.port); server::run_server(cli.port, cli.auth_user, cli.auth_pass, cli.ecsrp5, v4, v6).await?; } else if let Some(host) = cli.client { diff --git a/src/server.rs b/src/server.rs index 1641267..be930b8 100644 --- a/src/server.rs +++ b/src/server.rs @@ -198,7 +198,7 @@ async fn handle_client( ); // Build auth OK response - include session token for TCP multi-connection - let is_tcp_multi = !cmd.is_udp() && cmd.tcp_conn_count > 1; + let is_tcp_multi = !cmd.is_udp() && cmd.tcp_conn_count > 0; let session_token: u16 = if is_tcp_multi { rand::random::() | 0x0101 // ensure both bytes non-zero } else { @@ -669,7 +669,7 @@ async fn run_udp_test_server( // On IPv6, send a probe packet to trigger NDP neighbor resolution before blasting. // macOS returns ENOBUFS on send_to() until the neighbor cache is populated. if peer.is_ipv6() { - let _ = udp.send_to(&[], client_udp_addr).await; + let _ = udp.send_to(&[0u8; 1], client_udp_addr).await; tokio::time::sleep(Duration::from_millis(200)).await; tracing::debug!("IPv6 NDP probe sent to {}", client_udp_addr); } @@ -682,7 +682,7 @@ async fn run_udp_test_server( // from multiple source ports). For single-connection, always connect() — // this is critical for IPv6 where send_to() hits ENOBUFS but send() works. // recv_from() works fine on connected sockets for single source. - let use_unconnected = cmd.tcp_conn_count > 1; + let use_unconnected = cmd.tcp_conn_count > 0; if !use_unconnected { udp.connect(client_udp_addr).await?; } @@ -949,11 +949,17 @@ async fn udp_status_loop( let tx_bytes = state.tx_bytes.swap(0, Ordering::Relaxed); let lost = state.rx_lost_packets.swap(0, Ordering::Relaxed); - // Always report rx_bytes — how much we received from the client. - // MikroTik client tracks its own Rx independently by counting UDP arrivals. + // Report bytes relevant to the active direction. + // When TX-only: report tx_bytes so client knows data is flowing. + // When RX or BOTH: report rx_bytes (how much we received from client). + let report_bytes = if cmd.server_tx() && !cmd.server_rx() { + tx_bytes + } else { + rx_bytes + }; let status = StatusMessage { seq, - bytes_received: rx_bytes as u32, + bytes_received: report_bytes as u32, }; let serialized = status.serialize(); tracing::debug!(