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}