plugin/https: Add max_streams to limit HTTP/2 concurrent streams (#8522)

Add a max_streams option to the *https* plugin to limit the number of
concurrent HTTP/2 streams per DoH connection. This lets operators cap
per-connection concurrency (guarding against resource exhaustion) or
raise it above the Go default for high-fan-in clients that multiplex
many requests over a single connection.

Semantics match the existing *https3* plugin's max_streams:
- omitted  -> Go HTTP/2 server default is used
- 0        -> use the underlying HTTP/2 transport default
- positive -> advertise exactly that many concurrent streams
- negative -> rejected at config parse time

The limit is applied via the standard library http.Server.HTTP2
(HTTP2Config.MaxConcurrentStreams) so it is advertised in the server's
SETTINGS frame.

Signed-off-by: Mekias Yohannes <mmyohannes@gmail.com>
This commit is contained in:
myohannes
2026-09-06 22:16:44 -04:00
committed by GitHub
parent 558c9757a9
commit 2b8c305203
9 changed files with 392 additions and 3 deletions

View File

@@ -27,6 +27,10 @@ import (
const (
// DefaultHTTPSMaxConnections is the default maximum number of concurrent connections.
DefaultHTTPSMaxConnections = 200
// DefaultHTTPSMaxStreams is the default maximum number of concurrent HTTP/2 streams
// per connection, used when max_streams is not specified.
DefaultHTTPSMaxStreams = 250
)
// ServerHTTPS represents an instance of a DNS-over-HTTPS server.
@@ -91,6 +95,31 @@ func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
IdleTimeout: s.IdleTimeout,
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
}
// max_streams limits the number of concurrent HTTP/2 streams per connection. When unset,
// DefaultHTTPSMaxStreams is applied; a value of 0 leaves the underlying HTTP/2 transport
// default in place; a positive value sets the limit explicitly. The chosen value is
// advertised in the server's SETTINGS frame. Resolve across the whole group since blocks
// sharing a listener share one HTTP/2 server; conflicting explicit values are rejected.
maxStreams := DefaultHTTPSMaxStreams
var resolved *int
for _, conf := range group {
if conf == nil || conf.MaxHTTPSStreams == nil {
continue
}
if resolved != nil && *resolved != *conf.MaxHTTPSStreams {
return nil, fmt.Errorf("conflicting max_streams values for shared HTTPS listener %s: %d and %d",
addr, *resolved, *conf.MaxHTTPSStreams)
}
resolved = conf.MaxHTTPSStreams
}
if resolved != nil {
maxStreams = *resolved
}
if maxStreams > 0 {
srv.HTTP2 = &http.HTTP2Config{
MaxConcurrentStreams: maxStreams,
}
}
maxConnections := DefaultHTTPSMaxConnections
if len(group) > 0 && group[0] != nil && group[0].MaxHTTPSConnections != nil {
maxConnections = *group[0].MaxHTTPSConnections