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