Skip to content

Commit 495c39f

Browse files
zh-jqcursoragent
andcommitted
vey-icap-client: add tcp_connect_timeout for ICAP TCP connect
Black-holed ICAP peers previously blocked until the OS SYN timeout before bypass could apply. Default is 1s. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 37ea724 commit 495c39f

5 files changed

Lines changed: 45 additions & 1 deletion

File tree

lib/vey-icap-client/src/service/config/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub struct IcapServiceConfig {
3535
pub(crate) tls_name: ServerName<'static>,
3636
pub(crate) connection_pool: ConnectionPoolConfig,
3737
pub(crate) tcp_keepalive: TcpKeepAliveConfig,
38+
pub(crate) tcp_connect_timeout: Duration,
3839
#[cfg(unix)]
3940
pub(crate) use_unix_socket: Option<PathBuf>,
4041
pub(crate) icap_206_enable: bool,
@@ -80,6 +81,7 @@ impl IcapServiceConfig {
8081
tls_name,
8182
connection_pool: ConnectionPoolConfig::default(),
8283
tcp_keepalive: TcpKeepAliveConfig::default_enabled(),
84+
tcp_connect_timeout: Duration::from_secs(1),
8385
#[cfg(unix)]
8486
use_unix_socket: None,
8587
icap_206_enable: false,
@@ -95,6 +97,10 @@ impl IcapServiceConfig {
9597
self.tcp_keepalive = config;
9698
}
9799

100+
pub fn set_tcp_connect_timeout(&mut self, time: Duration) {
101+
self.tcp_connect_timeout = time;
102+
}
103+
98104
pub fn set_tls_client(&mut self, config: RustlsClientConfigBuilder) {
99105
self.tls_client = Some(config);
100106
}
@@ -203,6 +209,7 @@ mod tests {
203209
let mut config = IcapServiceConfig::new(IcapMethod::Reqmod, url).unwrap();
204210
assert!(config.tls_client.is_none());
205211
assert_eq!(config.upstream.port(), 1344);
212+
assert_eq!(config.tcp_connect_timeout, Duration::from_secs(1));
206213
config.user_agent = Some("vey-test/1.0".to_string());
207214

208215
let header = String::from_utf8(config.build_request_header()).unwrap();

lib/vey-icap-client/src/service/config/yaml.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ impl IcapServiceConfig {
4646
config.set_tcp_keepalive(keepalive);
4747
Ok(())
4848
}
49+
"tcp_connect_timeout" => {
50+
let connect_timeout = vey_yaml::humanize::as_duration(v)
51+
.context(format!("invalid humanize duration value for key {k}"))?;
52+
config.set_tcp_connect_timeout(connect_timeout);
53+
Ok(())
54+
}
4955
#[cfg(unix)]
5056
"use_unix_socket" => {
5157
let path = vey_yaml::value::as_absolute_path(v)
@@ -205,6 +211,7 @@ mod tests {
205211
max_idle_count: 10
206212
idle_timeout: 30s
207213
disable_preview: true
214+
tcp_connect_timeout: "3s"
208215
respond_shared_names:
209216
- "X-Header-1"
210217
- "X-Header-2"
@@ -218,6 +225,10 @@ mod tests {
218225
config.tls_name,
219226
rustls_pki_types::ServerName::try_from("example.com").unwrap()
220227
);
228+
assert_eq!(
229+
config.tcp_connect_timeout,
230+
std::time::Duration::from_secs(3)
231+
);
221232
assert_eq!(
222233
config.tcp_keepalive.idle_time(),
223234
std::time::Duration::from_secs(60)
@@ -380,6 +391,14 @@ mod tests {
380391
);
381392
assert!(IcapServiceConfig::parse_respmod_service_yaml(&yaml, None).is_err());
382393

394+
let yaml = yaml_doc!(
395+
r#"
396+
url: "icap://example.com:1344/service"
397+
tcp_connect_timeout: "-1s"
398+
"#
399+
);
400+
assert!(IcapServiceConfig::parse_reqmod_service_yaml(&yaml, None).is_err());
401+
383402
let yaml = yaml_doc!(
384403
r#"
385404
respond_shared_names: false

lib/vey-icap-client/src/service/connection.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ impl IcapConnector {
117117
&Default::default(),
118118
true,
119119
)?;
120-
let stream = socket.connect(peer).await?;
120+
let stream = tokio::time::timeout(self.config.tcp_connect_timeout, socket.connect(peer))
121+
.await
122+
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "ICAP tcp connect timed out"))??;
121123

122124
if let Some(client) = &self.tls_client {
123125
let tls_connector = TlsConnector::from(client.driver.clone());

sphinx/vey-values/configuration/values/audit.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ If the value is a map, the following keys are supported:
8888

8989
**default**: enabled with default value
9090

91+
* tcp_connect_timeout
92+
93+
**optional**, **type**: :ref:`humanize duration <conf_value_humanize_duration>`
94+
95+
TCP connection timeout when establishing a connection to the ICAP server.
96+
Without this bound, an unreachable (black-holed) ICAP endpoint can block each
97+
audited request for the full OS SYN timeout before ``bypass`` can take effect.
98+
99+
**default**: 1s
100+
101+
.. availability::
102+
103+
104+
- ``vey-proxy``: available since ``1.13.10``
105+
91106
* icap_connection_pool
92107

93108
**optional**, **type**: :ref:`connection pool <conf_value_connection_pool_config>`

vey-proxy/CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ CHANGELOG
33
Release notes for `vey-proxy`, ordered from newest to oldest.
44

55
v1.13.10:
6+
- Feature: add ICAP service `tcp_connect_timeout` (default 1s) so black-holed ICAP endpoints fail promptly and `bypass` can take effect
67
- BUG FIX: ICAP `Host` includes the non-default port (and brackets IPv6 literals)
78
- BUG FIX: ICAP OPTIONS requests include `Encapsulated: null-body=0` (RFC 3507)
89
- BUG FIX: ICAP/ICAPS service URLs without an explicit port use defaults 1344 / 11344 instead of failing to parse

0 commit comments

Comments
 (0)