diff --git a/CHANGELOG.md b/CHANGELOG.md index fb9b3ea..7b6c7f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,13 @@ ### Added - Add `--channel-open-timeout` CLI flag. +- Add `--disable-tcp-keepalive` CLI flag. - Add `--tcp-keepalive-time` CLI flag. - Add `--tcp-keepalive-interval` CLI flag. ### Fixed -- Fix dropped handlers not being removed from HTTP pools. +- Remove dropped handlers from HTTP pools. ## 0.10.0 (2026-06-09) diff --git a/book/src/cli.md b/book/src/cli.md index 92e0444..4e035b4 100644 --- a/book/src/cli.md +++ b/book/src/cli.md @@ -296,7 +296,7 @@ Expose HTTP/SSH/TCP services through SSH port forwarding. - default: Default (compact, ANSI-formatted, single-line) - json: JSON format - duper: Duper format - + [default: default] --ip-allowlist <CIDR> @@ -426,20 +426,25 @@ Expose HTTP/SSH/TCP services through SSH port forwarding. By default, these connections are not terminated by Sandhole. + --disable-tcp-keepalive + Disable TCP keepalive on SSH, HTTP, HTTPS and TCP connections. + By default, it is enabled. + + Disabling TCP keepalive may lead to "connection reset by peer" + errors on socket reuse. + --tcp-keepalive-time <DURATION> - If set, enables TCP keepalive on accepted client connections, - sending the first probe after the set idle time. + Time to send the first TCP keepalive probe after the set idle time. - Set this option to avoid "connection reset by peer" on - socket reuse. + Only applies when `--disable-tcp-keepalive` is unset. - By default, keepalive is disabled. + [default: 20s] --tcp-keepalive-interval <DURATION> Interval between TCP keepalive probes once `--tcp-keepalive-time` has elapsed. - Only applies when `--tcp-keepalive-time` is set. + Only applies when `--disable-tcp-keepalive` is unset. [default: 10s] diff --git a/src/config.rs b/src/config.rs index 77d24c4..e99f5a0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -540,18 +540,26 @@ pub struct ApplicationConfig { #[arg(long, value_parser = validate_duration, value_name = "DURATION")] pub tcp_connection_timeout: Option, - /// If set, enables TCP keepalive on accepted client connections, - /// sending the first probe after the set idle time. + /// Disable TCP keepalive on SSH, HTTP, HTTPS and TCP connections. By default, it is enabled. /// - /// Set this option to avoid "connection reset by peer" on socket reuse. + /// Disabling TCP keepalive may lead to "connection reset by peer" errors on socket reuse. + #[arg(long, default_value_t = false)] + pub disable_tcp_keepalive: bool, + + /// Time to send the first TCP keepalive probe after the set idle time. /// - /// By default, keepalive is disabled. - #[arg(long, value_parser = validate_duration, value_name = "DURATION")] - pub tcp_keepalive_time: Option, + /// Only applies when `--disable-tcp-keepalive` is unset. + #[arg( + long, + default_value = "20s", + value_parser = validate_duration, + value_name = "DURATION" + )] + pub tcp_keepalive_time: Duration, /// Interval between TCP keepalive probes once `--tcp-keepalive-time` has elapsed. /// - /// Only applies when `--tcp-keepalive-time` is set. + /// Only applies when `--disable-tcp-keepalive` is unset. #[arg( long, default_value = "10s", @@ -690,7 +698,8 @@ mod application_config_tests { authentication_request_timeout: Duration::from_secs(5), http_request_timeout: None, tcp_connection_timeout: None, - tcp_keepalive_time: None, + disable_tcp_keepalive: false, + tcp_keepalive_time: Duration::from_secs(20), tcp_keepalive_interval: Duration::from_secs(10), udp_timeout: Duration::from_secs(60), } @@ -763,6 +772,7 @@ mod application_config_tests { "--authentication-request-timeout=6s", "--http-request-timeout=15s", "--tcp-connection-timeout=30s", + "--disable-tcp-keepalive", "--tcp-keepalive-time=15s", "--tcp-keepalive-interval=5s", "--udp-timeout=30s", @@ -839,7 +849,8 @@ mod application_config_tests { authentication_request_timeout: Duration::from_secs(6), http_request_timeout: Some(Duration::from_secs(15)), tcp_connection_timeout: Some(Duration::from_secs(30)), - tcp_keepalive_time: Some(Duration::from_secs(15)), + disable_tcp_keepalive: true, + tcp_keepalive_time: Duration::from_secs(15), tcp_keepalive_interval: Duration::from_secs(5), udp_timeout: Duration::from_secs(30), } diff --git a/src/entrypoint.rs b/src/entrypoint.rs index c4cf7f4..73ebd7e 100644 --- a/src/entrypoint.rs +++ b/src/entrypoint.rs @@ -265,11 +265,15 @@ pub async fn entrypoint(config: ApplicationConfig) -> color_eyre::Result<()> { Some(max_quota) => Arc::new(Box::new(Arc::new(QuotaMap::new(max_quota.into())))), None => Arc::new(Box::new(DummyQuotaHandler)), }; - let tcp_keepalive = config.tcp_keepalive_time.map(|tcp_keepalive_time| { - TcpKeepalive::new() - .with_time(tcp_keepalive_time) - .with_interval(config.tcp_keepalive_interval) - }); + let tcp_keepalive = if config.disable_tcp_keepalive { + None + } else { + Some( + TcpKeepalive::new() + .with_time(config.tcp_keepalive_time) + .with_interval(config.tcp_keepalive_interval), + ) + }; let http_connections = Arc::new( ConnectionMap::builder() .strategy(config.load_balancing)