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}