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 65,535
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 65,535
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 how long an HTTP/1 connection may take to send a complete request
251    /// header block before it is closed. Until the headers arrive no request
252    /// exists that a request deadline could apply to, so this is the only bound
253    /// on a peer that stalls mid-headers.
254    ///
255    /// Default is 30 seconds. `None` disables the deadline.
256    pub fn http1_header_read_timeout(self, timeout: Option<Duration>) -> Self {
257        Config {
258            http1_header_read_timeout: timeout,
259            ..self
260        }
261    }
262
263    /// Sets how long an accepted connection may take to complete its TLS
264    /// handshake before it is closed. The peer is unauthenticated for the whole
265    /// handshake, so without this a silent peer holds a task and a file
266    /// descriptor indefinitely.
267    ///
268    /// Default is 5 seconds. `None` disables the deadline.
269    pub fn handshake_timeout(self, handshake_timeout: Option<Duration>) -> Self {
270        Self {
271            handshake_timeout,
272            ..self
273        }
274    }
275
276    /// Sets how many accepted connections may be handshaking at the same time.
277    /// While the limit is reached the server stops accepting, leaving new
278    /// connections in the kernel backlog instead of holding file descriptors
279    /// for them.
280    ///
281    /// Default is 4096. `None` removes the limit.
282    pub fn max_pending_connections(self, max_pending_connections: Option<usize>) -> Self {
283        Self {
284            max_pending_connections,
285            ..self
286        }
287    }
288
289    /// Sets how many established connections a single peer may hold at once.
290    /// Further connections from a peer already at the limit are closed as soon
291    /// as they are accepted.
292    ///
293    /// Only connections that authenticate with a client certificate are
294    /// counted, since a peer that presents none cannot be told apart from any
295    /// other.
296    ///
297    /// Default is no limit (`None`).
298    pub fn max_connections_per_peer(self, max_connections_per_peer: Option<usize>) -> Self {
299        Self {
300            max_connections_per_peer,
301            ..self
302        }
303    }
304
305    /// Sets a callback invoked with the peer's public key each time one of its
306    /// connections is established, closed or refused at the limit. Only
307    /// connections counted under `max_connections_per_peer` are reported. It
308    /// runs on the accept loop or a connection's task, so it must not block.
309    pub fn on_peer_connection_event(
310        self,
311        on_peer_connection_event: impl Fn(&[u8], PeerConnectionEvent) + Send + Sync + 'static,
312    ) -> Self {
313        Self {
314            on_peer_connection_event: Some(OnPeerConnectionEvent(Arc::new(
315                on_peer_connection_event,
316            ))),
317            ..self
318        }
319    }
320
321    /// Measures the connections of the listener. The measuring changes no
322    /// behaviour.
323    ///
324    /// Default is no measuring (`None`).
325    pub fn metrics(self, metrics: Option<ListenerMetrics>) -> Self {
326        Self { metrics, ..self }
327    }
328
329    /// Rejects settings the accept loop cannot recover from.
330    pub(crate) fn validate(&self) -> Result<(), crate::BoxError> {
331        if self.max_connections_per_peer == Some(0) {
332            return Err("'max_connections_per_peer' must be greater than zero, \
333                        a peer allowed no connection can never be served"
334                .into());
335        }
336
337        match self.max_pending_connections {
338            Some(0) => Err("'max_pending_connections' must be greater than zero, \
339                            a server that accepts no connection is never useful"
340                .into()),
341            // Reaching the limit stops accepting until a handshake finishes, so
342            // without a deadline enough silent peers stall the server for good.
343            Some(_) if self.handshake_timeout.is_none() => Err(
344                "'max_pending_connections' requires a 'handshake_timeout' to release its \
345                     slots"
346                    .into(),
347            ),
348            _ => Ok(()),
349        }
350    }
351
352    pub(crate) fn connection_builder(
353        &self,
354    ) -> hyper_util::server::conn::auto::Builder<hyper_util::rt::TokioExecutor> {
355        let mut builder =
356            hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
357
358        if self.accept_http1 {
359            builder
360                .http1()
361                .timer(hyper_util::rt::TokioTimer::new())
362                .header_read_timeout(self.http1_header_read_timeout);
363        } else {
364            builder = builder.http2_only();
365        }
366
367        if self.enable_connect_protocol {
368            builder.http2().enable_connect_protocol();
369        }
370
371        let http2_keepalive_timeout = self
372            .http2_keepalive_timeout
373            .unwrap_or_else(|| Duration::new(DEFAULT_HTTP2_KEEPALIVE_TIMEOUT_SECS, 0));
374
375        builder
376            .http2()
377            .timer(hyper_util::rt::TokioTimer::new())
378            .initial_connection_window_size(self.init_connection_window_size)
379            .initial_stream_window_size(self.init_stream_window_size)
380            .max_concurrent_streams(self.max_concurrent_streams)
381            .keep_alive_interval(self.http2_keepalive_interval)
382            .keep_alive_timeout(http2_keepalive_timeout)
383            .adaptive_window(self.http2_adaptive_window.unwrap_or_default())
384            .max_pending_accept_reset_streams(self.http2_max_pending_accept_reset_streams)
385            .max_frame_size(self.max_frame_size);
386
387        if let Some(max_header_list_size) = self.http2_max_header_list_size {
388            builder.http2().max_header_list_size(max_header_list_size);
389        }
390
391        builder
392    }
393}