Skip to content

Commit cc34cf1

Browse files
committed
feat(tls): add tls_handshake_timeout config option
Owns the TLS handshake step of outbound HTTPS connections directly instead of delegating to hyper_rustls's fused connect-and-handshake future, so a timeout can be scoped to just the handshake rather than the whole connect+handshake duration. Wires this through as a new tls_handshake_timeout Datadog config option, defaulting to 10s.
1 parent 0f99ce3 commit cc34cf1

16 files changed

Lines changed: 181 additions & 110 deletions

File tree

docs/agent-data-plane/configuration/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ tracking.
2929
| -------------------------------------------- | ----------------------------------------------- | ------- |
3030
| `dogstatsd_experimental_http.enabled` | Enable experimental HTTP/H2C DSD listener | [#1682] |
3131
| `dogstatsd_experimental_http.listen_address` | Bind address for experimental HTTP DSD listener | [#1682] |
32-
| `tls_handshake_timeout` | HTTP TLS handshake timeout | [#178] |
3332

3433
<!-- section:unsupported-not-planned -->
3534
### Not Planned
@@ -735,6 +734,7 @@ compressed wire payload bytes.
735734
| `syslog_rfc` | Use RFC-style syslog header |
736735
| `syslog_uri` | Syslog destination URI |
737736
| `telemetry.dogstatsd_origin` | Per-origin processed-metrics telemetry |
737+
| `tls_handshake_timeout` | HTTP TLS handshake timeout |
738738
| `use_proxy_for_cloud_metadata` | Proxy cloud metadata endpoints |
739739
| `use_v2_api.series` | Send series via V2 protobuf endpoint |
740740
| `use_v3_api.series.enabled` | Global V3 series mode |

lib/agent-data-plane-config-system/src/translators/datadog_translator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,10 @@ impl DatadogConfigWitness for DatadogTranslator<'_> {
824824
self.config.shared.endpoints.tls.min_tls_version = value;
825825
}
826826

827+
fn consume_tls_handshake_timeout(&mut self, value: Duration) {
828+
self.config.shared.endpoints.tls.handshake_timeout = value;
829+
}
830+
827831
fn consume_multi_region_failover_api_key(&mut self, value: String) {
828832
self.config.domains.multi_region_failover.api_key = non_empty(value);
829833
}

lib/agent-data-plane-config/src/shared.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ pub struct Tls {
115115

116116
/// Path to which TLS session keys are logged, for debugging.
117117
pub sslkeylogfile: String,
118+
119+
/// Timeout for completing the TLS handshake after a connection is established.
120+
pub handshake_timeout: Duration,
118121
}
119122

120123
/// Payload compression settings applied before transmission.

lib/datadog-agent/config-testing/build/registry_gen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ static GOLDEN_ORDER: &[(&str, &[&str])] = &[
172172
"forwarder_storage_max_size_in_bytes",
173173
"forwarder_storage_path",
174174
"forwarder_outdated_file_in_days",
175+
"tls_handshake_timeout",
175176
],
176177
),
177178
(

lib/datadog-agent/config-testing/src/config_registry/forwarder.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,17 @@ crate::declare_annotations! {
313313
test_json: None,
314314
pipeline_affinity: PipelineAffinity::CrossCutting,
315315
};
316+
/// `tls_handshake_timeout`-HTTP TLS handshake timeout
317+
TLS_HANDSHAKE_TIMEOUT = SalukiAnnotation {
318+
schema: &schema::TLS_HANDSHAKE_TIMEOUT,
319+
support_level: SupportLevel::Full,
320+
additional_yaml_paths: &[],
321+
env_var_override: None,
322+
used_by: &[structs::FORWARDER_CONFIGURATION],
323+
value_type_override: None,
324+
test_json: None,
325+
pipeline_affinity: PipelineAffinity::CrossCutting,
326+
};
316327
/// `forwarder_apikey_validation_interval`-API key check interval (minutes)
317328
FORWARDER_APIKEY_VALIDATION_INTERVAL = SalukiAnnotation {
318329
schema: &schema::FORWARDER_APIKEY_VALIDATION_INTERVAL,

lib/datadog-agent/config-testing/src/config_registry/unsupported.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,6 @@ crate::declare_annotations! {
6060
test_json: None,
6161
pipeline_affinity: PipelineAffinity::CrossCutting,
6262
};
63-
/// `tls_handshake_timeout`-HTTP TLS handshake timeout
64-
TLS_HANDSHAKE_TIMEOUT = SalukiAnnotation {
65-
schema: &schema::TLS_HANDSHAKE_TIMEOUT,
66-
support_level: SupportLevel::Incompatible(Severity::Medium),
67-
additional_yaml_paths: &[],
68-
env_var_override: None,
69-
used_by: &[],
70-
value_type_override: None,
71-
test_json: None,
72-
pipeline_affinity: PipelineAffinity::CrossCutting,
73-
};
7463
/// `aggregator_buffer_size`-Channel buffer depth for aggregator queues
7564
AGGREGATOR_BUFFER_SIZE = SalukiAnnotation {
7665
schema: &schema::AGGREGATOR_BUFFER_SIZE,

lib/datadog-agent/config/schema/schema_overlay.yaml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,12 +2503,18 @@ inventory:
25032503
config_registry_filename: dogstatsd.rs
25042504

25052505
tls_handshake_timeout:
2506-
support: none
2507-
severity: medium
2508-
planned: true
2506+
support: full
25092507
pipelines: [cross_cutting]
25102508
description: "HTTP TLS handshake timeout"
2511-
documentation: "Existing request timeout covers the gap."
2509+
documentation: |
2510+
ADP applies `tls_handshake_timeout` to the TLS handshake step of outbound Datadog intake connections,
2511+
independent of `forwarder_timeout`, which bounds the full request. ADP owns the TLS layer directly rather than
2512+
delegating the handshake to an opaque connect-and-handshake future, so it can time out the handshake without
2513+
also aborting an in-progress TCP connect.
2514+
test_support:
2515+
used_by: [ForwarderConfiguration]
2516+
additional_attributes:
2517+
config_registry_filename: forwarder.rs
25122518
issue: "#178"
25132519

25142520
use_dogstatsd:

lib/datadog-agent/config/src/classifier/classifier.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ mod tests {
115115
#[test]
116116
fn incompatible_non_default() {
117117
let c = classifier();
118-
let result = c.classify("tls_handshake_timeout", &Value::Number(999.into())).unwrap();
118+
let result = c
119+
.classify("dogstatsd_stats_buffer", &Value::Number(999.into()))
120+
.unwrap();
119121
assert!(matches!(result.support_level, SupportLevel::Incompatible(_)));
120122
assert!(!result.is_default);
121123
}
@@ -139,8 +141,10 @@ mod tests {
139141
#[test]
140142
fn duration_default_null_is_not_default() {
141143
let c = classifier();
142-
// tls_handshake_timeout has a duration default (10s); a null value can't be normalized.
143-
let result = c.classify("tls_handshake_timeout", &Value::Null).unwrap();
144+
// dogstatsd_packet_buffer_flush_timeout has a duration default (100ms); a null value can't be normalized.
145+
let result = c
146+
.classify("dogstatsd_packet_buffer_flush_timeout", &Value::Null)
147+
.unwrap();
144148
assert!(!result.is_default);
145149
}
146150

@@ -149,14 +153,17 @@ mod tests {
149153
let c = classifier();
150154
// Neither an empty string nor arbitrary text parses as a duration, so neither matches.
151155
assert!(
152-
!c.classify("tls_handshake_timeout", &Value::String("".into()))
156+
!c.classify("dogstatsd_packet_buffer_flush_timeout", &Value::String("".into()))
153157
.unwrap()
154158
.is_default
155159
);
156160
assert!(
157-
!c.classify("tls_handshake_timeout", &Value::String("something".into()))
158-
.unwrap()
159-
.is_default
161+
!c.classify(
162+
"dogstatsd_packet_buffer_flush_timeout",
163+
&Value::String("something".into())
164+
)
165+
.unwrap()
166+
.is_default
160167
);
161168
}
162169

@@ -165,15 +172,15 @@ mod tests {
165172
let c = classifier();
166173
// The default is also matched when supplied as a Go duration string rather than nanoseconds.
167174
let result = c
168-
.classify("tls_handshake_timeout", &Value::String("10s".into()))
175+
.classify("dogstatsd_packet_buffer_flush_timeout", &Value::String("100ms".into()))
169176
.unwrap();
170177
assert!(result.is_default);
171178
}
172179

173180
#[test]
174181
fn incompatible_severity_levels() {
175182
let c = classifier();
176-
let result = c.classify("tls_handshake_timeout", &Value::Number(30.into())).unwrap();
183+
let result = c.classify("dogstatsd_stats_buffer", &Value::Number(30.into())).unwrap();
177184
assert!(matches!(
178185
result.support_level,
179186
SupportLevel::Incompatible(Severity::Medium)
@@ -183,20 +190,26 @@ mod tests {
183190
#[test]
184191
fn duration_default_as_nanoseconds_is_default() {
185192
let c = classifier();
186-
// The Agent transmits tls_handshake_timeout (schema default "10s") as integer nanoseconds.
187-
// The classifier must recognize this as the default and not flag it as an override.
193+
// The Agent transmits dogstatsd_packet_buffer_flush_timeout (schema default "100ms") as integer
194+
// nanoseconds. The classifier must recognize this as the default and not flag it as an override.
188195
let result = c
189-
.classify("tls_handshake_timeout", &Value::Number(10_000_000_000i64.into()))
196+
.classify(
197+
"dogstatsd_packet_buffer_flush_timeout",
198+
&Value::Number(100_000_000i64.into()),
199+
)
190200
.unwrap();
191201
assert!(result.is_default);
192202
}
193203

194204
#[test]
195205
fn duration_non_default_nanoseconds_is_not_default() {
196206
let c = classifier();
197-
// 5s in nanoseconds is not the 10s default.
207+
// 5ms in nanoseconds is not the 100ms default.
198208
let result = c
199-
.classify("tls_handshake_timeout", &Value::Number(5_000_000_000i64.into()))
209+
.classify(
210+
"dogstatsd_packet_buffer_flush_timeout",
211+
&Value::Number(5_000_000i64.into()),
212+
)
200213
.unwrap();
201214
assert!(!result.is_default);
202215
}

lib/datadog-agent/config/src/generated/classifier_data.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,6 @@ pub(crate) static CLASSIFIER_ENTRIES: &[ClassifierEntry] = &[
305305
pipeline_affinity: PipelineAffinity::Pipelines(&[Pipeline::DogStatsD]),
306306
default: DefaultValue::Json("[]"),
307307
},
308-
ClassifierEntry {
309-
yaml_path: "tls_handshake_timeout",
310-
aliases: &[],
311-
support_level: SupportLevel::Incompatible(Severity::Medium),
312-
pipeline_affinity: PipelineAffinity::CrossCutting,
313-
default: DefaultValue::DurationNanos(10000000000),
314-
},
315308
ClassifierEntry {
316309
yaml_path: "use_dogstatsd",
317310
aliases: &[],

lib/datadog-agent/config/src/generated/datadog_configuration.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,13 @@ pub struct DatadogConfiguration {
405405
#[serde(default)]
406406
pub telemetry: Telemetry,
407407

408+
#[serde(
409+
default = "duration_defaults::tls_handshake_timeout",
410+
411+
deserialize_with = "crate::duration_de::deserialize_go_duration"
412+
)]
413+
pub tls_handshake_timeout: std::time::Duration,
414+
408415
#[serde(default)]
409416
pub use_proxy_for_cloud_metadata: bool,
410417

@@ -552,6 +559,7 @@ impl Default for DatadogConfiguration {
552559
syslog_rfc: Default::default(),
553560
syslog_uri: Default::default(),
554561
telemetry: Default::default(),
562+
tls_handshake_timeout: duration_defaults::tls_handshake_timeout(),
555563
use_proxy_for_cloud_metadata: Default::default(),
556564
use_v2_api: Default::default(),
557565
use_v3_api: Default::default(),
@@ -1828,4 +1836,7 @@ mod duration_defaults {
18281836
pub(super) fn expected_tags_duration() -> std::time::Duration {
18291837
std::time::Duration::from_nanos(0)
18301838
}
1839+
pub(super) fn tls_handshake_timeout() -> std::time::Duration {
1840+
std::time::Duration::from_nanos(10000000000)
1841+
}
18311842
}

0 commit comments

Comments
 (0)