From c0a76f19428f0bcabaae3ebfc32f81505dcf13a8 Mon Sep 17 00:00:00 2001 From: Aleksandr Bogomiakov Date: Wed, 30 Sep 2026 22:56:08 +0100 Subject: [PATCH] Enable all available protocols by default with tcp-tls priority By default, the agent now enables all available data plane protocols starting with `tcp-tls,wg-quic`, removing the need to manually pass `--protocol` to enable HTTPS-camouflage transport. The `--protocol` option remains available to explicitly restrict or select specific protocols (or reorder their priority). Furthermore, in `crates/tsunagi-tcp-tls`, if default port 443 is unavailable (e.g., due to permission constraints or address in use), the transport now attempts fallback port 8443, and if 8443 is also unavailable, gracefully falls back to an ephemeral port rather than failing agent startup. Key changes: - In `crates/tsunagi-cli/src/main.rs`, update `UpArgs::protocols` default_value to `"tcp-tls,wg-quic"`. - In `crates/tsunagi-cli/src/main.rs`, reorder `PROTOCOLS` registry with `tcp-tls` first, followed by `wg-quic`. - In `crates/tsunagi-tcp-tls/src/transport.rs`, add multi-tier fallback (443 -> 8443 -> ephemeral) for unconstrained port bindings. Validation on Linux: - cargo fmt --all -- --check passed. - cargo clippy --locked --workspace --all-targets -- -D warnings passed. - cargo test --locked --workspace --all-targets passed (349 tests passed). - cargo build --release passed. --- crates/tsunagi-cli/src/main.rs | 14 +++---- crates/tsunagi-tcp-tls/src/transport.rs | 50 +++++++++++++++++++------ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/crates/tsunagi-cli/src/main.rs b/crates/tsunagi-cli/src/main.rs index e1df07d..d606ea0 100644 --- a/crates/tsunagi-cli/src/main.rs +++ b/crates/tsunagi-cli/src/main.rs @@ -424,7 +424,7 @@ struct UpArgs { long = "protocol", value_name = "LIST", value_delimiter = ',', - default_value = "wg-quic", + default_value = "tcp-tls,wg-quic", help_heading = "Transport" )] protocols: Vec, @@ -1175,6 +1175,12 @@ struct ProtocolSpec { /// Every protocol this build has. const PROTOCOLS: &[ProtocolSpec] = &[ + ProtocolSpec { + name: TCP_TLS_PROTOCOL, + version: TCP_TLS_VERSION, + summary: "Noise E2EE carried over TCP TLS 1.3 (port 443) disguised as HTTPS traffic", + options: TcpTlsCodec::OPTIONS, + }, ProtocolSpec { name: tsunagi_wg_quic::WIREGUARD_PROTOCOL, version: tsunagi_wg_quic::ANNOUNCEMENT_VERSION, @@ -1182,12 +1188,6 @@ const PROTOCOLS: &[ProtocolSpec] = &[ crosses NAT and survives where plain WireGuard is blocked", options: WireguardPlugin::OPTIONS, }, - ProtocolSpec { - name: TCP_TLS_PROTOCOL, - version: TCP_TLS_VERSION, - summary: "Noise E2EE carried over TCP TLS 1.3 (port 443) disguised as HTTPS traffic", - options: TcpTlsCodec::OPTIONS, - }, ]; /// One `-o` setting, and the protocol it was aimed at. diff --git a/crates/tsunagi-tcp-tls/src/transport.rs b/crates/tsunagi-tcp-tls/src/transport.rs index 0fa4d68..c9d6fce 100644 --- a/crates/tsunagi-tcp-tls/src/transport.rs +++ b/crates/tsunagi-tcp-tls/src/transport.rs @@ -354,12 +354,12 @@ impl TcpTlsTransport { tracing::info!(port = target_port, "TCP TLS listening"); Some(Arc::new(l)) } - Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => { + Err(err) if requested_port.is_none() => { let exe = std::env::current_exe() .map(|p| p.display().to_string()) .unwrap_or_else(|_| "tsunagi".into()); - if requested_port.is_none() { + if err.kind() == std::io::ErrorKind::PermissionDenied { tracing::warn!( port = target_port, %err, @@ -368,23 +368,49 @@ impl TcpTlsTransport { sudo setcap cap_net_admin,cap_net_bind_service+p {exe}\n\ Falling back to port {FALLBACK_PORT}." ); - - let fallback_addr = SocketAddr::from(([0, 0, 0, 0], FALLBACK_PORT)); - let l = TcpListener::bind(fallback_addr).await?; - tracing::info!(port = FALLBACK_PORT, "TCP TLS listening on fallback port"); - Some(Arc::new(l)) } else { tracing::warn!( port = target_port, %err, - "Cannot bind to requested port {target_port} without privileges. \ - To allow port {target_port} and TUN on Linux, grant capabilities once with:\n \ - sudo setcap cap_net_admin,cap_net_bind_service+p {exe}" + "Cannot bind to default port {target_port}: {err}. \ + Falling back to port {FALLBACK_PORT}." ); - return Err(Box::new(err)); + } + + let fallback_addr = SocketAddr::from(([0, 0, 0, 0], FALLBACK_PORT)); + match TcpListener::bind(fallback_addr).await { + Ok(l) => { + tracing::info!(port = FALLBACK_PORT, "TCP TLS listening on fallback port"); + Some(Arc::new(l)) + } + Err(fallback_err) => { + tracing::warn!( + port = FALLBACK_PORT, + err = %fallback_err, + "Cannot bind to fallback port {FALLBACK_PORT}: {fallback_err}. \ + Falling back to ephemeral port." + ); + let ephemeral_addr = SocketAddr::from(([0, 0, 0, 0], 0)); + let l = TcpListener::bind(ephemeral_addr).await?; + let bound = l.local_addr()?.port(); + tracing::info!(port = bound, "TCP TLS listening on ephemeral port"); + Some(Arc::new(l)) + } } } - Err(err) => return Err(Box::new(err)), + Err(err) => { + let exe = std::env::current_exe() + .map(|p| p.display().to_string()) + .unwrap_or_else(|_| "tsunagi".into()); + tracing::warn!( + port = target_port, + %err, + "Cannot bind to requested port {target_port}: {err}. \ + To allow port {target_port} and TUN on Linux, grant capabilities once with:\n \ + sudo setcap cap_net_admin,cap_net_bind_service+p {exe}" + ); + return Err(Box::new(err)); + } }; let bound_port = listener