Repository navigation
Conversation
A host with "h3" in Protocols now serves HTTP/3 with the certificate mod_ssl already resolved for it (SSLCertificateFile and mod_md alike), the way mod_http2 rides mod_ssl. The certificate and key are loaded in the ap_ssl_add_cert_files hook, during startup while httpd still runs privileged, so a key readable only by root loads for QUIC as it does for mod_ssl instead of failing in the unprivileged child. H3CertificatePath and H3CertificateKeyPath are removed; drop them from existing configurations. Each virtual host on a shared port now presents its own certificate over HTTP/3, chosen by the client SNI; an unmatched name gets the listener default. Names come from ServerName, ServerAlias and the wildcard ServerAlias names that httpd keeps in wild_names, and every entry is matched with ap_strcasecmp_match, the matcher httpd uses for its own virtual hosts. A cert_cb on the listener context applies the matched host certificate, key and chain to the connection: SSL_set_SSL_CTX does not switch the certificate of a QUIC connection. Landing it exposed that the module ran with AP_MODULE_FLAG_NONE, so a host without H3 directives shared the main server configuration; per-host H3AltSvc and H3AltSvcMaxAge were silently ignored. Set ALWAYS_MERGE.
httpd prepends virtual hosts to the server list, so the first host with an HTTP/3 context in that list is the last one defined. The listener took that host's settings and certificate, and a client whose SNI matched no host got that certificate, where mod_ssl serves the first virtual host defined for the address. Take the last match in both h3_post_config and find_h3_server instead.
Each child bound the UDP port itself after httpd dropped root, so a port below 1024 needed CAP_NET_BIND_SERVICE on the httpd binary. On Linux the parent now binds the socket in post_config, while it still runs as root, and keeps it in the process pool, so it stays bound across graceful restarts. The children inherit it. One child at a time serves it: the child that binds an abstract Unix socket name owns the port, and the kernel releases the name when that child exits. Other platforms keep the old per-child bind.
With ListenCoresBucketsRatio, httpd splits its TCP listeners into buckets. The parent now binds one SO_REUSEPORT UDP socket per bucket, and each child serves the first bucket that no other child owns. So more than one child serves HTTP/3. The parent keeps the sockets while children start and stop, so the kernel hash does not change and each peer stays on the same socket. Without buckets, there is one socket as before.
Some MPMs count their listener buckets in the run step, after post_config, so ap_num_listen_buckets was still 1 there. The module now uses the same count as httpd: online cores / ratio, capped at the MPM daemon limit, because a bucket with no child loses its peers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #948. This branch is stacked on it, so until #948 is merged the diff also shows its two commits. The three commits of this PR are the last three.
1. Bind the QUIC socket in the parent, before the privilege drop
Each child binds the UDP port itself after httpd drops root. With the usual
Listen 443and children running asdaemon/apache, that bind fails unless the httpd binary hasCAP_NET_BIND_SERVICE, so HTTP/3 does not come up on 443.On Linux the parent now binds the socket in post_config, while it still runs as root, and keeps it in the process pool, so it stays bound across graceful restarts. Children inherit it. One child at a time serves it: the child that binds an abstract Unix socket name owns the port, and the kernel releases the name when that child exits, so the next child takes over. Other platforms keep the per-child bind.
2. Bind one SO_REUSEPORT QUIC socket per httpd listener bucket
With
ListenCoresBucketsRatio, httpd splits its TCP listeners into buckets. The parent now binds oneSO_REUSEPORTUDP socket per bucket, and each child serves the first bucket no other child owns, so more than one child serves HTTP/3. The parent keeps the sockets while children start and stop, so the kernel's reuseport hash does not change and each peer stays on the same socket. Without buckets there is one socket, as before.3. Count the QUIC buckets from ListenCoresBucketsRatio in post_config
Some MPMs count their listener buckets in the run step, after post_config, so
ap_num_listen_bucketsis still 1 there. The module uses the same count as httpd: online cores divided by the ratio, capped at the MPM daemon limit, because a bucket with no child would lose its peers.Tested on EL9 (OpenSSL 4.0 with QUIC, nghttp3 1.19), httpd trunk built with
--enable-http3=shared, no new warnings. Children run asdaemon, key0600 root:Listen 443, noH3Port: HTTP/3 GET returns 200, and again after each of two graceful restarts.ListenCoresBucketsRatio 4,StartServers 4: 16 of 16 HTTP/3 requests return 200, accepted by all four children (6/2/6/2). After a graceful restart, 16 of 16 again, spread over all four new children (4/2/6/4). No errors in the log.A client that changes address can be hashed to another bucket's socket. The connection-ID based routing that keeps such a connection in its child is not part of this PR.