Skip to main content

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}