Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ A clear description of what you expected to happen.
- httpd Version: [e.g. 2.5.0-trunk]
- OpenSSL Version: [e.g. 3.5.0]
- nghttp3 Version: [e.g. 1.17.0]
- ngtcp2 Version: [e.g. 1.25.0]
- APR Version: [e.g. 1.7.0]

**Crash Logs & Additional Context**
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
run: |
git submodule update --init
git submodule update --init --recursive dependencies/nghttp3
git submodule update --init dependencies/ngtcp2

- name: Set up buildx
uses: docker/setup-buildx-action@v4
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
run: |
git submodule update --init
git submodule update --init --recursive dependencies/nghttp3
git submodule update --init dependencies/ngtcp2

- name: Set up NASM
shell: pwsh
Expand Down Expand Up @@ -86,12 +87,13 @@ jobs:
@(
"dependencies/apr-dist/bin"
"dependencies/nghttp3-dist/bin"
"dependencies/ngtcp2-dist/bin"
"dependencies/openssl-dist/bin"
"$env:VCPKG_INSTALLATION_ROOT/installed/x64-windows/bin"
$redist.FullName
) | ForEach-Object { Get-ChildItem $_ -Filter *.dll | Copy-Item -Destination stage/bin -Force }

$required = 'libapr-1.dll', 'libaprutil-1.dll', 'nghttp3.dll',
$required = 'libapr-1.dll', 'libaprutil-1.dll', 'nghttp3.dll', 'ngtcp2.dll', 'ngtcp2_crypto_ossl.dll',
'libssl-3-x64.dll', 'libcrypto-3-x64.dll', 'VCRUNTIME140.dll'
foreach ($dll in $required) {
if (-not (Test-Path "stage/bin/$dll")) { throw "staging is missing $dll" }
Expand Down
14 changes: 11 additions & 3 deletions .github/workflows/interop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
run: |
git submodule update --init
git submodule update --init --recursive dependencies/nghttp3
git submodule update --init dependencies/ngtcp2

- name: Set up buildx
uses: docker/setup-buildx-action@v4
Expand Down Expand Up @@ -141,12 +142,19 @@ jobs:
mv impls.json implementations_quic.json

- name: Run matrix
continue-on-error: true
run: python run.py -s mod_http3 -c "$CLIENT" -t http3 -l logs -j results.json
# The runner's tshark can crash on a cut capture: allow 2 failed tries.
run: |
for try in 1 2 3; do
rm -f results.json
python run.py -s mod_http3 -c "$CLIENT" -t http3 -l logs/$try -j results.json || true
result=$(jq -r '.results[0][0].result' results.json 2>/dev/null || true)
case $result in succeeded|unsupported) break ;; esac
echo "::warning title=$CLIENT::try $try: ${result:-no result}"
done

- name: Report verdict
run: |
result=$(jq -r '.results[0][0].result' results.json)
result=$(jq -r '.results[0][0].result' results.json 2>/dev/null || echo 'no result')
echo "### $CLIENT — \`$result\`" >>"$GITHUB_STEP_SUMMARY"
case $result in
succeeded) ;;
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
run: |
git submodule update --init
git submodule update --init --recursive dependencies/nghttp3
git submodule update --init dependencies/ngtcp2

- name: Set up buildx
uses: docker/setup-buildx-action@v4
Expand Down
7 changes: 7 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@
ignore = dirty
update = checkout
branch = openssl-3.5
[submodule "dependencies/ngtcp2"]
path = dependencies/ngtcp2
url = https://github.com/ngtcp2/ngtcp2.git
shallow = true
ignore = untracked
update = checkout
#branch = main # tag: v1.25.0
3 changes: 1 addition & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ Individuals

* Jean-Frédéric Clere <jfclere apache.org> <jfclere gmail.com>
* Tarek Ibrahim <tareki pulsarxtech.com> <t1br4h1m gmail.com>


* Alexander Gerasimov https://codeit.guru/ <codeguard gmail.com>
56 changes: 56 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,62 @@ mod_http3 changes
Changes are listed most recent first.
Security-related entries always appear at the top of their release block.

unreleased
--------------------
*) Added H3EarlyData (default off). A resumed client may send its first
request as 0-RTT data and gets the response one round trip sooner.
Only safe methods run before the handshake completes; other methods
wait for it, because 0-RTT data can be replayed (RFC 8470).
[Alexander Gerasimov <codeguard gmail.com>]

*) Moved the QUIC transport from the OpenSSL QUIC server to ngtcp2, with
OpenSSL as the TLS backend through its QUIC TLS API. OpenSSL still
drops server-side 0-RTT packets. ngtcp2 >= 1.25.0 is a new build
dependency (WITH_NGTCP2). Stream data is no longer copied: nghttp3
releases a buffer when the peer acknowledges it.
[Alexander Gerasimov <codeguard gmail.com>]

*) Fixed an event-thread spin when a client holds its flow-control window
shut: a blocked stream is now blocked again each time nghttp3 offers
it. H3IdleTimeout now always closes with NO_ERROR; the QUIC idle timer
runs 2 seconds longer and closes silently. A graceful restart no
longer waits forever on a connection whose handshake was still
running, so the new child gets the UDP port once the old one drains.
Fixed a crash under load: worker threads and the event thread used
pools that share one allocator, and the allocator had no lock.
[Alexander Gerasimov <codeguard gmail.com>]

v0.0.71 (2026-09-08)
--------------------
*) SECURITY: Load the HTTP/3 certificate and key in post_config, while httpd
still runs privileged, so a root-only key no longer fails in the
unprivileged child and both mod_ssl and mod_http3 read the same files.
[Alexander Gerasimov <codeguard gmail.com>]

*) A host with "h3" in Protocols now serves HTTP/3 with the certificate
mod_ssl resolved for it (SSLCertificateFile and mod_md alike), the way
mod_http2 rides mod_ssl. H3CertificatePath and H3CertificateKeyPath are
removed; drop them from existing configurations.
[Alexander Gerasimov <codeguard gmail.com>]

*) Select the HTTP/3 certificate by SNI, so each virtual host on a shared
port serves its own certificate instead of the first host's. Names
are matched as httpd matches virtual hosts, wildcard ServerAlias
included.
[Alexander Gerasimov <codeguard gmail.com>]

*) Give every virtual host its own mod_http3 configuration
(AP_MODULE_FLAG_ALWAYS_MERGE). A host without H3 directives used to share
the main server's, so per-host settings such as H3AltSvc and H3AltSvcMaxAge
were silently ignored and hosts could not carry their own certificate.
[Alexander Gerasimov <codeguard gmail.com>]

*) Test suite: stop.conf now names the same pid file as httpd.conf, so
"apachectl -k stop" actually stops the server on httpd builds whose
default pid file lives in run/; before, every restart in the suite kept
talking to the previous server.
[Alexander Gerasimov <codeguard gmail.com>]

v0.0.70 (2026-09-06)
--------------------
*) SECURITY: Updated the httpd submodule so mpm_event tolerates a connection
Expand Down
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.26)

project(mod_http3 VERSION 0.0.70)
project(mod_http3 VERSION 0.0.71)

# -- Compiler and Build Type Checks --
if(NOT CMAKE_C_COMPILER_ID MATCHES "^(GNU|MSVC)$")
Expand Down Expand Up @@ -47,6 +47,7 @@ option(ENABLE_ASAN "Address Sanitizer" OFF)
option(ENABLE_WERROR "Treat warnings as errors" OFF)

set(WITH_NGHTTP3 "" CACHE PATH "Path to nghttp3 installation prefix")
set(WITH_NGTCP2 "" CACHE PATH "Path to ngtcp2 installation prefix, built with OpenSSL")
set(WITH_SSL "" CACHE PATH "Path to OpenSSL installation prefix")
set(WITH_HTTPD "" CACHE PATH "Path to httpd installation prefix (includes APR/APU)")
set(WITH_APR "" CACHE PATH "Path to APR installation prefix")
Expand All @@ -63,8 +64,9 @@ include(flags)
add_library(${PROJECT_NAME}-deps INTERFACE)
include(nghttp3)
include(openssl)
include(ngtcp2)
include(httpd)
target_link_libraries(${PROJECT_NAME}-deps INTERFACE nghttp3 openssl httpd)
target_link_libraries(${PROJECT_NAME}-deps INTERFACE nghttp3 openssl ngtcp2 httpd)

# -- Core object library --
file(GLOB_RECURSE sources CONFIGURE_DEPENDS mod_http3/src/*.c)
Expand Down
17 changes: 8 additions & 9 deletions INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
To use system-installed dependencies, provide WITH_* paths:

$ git submodule update --init dependencies/nghttp3
$ git submodule update --init dependencies/ngtcp2
$ cmake -B build \
-DWITH_SSL=/opt/openssl \
-DWITH_HTTPD=/opt/httpd \
Expand All @@ -35,11 +36,12 @@

Requirements:

OpenSSL >= 3.5.0 (with QUIC support)
OpenSSL >= 3.5.0 (with the QUIC TLS API)
httpd MMN >= 20211221
APR >= 1.7.0
APU >= 1.6.0
nghttp3 >= 1.18.0
ngtcp2 >= 1.25.0 (built with OpenSSL)

APR-util needs expat and httpd needs PCRE2. Neither is a submodule:
both belong to the server stack rather than to mod_http3, and both are
Expand Down Expand Up @@ -116,15 +118,13 @@
reject a directly-trusted self-signed leaf. Import ca.crt to trust
the server. The script refuses to overwrite existing keys.

The key must be readable by the httpd child user:

$ chgrp daemon /path/to/httpd/conf/certs/server.key
$ chmod 640 /path/to/httpd/conf/certs/server.key
The key is read at startup, before httpd drops privileges, so the
permissions mod_ssl accepts are enough.

3. Configure httpd.

LoadModule must appear before the <VirtualHost> block.
H3CertificatePath and H3CertificateKeyPath are required.
LoadModule must appear before the <VirtualHost> block. A host serves
HTTP/3 when h3 is in its Protocols and mod_ssl has its certificate.

Minimal httpd.conf:

Expand All @@ -142,8 +142,7 @@
SSLCertificateFile conf/certs/server.crt
SSLCertificateKeyFile conf/certs/server.key

H3CertificatePath conf/certs/server.crt
H3CertificateKeyPath conf/certs/server.key
Protocols h3 h2 http/1.1

DocumentRoot htdocs
<Directory htdocs>
Expand Down
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ This product makes use of the following third-party libraries:
Copyright 2019-2026 nghttp3 contributors
Licensed under the MIT License.

ngtcp2 (https://github.com/ngtcp2/ngtcp2)
Copyright 2016-2026 ngtcp2 contributors
Licensed under the MIT License.

OpenSSL (https://www.openssl.org/)
Copyright 1998-2026 The OpenSSL Project Authors
Licensed under the Apache License 2.0.
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Default build compiles all dependencies (OpenSSL, APR, APR-util, httpd) from sub
```sh
git submodule update --init
git submodule update --init --recursive dependencies/nghttp3
git submodule update --init dependencies/ngtcp2
cmake -B build
cmake --build build
```
Expand Down Expand Up @@ -39,6 +40,7 @@ See [INSTALL](INSTALL) for full build instructions.
| `WITH_APR` | (empty) | Path to APR prefix (overrides source build) |
| `WITH_APU` | (empty) | Path to APR-util prefix (overrides source build) |
| `WITH_NGHTTP3` | (empty) | Path to nghttp3 prefix (overrides source build) |
| `WITH_NGTCP2` | (empty) | Path to ngtcp2 prefix, built with OpenSSL (overrides source build) |
| `ENABLE_ASAN` | `OFF` | Address Sanitizer (requires `Debug`) |
| `ENABLE_UBSAN` | `OFF` | UB Sanitizer (requires `Debug`) |
| `ENABLE_WERROR` | `OFF` | Treat warnings as errors |
Expand Down Expand Up @@ -69,9 +71,6 @@ Listen 4433 https

Protocols h3 h2 http/1.1

H3CertificatePath conf/server.crt
H3CertificateKeyPath conf/server.key

DocumentRoot htdocs
<Directory htdocs>
Require all granted
Expand Down
37 changes: 37 additions & 0 deletions cmake/modules/ngtcp2.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -- ngtcp2 --
if(TARGET ngtcp2)
return()
endif()

set(NGTCP2_VERSION_MIN "1.25.0")

if(WIN32)
include(windows/ngtcp2)
else()
include(unix/ngtcp2)
endif()

find_library(NGTCP2_LIBRARY NAMES ngtcp2
PATHS "${NGTCP2_OUTPUT_DIRECTORY}/lib" "${NGTCP2_OUTPUT_DIRECTORY}/lib64" NO_DEFAULT_PATH)
find_library(NGTCP2_CRYPTO_OSSL_LIBRARY NAMES ngtcp2_crypto_ossl
PATHS "${NGTCP2_OUTPUT_DIRECTORY}/lib" "${NGTCP2_OUTPUT_DIRECTORY}/lib64" NO_DEFAULT_PATH)
if(NOT NGTCP2_LIBRARY OR NOT NGTCP2_CRYPTO_OSSL_LIBRARY)
message(FATAL_ERROR
"[ngtcp2] error: ngtcp2 or ngtcp2_crypto_ossl not found in ${NGTCP2_OUTPUT_DIRECTORY}. "
"ngtcp2 must be built with OpenSSL support.")
endif()

file(READ "${NGTCP2_OUTPUT_DIRECTORY}/include/ngtcp2/version.h" _NGTCP2_VERSION_H_CONTENT)
string(REGEX MATCH "#define NGTCP2_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)" _ "${_NGTCP2_VERSION_H_CONTENT}")
set(NGTCP2_VERSION "${CMAKE_MATCH_1}")

if(NOT NGTCP2_VERSION OR NGTCP2_VERSION VERSION_LESS NGTCP2_VERSION_MIN)
message(FATAL_ERROR
"[ngtcp2] error: need >= ${NGTCP2_VERSION_MIN}, found ${NGTCP2_VERSION} in ${NGTCP2_OUTPUT_DIRECTORY}")
endif()

message(STATUS "[ngtcp2] found (${NGTCP2_VERSION}): ${NGTCP2_OUTPUT_DIRECTORY}")

add_library(ngtcp2 INTERFACE)
target_include_directories(ngtcp2 SYSTEM INTERFACE "${NGTCP2_OUTPUT_DIRECTORY}/include")
target_link_libraries(ngtcp2 INTERFACE "${NGTCP2_CRYPTO_OSSL_LIBRARY}" "${NGTCP2_LIBRARY}" openssl)
49 changes: 49 additions & 0 deletions cmake/modules/unix/ngtcp2.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
if(WITH_NGTCP2)
set(NGTCP2_OUTPUT_DIRECTORY "${WITH_NGTCP2}")
else()
set(NGTCP2_DIRECTORY "${DEPENDENCIES_DIRECTORY}/ngtcp2")
set(NGTCP2_OUTPUT_DIRECTORY "${DEPENDENCIES_OUTPUT_DIRECTORY}/ngtcp2-dist")

if(NOT EXISTS "${NGTCP2_OUTPUT_DIRECTORY}/.done")
require_initialized_submodule("${NGTCP2_DIRECTORY}")
file(MAKE_DIRECTORY "${NGTCP2_OUTPUT_DIRECTORY}/logs")

message(STATUS "[ngtcp2] Configuring -> ${NGTCP2_OUTPUT_DIRECTORY}")
execute_process(
COMMAND autoreconf -i
WORKING_DIRECTORY "${NGTCP2_DIRECTORY}"
RESULT_VARIABLE _NGTCP2_RESULT
OUTPUT_FILE "${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-autoreconf.log"
ERROR_FILE "${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-autoreconf.log")
if(NOT _NGTCP2_RESULT EQUAL 0)
message(FATAL_ERROR "[ngtcp2] error: autoreconf failed -- see ${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-autoreconf.log")
endif()

# ngtcp2 finds OpenSSL through pkg-config; point it at the one we use.
execute_process(
COMMAND ${CMAKE_COMMAND} -E env
"PKG_CONFIG_PATH=${OPENSSL_OUTPUT_DIRECTORY}/lib64/pkgconfig:${OPENSSL_OUTPUT_DIRECTORY}/lib/pkgconfig"
./configure --prefix=${NGTCP2_OUTPUT_DIRECTORY} --enable-lib-only --with-openssl
WORKING_DIRECTORY "${NGTCP2_DIRECTORY}"
RESULT_VARIABLE _NGTCP2_RESULT
OUTPUT_FILE "${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-configure.log"
ERROR_FILE "${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-configure.log")
if(NOT _NGTCP2_RESULT EQUAL 0)
message(FATAL_ERROR "[ngtcp2] error: configure failed -- see ${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-configure.log")
endif()

message(STATUS "[ngtcp2] Building (${DEPENDENCIES_PARALLEL} jobs)")
execute_process(
COMMAND make -j${DEPENDENCIES_PARALLEL} install
WORKING_DIRECTORY "${NGTCP2_DIRECTORY}"
RESULT_VARIABLE _NGTCP2_RESULT
OUTPUT_FILE "${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-build.log"
ERROR_FILE "${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-build.log")
if(NOT _NGTCP2_RESULT EQUAL 0)
message(FATAL_ERROR "[ngtcp2] error: build failed -- see ${NGTCP2_OUTPUT_DIRECTORY}/logs/ngtcp2-build.log")
endif()

string(TIMESTAMP _NGTCP2_DONE_TIME "%Y-%b-%d_%H-%M-%S")
file(WRITE "${NGTCP2_OUTPUT_DIRECTORY}/.done" "${_NGTCP2_DONE_TIME}")
endif()
endif()
Loading
Loading