Skip to content

Commit 653205f

Browse files
MagicalTuxclaude
andcommitted
config/cli: expose max_conns_per_ip (F6)
Add a max_conns_per_ip field to ServerConfig (serde default so existing configs still parse under deny_unknown_fields) and a --max-conns-per-ip flag to the httpsd binary, both applied via Server::max_conns_per_ip. Addresses audit finding F6 (per-IP connection limits). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6f03f66 commit 653205f

2 files changed

Lines changed: 20 additions & 0 deletions

File tree

src/bin/httpsd.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ OPTIONS:
105105
--tls-key FILE PEM private key
106106
--self-signed[=H] generate a self-signed certificate (default host localhost)
107107
--workers N number of worker threads
108+
--max-conns-per-ip N cap concurrent connections per client IP (0 = unlimited)
108109
--no-http3 do not serve HTTP/3 (on by default with a TLS cert)
109110
--http ADDR also bind a plain-HTTP listener for redirects + ACME HTTP-01
110111
--allow-http serve content over HTTP instead of redirecting to HTTPS
@@ -135,6 +136,7 @@ struct Options {
135136
tls_key: Option<String>,
136137
self_signed: Option<String>,
137138
workers: Option<usize>,
139+
max_conns_per_ip: Option<u32>,
138140
no_http3: bool,
139141
no_compress: bool,
140142
allow_http: bool,
@@ -166,6 +168,7 @@ impl Options {
166168
tls_key: None,
167169
self_signed: None,
168170
workers: None,
171+
max_conns_per_ip: None,
169172
no_http3: false,
170173
no_compress: false,
171174
allow_http: false,
@@ -206,6 +209,13 @@ impl Options {
206209
let v = take_value(args, &mut i, arg)?;
207210
opts.workers = Some(v.parse().map_err(|_| format!("invalid --workers: {v}"))?);
208211
}
212+
"--max-conns-per-ip" => {
213+
let v = take_value(args, &mut i, arg)?;
214+
opts.max_conns_per_ip = Some(
215+
v.parse()
216+
.map_err(|_| format!("invalid --max-conns-per-ip: {v}"))?,
217+
);
218+
}
209219
"--no-compress" => opts.no_compress = true,
210220
"--allow-http" => opts.allow_http = true,
211221
"--http" => opts.http_listen = Some(take_value(args, &mut i, arg)?),
@@ -274,6 +284,9 @@ impl Options {
274284
if let Some(workers) = self.workers {
275285
server = server.workers(workers);
276286
}
287+
if let Some(n) = self.max_conns_per_ip {
288+
server = server.max_conns_per_ip(n);
289+
}
277290
if self.no_server_header {
278291
server = server.server_name(None);
279292
} else if let Some(name) = &self.server_name {

src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ pub struct ServerConfig {
168168
pub no_server_header: bool,
169169
/// Worker thread count (thread-pool runtime).
170170
pub workers: Option<usize>,
171+
/// Cap on concurrent connections from a single client IP (0 = unlimited,
172+
/// the default). Bounds any single source's share of the connection budget.
173+
#[serde(default)]
174+
pub max_conns_per_ip: Option<u32>,
171175
/// TLS settings.
172176
pub tls: Option<TlsConfig>,
173177
/// Compression settings.
@@ -256,6 +260,9 @@ impl ServerConfig {
256260
if let Some(workers) = self.workers {
257261
server = server.workers(workers);
258262
}
263+
if let Some(n) = self.max_conns_per_ip {
264+
server = server.max_conns_per_ip(n);
265+
}
259266
if self.no_server_header {
260267
server = server.server_name(None);
261268
} else if self.server_name.is_some() {

0 commit comments

Comments
 (0)