iota_http/config.rs
1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt, sync::Arc, time::Duration};
6
7use crate::metrics::ListenerMetrics;
8
9const DEFAULT_HTTP2_KEEPALIVE_TIMEOUT_SECS: u64 = 20;
10/// hyper's own default for the header read deadline; hyper only enforces it
11/// when a timer is configured, which this crate always does.
12const DEFAULT_HTTP1_HEADER_READ_TIMEOUT: Duration = Duration::from_secs(30);
13/// Covers a round trip plus a few TCP retransmissions on a lossy link; an
14/// unloaded TLS 1.3 handshake completes in one round trip.
15const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5);
16/// Every connection to a TLS listener passes through the handshake phase, so
17/// together with the handshake deadline this bounds how many silent peers it
18/// takes to make honest connections wait in the kernel backlog. Concurrent
19/// handshakes only build up when peers are slow or silent, so this leaves ample
20/// room for a legitimate reconnect burst.
21const DEFAULT_MAX_PENDING_CONNECTIONS: usize = 4096;
22
23#[derive(Debug, Clone)]
24pub struct Config {
25 init_stream_window_size: Option<u32>,
26 init_connection_window_size: Option<u32>,
27 max_concurrent_streams: Option<u32>,
28 pub(crate) tcp_keepalive: Option<Duration>,
29 http2_keepalive_interval: Option<Duration>,
30 http2_keepalive_timeout: Option<Duration>,
31 http2_adaptive_window: Option<bool>,
32 http2_max_pending_accept_reset_streams: Option<usize>,
33 http2_max_header_list_size: Option<u32>,
34 max_frame_size: Option<u32>,
35 pub(crate) accept_http1: bool,
36 http1_header_read_timeout: Option<Duration>,
37 enable_connect_protocol: bool,
38 pub(crate) max_connection_age: Option<Duration>,
39 pub(crate) handshake_timeout: Option<Duration>,
40 pub(crate) max_pending_connections: Option<usize>,
41 pub(crate) max_connections_per_peer: Option<usize>,
42 pub(crate) on_peer_connection_event: Option<OnPeerConnectionEvent>,
43 pub(crate) metrics: Option<ListenerMetrics>,
44}
45
46/// A change to the connections an authenticated peer holds, with the number
47/// it holds afterwards.
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum PeerConnectionEvent {
50 /// A connection was accepted and counted.
51 Established { held: usize },
52 /// A counted connection closed.
53 Closed { held: usize },
54 /// A further connection was closed because the peer already holds the
55 /// limit.
56 RefusedAtLimit { held: usize },
57}
58
59type PeerConnectionCallback = Arc<dyn Fn(&[u8], PeerConnectionEvent) + Send + Sync>;
60
61/// Called with the peer's public key on each of its connection events.
62#[derive(Clone)]
63pub(crate) struct OnPeerConnectionEvent(PeerConnectionCallback);
64
65impl OnPeerConnectionEvent {
66 pub(crate) fn call(&self, peer_public_key: &[u8], event: PeerConnectionEvent) {
67 (self.0)(peer_public_key, event)
68 }
69}
70
71impl fmt::Debug for OnPeerConnectionEvent {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 f.write_str("OnPeerConnectionEvent")
74 }
75}
76
77impl Default for Config {
78 fn default() -> Self {
79 Self {
80 init_stream_window_size: None,
81 init_connection_window_size: None,
82 max_concurrent_streams: None,
83 tcp_keepalive: None,
84 http2_keepalive_interval: None,
85 http2_keepalive_timeout: None,
86 http2_adaptive_window: None,
87 http2_max_pending_accept_reset_streams: None,
88 http2_max_header_list_size: None,
89 max_frame_size: None,
90 accept_http1: true,
91 http1_header_read_timeout: Some(DEFAULT_HTTP1_HEADER_READ_TIMEOUT),
92 enable_connect_protocol: true,
93 max_connection_age: None,
94 handshake_timeout: Some(DEFAULT_HANDSHAKE_TIMEOUT),
95 max_pending_connections: Some(DEFAULT_MAX_PENDING_CONNECTIONS),
96 max_connections_per_peer: None,
97 on_peer_connection_event: None,
98 metrics: None,
99 }
100 }
101}
102
103impl Config {
104 /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
105 /// stream-level flow control.
106 ///
107 /// Default is 1 MiB, the default of hyper.
108 ///
109 /// [spec]: https://httpwg.org/specs/rfc9113.html#InitialWindowSize
110 pub fn initial_stream_window_size(self, sz: impl Into<Option<u32>>) -> Self {
111 Self {
112 init_stream_window_size: sz.into(),
113 ..self
114 }
115 }
116
117 /// Sets the max connection-level flow control for HTTP2
118 ///
119 /// Default is 1 MiB, the default of hyper.
120 pub fn initial_connection_window_size(self, sz: impl Into<Option<u32>>) -> Self {
121 Self {
122 init_connection_window_size: sz.into(),
123 ..self
124 }
125 }
126
127 /// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2
128 /// connections.
129 ///
130 /// Default is no limit (`None`).
131 ///
132 /// [spec]: https://httpwg.org/specs/rfc9113.html#n-stream-concurrency
133 pub fn max_concurrent_streams(self, max: impl Into<Option<u32>>) -> Self {
134 Self {
135 max_concurrent_streams: max.into(),
136 ..self
137 }
138 }
139
140 /// Sets the maximum time option in milliseconds that a connection may exist
141 ///
142 /// Default is no limit (`None`).
143 pub fn max_connection_age(self, max_connection_age: Duration) -> Self {
144 Self {
145 max_connection_age: Some(max_connection_age),
146 ..self
147 }
148 }
149
150 /// Set whether HTTP2 Ping frames are enabled on accepted connections.
151 ///
152 /// If `None` is specified, HTTP2 keepalive is disabled, otherwise the
153 /// duration specified will be the time interval between HTTP2 Ping
154 /// frames. The timeout for receiving an acknowledgement of the
155 /// keepalive ping can be set with [`Config::http2_keepalive_timeout`].
156 ///
157 /// Default is no HTTP2 keepalive (`None`)
158 pub fn http2_keepalive_interval(self, http2_keepalive_interval: Option<Duration>) -> Self {
159 Self {
160 http2_keepalive_interval,
161 ..self
162 }
163 }
164
165 /// Sets a timeout for receiving an acknowledgement of the keepalive ping.
166 ///
167 /// If the ping is not acknowledged within the timeout, the connection will
168 /// be closed. Does nothing if http2_keep_alive_interval is disabled.
169 ///
170 /// Default is 20 seconds.
171 pub fn http2_keepalive_timeout(self, http2_keepalive_timeout: Option<Duration>) -> Self {
172 Self {
173 http2_keepalive_timeout,
174 ..self
175 }
176 }
177
178 /// Sets whether to use an adaptive flow control. Defaults to false.
179 /// Enabling this will override the limits set in
180 /// http2_initial_stream_window_size and
181 /// http2_initial_connection_window_size.
182 pub fn http2_adaptive_window(self, enabled: Option<bool>) -> Self {
183 Self {
184 http2_adaptive_window: enabled,
185 ..self
186 }
187 }
188
189 /// Configures the maximum number of pending reset streams allowed before a
190 /// GOAWAY will be sent.
191 ///
192 /// This will default to whatever the default in h2 is. As of v0.3.17, it is
193 /// 20.
194 ///
195 /// See <https://github.com/hyperium/hyper/issues/2877> for more information.
196 pub fn http2_max_pending_accept_reset_streams(self, max: Option<usize>) -> Self {
197 Self {
198 http2_max_pending_accept_reset_streams: max,
199 ..self
200 }
201 }
202
203 /// Set whether TCP keepalive messages are enabled on accepted connections.
204 ///
205 /// If `None` is specified, keepalive is disabled, otherwise the duration
206 /// specified will be the time to remain idle before sending TCP keepalive
207 /// probes.
208 ///
209 /// Default is no keepalive (`None`)
210 pub fn tcp_keepalive(self, tcp_keepalive: Option<Duration>) -> Self {
211 Self {
212 tcp_keepalive,
213 ..self
214 }
215 }
216
217 /// Sets the max size of received header frames.
218 ///
219 /// This will default to whatever the default in hyper is. As of v1.4.1, it
220 /// is 16 KiB.
221 pub fn http2_max_header_list_size(self, max: impl Into<Option<u32>>) -> Self {
222 Self {
223 http2_max_header_list_size: max.into(),
224 ..self
225 }
226 }
227
228 /// Sets the maximum frame size to use for HTTP2.
229 ///
230 /// Passing `None` will do nothing.
231 ///
232 /// If not set, will default from underlying transport.
233 pub fn max_frame_size(self, frame_size: impl Into<Option<u32>>) -> Self {
234 Self {
235 max_frame_size: frame_size.into(),
236 ..self
237 }
238 }
239
240 /// Allow this accepting http1 requests.
241 ///
242 /// Default is `true`.
243 pub fn accept_http1(self, accept_http1: bool) -> Self {
244 Config {
245 accept_http1,
246 ..self
247 }
248 }
249
250 /// Sets whether HTTP/2 connections offer the extended CONNECT protocol
251 /// ([RFC 8441]), which lets a client tunnel another protocol, such as
252 /// WebSocket, through an HTTP/2 stream.
253 ///
254 /// Default is `true`.
255 ///
256 /// [RFC 8441]: https://www.rfc-editor.org/rfc/rfc8441
257 pub fn enable_connect_protocol(self, enable_connect_protocol: bool) -> Self {
258 Config {
259 enable_connect_protocol,
260 ..self
261 }
262 }
263
264 /// Sets how long an HTTP/1 connection may take to send a complete request
265 /// header block before it is closed. Until the headers arrive no request
266 /// exists that a request deadline could apply to, so this is the only bound
267 /// on a peer that stalls mid-headers.
268 ///
269 /// Default is 30 seconds. `None` disables the deadline.
270 pub fn http1_header_read_timeout(self, timeout: Option<Duration>) -> Self {
271 Config {
272 http1_header_read_timeout: timeout,
273 ..self
274 }
275 }
276
277 /// Sets how long an accepted connection may take to complete its TLS
278 /// handshake before it is closed. The peer is unauthenticated for the whole
279 /// handshake, so without this a silent peer holds a task and a file
280 /// descriptor indefinitely.
281 ///
282 /// Default is 5 seconds. `None` disables the deadline.
283 pub fn handshake_timeout(self, handshake_timeout: Option<Duration>) -> Self {
284 Self {
285 handshake_timeout,
286 ..self
287 }
288 }
289
290 /// Sets how many accepted connections may be handshaking at the same time.
291 /// While the limit is reached the server stops accepting, leaving new
292 /// connections in the kernel backlog instead of holding file descriptors
293 /// for them.
294 ///
295 /// Default is 4096. `None` removes the limit.
296 pub fn max_pending_connections(self, max_pending_connections: Option<usize>) -> Self {
297 Self {
298 max_pending_connections,
299 ..self
300 }
301 }
302
303 /// Sets how many established connections a single peer may hold at once.
304 /// Further connections from a peer already at the limit are closed as soon
305 /// as they are accepted.
306 ///
307 /// Only connections that authenticate with a client certificate are
308 /// counted, since a peer that presents none cannot be told apart from any
309 /// other.
310 ///
311 /// Default is no limit (`None`).
312 pub fn max_connections_per_peer(self, max_connections_per_peer: Option<usize>) -> Self {
313 Self {
314 max_connections_per_peer,
315 ..self
316 }
317 }
318
319 /// Sets a callback invoked with the peer's public key each time one of its
320 /// connections is established, closed or refused at the limit. Only
321 /// connections counted under `max_connections_per_peer` are reported. It
322 /// runs on the accept loop or a connection's task, so it must not block.
323 pub fn on_peer_connection_event(
324 self,
325 on_peer_connection_event: impl Fn(&[u8], PeerConnectionEvent) + Send + Sync + 'static,
326 ) -> Self {
327 Self {
328 on_peer_connection_event: Some(OnPeerConnectionEvent(Arc::new(
329 on_peer_connection_event,
330 ))),
331 ..self
332 }
333 }
334
335 /// Measures the connections and the requests of the listener. The
336 /// measuring changes no behaviour.
337 ///
338 /// Default is no measuring (`None`).
339 pub fn metrics(self, metrics: Option<ListenerMetrics>) -> Self {
340 Self { metrics, ..self }
341 }
342
343 /// Rejects settings the accept loop cannot recover from.
344 pub(crate) fn validate(&self) -> Result<(), crate::BoxError> {
345 if self.max_connections_per_peer == Some(0) {
346 return Err("'max_connections_per_peer' must be greater than zero, \
347 a peer allowed no connection can never be served"
348 .into());
349 }
350
351 match self.max_pending_connections {
352 Some(0) => Err("'max_pending_connections' must be greater than zero, \
353 a server that accepts no connection is never useful"
354 .into()),
355 // Reaching the limit stops accepting until a handshake finishes, so
356 // without a deadline enough silent peers stall the server for good.
357 Some(_) if self.handshake_timeout.is_none() => Err(
358 "'max_pending_connections' requires a 'handshake_timeout' to release its \
359 slots"
360 .into(),
361 ),
362 _ => Ok(()),
363 }
364 }
365
366 pub(crate) fn connection_builder(
367 &self,
368 ) -> hyper_util::server::conn::auto::Builder<hyper_util::rt::TokioExecutor> {
369 let mut builder =
370 hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
371
372 if self.accept_http1 {
373 builder
374 .http1()
375 .timer(hyper_util::rt::TokioTimer::new())
376 .header_read_timeout(self.http1_header_read_timeout);
377 } else {
378 builder = builder.http2_only();
379 }
380
381 if self.enable_connect_protocol {
382 builder.http2().enable_connect_protocol();
383 }
384
385 let http2_keepalive_timeout = self
386 .http2_keepalive_timeout
387 .unwrap_or_else(|| Duration::new(DEFAULT_HTTP2_KEEPALIVE_TIMEOUT_SECS, 0));
388
389 builder
390 .http2()
391 .timer(hyper_util::rt::TokioTimer::new())
392 .initial_connection_window_size(self.init_connection_window_size)
393 .initial_stream_window_size(self.init_stream_window_size)
394 .max_concurrent_streams(self.max_concurrent_streams)
395 .keep_alive_interval(self.http2_keepalive_interval)
396 .keep_alive_timeout(http2_keepalive_timeout)
397 .adaptive_window(self.http2_adaptive_window.unwrap_or_default())
398 .max_pending_accept_reset_streams(self.http2_max_pending_accept_reset_streams)
399 .max_frame_size(self.max_frame_size);
400
401 if let Some(max_header_list_size) = self.http2_max_header_list_size {
402 builder.http2().max_header_list_size(max_header_list_size);
403 }
404
405 builder
406 }
407}