Merge branch 'master' into master

This commit is contained in:
Vladimir Serdyuk 2022-02-01 11:38:58 +03:00 committed by GitHub
commit 3b3ec13e13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 3441 additions and 929 deletions

View File

@ -60,8 +60,6 @@ jobs:
pkg-config \ pkg-config \
libtool libtool
echo 'PKG_CONFIG_PATH=/usr/local/opt/libressl/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig' >> $GITHUB_ENV echo 'PKG_CONFIG_PATH=/usr/local/opt/libressl/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig' >> $GITHUB_ENV
# This fixes infamous 'stdio.h not found' error.
echo 'SDKROOT='"$(xcrun --sdk macosx --show-sdk-path)" >> $GITHUB_ENV
- name: Setup clang (Linux) - name: Setup clang (Linux)
if: runner.os == 'Linux' && matrix.compiler == 'clang' if: runner.os == 'Linux' && matrix.compiler == 'clang'
run: | run: |
@ -87,28 +85,28 @@ jobs:
run: | run: |
git clone -b v0.4.0 https://github.com/libbpf/libbpf git clone -b v0.4.0 https://github.com/libbpf/libbpf
cd libbpf cd libbpf
export PREFIX=$PWD/build PREFIX=$PWD/build make -C src install
cd src
make install
EXTRA_AUTOTOOLS_OPTS="--with-libbpf" EXTRA_AUTOTOOLS_OPTS="--with-libbpf"
EXTRA_CMAKE_OPTS="-DWITH_LIBBPF=1"
echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV
echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV
- name: Build quictls/openssl v1.1.1 - name: Build quictls/openssl v1.1.1
if: matrix.http3 == 'http3' && matrix.openssl == 'openssl1' if: matrix.http3 == 'http3' && matrix.openssl == 'openssl1'
run: | run: |
git clone --depth 1 -b OpenSSL_1_1_1l+quic https://github.com/quictls/openssl git clone --depth 1 -b OpenSSL_1_1_1m+quic https://github.com/quictls/openssl
cd openssl cd openssl
./config enable-tls1_3 --prefix=$PWD/build ./config enable-tls1_3 --prefix=$PWD/build
make -j$(nproc) make -j$(nproc)
make install_sw make install_sw
- name: Build quictls/openssl v3.0.0 - name: Build quictls/openssl v3.0.x
if: matrix.http3 == 'http3' && matrix.openssl == 'openssl3' if: matrix.http3 == 'http3' && matrix.openssl == 'openssl3'
run: | run: |
unset CPPFLAGS unset CPPFLAGS
unset LDFLAGS unset LDFLAGS
git clone --depth 1 -b openssl-3.0.0+quic https://github.com/quictls/openssl git clone --depth 1 -b openssl-3.0.1+quic https://github.com/quictls/openssl
cd openssl cd openssl
./config enable-tls1_3 --prefix=$PWD/build --libdir=$PWD/build/lib ./config enable-tls1_3 --prefix=$PWD/build --libdir=$PWD/build/lib
make -j$(nproc) make -j$(nproc)
@ -118,6 +116,7 @@ jobs:
run: | run: |
git clone https://github.com/ngtcp2/nghttp3 git clone https://github.com/ngtcp2/nghttp3
cd nghttp3 cd nghttp3
git checkout 74a222fe0c89b7202bcdaf6ef27a232edffc85e3
autoreconf -i autoreconf -i
./configure --prefix=$PWD/build --enable-lib-only ./configure --prefix=$PWD/build --enable-lib-only
make -j$(nproc) check make -j$(nproc) check
@ -125,7 +124,7 @@ jobs:
- name: Build ngtcp2 - name: Build ngtcp2
if: matrix.http3 == 'http3' if: matrix.http3 == 'http3'
run: | run: |
git clone https://github.com/ngtcp2/ngtcp2 git clone --depth 1 -b v0.1.0 https://github.com/ngtcp2/ngtcp2
cd ngtcp2 cd ngtcp2
autoreconf -i autoreconf -i
./configure --prefix=$PWD/build --enable-lib-only PKG_CONFIG_PATH="../openssl/build/lib/pkgconfig" ./configure --prefix=$PWD/build --enable-lib-only PKG_CONFIG_PATH="../openssl/build/lib/pkgconfig"
@ -137,31 +136,41 @@ jobs:
PKG_CONFIG_PATH="$PWD/openssl/build/lib/pkgconfig:$PWD/nghttp3/build/lib/pkgconfig:$PWD/ngtcp2/build/lib/pkgconfig:$PWD/libbpf/build/lib64/pkgconfig:$PKG_CONFIG_PATH" PKG_CONFIG_PATH="$PWD/openssl/build/lib/pkgconfig:$PWD/nghttp3/build/lib/pkgconfig:$PWD/ngtcp2/build/lib/pkgconfig:$PWD/libbpf/build/lib64/pkgconfig:$PKG_CONFIG_PATH"
LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/openssl/build/lib -Wl,-rpath,$PWD/libbpf/build/lib64" LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/openssl/build/lib -Wl,-rpath,$PWD/libbpf/build/lib64"
EXTRA_AUTOTOOLS_OPTS="--enable-http3 $EXTRA_AUTOTOOLS_OPTS" EXTRA_AUTOTOOLS_OPTS="--enable-http3 $EXTRA_AUTOTOOLS_OPTS"
EXTRA_CMAKE_OPTS="-DENABLE_HTTP3=1 $EXTRA_CMAKE_OPTS"
echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV
echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV
echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV
echo 'EXTRA_CMAKE_OPTS=-DENABLE_HTTP3=ON' >> $GITHUB_ENV echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV
- name: Setup git submodules - name: Setup git submodules
run: | run: |
git submodule update --init git submodule update --init
- name: Configure autotools - name: Configure autotools
if: matrix.buildtool == 'autotools'
run: | run: |
autoreconf -i autoreconf -i
./configure --enable-werror --with-mruby $EXTRA_AUTOTOOLS_OPTS ./configure
- name: Configure cmake - name: Configure cmake
if: matrix.buildtool == 'cmake' if: matrix.buildtool == 'cmake'
run: | run: |
cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" . make dist
VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"')
tar xf nghttp2-$VERSION.tar.gz
cd nghttp2-$VERSION
echo 'NGHTTP2_CMAKE_DIR='"$PWD" >> $GITHUB_ENV
# This fixes infamous 'stdio.h not found' error.
echo 'SDKROOT='"$(xcrun --sdk macosx --show-sdk-path)" >> $GITHUB_ENV
cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 -DENABLE_APP=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" .
- name: Build nghttp2 with autotools - name: Build nghttp2 with autotools
if: matrix.buildtool == 'autotools' if: matrix.buildtool == 'autotools'
run: | run: |
make distcheck \ make distcheck \
DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\"" DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --with-libev --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\""
- name: Build nghttp2 with cmake - name: Build nghttp2 with cmake
if: matrix.buildtool == 'cmake' if: matrix.buildtool == 'cmake'
run: | run: |
cd $NGHTTP2_CMAKE_DIR
make make
make check make check
- name: Integration test - name: Integration test
@ -169,5 +178,5 @@ jobs:
# artifacts. # artifacts.
if: matrix.buildtool == 'cmake' if: matrix.buildtool == 'cmake'
run: | run: |
cd integration-tests cd $NGHTTP2_CMAKE_DIR/integration-tests
make itprep it make itprep it

View File

@ -19,6 +19,7 @@ Alek Storm
Alex Nalivko Alex Nalivko
Alexandros Konstantinakis-Karmis Alexandros Konstantinakis-Karmis
Alexis La Goutte Alexis La Goutte
Amir Livneh
Amir Pakdel Amir Pakdel
Anders Bakken Anders Bakken
Andreas Pohl Andreas Pohl
@ -34,11 +35,13 @@ Bernard Spil
Brendan Heinonen Brendan Heinonen
Brian Card Brian Card
Brian Suh Brian Suh
Daniel Bevenius
Daniel Evers Daniel Evers
Daniel Stenberg Daniel Stenberg
Dave Reisner Dave Reisner
David Beitey David Beitey
David Weekly David Weekly
Dmitri Tikhonov
Dmitriy Vetutnev Dmitriy Vetutnev
Don Don
Dylan Plecki Dylan Plecki
@ -48,9 +51,12 @@ Fabian Wiesel
Gabi Davar Gabi Davar
Gaël PORTAY Gaël PORTAY
Geoff Hill Geoff Hill
George Liu
Gitai Gitai
Google Inc. Google Inc.
Hajime Fujita
Jacky Tian Jacky Tian
Jacky_Yin
Jacob Champion Jacob Champion
James M Snell James M Snell
Jan Kundrát Jan Kundrát
@ -76,6 +82,7 @@ MATSUMOTO Ryosuke
Marc Bachmann Marc Bachmann
Matt Rudary Matt Rudary
Matt Way Matt Way
Michael Kaufmann
Mike Conlen Mike Conlen
Mike Frysinger Mike Frysinger
Mike Lothian Mike Lothian
@ -127,6 +134,7 @@ es
fangdingjun fangdingjun
jwchoi jwchoi
kumagi kumagi
lhuang04
lstefani lstefani
makovich makovich
mod-h2-dev mod-h2-dev

View File

@ -24,13 +24,13 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0)
# XXX using 1.8.90 instead of 1.9.0-DEV # XXX using 1.8.90 instead of 1.9.0-DEV
project(nghttp2 VERSION 1.44.90) project(nghttp2 VERSION 1.46.90)
# See versioning rule: # See versioning rule:
# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html # https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
set(LT_CURRENT 34) set(LT_CURRENT 35)
set(LT_REVISION 2) set(LT_REVISION 1)
set(LT_AGE 20) set(LT_AGE 21)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
include(Version) include(Version)
@ -63,8 +63,16 @@ find_package(Libcares 1.7.5)
find_package(ZLIB 1.2.3) find_package(ZLIB 1.2.3)
find_package(Libngtcp2 0.0.0) find_package(Libngtcp2 0.0.0)
find_package(Libngtcp2_crypto_openssl 0.0.0) find_package(Libngtcp2_crypto_openssl 0.0.0)
if(LIBNGTCP2_CRYPTO_OPENSSL_FOUND)
set(HAVE_LIBNGTCP2_CRYPTO_OPENSSL 1)
endif()
find_package(Libnghttp3 0.0.0) find_package(Libnghttp3 0.0.0)
if(WITH_LIBBPF)
find_package(Libbpf 0.4.0) find_package(Libbpf 0.4.0)
if(NOT LIBBPF_FOUND)
message(FATAL_ERROR "libbpf was requested (WITH_LIBBPF=1) but not found.")
endif()
endif()
if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND) if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND)
set(ENABLE_APP_DEFAULT ON) set(ENABLE_APP_DEFAULT ON)
else() else()
@ -171,7 +179,7 @@ endif()
# case "$host" in # case "$host" in
# *android*) # *android*)
# android_build=yes # android_build=yes
# # android does not need -pthread, but needs followng 3 libs for C++ # # android does not need -pthread, but needs following 3 libs for C++
# APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic -lsupc++" # APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic -lsupc++"
# dl: openssl requires libdl when it is statically linked. # dl: openssl requires libdl when it is statically linked.

View File

@ -23,9 +23,8 @@ option(WITH_LIBXML2 "Use libxml2"
${WITH_LIBXML2_DEFAULT}) ${WITH_LIBXML2_DEFAULT})
option(WITH_JEMALLOC "Use jemalloc" option(WITH_JEMALLOC "Use jemalloc"
${WITH_JEMALLOC_DEFAULT}) ${WITH_JEMALLOC_DEFAULT})
option(WITH_SPDYLAY "Use spdylay"
${WITH_SPDYLAY_DEFAULT})
option(WITH_MRUBY "Use mruby") option(WITH_MRUBY "Use mruby")
option(WITH_NEVERBLEED "Use neverbleed") option(WITH_NEVERBLEED "Use neverbleed")
option(WITH_LIBBPF "Use libbpf")
# vim: ft=cmake: # vim: ft=cmake:

View File

@ -46,16 +46,20 @@ EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-make \
cmake/FindLibevent.cmake \ cmake/FindLibevent.cmake \
cmake/FindJansson.cmake \ cmake/FindJansson.cmake \
cmake/FindLibcares.cmake \ cmake/FindLibcares.cmake \
cmake/FindSystemd.cmake cmake/FindSystemd.cmake \
cmake/FindLibbpf.cmake \
cmake/FindLibnghttp3.cmake \
cmake/FindLibngtcp2.cmake \
cmake/FindLibngtcp2_crypto_openssl.cmake
.PHONY: clang-format .PHONY: clang-format
# Format source files using clang-format. Don't format source files # Format source files using clang-format. Don't format source files
# under third-party directory since we are not responsible for thier # under third-party directory since we are not responsible for their
# coding style. # coding style.
clang-format: clang-format:
CLANGFORMAT=`git config --get clangformat.binary`; \ CLANGFORMAT=`git config --get clangformat.binary`; \
test -z $${CLANGFORMAT} && CLANGFORMAT="clang-format"; \ test -z $${CLANGFORMAT} && CLANGFORMAT="clang-format"; \
$${CLANGFORMAT} -i lib/*.{c,h} lib/includes/nghttp2/*.h \ $${CLANGFORMAT} -i lib/*.{c,h} lib/includes/nghttp2/*.h \
src/*.{c,cc,h} src/includes/nghttp2/*.h examples/*.{c,cc} \ src/*.{c,cc,h} src/includes/nghttp2/*.h examples/*.{c,cc} \
tests/*.{c,h} tests/*.{c,h} bpf/*.c

View File

@ -32,12 +32,14 @@ Public Test Server
The following endpoints are available to try out our nghttp2 The following endpoints are available to try out our nghttp2
implementation. implementation.
* https://nghttp2.org/ (TLS + ALPN/NPN) * https://nghttp2.org/ (TLS + ALPN/NPN and HTTP/3)
This endpoint supports ``h2``, ``h2-16``, ``h2-14``, and This endpoint supports ``h2``, ``h2-16``, ``h2-14``, and
``http/1.1`` via ALPN/NPN and requires TLSv1.2 for HTTP/2 ``http/1.1`` via ALPN/NPN and requires TLSv1.2 for HTTP/2
connection. connection.
It also supports HTTP/3.
* http://nghttp2.org/ (HTTP Upgrade and HTTP/2 Direct) * http://nghttp2.org/ (HTTP Upgrade and HTTP/2 Direct)
``h2c`` and ``http/1.1``. ``h2c`` and ``http/1.1``.
@ -149,16 +151,24 @@ To enable the experimental HTTP/3 support for h2load and nghttpx, the
following libraries are required: following libraries are required:
* `OpenSSL with QUIC support * `OpenSSL with QUIC support
<https://github.com/quictls/openssl/tree/OpenSSL_1_1_1k+quic>`_ <https://github.com/quictls/openssl/tree/OpenSSL_1_1_1m+quic>`_; or
`BoringSSL <https://boringssl.googlesource.com/boringssl/>`_ (commit
f6ef1c560ae5af51e2df5d8d2175bed207b28b8f)
* `ngtcp2 <https://github.com/ngtcp2/ngtcp2>`_ * `ngtcp2 <https://github.com/ngtcp2/ngtcp2>`_
* `nghttp3 <https://github.com/ngtcp2/nghttp3>`_ * `nghttp3 <https://github.com/ngtcp2/nghttp3>`_
Use ``--enable-http3`` configure option to enable HTTP/3 feature for
h2load and nghttpx.
In order to build optional eBPF program to direct an incoming QUIC UDP In order to build optional eBPF program to direct an incoming QUIC UDP
datagram to a correct socket for nghttpx, the following libraries are datagram to a correct socket for nghttpx, the following libraries are
required: required:
* libbpf-dev >= 0.4.0 * libbpf-dev >= 0.4.0
Use ``--with-libbpf`` configure option to build eBPF program.
libelf-dev is needed to build libbpf.
For Ubuntu 20.04, you can build libbpf from `the source code For Ubuntu 20.04, you can build libbpf from `the source code
<https://github.com/libbpf/libbpf/releases/tag/v0.4.0>`_. nghttpx <https://github.com/libbpf/libbpf/releases/tag/v0.4.0>`_. nghttpx
requires eBPF program for reloading its configuration and hot swapping requires eBPF program for reloading its configuration and hot swapping
@ -326,6 +336,89 @@ The generated documents will not be installed with ``make install``.
The online documentation is available at The online documentation is available at
https://nghttp2.org/documentation/ https://nghttp2.org/documentation/
Build HTTP/3 enabled h2load and nghttpx
---------------------------------------
To build h2load and nghttpx with HTTP/3 feature enabled, run the
configure script with ``--enable-http3``.
For nghttpx to reload configurations and swapping its executable while
gracefully terminating old worker processes, eBPF is required. Run
the configure script with ``--enable-http3 --with-libbpf`` to build
eBPF program. The QUIC keying material must be set with
``--frontend-quic-secret-file`` in order to keep the existing
connections alive during reload.
The detailed steps to build HTTP/3 enabled h2load and nghttpx follow.
Build custom OpenSSL:
.. code-block:: text
$ git clone --depth 1 -b OpenSSL_1_1_1m+quic https://github.com/quictls/openssl
$ cd openssl
$ ./config --prefix=$PWD/build --openssldir=/etc/ssl
$ make -j$(nproc)
$ make install_sw
$ cd ..
Build nghttp3:
.. code-block:: text
$ git clone https://github.com/ngtcp2/nghttp3
$ cd nghttp3
$ git checkout 74a222fe0c89b7202bcdaf6ef27a232edffc85e3
$ autoreconf -i
$ ./configure --prefix=$PWD/build --enable-lib-only
$ make -j$(nproc)
$ make install
$ cd ..
Build ngtcp2:
.. code-block:: text
$ git clone --depth 1 -b v0.1.0 https://github.com/ngtcp2/ngtcp2
$ cd ngtcp2
$ autoreconf -i
$ ./configure --prefix=$PWD/build --enable-lib-only \
PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig"
$ make -j$(nproc)
$ make install
$ cd ..
If your Linux distribution does not have libbpf-dev >= 0.4.0, build
from source:
.. code-block:: text
$ git clone --depth 1 -b v0.4.0 https://github.com/libbpf/libbpf
$ cd libbpf
$ PREFIX=$PWD/build make -C src install
$ cd ..
Build nghttp2:
.. code-block:: text
$ git clone https://github.com/nghttp2/nghttp2
$ cd nghttp2
$ git submodule update --init
$ autoreconf -i
$ ./configure --with-mruby --with-neverbleed --enable-http3 --with-libbpf \
--disable-python-bindings \
CC=clang-12 CXX=clang++-12 \
PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig:$PWD/../ngtcp2/build/lib/pkgconfig:$PWD/../libbpf/build/lib64/pkgconfig" \
LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/../openssl/build/lib -Wl,-rpath,$PWD/../libbpf/build/lib64"
$ make -j$(nproc)
The eBPF program ``reuseport_kern.o`` should be found under bpf
directory. Pass ``--quic-bpf-program-file=bpf/reuseport_kern.o``
option to nghttpx to load it. See also `HTTP/3 section in nghttpx -
HTTP/2 proxy - HOW-TO
<https://nghttp2.org/documentation/nghttpx-howto.html#http-3>`_.
Unit tests Unit tests
---------- ----------
@ -753,7 +846,7 @@ information. Here is sample output from ``nghttpd``:
nghttpx - proxy nghttpx - proxy
+++++++++++++++ +++++++++++++++
``nghttpx`` is a multi-threaded reverse proxy for HTTP/2, and ``nghttpx`` is a multi-threaded reverse proxy for HTTP/3, HTTP/2, and
HTTP/1.1, and powers http://nghttp2.org and supports HTTP/2 server HTTP/1.1, and powers http://nghttp2.org and supports HTTP/2 server
push. push.
@ -774,16 +867,16 @@ ticket keys among multiple ``nghttpx`` instances via memcached.
``nghttpx`` has 2 operation modes: ``nghttpx`` has 2 operation modes:
================== ================ ================ ============= ================== ======================== ================ =============
Mode option Frontend Backend Note Mode option Frontend Backend Note
================== ================ ================ ============= ================== ======================== ================ =============
default mode HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy default mode HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy
``--http2-proxy`` HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Forward proxy ``--http2-proxy`` HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Forward proxy
================== ================ ================ ============= ================== ======================== ================ =============
The interesting mode at the moment is the default mode. It works like The interesting mode at the moment is the default mode. It works like
a reverse proxy and listens for HTTP/2, and HTTP/1.1 and can be a reverse proxy and listens for HTTP/3, HTTP/2, and HTTP/1.1 and can
deployed as a SSL/TLS terminator for existing web server. be deployed as a SSL/TLS terminator for existing web server.
In all modes, the frontend connections are encrypted by SSL/TLS by In all modes, the frontend connections are encrypted by SSL/TLS by
default. To disable encryption, use the ``no-tls`` keyword in default. To disable encryption, use the ``no-tls`` keyword in
@ -801,7 +894,7 @@ server:
.. code-block:: text .. code-block:: text
Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server
[reverse proxy] [reverse proxy]
With the ``--http2-proxy`` option, it works as forward proxy, and it With the ``--http2-proxy`` option, it works as forward proxy, and it
@ -809,7 +902,7 @@ is so called secure HTTP/2 proxy:
.. code-block:: text .. code-block:: text
Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy
[secure proxy] (e.g., Squid, ATS) [secure proxy] (e.g., Squid, ATS)
The ``Client`` in the above example needs to be configured to use The ``Client`` in the above example needs to be configured to use
@ -842,7 +935,7 @@ proxy through an HTTP proxy:
.. code-block:: text .. code-block:: text
Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) -- Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) --
--===================---> HTTP/2 Proxy --===================---> HTTP/2 Proxy
(HTTP proxy tunnel) (e.g., nghttpx -s) (HTTP proxy tunnel) (e.g., nghttpx -s)
@ -850,8 +943,8 @@ proxy through an HTTP proxy:
Benchmarking tool Benchmarking tool
----------------- -----------------
The ``h2load`` program is a benchmarking tool for HTTP/2. The UI of The ``h2load`` program is a benchmarking tool for HTTP/3, HTTP/2, and
``h2load`` is heavily inspired by ``weighttp`` HTTP/1.1. The UI of ``h2load`` is heavily inspired by ``weighttp``
(https://github.com/lighttpd/weighttp). The typical usage is as (https://github.com/lighttpd/weighttp). The typical usage is as
follows: follows:
@ -902,17 +995,6 @@ like so:
$ h2load --npn-list h3 https://127.0.0.1:4433 $ h2load --npn-list h3 https://127.0.0.1:4433
HTTP/3
------
To build h2load and nghttpx with HTTP/3 feature enabled, run the
configure script with ``--enable-http3``.
For nghttpx to reload configurations and swapping its executable while
gracefully terminating old worker processes, eBPF is required. Run
the configure script with ``--enable-http3 --with-libbpf`` to build
eBPF program.
HPACK tools HPACK tools
----------- -----------

View File

@ -21,9 +21,9 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
if HAVE_LIBBPF EXTRA_DIST = CMakeLists.txt reuseport_kern.c
EXTRA_DIST = reuseport_kern.c if HAVE_LIBBPF
bpf_pkglibdir = $(pkglibdir) bpf_pkglibdir = $(pkglibdir)
bpf_pkglib_DATA = reuseport_kern.o bpf_pkglib_DATA = reuseport_kern.o

View File

@ -38,6 +38,320 @@
* how to install kernel header files. * how to install kernel header files.
*/ */
/* AES_CBC_decrypt_buffer: https://github.com/kokke/tiny-AES-c
License is Public Domain. Commit hash:
12e7744b4919e9d55de75b7ab566326a1c8e7a67 */
#define AES_BLOCKLEN \
16 /* Block length in bytes - AES is 128b block \
only */
#define AES_KEYLEN 16 /* Key length in bytes */
#define AES_keyExpSize 176
struct AES_ctx {
__u8 RoundKey[AES_keyExpSize];
};
/* The number of columns comprising a state in AES. This is a constant
in AES. Value=4 */
#define Nb 4
#define Nk 4 /* The number of 32 bit words in a key. */
#define Nr 10 /* The number of rounds in AES Cipher. */
/* state - array holding the intermediate results during
decryption. */
typedef __u8 state_t[4][4];
/* The lookup-tables are marked const so they can be placed in
read-only storage instead of RAM The numbers below can be computed
dynamically trading ROM for RAM - This can be useful in (embedded)
bootloader applications, where ROM is often limited. */
static const __u8 sbox[256] = {
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26,
0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed,
0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f,
0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14,
0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f,
0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
0xb0, 0x54, 0xbb, 0x16};
static const __u8 rsbox[256] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e,
0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32,
0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50,
0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05,
0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41,
0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8,
0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59,
0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d,
0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63,
0x55, 0x21, 0x0c, 0x7d};
/* The round constant word array, Rcon[i], contains the values given
by x to the power (i-1) being powers of x (x is denoted as {02}) in
the field GF(2^8) */
static const __u8 Rcon[11] = {0x8d, 0x01, 0x02, 0x04, 0x08, 0x10,
0x20, 0x40, 0x80, 0x1b, 0x36};
#define getSBoxValue(num) (sbox[(num)])
/* This function produces Nb(Nr+1) round keys. The round keys are used
in each round to decrypt the states. */
static void KeyExpansion(__u8 *RoundKey, const __u8 *Key) {
unsigned i, j, k;
__u8 tempa[4]; /* Used for the column/row operations */
/* The first round key is the key itself. */
for (i = 0; i < Nk; ++i) {
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
}
/* All other round keys are found from the previous round keys. */
for (i = Nk; i < Nb * (Nr + 1); ++i) {
{
k = (i - 1) * 4;
tempa[0] = RoundKey[k + 0];
tempa[1] = RoundKey[k + 1];
tempa[2] = RoundKey[k + 2];
tempa[3] = RoundKey[k + 3];
}
if (i % Nk == 0) {
/* This function shifts the 4 bytes in a word to the left once.
[a0,a1,a2,a3] becomes [a1,a2,a3,a0] */
/* Function RotWord() */
{
const __u8 u8tmp = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = u8tmp;
}
/* SubWord() is a function that takes a four-byte input word and
applies the S-box to each of the four bytes to produce an
output word. */
/* Function Subword() */
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
tempa[0] = tempa[0] ^ Rcon[i / Nk];
}
j = i * 4;
k = (i - Nk) * 4;
RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
}
}
static void AES_init_ctx(struct AES_ctx *ctx, const __u8 *key) {
KeyExpansion(ctx->RoundKey, key);
}
/* This function adds the round key to state. The round key is added
to the state by an XOR function. */
static void AddRoundKey(__u8 round, state_t *state, const __u8 *RoundKey) {
__u8 i, j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
(*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j];
}
}
}
static __u8 xtime(__u8 x) { return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); }
#define Multiply(x, y) \
(((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ \
((y >> 2 & 1) * xtime(xtime(x))) ^ \
((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ \
((y >> 4 & 1) * xtime(xtime(xtime(xtime(x))))))
#define getSBoxInvert(num) (rsbox[(num)])
/* MixColumns function mixes the columns of the state matrix. The
method used to multiply may be difficult to understand for the
inexperienced. Please use the references to gain more
information. */
static void InvMixColumns(state_t *state) {
int i;
__u8 a, b, c, d;
for (i = 0; i < 4; ++i) {
a = (*state)[i][0];
b = (*state)[i][1];
c = (*state)[i][2];
d = (*state)[i][3];
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^
Multiply(d, 0x09);
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^
Multiply(d, 0x0d);
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^
Multiply(d, 0x0b);
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^
Multiply(d, 0x0e);
}
}
extern __u32 LINUX_KERNEL_VERSION __kconfig;
/* The SubBytes Function Substitutes the values in the state matrix
with values in an S-box. */
static void InvSubBytes(state_t *state) {
__u8 i, j;
if (LINUX_KERNEL_VERSION < KERNEL_VERSION(5, 10, 0)) {
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
/* Ubuntu 20.04 LTS kernel 5.4.0 needs this workaround
otherwise "math between map_value pointer and register with
unbounded min value is not allowed". 5.10.0 is a kernel
version that works but it might not be the minimum
version. */
__u8 k = (*state)[j][i];
(*state)[j][i] = k ? getSBoxInvert(k) : getSBoxInvert(0);
}
}
} else {
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
}
}
}
}
static void InvShiftRows(state_t *state) {
__u8 temp;
/* Rotate first row 1 columns to right */
temp = (*state)[3][1];
(*state)[3][1] = (*state)[2][1];
(*state)[2][1] = (*state)[1][1];
(*state)[1][1] = (*state)[0][1];
(*state)[0][1] = temp;
/* Rotate second row 2 columns to right */
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
/* Rotate third row 3 columns to right */
temp = (*state)[0][3];
(*state)[0][3] = (*state)[1][3];
(*state)[1][3] = (*state)[2][3];
(*state)[2][3] = (*state)[3][3];
(*state)[3][3] = temp;
}
static void InvCipher(state_t *state, const __u8 *RoundKey) {
/* Add the First round key to the state before starting the
rounds. */
AddRoundKey(Nr, state, RoundKey);
/* There will be Nr rounds. The first Nr-1 rounds are identical.
These Nr rounds are executed in the loop below. Last one without
InvMixColumn() */
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 1, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 2, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 3, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 4, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 5, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 6, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 7, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 8, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 9, state, RoundKey);
InvMixColumns(state);
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(Nr - 10, state, RoundKey);
}
static void AES_ECB_decrypt(const struct AES_ctx *ctx, __u8 *buf) {
/* The next function call decrypts the PlainText with the Key using
AES algorithm. */
InvCipher((state_t *)buf, ctx->RoundKey);
}
/* rol32: From linux kernel source code */ /* rol32: From linux kernel source code */
/** /**
@ -125,13 +439,13 @@ struct bpf_map_def SEC("maps") reuseport_array = {
struct bpf_map_def SEC("maps") sk_info = { struct bpf_map_def SEC("maps") sk_info = {
.type = BPF_MAP_TYPE_ARRAY, .type = BPF_MAP_TYPE_ARRAY,
.max_entries = 1, .max_entries = 3,
.key_size = sizeof(__u32), .key_size = sizeof(__u32),
.value_size = sizeof(__u32), .value_size = sizeof(__u64),
}; };
typedef struct quic_hd { typedef struct quic_hd {
const __u8 *dcid; __u8 *dcid;
__u32 dcidlen; __u32 dcidlen;
__u32 dcid_offset; __u32 dcid_offset;
__u8 type; __u8 type;
@ -141,6 +455,7 @@ typedef struct quic_hd {
#define MAX_DCIDLEN 20 #define MAX_DCIDLEN 20
#define MIN_DCIDLEN 8 #define MIN_DCIDLEN 8
#define CID_PREFIXLEN 8 #define CID_PREFIXLEN 8
#define CID_PREFIX_OFFSET 1
enum { enum {
NGTCP2_PKT_INITIAL = 0x0, NGTCP2_PKT_INITIAL = 0x0,
@ -149,9 +464,8 @@ enum {
NGTCP2_PKT_SHORT = 0x40, NGTCP2_PKT_SHORT = 0x40,
}; };
static inline int parse_quic(quic_hd *qhd, const __u8 *data, static inline int parse_quic(quic_hd *qhd, __u8 *data, __u8 *data_end) {
const __u8 *data_end) { __u8 *p;
const __u8 *p;
__u64 dcidlen; __u64 dcidlen;
if (*data & 0x80) { if (*data & 0x80) {
@ -192,7 +506,7 @@ static __u32 hash(const __u8 *data, __u32 datalen, __u32 initval) {
static __u32 sk_index_from_dcid(const quic_hd *qhd, static __u32 sk_index_from_dcid(const quic_hd *qhd,
const struct sk_reuseport_md *reuse_md, const struct sk_reuseport_md *reuse_md,
__u32 num_socks) { __u64 num_socks) {
__u32 len = qhd->dcidlen; __u32 len = qhd->dcidlen;
__u32 h = reuse_md->hash; __u32 h = reuse_md->hash;
__u8 hbuf[8]; __u8 hbuf[8];
@ -259,11 +573,14 @@ static __u32 sk_index_from_dcid(const quic_hd *qhd,
SEC("sk_reuseport") SEC("sk_reuseport")
int select_reuseport(struct sk_reuseport_md *reuse_md) { int select_reuseport(struct sk_reuseport_md *reuse_md) {
__u32 sk_index, *psk_index; __u32 sk_index, *psk_index;
__u32 *pnum_socks; __u64 *pnum_socks, *pkey;
__u32 zero = 0; __u32 zero = 0, key_high_idx = 1, key_low_idx = 2;
int rv; int rv;
quic_hd qhd; quic_hd qhd;
__u8 qpktbuf[6 + MAX_DCIDLEN]; __u8 qpktbuf[6 + MAX_DCIDLEN];
struct AES_ctx aes_ctx;
__u8 key[AES_KEYLEN];
__u8 *cid_prefix;
if (bpf_skb_load_bytes(reuse_md, sizeof(struct udphdr), qpktbuf, if (bpf_skb_load_bytes(reuse_md, sizeof(struct udphdr), qpktbuf,
sizeof(qpktbuf)) != 0) { sizeof(qpktbuf)) != 0) {
@ -275,16 +592,35 @@ int select_reuseport(struct sk_reuseport_md *reuse_md) {
return SK_DROP; return SK_DROP;
} }
pkey = bpf_map_lookup_elem(&sk_info, &key_high_idx);
if (pkey == NULL) {
return SK_DROP;
}
__builtin_memcpy(key, pkey, sizeof(*pkey));
pkey = bpf_map_lookup_elem(&sk_info, &key_low_idx);
if (pkey == NULL) {
return SK_DROP;
}
__builtin_memcpy(key + sizeof(*pkey), pkey, sizeof(*pkey));
rv = parse_quic(&qhd, qpktbuf, qpktbuf + sizeof(qpktbuf)); rv = parse_quic(&qhd, qpktbuf, qpktbuf + sizeof(qpktbuf));
if (rv != 0) { if (rv != 0) {
return SK_DROP; return SK_DROP;
} }
AES_init_ctx(&aes_ctx, key);
switch (qhd.type) { switch (qhd.type) {
case NGTCP2_PKT_INITIAL: case NGTCP2_PKT_INITIAL:
case NGTCP2_PKT_0RTT: case NGTCP2_PKT_0RTT:
if (qhd.dcidlen == SV_DCIDLEN) { if (qhd.dcidlen == SV_DCIDLEN) {
psk_index = bpf_map_lookup_elem(&cid_prefix_map, qhd.dcid); cid_prefix = qhd.dcid + CID_PREFIX_OFFSET;
AES_ECB_decrypt(&aes_ctx, cid_prefix);
psk_index = bpf_map_lookup_elem(&cid_prefix_map, cid_prefix);
if (psk_index != NULL) { if (psk_index != NULL) {
sk_index = *psk_index; sk_index = *psk_index;
@ -301,7 +637,10 @@ int select_reuseport(struct sk_reuseport_md *reuse_md) {
return SK_DROP; return SK_DROP;
} }
psk_index = bpf_map_lookup_elem(&cid_prefix_map, qhd.dcid); cid_prefix = qhd.dcid + CID_PREFIX_OFFSET;
AES_ECB_decrypt(&aes_ctx, cid_prefix);
psk_index = bpf_map_lookup_elem(&cid_prefix_map, cid_prefix);
if (psk_index == NULL) { if (psk_index == NULL) {
sk_index = sk_index_from_dcid(&qhd, reuse_md, *pnum_socks); sk_index = sk_index_from_dcid(&qhd, reuse_md, *pnum_socks);

View File

@ -87,3 +87,6 @@
/* Define to 1 if you have enum bpf_stats_type in linux/bpf.h. */ /* Define to 1 if you have enum bpf_stats_type in linux/bpf.h. */
#cmakedefine HAVE_BPF_STATS_TYPE 1 #cmakedefine HAVE_BPF_STATS_TYPE 1
/* Define to 1 if you have `libngtcp2_crypto_openssl` library. */
#cmakedefine HAVE_LIBNGTCP2_CRYPTO_OPENSSL

View File

@ -22,10 +22,10 @@ dnl OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
dnl WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dnl WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
dnl Do not change user variables! dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html dnl https://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61) AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.45.0-DEV], [t-tujikawa@users.sourceforge.net]) AC_INIT([nghttp2], [1.47.0-DEV], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.]) AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
@ -43,10 +43,10 @@ AM_INIT_AUTOMAKE([subdir-objects])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule: dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html dnl https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 34) AC_SUBST(LT_CURRENT, 35)
AC_SUBST(LT_REVISION, 2) AC_SUBST(LT_REVISION, 1)
AC_SUBST(LT_AGE, 20) AC_SUBST(LT_AGE, 21)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"` minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`
@ -220,6 +220,11 @@ PKG_PROG_PKG_CONFIG([0.20])
AM_PATH_PYTHON([3.8],, [:]) AM_PATH_PYTHON([3.8],, [:])
if test "x$request_python_bindings" = "xyes" &&
test "x$PYTHON" = "x:"; then
AC_MSG_ERROR([python was requested (enable-python-bindings) but not found])
fi
if [test "x$request_lib_only" = "xyes"]; then if [test "x$request_lib_only" = "xyes"]; then
request_app=no request_app=no
request_hpack_tools=no request_hpack_tools=no
@ -227,8 +232,10 @@ if [test "x$request_lib_only" = "xyes"]; then
request_python_bindings=no request_python_bindings=no
fi fi
if [test "x$request_python_bindings" != "xno"]; then if test "x$request_python_bindings" != "xno" &&
AX_PYTHON_DEVEL([>= '3.8']) test "x$PYTHON" != "x:"; then
# version check is broken
AX_PYTHON_DEVEL()
fi fi
if test "x${cython_path}" = "x"; then if test "x${cython_path}" = "x"; then
@ -342,7 +349,7 @@ APPLDFLAGS=
case "$host_os" in case "$host_os" in
*android*) *android*)
android_build=yes android_build=yes
# android does not need -pthread, but needs followng 3 libs for C++ # android does not need -pthread, but needs following 3 libs for C++
APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic -lsupc++" APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic -lsupc++"
;; ;;
*) *)
@ -467,6 +474,7 @@ if test "x${request_openssl}" != "xno"; then
CFLAGS="$OPENSSL_CFLAGS $CFLAGS" CFLAGS="$OPENSSL_CFLAGS $CFLAGS"
LIBS="$OPENSSL_LIBS $LIBS" LIBS="$OPENSSL_LIBS $LIBS"
# quictls/openssl has SSL_is_quic.
have_ssl_is_quic=no have_ssl_is_quic=no
AC_MSG_CHECKING([for SSL_is_quic]) AC_MSG_CHECKING([for SSL_is_quic])
AC_LINK_IFELSE([AC_LANG_PROGRAM([[ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
@ -478,6 +486,17 @@ if test "x${request_openssl}" != "xno"; then
[AC_MSG_RESULT([yes]); have_ssl_is_quic=yes], [AC_MSG_RESULT([yes]); have_ssl_is_quic=yes],
[AC_MSG_RESULT([no]); have_ssl_is_quic=no]) [AC_MSG_RESULT([no]); have_ssl_is_quic=no])
# boringssl has SSL_set_quic_early_data_context.
AC_MSG_CHECKING([for SSL_set_quic_early_data_context])
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
#include <openssl/ssl.h>
]], [[
SSL *ssl = NULL;
SSL_set_quic_early_data_context(ssl, NULL, 0);
]])],
[AC_MSG_RESULT([yes]); have_boringssl_quic=yes],
[AC_MSG_RESULT([no]); have_boringssl_quic=no])
CFLAGS="$save_CFLAGS" CFLAGS="$save_CFLAGS"
LIBS="$save_LIBS" LIBS="$save_LIBS"
fi fi
@ -506,7 +525,7 @@ fi
# ngtcp2 (for src) # ngtcp2 (for src)
have_libngtcp2=no have_libngtcp2=no
if test "x${request_libngtcp2}" != "xno"; then if test "x${request_libngtcp2}" != "xno"; then
PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 0.0.0], [have_libngtcp2=yes], PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 0.1.0], [have_libngtcp2=yes],
[have_libngtcp2=no]) [have_libngtcp2=no])
if test "x${have_libngtcp2}" = "xno"; then if test "x${have_libngtcp2}" = "xno"; then
AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS) AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS)
@ -520,25 +539,52 @@ fi
# ngtcp2_crypto_openssl (for src) # ngtcp2_crypto_openssl (for src)
have_libngtcp2_crypto_openssl=no have_libngtcp2_crypto_openssl=no
if test "x${request_libngtcp2}" != "xno"; then if test "x${have_ssl_is_quic}" = "xyes" &&
test "x${request_libngtcp2}" != "xno"; then
PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_OPENSSL], PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_OPENSSL],
[libngtcp2_crypto_openssl >= 0.0.0], [libngtcp2_crypto_openssl >= 0.0.0],
[have_libngtcp2_crypto_openssl=yes], [have_libngtcp2_crypto_openssl=yes],
[have_libngtcp2_crypto_openssl=no]) [have_libngtcp2_crypto_openssl=no])
if test "x${have_libngtcp2_crypto_openssl}" = "xno"; then if test "x${have_libngtcp2_crypto_openssl}" = "xno"; then
AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_OPENSSL_PKG_ERRORS) AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_OPENSSL_PKG_ERRORS)
else
AC_DEFINE([HAVE_LIBNGTCP2_CRYPTO_OPENSSL], [1],
[Define to 1 if you have `libngtcp2_crypto_openssl` library.])
fi fi
fi fi
if test "x${request_libngtcp2}" = "xyes" && if test "x${have_ssl_is_quic}" = "xyes" &&
test "x${request_libngtcp2}" = "xyes" &&
test "x${have_libngtcp2_crypto_openssl}" != "xyes"; then test "x${have_libngtcp2_crypto_openssl}" != "xyes"; then
AC_MSG_ERROR([libngtcp2_crypto_openssl was requested (--with-libngtcp2) but not found]) AC_MSG_ERROR([libngtcp2_crypto_openssl was requested (--with-libngtcp2) but not found])
fi fi
# ngtcp2_crypto_boringssl (for src)
have_libngtcp2_crypto_boringssl=no
if test "x${have_boringssl_quic}" = "xyes" &&
test "x${request_libngtcp2}" != "xno"; then
PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_BORINGSSL],
[libngtcp2_crypto_boringssl >= 0.0.0],
[have_libngtcp2_crypto_boringssl=yes],
[have_libngtcp2_crypto_boringssl=no])
if test "x${have_libngtcp2_crypto_boringssl}" = "xno"; then
AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_BORINGSSL_PKG_ERRORS)
else
AC_DEFINE([HAVE_LIBNGTCP2_CRYPTO_BORINGSSL], [1],
[Define to 1 if you have `libngtcp2_crypto_boringssl` library.])
fi
fi
if test "x${have_boringssl_quic}" = "xyes" &&
test "x${request_libngtcp2}" = "xyes" &&
test "x${have_libngtcp2_crypto_boringssl}" != "xyes"; then
AC_MSG_ERROR([libngtcp2_crypto_boringssl was requested (--with-libngtcp2) but not found])
fi
# nghttp3 (for src) # nghttp3 (for src)
have_libnghttp3=no have_libnghttp3=no
if test "x${request_libnghttp3}" != "xno"; then if test "x${request_libnghttp3}" != "xno"; then
PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.0.0], [have_libnghttp3=yes], PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.1.0], [have_libnghttp3=yes],
[have_libnghttp3=no]) [have_libnghttp3=no])
if test "x${have_libnghttp3}" = "xno"; then if test "x${have_libnghttp3}" = "xno"; then
AC_MSG_NOTICE($LIBNGHTTP3_PKG_ERRORS) AC_MSG_NOTICE($LIBNGHTTP3_PKG_ERRORS)
@ -742,9 +788,11 @@ AM_CONDITIONAL([ENABLE_APP], [ test "x${enable_app}" = "xyes" ])
# Check HTTP/3 support # Check HTTP/3 support
enable_http3=no enable_http3=no
if test "x${request_http3}" != "xno" && if test "x${request_http3}" != "xno" &&
test "x${have_ssl_is_quic}" = "xyes" && (test "x${have_ssl_is_quic}" = "xyes" ||
test "x${have_boringssl_quic}" = "xyes") &&
test "x${have_libngtcp2}" = "xyes" && test "x${have_libngtcp2}" = "xyes" &&
test "x${have_libngtcp2_crypto_openssl}" = "xyes" && (test "x${have_libngtcp2_crypto_openssl}" = "xyes" ||
test "x${have_libngtcp2_crypto_boringssl}" = "xyes") &&
test "x${have_libnghttp3}" = "xyes"; then test "x${have_libnghttp3}" = "xyes"; then
enable_http3=yes enable_http3=yes
AC_DEFINE([ENABLE_HTTP3], [1], [Define to 1 if HTTP/3 is enabled.]) AC_DEFINE([ENABLE_HTTP3], [1], [Define to 1 if HTTP/3 is enabled.])
@ -983,31 +1031,6 @@ AC_CHECK_DECLS([initgroups], [], [], [[
#include <grp.h> #include <grp.h>
]]) ]])
have_netinet_udp_h_udp_segment=no
AC_CHECK_DECL([UDP_SEGMENT], [have_netinet_udp_h_udp_segment=yes],
[have_netinet_udp_h_udp_segment=no], [[
#include <netinet/udp.h>
]])
if test "x$have_netinet_udp_h_udp_segment" = "xno"; then
have_linux_udp_h_udp_segment=no
AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
[[
#include <linux/udp.h>
]],
[[
#if UDP_SEGMENT != 103
exit(1)
#endif
]])],
[have_linux_udp_h_udp_segment=yes],
[have_linux_udp_h_udp_segment=no])
if test "x$have_linux_udp_h_udp_segment" = "xyes"; then
EXTRA_DEFS="$EXTRA_DEFS -DUDP_SEGMENT=103"
fi
fi
save_CFLAGS=$CFLAGS save_CFLAGS=$CFLAGS
save_CXXFLAGS=$CXXFLAGS save_CXXFLAGS=$CXXFLAGS
@ -1202,6 +1225,7 @@ AC_MSG_NOTICE([summary of build options:
Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}') Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}')
libngtcp2: ${have_libngtcp2} (CFLAGS='${LIBNGTCP2_CFLAGS}' LIBS='${LIBNGTCP2_LIBS}') libngtcp2: ${have_libngtcp2} (CFLAGS='${LIBNGTCP2_CFLAGS}' LIBS='${LIBNGTCP2_LIBS}')
libngtcp2_crypto_openssl: ${have_libngtcp2_crypto_openssl} (CFLAGS='${LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_OPENSSL_LIBS}') libngtcp2_crypto_openssl: ${have_libngtcp2_crypto_openssl} (CFLAGS='${LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_OPENSSL_LIBS}')
libngtcp2_crypto_boringssl: ${have_libngtcp2_crypto_boringssl} (CFLAGS='${LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_BORINGSSL_LIBS}')
libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}') libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}')
libbpf: ${have_libbpf} (CFLAGS='${LIBBPF_CFLAGS}' LIBS='${LIBBPF_LIBS}') libbpf: ${have_libbpf} (CFLAGS='${LIBBPF_CFLAGS}' LIBS='${LIBBPF_LIBS}')
Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}') Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}')

View File

@ -184,9 +184,9 @@ set(EXTRA_DIST
sources/python-apiref.rst sources/python-apiref.rst
sources/building-android-binary.rst sources/building-android-binary.rst
sources/contribute.rst sources/contribute.rst
_exts/sphinxcontrib/LICENSE.rubydomain _exts/rubydomain/LICENSE.rubydomain
_exts/sphinxcontrib/__init__.py _exts/rubydomain/__init__.py
_exts/sphinxcontrib/rubydomain.py _exts/rubydomain/rubydomain.py
_themes/sphinx_rtd_theme/__init__.py _themes/sphinx_rtd_theme/__init__.py
_themes/sphinx_rtd_theme/breadcrumbs.html _themes/sphinx_rtd_theme/breadcrumbs.html
_themes/sphinx_rtd_theme/footer.html _themes/sphinx_rtd_theme/footer.html

View File

@ -206,9 +206,9 @@ EXTRA_DIST = \
sources/building-android-binary.rst \ sources/building-android-binary.rst \
sources/contribute.rst \ sources/contribute.rst \
sources/security.rst \ sources/security.rst \
_exts/sphinxcontrib/LICENSE.rubydomain \ _exts/rubydomain/LICENSE.rubydomain \
_exts/sphinxcontrib/__init__.py \ _exts/rubydomain/__init__.py \
_exts/sphinxcontrib/rubydomain.py \ _exts/rubydomain/rubydomain.py \
_themes/sphinx_rtd_theme/__init__.py \ _themes/sphinx_rtd_theme/__init__.py \
_themes/sphinx_rtd_theme/breadcrumbs.html \ _themes/sphinx_rtd_theme/breadcrumbs.html \
_themes/sphinx_rtd_theme/footer.html \ _themes/sphinx_rtd_theme/footer.html \
@ -272,7 +272,7 @@ EXTRA_DIST = \
# You can set these variables from the command line. # You can set these variables from the command line.
SPHINXOPTS = SPHINXOPTS =
SPHINXBUILD = sphinx-build SPHINXBUILD ?= sphinx-build
PAPER = PAPER =
BUILDDIR = manual BUILDDIR = manual

View File

@ -493,7 +493,7 @@ class RubyModuleIndex(Index):
# list of all modules, sorted by module name # list of all modules, sorted by module name
modules = sorted(_iteritems(self.domain.data['modules']), modules = sorted(_iteritems(self.domain.data['modules']),
key=lambda x: x[0].lower()) key=lambda x: x[0].lower())
# sort out collapsable modules # sort out collapsible modules
prev_modname = '' prev_modname = ''
num_toplevels = 0 num_toplevels = 0
for modname, (docname, synopsis, platforms, deprecated) in modules: for modname, (docname, synopsis, platforms, deprecated) in modules:

View File

@ -8,7 +8,7 @@ _h2load()
_get_comp_words_by_ref cur prev _get_comp_words_by_ref cur prev
case $cur in case $cur in
-*) -*)
COMPREPLY=( $( compgen -W '--connection-window-bits --clients --verbose --ciphers --rate --no-tls-proto --connect-to --header-table-size --requests --log-file --base-uri --no-udp-gso --h1 --threads --npn-list --rate-period --rps --data --tls13-ciphers --version --connection-inactivity-timeout --timing-script-file --encoder-header-table-size --max-concurrent-streams --connection-active-timeout --input-file --help --groups --window-bits --qlog-file-base --warm-up-time --duration --header ' -- "$cur" ) ) COMPREPLY=( $( compgen -W '--requests --clients --threads --input-file --max-concurrent-streams --window-bits --connection-window-bits --header --ciphers --tls13-ciphers --no-tls-proto --data --rate --rate-period --duration --warm-up-time --connection-active-timeout --connection-inactivity-timeout --timing-script-file --base-uri --npn-list --h1 --header-table-size --encoder-header-table-size --log-file --qlog-file-base --connect-to --rps --groups --no-udp-gso --max-udp-payload-size --verbose --version --help ' -- "$cur" ) )
;; ;;
*) *)
_filedir _filedir

View File

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function
import subprocess import subprocess
import io import io
import re import re

View File

@ -8,7 +8,7 @@ _nghttp()
_get_comp_words_by_ref cur prev _get_comp_words_by_ref cur prev
case $cur in case $cur in
-*) -*)
COMPREPLY=( $( compgen -W '--no-push --verbose --no-dep --get-assets --har --header-table-size --multiply --encoder-header-table-size --padding --hexdump --max-concurrent-streams --continuation --connection-window-bits --peer-max-concurrent-streams --timeout --data --no-content-length --version --color --cert --upgrade --remote-name --trailer --weight --help --key --null-out --window-bits --expect-continue --stat --no-verify-peer --header ' -- "$cur" ) ) COMPREPLY=( $( compgen -W '--verbose --null-out --remote-name --timeout --window-bits --connection-window-bits --get-assets --stat --header --trailer --cert --key --data --multiply --upgrade --weight --peer-max-concurrent-streams --header-table-size --encoder-header-table-size --padding --har --color --continuation --no-content-length --no-dep --hexdump --no-push --max-concurrent-streams --expect-continue --no-verify-peer --version --help ' -- "$cur" ) )
;; ;;
*) *)
_filedir _filedir

View File

@ -8,7 +8,7 @@ _nghttpd()
_get_comp_words_by_ref cur prev _get_comp_words_by_ref cur prev
case $cur in case $cur in
-*) -*)
COMPREPLY=( $( compgen -W '--htdocs --verbose --daemon --echo-upload --error-gzip --push --header-table-size --encoder-header-table-size --padding --hexdump --max-concurrent-streams --no-tls --connection-window-bits --mime-types-file --no-content-length --workers --version --color --early-response --dh-param-file --trailer --address --window-bits --verify-client --help ' -- "$cur" ) ) COMPREPLY=( $( compgen -W '--address --daemon --verify-client --htdocs --verbose --no-tls --header-table-size --encoder-header-table-size --color --push --padding --max-concurrent-streams --workers --error-gzip --window-bits --connection-window-bits --dh-param-file --early-response --trailer --hexdump --echo-upload --mime-types-file --no-content-length --version --help ' -- "$cur" ) )
;; ;;
*) *)
_filedir _filedir

View File

@ -8,7 +8,7 @@ _nghttpx()
_get_comp_words_by_ref cur prev _get_comp_words_by_ref cur prev
case $cur in case $cur in
-*) -*)
COMPREPLY=( $( compgen -W '--worker-read-rate --include --frontend-http2-dump-response-header --tls-ticket-key-file --verify-client-cacert --max-response-header-fields --backend-http2-window-size --tls13-client-ciphers --frontend-keep-alive-timeout --backend-request-buffer --max-request-header-fields --backend-connect-timeout --tls-max-proto-version --frontend-quic-idle-timeout --frontend-http3-read-timeout --conf --dns-lookup-timeout --frontend-http3-connection-window-size --backend-http2-max-concurrent-streams --worker-write-burst --no-quic-bpf --npn-list --dns-max-try --fetch-ocsp-response-file --tls-session-cache-memcached-cert-file --no-via --mruby-file --no-server-push --stream-read-timeout --client-ciphers --ocsp-update-interval --forwarded-for --accesslog-syslog --dns-cache-timeout --frontend-http2-read-timeout --listener-disable-timeout --frontend-http3-window-size --ciphers --client-psk-secrets --strip-incoming-x-forwarded-for --no-server-rewrite --private-key-passwd-file --backend-keep-alive-timeout --backend-http-proxy-uri --frontend-max-requests --tls-no-postpone-early-data --rlimit-nofile --no-strip-incoming-x-forwarded-proto --tls-ticket-key-memcached-cert-file --no-verify-ocsp --forwarded-by --tls-session-cache-memcached-private-key-file --error-page --ocsp-startup --backend-write-timeout --tls-dyn-rec-warmup-threshold --frontend-http2-window-size --tls-ticket-key-memcached-max-retry --http2-no-cookie-crumbling --frontend-http3-max-concurrent-streams --worker-read-burst --dh-param-file --accesslog-format --errorlog-syslog --redirect-https-port --request-header-field-buffer --api-max-request-body --frontend-http2-decoder-dynamic-table-size --errorlog-file --frontend-http2-max-concurrent-streams --psk-secrets --frontend-write-timeout --tls-ticket-key-cipher --read-burst --no-add-x-forwarded-proto --backend --server-name --insecure --backend-max-backoff --log-level --host-rewrite --quic-bpf-program-file --http2-altsvc --frontend-http3-max-connection-window-size --tls-ticket-key-memcached-interval --frontend-http2-setting-timeout --frontend-http2-connection-window-size --worker-frontend-connections --syslog-facility --fastopen --no-location-rewrite --single-thread --no-http2-cipher-block-list --tls-session-cache-memcached --no-ocsp --backend-response-buffer --tls-min-proto-version --workers --add-x-forwarded-for --add-forwarded --frontend-http3-max-window-size --worker-write-rate --add-request-header --backend-http2-settings-timeout --subcert --ignore-per-pattern-mruby-error --ecdh-curves --no-kqueue --help --frontend-frame-debug --tls-sct-dir --pid-file --frontend-http2-dump-request-header --daemon --write-rate --altsvc --backend-http2-decoder-dynamic-table-size --no-strip-incoming-early-data --user --verify-client-tolerate-expired --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --backend-connections-per-host --tls-max-early-data --response-header-field-buffer --tls-ticket-key-memcached-address-family --padding --tls-session-cache-memcached-address-family --stream-write-timeout --cacert --frontend-quic-debug-log --tls-ticket-key-memcached-private-key-file --accesslog-write-early --backend-address-family --backend-http2-connection-window-size --tls13-ciphers --client-no-http2-cipher-block-list --version --add-response-header --backend-read-timeout --frontend-http2-optimize-window-size --frontend --accesslog-file --http2-proxy --backend-http2-encoder-dynamic-table-size --client-private-key-file --single-process --client-cert-file --tls-ticket-key-memcached --tls-dyn-rec-idle-timeout --frontend-http2-optimize-write-buffer-size --verify-client --frontend-http2-encoder-dynamic-table-size --read-rate --backend-connections-per-frontend --strip-incoming-forwarded ' -- "$cur" ) ) COMPREPLY=( $( compgen -W '--backend --frontend --backlog --backend-address-family --backend-http-proxy-uri --workers --single-thread --read-rate --read-burst --write-rate --write-burst --worker-read-rate --worker-read-burst --worker-write-rate --worker-write-burst --worker-frontend-connections --backend-connections-per-host --backend-connections-per-frontend --rlimit-nofile --rlimit-memlock --backend-request-buffer --backend-response-buffer --fastopen --no-kqueue --frontend-http2-read-timeout --frontend-http3-read-timeout --frontend-read-timeout --frontend-write-timeout --frontend-keep-alive-timeout --stream-read-timeout --stream-write-timeout --backend-read-timeout --backend-write-timeout --backend-connect-timeout --backend-keep-alive-timeout --listener-disable-timeout --frontend-http2-setting-timeout --backend-http2-settings-timeout --backend-max-backoff --ciphers --tls13-ciphers --client-ciphers --tls13-client-ciphers --ecdh-curves --insecure --cacert --private-key-passwd-file --subcert --dh-param-file --npn-list --verify-client --verify-client-cacert --verify-client-tolerate-expired --client-private-key-file --client-cert-file --tls-min-proto-version --tls-max-proto-version --tls-ticket-key-file --tls-ticket-key-memcached --tls-ticket-key-memcached-address-family --tls-ticket-key-memcached-interval --tls-ticket-key-memcached-max-retry --tls-ticket-key-memcached-max-fail --tls-ticket-key-cipher --tls-ticket-key-memcached-cert-file --tls-ticket-key-memcached-private-key-file --fetch-ocsp-response-file --ocsp-update-interval --ocsp-startup --no-verify-ocsp --no-ocsp --tls-session-cache-memcached --tls-session-cache-memcached-address-family --tls-session-cache-memcached-cert-file --tls-session-cache-memcached-private-key-file --tls-dyn-rec-warmup-threshold --tls-dyn-rec-idle-timeout --no-http2-cipher-block-list --client-no-http2-cipher-block-list --tls-sct-dir --psk-secrets --client-psk-secrets --tls-no-postpone-early-data --tls-max-early-data --frontend-http2-max-concurrent-streams --backend-http2-max-concurrent-streams --frontend-http2-window-size --frontend-http2-connection-window-size --backend-http2-window-size --backend-http2-connection-window-size --http2-no-cookie-crumbling --padding --no-server-push --frontend-http2-optimize-write-buffer-size --frontend-http2-optimize-window-size --frontend-http2-encoder-dynamic-table-size --frontend-http2-decoder-dynamic-table-size --backend-http2-encoder-dynamic-table-size --backend-http2-decoder-dynamic-table-size --http2-proxy --log-level --accesslog-file --accesslog-syslog --accesslog-format --accesslog-write-early --errorlog-file --errorlog-syslog --syslog-facility --add-x-forwarded-for --strip-incoming-x-forwarded-for --no-add-x-forwarded-proto --no-strip-incoming-x-forwarded-proto --add-forwarded --strip-incoming-forwarded --forwarded-by --forwarded-for --no-via --no-strip-incoming-early-data --no-location-rewrite --host-rewrite --altsvc --http2-altsvc --add-request-header --add-response-header --request-header-field-buffer --max-request-header-fields --response-header-field-buffer --max-response-header-fields --error-page --server-name --no-server-rewrite --redirect-https-port --api-max-request-body --dns-cache-timeout --dns-lookup-timeout --dns-max-try --frontend-max-requests --frontend-http2-dump-request-header --frontend-http2-dump-response-header --frontend-frame-debug --daemon --pid-file --user --single-process --max-worker-processes --worker-process-grace-shutdown-period --mruby-file --ignore-per-pattern-mruby-error --frontend-quic-idle-timeout --frontend-quic-debug-log --quic-bpf-program-file --frontend-quic-early-data --frontend-quic-qlog-dir --frontend-quic-require-token --frontend-quic-congestion-controller --frontend-quic-secret-file --quic-server-id --frontend-quic-initial-rtt --no-quic-bpf --frontend-http3-window-size --frontend-http3-connection-window-size --frontend-http3-max-window-size --frontend-http3-max-connection-window-size --frontend-http3-max-concurrent-streams --conf --include --version --help ' -- "$cur" ) )
;; ;;
*) *)
_filedir _filedir

View File

@ -41,7 +41,7 @@ import sys, os
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.')) #sys.path.insert(0, os.path.abspath('.'))
sys.path.append(os.path.abspath('@top_srcdir@/doc/_exts')) sys.path.insert(0, os.path.abspath('@top_srcdir@/doc/_exts'))
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
@ -50,7 +50,7 @@ sys.path.append(os.path.abspath('@top_srcdir@/doc/_exts'))
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinxcontrib.rubydomain'] extensions = ['rubydomain.rubydomain']
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['@top_srcdir@/_templates'] templates_path = ['@top_srcdir@/_templates']

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "H2LOAD" "1" "Aug 31, 2021" "1.45.0-DEV" "nghttp2" .TH "H2LOAD" "1" "Oct 19, 2021" "1.46.0" "nghttp2"
.SH NAME .SH NAME
h2load \- HTTP/2 benchmarking tool h2load \- HTTP/2 benchmarking tool
. .
@ -328,6 +328,11 @@ Disable UDP GSO.
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B \-\-max\-udp\-payload\-size=<SIZE>
Specify the maximum outgoing UDP datagram payload size.
.UNINDENT
.INDENT 0.0
.TP
.B \-v, \-\-verbose .B \-v, \-\-verbose
Output debug information. Output debug information.
.UNINDENT .UNINDENT

View File

@ -277,6 +277,10 @@ OPTIONS
Disable UDP GSO. Disable UDP GSO.
.. option:: --max-udp-payload-size=<SIZE>
Specify the maximum outgoing UDP datagram payload size.
.. option:: -v, --verbose .. option:: -v, --verbose
Output debug information. Output debug information.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTP" "1" "Aug 31, 2021" "1.45.0-DEV" "nghttp2" .TH "NGHTTP" "1" "Oct 19, 2021" "1.46.0" "nghttp2"
.SH NAME .SH NAME
nghttp \- HTTP/2 client nghttp \- HTTP/2 client
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPD" "1" "Aug 31, 2021" "1.45.0-DEV" "nghttp2" .TH "NGHTTPD" "1" "Oct 19, 2021" "1.46.0" "nghttp2"
.SH NAME .SH NAME
nghttpd \- HTTP/2 server nghttpd \- HTTP/2 server
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPX" "1" "Aug 31, 2021" "1.45.0-DEV" "nghttp2" .TH "NGHTTPX" "1" "Oct 19, 2021" "1.46.0" "nghttp2"
.SH NAME .SH NAME
nghttpx \- HTTP/2 proxy nghttpx \- HTTP/2 proxy
. .
@ -35,7 +35,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
\fBnghttpx\fP [OPTIONS]... [<PRIVATE_KEY> <CERT>] \fBnghttpx\fP [OPTIONS]... [<PRIVATE_KEY> <CERT>]
.SH DESCRIPTION .SH DESCRIPTION
.sp .sp
A reverse proxy for HTTP/2, and HTTP/1. A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B <PRIVATE_KEY> .B <PRIVATE_KEY>
@ -503,6 +503,15 @@ Default: \fB0\fP
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B \-\-rlimit\-memlock=<N>
Set maximum number of bytes of memory that may be locked
into RAM. If 0 is given, nghttpx does not set the
limit.
.sp
Default: \fB0\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-request\-buffer=<SIZE> .B \-\-backend\-request\-buffer=<SIZE>
Set buffer size used to store backend request. Set buffer size used to store backend request.
.sp .sp
@ -1089,12 +1098,13 @@ option. But be aware its implications.
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B \-\-tls\-no\-postpone\-early\-data .B \-\-tls\-no\-postpone\-early\-data
By default, nghttpx postpones forwarding HTTP requests By default, except for QUIC connections, nghttpx
sent in early data, including those sent in partially in postpones forwarding HTTP requests sent in early data,
it, until TLS handshake finishes. If all backend server including those sent in partially in it, until TLS
recognizes "Early\-Data" header field, using this option handshake finishes. If all backend server recognizes
makes nghttpx not postpone forwarding request and get "Early\-Data" header field, using this option makes
full potential of 0\-RTT data. nghttpx not postpone forwarding request and get full
potential of 0\-RTT data.
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
@ -1507,7 +1517,7 @@ they are treated as nothing is specified. They are
advertised in alt\-svc header field only in HTTP/1.1 advertised in alt\-svc header field only in HTTP/1.1
frontend. This option can be used multiple times to frontend. This option can be used multiple times to
specify multiple alternative services. specify multiple alternative services.
Example: \fI\%\-\-altsvc\fP="h2,443,,,ma=3600; persist=1\(aq Example: \fI\%\-\-altsvc\fP="h2,443,,,ma=3600; persist=1"
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
@ -1704,6 +1714,33 @@ process. nghttpx still spawns additional process if
neverbleed is used. In the single process mode, the neverbleed is used. In the single process mode, the
signal handling feature is disabled. signal handling feature is disabled.
.UNINDENT .UNINDENT
.INDENT 0.0
.TP
.B \-\-max\-worker\-processes=<N>
The maximum number of worker processes. nghttpx spawns
new worker process when it reloads its configuration.
The previous worker process enters graceful termination
period and will terminate when it finishes handling the
existing connections. However, if reloading
configurations happen very frequently, the worker
processes might be piled up if they take a bit long time
to finish the existing connections. With this option,
if the number of worker processes exceeds the given
value, the oldest worker process is terminated
immediately. Specifying 0 means no limit and it is the
default behaviour.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-worker\-process\-grace\-shutdown\-period=<DURATION>
Maximum period for a worker process to terminate
gracefully. When a worker process enters in graceful
shutdown period (e.g., when nghttpx reloads its
configuration) and it does not finish handling the
existing connections in the given period of time, it is
immediately terminated. Specifying 0 means no limit and
it is the default behaviour.
.UNINDENT
.SS Scripting .SS Scripting
.INDENT 0.0 .INDENT 0.0
.TP .TP
@ -1741,6 +1778,86 @@ Default: \fB/usr/local/lib/nghttp2/reuseport_kern.o\fP
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B \-\-frontend\-quic\-early\-data
Enable early data on frontend QUIC connections. nghttpx
sends "Early\-Data" header field to a backend server if a
request is received in early data and handshake has not
finished. All backend servers should deal with possibly
replayed requests.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-frontend\-quic\-qlog\-dir=<DIR>
Specify a directory where a qlog file is written for
frontend QUIC connections. A qlog file is created per
each QUIC connection. The file name is ISO8601 basic
format, followed by "\-", server Source Connection ID and
".qlog".
.UNINDENT
.INDENT 0.0
.TP
.B \-\-frontend\-quic\-require\-token
Require an address validation token for a frontend QUIC
connection. Server sends a token in Retry packet or
NEW_TOKEN frame in the previous connection.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-frontend\-quic\-congestion\-controller=<CC>
Specify a congestion controller algorithm for a frontend
QUIC connection. <CC> should be either "cubic" or
"bbr".
.sp
Default: \fBcubic\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-frontend\-quic\-secret\-file=<PATH>
Path to file that contains secure random data to be used
as QUIC keying materials. It is used to derive keys for
encrypting tokens and Connection IDs. It is not used to
encrypt QUIC packets. Each line of this file must
contain exactly 136 bytes hex\-encoded string (when
decoded the byte string is 68 bytes long). The first 2
bits of decoded byte string are used to identify the
keying material. An empty line or a line which starts
\(aq#\(aq is ignored. The file can contain more than one
keying materials. Because the identifier is 2 bits, at
most 4 keying materials are read and the remaining data
is discarded. The first keying material in the file is
primarily used for encryption and decryption for new
connection. The other ones are used to decrypt data for
the existing connections. Specifying multiple keying
materials enables key rotation. Please note that key
rotation does not occur automatically. User should
update files or change options values and restart
nghttpx gracefully. If opening or reading given file
fails, all loaded keying materials are discarded and it
is treated as if none of this option is given. If this
option is not given or an error occurred while opening
or reading a file, a keying material is generated
internally on startup and reload.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-quic\-server\-id=<HEXSTRING>
Specify server ID encoded in Connection ID to identify
this particular server instance. Connection ID is
encrypted and this part is not visible in public. It
must be 4 bytes long and must be encoded in hex string
(which is 8 bytes long). If this option is omitted, a
random server ID is generated on startup and
configuration reload.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-frontend\-quic\-initial\-rtt=<DURATION>
Specify the initial RTT of the frontend QUIC connection.
.sp
Default: \fB333ms\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-no\-quic\-bpf .B \-\-no\-quic\-bpf
Disable eBPF. Disable eBPF.
.UNINDENT .UNINDENT

View File

@ -14,7 +14,7 @@ SYNOPSIS
DESCRIPTION DESCRIPTION
----------- -----------
A reverse proxy for HTTP/2, and HTTP/1. A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.
.. describe:: <PRIVATE_KEY> .. describe:: <PRIVATE_KEY>
@ -472,6 +472,14 @@ Performance
Default: ``0`` Default: ``0``
.. option:: --rlimit-memlock=<N>
Set maximum number of bytes of memory that may be locked
into RAM. If 0 is given, nghttpx does not set the
limit.
Default: ``0``
.. option:: --backend-request-buffer=<SIZE> .. option:: --backend-request-buffer=<SIZE>
Set buffer size used to store backend request. Set buffer size used to store backend request.
@ -1003,12 +1011,13 @@ SSL/TLS
.. option:: --tls-no-postpone-early-data .. option:: --tls-no-postpone-early-data
By default, nghttpx postpones forwarding HTTP requests By default, except for QUIC connections, nghttpx
sent in early data, including those sent in partially in postpones forwarding HTTP requests sent in early data,
it, until TLS handshake finishes. If all backend server including those sent in partially in it, until TLS
recognizes "Early-Data" header field, using this option handshake finishes. If all backend server recognizes
makes nghttpx not postpone forwarding request and get "Early-Data" header field, using this option makes
full potential of 0-RTT data. nghttpx not postpone forwarding request and get full
potential of 0-RTT data.
.. option:: --tls-max-early-data=<SIZE> .. option:: --tls-max-early-data=<SIZE>
@ -1367,7 +1376,7 @@ HTTP
advertised in alt-svc header field only in HTTP/1.1 advertised in alt-svc header field only in HTTP/1.1
frontend. This option can be used multiple times to frontend. This option can be used multiple times to
specify multiple alternative services. specify multiple alternative services.
Example: :option:`--altsvc`\="h2,443,,,ma=3600; persist=1' Example: :option:`--altsvc`\="h2,443,,,ma=3600; persist=1"
.. option:: --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]> .. option:: --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
@ -1553,6 +1562,31 @@ Process
neverbleed is used. In the single process mode, the neverbleed is used. In the single process mode, the
signal handling feature is disabled. signal handling feature is disabled.
.. option:: --max-worker-processes=<N>
The maximum number of worker processes. nghttpx spawns
new worker process when it reloads its configuration.
The previous worker process enters graceful termination
period and will terminate when it finishes handling the
existing connections. However, if reloading
configurations happen very frequently, the worker
processes might be piled up if they take a bit long time
to finish the existing connections. With this option,
if the number of worker processes exceeds the given
value, the oldest worker process is terminated
immediately. Specifying 0 means no limit and it is the
default behaviour.
.. option:: --worker-process-grace-shutdown-period=<DURATION>
Maximum period for a worker process to terminate
gracefully. When a worker process enters in graceful
shutdown period (e.g., when nghttpx reloads its
configuration) and it does not finish handling the
existing connections in the given period of time, it is
immediately terminated. Specifying 0 means no limit and
it is the default behaviour.
Scripting Scripting
~~~~~~~~~ ~~~~~~~~~
@ -1589,6 +1623,79 @@ HTTP/3 and QUIC
Default: ``/usr/local/lib/nghttp2/reuseport_kern.o`` Default: ``/usr/local/lib/nghttp2/reuseport_kern.o``
.. option:: --frontend-quic-early-data
Enable early data on frontend QUIC connections. nghttpx
sends "Early-Data" header field to a backend server if a
request is received in early data and handshake has not
finished. All backend servers should deal with possibly
replayed requests.
.. option:: --frontend-quic-qlog-dir=<DIR>
Specify a directory where a qlog file is written for
frontend QUIC connections. A qlog file is created per
each QUIC connection. The file name is ISO8601 basic
format, followed by "-", server Source Connection ID and
".qlog".
.. option:: --frontend-quic-require-token
Require an address validation token for a frontend QUIC
connection. Server sends a token in Retry packet or
NEW_TOKEN frame in the previous connection.
.. option:: --frontend-quic-congestion-controller=<CC>
Specify a congestion controller algorithm for a frontend
QUIC connection. <CC> should be either "cubic" or
"bbr".
Default: ``cubic``
.. option:: --frontend-quic-secret-file=<PATH>
Path to file that contains secure random data to be used
as QUIC keying materials. It is used to derive keys for
encrypting tokens and Connection IDs. It is not used to
encrypt QUIC packets. Each line of this file must
contain exactly 136 bytes hex-encoded string (when
decoded the byte string is 68 bytes long). The first 2
bits of decoded byte string are used to identify the
keying material. An empty line or a line which starts
'#' is ignored. The file can contain more than one
keying materials. Because the identifier is 2 bits, at
most 4 keying materials are read and the remaining data
is discarded. The first keying material in the file is
primarily used for encryption and decryption for new
connection. The other ones are used to decrypt data for
the existing connections. Specifying multiple keying
materials enables key rotation. Please note that key
rotation does not occur automatically. User should
update files or change options values and restart
nghttpx gracefully. If opening or reading given file
fails, all loaded keying materials are discarded and it
is treated as if none of this option is given. If this
option is not given or an error occurred while opening
or reading a file, a keying material is generated
internally on startup and reload.
.. option:: --quic-server-id=<HEXSTRING>
Specify server ID encoded in Connection ID to identify
this particular server instance. Connection ID is
encrypted and this part is not visible in public. It
must be 4 bytes long and must be encoded in hex string
(which is 8 bytes long). If this option is omitted, a
random server ID is generated on startup and
configuration reload.
.. option:: --frontend-quic-initial-rtt=<DURATION>
Specify the initial RTT of the frontend QUIC connection.
Default: ``333ms``
.. option:: --no-quic-bpf .. option:: --no-quic-bpf
Disable eBPF. Disable eBPF.

View File

@ -14,8 +14,8 @@ Default mode
If nghttpx is invoked without :option:`--http2-proxy`, it operates in If nghttpx is invoked without :option:`--http2-proxy`, it operates in
default mode. In this mode, it works as reverse proxy (gateway) for default mode. In this mode, it works as reverse proxy (gateway) for
both HTTP/2 and HTTP/1 clients to backend servers. This is also known HTTP/3, HTTP/2 and HTTP/1 clients to backend servers. This is also
as "HTTP/2 router". known as "HTTP/2 router".
By default, frontend connection is encrypted using SSL/TLS. So By default, frontend connection is encrypted using SSL/TLS. So
server's private key and certificate must be supplied to the command server's private key and certificate must be supplied to the command
@ -28,6 +28,9 @@ the frontend, and an HTTP/1 connection can be upgraded to HTTP/2 using
HTTP Upgrade. Starting HTTP/2 connection by sending HTTP/2 connection HTTP Upgrade. Starting HTTP/2 connection by sending HTTP/2 connection
preface is also supported. preface is also supported.
In order to receive HTTP/3 traffic, use ``quic`` parameter in
:option:`--frontend` option (.e.g, ``--frontend='*,443;quic'``)
nghttpx can listen on multiple frontend addresses. This is achieved nghttpx can listen on multiple frontend addresses. This is achieved
by using multiple :option:`--frontend` options. For each frontend by using multiple :option:`--frontend` options. For each frontend
address, TLS can be enabled or disabled. address, TLS can be enabled or disabled.
@ -509,6 +512,8 @@ Bootstrapping WebSockets with HTTP/2 for both frontend and backend
connections. This feature is enabled by default and no configuration connections. This feature is enabled by default and no configuration
is required. is required.
WebSockets over HTTP/3 is also supported.
HTTP/3 HTTP/3
------ ------
@ -529,7 +534,28 @@ nghttpx does not support HTTP/3 on backend connection.
Hot swapping (SIGUSR2) or configuration reload (SIGHUP) require eBPF Hot swapping (SIGUSR2) or configuration reload (SIGHUP) require eBPF
program. Without eBPF, old worker processes keep getting HTTP/3 program. Without eBPF, old worker processes keep getting HTTP/3
traffic and do not work as intended. traffic and do not work as intended. The QUIC keying material to
encrypt Connection ID must be set with
:option:`--frontend-quic-secret-file` and must provide the existing
keys in order to keep the existing connections alive during reload.
The construction of Connection ID closely follows Block Cipher CID
Algorithm described in `QUIC-LB draft
<https://datatracker.ietf.org/doc/html/draft-ietf-quic-load-balancers>`_.
A Connection ID that nghttpx generates is always 20 bytes long. It
uses first 2 bits as a configuration ID. The remaining bits in the
first byte are reserved and random. The next 4 bytes are server ID.
The next 4 bytes are used to route UDP datagram to a correct
``SO_REUSEPORT`` socket. The remaining bytes are randomly generated.
The server ID and the next 12 bytes are encrypted with AES-ECB. The
key is derived from the keying materials stored in a file specified by
:option:`--frontend-quic-secret-file`. The first 2 bits of keying
material in the file is used as a configuration ID. The remaining
bits and following 3 bytes are reserved and unused. The next 32 bytes
are used as an initial secret. The remaining 32 bytes are used as a
salt. The encryption key is generated by `HKDF
<https://datatracker.ietf.org/doc/html/rfc5869>`_ with SHA256 and
these keying materials and ``connection id encryption key`` as info.
In order announce that HTTP/3 endpoint is available, you should In order announce that HTTP/3 endpoint is available, you should
specify alt-svc header field. For example, the following options send specify alt-svc header field. For example, the following options send

78
docker/Dockerfile Normal file
View File

@ -0,0 +1,78 @@
FROM debian:11 as build
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git clang make binutils autoconf automake autotools-dev libtool \
pkg-config \
zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \
libelf-dev
RUN git clone --depth 1 -b OpenSSL_1_1_1m+quic https://github.com/quictls/openssl && \
cd openssl && \
./config --openssldir=/etc/ssl && \
make -j$(nproc) && \
make install_sw && \
cd .. && \
rm -rf openssl
RUN git clone --depth 1 -b v0.1.1 https://github.com/ngtcp2/nghttp3 && \
cd nghttp3 && \
autoreconf -i && \
./configure --enable-lib-only && \
make -j$(nproc) && \
make install-strip && \
cd .. && \
rm -rf nghttp3
RUN git clone --depth 1 -b v0.1.0 https://github.com/ngtcp2/ngtcp2 && \
cd ngtcp2 && \
autoreconf -i && \
./configure --enable-lib-only \
LIBTOOL_LDFLAGS="-static-libtool-libs" \
OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -lpthread" \
PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \
make -j$(nproc) && \
make install-strip && \
cd .. && \
rm -rf ngtcp2
RUN git clone --depth 1 -b v0.4.0 https://github.com/libbpf/libbpf && \
cd libbpf && \
PREFIX=/usr/local make -C src install && \
cd .. && \
rm -rf libbpf
RUN git clone --depth 1 https://github.com/nghttp2/nghttp2.git && \
cd nghttp2 && \
git submodule update --init && \
autoreconf -i && \
./configure --disable-examples --disable-hpack-tools \
--disable-python-bindings --with-mruby --with-neverbleed \
--enable-http3 --with-libbpf \
CC=clang CXX=clang++ \
LIBTOOL_LDFLAGS="-static-libtool-libs" \
OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -pthread" \
LIBEV_LIBS="-l:libev.a" \
JEMALLOC_LIBS="-l:libjemalloc.a" \
LIBCARES_LIBS="-l:libcares.a" \
ZLIB_LIBS="-l:libz.a" \
LIBBPF_LIBS="-L/usr/local/lib64 -l:libbpf.a -l:libelf.a" \
LDFLAGS="-static-libgcc -static-libstdc++" \
PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \
make -j$(nproc) install-strip && \
cd .. && \
rm -rf nghttp2
FROM gcr.io/distroless/base-debian11
COPY --from=build \
/usr/local/share/nghttp2/ \
/usr/local/share/nghttp2/
COPY --from=build \
/usr/local/bin/h2load \
/usr/local/bin/nghttpx \
/usr/local/bin/nghttp \
/usr/local/bin/nghttpd \
/usr/local/bin/
COPY --from=build /usr/local/lib/nghttp2/reuseport_kern.o \
/usr/local/lib/nghttp2/

View File

@ -1,39 +0,0 @@
FROM debian:10 as build
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git g++ make binutils autoconf automake autotools-dev libtool \
pkg-config \
zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison && \
git clone --depth 1 -b OpenSSL_1_1_1l+quic https://github.com/quictls/openssl && \
cd openssl && ./config enable-tls1_3 --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \
git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \
cd nghttp3 && autoreconf -i && \
./configure --enable-lib-only && \
make -j$(nproc) && make install-strip && cd .. && rm -rf nghttp3 && \
git clone --depth 1 https://github.com/ngtcp2/ngtcp2 && \
cd ngtcp2 && autoreconf -i && \
./configure --enable-lib-only \
LIBTOOL_LDFLAGS="-static-libtool-libs" \
OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -lpthread" && \
make -j$(nproc) && make install-strip && cd .. && rm -rf ngtcp2 && \
git clone --depth 1 https://github.com/nghttp2/nghttp2.git && \
cd nghttp2 && \
git submodule update --init && autoreconf -i && \
./configure --disable-examples --disable-hpack-tools \
--disable-python-bindings --with-mruby --with-neverbleed \
--enable-http3 \
LIBTOOL_LDFLAGS="-static-libtool-libs" \
LIBS="-ldl -pthread" \
OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a" \
LIBEV_LIBS="-l:libev.a" \
JEMALLOC_LIBS="-l:libjemalloc.a" \
LIBCARES_LIBS="-l:libcares.a" \
ZLIB_LIBS="-l:libz.a" && \
make -j$(nproc) install-strip
FROM gcr.io/distroless/cc-debian10
COPY --from=build /usr/local/bin/h2load /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/h2load"]

25
docker/README.rst Normal file
View File

@ -0,0 +1,25 @@
Dockerfile
==========
Dockerfile creates the applications bundled with nghttp2.
These applications are:
- nghttp
- nghttpd
- nghttpx
- h2load
HTTP/3 and eBPF features are enabled.
In order to run nghttpx with HTTP/3 endpoint, you need to run the
image with the escalated privilege and higher memlock value. Here is
the example command-line to run nghttpx to listen to HTTP/3 on port
443, assuming that the current directory contains a private key and a
certificate in server.key and server.crt respectively :
.. code-block:: text
$ docker run --rm -it -v $PWD:/shared --net=host --privileged \
nghttp2 nghttpx \
/shared/server.key /shared/server.crt \
-f'*,443;quic' --rlimit-memlock 524288

View File

@ -380,6 +380,10 @@ static void init_ssl_ctx(SSL_CTX *ssl_ctx) {
#ifndef OPENSSL_NO_NEXTPROTONEG #ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
#endif /* !OPENSSL_NO_NEXTPROTONEG */ #endif /* !OPENSSL_NO_NEXTPROTONEG */
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
} }
static void ssl_handshake(SSL *ssl, int fd) { static void ssl_handshake(SSL *ssl, int fd) {
@ -544,7 +548,7 @@ static void fetch_uri(const struct URI *uri) {
if (fd == -1) { if (fd == -1) {
die("Could not open file descriptor"); die("Could not open file descriptor");
} }
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); ssl_ctx = SSL_CTX_new(TLS_client_method());
if (ssl_ctx == NULL) { if (ssl_ctx == NULL) {
dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL)); dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL));
} }
@ -715,8 +719,18 @@ int main(int argc, char **argv) {
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, 0); sigaction(SIGPIPE, &act, 0);
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
/* No explicit initialization is required. */
#elif defined(OPENSSL_IS_BORINGSSL)
CRYPTO_library_init();
#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \
!defined(OPENSSL_IS_BORINGSSL) */
OPENSSL_config(NULL);
SSL_load_error_strings(); SSL_load_error_strings();
SSL_library_init(); SSL_library_init();
OpenSSL_add_all_algorithms();
#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \
!defined(OPENSSL_IS_BORINGSSL) */
rv = parse_uri(&uri, argv[1]); rv = parse_uri(&uri, argv[1]);
if (rv != 0) { if (rv != 0) {

View File

@ -44,7 +44,7 @@ static void deflate(nghttp2_hd_deflater *deflater,
static int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in, static int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in,
size_t inlen, int final); size_t inlen, int final);
int main() { int main(void) {
int rv; int rv;
nghttp2_hd_deflater *deflater; nghttp2_hd_deflater *deflater;
nghttp2_hd_inflater *inflater; nghttp2_hd_inflater *inflater;

View File

@ -328,7 +328,7 @@ static int select_next_proto_cb(SSL *ssl, unsigned char **out,
/* Create SSL_CTX. */ /* Create SSL_CTX. */
static SSL_CTX *create_ssl_ctx(void) { static SSL_CTX *create_ssl_ctx(void) {
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) { if (!ssl_ctx) {
errx(1, "Could not create SSL/TLS context: %s", errx(1, "Could not create SSL/TLS context: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
@ -617,8 +617,18 @@ int main(int argc, char **argv) {
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, NULL); sigaction(SIGPIPE, &act, NULL);
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
/* No explicit initialization is required. */
#elif defined(OPENSSL_IS_BORINGSSL)
CRYPTO_library_init();
#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \
!defined(OPENSSL_IS_BORINGSSL) */
OPENSSL_config(NULL);
SSL_load_error_strings(); SSL_load_error_strings();
SSL_library_init(); SSL_library_init();
OpenSSL_add_all_algorithms();
#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \
!defined(OPENSSL_IS_BORINGSSL) */
run(argv[1]); run(argv[1]);
return 0; return 0;

View File

@ -143,7 +143,7 @@ static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) {
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
ssl_ctx = SSL_CTX_new(SSLv23_server_method()); ssl_ctx = SSL_CTX_new(TLS_server_method());
if (!ssl_ctx) { if (!ssl_ctx) {
errx(1, "Could not create SSL/TLS context: %s", errx(1, "Could not create SSL/TLS context: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
@ -153,14 +153,9 @@ static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) {
SSL_OP_NO_COMPRESSION | SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L #if OPENSSL_VERSION_NUMBER >= 0x30000000L
{ if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
EVP_PKEY *ecdh; errx(1, "SSL_CTX_set1_curves_list failed: %s",
ecdh = EVP_EC_gen("P-256"); ERR_error_string(ERR_get_error(), NULL));
if (!ecdh) {
errx(1, "EVP_EC_gen failed: %s", ERR_error_string(ERR_get_error(), NULL));
}
SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
EVP_PKEY_free(ecdh);
} }
#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ #else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
{ {
@ -822,8 +817,18 @@ int main(int argc, char **argv) {
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, NULL); sigaction(SIGPIPE, &act, NULL);
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
/* No explicit initialization is required. */
#elif defined(OPENSSL_IS_BORINGSSL)
CRYPTO_library_init();
#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \
!defined(OPENSSL_IS_BORINGSSL) */
OPENSSL_config(NULL);
SSL_load_error_strings(); SSL_load_error_strings();
SSL_library_init(); SSL_library_init();
OpenSSL_add_all_algorithms();
#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \
!defined(OPENSSL_IS_BORINGSSL) */
run(argv[1], argv[2], argv[3]); run(argv[1], argv[2], argv[3]);
return 0; return 0;

View File

@ -190,6 +190,14 @@ OPTIONS = [
"frontend-http3-max-concurrent-streams", "frontend-http3-max-concurrent-streams",
"frontend-quic-early-data", "frontend-quic-early-data",
"frontend-quic-qlog-dir", "frontend-quic-qlog-dir",
"frontend-quic-require-token",
"frontend-quic-congestion-controller",
"quic-server-id",
"frontend-quic-secret-file",
"rlimit-memlock",
"max-worker-processes",
"worker-process-grace-shutdown-period",
"frontend-quic-initial-rtt",
] ]
LOGVARS = [ LOGVARS = [

View File

@ -5,14 +5,15 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/websocket"
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
"syscall" "syscall"
"testing" "testing"
"time" "time"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/websocket"
) )
// TestH1H1PlainGET tests whether simple HTTP/1 GET request works. // TestH1H1PlainGET tests whether simple HTTP/1 GET request works.
@ -34,7 +35,7 @@ func TestH1H1PlainGET(t *testing.T) {
} }
// TestH1H1PlainGETClose tests whether simple HTTP/1 GET request with // TestH1H1PlainGETClose tests whether simple HTTP/1 GET request with
// Connetion: close request header field works. // Connection: close request header field works.
func TestH1H1PlainGETClose(t *testing.T) { func TestH1H1PlainGETClose(t *testing.T) {
st := newServerTester(nil, t, noopHandler) st := newServerTester(nil, t, noopHandler)
defer st.Close() defer st.Close()
@ -1171,3 +1172,31 @@ Content-Length: 1000000
t.Errorf("status: %v; want %v", got, want) t.Errorf("status: %v; want %v", got, want)
} }
} }
// TestH1H1ChunkedEndsPrematurely tests that an HTTP/1.1 request fails
// if the backend chunked encoded response ends prematurely.
func TestH1H1ChunkedEndsPrematurely(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
hj, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
return
}
conn, bufrw, err := hj.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close()
bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n")
bufrw.Flush()
})
defer st.Close()
_, err := st.http1(requestParam{
name: "TestH1H1ChunkedEndsPrematurely",
})
if err == nil {
t.Fatal("st.http1() should fail")
}
}

View File

@ -565,7 +565,7 @@ func TestH2H1BadResponseCL(t *testing.T) {
t.Fatalf("Error st.http2() = %v", err) t.Fatalf("Error st.http2() = %v", err)
} }
want := http2.ErrCodeProtocol want := http2.ErrCodeInternal
if res.errCode != want { if res.errCode != want {
t.Errorf("res.errCode = %v; want %v", res.errCode, want) t.Errorf("res.errCode = %v; want %v", res.errCode, want)
} }
@ -2838,3 +2838,35 @@ func TestH2ResponseBeforeRequestEnd(t *testing.T) {
t.Errorf("res.status: %v; want %v", got, want) t.Errorf("res.status: %v; want %v", got, want)
} }
} }
// TestH2H1ChunkedEndsPrematurely tests that a stream is reset if the
// backend chunked encoded response ends prematurely.
func TestH2H1ChunkedEndsPrematurely(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
hj, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
return
}
conn, bufrw, err := hj.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close()
bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n")
bufrw.Flush()
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1ChunkedEndsPrematurely",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.errCode, http2.ErrCodeInternal; got != want {
t.Errorf("res.errCode = %v; want %v", got, want)
}
}

View File

@ -99,7 +99,7 @@ void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem);
* |new_cap|. If extensions took place, buffer pointers in |buf| will * |new_cap|. If extensions took place, buffer pointers in |buf| will
* change. * change.
* *
* This function returns 0 if it succeeds, or one of the followings * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
* *
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM

View File

@ -654,8 +654,6 @@ int nghttp2_frame_unpack_goaway_payload2(nghttp2_goaway *frame,
var_gift_payloadlen = 0; var_gift_payloadlen = 0;
} }
payloadlen -= var_gift_payloadlen;
if (!var_gift_payloadlen) { if (!var_gift_payloadlen) {
var_gift_payload = NULL; var_gift_payload = NULL;
} else { } else {

View File

@ -46,7 +46,7 @@
#define NGHTTP2_MAX_FRAME_SIZE_MIN (1 << 14) #define NGHTTP2_MAX_FRAME_SIZE_MIN (1 << 14)
#define NGHTTP2_MAX_PAYLOADLEN 16384 #define NGHTTP2_MAX_PAYLOADLEN 16384
/* The one frame buffer length for tranmission. We may use several of /* The one frame buffer length for transmission. We may use several of
them to support CONTINUATION. To account for Pad Length field, we them to support CONTINUATION. To account for Pad Length field, we
allocate extra 1 byte, which saves extra large memcopying. */ allocate extra 1 byte, which saves extra large memcopying. */
#define NGHTTP2_FRAMEBUF_CHUNKLEN \ #define NGHTTP2_FRAMEBUF_CHUNKLEN \

View File

@ -1263,6 +1263,8 @@ int nghttp2_hd_inflate_change_table_size(
return NGHTTP2_ERR_INVALID_STATE; return NGHTTP2_ERR_INVALID_STATE;
} }
inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size;
/* It seems that encoder is not required to send dynamic table size /* It seems that encoder is not required to send dynamic table size
update if the table size is not changed after applying update if the table size is not changed after applying
SETTINGS_HEADER_TABLE_SIZE. RFC 7541 is ambiguous here, but this SETTINGS_HEADER_TABLE_SIZE. RFC 7541 is ambiguous here, but this
@ -1275,13 +1277,12 @@ int nghttp2_hd_inflate_change_table_size(
/* Remember minimum value, and validate that encoder sends the /* Remember minimum value, and validate that encoder sends the
value less than or equal to this. */ value less than or equal to this. */
inflater->min_hd_table_bufsize_max = settings_max_dynamic_table_size; inflater->min_hd_table_bufsize_max = settings_max_dynamic_table_size;
}
inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size;
inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size; inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size;
hd_context_shrink_table_size(&inflater->ctx, NULL); hd_context_shrink_table_size(&inflater->ctx, NULL);
}
return 0; return 0;
} }

View File

@ -189,6 +189,7 @@ static int map_resize(nghttp2_map *map, uint32_t new_tablelen,
nghttp2_map_bucket *new_table; nghttp2_map_bucket *new_table;
nghttp2_map_bucket *bkt; nghttp2_map_bucket *bkt;
int rv; int rv;
(void)rv;
new_table = new_table =
nghttp2_mem_calloc(map->mem, new_tablelen, sizeof(nghttp2_map_bucket)); nghttp2_mem_calloc(map->mem, new_tablelen, sizeof(nghttp2_map_bucket));

View File

@ -42,7 +42,7 @@
#if defined(WIN32) #if defined(WIN32)
/* Windows requires ws2_32 library for ntonl family functions. We /* Windows requires ws2_32 library for ntonl family functions. We
define inline functions for those function so that we don't have define inline functions for those function so that we don't have
dependeny on that lib. */ dependency on that lib. */
# ifdef _MSC_VER # ifdef _MSC_VER
# define STIN static __inline # define STIN static __inline

View File

@ -111,7 +111,7 @@ struct nghttp2_outbound_item {
to this structure to avoid frequent memory allocation. */ to this structure to avoid frequent memory allocation. */
nghttp2_ext_frame_payload ext_frame_payload; nghttp2_ext_frame_payload ext_frame_payload;
nghttp2_aux_data aux_data; nghttp2_aux_data aux_data;
/* The priority used in priority comparion. Smaller is served /* The priority used in priority comparison. Smaller is served
earlier. For PING, SETTINGS and non-DATA frames (excluding earlier. For PING, SETTINGS and non-DATA frames (excluding
response HEADERS frame) have dedicated cycle value defined above. response HEADERS frame) have dedicated cycle value defined above.
For DATA frame, cycle is computed by taking into account of For DATA frame, cycle is computed by taking into account of

View File

@ -114,7 +114,7 @@ typedef int (*nghttp2_pq_item_cb)(nghttp2_pq_entry *item, void *arg);
void nghttp2_pq_update(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg); void nghttp2_pq_update(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg);
/* /*
* Applys |fun| to each item in |pq|. The |arg| is passed as arg * Applies |fun| to each item in |pq|. The |arg| is passed as arg
* parameter to callback function. This function must not change the * parameter to callback function. This function must not change the
* ordering key. If the return value from callback is nonzero, this * ordering key. If the return value from callback is nonzero, this
* function returns 1 immediately without iterating remaining items. * function returns 1 immediately without iterating remaining items.

View File

@ -5341,7 +5341,7 @@ static ssize_t inbound_frame_compute_pad(nghttp2_inbound_frame *iframe) {
/* /*
* This function returns the effective payload length in the data of * This function returns the effective payload length in the data of
* length |readlen| when the remaning payload is |payloadleft|. The * length |readlen| when the remaining payload is |payloadleft|. The
* |payloadleft| does not include |readlen|. If padding was started * |payloadleft| does not include |readlen|. If padding was started
* strictly before this data chunk, this function returns -1. * strictly before this data chunk, this function returns -1.
*/ */

View File

@ -408,7 +408,7 @@ int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id,
uint32_t error_code); uint32_t error_code);
/* /*
* Adds PING frame. This is a convenient functin built on top of * Adds PING frame. This is a convenient function built on top of
* nghttp2_session_add_frame() to add PING easily. * nghttp2_session_add_frame() to add PING easily.
* *
* If the |opaque_data| is not NULL, it must point to 8 bytes memory * If the |opaque_data| is not NULL, it must point to 8 bytes memory

View File

@ -33,7 +33,7 @@
#include "nghttp2_frame.h" #include "nghttp2_frame.h"
/* Maximum distance between any two stream's cycle in the same /* Maximum distance between any two stream's cycle in the same
prirority queue. Imagine stream A's cycle is A, and stream B's priority queue. Imagine stream A's cycle is A, and stream B's
cycle is B, and A < B. The cycle is unsigned 32 bit integer, it cycle is B, and A < B. The cycle is unsigned 32 bit integer, it
may get overflow. Because of how we calculate the next cycle may get overflow. Because of how we calculate the next cycle
value, if B - A is less than or equals to value, if B - A is less than or equals to

View File

@ -492,8 +492,6 @@ int nghttp2_session_set_local_window_size(nghttp2_session *session,
return nghttp2_session_update_recv_stream_window_size(session, stream, 0, return nghttp2_session_update_recv_stream_window_size(session, stream, 0,
1); 1);
} }
return 0;
} }
int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags, int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags,

View File

@ -67,7 +67,7 @@
# modified version of the Autoconf Macro, you may extend this special # modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well. # exception to the GPL to apply to your modified version as well.
#serial 21 #serial 23
AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL])
AC_DEFUN([AX_PYTHON_DEVEL],[ AC_DEFUN([AX_PYTHON_DEVEL],[
@ -135,27 +135,45 @@ variable to configure. See ``configure --help'' for reference.
# #
# Check if you have distutils, else fail # Check if you have distutils, else fail
# #
AC_MSG_CHECKING([for the distutils Python package]) AC_MSG_CHECKING([for the sysconfig Python package])
ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1`
if test $? -eq 0; then if test $? -eq 0; then
AC_MSG_RESULT([yes]) AC_MSG_RESULT([yes])
IMPORT_SYSCONFIG="import sysconfig"
else else
AC_MSG_RESULT([no]) AC_MSG_RESULT([no])
AC_MSG_CHECKING([for the distutils Python package])
ac_sysconfig_result=`$PYTHON -c "from distutils import sysconfig" 2>&1`
if test $? -eq 0; then
AC_MSG_RESULT([yes])
IMPORT_SYSCONFIG="from distutils import sysconfig"
else
AC_MSG_ERROR([cannot import Python module "distutils". AC_MSG_ERROR([cannot import Python module "distutils".
Please check your Python installation. The error was: Please check your Python installation. The error was:
$ac_distutils_result]) $ac_sysconfig_result])
PYTHON_VERSION="" PYTHON_VERSION=""
fi fi
fi
# #
# Check for Python include path # Check for Python include path
# #
AC_MSG_CHECKING([for Python include path]) AC_MSG_CHECKING([for Python include path])
if test -z "$PYTHON_CPPFLAGS"; then if test -z "$PYTHON_CPPFLAGS"; then
python_path=`$PYTHON -c "import distutils.sysconfig; \ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
print (distutils.sysconfig.get_python_inc ());"` # sysconfig module has different functions
plat_python_path=`$PYTHON -c "import distutils.sysconfig; \ python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (distutils.sysconfig.get_python_inc (plat_specific=1));"` print (sysconfig.get_path ('include'));"`
plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (sysconfig.get_path ('platinclude'));"`
else
# old distutils way
python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (sysconfig.get_python_inc ());"`
plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (sysconfig.get_python_inc (plat_specific=1));"`
fi
if test -n "${python_path}"; then if test -n "${python_path}"; then
if test "${plat_python_path}" != "${python_path}"; then if test "${plat_python_path}" != "${python_path}"; then
python_path="-I$python_path -I$plat_python_path" python_path="-I$python_path -I$plat_python_path"
@ -179,7 +197,7 @@ $ac_distutils_result])
# join all versioning strings, on some systems # join all versioning strings, on some systems
# major/minor numbers could be in different list elements # major/minor numbers could be in different list elements
from distutils.sysconfig import * from sysconfig import *
e = get_config_var('VERSION') e = get_config_var('VERSION')
if e is not None: if e is not None:
print(e) print(e)
@ -202,8 +220,8 @@ EOD`
ac_python_libdir=`cat<<EOD | $PYTHON - ac_python_libdir=`cat<<EOD | $PYTHON -
# There should be only one # There should be only one
import distutils.sysconfig $IMPORT_SYSCONFIG
e = distutils.sysconfig.get_config_var('LIBDIR') e = sysconfig.get_config_var('LIBDIR')
if e is not None: if e is not None:
print (e) print (e)
EOD` EOD`
@ -211,8 +229,8 @@ EOD`
# Now, for the library: # Now, for the library:
ac_python_library=`cat<<EOD | $PYTHON - ac_python_library=`cat<<EOD | $PYTHON -
import distutils.sysconfig $IMPORT_SYSCONFIG
c = distutils.sysconfig.get_config_vars() c = sysconfig.get_config_vars()
if 'LDVERSION' in c: if 'LDVERSION' in c:
print ('python'+c[['LDVERSION']]) print ('python'+c[['LDVERSION']])
else: else:
@ -231,7 +249,7 @@ EOD`
else else
# old way: use libpython from python_configdir # old way: use libpython from python_configdir
ac_python_libdir=`$PYTHON -c \ ac_python_libdir=`$PYTHON -c \
"from distutils.sysconfig import get_python_lib as f; \ "from sysconfig import get_python_lib as f; \
import os; \ import os; \
print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"` print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"`
PYTHON_LIBS="-L$ac_python_libdir -lpython$ac_python_version" PYTHON_LIBS="-L$ac_python_libdir -lpython$ac_python_version"
@ -252,19 +270,42 @@ EOD`
# #
AC_MSG_CHECKING([for Python site-packages path]) AC_MSG_CHECKING([for Python site-packages path])
if test -z "$PYTHON_SITE_PKG"; then if test -z "$PYTHON_SITE_PKG"; then
PYTHON_SITE_PKG=`$PYTHON -c "import distutils.sysconfig; \ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
print (distutils.sysconfig.get_python_lib(0,0));"` PYTHON_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (sysconfig.get_path('purelib'));"`
else
# distutils.sysconfig way
PYTHON_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (sysconfig.get_python_lib(0,0));"`
fi
fi fi
AC_MSG_RESULT([$PYTHON_SITE_PKG]) AC_MSG_RESULT([$PYTHON_SITE_PKG])
AC_SUBST([PYTHON_SITE_PKG]) AC_SUBST([PYTHON_SITE_PKG])
#
# Check for platform-specific site packages
#
AC_MSG_CHECKING([for Python platform specific site-packages path])
if test -z "$PYTHON_SITE_PKG"; then
if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (sysconfig.get_path('platlib'));"`
else
# distutils.sysconfig way
PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
print (sysconfig.get_python_lib(1,0));"`
fi
fi
AC_MSG_RESULT([$PYTHON_PLATFORM_SITE_PKG])
AC_SUBST([PYTHON_PLATFORM_SITE_PKG])
# #
# libraries which must be linked in when embedding # libraries which must be linked in when embedding
# #
AC_MSG_CHECKING(python extra libraries) AC_MSG_CHECKING(python extra libraries)
if test -z "$PYTHON_EXTRA_LIBS"; then if test -z "$PYTHON_EXTRA_LIBS"; then
PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \ PYTHON_EXTRA_LIBS=`$PYTHON -c "$IMPORT_SYSCONFIG; \
conf = distutils.sysconfig.get_config_var; \ conf = sysconfig.get_config_var; \
print (conf('LIBS') + ' ' + conf('SYSLIBS'))"` print (conf('LIBS') + ' ' + conf('SYSLIBS'))"`
fi fi
AC_MSG_RESULT([$PYTHON_EXTRA_LIBS]) AC_MSG_RESULT([$PYTHON_EXTRA_LIBS])
@ -275,8 +316,8 @@ EOD`
# #
AC_MSG_CHECKING(python extra linking flags) AC_MSG_CHECKING(python extra linking flags)
if test -z "$PYTHON_EXTRA_LDFLAGS"; then if test -z "$PYTHON_EXTRA_LDFLAGS"; then
PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "import distutils.sysconfig; \ PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "$IMPORT_SYSCONFIG; \
conf = distutils.sysconfig.get_config_var; \ conf = sysconfig.get_config_var; \
print (conf('LINKFORSHARED'))"` print (conf('LINKFORSHARED'))"`
fi fi
AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS]) AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS])

View File

@ -701,7 +701,7 @@ cdef class _HTTP2SessionCoreBase:
if outbuflen == 0: if outbuflen == 0:
break break
if outbuflen < 0: if outbuflen < 0:
raise Exception('nghttp2_session_mem_send faild: {}'.format\ raise Exception('nghttp2_session_mem_send failed: {}'.format\
(_strerror(outbuflen))) (_strerror(outbuflen)))
self.transport.write(outbuf[:outbuflen]) self.transport.write(outbuf[:outbuflen])
@ -1057,8 +1057,7 @@ if asyncio:
"""HTTP/2 request (stream) handler base class. """HTTP/2 request (stream) handler base class.
The class is used to handle the HTTP/2 stream. By default, it does The class is used to handle the HTTP/2 stream. By default, it does
not nothing. It must be subclassed to handle each event callback nothing. It must be subclassed to handle each event callback method.
method.
The first callback method invoked is on_headers(). It is called The first callback method invoked is on_headers(). It is called
when HEADERS frame, which includes request header fields, is when HEADERS frame, which includes request header fields, is
@ -1084,7 +1083,7 @@ if asyncio:
address. address.
client_certificate client_certificate
May contain the client certifcate in its non-binary form May contain the client certificate in its non-binary form
stream_id stream_id
Stream ID of this stream Stream ID of this stream

View File

@ -2110,7 +2110,7 @@ int HttpServer::run() {
std::vector<unsigned char> next_proto; std::vector<unsigned char> next_proto;
if (!config_->no_tls) { if (!config_->no_tls) {
ssl_ctx = SSL_CTX_new(SSLv23_server_method()); ssl_ctx = SSL_CTX_new(TLS_server_method());
if (!ssl_ctx) { if (!ssl_ctx) {
std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl; std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1; return -1;
@ -2143,22 +2143,13 @@ int HttpServer::run() {
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER);
#ifndef OPENSSL_NO_EC #ifndef OPENSSL_NO_EC
// Disabled SSL_CTX_set_ecdh_auto, because computational cost of # if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L
// chosen curve is much higher than P-256. if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) {
std::cerr << "SSL_CTX_set1_curves_list failed: "
// SSL_CTX_set_ecdh_auto(ssl_ctx, 1);
// Use P-256, which is sufficiently secure at the time of this
// writing.
# if OPENSSL_3_0_0_API
auto ecdh = EVP_EC_gen("P-256");
if (ecdh == nullptr) {
std::cerr << "EC_KEY_new_by_curv_name failed: "
<< ERR_error_string(ERR_get_error(), nullptr); << ERR_error_string(ERR_get_error(), nullptr);
return -1; return -1;
} }
SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); # else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L)
EVP_PKEY_free(ecdh);
# else // !OPENSSL_3_0_0_API
auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (ecdh == nullptr) { if (ecdh == nullptr) {
std::cerr << "EC_KEY_new_by_curv_name failed: " std::cerr << "EC_KEY_new_by_curv_name failed: "
@ -2167,7 +2158,7 @@ int HttpServer::run() {
} }
SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
EC_KEY_free(ecdh); EC_KEY_free(ecdh);
# endif // !OPENSSL_3_0_0_API # endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L)
#endif // OPENSSL_NO_EC #endif // OPENSSL_NO_EC
if (!config_->dh_param_file.empty()) { if (!config_->dh_param_file.empty()) {
@ -2191,8 +2182,11 @@ int HttpServer::run() {
return -1; return -1;
} }
SSL_CTX_set_tmp_dh(ssl_ctx, dh); if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
EVP_PKEY_free(dh); std::cerr << "SSL_CTX_set0_tmp_dh_pkey failed: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
#else // !OPENSSL_3_0_0_API #else // !OPENSSL_3_0_0_API
auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);

View File

@ -47,6 +47,7 @@ AM_CPPFLAGS = \
@LIBEV_CFLAGS@ \ @LIBEV_CFLAGS@ \
@LIBNGHTTP3_CFLAGS@ \ @LIBNGHTTP3_CFLAGS@ \
@LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS@ \ @LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS@ \
@LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS@ \
@LIBNGTCP2_CFLAGS@ \ @LIBNGTCP2_CFLAGS@ \
@OPENSSL_CFLAGS@ \ @OPENSSL_CFLAGS@ \
@LIBCARES_CFLAGS@ \ @LIBCARES_CFLAGS@ \
@ -65,6 +66,7 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \
@LIBEV_LIBS@ \ @LIBEV_LIBS@ \
@LIBNGHTTP3_LIBS@ \ @LIBNGHTTP3_LIBS@ \
@LIBNGTCP2_CRYPTO_OPENSSL_LIBS@ \ @LIBNGTCP2_CRYPTO_OPENSSL_LIBS@ \
@LIBNGTCP2_CRYPTO_BORINGSSL_LIBS@ \
@LIBNGTCP2_LIBS@ \ @LIBNGTCP2_LIBS@ \
@OPENSSL_LIBS@ \ @OPENSSL_LIBS@ \
@LIBCARES_LIBS@ \ @LIBCARES_LIBS@ \

View File

@ -81,6 +81,7 @@ bool recorded(const std::chrono::steady_clock::time_point &t) {
} }
} // namespace } // namespace
#if OPENSSL_1_1_1_API
namespace { namespace {
std::ofstream keylog_file; std::ofstream keylog_file;
void keylog_callback(const SSL *ssl, const char *line) { void keylog_callback(const SSL *ssl, const char *line) {
@ -89,6 +90,7 @@ void keylog_callback(const SSL *ssl, const char *line) {
keylog_file.flush(); keylog_file.flush();
} }
} // namespace } // namespace
#endif // OPENSSL_1_1_1_API
Config::Config() Config::Config()
: ciphers(tls::DEFAULT_CIPHER_LIST), : ciphers(tls::DEFAULT_CIPHER_LIST),
@ -104,6 +106,7 @@ Config::Config()
max_concurrent_streams(1), max_concurrent_streams(1),
window_bits(30), window_bits(30),
connection_window_bits(30), connection_window_bits(30),
max_frame_size(16_k),
rate(0), rate(0),
rate_period(1.0), rate_period(1.0),
duration(0.0), duration(0.0),
@ -1514,7 +1517,8 @@ int get_ev_loop_flags() {
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
size_t rate, size_t max_samples, Config *config) size_t rate, size_t max_samples, Config *config)
: stats(req_todo, nclients), : randgen(util::make_mt19937()),
stats(req_todo, nclients),
loop(ev_loop_new(get_ev_loop_flags())), loop(ev_loop_new(get_ev_loop_flags())),
ssl_ctx(ssl_ctx), ssl_ctx(ssl_ctx),
config(config), config(config),
@ -2106,6 +2110,11 @@ Options:
http/1.1 is used, this specifies the number of HTTP http/1.1 is used, this specifies the number of HTTP
pipelining requests in-flight. pipelining requests in-flight.
Default: 1 Default: 1
-f, --max-frame-size=<SIZE>
Maximum frame size that the local endpoint is willing to
receive.
Default: )"
<< util::utos_unit(config.max_frame_size) << R"(
-w, --window-bits=<N> -w, --window-bits=<N>
Sets the stream level initial window size to (2**<N>)-1. Sets the stream level initial window size to (2**<N>)-1.
For QUIC, <N> is capped to 26 (roughly 64MiB). For QUIC, <N> is capped to 26 (roughly 64MiB).
@ -2119,7 +2128,7 @@ Options:
-H, --header=<HEADER> -H, --header=<HEADER>
Add/Override a header to the requests. Add/Override a header to the requests.
--ciphers=<SUITE> --ciphers=<SUITE>
Set allowed cipher list for TLSv1.2 or ealier. The Set allowed cipher list for TLSv1.2 or earlier. The
format of the string is described in OpenSSL ciphers(1). format of the string is described in OpenSSL ciphers(1).
Default: )" Default: )"
<< config.ciphers << R"( << config.ciphers << R"(
@ -2243,11 +2252,10 @@ Options:
to buffering. Status code is -1 for failed streams. to buffering. Status code is -1 for failed streams.
--qlog-file-base=<PATH> --qlog-file-base=<PATH>
Enable qlog output and specify base file name for qlogs. Enable qlog output and specify base file name for qlogs.
Qlog is emitted for each connection. Qlog is emitted for each connection. For a given base
For a given base name "base", each output file name name "base", each output file name becomes
becomes "base.M.N.qlog" where M is worker ID and N is "base.M.N.sqlog" where M is worker ID and N is client ID
client ID (e.g. "base.0.3.qlog"). (e.g. "base.0.3.sqlog"). Only effective in QUIC runs.
Only effective in QUIC runs.
--connect-to=<HOST>[:<PORT>] --connect-to=<HOST>[:<PORT>]
Host and port to connect instead of using the authority Host and port to connect instead of using the authority
in <URI>. in <URI>.
@ -2299,6 +2307,7 @@ int main(int argc, char **argv) {
{"threads", required_argument, nullptr, 't'}, {"threads", required_argument, nullptr, 't'},
{"max-concurrent-streams", required_argument, nullptr, 'm'}, {"max-concurrent-streams", required_argument, nullptr, 'm'},
{"window-bits", required_argument, nullptr, 'w'}, {"window-bits", required_argument, nullptr, 'w'},
{"max-frame-size", required_argument, nullptr, 'f'},
{"connection-window-bits", required_argument, nullptr, 'W'}, {"connection-window-bits", required_argument, nullptr, 'W'},
{"input-file", required_argument, nullptr, 'i'}, {"input-file", required_argument, nullptr, 'i'},
{"header", required_argument, nullptr, 'H'}, {"header", required_argument, nullptr, 'H'},
@ -2330,7 +2339,7 @@ int main(int argc, char **argv) {
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
auto c = getopt_long(argc, argv, auto c = getopt_long(argc, argv,
"hvW:c:d:m:n:p:t:w:H:i:r:T:N:D:B:", long_options, "hvW:c:d:m:n:p:t:w:f:H:i:r:T:N:D:B:", long_options,
&option_index); &option_index);
if (c == -1) { if (c == -1) {
break; break;
@ -2376,6 +2385,24 @@ int main(int argc, char **argv) {
} }
break; break;
} }
case 'f': {
auto n = util::parse_uint_with_unit(optarg);
if (n == -1) {
std::cerr << "--max-frame-size: bad option value: " << optarg
<< std::endl;
exit(EXIT_FAILURE);
}
if (static_cast<uint64_t>(n) < 16_k) {
std::cerr << "--max-frame-size: minimum 16384" << std::endl;
exit(EXIT_FAILURE);
}
if (static_cast<uint64_t>(n) > 16_m - 1) {
std::cerr << "--max-frame-size: maximum 16777215" << std::endl;
exit(EXIT_FAILURE);
}
config.max_frame_size = n;
break;
}
case 'H': { case 'H': {
char *header = optarg; char *header = optarg;
// Skip first possible ':' in the header name // Skip first possible ':' in the header name
@ -2809,7 +2836,7 @@ int main(int argc, char **argv) {
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, nullptr); sigaction(SIGPIPE, &act, nullptr);
auto ssl_ctx = SSL_CTX_new(SSLv23_client_method()); auto ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) { if (!ssl_ctx) {
std::cerr << "Failed to create SSL_CTX: " std::cerr << "Failed to create SSL_CTX: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl; << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
@ -2843,19 +2870,26 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) { if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) {
std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr) << " failed: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl; << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
#endif // OPENSSL_1_1_1_API #endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
#if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) { if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) {
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
#else // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
if (SSL_CTX_set1_curves_list(ssl_ctx, config.groups.c_str()) != 1) {
std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl;
exit(EXIT_FAILURE);
}
#endif // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
#ifndef OPENSSL_NO_NEXTPROTONEG #ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,

View File

@ -95,6 +95,7 @@ struct Config {
ssize_t max_concurrent_streams; ssize_t max_concurrent_streams;
size_t window_bits; size_t window_bits;
size_t connection_window_bits; size_t connection_window_bits;
size_t max_frame_size;
// rate at which connections should be made // rate at which connections should be made
size_t rate; size_t rate;
ev_tstamp rate_period; ev_tstamp rate_period;
@ -269,6 +270,7 @@ struct Sampling {
struct Worker { struct Worker {
MemchunkPool mcpool; MemchunkPool mcpool;
std::mt19937 randgen;
Stats stats; Stats stats;
Sampling request_times_smp; Sampling request_times_smp;
Sampling client_smp; Sampling client_smp;
@ -474,8 +476,10 @@ struct Client {
int quic_stream_stop_sending(int64_t stream_id, uint64_t app_error_code); int quic_stream_stop_sending(int64_t stream_id, uint64_t app_error_code);
int quic_extend_max_local_streams(); int quic_extend_max_local_streams();
int quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, int quic_on_rx_secret(ngtcp2_crypto_level level, const uint8_t *secret,
const uint8_t *tx_secret, size_t secretlen); size_t secretlen);
int quic_on_tx_secret(ngtcp2_crypto_level level, const uint8_t *secret,
size_t secretlen);
void quic_set_tls_alert(uint8_t alert); void quic_set_tls_alert(uint8_t alert);
void quic_write_client_handshake(ngtcp2_crypto_level level, void quic_write_client_handshake(ngtcp2_crypto_level level,

View File

@ -215,7 +215,7 @@ void Http2Session::on_connect() {
nghttp2_option_del(opt); nghttp2_option_del(opt);
std::array<nghttp2_settings_entry, 3> iv; std::array<nghttp2_settings_entry, 4> iv;
size_t niv = 2; size_t niv = 2;
iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
iv[0].value = 0; iv[0].value = 0;
@ -227,6 +227,11 @@ void Http2Session::on_connect() {
iv[niv].value = config->header_table_size; iv[niv].value = config->header_table_size;
++niv; ++niv;
} }
if (config->max_frame_size != 16_k) {
iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
iv[niv].value = config->max_frame_size;
++niv;
}
rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(), niv); rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(), niv);

View File

@ -213,19 +213,17 @@ void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name,
} }
namespace { namespace {
int send_stop_sending(nghttp3_conn *conn, int64_t stream_id, int stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
uint64_t app_error_code, void *user_data, void *user_data, void *stream_user_data) {
void *stream_user_data) {
auto s = static_cast<Http3Session *>(user_data); auto s = static_cast<Http3Session *>(user_data);
if (s->send_stop_sending(stream_id, app_error_code) != 0) { if (s->stop_sending(stream_id, app_error_code) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE; return NGHTTP3_ERR_CALLBACK_FAILURE;
} }
return 0; return 0;
} }
} // namespace } // namespace
int Http3Session::send_stop_sending(int64_t stream_id, int Http3Session::stop_sending(int64_t stream_id, uint64_t app_error_code) {
uint64_t app_error_code) {
auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, stream_id, auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, stream_id,
app_error_code); app_error_code);
if (rv != 0) { if (rv != 0) {
@ -300,14 +298,14 @@ int Http3Session::init_conn() {
nullptr, // begin_trailers nullptr, // begin_trailers
h2load::recv_header, h2load::recv_header,
nullptr, // end_trailers nullptr, // end_trailers
h2load::send_stop_sending, h2load::stop_sending,
}; };
auto config = client_->worker->config; auto config = client_->worker->config;
nghttp3_settings settings; nghttp3_settings settings;
nghttp3_settings_default(&settings); nghttp3_settings_default(&settings);
settings.qpack_max_table_capacity = config->header_table_size; settings.qpack_max_dtable_capacity = config->header_table_size;
settings.qpack_blocked_streams = 100; settings.qpack_blocked_streams = 100;
auto mem = nghttp3_mem_default(); auto mem = nghttp3_mem_default();

View File

@ -51,7 +51,7 @@ public:
void begin_headers(int64_t stream_id); void begin_headers(int64_t stream_id);
void recv_header(int64_t stream_id, const nghttp3_vec *name, void recv_header(int64_t stream_id, const nghttp3_vec *name,
const nghttp3_vec *value); const nghttp3_vec *value);
int send_stop_sending(int64_t stream_id, uint64_t app_error_code); int stop_sending(int64_t stream_id, uint64_t app_error_code);
int close_stream(int64_t stream_id, uint64_t app_error_code); int close_stream(int64_t stream_id, uint64_t app_error_code);
int shutdown_stream_read(int64_t stream_id); int shutdown_stream_read(int64_t stream_id);

View File

@ -28,18 +28,20 @@
#include <iostream> #include <iostream>
#ifdef HAVE_LIBNGTCP2_CRYPTO_OPENSSL
# include <ngtcp2/ngtcp2_crypto_openssl.h> # include <ngtcp2/ngtcp2_crypto_openssl.h>
#endif // HAVE_LIBNGTCP2_CRYPTO_OPENSSL
#ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
# include <ngtcp2/ngtcp2_crypto_boringssl.h>
#endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/rand.h>
#include "h2load_http3_session.h" #include "h2load_http3_session.h"
namespace h2load { namespace h2load {
namespace {
auto randgen = util::make_mt19937();
} // namespace
namespace { namespace {
int handshake_completed(ngtcp2_conn *conn, void *user_data) { int handshake_completed(ngtcp2_conn *conn, void *user_data) {
auto c = static_cast<Client *>(user_data); auto c = static_cast<Client *>(user_data);
@ -118,7 +120,7 @@ int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
} }
if (c->quic_stream_close(stream_id, app_error_code) != 0) { if (c->quic_stream_close(stream_id, app_error_code) != 0) {
return -1; return NGTCP2_ERR_CALLBACK_FAILURE;
} }
return 0; return 0;
} }
@ -138,7 +140,7 @@ int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
void *stream_user_data) { void *stream_user_data) {
auto c = static_cast<Client *>(user_data); auto c = static_cast<Client *>(user_data);
if (c->quic_stream_reset(stream_id, app_error_code) != 0) { if (c->quic_stream_reset(stream_id, app_error_code) != 0) {
return -1; return NGTCP2_ERR_CALLBACK_FAILURE;
} }
return 0; return 0;
} }
@ -158,7 +160,7 @@ int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
void *stream_user_data) { void *stream_user_data) {
auto c = static_cast<Client *>(user_data); auto c = static_cast<Client *>(user_data);
if (c->quic_stream_stop_sending(stream_id, app_error_code) != 0) { if (c->quic_stream_stop_sending(stream_id, app_error_code) != 0) {
return -1; return NGTCP2_ERR_CALLBACK_FAILURE;
} }
return 0; return 0;
} }
@ -197,13 +199,15 @@ int Client::quic_extend_max_local_streams() {
namespace { namespace {
int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
size_t cidlen, void *user_data) { size_t cidlen, void *user_data) {
auto dis = std::uniform_int_distribution<uint8_t>( if (RAND_bytes(cid->data, cidlen) != 1) {
0, std::numeric_limits<uint8_t>::max()); return NGTCP2_ERR_CALLBACK_FAILURE;
auto f = [&dis]() { return dis(randgen); }; }
std::generate_n(cid->data, cidlen, f);
cid->datalen = cidlen; cid->datalen = cidlen;
std::generate_n(token, NGTCP2_STATELESS_RESET_TOKENLEN, f);
if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0; return 0;
} }
@ -222,11 +226,14 @@ void debug_log_printf(void *user_data, const char *fmt, ...) {
} // namespace } // namespace
namespace { namespace {
void generate_cid(ngtcp2_cid &dest) { int generate_cid(ngtcp2_cid &dest) {
auto dis = std::uniform_int_distribution<uint8_t>(
0, std::numeric_limits<uint8_t>::max());
dest.datalen = 8; dest.datalen = 8;
std::generate_n(dest.data, dest.datalen, [&dis]() { return dis(randgen); });
if (RAND_bytes(dest.data, dest.datalen) != 1) {
return -1;
}
return 0;
} }
} // namespace } // namespace
@ -236,15 +243,19 @@ ngtcp2_tstamp timestamp(struct ev_loop *loop) {
} }
} // namespace } // namespace
#ifdef HAVE_LIBNGTCP2_CRYPTO_OPENSSL
namespace { namespace {
int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
const uint8_t *rx_secret, const uint8_t *tx_secret, const uint8_t *rx_secret, const uint8_t *tx_secret,
size_t secret_len) { size_t secret_len) {
auto c = static_cast<Client *>(SSL_get_app_data(ssl)); auto c = static_cast<Client *>(SSL_get_app_data(ssl));
auto level = ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level);
if (c->quic_on_key( if (c->quic_on_rx_secret(level, rx_secret, secret_len) != 0) {
ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level), return 0;
rx_secret, tx_secret, secret_len) != 0) { }
if (c->quic_on_tx_secret(level, tx_secret, secret_len) != 0) {
return 0; return 0;
} }
@ -282,6 +293,70 @@ auto quic_method = SSL_QUIC_METHOD{
send_alert, send_alert,
}; };
} // namespace } // namespace
#endif // HAVE_LIBNGTCP2_CRYPTO_OPENSSL
#ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
namespace {
int set_read_secret(SSL *ssl, ssl_encryption_level_t ssl_level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secretlen) {
auto c = static_cast<Client *>(SSL_get_app_data(ssl));
if (c->quic_on_rx_secret(
ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level), secret,
secretlen) != 0) {
return 0;
}
return 1;
}
} // namespace
namespace {
int set_write_secret(SSL *ssl, ssl_encryption_level_t ssl_level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secretlen) {
auto c = static_cast<Client *>(SSL_get_app_data(ssl));
if (c->quic_on_tx_secret(
ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level), secret,
secretlen) != 0) {
return 0;
}
return 1;
}
} // namespace
namespace {
int add_handshake_data(SSL *ssl, ssl_encryption_level_t ssl_level,
const uint8_t *data, size_t len) {
auto c = static_cast<Client *>(SSL_get_app_data(ssl));
c->quic_write_client_handshake(
ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level), data, len);
return 1;
}
} // namespace
namespace {
int flush_flight(SSL *ssl) { return 1; }
} // namespace
namespace {
int send_alert(SSL *ssl, ssl_encryption_level_t level, uint8_t alert) {
auto c = static_cast<Client *>(SSL_get_app_data(ssl));
c->quic_set_tls_alert(alert);
return 1;
}
} // namespace
namespace {
auto quic_method = SSL_QUIC_METHOD{
set_read_secret, set_write_secret, add_handshake_data,
flush_flight, send_alert,
};
} // namespace
#endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
// qlog write callback -- excerpted from ngtcp2/examples/client_base.cc // qlog write callback -- excerpted from ngtcp2/examples/client_base.cc
namespace { namespace {
@ -297,6 +372,13 @@ void Client::quic_write_qlog(const void *data, size_t datalen) {
fwrite(data, 1, datalen, quic.qlog_file); fwrite(data, 1, datalen, quic.qlog_file);
} }
namespace {
void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
util::random_bytes(dest, dest + destlen,
*static_cast<std::mt19937 *>(rand_ctx->native_handle));
}
} // namespace
int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
const sockaddr *remote_addr, socklen_t remote_addrlen) { const sockaddr *remote_addr, socklen_t remote_addrlen) {
int rv; int rv;
@ -327,7 +409,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
ngtcp2_crypto_recv_retry_cb, ngtcp2_crypto_recv_retry_cb,
h2load::extend_max_local_streams_bidi, h2load::extend_max_local_streams_bidi,
nullptr, // extend_max_local_streams_uni nullptr, // extend_max_local_streams_uni
nullptr, // rand h2load::rand,
get_new_connection_id, get_new_connection_id,
nullptr, // remove_connection_id nullptr, // remove_connection_id
ngtcp2_crypto_update_key_cb, ngtcp2_crypto_update_key_cb,
@ -345,13 +427,17 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
nullptr, // recv_datagram nullptr, // recv_datagram
nullptr, // ack_datagram nullptr, // ack_datagram
nullptr, // lost_datagram nullptr, // lost_datagram
nullptr, // get_path_challenge_data ngtcp2_crypto_get_path_challenge_data_cb,
h2load::stream_stop_sending, h2load::stream_stop_sending,
}; };
ngtcp2_cid scid, dcid; ngtcp2_cid scid, dcid;
generate_cid(scid); if (generate_cid(scid) != 0) {
generate_cid(dcid); return -1;
}
if (generate_cid(dcid) != 0) {
return -1;
}
auto config = worker->config; auto config = worker->config;
@ -361,6 +447,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
settings.log_printf = debug_log_printf; settings.log_printf = debug_log_printf;
} }
settings.initial_ts = timestamp(worker->loop); settings.initial_ts = timestamp(worker->loop);
settings.rand_ctx.native_handle = &worker->randgen;
if (!config->qlog_file_base.empty()) { if (!config->qlog_file_base.empty()) {
assert(quic.qlog_file == nullptr); assert(quic.qlog_file == nullptr);
auto path = config->qlog_file_base; auto path = config->qlog_file_base;
@ -368,7 +455,7 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
path += util::utos(worker->id); path += util::utos(worker->id);
path += '.'; path += '.';
path += util::utos(id); path += util::utos(id);
path += ".qlog"; path += ".sqlog";
quic.qlog_file = fopen(path.c_str(), "w"); quic.qlog_file = fopen(path.c_str(), "w");
if (quic.qlog_file == nullptr) { if (quic.qlog_file == nullptr) {
std::cerr << "Failed to open a qlog file: " << path << std::endl; std::cerr << "Failed to open a qlog file: " << path << std::endl;
@ -393,8 +480,14 @@ int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
params.max_idle_timeout = 30 * NGTCP2_SECONDS; params.max_idle_timeout = 30 * NGTCP2_SECONDS;
auto path = ngtcp2_path{ auto path = ngtcp2_path{
{local_addrlen, const_cast<sockaddr *>(local_addr)}, {
{remote_addrlen, const_cast<sockaddr *>(remote_addr)}, const_cast<sockaddr *>(local_addr),
local_addrlen,
},
{
const_cast<sockaddr *>(remote_addr),
remote_addrlen,
},
}; };
assert(config->npn_list.size()); assert(config->npn_list.size());
@ -442,15 +535,16 @@ void Client::quic_close_connection() {
case quic::ErrorType::Transport: case quic::ErrorType::Transport:
nwrite = ngtcp2_conn_write_connection_close( nwrite = ngtcp2_conn_write_connection_close(
quic.conn, &ps.path, nullptr, buf.data(), buf.size(), quic.conn, &ps.path, nullptr, buf.data(), buf.size(),
quic.last_error.code, timestamp(worker->loop)); quic.last_error.code, nullptr, 0, timestamp(worker->loop));
break; break;
case quic::ErrorType::Application: case quic::ErrorType::Application:
nwrite = ngtcp2_conn_write_application_close( nwrite = ngtcp2_conn_write_application_close(
quic.conn, &ps.path, nullptr, buf.data(), buf.size(), quic.conn, &ps.path, nullptr, buf.data(), buf.size(),
quic.last_error.code, timestamp(worker->loop)); quic.last_error.code, nullptr, 0, timestamp(worker->loop));
break; break;
default: default:
assert(0); assert(0);
abort();
} }
if (nwrite < 0) { if (nwrite < 0) {
@ -461,24 +555,16 @@ void Client::quic_close_connection() {
ps.path.remote.addrlen, buf.data(), nwrite, 0); ps.path.remote.addrlen, buf.data(), nwrite, 0);
} }
int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret, int Client::quic_on_rx_secret(ngtcp2_crypto_level level, const uint8_t *secret,
const uint8_t *tx_secret, size_t secretlen) { size_t secretlen) {
if (ngtcp2_crypto_derive_and_install_rx_key(quic.conn, nullptr, nullptr, if (ngtcp2_crypto_derive_and_install_rx_key(quic.conn, nullptr, nullptr,
nullptr, level, rx_secret, nullptr, level, secret,
secretlen) != 0) { secretlen) != 0) {
std::cerr << "ngtcp2_crypto_derive_and_install_rx_key() failed" std::cerr << "ngtcp2_crypto_derive_and_install_rx_key() failed"
<< std::endl; << std::endl;
return -1; return -1;
} }
if (ngtcp2_crypto_derive_and_install_tx_key(quic.conn, nullptr, nullptr,
nullptr, level, tx_secret,
secretlen) != 0) {
std::cerr << "ngtcp2_crypto_derive_and_install_tx_key() failed"
<< std::endl;
return -1;
}
if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { if (level == NGTCP2_CRYPTO_LEVEL_APPLICATION) {
auto s = std::make_unique<Http3Session>(this); auto s = std::make_unique<Http3Session>(this);
if (s->init_conn() == -1) { if (s->init_conn() == -1) {
@ -490,6 +576,19 @@ int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret,
return 0; return 0;
} }
int Client::quic_on_tx_secret(ngtcp2_crypto_level level, const uint8_t *secret,
size_t secretlen) {
if (ngtcp2_crypto_derive_and_install_tx_key(quic.conn, nullptr, nullptr,
nullptr, level, secret,
secretlen) != 0) {
std::cerr << "ngtcp2_crypto_derive_and_install_tx_key() failed"
<< std::endl;
return -1;
}
return 0;
}
void Client::quic_set_tls_alert(uint8_t alert) { void Client::quic_set_tls_alert(uint8_t alert) {
quic.last_error = quic::err_transport_tls(alert); quic.last_error = quic::err_transport_tls(alert);
} }
@ -554,14 +653,25 @@ int Client::read_quic() {
++worker->stats.udp_dgram_recv; ++worker->stats.udp_dgram_recv;
auto path = ngtcp2_path{ auto path = ngtcp2_path{
{local_addr.len, &local_addr.su.sa}, {
{addrlen, &su.sa}, &local_addr.su.sa,
local_addr.len,
},
{
&su.sa,
addrlen,
},
}; };
rv = ngtcp2_conn_read_pkt(quic.conn, &path, &pi, buf.data(), nread, rv = ngtcp2_conn_read_pkt(quic.conn, &path, &pi, buf.data(), nread,
timestamp(worker->loop)); timestamp(worker->loop));
if (rv != 0) { if (rv != 0) {
std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
if (!quic.last_error.code) {
quic.last_error = quic::err_transport(rv);
}
return -1; return -1;
} }

View File

@ -916,7 +916,7 @@ void test_http2_rewrite_clean_path(void) {
CU_ASSERT("/delta%3A" == http2::rewrite_clean_path( CU_ASSERT("/delta%3A" == http2::rewrite_clean_path(
balloc, StringRef::from_lit("/delta%3a"))); balloc, StringRef::from_lit("/delta%3a")));
// path component is normalized before mathcing // path component is normalized before matching
CU_ASSERT( CU_ASSERT(
"/alpha/bravo/" == "/alpha/bravo/" ==
http2::rewrite_clean_path( http2::rewrite_clean_path(

View File

@ -2268,7 +2268,7 @@ int communicate(
auto loop = EV_DEFAULT; auto loop = EV_DEFAULT;
SSL_CTX *ssl_ctx = nullptr; SSL_CTX *ssl_ctx = nullptr;
if (scheme == "https") { if (scheme == "https") {
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) { if (!ssl_ctx) {
std::cerr << "[ERROR] Failed to create SSL_CTX: " std::cerr << "[ERROR] Failed to create SSL_CTX: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl; << ERR_error_string(ERR_get_error(), nullptr) << std::endl;

View File

@ -76,6 +76,11 @@
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>
#ifdef ENABLE_HTTP3
# include <ngtcp2/ngtcp2.h>
# include <nghttp3/nghttp3.h>
#endif // ENABLE_HTTP3
#include "shrpx_config.h" #include "shrpx_config.h"
#include "shrpx_tls.h" #include "shrpx_tls.h"
#include "shrpx_log_config.h" #include "shrpx_log_config.h"
@ -202,7 +207,8 @@ struct WorkerProcess {
) )
: loop(loop), : loop(loop),
worker_pid(worker_pid), worker_pid(worker_pid),
ipc_fd(ipc_fd) ipc_fd(ipc_fd),
termination_deadline(0.)
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
, ,
quic_ipc_fd(quic_ipc_fd), quic_ipc_fd(quic_ipc_fd),
@ -264,6 +270,7 @@ struct WorkerProcess {
struct ev_loop *loop; struct ev_loop *loop;
pid_t worker_pid; pid_t worker_pid;
int ipc_fd; int ipc_fd;
ev_tstamp termination_deadline;
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
int quic_ipc_fd; int quic_ipc_fd;
std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes; std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes;
@ -278,6 +285,74 @@ namespace {
std::deque<std::unique_ptr<WorkerProcess>> worker_processes; std::deque<std::unique_ptr<WorkerProcess>> worker_processes;
} // namespace } // namespace
namespace {
ev_timer worker_process_grace_period_timer;
} // namespace
namespace {
void worker_process_grace_period_timercb(struct ev_loop *loop, ev_timer *w,
int revents) {
auto now = ev_now(loop);
ev_tstamp next_repeat = 0.;
for (auto it = std::begin(worker_processes);
it != std::end(worker_processes);) {
auto &wp = *it;
if (!(wp->termination_deadline > 0.)) {
++it;
continue;
}
auto d = wp->termination_deadline - now;
if (d > 0) {
if (!(next_repeat > 0.) || d < next_repeat) {
next_repeat = d;
}
++it;
continue;
}
LOG(NOTICE) << "Deleting worker process pid=" << wp->worker_pid
<< " because its grace shutdown period is over";
it = worker_processes.erase(it);
}
if (next_repeat > 0.) {
w->repeat = next_repeat;
ev_timer_again(loop, w);
return;
}
ev_timer_stop(loop, w);
}
} // namespace
namespace {
void worker_process_set_termination_deadline(WorkerProcess *wp,
struct ev_loop *loop) {
auto config = get_config();
if (!(config->worker_process_grace_shutdown_period > 0.)) {
return;
}
wp->termination_deadline =
ev_now(loop) + config->worker_process_grace_shutdown_period;
if (!ev_is_active(&worker_process_grace_period_timer)) {
worker_process_grace_period_timer.repeat =
config->worker_process_grace_shutdown_period;
ev_timer_again(loop, &worker_process_grace_period_timer);
}
}
} // namespace
namespace { namespace {
void worker_process_add(std::unique_ptr<WorkerProcess> wp) { void worker_process_add(std::unique_ptr<WorkerProcess> wp) {
worker_processes.push_back(std::move(wp)); worker_processes.push_back(std::move(wp));
@ -285,7 +360,7 @@ void worker_process_add(std::unique_ptr<WorkerProcess> wp) {
} // namespace } // namespace
namespace { namespace {
void worker_process_remove(const WorkerProcess *wp) { void worker_process_remove(const WorkerProcess *wp, struct ev_loop *loop) {
for (auto it = std::begin(worker_processes); it != std::end(worker_processes); for (auto it = std::begin(worker_processes); it != std::end(worker_processes);
++it) { ++it) {
auto &s = *it; auto &s = *it;
@ -295,28 +370,46 @@ void worker_process_remove(const WorkerProcess *wp) {
} }
worker_processes.erase(it); worker_processes.erase(it);
if (worker_processes.empty()) {
ev_timer_stop(loop, &worker_process_grace_period_timer);
}
break; break;
} }
} }
} // namespace } // namespace
namespace { namespace {
void worker_process_remove_all() { void worker_process_adjust_limit() {
auto config = get_config();
if (config->max_worker_processes &&
worker_processes.size() > config->max_worker_processes) {
worker_processes.pop_front();
}
}
} // namespace
namespace {
void worker_process_remove_all(struct ev_loop *loop) {
std::deque<std::unique_ptr<WorkerProcess>>().swap(worker_processes); std::deque<std::unique_ptr<WorkerProcess>>().swap(worker_processes);
ev_timer_stop(loop, &worker_process_grace_period_timer);
} }
} // namespace } // namespace
namespace { namespace {
// Send signal |signum| to all worker processes, and clears // Send signal |signum| to all worker processes, and clears
// worker_processes. // worker_processes.
void worker_process_kill(int signum) { void worker_process_kill(int signum, struct ev_loop *loop) {
for (auto &s : worker_processes) { for (auto &s : worker_processes) {
if (s->worker_pid == -1) { if (s->worker_pid == -1) {
continue; continue;
} }
kill(s->worker_pid, signum); kill(s->worker_pid, signum);
} }
worker_process_remove_all(); worker_process_remove_all(loop);
} }
} // namespace } // namespace
@ -635,13 +728,14 @@ void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
close(addr.fd); close(addr.fd);
} }
ipc_send(wp, SHRPX_IPC_GRACEFUL_SHUTDOWN); ipc_send(wp, SHRPX_IPC_GRACEFUL_SHUTDOWN);
worker_process_set_termination_deadline(wp, loop);
return; return;
} }
case RELOAD_SIGNAL: case RELOAD_SIGNAL:
reload_config(wp); reload_config(wp);
return; return;
default: default:
worker_process_kill(w->signum); worker_process_kill(w->signum, loop);
ev_break(loop); ev_break(loop);
return; return;
} }
@ -656,7 +750,7 @@ void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
auto pid = wp->worker_pid; auto pid = wp->worker_pid;
worker_process_remove(wp); worker_process_remove(wp, loop);
if (worker_process_last_pid() == pid) { if (worker_process_last_pid() == pid) {
ev_break(loop); ev_break(loop);
@ -1342,6 +1436,7 @@ int generate_cid_prefix(
std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> &cid_prefixes, std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> &cid_prefixes,
const Config *config) { const Config *config) {
auto &apiconf = config->api; auto &apiconf = config->api;
auto &quicconf = config->quic;
size_t num_cid_prefix; size_t num_cid_prefix;
if (config->single_thread) { if (config->single_thread) {
@ -1360,7 +1455,7 @@ int generate_cid_prefix(
cid_prefixes.resize(num_cid_prefix); cid_prefixes.resize(num_cid_prefix);
for (auto &cid_prefix : cid_prefixes) { for (auto &cid_prefix : cid_prefixes) {
if (create_cid_prefix(cid_prefix.data()) != 0) { if (create_cid_prefix(cid_prefix.data(), quicconf.server_id.data()) != 0) {
return -1; return -1;
} }
} }
@ -1469,7 +1564,7 @@ pid_t fork_worker_process(
// Remove all WorkerProcesses to stop any registered watcher on // Remove all WorkerProcesses to stop any registered watcher on
// default loop. // default loop.
worker_process_remove_all(); worker_process_remove_all(EV_DEFAULT);
close_unused_inherited_addr(iaddrs); close_unused_inherited_addr(iaddrs);
@ -1644,6 +1739,9 @@ int event_loop() {
return -1; return -1;
} }
ev_timer_init(&worker_process_grace_period_timer,
worker_process_grace_period_timercb, 0., 0.);
worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
, ,
@ -1670,6 +1768,8 @@ int event_loop() {
ev_run(loop, 0); ev_run(loop, 0);
ev_timer_stop(loop, &worker_process_grace_period_timer);
return 0; return 0;
} }
} // namespace } // namespace
@ -1854,6 +1954,16 @@ void fill_default_config(Config *config) {
auto &bpfconf = quicconf.bpf; auto &bpfconf = quicconf.bpf;
bpfconf.prog_file = StringRef::from_lit(PKGLIBDIR "/reuseport_kern.o"); bpfconf.prog_file = StringRef::from_lit(PKGLIBDIR "/reuseport_kern.o");
upstreamconf.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
upstreamconf.initial_rtt =
static_cast<ev_tstamp>(NGTCP2_DEFAULT_INITIAL_RTT) / NGTCP2_SECONDS;
}
if (RAND_bytes(quicconf.server_id.data(), quicconf.server_id.size()) != 1) {
assert(0);
abort();
} }
auto &http3conf = config->http3; auto &http3conf = config->http3;
@ -1948,14 +2058,18 @@ void fill_default_config(Config *config) {
namespace { namespace {
void print_version(std::ostream &out) { void print_version(std::ostream &out) {
out << "nghttpx nghttp2/" NGHTTP2_VERSION << std::endl; out << "nghttpx nghttp2/" NGHTTP2_VERSION
#ifdef ENABLE_HTTP3
" ngtcp2/" NGTCP2_VERSION " nghttp3/" NGHTTP3_VERSION
#endif // ENABLE_HTTP3
<< std::endl;
} }
} // namespace } // namespace
namespace { namespace {
void print_usage(std::ostream &out) { void print_usage(std::ostream &out) {
out << R"(Usage: nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>] out << R"(Usage: nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>]
A reverse proxy for HTTP/2, and HTTP/1.)" A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.)"
<< std::endl; << std::endl;
} }
} // namespace } // namespace
@ -2366,6 +2480,12 @@ Performance:
If 0 is given, nghttpx does not set the limit. If 0 is given, nghttpx does not set the limit.
Default: )" Default: )"
<< config->rlimit_nofile << R"( << config->rlimit_nofile << R"(
--rlimit-memlock=<N>
Set maximum number of bytes of memory that may be locked
into RAM. If 0 is given, nghttpx does not set the
limit.
Default: )"
<< config->rlimit_memlock << R"(
--backend-request-buffer=<SIZE> --backend-request-buffer=<SIZE>
Set buffer size used to store backend request. Set buffer size used to store backend request.
Default: )" Default: )"
@ -3054,7 +3174,7 @@ HTTP:
advertised in alt-svc header field only in HTTP/1.1 advertised in alt-svc header field only in HTTP/1.1
frontend. This option can be used multiple times to frontend. This option can be used multiple times to
specify multiple alternative services. specify multiple alternative services.
Example: --altsvc="h2,443,,,ma=3600; persist=1' Example: --altsvc="h2,443,,,ma=3600; persist=1"
--http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]> --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
Just like --altsvc option, but this altsvc is only sent Just like --altsvc option, but this altsvc is only sent
in HTTP/2 frontend. in HTTP/2 frontend.
@ -3185,6 +3305,27 @@ Process:
process. nghttpx still spawns additional process if process. nghttpx still spawns additional process if
neverbleed is used. In the single process mode, the neverbleed is used. In the single process mode, the
signal handling feature is disabled. signal handling feature is disabled.
--max-worker-processes=<N>
The maximum number of worker processes. nghttpx spawns
new worker process when it reloads its configuration.
The previous worker process enters graceful termination
period and will terminate when it finishes handling the
existing connections. However, if reloading
configurations happen very frequently, the worker
processes might be piled up if they take a bit long time
to finish the existing connections. With this option,
if the number of worker processes exceeds the given
value, the oldest worker process is terminated
immediately. Specifying 0 means no limit and it is the
default behaviour.
--worker-process-grace-shutdown-period=<DURATION>
Maximum period for a worker process to terminate
gracefully. When a worker process enters in graceful
shutdown period (e.g., when nghttpx reloads its
configuration) and it does not finish handling the
existing connections in the given period of time, it is
immediately terminated. Specifying 0 means no limit and
it is the default behaviour.
Scripting: Scripting:
--mruby-file=<PATH> --mruby-file=<PATH>
@ -3221,7 +3362,57 @@ HTTP/3 and QUIC:
frontend QUIC connections. A qlog file is created per frontend QUIC connections. A qlog file is created per
each QUIC connection. The file name is ISO8601 basic each QUIC connection. The file name is ISO8601 basic
format, followed by "-", server Source Connection ID and format, followed by "-", server Source Connection ID and
".qlog". ".sqlog".
--frontend-quic-require-token
Require an address validation token for a frontend QUIC
connection. Server sends a token in Retry packet or
NEW_TOKEN frame in the previous connection.
--frontend-quic-congestion-controller=<CC>
Specify a congestion controller algorithm for a frontend
QUIC connection. <CC> should be either "cubic" or
"bbr".
Default: )"
<< (config->quic.upstream.congestion_controller == NGTCP2_CC_ALGO_CUBIC
? "cubic"
: "bbr")
<< R"(
--frontend-quic-secret-file=<PATH>
Path to file that contains secure random data to be used
as QUIC keying materials. It is used to derive keys for
encrypting tokens and Connection IDs. It is not used to
encrypt QUIC packets. Each line of this file must
contain exactly 136 bytes hex-encoded string (when
decoded the byte string is 68 bytes long). The first 2
bits of decoded byte string are used to identify the
keying material. An empty line or a line which starts
'#' is ignored. The file can contain more than one
keying materials. Because the identifier is 2 bits, at
most 4 keying materials are read and the remaining data
is discarded. The first keying material in the file is
primarily used for encryption and decryption for new
connection. The other ones are used to decrypt data for
the existing connections. Specifying multiple keying
materials enables key rotation. Please note that key
rotation does not occur automatically. User should
update files or change options values and restart
nghttpx gracefully. If opening or reading given file
fails, all loaded keying materials are discarded and it
is treated as if none of this option is given. If this
option is not given or an error occurred while opening
or reading a file, a keying material is generated
internally on startup and reload.
--quic-server-id=<HEXSTRING>
Specify server ID encoded in Connection ID to identify
this particular server instance. Connection ID is
encrypted and this part is not visible in public. It
must be 4 bytes long and must be encoded in hex string
(which is 8 bytes long). If this option is omitted, a
random server ID is generated on startup and
configuration reload.
--frontend-quic-initial-rtt=<DURATION>
Specify the initial RTT of the frontend QUIC connection.
Default: )"
<< util::duration_str(config->quic.upstream.initial_rtt) << R"(
--no-quic-bpf --no-quic-bpf
Disable eBPF. Disable eBPF.
--frontend-http3-window-size=<SIZE> --frontend-http3-window-size=<SIZE>
@ -3475,9 +3666,12 @@ int process_options(Config *config,
return -1; return -1;
} }
std::array<char, util::max_hostport> hostport_buf;
auto &proxy = config->downstream_http_proxy; auto &proxy = config->downstream_http_proxy;
if (!proxy.host.empty()) { if (!proxy.host.empty()) {
auto hostport = util::make_hostport(StringRef{proxy.host}, proxy.port); auto hostport = util::make_hostport(std::begin(hostport_buf),
StringRef{proxy.host}, proxy.port);
if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port, if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port,
AF_UNSPEC) == -1) { AF_UNSPEC) == -1) {
LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport; LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport;
@ -3490,7 +3684,8 @@ int process_options(Config *config,
{ {
auto &memcachedconf = tlsconf.session_cache.memcached; auto &memcachedconf = tlsconf.session_cache.memcached;
if (!memcachedconf.host.empty()) { if (!memcachedconf.host.empty()) {
auto hostport = util::make_hostport(StringRef{memcachedconf.host}, auto hostport = util::make_hostport(std::begin(hostport_buf),
StringRef{memcachedconf.host},
memcachedconf.port); memcachedconf.port);
if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(), if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
memcachedconf.port, memcachedconf.family) == -1) { memcachedconf.port, memcachedconf.family) == -1) {
@ -3511,7 +3706,8 @@ int process_options(Config *config,
{ {
auto &memcachedconf = tlsconf.ticket.memcached; auto &memcachedconf = tlsconf.ticket.memcached;
if (!memcachedconf.host.empty()) { if (!memcachedconf.host.empty()) {
auto hostport = util::make_hostport(StringRef{memcachedconf.host}, auto hostport = util::make_hostport(std::begin(hostport_buf),
StringRef{memcachedconf.host},
memcachedconf.port); memcachedconf.port);
if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(), if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
memcachedconf.port, memcachedconf.family) == -1) { memcachedconf.port, memcachedconf.family) == -1) {
@ -3538,6 +3734,18 @@ int process_options(Config *config,
} }
} }
#ifdef RLIMIT_MEMLOCK
if (config->rlimit_memlock) {
struct rlimit lim = {static_cast<rlim_t>(config->rlimit_memlock),
static_cast<rlim_t>(config->rlimit_memlock)};
if (setrlimit(RLIMIT_MEMLOCK, &lim) != 0) {
auto error = errno;
LOG(WARN) << "Setting rlimit-memlock failed: "
<< xsi_strerror(error, errbuf.data(), errbuf.size());
}
}
#endif // RLIMIT_MEMLOCK
auto &fwdconf = config->http.forwarded; auto &fwdconf = config->http.forwarded;
if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED && if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED &&
@ -3682,6 +3890,7 @@ void reload_config(WorkerProcess *wp) {
// Send last worker process a graceful shutdown notice // Send last worker process a graceful shutdown notice
auto &last_wp = worker_processes.back(); auto &last_wp = worker_processes.back();
ipc_send(last_wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN); ipc_send(last_wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN);
worker_process_set_termination_deadline(last_wp.get(), loop);
// We no longer use signals for this worker. // We no longer use signals for this worker.
last_wp->shutdown_signal_watchers(); last_wp->shutdown_signal_watchers();
@ -3692,6 +3901,8 @@ void reload_config(WorkerProcess *wp) {
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
)); ));
worker_process_adjust_limit();
if (!get_config()->pid_file.empty()) { if (!get_config()->pid_file.empty()) {
save_pid(); save_pid();
} }
@ -4011,6 +4222,19 @@ int main(int argc, char **argv) {
{SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180}, {SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180},
{SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag, {SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag,
181}, 181},
{SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN.c_str(), no_argument, &flag,
182},
{SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER.c_str(),
required_argument, &flag, 183},
{SHRPX_OPT_QUIC_SERVER_ID.c_str(), required_argument, &flag, 185},
{SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE.c_str(), required_argument, &flag,
186},
{SHRPX_OPT_RLIMIT_MEMLOCK.c_str(), required_argument, &flag, 187},
{SHRPX_OPT_MAX_WORKER_PROCESSES.c_str(), required_argument, &flag, 188},
{SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD.c_str(),
required_argument, &flag, 189},
{SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag,
190},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@ -4878,6 +5102,43 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR, cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR,
StringRef{optarg}); StringRef{optarg});
break; break;
case 182:
// --frontend-quic-require-token
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN,
StringRef::from_lit("yes"));
break;
case 183:
// --frontend-quic-congestion-controller
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER,
StringRef{optarg});
break;
case 185:
// --quic-server-id
cmdcfgs.emplace_back(SHRPX_OPT_QUIC_SERVER_ID, StringRef{optarg});
break;
case 186:
// --frontend-quic-secret-file
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE,
StringRef{optarg});
break;
case 187:
// --rlimit-memlock
cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_MEMLOCK, StringRef{optarg});
break;
case 188:
// --max-worker-processes
cmdcfgs.emplace_back(SHRPX_OPT_MAX_WORKER_PROCESSES, StringRef{optarg});
break;
case 189:
// --worker-process-grace-shutdown-period
cmdcfgs.emplace_back(SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD,
StringRef{optarg});
break;
case 190:
// --frontend-quic-initial-rtt
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT,
StringRef{optarg});
break;
default: default:
break; break;
} }

View File

@ -463,7 +463,7 @@ int APIDownstreamConnection::on_read() { return 0; }
int APIDownstreamConnection::on_write() { return 0; } int APIDownstreamConnection::on_write() { return 0; }
void APIDownstreamConnection::on_upstream_change(Upstream *uptream) {} void APIDownstreamConnection::on_upstream_change(Upstream *upstream) {}
bool APIDownstreamConnection::poolable() const { return false; } bool APIDownstreamConnection::poolable() const { return false; }

View File

@ -81,7 +81,7 @@ public:
virtual int on_read(); virtual int on_read();
virtual int on_write(); virtual int on_write();
virtual void on_upstream_change(Upstream *uptream); virtual void on_upstream_change(Upstream *upstream);
// true if this object is poolable. // true if this object is poolable.
virtual bool poolable() const; virtual bool poolable() const;

View File

@ -292,11 +292,12 @@ int ClientHandler::write_tls() {
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
int ClientHandler::read_quic(const UpstreamAddr *faddr, int ClientHandler::read_quic(const UpstreamAddr *faddr,
const Address &remote_addr, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, const Address &local_addr,
const ngtcp2_pkt_info &pi, const uint8_t *data,
size_t datalen) { size_t datalen) {
auto upstream = static_cast<Http3Upstream *>(upstream_.get()); auto upstream = static_cast<Http3Upstream *>(upstream_.get());
return upstream->on_read(faddr, remote_addr, local_addr, data, datalen); return upstream->on_read(faddr, remote_addr, local_addr, pi, data, datalen);
} }
int ClientHandler::write_quic() { return upstream_->on_write(); } int ClientHandler::write_quic() { return upstream_->on_write(); }
@ -517,7 +518,6 @@ void ClientHandler::setup_upstream_io_callback() {
void ClientHandler::setup_http3_upstream( void ClientHandler::setup_http3_upstream(
std::unique_ptr<Http3Upstream> &&upstream) { std::unique_ptr<Http3Upstream> &&upstream) {
upstream_ = std::move(upstream); upstream_ = std::move(upstream);
alpn_ = StringRef::from_lit("h3");
write_ = &ClientHandler::write_quic; write_ = &ClientHandler::write_quic;
auto config = get_config(); auto config = get_config();
@ -884,7 +884,6 @@ DownstreamAddr *ClientHandler::get_downstream_addr(int &err,
err = -1; err = -1;
return nullptr; return nullptr;
} }
aff_idx = i;
} }
return addr; return addr;
@ -1599,4 +1598,13 @@ StringRef ClientHandler::get_alpn() const { return alpn_; }
BlockAllocator &ClientHandler::get_block_allocator() { return balloc_; } BlockAllocator &ClientHandler::get_block_allocator() { return balloc_; }
void ClientHandler::set_alpn_from_conn() {
const unsigned char *alpn;
unsigned int alpnlen;
SSL_get0_alpn_selected(conn_.tls.ssl, &alpn, &alpnlen);
alpn_ = make_string_ref(balloc_, StringRef{alpn, alpnlen});
}
} // namespace shrpx } // namespace shrpx

View File

@ -149,7 +149,8 @@ public:
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
void setup_http3_upstream(std::unique_ptr<Http3Upstream> &&upstream); void setup_http3_upstream(std::unique_ptr<Http3Upstream> &&upstream);
int read_quic(const UpstreamAddr *faddr, const Address &remote_addr, int read_quic(const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, size_t datalen); const Address &local_addr, const ngtcp2_pkt_info &pi,
const uint8_t *data, size_t datalen);
int write_quic(); int write_quic();
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
@ -187,6 +188,8 @@ public:
BlockAllocator &get_block_allocator(); BlockAllocator &get_block_allocator();
void set_alpn_from_conn();
private: private:
// Allocator to allocate memory for connection-wide objects. Make // Allocator to allocate memory for connection-wide objects. Make
// sure that the allocations must be bounded, and not proportional // sure that the allocations must be bounded, and not proportional

View File

@ -230,10 +230,81 @@ read_tls_ticket_key_file(const std::vector<StringRef> &files,
return ticket_keys; return ticket_keys;
} }
#ifdef ENABLE_HTTP3
std::shared_ptr<QUICKeyingMaterials>
read_quic_secret_file(const StringRef &path) {
constexpr size_t expectedlen =
SHRPX_QUIC_SECRET_RESERVEDLEN + SHRPX_QUIC_SECRETLEN + SHRPX_QUIC_SALTLEN;
auto qkms = std::make_shared<QUICKeyingMaterials>();
auto &kms = qkms->keying_materials;
std::ifstream f(path.c_str());
if (!f) {
LOG(ERROR) << "frontend-quic-secret-file: could not open file " << path;
return nullptr;
}
std::array<char, 4096> buf;
while (f.getline(buf.data(), buf.size())) {
auto len = strlen(buf.data());
if (len == 0 || buf[0] == '#') {
continue;
}
auto s = StringRef{std::begin(buf), std::begin(buf) + len};
if (s.size() != expectedlen * 2 || !util::is_hex_string(s)) {
LOG(ERROR) << "frontend-quic-secret-file: each line must be a "
<< expectedlen * 2 << " bytes hex encoded string";
return nullptr;
}
kms.emplace_back();
auto &qkm = kms.back();
auto p = std::begin(s);
util::decode_hex(std::begin(qkm.reserved),
StringRef{p, p + qkm.reserved.size()});
p += qkm.reserved.size() * 2;
util::decode_hex(std::begin(qkm.secret),
StringRef{p, p + qkm.secret.size()});
p += qkm.secret.size() * 2;
util::decode_hex(std::begin(qkm.salt), StringRef{p, p + qkm.salt.size()});
p += qkm.salt.size() * 2;
assert(static_cast<size_t>(p - std::begin(s)) == expectedlen * 2);
qkm.id = qkm.reserved[0] & 0xc0;
if (kms.size() == 4) {
break;
}
}
if (f.bad() || (!f.eof() && f.fail())) {
LOG(ERROR)
<< "frontend-quic-secret-file: error occurred while reading file "
<< path;
return nullptr;
}
if (kms.empty()) {
LOG(WARN)
<< "frontend-quic-secret-file: no keying materials are present in file "
<< path;
return nullptr;
}
return qkms;
}
#endif // ENABLE_HTTP3
FILE *open_file_for_write(const char *filename) { FILE *open_file_for_write(const char *filename) {
std::array<char, STRERROR_BUFSIZE> errbuf; std::array<char, STRERROR_BUFSIZE> errbuf;
#if defined O_CLOEXEC #ifdef O_CLOEXEC
auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR); S_IRUSR | S_IWUSR);
#else #else
@ -1983,6 +2054,11 @@ int option_lookup_token(const char *name, size_t namelen) {
break; break;
case 14: case 14:
switch (name[13]) { switch (name[13]) {
case 'd':
if (util::strieq_l("quic-server-i", name, 13)) {
return SHRPX_OPTID_QUIC_SERVER_ID;
}
break;
case 'e': case 'e':
if (util::strieq_l("accesslog-fil", name, 13)) { if (util::strieq_l("accesslog-fil", name, 13)) {
return SHRPX_OPTID_ACCESSLOG_FILE; return SHRPX_OPTID_ACCESSLOG_FILE;
@ -1993,6 +2069,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_NO_SERVER_PUSH; return SHRPX_OPTID_NO_SERVER_PUSH;
} }
break; break;
case 'k':
if (util::strieq_l("rlimit-memloc", name, 13)) {
return SHRPX_OPTID_RLIMIT_MEMLOCK;
}
break;
case 'p': case 'p':
if (util::strieq_l("no-verify-ocs", name, 13)) { if (util::strieq_l("no-verify-ocs", name, 13)) {
return SHRPX_OPTID_NO_VERIFY_OCSP; return SHRPX_OPTID_NO_VERIFY_OCSP;
@ -2172,6 +2253,9 @@ int option_lookup_token(const char *name, size_t namelen) {
} }
break; break;
case 's': case 's':
if (util::strieq_l("max-worker-processe", name, 19)) {
return SHRPX_OPTID_MAX_WORKER_PROCESSES;
}
if (util::strieq_l("tls13-client-cipher", name, 19)) { if (util::strieq_l("tls13-client-cipher", name, 19)) {
return SHRPX_OPTID_TLS13_CLIENT_CIPHERS; return SHRPX_OPTID_TLS13_CLIENT_CIPHERS;
} }
@ -2339,6 +2423,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("backend-http2-window-siz", name, 24)) { if (util::strieq_l("backend-http2-window-siz", name, 24)) {
return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE; return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE;
} }
if (util::strieq_l("frontend-quic-secret-fil", name, 24)) {
return SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE;
}
break; break;
case 'g': case 'g':
if (util::strieq_l("http2-no-cookie-crumblin", name, 24)) { if (util::strieq_l("http2-no-cookie-crumblin", name, 24)) {
@ -2353,6 +2440,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS; return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS;
} }
break; break;
case 't':
if (util::strieq_l("frontend-quic-initial-rt", name, 24)) {
return SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT;
}
break;
} }
break; break;
case 26: case 26:
@ -2401,6 +2493,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED; return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED;
} }
break; break;
case 'n':
if (util::strieq_l("frontend-quic-require-toke", name, 26)) {
return SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN;
}
break;
case 'r': case 'r':
if (util::strieq_l("request-header-field-buffe", name, 26)) { if (util::strieq_l("request-header-field-buffe", name, 26)) {
return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER; return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER;
@ -2566,11 +2663,19 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) { if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) {
return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER; return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER;
} }
if (util::strieq_l("frontend-quic-congestion-controlle", name, 34)) {
return SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER;
}
break; break;
} }
break; break;
case 36: case 36:
switch (name[35]) { switch (name[35]) {
case 'd':
if (util::strieq_l("worker-process-grace-shutdown-perio", name, 35)) {
return SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD;
}
break;
case 'e': case 'e':
if (util::strieq_l("backend-http2-connection-window-siz", name, 35)) { if (util::strieq_l("backend-http2-connection-window-siz", name, 35)) {
return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE; return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE;
@ -2765,11 +2870,18 @@ int parse_config(Config *config, int optid, const StringRef &opt,
return -1; return -1;
} }
if (params.quic && params.alt_mode != UpstreamAltMode::NONE) { if (params.quic) {
if (params.alt_mode != UpstreamAltMode::NONE) {
LOG(ERROR) << "frontend: api or healthmon cannot be used with quic"; LOG(ERROR) << "frontend: api or healthmon cannot be used with quic";
return -1; return -1;
} }
if (!params.tls) {
LOG(ERROR) << "frontend: quic requires TLS";
return -1;
}
}
UpstreamAddr addr{}; UpstreamAddr addr{};
addr.fd = -1; addr.fd = -1;
addr.tls = params.tls; addr.tls = params.tls;
@ -3842,7 +3954,7 @@ int parse_config(Config *config, int optid, const StringRef &opt,
"65535], inclusive"; "65535], inclusive";
return -1; return -1;
} }
config->http.redirect_https_port = optarg; config->http.redirect_https_port = make_string_ref(config->balloc, optarg);
return 0; return 0;
} }
case SHRPX_OPTID_FRONTEND_MAX_REQUESTS: case SHRPX_OPTID_FRONTEND_MAX_REQUESTS:
@ -3983,10 +4095,75 @@ int parse_config(Config *config, int optid, const StringRef &opt,
return 0; return 0;
case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR: case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR:
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
config->quic.upstream.qlog.dir = optarg; config->quic.upstream.qlog.dir = make_string_ref(config->balloc, optarg);
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
return 0; return 0;
case SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN:
#ifdef ENABLE_HTTP3
config->quic.upstream.require_token = util::strieq_l("yes", optarg);
#endif // ENABLE_HTTP3
return 0;
case SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER:
#ifdef ENABLE_HTTP3
if (util::strieq_l("cubic", optarg)) {
config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
} else if (util::strieq_l("bbr", optarg)) {
config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_BBR;
} else {
LOG(ERROR) << opt << ": must be either cubic or bbr";
return -1;
}
#endif // ENABLE_HTTP3
return 0;
case SHRPX_OPTID_QUIC_SERVER_ID:
#ifdef ENABLE_HTTP3
if (optarg.size() != config->quic.server_id.size() * 2 ||
!util::is_hex_string(optarg)) {
LOG(ERROR) << opt << ": must be a hex-string";
return -1;
}
util::decode_hex(std::begin(config->quic.server_id), optarg);
#endif // ENABLE_HTTP3
return 0;
case SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE:
#ifdef ENABLE_HTTP3
config->quic.upstream.secret_file = make_string_ref(config->balloc, optarg);
#endif // ENABLE_HTTP3
return 0;
case SHRPX_OPTID_RLIMIT_MEMLOCK: {
int n;
if (parse_uint(&n, opt, optarg) != 0) {
return -1;
}
if (n < 0) {
LOG(ERROR) << opt << ": specify the integer more than or equal to 0";
return -1;
}
config->rlimit_memlock = n;
return 0;
}
case SHRPX_OPTID_MAX_WORKER_PROCESSES:
return parse_uint(&config->max_worker_processes, opt, optarg);
case SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD:
return parse_duration(&config->worker_process_grace_shutdown_period, opt,
optarg);
case SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT: {
#ifdef ENABLE_HTTP3
return parse_duration(&config->quic.upstream.initial_rtt, opt, optarg);
#endif // ENABLE_HTTP3
return 0;
}
case SHRPX_OPTID_CONF: case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored"; LOG(WARN) << "conf: ignored";
@ -4286,7 +4463,7 @@ int configure_downstream_group(Config *config, bool http2_proxy,
if (!g.mruby_file.empty()) { if (!g.mruby_file.empty()) {
if (mruby::create_mruby_context(g.mruby_file) == nullptr) { if (mruby::create_mruby_context(g.mruby_file) == nullptr) {
LOG(config->ignore_per_pattern_mruby_error ? ERROR : FATAL) LOG(config->ignore_per_pattern_mruby_error ? ERROR : FATAL)
<< "backend: Could not compile mruby flie for pattern " << "backend: Could not compile mruby file for pattern "
<< g.pattern; << g.pattern;
if (!config->ignore_per_pattern_mruby_error) { if (!config->ignore_per_pattern_mruby_error) {
return -1; return -1;
@ -4321,6 +4498,8 @@ int configure_downstream_group(Config *config, bool http2_proxy,
auto resolve_flags = numeric_addr_only ? AI_NUMERICHOST | AI_NUMERICSERV : 0; auto resolve_flags = numeric_addr_only ? AI_NUMERICHOST | AI_NUMERICSERV : 0;
std::array<char, util::max_hostport> hostport_buf;
for (auto &g : addr_groups) { for (auto &g : addr_groups) {
std::unordered_map<StringRef, uint32_t> wgchk; std::unordered_map<StringRef, uint32_t> wgchk;
for (auto &addr : g.addrs) { for (auto &addr : g.addrs) {
@ -4366,7 +4545,7 @@ int configure_downstream_group(Config *config, bool http2_proxy,
util::make_http_hostport(downstreamconf.balloc, addr.host, addr.port); util::make_http_hostport(downstreamconf.balloc, addr.host, addr.port);
auto hostport = auto hostport =
util::make_hostport(downstreamconf.balloc, addr.host, addr.port); util::make_hostport(std::begin(hostport_buf), addr.host, addr.port);
if (!addr.dns) { if (!addr.dns) {
if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port, if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,

View File

@ -387,6 +387,20 @@ constexpr auto SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA =
StringRef::from_lit("frontend-quic-early-data"); StringRef::from_lit("frontend-quic-early-data");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR = constexpr auto SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR =
StringRef::from_lit("frontend-quic-qlog-dir"); StringRef::from_lit("frontend-quic-qlog-dir");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN =
StringRef::from_lit("frontend-quic-require-token");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER =
StringRef::from_lit("frontend-quic-congestion-controller");
constexpr auto SHRPX_OPT_QUIC_SERVER_ID = StringRef::from_lit("quic-server-id");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE =
StringRef::from_lit("frontend-quic-secret-file");
constexpr auto SHRPX_OPT_RLIMIT_MEMLOCK = StringRef::from_lit("rlimit-memlock");
constexpr auto SHRPX_OPT_MAX_WORKER_PROCESSES =
StringRef::from_lit("max-worker-processes");
constexpr auto SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD =
StringRef::from_lit("worker-process-grace-shutdown-period");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT =
StringRef::from_lit("frontend-quic-initial-rtt");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -598,10 +612,18 @@ struct TLSCertificate {
}; };
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
struct QUICSecret { struct QUICKeyingMaterial {
std::array<uint8_t, SHRPX_QUIC_STATELESS_RESET_SECRETLEN> std::array<uint8_t, SHRPX_QUIC_SECRET_RESERVEDLEN> reserved;
stateless_reset_secret; std::array<uint8_t, SHRPX_QUIC_SECRETLEN> secret;
std::array<uint8_t, SHRPX_QUIC_TOKEN_SECRETLEN> token_secret; std::array<uint8_t, SHRPX_QUIC_SALTLEN> salt;
std::array<uint8_t, SHRPX_QUIC_CID_ENCRYPTION_KEYLEN> cid_encryption_key;
// Identifier of this keying material. Only the first 2 bits are
// used.
uint8_t id;
};
struct QUICKeyingMaterials {
std::vector<QUICKeyingMaterial> keying_materials;
}; };
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
@ -669,7 +691,7 @@ struct TLSConfig {
ev_tstamp idle_timeout; ev_tstamp idle_timeout;
} dyn_rec; } dyn_rec;
// OCSP realted configurations // OCSP related configurations
struct { struct {
ev_tstamp update_interval; ev_tstamp update_interval;
StringRef fetch_ocsp_response_file; StringRef fetch_ocsp_response_file;
@ -754,12 +776,17 @@ struct QUICConfig {
struct { struct {
StringRef dir; StringRef dir;
} qlog; } qlog;
ngtcp2_cc_algo congestion_controller;
bool early_data; bool early_data;
bool require_token;
StringRef secret_file;
ev_tstamp initial_rtt;
} upstream; } upstream;
struct { struct {
StringRef prog_file; StringRef prog_file;
bool disabled; bool disabled;
} bpf; } bpf;
std::array<uint8_t, SHRPX_QUIC_SERVER_IDLEN> server_id;
}; };
struct Http3Config { struct Http3Config {
@ -1044,6 +1071,7 @@ struct Config {
num_worker{0}, num_worker{0},
padding{0}, padding{0},
rlimit_nofile{0}, rlimit_nofile{0},
rlimit_memlock{0},
uid{0}, uid{0},
gid{0}, gid{0},
pid{0}, pid{0},
@ -1053,7 +1081,9 @@ struct Config {
single_process{false}, single_process{false},
single_thread{false}, single_thread{false},
ignore_per_pattern_mruby_error{false}, ignore_per_pattern_mruby_error{false},
ev_loop_flags{0} { ev_loop_flags{0},
max_worker_processes{0},
worker_process_grace_shutdown_period{0.} {
} }
~Config(); ~Config();
@ -1092,6 +1122,7 @@ struct Config {
size_t num_worker; size_t num_worker;
size_t padding; size_t padding;
size_t rlimit_nofile; size_t rlimit_nofile;
size_t rlimit_memlock;
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
pid_t pid; pid_t pid;
@ -1106,6 +1137,8 @@ struct Config {
bool ignore_per_pattern_mruby_error; bool ignore_per_pattern_mruby_error;
// flags passed to ev_default_loop() and ev_loop_new() // flags passed to ev_default_loop() and ev_loop_new()
int ev_loop_flags; int ev_loop_flags;
size_t max_worker_processes;
ev_tstamp worker_process_grace_shutdown_period;
}; };
const Config *get_config(); const Config *get_config();
@ -1207,10 +1240,14 @@ enum {
SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT, SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT,
SHRPX_OPTID_FRONTEND_MAX_REQUESTS, SHRPX_OPTID_FRONTEND_MAX_REQUESTS,
SHRPX_OPTID_FRONTEND_NO_TLS, SHRPX_OPTID_FRONTEND_NO_TLS,
SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER,
SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG, SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG,
SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA, SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA,
SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT, SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT,
SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT,
SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR, SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR,
SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN,
SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE,
SHRPX_OPTID_FRONTEND_READ_TIMEOUT, SHRPX_OPTID_FRONTEND_READ_TIMEOUT,
SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT, SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
SHRPX_OPTID_HEADER_FIELD_BUFFER, SHRPX_OPTID_HEADER_FIELD_BUFFER,
@ -1228,6 +1265,7 @@ enum {
SHRPX_OPTID_MAX_HEADER_FIELDS, SHRPX_OPTID_MAX_HEADER_FIELDS,
SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS, SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS,
SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS, SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS,
SHRPX_OPTID_MAX_WORKER_PROCESSES,
SHRPX_OPTID_MRUBY_FILE, SHRPX_OPTID_MRUBY_FILE,
SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO, SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO,
SHRPX_OPTID_NO_HOST_REWRITE, SHRPX_OPTID_NO_HOST_REWRITE,
@ -1252,11 +1290,13 @@ enum {
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE, SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
SHRPX_OPTID_PSK_SECRETS, SHRPX_OPTID_PSK_SECRETS,
SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE, SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE,
SHRPX_OPTID_QUIC_SERVER_ID,
SHRPX_OPTID_READ_BURST, SHRPX_OPTID_READ_BURST,
SHRPX_OPTID_READ_RATE, SHRPX_OPTID_READ_RATE,
SHRPX_OPTID_REDIRECT_HTTPS_PORT, SHRPX_OPTID_REDIRECT_HTTPS_PORT,
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER, SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER, SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RLIMIT_MEMLOCK,
SHRPX_OPTID_RLIMIT_NOFILE, SHRPX_OPTID_RLIMIT_NOFILE,
SHRPX_OPTID_SERVER_NAME, SHRPX_OPTID_SERVER_NAME,
SHRPX_OPTID_SINGLE_PROCESS, SHRPX_OPTID_SINGLE_PROCESS,
@ -1297,6 +1337,7 @@ enum {
SHRPX_OPTID_VERIFY_CLIENT_CACERT, SHRPX_OPTID_VERIFY_CLIENT_CACERT,
SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED, SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED,
SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS, SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS,
SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD,
SHRPX_OPTID_WORKER_READ_BURST, SHRPX_OPTID_WORKER_READ_BURST,
SHRPX_OPTID_WORKER_READ_RATE, SHRPX_OPTID_WORKER_READ_RATE,
SHRPX_OPTID_WORKER_WRITE_BURST, SHRPX_OPTID_WORKER_WRITE_BURST,
@ -1361,6 +1402,11 @@ std::unique_ptr<TicketKeys>
read_tls_ticket_key_file(const std::vector<StringRef> &files, read_tls_ticket_key_file(const std::vector<StringRef> &files,
const EVP_CIPHER *cipher, const EVP_MD *hmac); const EVP_CIPHER *cipher, const EVP_MD *hmac);
#ifdef ENABLE_HTTP3
std::shared_ptr<QUICKeyingMaterials>
read_quic_secret_file(const StringRef &path);
#endif // ENABLE_HTTP3
// Returns string representation of |proto|. // Returns string representation of |proto|.
StringRef strproto(Proto proto); StringRef strproto(Proto proto);

View File

@ -397,11 +397,14 @@ int Connection::tls_handshake() {
ERR_clear_error(); ERR_clear_error();
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API || defined(OPENSSL_IS_BORINGSSL)
auto &tlsconf = get_config()->tls;
#endif // OPENSSL_1_1_1_API || defined(OPENSSL_IS_BORINGSSL)
#if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (!tls.server_handshake || tls.early_data_finish) { if (!tls.server_handshake || tls.early_data_finish) {
rv = SSL_do_handshake(tls.ssl); rv = SSL_do_handshake(tls.ssl);
} else { } else {
auto &tlsconf = get_config()->tls;
for (;;) { for (;;) {
size_t nread; size_t nread;
@ -449,9 +452,9 @@ int Connection::tls_handshake() {
} }
} }
} }
#else // !OPENSSL_1_1_1_API #else // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
rv = SSL_do_handshake(tls.ssl); rv = SSL_do_handshake(tls.ssl);
#endif // !OPENSSL_1_1_1_API #endif // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
if (rv <= 0) { if (rv <= 0) {
auto err = SSL_get_error(tls.ssl, rv); auto err = SSL_get_error(tls.ssl, rv);
@ -499,7 +502,12 @@ int Connection::tls_handshake() {
// Don't send handshake data if handshake was completed in OpenSSL // Don't send handshake data if handshake was completed in OpenSSL
// routine. We have to check HTTP/2 requirement if HTTP/2 was // routine. We have to check HTTP/2 requirement if HTTP/2 was
// negotiated before sending finished message to the peer. // negotiated before sending finished message to the peer.
if (rv != 1 && tls.wbuf.rleft()) { if ((rv != 1
#ifdef OPENSSL_IS_BORINGSSL
|| SSL_in_init(tls.ssl)
#endif // OPENSSL_IS_BORINGSSL
) &&
tls.wbuf.rleft()) {
// First write indicates that resumption stuff has done. // First write indicates that resumption stuff has done.
if (tls.handshake_state != TLSHandshakeState::WRITE_STARTED) { if (tls.handshake_state != TLSHandshakeState::WRITE_STARTED) {
tls.handshake_state = TLSHandshakeState::WRITE_STARTED; tls.handshake_state = TLSHandshakeState::WRITE_STARTED;
@ -535,6 +543,40 @@ int Connection::tls_handshake() {
return SHRPX_ERR_INPROGRESS; return SHRPX_ERR_INPROGRESS;
} }
#ifdef OPENSSL_IS_BORINGSSL
if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) &&
SSL_in_init(tls.ssl)) {
auto nread = SSL_read(tls.ssl, buf.data(), buf.size());
if (nread <= 0) {
auto err = SSL_get_error(tls.ssl, nread);
switch (err) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
break;
case SSL_ERROR_ZERO_RETURN:
return SHRPX_ERR_EOF;
case SSL_ERROR_SSL:
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "SSL_read: "
<< ERR_error_string(ERR_get_error(), nullptr);
}
return SHRPX_ERR_NETWORK;
default:
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
}
return SHRPX_ERR_NETWORK;
}
} else {
tls.earlybuf.append(buf.data(), nread);
}
if (SSL_in_init(tls.ssl)) {
return SHRPX_ERR_INPROGRESS;
}
}
#endif // OPENSSL_IS_BORINGSSL
// Handshake was done // Handshake was done
rv = check_http2_requirement(); rv = check_http2_requirement();
@ -571,6 +613,36 @@ int Connection::write_tls_pending_handshake() {
tls.wbuf.drain(nwrite); tls.wbuf.drain(nwrite);
} }
#ifdef OPENSSL_IS_BORINGSSL
if (!SSL_in_init(tls.ssl)) {
// This will send a session ticket.
auto nwrite = SSL_write(tls.ssl, "", 0);
if (nwrite < 0) {
auto err = SSL_get_error(tls.ssl, nwrite);
switch (err) {
case SSL_ERROR_WANT_READ:
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Close connection due to TLS renegotiation";
}
return SHRPX_ERR_NETWORK;
case SSL_ERROR_WANT_WRITE:
break;
case SSL_ERROR_SSL:
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "SSL_write: "
<< ERR_error_string(ERR_get_error(), nullptr);
}
return SHRPX_ERR_NETWORK;
default:
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "SSL_write: SSL_get_error returned " << err;
}
return SHRPX_ERR_NETWORK;
}
}
}
#endif // OPENSSL_IS_BORINGSSL
// We have to start read watcher, since later stage of code expects // We have to start read watcher, since later stage of code expects
// this. // this.
rlimit.startw(); rlimit.startw();
@ -681,7 +753,7 @@ ssize_t Connection::write_tls(const void *data, size_t len) {
// length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
// get_write_limit() may return smaller length than previously // get_write_limit() may return smaller length than previously
// passed to SSL_write, which violates OpenSSL assumption. To avoid // passed to SSL_write, which violates OpenSSL assumption. To avoid
// this, we keep last legnth passed to SSL_write to // this, we keep last length passed to SSL_write to
// tls.last_writelen if SSL_write indicated I/O blocking. // tls.last_writelen if SSL_write indicated I/O blocking.
if (tls.last_writelen == 0) { if (tls.last_writelen == 0) {
len = std::min(len, wlimit.avail()); len = std::min(len, wlimit.avail());
@ -698,7 +770,7 @@ ssize_t Connection::write_tls(const void *data, size_t len) {
ERR_clear_error(); ERR_clear_error();
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
int rv; int rv;
if (SSL_is_init_finished(tls.ssl)) { if (SSL_is_init_finished(tls.ssl)) {
rv = SSL_write(tls.ssl, data, len); rv = SSL_write(tls.ssl, data, len);
@ -710,9 +782,9 @@ ssize_t Connection::write_tls(const void *data, size_t len) {
rv = nwrite; rv = nwrite;
} }
} }
#else // !OPENSSL_1_1_1_API #else // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
auto rv = SSL_write(tls.ssl, data, len); auto rv = SSL_write(tls.ssl, data, len);
#endif // !OPENSSL_1_1_1_API #endif // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
if (rv <= 0) { if (rv <= 0) {
auto err = SSL_get_error(tls.ssl, rv); auto err = SSL_get_error(tls.ssl, rv);
@ -759,7 +831,7 @@ ssize_t Connection::read_tls(void *data, size_t len) {
// length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
// rlimit_.avail() or rlimit_.avail() may return different length // rlimit_.avail() or rlimit_.avail() may return different length
// than the length previously passed to SSL_read, which violates // than the length previously passed to SSL_read, which violates
// OpenSSL assumption. To avoid this, we keep last legnth passed // OpenSSL assumption. To avoid this, we keep last length passed
// to SSL_read to tls_last_readlen_ if SSL_read indicated I/O // to SSL_read to tls_last_readlen_ if SSL_read indicated I/O
// blocking. // blocking.
if (tls.last_readlen == 0) { if (tls.last_readlen == 0) {
@ -772,7 +844,7 @@ ssize_t Connection::read_tls(void *data, size_t len) {
tls.last_readlen = 0; tls.last_readlen = 0;
} }
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (!tls.early_data_finish) { if (!tls.early_data_finish) {
// TLSv1.3 handshake is still going on. // TLSv1.3 handshake is still going on.
size_t nread; size_t nread;
@ -811,7 +883,7 @@ ssize_t Connection::read_tls(void *data, size_t len) {
} }
return nread; return nread;
} }
#endif // OPENSSL_1_1_1_API #endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
auto rv = SSL_read(tls.ssl, data, len); auto rv = SSL_read(tls.ssl, data, len);

View File

@ -298,8 +298,6 @@ int ConnectionHandler::create_single_worker() {
#endif // HAVE_MRUBY #endif // HAVE_MRUBY
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
single_worker_->set_quic_secret(quic_secret_);
if (single_worker_->setup_quic_server_socket() != 0) { if (single_worker_->setup_quic_server_socket() != 0) {
return -1; return -1;
} }
@ -404,8 +402,6 @@ int ConnectionHandler::create_worker_thread(size_t num) {
# endif // HAVE_MRUBY # endif // HAVE_MRUBY
# ifdef ENABLE_HTTP3 # ifdef ENABLE_HTTP3
worker->set_quic_secret(quic_secret_);
if ((!apiconf.enabled || i != 0) && if ((!apiconf.enabled || i != 0) &&
worker->setup_quic_server_socket() != 0) { worker->setup_quic_server_socket() != 0) {
return -1; return -1;
@ -744,9 +740,9 @@ void ConnectionHandler::handle_ocsp_complete() {
// that case we get nullptr. // that case we get nullptr.
auto quic_ssl_ctx = quic_all_ssl_ctx_[ocsp_.next]; auto quic_ssl_ctx = quic_all_ssl_ctx_[ocsp_.next];
if (quic_ssl_ctx) { if (quic_ssl_ctx) {
# ifndef OPENSSL_IS_BORINGSSL
auto quic_tls_ctx_data = static_cast<tls::TLSContextData *>( auto quic_tls_ctx_data = static_cast<tls::TLSContextData *>(
SSL_CTX_get_app_data(quic_ssl_ctx)); SSL_CTX_get_app_data(quic_ssl_ctx));
# ifndef OPENSSL_IS_BORINGSSL
# ifdef HAVE_ATOMIC_STD_SHARED_PTR # ifdef HAVE_ATOMIC_STD_SHARED_PTR
std::atomic_store_explicit( std::atomic_store_explicit(
&quic_tls_ctx_data->ocsp_data, &quic_tls_ctx_data->ocsp_data,
@ -758,7 +754,8 @@ void ConnectionHandler::handle_ocsp_complete() {
std::make_shared<std::vector<uint8_t>>(ocsp_.resp); std::make_shared<std::vector<uint8_t>>(ocsp_.resp);
# endif // !HAVE_ATOMIC_STD_SHARED_PTR # endif // !HAVE_ATOMIC_STD_SHARED_PTR
# else // OPENSSL_IS_BORINGSSL # else // OPENSSL_IS_BORINGSSL
SSL_CTX_set_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size()); SSL_CTX_set_ocsp_response(quic_ssl_ctx, ocsp_.resp.data(),
ocsp_.resp.size());
# endif // OPENSSL_IS_BORINGSSL # endif // OPENSSL_IS_BORINGSSL
} }
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
@ -1020,12 +1017,10 @@ void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) {
} }
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
int ConnectionHandler::forward_quic_packet(const UpstreamAddr *faddr, int ConnectionHandler::forward_quic_packet(
const Address &remote_addr, const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const Address &local_addr, const ngtcp2_pkt_info &pi,
const uint8_t *cid_prefix, const uint8_t *cid_prefix, const uint8_t *data, size_t datalen) {
const uint8_t *data,
size_t datalen) {
assert(!get_config()->single_thread); assert(!get_config()->single_thread);
for (auto &worker : workers_) { for (auto &worker : workers_) {
@ -1037,7 +1032,7 @@ int ConnectionHandler::forward_quic_packet(const UpstreamAddr *faddr,
WorkerEvent wev{}; WorkerEvent wev{};
wev.type = WorkerEventType::QUIC_PKT_FORWARD; wev.type = WorkerEventType::QUIC_PKT_FORWARD;
wev.quic_pkt = std::make_unique<QUICPacket>(faddr->index, remote_addr, wev.quic_pkt = std::make_unique<QUICPacket>(faddr->index, remote_addr,
local_addr, data, datalen); local_addr, pi, data, datalen);
worker->send(std::move(wev)); worker->send(std::move(wev));
@ -1047,25 +1042,14 @@ int ConnectionHandler::forward_quic_packet(const UpstreamAddr *faddr,
return -1; return -1;
} }
int ConnectionHandler::create_quic_secret() { void ConnectionHandler::set_quic_keying_materials(
auto quic_secret = std::make_shared<QUICSecret>(); std::shared_ptr<QUICKeyingMaterials> qkms) {
quic_keying_materials_ = std::move(qkms);
if (generate_quic_stateless_reset_secret(
quic_secret->stateless_reset_secret.data()) != 0) {
LOG(ERROR) << "Failed to generate QUIC Stateless Reset secret";
return -1;
} }
if (generate_quic_token_secret(quic_secret->token_secret.data()) != 0) { const std::shared_ptr<QUICKeyingMaterials> &
LOG(ERROR) << "Failed to generate QUIC token secret"; ConnectionHandler::get_quic_keying_materials() const {
return quic_keying_materials_;
return -1;
}
quic_secret_ = std::move(quic_secret);
return 0;
} }
void ConnectionHandler::set_cid_prefixes( void ConnectionHandler::set_cid_prefixes(
@ -1094,6 +1078,26 @@ ConnectionHandler::match_quic_lingering_worker_process_cid_prefix(
std::vector<BPFRef> &ConnectionHandler::get_quic_bpf_refs() { std::vector<BPFRef> &ConnectionHandler::get_quic_bpf_refs() {
return quic_bpf_refs_; return quic_bpf_refs_;
} }
void ConnectionHandler::unload_bpf_objects() {
std::array<char, STRERROR_BUFSIZE> errbuf;
LOG(NOTICE) << "Unloading BPF objects";
for (auto &ref : quic_bpf_refs_) {
if (ref.obj == nullptr) {
continue;
}
if (bpf_object__unload(ref.obj) != 0) {
LOG(WARN) << "Failed to unload bpf object: "
<< xsi_strerror(errno, errbuf.data(), errbuf.size());
continue;
}
ref.obj = nullptr;
}
}
# endif // HAVE_LIBBPF # endif // HAVE_LIBBPF
void ConnectionHandler::set_quic_ipc_fd(int fd) { quic_ipc_fd_ = fd; } void ConnectionHandler::set_quic_ipc_fd(int fd) { quic_ipc_fd_ = fd; }
@ -1105,10 +1109,11 @@ void ConnectionHandler::set_quic_lingering_worker_processes(
int ConnectionHandler::forward_quic_packet_to_lingering_worker_process( int ConnectionHandler::forward_quic_packet_to_lingering_worker_process(
QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr, QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, size_t datalen) { const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data,
size_t datalen) {
std::array<uint8_t, 512> header; std::array<uint8_t, 512> header;
assert(header.size() >= 1 + 1 + 1 + sizeof(sockaddr_storage) * 2); assert(header.size() >= 1 + 1 + 1 + 1 + sizeof(sockaddr_storage) * 2);
assert(remote_addr.len > 0); assert(remote_addr.len > 0);
assert(local_addr.len > 0); assert(local_addr.len > 0);
@ -1121,6 +1126,7 @@ int ConnectionHandler::forward_quic_packet_to_lingering_worker_process(
*p++ = static_cast<uint8_t>(local_addr.len - 1); *p++ = static_cast<uint8_t>(local_addr.len - 1);
p = std::copy_n(reinterpret_cast<const uint8_t *>(&local_addr.su), p = std::copy_n(reinterpret_cast<const uint8_t *>(&local_addr.su),
local_addr.len, p); local_addr.len, p);
*p++ = pi.ecn;
iovec msg_iov[] = { iovec msg_iov[] = {
{ {
@ -1179,14 +1185,14 @@ int ConnectionHandler::quic_ipc_read() {
return 0; return 0;
} }
size_t len = 1 + 1 + 1; size_t len = 1 + 1 + 1 + 1;
// Wire format: // Wire format:
// TYPE(1) REMOTE_ADDRLEN(1) REMOTE_ADDR(N) LOCAL_ADDRLEN(1) REMOTE_ADDR(N) // TYPE(1) REMOTE_ADDRLEN(1) REMOTE_ADDR(N) LOCAL_ADDRLEN(1) LOCAL_ADDR(N)
// DGRAM_PAYLAOD(N) // ECN(1) DGRAM_PAYLOAD(N)
// //
// When encoding, REMOTE_ADDRLEN and LOCAL_ADDRLEN is decremented by // When encoding, REMOTE_ADDRLEN and LOCAL_ADDRLEN are decremented
// 1. // by 1.
if (static_cast<size_t>(nread) < len) { if (static_cast<size_t>(nread) < len) {
return 0; return 0;
} }
@ -1243,6 +1249,8 @@ int ConnectionHandler::quic_ipc_read() {
p += local_addrlen; p += local_addrlen;
pkt->pi.ecn = *p++;
auto datalen = nread - (p - buf.data()); auto datalen = nread - (p - buf.data());
pkt->data.assign(p, p + datalen); pkt->data.assign(p, p + datalen);
@ -1265,8 +1273,8 @@ int ConnectionHandler::quic_ipc_read() {
return -1; return -1;
} }
if (dcidlen < SHRPX_QUIC_CID_PREFIXLEN) { if (dcidlen != SHRPX_QUIC_SCIDLEN) {
LOG(ERROR) << "DCID is too short"; LOG(ERROR) << "DCID length is invalid";
return -1; return -1;
} }
@ -1282,13 +1290,25 @@ int ConnectionHandler::quic_ipc_read() {
// Ignore return value // Ignore return value
quic_conn_handler->handle_packet(faddr, pkt->remote_addr, pkt->local_addr, quic_conn_handler->handle_packet(faddr, pkt->remote_addr, pkt->local_addr,
pkt->data.data(), pkt->data.size()); pkt->pi, pkt->data.data(),
pkt->data.size());
return 0; return 0;
} }
auto &qkm = quic_keying_materials_->keying_materials.front();
std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
if (decrypt_quic_connection_id(decrypted_dcid.data(),
dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
qkm.cid_encryption_key.data()) != 0) {
return -1;
}
for (auto &worker : workers_) { for (auto &worker : workers_) {
if (!std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN, if (!std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker->get_cid_prefix())) { worker->get_cid_prefix())) {
continue; continue;
} }

View File

@ -106,6 +106,7 @@ struct SerialEvent {
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
# ifdef HAVE_LIBBPF # ifdef HAVE_LIBBPF
struct BPFRef { struct BPFRef {
bpf_object *obj;
int reuseport_array; int reuseport_array;
int cid_prefix_map; int cid_prefix_map;
}; };
@ -162,7 +163,7 @@ public:
// Cancels ocsp update process // Cancels ocsp update process
void cancel_ocsp_update(); void cancel_ocsp_update();
// Starts ocsp update for certficate |cert_file|. // Starts ocsp update for certificate |cert_file|.
int start_ocsp_update(const char *cert_file); int start_ocsp_update(const char *cert_file);
// Reads incoming data from ocsp update process // Reads incoming data from ocsp update process
void read_ocsp_chunk(); void read_ocsp_chunk();
@ -195,10 +196,12 @@ public:
const std::vector<SSL_CTX *> &get_quic_indexed_ssl_ctx(size_t idx) const; const std::vector<SSL_CTX *> &get_quic_indexed_ssl_ctx(size_t idx) const;
int forward_quic_packet(const UpstreamAddr *faddr, const Address &remote_addr, int forward_quic_packet(const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const uint8_t *cid_prefix, const Address &local_addr, const ngtcp2_pkt_info &pi,
const uint8_t *data, size_t datalen); const uint8_t *cid_prefix, const uint8_t *data,
size_t datalen);
int create_quic_secret(); void set_quic_keying_materials(std::shared_ptr<QUICKeyingMaterials> qkms);
const std::shared_ptr<QUICKeyingMaterials> &get_quic_keying_materials() const;
void set_cid_prefixes( void set_cid_prefixes(
const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>>
@ -216,7 +219,8 @@ public:
int forward_quic_packet_to_lingering_worker_process( int forward_quic_packet_to_lingering_worker_process(
QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr, QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, size_t datalen); const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data,
size_t datalen);
void set_quic_ipc_fd(int fd); void set_quic_ipc_fd(int fd);
@ -224,6 +228,7 @@ public:
# ifdef HAVE_LIBBPF # ifdef HAVE_LIBBPF
std::vector<BPFRef> &get_quic_bpf_refs(); std::vector<BPFRef> &get_quic_bpf_refs();
void unload_bpf_objects();
# endif // HAVE_LIBBPF # endif // HAVE_LIBBPF
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
@ -263,7 +268,7 @@ private:
# ifdef HAVE_LIBBPF # ifdef HAVE_LIBBPF
std::vector<BPFRef> quic_bpf_refs_; std::vector<BPFRef> quic_bpf_refs_;
# endif // HAVE_LIBBPF # endif // HAVE_LIBBPF
std::shared_ptr<QUICSecret> quic_secret_; std::shared_ptr<QUICKeyingMaterials> quic_keying_materials_;
std::vector<SSL_CTX *> quic_all_ssl_ctx_; std::vector<SSL_CTX *> quic_all_ssl_ctx_;
std::vector<std::vector<SSL_CTX *>> quic_indexed_ssl_ctx_; std::vector<std::vector<SSL_CTX *>> quic_indexed_ssl_ctx_;
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3

View File

@ -88,7 +88,7 @@ public:
int on_write(int fd); int on_write(int fd);
int on_timeout(); int on_timeout();
// Calls this function when DNS query finished. // Calls this function when DNS query finished.
void on_result(int staus, hostent *hostent); void on_result(int status, hostent *hostent);
void reset_timeout(); void reset_timeout();
void start_rev(int fd); void start_rev(int fd);

View File

@ -58,7 +58,7 @@ public:
virtual int on_write() = 0; virtual int on_write() = 0;
virtual int on_timeout() { return 0; } virtual int on_timeout() { return 0; }
virtual void on_upstream_change(Upstream *uptream) = 0; virtual void on_upstream_change(Upstream *upstream) = 0;
// true if this object is poolable. // true if this object is poolable.
virtual bool poolable() const = 0; virtual bool poolable() const = 0;

View File

@ -98,7 +98,8 @@ int HealthMonitorDownstreamConnection::on_read() { return 0; }
int HealthMonitorDownstreamConnection::on_write() { return 0; } int HealthMonitorDownstreamConnection::on_write() { return 0; }
void HealthMonitorDownstreamConnection::on_upstream_change(Upstream *uptream) {} void HealthMonitorDownstreamConnection::on_upstream_change(Upstream *upstream) {
}
bool HealthMonitorDownstreamConnection::poolable() const { return false; } bool HealthMonitorDownstreamConnection::poolable() const { return false; }

View File

@ -49,7 +49,7 @@ public:
virtual int on_read(); virtual int on_read();
virtual int on_write(); virtual int on_write();
virtual void on_upstream_change(Upstream *uptream); virtual void on_upstream_change(Upstream *upstream);
// true if this object is poolable. // true if this object is poolable.
virtual bool poolable() const; virtual bool poolable() const;

View File

@ -60,7 +60,7 @@ public:
virtual void on_upstream_change(Upstream *upstream) {} virtual void on_upstream_change(Upstream *upstream) {}
// This object is not poolable because we dont' have facility to // This object is not poolable because we don't have facility to
// migrate to another Http2Session object. // migrate to another Http2Session object.
virtual bool poolable() const { return false; } virtual bool poolable() const { return false; }

View File

@ -206,13 +206,13 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
on_read_ = &Http2Session::read_noop; on_read_ = &Http2Session::read_noop;
on_write_ = &Http2Session::write_noop; on_write_ = &Http2Session::write_noop;
// We will resuse this many times, so use repeat timeout value. The // We will reuse this many times, so use repeat timeout value. The
// timeout value is set later. // timeout value is set later.
ev_timer_init(&connchk_timer_, connchk_timeout_cb, 0., 0.); ev_timer_init(&connchk_timer_, connchk_timeout_cb, 0., 0.);
connchk_timer_.data = this; connchk_timer_.data = this;
// SETTINGS ACK timeout is 10 seconds for now. We will resuse this // SETTINGS ACK timeout is 10 seconds for now. We will reuse this
// many times, so use repeat timeout value. // many times, so use repeat timeout value.
ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.); ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.);

View File

@ -1317,7 +1317,7 @@ int Http2Upstream::downstream_eof(DownstreamConnection *dconn) {
downstream->pop_downstream_connection(); downstream->pop_downstream_connection();
// dconn was deleted // dconn was deleted
dconn = nullptr; dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback. // downstream will be deleted in on_stream_close_callback.
if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF // Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -2183,7 +2183,7 @@ int Http2Upstream::submit_push_promise(const StringRef &scheme,
// 4 for :method, :scheme, :path and :authority // 4 for :method, :scheme, :path and :authority
nva.reserve(4 + req.fs.headers().size()); nva.reserve(4 + req.fs.headers().size());
// juse use "GET" for now // just use "GET" for now
nva.push_back(http2::make_nv_ll(":method", "GET")); nva.push_back(http2::make_nv_ll(":method", "GET"));
nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme)); nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
nva.push_back(http2::make_nv_ls_nocopy(":path", path)); nva.push_back(http2::make_nv_ls_nocopy(":path", path));

View File

@ -27,6 +27,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <netinet/udp.h>
#include <cstdio> #include <cstdio>
@ -39,6 +40,7 @@
#include "shrpx_quic.h" #include "shrpx_quic.h"
#include "shrpx_worker.h" #include "shrpx_worker.h"
#include "shrpx_http.h" #include "shrpx_http.h"
#include "shrpx_connection_handler.h"
#ifdef HAVE_MRUBY #ifdef HAVE_MRUBY
# include "shrpx_mruby.h" # include "shrpx_mruby.h"
#endif // HAVE_MRUBY #endif // HAVE_MRUBY
@ -116,13 +118,16 @@ size_t downstream_queue_size(Worker *worker) {
Http3Upstream::Http3Upstream(ClientHandler *handler) Http3Upstream::Http3Upstream(ClientHandler *handler)
: handler_{handler}, : handler_{handler},
max_udp_payload_size_{SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE},
qlog_fd_{-1}, qlog_fd_{-1},
hashed_scid_{},
conn_{nullptr}, conn_{nullptr},
tls_alert_{0}, tls_alert_{0},
httpconn_{nullptr}, httpconn_{nullptr},
downstream_queue_{downstream_queue_size(handler->get_worker()), downstream_queue_{downstream_queue_size(handler->get_worker()),
!get_config()->http2_proxy}, !get_config()->http2_proxy},
idle_close_{false} { idle_close_{false},
retry_close_{false} {
ev_timer_init(&timer_, timeoutcb, 0., 0.); ev_timer_init(&timer_, timeoutcb, 0., 0.);
timer_.data = this; timer_.data = this;
@ -151,9 +156,7 @@ Http3Upstream::~Http3Upstream() {
nghttp3_conn_del(httpconn_); nghttp3_conn_del(httpconn_);
if (conn_) {
ngtcp2_conn_del(conn_); ngtcp2_conn_del(conn_);
}
if (qlog_fd_ != -1) { if (qlog_fd_ != -1) {
close(qlog_fd_); close(qlog_fd_);
@ -192,9 +195,7 @@ void qlog_write(void *user_data, uint32_t flags, const void *data,
void Http3Upstream::qlog_write(const void *data, size_t datalen, bool fin) { void Http3Upstream::qlog_write(const void *data, size_t datalen, bool fin) {
assert(qlog_fd_ != -1); assert(qlog_fd_ != -1);
ssize_t nwrite; while (write(qlog_fd_, data, datalen) == -1 && errno == EINTR)
while ((nwrite = write(qlog_fd_, data, datalen)) == -1 && errno == EINTR)
; ;
if (fin) { if (fin) {
@ -216,22 +217,23 @@ int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
auto upstream = static_cast<Http3Upstream *>(user_data); auto upstream = static_cast<Http3Upstream *>(user_data);
auto handler = upstream->get_client_handler(); auto handler = upstream->get_client_handler();
auto worker = handler->get_worker(); auto worker = handler->get_worker();
auto conn_handler = worker->get_connection_handler();
auto &qkms = conn_handler->get_quic_keying_materials();
auto &qkm = qkms->keying_materials.front();
if (generate_quic_connection_id(cid, cidlen, worker->get_cid_prefix()) != 0) { if (generate_quic_connection_id(*cid, cidlen, worker->get_cid_prefix(),
qkm.id, qkm.cid_encryption_key.data()) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE; return NGTCP2_ERR_CALLBACK_FAILURE;
} }
auto &quic_secret = worker->get_quic_secret(); if (generate_quic_stateless_reset_token(token, *cid, qkm.secret.data(),
auto &secret = quic_secret->stateless_reset_secret; qkm.secret.size()) != 0) {
if (generate_quic_stateless_reset_token(token, cid, secret.data(),
secret.size()) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE; return NGTCP2_ERR_CALLBACK_FAILURE;
} }
auto quic_connection_handler = worker->get_quic_connection_handler(); auto quic_connection_handler = worker->get_quic_connection_handler();
quic_connection_handler->add_connection_id(cid, handler); quic_connection_handler->add_connection_id(*cid, handler);
return 0; return 0;
} }
@ -245,7 +247,7 @@ int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
auto worker = handler->get_worker(); auto worker = handler->get_worker();
auto quic_conn_handler = worker->get_quic_connection_handler(); auto quic_conn_handler = worker->get_quic_connection_handler();
quic_conn_handler->remove_connection_id(cid); quic_conn_handler->remove_connection_id(*cid);
return 0; return 0;
} }
@ -476,16 +478,26 @@ int handshake_completed(ngtcp2_conn *conn, void *user_data) {
} // namespace } // namespace
int Http3Upstream::handshake_completed() { int Http3Upstream::handshake_completed() {
handler_->set_alpn_from_conn();
auto alpn = handler_->get_alpn();
if (alpn.empty()) {
ULOG(ERROR, this) << "NO ALPN was negotiated";
return -1;
}
std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token; std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token;
size_t tokenlen; size_t tokenlen;
auto path = ngtcp2_conn_get_path(conn_); auto path = ngtcp2_conn_get_path(conn_);
auto worker = handler_->get_worker(); auto worker = handler_->get_worker();
auto &quic_secret = worker->get_quic_secret(); auto conn_handler = worker->get_connection_handler();
auto &secret = quic_secret->token_secret; auto &qkms = conn_handler->get_quic_keying_materials();
auto &qkm = qkms->keying_materials.front();
if (generate_token(token.data(), tokenlen, path->remote.addr, if (generate_token(token.data(), tokenlen, path->remote.addr,
path->remote.addrlen, secret.data()) != 0) { path->remote.addrlen, qkm.secret.data(),
qkm.secret.size()) != 0) {
return 0; return 0;
} }
@ -507,6 +519,7 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
int rv; int rv;
auto worker = handler_->get_worker(); auto worker = handler_->get_worker();
auto conn_handler = worker->get_connection_handler();
auto callbacks = ngtcp2_callbacks{ auto callbacks = ngtcp2_callbacks{
nullptr, // client_initial nullptr, // client_initial
@ -547,17 +560,21 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
shrpx::stream_stop_sending, shrpx::stream_stop_sending,
}; };
ngtcp2_cid scid;
if (generate_quic_connection_id(&scid, SHRPX_QUIC_SCIDLEN,
worker->get_cid_prefix()) != 0) {
return -1;
}
auto config = get_config(); auto config = get_config();
auto &quicconf = config->quic; auto &quicconf = config->quic;
auto &http3conf = config->http3; auto &http3conf = config->http3;
auto &qkms = conn_handler->get_quic_keying_materials();
auto &qkm = qkms->keying_materials.front();
ngtcp2_cid scid;
if (generate_quic_connection_id(scid, SHRPX_QUIC_SCIDLEN,
worker->get_cid_prefix(), qkm.id,
qkm.cid_encryption_key.data()) != 0) {
return -1;
}
ngtcp2_settings settings; ngtcp2_settings settings;
ngtcp2_settings_default(&settings); ngtcp2_settings_default(&settings);
if (quicconf.upstream.debug.log) { if (quicconf.upstream.debug.log) {
@ -574,7 +591,9 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
} }
settings.initial_ts = quic_timestamp(); settings.initial_ts = quic_timestamp();
settings.cc_algo = NGTCP2_CC_ALGO_BBR; settings.initial_rtt = static_cast<ngtcp2_tstamp>(
quicconf.upstream.initial_rtt * NGTCP2_SECONDS);
settings.cc_algo = quicconf.upstream.congestion_controller;
settings.max_window = http3conf.upstream.max_connection_window_size; settings.max_window = http3conf.upstream.max_connection_window_size;
settings.max_stream_window = http3conf.upstream.max_window_size; settings.max_stream_window = http3conf.upstream.max_window_size;
settings.max_udp_payload_size = SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE; settings.max_udp_payload_size = SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE;
@ -593,6 +612,40 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
params.max_idle_timeout = static_cast<ngtcp2_tstamp>( params.max_idle_timeout = static_cast<ngtcp2_tstamp>(
quicconf.upstream.timeout.idle * NGTCP2_SECONDS); quicconf.upstream.timeout.idle * NGTCP2_SECONDS);
#ifdef OPENSSL_IS_BORINGSSL
if (quicconf.upstream.early_data) {
ngtcp2_transport_params early_data_params{
.initial_max_stream_data_bidi_local =
params.initial_max_stream_data_bidi_local,
.initial_max_stream_data_bidi_remote =
params.initial_max_stream_data_bidi_remote,
.initial_max_stream_data_uni = params.initial_max_stream_data_uni,
.initial_max_data = params.initial_max_data,
.initial_max_streams_bidi = params.initial_max_streams_bidi,
.initial_max_streams_uni = params.initial_max_streams_uni,
};
// TODO include HTTP/3 SETTINGS
std::array<uint8_t, 128> quic_early_data_ctx;
auto quic_early_data_ctxlen = ngtcp2_encode_transport_params(
quic_early_data_ctx.data(), quic_early_data_ctx.size(),
NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, &early_data_params);
assert(quic_early_data_ctxlen > 0);
assert(static_cast<size_t>(quic_early_data_ctxlen) <=
quic_early_data_ctx.size());
if (SSL_set_quic_early_data_context(handler_->get_ssl(),
quic_early_data_ctx.data(),
quic_early_data_ctxlen) != 1) {
ULOG(ERROR, this) << "SSL_set_quic_early_data_context failed";
return -1;
}
}
#endif // OPENSSL_IS_BORINGSSL
if (odcid) { if (odcid) {
params.original_dcid = *odcid; params.original_dcid = *odcid;
params.retry_scid = initial_hd.dcid; params.retry_scid = initial_hd.dcid;
@ -601,12 +654,8 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
params.original_dcid = initial_hd.dcid; params.original_dcid = initial_hd.dcid;
} }
auto &quic_secret = worker->get_quic_secret(); rv = generate_quic_stateless_reset_token(
auto &stateless_reset_secret = quic_secret->stateless_reset_secret; params.stateless_reset_token, scid, qkm.secret.data(), qkm.secret.size());
rv = generate_quic_stateless_reset_token(params.stateless_reset_token, &scid,
stateless_reset_secret.data(),
stateless_reset_secret.size());
if (rv != 0) { if (rv != 0) {
ULOG(ERROR, this) << "generate_quic_stateless_reset_token failed"; ULOG(ERROR, this) << "generate_quic_stateless_reset_token failed";
return -1; return -1;
@ -614,8 +663,14 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
params.stateless_reset_token_present = 1; params.stateless_reset_token_present = 1;
auto path = ngtcp2_path{ auto path = ngtcp2_path{
{local_addr.len, const_cast<sockaddr *>(&local_addr.su.sa)}, {
{remote_addr.len, const_cast<sockaddr *>(&remote_addr.su.sa)}, const_cast<sockaddr *>(&local_addr.su.sa),
local_addr.len,
},
{
const_cast<sockaddr *>(&remote_addr.su.sa),
remote_addr.len,
},
const_cast<UpstreamAddr *>(faddr), const_cast<UpstreamAddr *>(faddr),
}; };
@ -631,8 +686,13 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
auto quic_connection_handler = worker->get_quic_connection_handler(); auto quic_connection_handler = worker->get_quic_connection_handler();
quic_connection_handler->add_connection_id(&initial_hd.dcid, handler_); if (generate_quic_hashed_connection_id(hashed_scid_, remote_addr, local_addr,
quic_connection_handler->add_connection_id(&scid, handler_); initial_hd.dcid) != 0) {
return -1;
}
quic_connection_handler->add_connection_id(hashed_scid_, handler_);
quic_connection_handler->add_connection_id(scid, handler_);
return 0; return 0;
} }
@ -652,11 +712,12 @@ int Http3Upstream::on_write() {
int Http3Upstream::write_streams() { int Http3Upstream::write_streams() {
std::array<nghttp3_vec, 16> vec; std::array<nghttp3_vec, 16> vec;
std::array<uint8_t, 64_k> buf; std::array<uint8_t, 64_k> buf;
auto max_udp_payload_size = ngtcp2_conn_get_path_max_udp_payload_size(conn_); auto max_udp_payload_size = std::min(
max_udp_payload_size_, ngtcp2_conn_get_path_max_udp_payload_size(conn_));
size_t max_pktcnt = size_t max_pktcnt =
std::min(static_cast<size_t>(64_k), ngtcp2_conn_get_send_quantum(conn_)) / std::min(static_cast<size_t>(64_k), ngtcp2_conn_get_send_quantum(conn_)) /
max_udp_payload_size; max_udp_payload_size;
ngtcp2_pkt_info pi; ngtcp2_pkt_info pi, prev_pi;
uint8_t *bufpos = buf.data(); uint8_t *bufpos = buf.data();
ngtcp2_path_storage ps, prev_ps; ngtcp2_path_storage ps, prev_ps;
size_t pktcnt = 0; size_t pktcnt = 0;
@ -666,6 +727,13 @@ int Http3Upstream::write_streams() {
ngtcp2_path_storage_zero(&ps); ngtcp2_path_storage_zero(&ps);
ngtcp2_path_storage_zero(&prev_ps); ngtcp2_path_storage_zero(&prev_ps);
auto config = get_config();
auto &quicconf = config->quic;
if (quicconf.upstream.congestion_controller != NGTCP2_CC_ALGO_BBR) {
max_pktcnt = std::min(max_pktcnt, static_cast<size_t>(10));
}
for (;;) { for (;;) {
int64_t stream_id = -1; int64_t stream_id = -1;
int fin = 0; int fin = 0;
@ -750,15 +818,17 @@ int Http3Upstream::write_streams() {
if (nwrite == 0) { if (nwrite == 0) {
if (bufpos - buf.data()) { if (bufpos - buf.data()) {
quic_send_packet(static_cast<UpstreamAddr *>(prev_ps.path.user_data), send_packet(static_cast<UpstreamAddr *>(prev_ps.path.user_data),
prev_ps.path.remote.addr, prev_ps.path.remote.addrlen, prev_ps.path.remote.addr, prev_ps.path.remote.addrlen,
prev_ps.path.local.addr, prev_ps.path.local.addrlen, prev_ps.path.local.addr, prev_ps.path.local.addrlen,
buf.data(), bufpos - buf.data(), max_udp_payload_size); prev_pi, buf.data(), bufpos - buf.data(),
max_udp_payload_size);
ngtcp2_conn_update_pkt_tx_time(conn_, ts);
reset_idle_timer(); reset_idle_timer();
} }
ngtcp2_conn_update_pkt_tx_time(conn_, ts);
handler_->get_connection()->wlimit.stopw(); handler_->get_connection()->wlimit.stopw();
return 0; return 0;
@ -769,16 +839,18 @@ int Http3Upstream::write_streams() {
#ifdef UDP_SEGMENT #ifdef UDP_SEGMENT
if (pktcnt == 0) { if (pktcnt == 0) {
ngtcp2_path_copy(&prev_ps.path, &ps.path); ngtcp2_path_copy(&prev_ps.path, &ps.path);
} else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path)) { prev_pi = pi;
quic_send_packet(static_cast<UpstreamAddr *>(prev_ps.path.user_data), } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) ||
prev_pi.ecn != pi.ecn) {
send_packet(static_cast<UpstreamAddr *>(prev_ps.path.user_data),
prev_ps.path.remote.addr, prev_ps.path.remote.addrlen, prev_ps.path.remote.addr, prev_ps.path.remote.addrlen,
prev_ps.path.local.addr, prev_ps.path.local.addrlen, prev_ps.path.local.addr, prev_ps.path.local.addrlen, prev_pi,
buf.data(), bufpos - buf.data() - nwrite, buf.data(), bufpos - buf.data() - nwrite,
max_udp_payload_size); max_udp_payload_size);
quic_send_packet(static_cast<UpstreamAddr *>(ps.path.user_data), send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
ps.path.remote.addr, ps.path.remote.addrlen, ps.path.remote.addr, ps.path.remote.addrlen,
ps.path.local.addr, ps.path.local.addrlen, ps.path.local.addr, ps.path.local.addrlen, pi,
bufpos - nwrite, nwrite, max_udp_payload_size); bufpos - nwrite, nwrite, max_udp_payload_size);
ngtcp2_conn_update_pkt_tx_time(conn_, ts); ngtcp2_conn_update_pkt_tx_time(conn_, ts);
@ -791,9 +863,9 @@ int Http3Upstream::write_streams() {
if (++pktcnt == max_pktcnt || if (++pktcnt == max_pktcnt ||
static_cast<size_t>(nwrite) < max_udp_payload_size) { static_cast<size_t>(nwrite) < max_udp_payload_size) {
quic_send_packet(static_cast<UpstreamAddr *>(ps.path.user_data), send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
ps.path.remote.addr, ps.path.remote.addrlen, ps.path.remote.addr, ps.path.remote.addrlen,
ps.path.local.addr, ps.path.local.addrlen, buf.data(), ps.path.local.addr, ps.path.local.addrlen, pi, buf.data(),
bufpos - buf.data(), max_udp_payload_size); bufpos - buf.data(), max_udp_payload_size);
ngtcp2_conn_update_pkt_tx_time(conn_, ts); ngtcp2_conn_update_pkt_tx_time(conn_, ts);
@ -804,10 +876,9 @@ int Http3Upstream::write_streams() {
return 0; return 0;
} }
#else // !UDP_SEGMENT #else // !UDP_SEGMENT
quic_send_packet(static_cast<UpstreamAddr *>(ps.path.user_data), send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
ps.path.remote.addr, ps.path.remote.addrlen, ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr,
ps.path.local.addr, ps.path.local.addrlen, buf.data(), ps.path.local.addrlen, pi, buf.data(), bufpos - buf.data(), 0);
bufpos - buf.data(), 0);
if (++pktcnt == max_pktcnt) { if (++pktcnt == max_pktcnt) {
ngtcp2_conn_update_pkt_tx_time(conn_, ts); ngtcp2_conn_update_pkt_tx_time(conn_, ts);
@ -952,7 +1023,7 @@ int Http3Upstream::downstream_eof(DownstreamConnection *dconn) {
downstream->pop_downstream_connection(); downstream->pop_downstream_connection();
// dconn was deleted // dconn was deleted
dconn = nullptr; dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback. // downstream will be deleted in on_stream_close_callback.
if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF // Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -964,7 +1035,9 @@ int Http3Upstream::downstream_eof(DownstreamConnection *dconn) {
// downstream_read_data_callback to send RST_STREAM after pending // downstream_read_data_callback to send RST_STREAM after pending
// response body is sent. This is needed to ensure that RST_STREAM // response body is sent. This is needed to ensure that RST_STREAM
// is sent after all pending data are sent. // is sent after all pending data are sent.
on_downstream_body_complete(downstream); if (on_downstream_body_complete(downstream) != 0) {
return -1;
}
} else if (downstream->get_response_state() != } else if (downstream->get_response_state() !=
DownstreamState::MSG_COMPLETE) { DownstreamState::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let // If stream was not closed, then we set MSG_COMPLETE and let
@ -1008,7 +1081,9 @@ int Http3Upstream::downstream_error(DownstreamConnection *dconn, int events) {
} else { } else {
if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
if (downstream->get_upgraded()) { if (downstream->get_upgraded()) {
on_downstream_body_complete(downstream); if (on_downstream_body_complete(downstream) != 0) {
return -1;
}
} else { } else {
shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
} }
@ -1295,6 +1370,24 @@ int Http3Upstream::on_downstream_body_complete(Downstream *downstream) {
return 0; return 0;
} }
if (!downstream->get_upgraded()) {
const auto &trailers = resp.fs.trailers();
if (!trailers.empty()) {
std::vector<nghttp3_nv> nva;
nva.reserve(trailers.size());
http3::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
if (!nva.empty()) {
auto rv = nghttp3_conn_submit_trailers(
httpconn_, downstream->get_stream_id(), nva.data(), nva.size());
if (rv != 0) {
ULOG(FATAL, this) << "nghttp3_conn_submit_trailers() failed: "
<< nghttp3_strerror(rv);
return -1;
}
}
}
}
nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id()); nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id());
downstream->ensure_upstream_wtimer(); downstream->ensure_upstream_wtimer();
@ -1312,17 +1405,15 @@ void Http3Upstream::on_handler_delete() {
auto worker = handler_->get_worker(); auto worker = handler_->get_worker();
auto quic_conn_handler = worker->get_quic_connection_handler(); auto quic_conn_handler = worker->get_quic_connection_handler();
quic_conn_handler->remove_connection_id( std::vector<ngtcp2_cid> scids(ngtcp2_conn_get_num_scid(conn_) + 1);
ngtcp2_conn_get_client_initial_dcid(conn_));
std::vector<ngtcp2_cid> scids(ngtcp2_conn_get_num_scid(conn_));
ngtcp2_conn_get_scid(conn_, scids.data()); ngtcp2_conn_get_scid(conn_, scids.data());
scids.back() = hashed_scid_;
for (auto &cid : scids) { for (auto &cid : scids) {
quic_conn_handler->remove_connection_id(&cid); quic_conn_handler->remove_connection_id(cid);
} }
if (idle_close_) { if (idle_close_ || retry_close_) {
return; return;
} }
@ -1337,7 +1428,7 @@ void Http3Upstream::on_handler_delete() {
auto nwrite = ngtcp2_conn_write_connection_close( auto nwrite = ngtcp2_conn_write_connection_close(
conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(), conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(),
NGTCP2_NO_ERROR, quic_timestamp()); NGTCP2_NO_ERROR, nullptr, 0, quic_timestamp());
if (nwrite < 0) { if (nwrite < 0) {
if (nwrite != NGTCP2_ERR_INVALID_STATE) { if (nwrite != NGTCP2_ERR_INVALID_STATE) {
ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: " ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: "
@ -1349,10 +1440,9 @@ void Http3Upstream::on_handler_delete() {
conn_close_.resize(nwrite); conn_close_.resize(nwrite);
quic_send_packet(static_cast<UpstreamAddr *>(ps.path.user_data), send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
ps.path.remote.addr, ps.path.remote.addrlen, ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr,
ps.path.local.addr, ps.path.local.addrlen, ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0);
conn_close_.data(), nwrite, 0);
} }
auto d = auto d =
@ -1571,34 +1661,63 @@ void Http3Upstream::cancel_premature_downstream(
int Http3Upstream::on_read(const UpstreamAddr *faddr, int Http3Upstream::on_read(const UpstreamAddr *faddr,
const Address &remote_addr, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, const Address &local_addr, const ngtcp2_pkt_info &pi,
size_t datalen) { const uint8_t *data, size_t datalen) {
int rv; int rv;
ngtcp2_pkt_info pi{};
auto path = ngtcp2_path{ auto path = ngtcp2_path{
{ {
local_addr.len,
const_cast<sockaddr *>(&local_addr.su.sa), const_cast<sockaddr *>(&local_addr.su.sa),
local_addr.len,
}, },
{ {
remote_addr.len,
const_cast<sockaddr *>(&remote_addr.su.sa), const_cast<sockaddr *>(&remote_addr.su.sa),
remote_addr.len,
}, },
const_cast<UpstreamAddr *>(faddr), const_cast<UpstreamAddr *>(faddr),
}; };
rv = ngtcp2_conn_read_pkt(conn_, &path, &pi, data, datalen, quic_timestamp()); rv = ngtcp2_conn_read_pkt(conn_, &path, &pi, data, datalen, quic_timestamp());
if (rv != 0) { if (rv != 0) {
ULOG(ERROR, this) << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv);
switch (rv) { switch (rv) {
case NGTCP2_ERR_DRAINING: case NGTCP2_ERR_DRAINING:
// TODO Start drain period
return -1; return -1;
case NGTCP2_ERR_RETRY: case NGTCP2_ERR_RETRY: {
// TODO Send Retry packet auto worker = handler_->get_worker();
auto quic_conn_handler = worker->get_quic_connection_handler();
uint32_t version;
const uint8_t *dcid, *scid;
size_t dcidlen, scidlen;
rv = ngtcp2_pkt_decode_version_cid(&version, &dcid, &dcidlen, &scid,
&scidlen, data, datalen,
SHRPX_QUIC_SCIDLEN);
if (rv != 0) {
return -1; return -1;
}
if (worker->get_graceful_shutdown()) {
ngtcp2_cid ini_dcid, ini_scid;
ngtcp2_cid_init(&ini_dcid, dcid, dcidlen);
ngtcp2_cid_init(&ini_scid, scid, scidlen);
quic_conn_handler->send_connection_close(
faddr, version, ini_dcid, ini_scid, remote_addr, local_addr,
NGTCP2_CONNECTION_REFUSED);
return -1;
}
retry_close_ = true;
quic_conn_handler->send_retry(handler_->get_upstream_addr(), version,
dcid, dcidlen, scid, scidlen, remote_addr,
local_addr);
return -1;
}
case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM:
case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM:
case NGTCP2_ERR_TRANSPORT_PARAM: case NGTCP2_ERR_TRANSPORT_PARAM:
@ -1615,7 +1734,8 @@ int Http3Upstream::on_read(const UpstreamAddr *faddr,
} }
} }
// TODO Send connection close ULOG(ERROR, this) << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv);
return handle_error(); return handle_error();
} }
@ -1624,6 +1744,29 @@ int Http3Upstream::on_read(const UpstreamAddr *faddr,
return 0; return 0;
} }
int Http3Upstream::send_packet(const UpstreamAddr *faddr,
const sockaddr *remote_sa, size_t remote_salen,
const sockaddr *local_sa, size_t local_salen,
const ngtcp2_pkt_info &pi, const uint8_t *data,
size_t datalen, size_t gso_size) {
auto rv = quic_send_packet(faddr, remote_sa, remote_salen, local_sa,
local_salen, pi, data, datalen, gso_size);
switch (rv) {
case 0:
return 0;
// With GSO, sendmsg may fail with EINVAL if UDP payload is too
// large.
case -EINVAL:
case -EMSGSIZE:
max_udp_payload_size_ = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
break;
default:
break;
}
return -1;
}
int Http3Upstream::handle_error() { int Http3Upstream::handle_error() {
if (ngtcp2_conn_is_in_closing_period(conn_)) { if (ngtcp2_conn_is_in_closing_period(conn_)) {
return -1; return -1;
@ -1643,7 +1786,7 @@ int Http3Upstream::handle_error() {
if (last_error_.type == quic::ErrorType::Transport) { if (last_error_.type == quic::ErrorType::Transport) {
nwrite = ngtcp2_conn_write_connection_close( nwrite = ngtcp2_conn_write_connection_close(
conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(), conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(),
last_error_.code, ts); last_error_.code, nullptr, 0, ts);
if (nwrite < 0) { if (nwrite < 0) {
ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: " ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: "
<< ngtcp2_strerror(nwrite); << ngtcp2_strerror(nwrite);
@ -1652,7 +1795,7 @@ int Http3Upstream::handle_error() {
} else { } else {
nwrite = ngtcp2_conn_write_application_close( nwrite = ngtcp2_conn_write_application_close(
conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(), conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(),
last_error_.code, ts); last_error_.code, nullptr, 0, ts);
if (nwrite < 0) { if (nwrite < 0) {
ULOG(ERROR, this) << "ngtcp2_conn_write_application_close: " ULOG(ERROR, this) << "ngtcp2_conn_write_application_close: "
<< ngtcp2_strerror(nwrite); << ngtcp2_strerror(nwrite);
@ -1662,10 +1805,9 @@ int Http3Upstream::handle_error() {
conn_close_.resize(nwrite); conn_close_.resize(nwrite);
quic_send_packet(static_cast<UpstreamAddr *>(ps.path.user_data), send_packet(static_cast<UpstreamAddr *>(ps.path.user_data),
ps.path.remote.addr, ps.path.remote.addrlen, ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr,
ps.path.local.addr, ps.path.local.addrlen, ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0);
conn_close_.data(), nwrite, 0);
return -1; return -1;
} }
@ -1762,7 +1904,7 @@ int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
namespace { namespace {
int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
size_t datalen, void *user_data, uint64_t datalen, void *user_data,
void *stream_user_data) { void *stream_user_data) {
auto upstream = static_cast<Http3Upstream *>(user_data); auto upstream = static_cast<Http3Upstream *>(user_data);
auto downstream = static_cast<Downstream *>(stream_user_data); auto downstream = static_cast<Downstream *>(stream_user_data);
@ -1778,7 +1920,7 @@ int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
} // namespace } // namespace
int Http3Upstream::http_acked_stream_data(Downstream *downstream, int Http3Upstream::http_acked_stream_data(Downstream *downstream,
size_t datalen) { uint64_t datalen) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Stream " << downstream->get_stream_id() << " " ULOG(INFO, this) << "Stream " << downstream->get_stream_id() << " "
<< datalen << " bytes acknowledged"; << datalen << " bytes acknowledged";
@ -1786,6 +1928,7 @@ int Http3Upstream::http_acked_stream_data(Downstream *downstream,
auto body = downstream->get_response_buf(); auto body = downstream->get_response_buf();
auto drained = body->drain_mark(datalen); auto drained = body->drain_mark(datalen);
(void)drained;
assert(datalen == drained); assert(datalen == drained);
@ -1822,8 +1965,29 @@ int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id,
return 0; return 0;
} }
if (upstream->http_recv_request_header(downstream, token, name, value, if (upstream->http_recv_request_header(downstream, token, name, value, flags,
flags) != 0) { /* trailer = */ false) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
namespace {
int http_recv_request_trailer(nghttp3_conn *conn, int64_t stream_id,
int32_t token, nghttp3_rcbuf *name,
nghttp3_rcbuf *value, uint8_t flags,
void *user_data, void *stream_user_data) {
auto upstream = static_cast<Http3Upstream *>(user_data);
auto downstream = static_cast<Downstream *>(stream_user_data);
if (!downstream || downstream->get_stop_reading()) {
return 0;
}
if (upstream->http_recv_request_header(downstream, token, name, value, flags,
/* trailer = */ true) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE; return NGHTTP3_ERR_CALLBACK_FAILURE;
} }
@ -1834,8 +1998,8 @@ int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id,
int Http3Upstream::http_recv_request_header(Downstream *downstream, int Http3Upstream::http_recv_request_header(Downstream *downstream,
int32_t h3token, int32_t h3token,
nghttp3_rcbuf *name, nghttp3_rcbuf *name,
nghttp3_rcbuf *value, nghttp3_rcbuf *value, uint8_t flags,
uint8_t flags) { bool trailer) {
auto namebuf = nghttp3_rcbuf_get_buf(name); auto namebuf = nghttp3_rcbuf_get_buf(name);
auto valuebuf = nghttp3_rcbuf_get_buf(value); auto valuebuf = nghttp3_rcbuf_get_buf(value);
auto &req = downstream->request(); auto &req = downstream->request();
@ -1857,8 +2021,13 @@ int Http3Upstream::http_recv_request_header(Downstream *downstream,
<< ", num=" << req.fs.num_fields() + 1; << ", num=" << req.fs.num_fields() + 1;
} }
// just ignore if this is a trailer part.
if (trailer) {
return 0;
}
if (error_reply(downstream, 431) != 0) { if (error_reply(downstream, 431) != 0) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return -1;
} }
return 0; return 0;
@ -1870,6 +2039,13 @@ int Http3Upstream::http_recv_request_header(Downstream *downstream,
downstream->add_rcbuf(name); downstream->add_rcbuf(name);
downstream->add_rcbuf(value); downstream->add_rcbuf(value);
if (trailer) {
req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len}, no_index,
token);
return 0;
}
req.fs.add_header_token(StringRef{namebuf.base, namebuf.len}, req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len}, no_index, StringRef{valuebuf.base, valuebuf.len}, no_index,
token); token);
@ -1877,7 +2053,7 @@ int Http3Upstream::http_recv_request_header(Downstream *downstream,
} }
namespace { namespace {
int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin,
void *user_data, void *stream_user_data) { void *user_data, void *stream_user_data) {
auto upstream = static_cast<Http3Upstream *>(user_data); auto upstream = static_cast<Http3Upstream *>(user_data);
auto handler = upstream->get_client_handler(); auto handler = upstream->get_client_handler();
@ -1887,7 +2063,7 @@ int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id,
return 0; return 0;
} }
if (upstream->http_end_request_headers(downstream) != 0) { if (upstream->http_end_request_headers(downstream, fin) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE; return NGHTTP3_ERR_CALLBACK_FAILURE;
} }
@ -1898,7 +2074,7 @@ int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id,
} }
} // namespace } // namespace
int Http3Upstream::http_end_request_headers(Downstream *downstream) { int Http3Upstream::http_end_request_headers(Downstream *downstream, int fin) {
auto lgconf = log_config(); auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now()); lgconf->update_tstamp(std::chrono::system_clock::now());
auto &req = downstream->request(); auto &req = downstream->request();
@ -1939,7 +2115,7 @@ int Http3Upstream::http_end_request_headers(Downstream *downstream) {
auto method_token = http2::lookup_method_token(method->value); auto method_token = http2::lookup_method_token(method->value);
if (method_token == -1) { if (method_token == -1) {
if (error_reply(downstream, 501) != 0) { if (error_reply(downstream, 501) != 0) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return -1;
} }
return 0; return 0;
} }
@ -1951,7 +2127,7 @@ int Http3Upstream::http_end_request_headers(Downstream *downstream) {
// For HTTP/2 proxy, we require :authority. // For HTTP/2 proxy, we require :authority.
if (method_token != HTTP_CONNECT && config->http2_proxy && if (method_token != HTTP_CONNECT && config->http2_proxy &&
faddr->alt_mode == UpstreamAltMode::NONE && !authority) { faddr->alt_mode == UpstreamAltMode::NONE && !authority) {
shutdown_stream(downstream, NGHTTP2_PROTOCOL_ERROR); shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR);
return 0; return 0;
} }
@ -1987,15 +2163,18 @@ int Http3Upstream::http_end_request_headers(Downstream *downstream) {
if (connect_proto) { if (connect_proto) {
if (connect_proto->value != "websocket") { if (connect_proto->value != "websocket") {
if (error_reply(downstream, 400) != 0) { if (error_reply(downstream, 400) != 0) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return -1;
} }
return 0; return 0;
} }
req.connect_proto = ConnectProto::WEBSOCKET; req.connect_proto = ConnectProto::WEBSOCKET;
} }
// We are not sure that request has body or not at the moment. if (!fin) {
req.http2_expect_body = true; req.http2_expect_body = true;
} else if (req.fs.content_length == -1) {
req.fs.content_length = 0;
}
downstream->inspect_http2_request(); downstream->inspect_http2_request();
@ -2009,7 +2188,7 @@ int Http3Upstream::http_end_request_headers(Downstream *downstream) {
if (mruby_ctx->run_on_request_proc(downstream) != 0) { if (mruby_ctx->run_on_request_proc(downstream) != 0) {
if (error_reply(downstream, 500) != 0) { if (error_reply(downstream, 500) != 0) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return -1;
} }
return 0; return 0;
} }
@ -2162,7 +2341,7 @@ int Http3Upstream::http_end_stream(Downstream *downstream) {
if (downstream->end_upload_data() != 0) { if (downstream->end_upload_data() != 0) {
if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
shutdown_stream(downstream, NGHTTP2_INTERNAL_ERROR); shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR);
} }
} }
@ -2238,12 +2417,12 @@ int Http3Upstream::http_stream_close(Downstream *downstream,
} }
namespace { namespace {
int http_send_stop_sending(nghttp3_conn *conn, int64_t stream_id, int http_stop_sending(nghttp3_conn *conn, int64_t stream_id,
uint64_t app_error_code, void *user_data, uint64_t app_error_code, void *user_data,
void *stream_user_data) { void *stream_user_data) {
auto upstream = static_cast<Http3Upstream *>(user_data); auto upstream = static_cast<Http3Upstream *>(user_data);
if (upstream->http_send_stop_sending(stream_id, app_error_code) != 0) { if (upstream->http_stop_sending(stream_id, app_error_code) != 0) {
return NGHTTP3_ERR_CALLBACK_FAILURE; return NGHTTP3_ERR_CALLBACK_FAILURE;
} }
@ -2251,7 +2430,7 @@ int http_send_stop_sending(nghttp3_conn *conn, int64_t stream_id,
} }
} // namespace } // namespace
int Http3Upstream::http_send_stop_sending(int64_t stream_id, int Http3Upstream::http_stop_sending(int64_t stream_id,
uint64_t app_error_code) { uint64_t app_error_code) {
auto rv = ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code); auto rv = ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
if (ngtcp2_err_is_fatal(rv)) { if (ngtcp2_err_is_fatal(rv)) {
@ -2305,9 +2484,9 @@ int Http3Upstream::setup_httpconn() {
shrpx::http_recv_request_header, shrpx::http_recv_request_header,
shrpx::http_end_request_headers, shrpx::http_end_request_headers,
nullptr, // begin_trailers nullptr, // begin_trailers
nullptr, // recv_trailer shrpx::http_recv_request_trailer,
nullptr, // end_trailers nullptr, // end_trailers
shrpx::http_send_stop_sending, shrpx::http_stop_sending,
shrpx::http_end_stream, shrpx::http_end_stream,
shrpx::http_reset_stream, shrpx::http_reset_stream,
}; };
@ -2316,7 +2495,7 @@ int Http3Upstream::setup_httpconn() {
nghttp3_settings settings; nghttp3_settings settings;
nghttp3_settings_default(&settings); nghttp3_settings_default(&settings);
settings.qpack_max_table_capacity = 4_k; settings.qpack_max_dtable_capacity = 4_k;
if (!config->http2_proxy) { if (!config->http2_proxy) {
settings.enable_connect_protocol = 1; settings.enable_connect_protocol = 1;
@ -2589,7 +2768,7 @@ int Http3Upstream::open_qlog_file(const StringRef &dir,
util::format_iso8601_basic(buf.data(), std::chrono::system_clock::now()); util::format_iso8601_basic(buf.data(), std::chrono::system_clock::now());
path += '-'; path += '-';
path += util::format_hex(scid.data, scid.datalen); path += util::format_hex(scid.data, scid.datalen);
path += ".qlog"; path += ".sqlog";
int fd; int fd;

View File

@ -92,7 +92,8 @@ public:
const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen); const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen);
int on_read(const UpstreamAddr *faddr, const Address &remote_addr, int on_read(const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, size_t datalen); const Address &local_addr, const ngtcp2_pkt_info &pi,
const uint8_t *data, size_t datalen);
int write_streams(); int write_streams();
@ -123,8 +124,8 @@ public:
void http_begin_request_headers(int64_t stream_id); void http_begin_request_headers(int64_t stream_id);
int http_recv_request_header(Downstream *downstream, int32_t token, int http_recv_request_header(Downstream *downstream, int32_t token,
nghttp3_rcbuf *name, nghttp3_rcbuf *value, nghttp3_rcbuf *name, nghttp3_rcbuf *value,
uint8_t flags); uint8_t flags, bool trailer);
int http_end_request_headers(Downstream *downstream); int http_end_request_headers(Downstream *downstream, int fin);
int http_end_stream(Downstream *downstream); int http_end_stream(Downstream *downstream);
void start_downstream(Downstream *downstream); void start_downstream(Downstream *downstream);
void initiate_downstream(Downstream *downstream); void initiate_downstream(Downstream *downstream);
@ -137,10 +138,10 @@ public:
int stream_close(int64_t stream_id, uint64_t app_error_code); int stream_close(int64_t stream_id, uint64_t app_error_code);
void log_response_headers(Downstream *downstream, void log_response_headers(Downstream *downstream,
const std::vector<nghttp3_nv> &nva) const; const std::vector<nghttp3_nv> &nva) const;
int http_acked_stream_data(Downstream *downstream, size_t datalen); int http_acked_stream_data(Downstream *downstream, uint64_t datalen);
int http_shutdown_stream_read(int64_t stream_id); int http_shutdown_stream_read(int64_t stream_id);
int http_reset_stream(int64_t stream_id, uint64_t app_error_code); int http_reset_stream(int64_t stream_id, uint64_t app_error_code);
int http_send_stop_sending(int64_t stream_id, uint64_t app_error_code); int http_stop_sending(int64_t stream_id, uint64_t app_error_code);
int http_recv_data(Downstream *downstream, const uint8_t *data, int http_recv_data(Downstream *downstream, const uint8_t *data,
size_t datalen); size_t datalen);
int handshake_completed(); int handshake_completed();
@ -148,6 +149,10 @@ public:
int start_graceful_shutdown(); int start_graceful_shutdown();
int submit_goaway(); int submit_goaway();
void idle_close(); void idle_close();
int send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
size_t remote_salen, const sockaddr *local_sa,
size_t local_salen, const ngtcp2_pkt_info &pi,
const uint8_t *data, size_t datalen, size_t gso_size);
void qlog_write(const void *data, size_t datalen, bool fin); void qlog_write(const void *data, size_t datalen, bool fin);
int open_qlog_file(const StringRef &dir, const ngtcp2_cid &scid) const; int open_qlog_file(const StringRef &dir, const ngtcp2_cid &scid) const;
@ -158,13 +163,16 @@ private:
ev_timer idle_timer_; ev_timer idle_timer_;
ev_timer shutdown_timer_; ev_timer shutdown_timer_;
ev_prepare prep_; ev_prepare prep_;
size_t max_udp_payload_size_;
int qlog_fd_; int qlog_fd_;
ngtcp2_cid hashed_scid_;
ngtcp2_conn *conn_; ngtcp2_conn *conn_;
quic::Error last_error_; quic::Error last_error_;
uint8_t tls_alert_; uint8_t tls_alert_;
nghttp3_conn *httpconn_; nghttp3_conn *httpconn_;
DownstreamQueue downstream_queue_; DownstreamQueue downstream_queue_;
bool idle_close_; bool idle_close_;
bool retry_close_;
std::vector<uint8_t> conn_close_; std::vector<uint8_t> conn_close_;
}; };

View File

@ -1218,6 +1218,18 @@ int HttpDownstreamConnection::read_clear() {
} }
if (nread < 0) { if (nread < 0) {
if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) {
auto htperr = llhttp_finish(&response_htp_);
if (htperr != HPE_OK) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP response ended prematurely: "
<< llhttp_errno_name(htperr);
}
return -1;
}
}
return nread; return nread;
} }
@ -1337,6 +1349,18 @@ int HttpDownstreamConnection::read_tls() {
} }
if (nread < 0) { if (nread < 0) {
if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) {
auto htperr = llhttp_finish(&response_htp_);
if (htperr != HPE_OK) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP response ended prematurely: "
<< llhttp_errno_name(htperr);
}
return -1;
}
}
return nread; return nread;
} }

View File

@ -755,7 +755,11 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(lgsp.ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(lgsp.ssl); auto x = SSL_get_peer_certificate(lgsp.ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
@ -766,7 +770,9 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
lf.type == LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256 lf.type == LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256
? EVP_sha256() ? EVP_sha256()
: EVP_sha1()); : EVP_sha1());
#if !OPENSSL_3_0_0_API
X509_free(x); X509_free(x);
#endif // !OPENSSL_3_0_0_API
if (len <= 0) { if (len <= 0) {
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
@ -780,7 +786,11 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(lgsp.ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(lgsp.ssl); auto x = SSL_get_peer_certificate(lgsp.ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
@ -788,7 +798,9 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
auto name = lf.type == LogFragmentType::TLS_CLIENT_ISSUER_NAME auto name = lf.type == LogFragmentType::TLS_CLIENT_ISSUER_NAME
? tls::get_x509_issuer_name(balloc, x) ? tls::get_x509_issuer_name(balloc, x)
: tls::get_x509_subject_name(balloc, x); : tls::get_x509_subject_name(balloc, x);
#if !OPENSSL_3_0_0_API
X509_free(x); X509_free(x);
#endif // !OPENSSL_3_0_0_API
if (name.empty()) { if (name.empty()) {
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
@ -801,13 +813,19 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(lgsp.ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(lgsp.ssl); auto x = SSL_get_peer_certificate(lgsp.ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
} }
auto sn = tls::get_x509_serial(balloc, x); auto sn = tls::get_x509_serial(balloc, x);
#if !OPENSSL_3_0_0_API
X509_free(x); X509_free(x);
#endif // !OPENSSL_3_0_0_API
if (sn.empty()) { if (sn.empty()) {
std::tie(p, last) = copy('-', p, last); std::tie(p, last) = copy('-', p, last);
break; break;
@ -964,7 +982,7 @@ int open_log_file(const char *path) {
strcmp(path, "/proc/self/fd/2") == 0) { strcmp(path, "/proc/self/fd/2") == 0) {
return STDERR_COPY; return STDERR_COPY;
} }
#if defined O_CLOEXEC #ifdef O_CLOEXEC
auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
S_IRUSR | S_IWUSR | S_IRGRP); S_IRUSR | S_IWUSR | S_IRGRP);

View File

@ -75,6 +75,7 @@ int MRubyContext::run_app(Downstream *downstream, int phase) {
break; break;
default: default:
assert(0); assert(0);
abort();
} }
auto res = mrb_funcall(mrb_, app_, method, 1, env_); auto res = mrb_funcall(mrb_, app_, method, 1, env_);

View File

@ -153,7 +153,11 @@ mrb_value env_get_tls_client_fingerprint_md(mrb_state *mrb, const EVP_MD *md) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(ssl); auto x = SSL_get_peer_certificate(ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
@ -161,7 +165,9 @@ mrb_value env_get_tls_client_fingerprint_md(mrb_state *mrb, const EVP_MD *md) {
// Currently the largest hash value is SHA-256, which is 32 bytes. // Currently the largest hash value is SHA-256, which is 32 bytes.
std::array<uint8_t, 32> buf; std::array<uint8_t, 32> buf;
auto slen = tls::get_x509_fingerprint(buf.data(), buf.size(), x, md); auto slen = tls::get_x509_fingerprint(buf.data(), buf.size(), x, md);
#if !OPENSSL_3_0_0_API
X509_free(x); X509_free(x);
#endif // !OPENSSL_3_0_0_API
if (slen == -1) { if (slen == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "could not compute client fingerprint"); mrb_raise(mrb, E_RUNTIME_ERROR, "could not compute client fingerprint");
} }
@ -199,14 +205,20 @@ mrb_value env_get_tls_client_subject_name(mrb_state *mrb, mrb_value self) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(ssl); auto x = SSL_get_peer_certificate(ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
auto &balloc = downstream->get_block_allocator(); auto &balloc = downstream->get_block_allocator();
auto name = tls::get_x509_subject_name(balloc, x); auto name = tls::get_x509_subject_name(balloc, x);
#if !OPENSSL_3_0_0_API
X509_free(x); X509_free(x);
#endif // !OPENSSL_3_0_0_API
return mrb_str_new(mrb, name.c_str(), name.size()); return mrb_str_new(mrb, name.c_str(), name.size());
} }
} // namespace } // namespace
@ -223,14 +235,20 @@ mrb_value env_get_tls_client_issuer_name(mrb_state *mrb, mrb_value self) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(ssl); auto x = SSL_get_peer_certificate(ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
auto &balloc = downstream->get_block_allocator(); auto &balloc = downstream->get_block_allocator();
auto name = tls::get_x509_issuer_name(balloc, x); auto name = tls::get_x509_issuer_name(balloc, x);
#if !OPENSSL_3_0_0_API
X509_free(x); X509_free(x);
#endif // !OPENSSL_3_0_0_API
return mrb_str_new(mrb, name.c_str(), name.size()); return mrb_str_new(mrb, name.c_str(), name.size());
} }
} // namespace } // namespace
@ -247,14 +265,20 @@ mrb_value env_get_tls_client_serial(mrb_state *mrb, mrb_value self) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(ssl); auto x = SSL_get_peer_certificate(ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
return mrb_str_new_static(mrb, "", 0); return mrb_str_new_static(mrb, "", 0);
} }
auto &balloc = downstream->get_block_allocator(); auto &balloc = downstream->get_block_allocator();
auto sn = tls::get_x509_serial(balloc, x); auto sn = tls::get_x509_serial(balloc, x);
#if !OPENSSL_3_0_0_API
X509_free(x); X509_free(x);
#endif // !OPENSSL_3_0_0_API
return mrb_str_new(mrb, sn.c_str(), sn.size()); return mrb_str_new(mrb, sn.c_str(), sn.size());
} }
} // namespace } // namespace
@ -271,16 +295,24 @@ mrb_value env_get_tls_client_not_before(mrb_state *mrb, mrb_value self) {
return mrb_fixnum_value(0); return mrb_fixnum_value(0);
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(ssl); auto x = SSL_get_peer_certificate(ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
return mrb_fixnum_value(0); return mrb_fixnum_value(0);
} }
time_t t; time_t t;
if (tls::get_x509_not_before(t, x) != 0) { if (tls::get_x509_not_before(t, x) != 0) {
return mrb_fixnum_value(0); t = 0;
} }
#if !OPENSSL_3_0_0_API
X509_free(x);
#endif // !OPENSSL_3_0_0_API
return mrb_fixnum_value(t); return mrb_fixnum_value(t);
} }
} // namespace } // namespace
@ -297,16 +329,24 @@ mrb_value env_get_tls_client_not_after(mrb_state *mrb, mrb_value self) {
return mrb_fixnum_value(0); return mrb_fixnum_value(0);
} }
#if OPENSSL_3_0_0_API
auto x = SSL_get0_peer_certificate(ssl);
#else // !OPENSSL_3_0_0_API
auto x = SSL_get_peer_certificate(ssl); auto x = SSL_get_peer_certificate(ssl);
#endif // !OPENSSL_3_0_0_API
if (!x) { if (!x) {
return mrb_fixnum_value(0); return mrb_fixnum_value(0);
} }
time_t t; time_t t;
if (tls::get_x509_not_after(t, x) != 0) { if (tls::get_x509_not_after(t, x) != 0) {
return mrb_fixnum_value(0); t = 0;
} }
#if !OPENSSL_3_0_0_API
X509_free(x);
#endif // !OPENSSL_3_0_0_API
return mrb_fixnum_value(t); return mrb_fixnum_value(t);
} }
} // namespace } // namespace

View File

@ -74,7 +74,7 @@ int NullDownstreamConnection::on_read() { return 0; }
int NullDownstreamConnection::on_write() { return 0; } int NullDownstreamConnection::on_write() { return 0; }
void NullDownstreamConnection::on_upstream_change(Upstream *uptream) {} void NullDownstreamConnection::on_upstream_change(Upstream *upstream) {}
bool NullDownstreamConnection::poolable() const { return false; } bool NullDownstreamConnection::poolable() const { return false; }

View File

@ -50,7 +50,7 @@ public:
virtual int on_read(); virtual int on_read();
virtual int on_write(); virtual int on_write();
virtual void on_upstream_change(Upstream *uptream); virtual void on_upstream_change(Upstream *upstream);
// true if this object is poolable. // true if this object is poolable.
virtual bool poolable() const; virtual bool poolable() const;

View File

@ -43,8 +43,6 @@
#include "util.h" #include "util.h"
#include "xsi_strerror.h" #include "xsi_strerror.h"
using namespace nghttp2;
bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs) { bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs) {
return ngtcp2_cid_eq(&lhs, &rhs); return ngtcp2_cid_eq(&lhs, &rhs);
} }
@ -59,8 +57,8 @@ ngtcp2_tstamp quic_timestamp() {
int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
size_t remote_salen, const sockaddr *local_sa, size_t remote_salen, const sockaddr *local_sa,
size_t local_salen, const uint8_t *data, size_t datalen, size_t local_salen, const ngtcp2_pkt_info &pi,
size_t gso_size) { const uint8_t *data, size_t datalen, size_t gso_size) {
iovec msg_iov = {const_cast<uint8_t *>(data), datalen}; iovec msg_iov = {const_cast<uint8_t *>(data), datalen};
msghdr msg{}; msghdr msg{};
msg.msg_name = const_cast<sockaddr *>(remote_sa); msg.msg_name = const_cast<sockaddr *>(remote_sa);
@ -125,6 +123,8 @@ int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
msg.msg_controllen = controllen; msg.msg_controllen = controllen;
util::fd_set_send_ecn(faddr->fd, local_sa->sa_family, pi.ecn);
ssize_t nwrite; ssize_t nwrite;
do { do {
@ -132,65 +132,138 @@ int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
} while (nwrite == -1 && errno == EINTR); } while (nwrite == -1 && errno == EINTR);
if (nwrite == -1) { if (nwrite == -1) {
return -1; if (LOG_ENABLED(INFO)) {
auto error = errno;
LOG(INFO) << "sendmsg failed: errno=" << error;
}
return -errno;
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "QUIC sent packet: local=" LOG(INFO) << "QUIC sent packet: local="
<< util::to_numeric_addr(local_sa, local_salen) << util::to_numeric_addr(local_sa, local_salen)
<< " remote=" << util::to_numeric_addr(remote_sa, remote_salen) << " remote=" << util::to_numeric_addr(remote_sa, remote_salen)
<< " " << nwrite << " bytes"; << " ecn=" << log::hex << pi.ecn << log::dec << " " << nwrite
<< " bytes";
} }
return 0; return 0;
} }
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen) { int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen,
if (RAND_bytes(cid->data, cidlen) != 1) { const uint8_t *server_id, uint8_t km_id,
const uint8_t *key) {
assert(cidlen == SHRPX_QUIC_SCIDLEN);
if (RAND_bytes(cid.data, cidlen) != 1) {
return -1; return -1;
} }
cid->datalen = cidlen; cid.datalen = cidlen;
return 0; cid.data[0] = (cid.data[0] & 0x3f) | km_id;
auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET;
std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, p);
return encrypt_quic_connection_id(p, p, key);
} }
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen, int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen,
const uint8_t *cid_prefix) { const uint8_t *cid_prefix, uint8_t km_id,
assert(cidlen > SHRPX_QUIC_CID_PREFIXLEN); const uint8_t *key) {
assert(cidlen == SHRPX_QUIC_SCIDLEN);
auto p = std::copy_n(cid_prefix, SHRPX_QUIC_CID_PREFIXLEN, cid->data); if (RAND_bytes(cid.data, cidlen) != 1) {
if (RAND_bytes(p, cidlen - SHRPX_QUIC_CID_PREFIXLEN) != 1) {
return -1; return -1;
} }
cid->datalen = cidlen; cid.datalen = cidlen;
cid.data[0] = (cid.data[0] & 0x3f) | km_id;
auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET;
std::copy_n(cid_prefix, SHRPX_QUIC_CID_PREFIXLEN, p);
return encrypt_quic_connection_id(p, p, key);
}
int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key) {
auto ctx = EVP_CIPHER_CTX_new();
auto d = defer(EVP_CIPHER_CTX_free, ctx);
if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
int len;
if (!EVP_EncryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
!EVP_EncryptFinal_ex(ctx, dest + len, &len)) {
return -1;
}
return 0; return 0;
} }
int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid *cid, int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key) {
auto ctx = EVP_CIPHER_CTX_new();
auto d = defer(EVP_CIPHER_CTX_free, ctx);
if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
int len;
if (!EVP_DecryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
!EVP_DecryptFinal_ex(ctx, dest + len, &len)) {
return -1;
}
return 0;
}
int generate_quic_hashed_connection_id(ngtcp2_cid &dest,
const Address &remote_addr,
const Address &local_addr,
const ngtcp2_cid &cid) {
auto ctx = EVP_MD_CTX_new();
auto d = defer(EVP_MD_CTX_free, ctx);
std::array<uint8_t, 32> h;
unsigned int hlen = EVP_MD_size(EVP_sha256());
if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) ||
!EVP_DigestUpdate(ctx, &remote_addr.su.sa, remote_addr.len) ||
!EVP_DigestUpdate(ctx, &local_addr.su.sa, local_addr.len) ||
!EVP_DigestUpdate(ctx, cid.data, cid.datalen) ||
!EVP_DigestFinal_ex(ctx, h.data(), &hlen)) {
return -1;
}
assert(hlen == h.size());
std::copy_n(std::begin(h), sizeof(dest.data), std::begin(dest.data));
dest.datalen = sizeof(dest.data);
return 0;
}
int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid,
const uint8_t *secret, const uint8_t *secret,
size_t secretlen) { size_t secretlen) {
if (ngtcp2_crypto_generate_stateless_reset_token(token, secret, secretlen, if (ngtcp2_crypto_generate_stateless_reset_token(token, secret, secretlen,
cid) != 0) { &cid) != 0) {
return -1;
}
return 0;
}
int generate_quic_stateless_reset_secret(uint8_t *secret) {
if (RAND_bytes(secret, SHRPX_QUIC_STATELESS_RESET_SECRETLEN) != 1) {
return -1;
}
return 0;
}
int generate_quic_token_secret(uint8_t *secret) {
if (RAND_bytes(secret, SHRPX_QUIC_TOKEN_SECRETLEN) != 1) {
return -1; return -1;
} }
@ -198,15 +271,15 @@ int generate_quic_token_secret(uint8_t *secret) {
} }
int generate_retry_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, int generate_retry_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
socklen_t salen, const ngtcp2_cid *retry_scid, socklen_t salen, const ngtcp2_cid &retry_scid,
const ngtcp2_cid *odcid, const uint8_t *token_secret) { const ngtcp2_cid &odcid, const uint8_t *secret,
size_t secretlen) {
auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()) std::chrono::system_clock::now().time_since_epoch())
.count(); .count();
auto stokenlen = ngtcp2_crypto_generate_retry_token( auto stokenlen = ngtcp2_crypto_generate_retry_token(
token, token_secret, SHRPX_QUIC_TOKEN_SECRETLEN, sa, salen, retry_scid, token, secret, secretlen, sa, salen, &retry_scid, &odcid, t);
odcid, t);
if (stokenlen < 0) { if (stokenlen < 0) {
return -1; return -1;
} }
@ -216,17 +289,18 @@ int generate_retry_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
return 0; return 0;
} }
int verify_retry_token(ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen,
const ngtcp2_cid *dcid, const sockaddr *sa, const ngtcp2_cid &dcid, const sockaddr *sa,
socklen_t salen, const uint8_t *token_secret) { socklen_t salen, const uint8_t *secret,
size_t secretlen) {
auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()) std::chrono::system_clock::now().time_since_epoch())
.count(); .count();
if (ngtcp2_crypto_verify_retry_token(odcid, token, tokenlen, token_secret, if (ngtcp2_crypto_verify_retry_token(&odcid, token, tokenlen, secret,
SHRPX_QUIC_TOKEN_SECRETLEN, sa, salen, secretlen, sa, salen, &dcid,
dcid, 10 * NGTCP2_SECONDS, t) != 0) { 10 * NGTCP2_SECONDS, t) != 0) {
return -1; return -1;
} }
@ -234,13 +308,13 @@ int verify_retry_token(ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
} }
int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
size_t salen, const uint8_t *token_secret) { size_t salen, const uint8_t *secret, size_t secretlen) {
auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()) std::chrono::system_clock::now().time_since_epoch())
.count(); .count();
auto stokenlen = ngtcp2_crypto_generate_regular_token( auto stokenlen = ngtcp2_crypto_generate_regular_token(
token, token_secret, SHRPX_QUIC_TOKEN_SECRETLEN, sa, salen, t); token, secret, secretlen, sa, salen, t);
if (stokenlen < 0) { if (stokenlen < 0) {
return -1; return -1;
} }
@ -251,18 +325,48 @@ int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
} }
int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa, int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa,
socklen_t salen, const uint8_t *token_secret) { socklen_t salen, const uint8_t *secret, size_t secretlen) {
auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()) std::chrono::system_clock::now().time_since_epoch())
.count(); .count();
if (ngtcp2_crypto_verify_regular_token(token, tokenlen, token_secret, if (ngtcp2_crypto_verify_regular_token(token, tokenlen, secret, secretlen, sa,
SHRPX_QUIC_TOKEN_SECRETLEN, sa, salen, salen, 3600 * NGTCP2_SECONDS,
3600 * NGTCP2_SECONDS, t) != 0) { t) != 0) {
return -1; return -1;
} }
return 0; return 0;
} }
int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen,
const uint8_t *secret,
size_t secretlen,
const uint8_t *salt,
size_t saltlen) {
constexpr uint8_t info[] = "connection id encryption key";
ngtcp2_crypto_md sha256;
ngtcp2_crypto_md_init(
&sha256, reinterpret_cast<void *>(const_cast<EVP_MD *>(EVP_sha256())));
if (ngtcp2_crypto_hkdf(key, keylen, &sha256, secret, secretlen, salt, saltlen,
info, str_size(info)) != 0) {
return -1;
}
return 0;
}
const QUICKeyingMaterial *
select_quic_keying_material(const QUICKeyingMaterials &qkms,
const uint8_t *cid) {
for (auto &qkm : qkms.keying_materials) {
if (((*cid) & 0xc0) == qkm.id) {
return &qkm;
}
}
return &qkms.keying_materials.front();
}
} // namespace shrpx } // namespace shrpx

View File

@ -33,6 +33,10 @@
#include <ngtcp2/ngtcp2.h> #include <ngtcp2/ngtcp2.h>
#include "network.h"
using namespace nghttp2;
namespace std { namespace std {
template <> struct hash<ngtcp2_cid> { template <> struct hash<ngtcp2_cid> {
std::size_t operator()(const ngtcp2_cid &cid) const noexcept { std::size_t operator()(const ngtcp2_cid &cid) const noexcept {
@ -56,48 +60,78 @@ bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs);
namespace shrpx { namespace shrpx {
struct UpstreamAddr; struct UpstreamAddr;
struct QUICKeyingMaterials;
struct QUICKeyingMaterial;
constexpr size_t SHRPX_QUIC_SCIDLEN = 20; constexpr size_t SHRPX_QUIC_SCIDLEN = 20;
constexpr size_t SHRPX_QUIC_SERVER_IDLEN = 4;
// SHRPX_QUIC_CID_PREFIXLEN includes SHRPX_QUIC_SERVER_IDLEN.
constexpr size_t SHRPX_QUIC_CID_PREFIXLEN = 8; constexpr size_t SHRPX_QUIC_CID_PREFIXLEN = 8;
constexpr size_t SHRPX_QUIC_CID_PREFIX_OFFSET = 1;
constexpr size_t SHRPX_QUIC_DECRYPTED_DCIDLEN = 16;
constexpr size_t SHRPX_QUIC_CID_ENCRYPTION_KEYLEN = 16;
constexpr size_t SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE = 1472; constexpr size_t SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE = 1472;
constexpr size_t SHRPX_QUIC_STATELESS_RESET_SECRETLEN = 32;
constexpr size_t SHRPX_QUIC_TOKEN_SECRETLEN = 32;
constexpr size_t SHRPX_QUIC_CONN_CLOSE_PKTLEN = 256; constexpr size_t SHRPX_QUIC_CONN_CLOSE_PKTLEN = 256;
constexpr size_t SHRPX_QUIC_STATELESS_RESET_BURST = 100; constexpr size_t SHRPX_QUIC_STATELESS_RESET_BURST = 100;
constexpr size_t SHRPX_QUIC_SECRET_RESERVEDLEN = 4;
constexpr size_t SHRPX_QUIC_SECRETLEN = 32;
constexpr size_t SHRPX_QUIC_SALTLEN = 32;
ngtcp2_tstamp quic_timestamp(); ngtcp2_tstamp quic_timestamp();
int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
size_t remote_salen, const sockaddr *local_sa, size_t remote_salen, const sockaddr *local_sa,
size_t local_salen, const uint8_t *data, size_t datalen, size_t local_salen, const ngtcp2_pkt_info &pi,
size_t gso_size); const uint8_t *data, size_t datalen, size_t gso_size);
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen); int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen,
const uint8_t *server_id, uint8_t km_id,
const uint8_t *key);
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen, int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen,
const uint8_t *cid_prefix); const uint8_t *cid_prefix, uint8_t km_id,
const uint8_t *key);
int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid *cid, int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key);
int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key);
int generate_quic_hashed_connection_id(ngtcp2_cid &dest,
const Address &remote_addr,
const Address &local_addr,
const ngtcp2_cid &cid);
int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid,
const uint8_t *secret, const uint8_t *secret,
size_t secretlen); size_t secretlen);
int generate_quic_stateless_reset_secret(uint8_t *secret);
int generate_quic_token_secret(uint8_t *secret);
int generate_retry_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, int generate_retry_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
socklen_t salen, const ngtcp2_cid *retry_scid, socklen_t salen, const ngtcp2_cid &retry_scid,
const ngtcp2_cid *odcid, const uint8_t *token_secret); const ngtcp2_cid &odcid, const uint8_t *secret,
size_t secretlen);
int verify_retry_token(ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen,
const ngtcp2_cid *dcid, const sockaddr *sa, const ngtcp2_cid &dcid, const sockaddr *sa,
socklen_t salen, const uint8_t *token_secret); socklen_t salen, const uint8_t *secret,
size_t secretlen);
int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa,
size_t salen, const uint8_t *token_secret); size_t salen, const uint8_t *secret, size_t secretlen);
int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa, int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa,
socklen_t salen, const uint8_t *token_secret); socklen_t salen, const uint8_t *secret, size_t secretlen);
int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen,
const uint8_t *secret,
size_t secretlen,
const uint8_t *salt,
size_t saltlen);
const QUICKeyingMaterial *
select_quic_keying_material(const QUICKeyingMaterials &qkms,
const uint8_t *cid);
} // namespace shrpx } // namespace shrpx

View File

@ -61,6 +61,7 @@ QUICConnectionHandler::~QUICConnectionHandler() {
int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
const Address &remote_addr, const Address &remote_addr,
const Address &local_addr, const Address &local_addr,
const ngtcp2_pkt_info &pi,
const uint8_t *data, size_t datalen) { const uint8_t *data, size_t datalen) {
int rv; int rv;
uint32_t version; uint32_t version;
@ -90,30 +91,70 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
ClientHandler *handler; ClientHandler *handler;
auto &quicconf = config->quic;
auto it = connections_.find(dcid_key); auto it = connections_.find(dcid_key);
if (it == std::end(connections_)) { if (it == std::end(connections_)) {
if (!std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN, auto cwit = close_waits_.find(dcid_key);
if (cwit != std::end(close_waits_)) {
auto cw = (*cwit).second;
cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen);
return 0;
}
if (data[0] & 0x80) {
if (generate_quic_hashed_connection_id(dcid_key, remote_addr, local_addr,
dcid_key) != 0) {
return 0;
}
it = connections_.find(dcid_key);
if (it == std::end(connections_)) {
auto cwit = close_waits_.find(dcid_key);
if (cwit != std::end(close_waits_)) {
auto cw = (*cwit).second;
cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen);
return 0;
}
}
}
}
if (it == std::end(connections_)) {
std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
auto &qkms = conn_handler->get_quic_keying_materials();
const QUICKeyingMaterial *qkm = nullptr;
if (dcidlen == SHRPX_QUIC_SCIDLEN) {
qkm = select_quic_keying_material(*qkms.get(), dcid);
if (decrypt_quic_connection_id(decrypted_dcid.data(),
dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
qkm->cid_encryption_key.data()) != 0) {
return 0;
}
if (qkm != &qkms->keying_materials.front() ||
!std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker_->get_cid_prefix())) { worker_->get_cid_prefix())) {
auto quic_lwp = auto quic_lwp =
conn_handler->match_quic_lingering_worker_process_cid_prefix(dcid, conn_handler->match_quic_lingering_worker_process_cid_prefix(
dcidlen); decrypted_dcid.data(), decrypted_dcid.size());
if (quic_lwp) { if (quic_lwp) {
if (conn_handler->forward_quic_packet_to_lingering_worker_process( if (conn_handler->forward_quic_packet_to_lingering_worker_process(
quic_lwp, remote_addr, local_addr, data, datalen) == 0) { quic_lwp, remote_addr, local_addr, pi, data, datalen) == 0) {
return 0; return 0;
} }
return 0; return 0;
} }
} }
auto it = close_waits_.find(dcid_key);
if (it != std::end(close_waits_)) {
auto cw = (*it).second;
cw->handle_packet(faddr, remote_addr, local_addr, data, datalen);
return 0;
} }
// new connection // new connection
@ -136,26 +177,56 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
switch (ngtcp2_accept(&hd, data, datalen)) { switch (ngtcp2_accept(&hd, data, datalen)) {
case 0: { case 0: {
// If we get Initial and it has the CID prefix of this worker, it // If we get Initial and it has the CID prefix of this worker,
// is likely that client is intentionally use the our prefix. // it is likely that client is intentionally use the prefix.
// Just drop it. // Just drop it.
if (std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN, if (dcidlen == SHRPX_QUIC_SCIDLEN) {
if (qkm != &qkms->keying_materials.front()) {
qkm = &qkms->keying_materials.front();
if (decrypt_quic_connection_id(decrypted_dcid.data(),
dcid + SHRPX_QUIC_CID_PREFIX_OFFSET,
qkm->cid_encryption_key.data()) != 0) {
return 0;
}
}
if (std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker_->get_cid_prefix())) { worker_->get_cid_prefix())) {
return 0; return 0;
} }
}
if (worker_->get_graceful_shutdown()) {
send_connection_close(faddr, version, hd.dcid, hd.scid, remote_addr,
local_addr, NGTCP2_CONNECTION_REFUSED);
return 0;
}
if (hd.token.len == 0) { if (hd.token.len == 0) {
if (quicconf.upstream.require_token) {
send_retry(faddr, version, dcid, dcidlen, scid, scidlen, remote_addr,
local_addr);
return 0;
}
break; break;
} }
auto &quic_secret = worker_->get_quic_secret(); if (dcidlen != SHRPX_QUIC_SCIDLEN) {
auto &secret = quic_secret->token_secret; // Initial packets with token must have DCID chosen by server.
return 0;
}
auto qkm = select_quic_keying_material(*qkms.get(), dcid);
switch (hd.token.base[0]) { switch (hd.token.base[0]) {
case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY: case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY:
if (verify_retry_token(&odcid, hd.token.base, hd.token.len, &hd.dcid, if (verify_retry_token(odcid, hd.token.base, hd.token.len, hd.dcid,
&remote_addr.su.sa, remote_addr.len, &remote_addr.su.sa, remote_addr.len,
secret.data()) != 0) { qkm->secret.data(), qkm->secret.size()) != 0) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Failed to validate Retry token from remote=" LOG(INFO) << "Failed to validate Retry token from remote="
<< util::to_numeric_addr(&remote_addr); << util::to_numeric_addr(&remote_addr);
@ -163,7 +234,7 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
// 2nd Retry packet is not allowed, so send CONNECTION_CLOSE // 2nd Retry packet is not allowed, so send CONNECTION_CLOSE
// with INVALID_TOKEN. // with INVALID_TOKEN.
send_connection_close(faddr, version, &hd.dcid, &hd.scid, remote_addr, send_connection_close(faddr, version, hd.dcid, hd.scid, remote_addr,
local_addr, NGTCP2_INVALID_TOKEN); local_addr, NGTCP2_INVALID_TOKEN);
return 0; return 0;
} }
@ -180,12 +251,20 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
break; break;
case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR:
if (verify_token(hd.token.base, hd.token.len, &remote_addr.su.sa, if (verify_token(hd.token.base, hd.token.len, &remote_addr.su.sa,
remote_addr.len, secret.data()) != 0) { remote_addr.len, qkm->secret.data(),
qkm->secret.size()) != 0) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Failed to validate token from remote=" LOG(INFO) << "Failed to validate token from remote="
<< util::to_numeric_addr(&remote_addr); << util::to_numeric_addr(&remote_addr);
} }
if (quicconf.upstream.require_token) {
send_retry(faddr, version, dcid, dcidlen, scid, scidlen,
remote_addr, local_addr);
return 0;
}
break; break;
} }
@ -199,12 +278,25 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
break; break;
default: default:
if (quicconf.upstream.require_token) {
send_retry(faddr, version, dcid, dcidlen, scid, scidlen, remote_addr,
local_addr);
return 0;
}
break; break;
} }
break; break;
} }
case NGTCP2_ERR_RETRY: case NGTCP2_ERR_RETRY:
if (worker_->get_graceful_shutdown()) {
send_connection_close(faddr, version, hd.dcid, hd.scid, remote_addr,
local_addr, NGTCP2_CONNECTION_REFUSED);
return 0;
}
send_retry(faddr, version, dcid, dcidlen, scid, scidlen, remote_addr, send_retry(faddr, version, dcid, dcidlen, scid, scidlen, remote_addr,
local_addr); local_addr);
return 0; return 0;
@ -214,11 +306,13 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
return 0; return 0;
default: default:
if (!config->single_thread && !(data[0] & 0x80) && if (!config->single_thread && !(data[0] & 0x80) &&
dcidlen > SHRPX_QUIC_CID_PREFIXLEN && dcidlen == SHRPX_QUIC_SCIDLEN &&
!std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN, !std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker_->get_cid_prefix())) { worker_->get_cid_prefix())) {
if (conn_handler->forward_quic_packet(faddr, remote_addr, local_addr, if (conn_handler->forward_quic_packet(faddr, remote_addr, local_addr,
dcid, data, datalen) == 0) { pi, decrypted_dcid.data(), data,
datalen) == 0) {
return 0; return 0;
} }
} }
@ -240,7 +334,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
handler = (*it).second; handler = (*it).second;
} }
if (handler->read_quic(faddr, remote_addr, local_addr, data, datalen) != 0) { if (handler->read_quic(faddr, remote_addr, local_addr, pi, data, datalen) !=
0) {
delete handler; delete handler;
return 0; return 0;
} }
@ -276,7 +371,9 @@ ClientHandler *QUICConnectionHandler::handle_new_connection(
return nullptr; return nullptr;
} }
#if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
assert(SSL_is_quic(ssl)); assert(SSL_is_quic(ssl));
#endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
SSL_set_accept_state(ssl); SSL_set_accept_state(ssl);
@ -284,7 +381,11 @@ ClientHandler *QUICConnectionHandler::handle_new_connection(
auto &quicconf = config->quic; auto &quicconf = config->quic;
if (quicconf.upstream.early_data) { if (quicconf.upstream.early_data) {
#if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
SSL_set_quic_early_data_enabled(ssl, 1); SSL_set_quic_early_data_enabled(ssl, 1);
#else // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
SSL_set_early_data_enabled(ssl, 1);
#endif // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
} }
// Disable TLS session ticket if we don't have working ticket // Disable TLS session ticket if we don't have working ticket
@ -348,11 +449,18 @@ int QUICConnectionHandler::send_retry(
return -1; return -1;
} }
auto config = get_config();
auto &quicconf = config->quic;
auto conn_handler = worker_->get_connection_handler();
auto &qkms = conn_handler->get_quic_keying_materials();
auto &qkm = qkms->keying_materials.front();
ngtcp2_cid retry_scid; ngtcp2_cid retry_scid;
retry_scid.datalen = SHRPX_QUIC_SCIDLEN; if (generate_quic_retry_connection_id(retry_scid, SHRPX_QUIC_SCIDLEN,
// We do not steer packet based on Retry CID. quicconf.server_id.data(), qkm.id,
if (RAND_bytes(retry_scid.data, retry_scid.datalen) != 1) { qkm.cid_encryption_key.data()) != 0) {
return -1; return -1;
} }
@ -363,16 +471,14 @@ int QUICConnectionHandler::send_retry(
ngtcp2_cid_init(&idcid, ini_dcid, ini_dcidlen); ngtcp2_cid_init(&idcid, ini_dcid, ini_dcidlen);
ngtcp2_cid_init(&iscid, ini_scid, ini_scidlen); ngtcp2_cid_init(&iscid, ini_scid, ini_scidlen);
auto &quic_secret = worker_->get_quic_secret();
auto &secret = quic_secret->token_secret;
if (generate_retry_token(token.data(), tokenlen, &remote_addr.su.sa, if (generate_retry_token(token.data(), tokenlen, &remote_addr.su.sa,
remote_addr.len, &retry_scid, &idcid, remote_addr.len, retry_scid, idcid,
secret.data()) != 0) { qkm.secret.data(), qkm.secret.size()) != 0) {
return -1; return -1;
} }
std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf; std::vector<uint8_t> buf;
buf.resize(256);
auto nwrite = auto nwrite =
ngtcp2_crypto_write_retry(buf.data(), buf.size(), version, &iscid, ngtcp2_crypto_write_retry(buf.data(), buf.size(), version, &iscid,
@ -382,9 +488,33 @@ int QUICConnectionHandler::send_retry(
return -1; return -1;
} }
return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, buf.resize(nwrite);
&local_addr.su.sa, local_addr.len, buf.data(), nwrite,
0); quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
&local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
buf.data(), buf.size(), 0);
if (generate_quic_hashed_connection_id(idcid, remote_addr, local_addr,
idcid) != 0) {
return -1;
}
auto d =
static_cast<ev_tstamp>(NGTCP2_DEFAULT_INITIAL_RTT * 3) / NGTCP2_SECONDS;
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Enter close-wait period " << d << "s with " << buf.size()
<< " bytes sentinel packet";
}
auto cw = std::make_unique<CloseWait>(worker_, std::vector<ngtcp2_cid>{idcid},
std::move(buf), d);
add_close_wait(cw.get());
cw.release();
return 0;
} }
int QUICConnectionHandler::send_version_negotiation( int QUICConnectionHandler::send_version_negotiation(
@ -411,8 +541,8 @@ int QUICConnectionHandler::send_version_negotiation(
} }
return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
&local_addr.su.sa, local_addr.len, buf.data(), nwrite, &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
0); buf.data(), nwrite, 0);
} }
int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr, int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr,
@ -440,11 +570,12 @@ int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr,
ngtcp2_cid_init(&cid, dcid, dcidlen); ngtcp2_cid_init(&cid, dcid, dcidlen);
auto &quic_secret = worker_->get_quic_secret(); auto conn_handler = worker_->get_connection_handler();
auto &secret = quic_secret->stateless_reset_secret; auto &qkms = conn_handler->get_quic_keying_materials();
auto &qkm = qkms->keying_materials.front();
rv = generate_quic_stateless_reset_token(token.data(), &cid, secret.data(), rv = generate_quic_stateless_reset_token(token.data(), cid, qkm.secret.data(),
secret.size()); qkm.secret.size());
if (rv != 0) { if (rv != 0) {
return -1; return -1;
} }
@ -473,18 +604,19 @@ int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr,
} }
return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
&local_addr.su.sa, local_addr.len, buf.data(), nwrite, &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
0); buf.data(), nwrite, 0);
} }
int QUICConnectionHandler::send_connection_close( int QUICConnectionHandler::send_connection_close(
const UpstreamAddr *faddr, uint32_t version, const ngtcp2_cid *ini_dcid, const UpstreamAddr *faddr, uint32_t version, const ngtcp2_cid &ini_dcid,
const ngtcp2_cid *ini_scid, const Address &remote_addr, const ngtcp2_cid &ini_scid, const Address &remote_addr,
const Address &local_addr, uint64_t error_code) { const Address &local_addr, uint64_t error_code) {
std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf; std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
auto nwrite = ngtcp2_crypto_write_connection_close( auto nwrite = ngtcp2_crypto_write_connection_close(
buf.data(), buf.size(), version, ini_scid, ini_dcid, error_code); buf.data(), buf.size(), version, &ini_scid, &ini_dcid, error_code,
nullptr, 0);
if (nwrite < 0) { if (nwrite < 0) {
LOG(ERROR) << "ngtcp2_crypto_write_connection_close failed"; LOG(ERROR) << "ngtcp2_crypto_write_connection_close failed";
return -1; return -1;
@ -494,23 +626,22 @@ int QUICConnectionHandler::send_connection_close(
LOG(INFO) << "Send Initial CONNECTION_CLOSE with error_code=" << log::hex LOG(INFO) << "Send Initial CONNECTION_CLOSE with error_code=" << log::hex
<< error_code << log::dec << error_code << log::dec
<< " to remote=" << util::to_numeric_addr(&remote_addr) << " to remote=" << util::to_numeric_addr(&remote_addr)
<< " dcid=" << util::format_hex(ini_scid->data, ini_scid->datalen) << " dcid=" << util::format_hex(ini_scid.data, ini_scid.datalen)
<< " scid=" << " scid=" << util::format_hex(ini_dcid.data, ini_dcid.datalen);
<< util::format_hex(ini_dcid->data, ini_dcid->datalen);
} }
return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
&local_addr.su.sa, local_addr.len, buf.data(), nwrite, &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
0); buf.data(), nwrite, 0);
} }
void QUICConnectionHandler::add_connection_id(const ngtcp2_cid *cid, void QUICConnectionHandler::add_connection_id(const ngtcp2_cid &cid,
ClientHandler *handler) { ClientHandler *handler) {
connections_.emplace(*cid, handler); connections_.emplace(cid, handler);
} }
void QUICConnectionHandler::remove_connection_id(const ngtcp2_cid *cid) { void QUICConnectionHandler::remove_connection_id(const ngtcp2_cid &cid) {
connections_.erase(*cid); connections_.erase(cid);
} }
void QUICConnectionHandler::add_close_wait(CloseWait *cw) { void QUICConnectionHandler::add_close_wait(CloseWait *cw) {
@ -548,10 +679,10 @@ static void close_wait_timeoutcb(struct ev_loop *loop, ev_timer *w,
} }
CloseWait::CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids, CloseWait::CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids,
std::vector<uint8_t> conn_close, ev_tstamp period) std::vector<uint8_t> pkt, ev_tstamp period)
: worker{worker}, : worker{worker},
scids{std::move(scids)}, scids{std::move(scids)},
conn_close{std::move(conn_close)}, pkt{std::move(pkt)},
bytes_recv{0}, bytes_recv{0},
bytes_sent{0}, bytes_sent{0},
num_pkts_recv{0}, num_pkts_recv{0},
@ -573,35 +704,36 @@ CloseWait::~CloseWait() {
--worker_stat->num_close_waits; --worker_stat->num_close_waits;
if (worker->get_graceful_shutdown() && worker_stat->num_connections == 0 && if (worker->get_graceful_shutdown() && worker_stat->num_connections == 0 &&
worker_stat->num_close_waits) { worker_stat->num_close_waits == 0) {
ev_break(loop); ev_break(loop);
} }
} }
int CloseWait::handle_packet(const UpstreamAddr *faddr, int CloseWait::handle_packet(const UpstreamAddr *faddr,
const Address &remote_addr, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, const Address &local_addr,
const ngtcp2_pkt_info &pi, const uint8_t *data,
size_t datalen) { size_t datalen) {
if (conn_close.empty()) { if (pkt.empty()) {
return 0; return 0;
} }
++num_pkts_recv; ++num_pkts_recv;
bytes_recv += datalen; bytes_recv += datalen;
if (bytes_sent + conn_close.size() > 3 * bytes_recv || if (bytes_sent + pkt.size() > 3 * bytes_recv ||
next_pkts_recv > num_pkts_recv) { next_pkts_recv > num_pkts_recv) {
return 0; return 0;
} }
if (quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, if (quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
&local_addr.su.sa, local_addr.len, conn_close.data(), &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{},
conn_close.size(), 0) != 0) { pkt.data(), pkt.size(), 0) != 0) {
return -1; return -1;
} }
next_pkts_recv *= 2; next_pkts_recv *= 2;
bytes_sent += conn_close.size(); bytes_sent += pkt.size();
return 0; return 0;
} }

View File

@ -51,19 +51,19 @@ class Worker;
// closing period). // closing period).
struct CloseWait { struct CloseWait {
CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids, CloseWait(Worker *worker, std::vector<ngtcp2_cid> scids,
std::vector<uint8_t> conn_close, ev_tstamp period); std::vector<uint8_t> pkt, ev_tstamp period);
~CloseWait(); ~CloseWait();
int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr, int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, const Address &local_addr, const ngtcp2_pkt_info &pi,
size_t datalen); const uint8_t *data, size_t datalen);
Worker *worker; Worker *worker;
// Source Connection IDs of the connection. // Source Connection IDs of the connection.
std::vector<ngtcp2_cid> scids; std::vector<ngtcp2_cid> scids;
// QUIC packet containing CONNECTION_CLOSE. It is empty when a // QUIC packet which is sent in response to the incoming packet. It
// connection entered in draining state. // might be empty.
std::vector<uint8_t> conn_close; std::vector<uint8_t> pkt;
// Close-wait (draining or closing period) timer. // Close-wait (draining or closing period) timer.
ev_timer timer; ev_timer timer;
// The number of bytes received during close-wait period. // The number of bytes received during close-wait period.
@ -82,8 +82,8 @@ public:
QUICConnectionHandler(Worker *worker); QUICConnectionHandler(Worker *worker);
~QUICConnectionHandler(); ~QUICConnectionHandler();
int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr, int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, const Address &local_addr, const ngtcp2_pkt_info &pi,
size_t datalen); const uint8_t *data, size_t datalen);
// Send Retry packet. |ini_dcid| is the destination Connection ID // Send Retry packet. |ini_dcid| is the destination Connection ID
// which appeared in Client Initial packet and its length is // which appeared in Client Initial packet and its length is
// |dcidlen|. |ini_scid| is the source Connection ID which appeared // |dcidlen|. |ini_scid| is the source Connection ID which appeared
@ -110,8 +110,8 @@ public:
// |ini_scid| is the source Connection ID which appeared in Client // |ini_scid| is the source Connection ID which appeared in Client
// Initial packet. // Initial packet.
int send_connection_close(const UpstreamAddr *faddr, uint32_t version, int send_connection_close(const UpstreamAddr *faddr, uint32_t version,
const ngtcp2_cid *ini_dcid, const ngtcp2_cid &ini_dcid,
const ngtcp2_cid *ini_scid, const ngtcp2_cid &ini_scid,
const Address &remote_addr, const Address &remote_addr,
const Address &local_addr, uint64_t error_code); const Address &local_addr, uint64_t error_code);
ClientHandler *handle_new_connection(const UpstreamAddr *faddr, ClientHandler *handle_new_connection(const UpstreamAddr *faddr,
@ -120,8 +120,8 @@ public:
const ngtcp2_pkt_hd &hd, const ngtcp2_pkt_hd &hd,
const ngtcp2_cid *odcid, const ngtcp2_cid *odcid,
const uint8_t *token, size_t tokenlen); const uint8_t *token, size_t tokenlen);
void add_connection_id(const ngtcp2_cid *cid, ClientHandler *handler); void add_connection_id(const ngtcp2_cid &cid, ClientHandler *handler);
void remove_connection_id(const ngtcp2_cid *cid); void remove_connection_id(const ngtcp2_cid &cid);
void add_close_wait(CloseWait *cw); void add_close_wait(CloseWait *cw);
void remove_close_wait(const CloseWait *cw); void remove_close_wait(const CloseWait *cw);

View File

@ -59,7 +59,8 @@ void QUICListener::on_read() {
msg.msg_iov = &msg_iov; msg.msg_iov = &msg_iov;
msg.msg_iovlen = 1; msg.msg_iovlen = 1;
uint8_t msg_ctrl[CMSG_SPACE(sizeof(in6_pktinfo))]; uint8_t
msg_ctrl[CMSG_SPACE(sizeof(uint8_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
msg.msg_control = msg_ctrl; msg.msg_control = msg_ctrl;
auto quic_conn_handler = worker_->get_quic_connection_handler(); auto quic_conn_handler = worker_->get_quic_connection_handler();
@ -83,11 +84,16 @@ void QUICListener::on_read() {
util::set_port(local_addr, faddr_->port); util::set_port(local_addr, faddr_->port);
ngtcp2_pkt_info pi{
.ecn = util::msghdr_get_ecn(&msg, su.storage.ss_family),
};
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "QUIC received packet: local=" LOG(INFO) << "QUIC received packet: local="
<< util::to_numeric_addr(&local_addr) << util::to_numeric_addr(&local_addr)
<< " remote=" << util::to_numeric_addr(&su.sa, msg.msg_namelen) << " remote=" << util::to_numeric_addr(&su.sa, msg.msg_namelen)
<< " " << nread << " bytes"; << " ecn=" << log::hex << pi.ecn << log::dec << " " << nread
<< " bytes";
} }
if (nread == 0) { if (nread == 0) {
@ -98,7 +104,7 @@ void QUICListener::on_read() {
remote_addr.su = su; remote_addr.su = su;
remote_addr.len = msg.msg_namelen; remote_addr.len = msg.msg_namelen;
quic_conn_handler->handle_packet(faddr_, remote_addr, local_addr, quic_conn_handler->handle_packet(faddr_, remote_addr, local_addr, pi,
buf.data(), nread); buf.data(), nread);
} }
} }

View File

@ -61,7 +61,12 @@
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
# include <ngtcp2/ngtcp2.h> # include <ngtcp2/ngtcp2.h>
# include <ngtcp2/ngtcp2_crypto.h> # include <ngtcp2/ngtcp2_crypto.h>
# ifdef HAVE_LIBNGTCP2_CRYPTO_OPENSSL
# include <ngtcp2/ngtcp2_crypto_openssl.h> # include <ngtcp2/ngtcp2_crypto_openssl.h>
# endif // HAVE_LIBNGTCP2_CRYPTO_OPENSSL
# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
# include <ngtcp2/ngtcp2_crypto_boringssl.h>
# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
#include "shrpx_log.h" #include "shrpx_log.h"
@ -196,7 +201,8 @@ int servername_callback(SSL *ssl, int *al, void *arg) {
auto hostname = StringRef{std::begin(buf), end_buf}; auto hostname = StringRef{std::begin(buf), end_buf};
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
auto cert_tree = SSL_is_quic(ssl) ? worker->get_quic_cert_lookup_tree() auto cert_tree = conn->proto == Proto::HTTP3
? worker->get_quic_cert_lookup_tree()
: worker->get_cert_lookup_tree(); : worker->get_cert_lookup_tree();
#else // !ENABLE_HTTP3 #else // !ENABLE_HTTP3
auto cert_tree = worker->get_cert_lookup_tree(); auto cert_tree = worker->get_cert_lookup_tree();
@ -212,7 +218,7 @@ int servername_callback(SSL *ssl, int *al, void *arg) {
auto conn_handler = worker->get_connection_handler(); auto conn_handler = worker->get_connection_handler();
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
const auto &ssl_ctx_list = SSL_is_quic(ssl) const auto &ssl_ctx_list = conn->proto == Proto::HTTP3
? conn_handler->get_quic_indexed_ssl_ctx(idx) ? conn_handler->get_quic_indexed_ssl_ctx(idx)
: conn_handler->get_indexed_ssl_ctx(idx); : conn_handler->get_indexed_ssl_ctx(idx);
#else // !ENABLE_HTTP3 #else // !ENABLE_HTTP3
@ -703,14 +709,19 @@ namespace {
int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out, int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) { unsigned int inlen, void *arg) {
constexpr StringRef alpnlist[] = {
StringRef::from_lit("h3"),
StringRef::from_lit("h3-29"),
};
for (auto &alpn : alpnlist) {
for (auto p = in, end = in + inlen; p < end;) { for (auto p = in, end = in + inlen; p < end;) {
auto proto_id = p + 1; auto proto_id = p + 1;
auto proto_len = *p; auto proto_len = *p;
if (proto_id + proto_len <= end && if (alpn.size() == proto_len &&
util::streq_l("h3", StringRef{proto_id, proto_len})) { memcmp(alpn.byte(), proto_id, alpn.size()) == 0) {
*out = proto_id;
*out = reinterpret_cast<const unsigned char *>(proto_id);
*outlen = proto_len; *outlen = proto_len;
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
@ -718,14 +729,16 @@ int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
p += 1 + proto_len; p += 1 + proto_len;
} }
}
return SSL_TLSEXT_ERR_NOACK; return SSL_TLSEXT_ERR_ALERT_FATAL;
} }
} // namespace } // namespace
# endif // OPENSSL_VERSION_NUMBER >= 0x10002000L # endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L #if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \
!defined(OPENSSL_IS_BORINGSSL)
# ifndef TLSEXT_TYPE_signed_certificate_timestamp # ifndef TLSEXT_TYPE_signed_certificate_timestamp
# define TLSEXT_TYPE_signed_certificate_timestamp 18 # define TLSEXT_TYPE_signed_certificate_timestamp 18
@ -815,7 +828,8 @@ int legacy_sct_parse_cb(SSL *ssl, unsigned int ext_type,
} // namespace } // namespace
# endif // !OPENSSL_1_1_1_API # endif // !OPENSSL_1_1_1_API
#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L #endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L &&
// !defined(OPENSSL_IS_BORINGSSL)
#ifndef OPENSSL_NO_PSK #ifndef OPENSSL_NO_PSK
namespace { namespace {
@ -913,7 +927,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
neverbleed_t *nb neverbleed_t *nb
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
) { ) {
auto ssl_ctx = SSL_CTX_new(SSLv23_server_method()); auto ssl_ctx = SSL_CTX_new(TLS_server_method());
if (!ssl_ctx) { if (!ssl_ctx) {
LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
DIE(); DIE();
@ -925,14 +939,14 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE |
SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_DH_USE |
SSL_OP_CIPHER_SERVER_PREFERENCE SSL_OP_CIPHER_SERVER_PREFERENCE
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
// The reason for disabling built-in anti-replay in OpenSSL is // The reason for disabling built-in anti-replay in OpenSSL is
// that it only works if client gets back to the same server. // that it only works if client gets back to the same server.
// The freshness check described in // The freshness check described in
// https://tools.ietf.org/html/rfc8446#section-8.3 is still // https://tools.ietf.org/html/rfc8446#section-8.3 is still
// performed. // performed.
| SSL_OP_NO_ANTI_REPLAY | SSL_OP_NO_ANTI_REPLAY
#endif // OPENSSL_1_1_1_API #endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
; ;
auto config = mod_config(); auto config = mod_config();
@ -963,13 +977,13 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
DIE(); DIE();
} }
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) {
LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr); << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
DIE(); DIE();
} }
#endif // OPENSSL_1_1_1_API #endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
#ifndef OPENSSL_NO_EC #ifndef OPENSSL_NO_EC
# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L # if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L
@ -1017,8 +1031,11 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
DIE(); DIE();
} }
SSL_CTX_set_tmp_dh(ssl_ctx, dh); if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
EVP_PKEY_free(dh); LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
#else // !OPENSSL_3_0_0_API #else // !OPENSSL_3_0_0_API
auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
if (dh == nullptr) { if (dh == nullptr) {
@ -1131,6 +1148,12 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr); SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr);
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
auto tls_ctx_data = new TLSContextData();
tls_ctx_data->cert_file = cert_file;
tls_ctx_data->sct_data = sct_data;
SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ #if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \
!defined(OPENSSL_IS_BORINGSSL) !defined(OPENSSL_IS_BORINGSSL)
// SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp)
@ -1163,31 +1186,34 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
} }
# endif // !OPENSSL_1_1_1_API # endif // !OPENSSL_1_1_1_API
} }
#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && #elif defined(OPENSSL_IS_BORINGSSL)
// !defined(OPENSSL_IS_BORINGSSL) if (!tls_ctx_data->sct_data.empty() &&
SSL_CTX_set_signed_cert_timestamp_list(
ssl_ctx, tls_ctx_data->sct_data.data(),
tls_ctx_data->sct_data.size()) != 1) {
LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
#endif // defined(OPENSSL_IS_BORINGSSL)
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (SSL_CTX_set_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) { if (SSL_CTX_set_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) {
LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " LOG(FATAL) << "SSL_CTX_set_max_early_data failed: "
<< ERR_error_string(ERR_get_error(), nullptr); << ERR_error_string(ERR_get_error(), nullptr);
DIE(); DIE();
} }
#endif // OPENSSL_1_1_1_API #endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
#ifndef OPENSSL_NO_PSK #ifndef OPENSSL_NO_PSK
SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
#endif // !LIBRESSL_NO_PSK #endif // !LIBRESSL_NO_PSK
auto tls_ctx_data = new TLSContextData();
tls_ctx_data->cert_file = cert_file;
tls_ctx_data->sct_data = sct_data;
SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
return ssl_ctx; return ssl_ctx;
} }
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
# ifdef HAVE_LIBNGTCP2_CRYPTO_OPENSSL
namespace { namespace {
int quic_set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, int quic_set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
const uint8_t *rx_secret, const uint8_t *rx_secret,
@ -1251,6 +1277,84 @@ auto quic_method = SSL_QUIC_METHOD{
quic_send_alert, quic_send_alert,
}; };
} // namespace } // namespace
# endif // HAVE_LIBNGTCP2_CRYPTO_OPENSSL
# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
namespace {
int quic_set_read_secret(SSL *ssl, ssl_encryption_level_t ssl_level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secretlen) {
auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto upstream = static_cast<Http3Upstream *>(handler->get_upstream());
auto level = ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level);
if (upstream->on_rx_secret(level, secret, secretlen) != 0) {
return 0;
}
return 1;
}
} // namespace
namespace {
int quic_set_write_secret(SSL *ssl, ssl_encryption_level_t ssl_level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secretlen) {
auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto upstream = static_cast<Http3Upstream *>(handler->get_upstream());
auto level = ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level);
if (upstream->on_tx_secret(level, secret, secretlen) != 0) {
return 0;
}
return 1;
}
} // namespace
namespace {
int quic_add_handshake_data(SSL *ssl, ssl_encryption_level_t ssl_level,
const uint8_t *data, size_t len) {
auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto upstream = static_cast<Http3Upstream *>(handler->get_upstream());
auto level = ngtcp2_crypto_boringssl_from_ssl_encryption_level(ssl_level);
upstream->add_crypto_data(level, data, len);
return 1;
}
} // namespace
namespace {
int quic_flush_flight(SSL *ssl) { return 1; }
} // namespace
namespace {
int quic_send_alert(SSL *ssl, ssl_encryption_level_t level, uint8_t alert) {
auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
auto handler = static_cast<ClientHandler *>(conn->data);
auto upstream = static_cast<Http3Upstream *>(handler->get_upstream());
if (!upstream) {
return 1;
}
upstream->set_tls_alert(alert);
return 1;
}
} // namespace
namespace {
auto quic_method = SSL_QUIC_METHOD{
quic_set_read_secret, quic_set_write_secret, quic_add_handshake_data,
quic_flush_flight, quic_send_alert,
};
} // namespace
# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL
SSL_CTX *create_quic_ssl_context(const char *private_key_file, SSL_CTX *create_quic_ssl_context(const char *private_key_file,
const char *cert_file, const char *cert_file,
@ -1271,14 +1375,14 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file,
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE |
SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_DH_USE |
SSL_OP_CIPHER_SERVER_PREFERENCE SSL_OP_CIPHER_SERVER_PREFERENCE
# if OPENSSL_1_1_1_API # if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
// The reason for disabling built-in anti-replay in OpenSSL is // The reason for disabling built-in anti-replay in OpenSSL is
// that it only works if client gets back to the same server. // that it only works if client gets back to the same server.
// The freshness check described in // The freshness check described in
// https://tools.ietf.org/html/rfc8446#section-8.3 is still // https://tools.ietf.org/html/rfc8446#section-8.3 is still
// performed. // performed.
| SSL_OP_NO_ANTI_REPLAY | SSL_OP_NO_ANTI_REPLAY
# endif // OPENSSL_1_1_1_API # endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
; ;
auto config = mod_config(); auto config = mod_config();
@ -1301,13 +1405,13 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file,
DIE(); DIE();
} }
# if OPENSSL_1_1_1_API # if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) {
LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr); << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
DIE(); DIE();
} }
# endif // OPENSSL_1_1_1_API # endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
# ifndef OPENSSL_NO_EC # ifndef OPENSSL_NO_EC
# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L # if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L
@ -1355,8 +1459,11 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file,
DIE(); DIE();
} }
SSL_CTX_set_tmp_dh(ssl_ctx, dh); if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) {
EVP_PKEY_free(dh); LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
# else // !OPENSSL_3_0_0_API # else // !OPENSSL_3_0_0_API
auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
if (dh == nullptr) { if (dh == nullptr) {
@ -1460,6 +1567,12 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file,
SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr); SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr);
# endif // OPENSSL_VERSION_NUMBER >= 0x10002000L # endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
auto tls_ctx_data = new TLSContextData();
tls_ctx_data->cert_file = cert_file;
tls_ctx_data->sct_data = sct_data;
SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
# if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ # if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \
!defined(OPENSSL_IS_BORINGSSL) !defined(OPENSSL_IS_BORINGSSL)
// SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp)
@ -1492,10 +1605,18 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file,
} }
# endif // !OPENSSL_1_1_1_API # endif // !OPENSSL_1_1_1_API
} }
# endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && # elif defined(OPENSSL_IS_BORINGSSL)
// !defined(OPENSSL_IS_BORINGSSL) if (!tls_ctx_data->sct_data.empty() &&
SSL_CTX_set_signed_cert_timestamp_list(
ssl_ctx, tls_ctx_data->sct_data.data(),
tls_ctx_data->sct_data.size()) != 1) {
LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
# endif // defined(OPENSSL_IS_BORINGSSL)
# if OPENSSL_1_1_1_API # if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
auto &quicconf = config->quic; auto &quicconf = config->quic;
if (quicconf.upstream.early_data && if (quicconf.upstream.early_data &&
@ -1505,7 +1626,7 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file,
<< ERR_error_string(ERR_get_error(), nullptr); << ERR_error_string(ERR_get_error(), nullptr);
DIE(); DIE();
} }
# endif // OPENSSL_1_1_1_API # endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
# ifndef OPENSSL_NO_PSK # ifndef OPENSSL_NO_PSK
SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
@ -1513,12 +1634,6 @@ SSL_CTX *create_quic_ssl_context(const char *private_key_file,
SSL_CTX_set_quic_method(ssl_ctx, &quic_method); SSL_CTX_set_quic_method(ssl_ctx, &quic_method);
auto tls_ctx_data = new TLSContextData();
tls_ctx_data->cert_file = cert_file;
tls_ctx_data->sct_data = sct_data;
SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
return ssl_ctx; return ssl_ctx;
} }
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
@ -1579,7 +1694,7 @@ SSL_CTX *create_ssl_client_context(
int (*next_proto_select_cb)(SSL *s, unsigned char **out, int (*next_proto_select_cb)(SSL *s, unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg)) { unsigned int inlen, void *arg)) {
auto ssl_ctx = SSL_CTX_new(SSLv23_client_method()); auto ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) { if (!ssl_ctx) {
LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
DIE(); DIE();
@ -1610,14 +1725,14 @@ SSL_CTX *create_ssl_client_context(
DIE(); DIE();
} }
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.client.tls13_ciphers.c_str()) == if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.client.tls13_ciphers.c_str()) ==
0) { 0) {
LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.client.tls13_ciphers LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.client.tls13_ciphers
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr); << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
DIE(); DIE();
} }
#endif // OPENSSL_1_1_1_API #endif // OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
@ -1955,14 +2070,20 @@ int verify_hostname(X509 *cert, const StringRef &hostname,
} // namespace } // namespace
int check_cert(SSL *ssl, const Address *addr, const StringRef &host) { int check_cert(SSL *ssl, const Address *addr, const StringRef &host) {
#if OPENSSL_3_0_0_API
auto cert = SSL_get0_peer_certificate(ssl);
#else // !OPENSSL_3_0_0_API
auto cert = SSL_get_peer_certificate(ssl); auto cert = SSL_get_peer_certificate(ssl);
#endif // !OPENSSL_3_0_0_API
if (!cert) { if (!cert) {
// By the protocol definition, TLS server always sends certificate // By the protocol definition, TLS server always sends certificate
// if it has. If certificate cannot be retrieved, authentication // if it has. If certificate cannot be retrieved, authentication
// without certificate is used, such as PSK. // without certificate is used, such as PSK.
return 0; return 0;
} }
#if !OPENSSL_3_0_0_API
auto cert_deleter = defer(X509_free, cert); auto cert_deleter = defer(X509_free, cert);
#endif // !OPENSSL_3_0_0_API
if (verify_hostname(cert, host, addr) != 0) { if (verify_hostname(cert, host, addr) != 0) {
LOG(ERROR) << "Certificate verification failed: hostname does not match"; LOG(ERROR) << "Certificate verification failed: hostname does not match";
@ -2619,7 +2740,7 @@ namespace {
int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) { int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) {
int rv; int rv;
#if OPENSSL_1_1_1_API #if OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
struct tm tm; struct tm tm;
rv = ASN1_TIME_to_tm(at, &tm); rv = ASN1_TIME_to_tm(at, &tm);
if (rv != 1) { if (rv != 1) {
@ -2627,7 +2748,7 @@ int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) {
} }
t = nghttp2_timegm(&tm); t = nghttp2_timegm(&tm);
#else // !OPENSSL_1_1_1_API #else // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
auto b = BIO_new(BIO_s_mem()); auto b = BIO_new(BIO_s_mem());
if (!b) { if (!b) {
return -1; return -1;
@ -2640,7 +2761,7 @@ int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) {
return -1; return -1;
} }
# if defined(OPENSSL_IS_BORINGSSL) # ifdef OPENSSL_IS_BORINGSSL
char *s; char *s;
# else # else
unsigned char *s; unsigned char *s;
@ -2653,7 +2774,7 @@ int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) {
} }
t = tt; t = tt;
#endif // !OPENSSL_1_1_1_API #endif // !(OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL))
return 0; return 0;
} }

View File

@ -121,7 +121,7 @@ void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void) {
static constexpr char nghttp2_certfile[] = static constexpr char nghttp2_certfile[] =
NGHTTP2_SRC_DIR "/test.nghttp2.org.pem"; NGHTTP2_SRC_DIR "/test.nghttp2.org.pem";
auto nghttp2_ssl_ctx = SSL_CTX_new(SSLv23_server_method()); auto nghttp2_ssl_ctx = SSL_CTX_new(TLS_server_method());
auto nghttp2_ssl_ctx_del = defer(SSL_CTX_free, nghttp2_ssl_ctx); auto nghttp2_ssl_ctx_del = defer(SSL_CTX_free, nghttp2_ssl_ctx);
auto nghttp2_tls_ctx_data = std::make_unique<tls::TLSContextData>(); auto nghttp2_tls_ctx_data = std::make_unique<tls::TLSContextData>();
nghttp2_tls_ctx_data->cert_file = nghttp2_certfile; nghttp2_tls_ctx_data->cert_file = nghttp2_certfile;
@ -132,7 +132,7 @@ void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void) {
static constexpr char examples_certfile[] = static constexpr char examples_certfile[] =
NGHTTP2_SRC_DIR "/test.example.com.pem"; NGHTTP2_SRC_DIR "/test.example.com.pem";
auto examples_ssl_ctx = SSL_CTX_new(SSLv23_server_method()); auto examples_ssl_ctx = SSL_CTX_new(TLS_server_method());
auto examples_ssl_ctx_del = defer(SSL_CTX_free, examples_ssl_ctx); auto examples_ssl_ctx_del = defer(SSL_CTX_free, examples_ssl_ctx);
auto examples_tls_ctx_data = std::make_unique<tls::TLSContextData>(); auto examples_tls_ctx_data = std::make_unique<tls::TLSContextData>();
examples_tls_ctx_data->cert_file = examples_certfile; examples_tls_ctx_data->cert_file = examples_certfile;

View File

@ -554,7 +554,7 @@ void Worker::process_events() {
quic_conn_handler_.handle_packet( quic_conn_handler_.handle_packet(
faddr, wev.quic_pkt->remote_addr, wev.quic_pkt->local_addr, faddr, wev.quic_pkt->remote_addr, wev.quic_pkt->local_addr,
wev.quic_pkt->data.data(), wev.quic_pkt->data.size()); wev.quic_pkt->pi, wev.quic_pkt->data.data(), wev.quic_pkt->data.size());
break; break;
} }
@ -833,6 +833,28 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
close(fd); close(fd);
continue; continue;
} }
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
auto error = errno;
LOG(WARN) << "Failed to set IPV6_RECVTCLASS option to listener socket: "
<< xsi_strerror(error, errbuf.data(), errbuf.size());
close(fd);
continue;
}
# if defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
int mtu_disc = IP_PMTUDISC_DO;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &mtu_disc,
static_cast<socklen_t>(sizeof(mtu_disc))) == -1) {
auto error = errno;
LOG(WARN)
<< "Failed to set IPV6_MTU_DISCOVER option to listener socket: "
<< xsi_strerror(error, errbuf.data(), errbuf.size());
close(fd);
continue;
}
# endif // defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
} else { } else {
if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
static_cast<socklen_t>(sizeof(val))) == -1) { static_cast<socklen_t>(sizeof(val))) == -1) {
@ -842,9 +864,28 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
close(fd); close(fd);
continue; continue;
} }
if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
auto error = errno;
LOG(WARN) << "Failed to set IP_RECVTOS option to listener socket: "
<< xsi_strerror(error, errbuf.data(), errbuf.size());
close(fd);
continue;
} }
// TODO Enable ECN # if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
int mtu_disc = IP_PMTUDISC_DO;
if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu_disc,
static_cast<socklen_t>(sizeof(mtu_disc))) == -1) {
auto error = errno;
LOG(WARN) << "Failed to set IP_MTU_DISCOVER option to listener socket: "
<< xsi_strerror(error, errbuf.data(), errbuf.size());
close(fd);
continue;
}
# endif // defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
}
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) { if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
auto error = errno; auto error = errno;
@ -890,6 +931,8 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
auto &ref = quic_bpf_refs[faddr.index]; auto &ref = quic_bpf_refs[faddr.index];
ref.obj = obj;
auto reuseport_array = auto reuseport_array =
bpf_object__find_map_by_name(obj, "reuseport_array"); bpf_object__find_map_by_name(obj, "reuseport_array");
err = libbpf_get_error(reuseport_array); err = libbpf_get_error(reuseport_array);
@ -923,7 +966,7 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
} }
constexpr uint32_t zero = 0; constexpr uint32_t zero = 0;
uint32_t num_socks = config->num_worker; uint64_t num_socks = config->num_worker;
if (bpf_map_update_elem(bpf_map__fd(sk_info), &zero, &num_socks, if (bpf_map_update_elem(bpf_map__fd(sk_info), &zero, &num_socks,
BPF_ANY) != 0) { BPF_ANY) != 0) {
@ -933,6 +976,29 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
return -1; return -1;
} }
constexpr uint32_t key_high_idx = 1;
constexpr uint32_t key_low_idx = 2;
auto &qkms = conn_handler_->get_quic_keying_materials();
auto &qkm = qkms->keying_materials.front();
if (bpf_map_update_elem(bpf_map__fd(sk_info), &key_high_idx,
qkm.cid_encryption_key.data(), BPF_ANY) != 0) {
LOG(FATAL) << "Failed to update key_high_idx sk_info: "
<< xsi_strerror(errno, errbuf.data(), errbuf.size());
close(fd);
return -1;
}
if (bpf_map_update_elem(bpf_map__fd(sk_info), &key_low_idx,
qkm.cid_encryption_key.data() + 8,
BPF_ANY) != 0) {
LOG(FATAL) << "Failed to update key_low_idx sk_info: "
<< xsi_strerror(errno, errbuf.data(), errbuf.size());
close(fd);
return -1;
}
auto prog_fd = bpf_program__fd(prog); auto prog_fd = bpf_program__fd(prog);
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &prog_fd, if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &prog_fd,
@ -987,14 +1053,6 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
const uint8_t *Worker::get_cid_prefix() const { return cid_prefix_.data(); } const uint8_t *Worker::get_cid_prefix() const { return cid_prefix_.data(); }
void Worker::set_quic_secret(const std::shared_ptr<QUICSecret> &secret) {
quic_secret_ = secret;
}
const std::shared_ptr<QUICSecret> &Worker::get_quic_secret() const {
return quic_secret_;
}
const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) { const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) {
std::array<char, NI_MAXHOST> host; std::array<char, NI_MAXHOST> host;
@ -1019,9 +1077,13 @@ const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) {
break; break;
default: default:
assert(0); assert(0);
abort();
} }
auto hostport = util::make_hostport(StringRef{host.data()}, port); std::array<char, util::max_hostport> hostport_buf;
auto hostport = util::make_http_hostport(std::begin(hostport_buf),
StringRef{host.data()}, port);
const UpstreamAddr *fallback_faddr = nullptr; const UpstreamAddr *fallback_faddr = nullptr;
for (auto &faddr : quic_upstream_addrs_) { for (auto &faddr : quic_upstream_addrs_) {
@ -1033,9 +1095,28 @@ const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) {
continue; continue;
} }
if (faddr.port == 443 || faddr.port == 80) {
switch (faddr.family) { switch (faddr.family) {
case AF_INET: case AF_INET:
if (util::starts_with(faddr.hostport, StringRef::from_lit("0.0.0.0:"))) { if (util::streq(faddr.hostport, StringRef::from_lit("0.0.0.0"))) {
fallback_faddr = &faddr;
}
break;
case AF_INET6:
if (util::streq(faddr.hostport, StringRef::from_lit("[::]"))) {
fallback_faddr = &faddr;
}
break;
default:
assert(0);
}
} else {
switch (faddr.family) {
case AF_INET:
if (util::starts_with(faddr.hostport,
StringRef::from_lit("0.0.0.0:"))) {
fallback_faddr = &faddr; fallback_faddr = &faddr;
} }
@ -1050,6 +1131,7 @@ const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) {
assert(0); assert(0);
} }
} }
}
return fallback_faddr; return fallback_faddr;
} }
@ -1227,8 +1309,10 @@ void downstream_failure(DownstreamAddr *addr, const Address *raddr) {
} }
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
int create_cid_prefix(uint8_t *cid_prefix) { int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id) {
if (RAND_bytes(cid_prefix, SHRPX_QUIC_CID_PREFIXLEN) != 1) { auto p = std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, cid_prefix);
if (RAND_bytes(p, SHRPX_QUIC_CID_PREFIXLEN - SHRPX_QUIC_SERVER_IDLEN) != 1) {
return -1; return -1;
} }

View File

@ -258,15 +258,18 @@ struct WorkerStat {
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
struct QUICPacket { struct QUICPacket {
QUICPacket(size_t upstream_addr_index, const Address &remote_addr, QUICPacket(size_t upstream_addr_index, const Address &remote_addr,
const Address &local_addr, const uint8_t *data, size_t datalen) const Address &local_addr, const ngtcp2_pkt_info &pi,
const uint8_t *data, size_t datalen)
: upstream_addr_index{upstream_addr_index}, : upstream_addr_index{upstream_addr_index},
remote_addr{remote_addr}, remote_addr{remote_addr},
local_addr{local_addr}, local_addr{local_addr},
pi{pi},
data{data, data + datalen} {} data{data, data + datalen} {}
QUICPacket() {} QUICPacket() : upstream_addr_index{}, remote_addr{}, local_addr{}, pi{} {}
size_t upstream_addr_index; size_t upstream_addr_index;
Address remote_addr; Address remote_addr;
Address local_addr; Address local_addr;
ngtcp2_pkt_info pi;
std::vector<uint8_t> data; std::vector<uint8_t> data;
}; };
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
@ -370,10 +373,6 @@ public:
const uint8_t *get_cid_prefix() const; const uint8_t *get_cid_prefix() const;
void set_quic_secret(const std::shared_ptr<QUICSecret> &secret);
const std::shared_ptr<QUICSecret> &get_quic_secret() const;
# ifdef HAVE_LIBBPF # ifdef HAVE_LIBBPF
bool should_attach_bpf() const; bool should_attach_bpf() const;
@ -412,7 +411,6 @@ private:
std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN> cid_prefix_; std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN> cid_prefix_;
std::vector<UpstreamAddr> quic_upstream_addrs_; std::vector<UpstreamAddr> quic_upstream_addrs_;
std::vector<std::unique_ptr<QUICListener>> quic_listeners_; std::vector<std::unique_ptr<QUICListener>> quic_listeners_;
std::shared_ptr<QUICSecret> quic_secret_;
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
std::shared_ptr<DownstreamConfig> downstreamconf_; std::shared_ptr<DownstreamConfig> downstreamconf_;
@ -441,7 +439,7 @@ private:
std::shared_ptr<TicketKeys> ticket_keys_; std::shared_ptr<TicketKeys> ticket_keys_;
std::vector<std::shared_ptr<DownstreamAddrGroup>> downstream_addr_groups_; std::vector<std::shared_ptr<DownstreamAddrGroup>> downstream_addr_groups_;
// Worker level blocker for downstream connection. For example, // Worker level blocker for downstream connection. For example,
// this is used when file decriptor is exhausted. // this is used when file descriptor is exhausted.
std::unique_ptr<ConnectBlocker> connect_blocker_; std::unique_ptr<ConnectBlocker> connect_blocker_;
bool graceful_shutdown_; bool graceful_shutdown_;
@ -467,8 +465,8 @@ void downstream_failure(DownstreamAddr *addr, const Address *raddr);
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
// Creates unpredictable SHRPX_QUIC_CID_PREFIXLEN bytes sequence which // Creates unpredictable SHRPX_QUIC_CID_PREFIXLEN bytes sequence which
// is used as a prefix of QUIC Connection ID. This function returns // is used as a prefix of QUIC Connection ID. This function returns
// -1 on failure. // -1 on failure. |server_id| must be 2 bytes long.
int create_cid_prefix(uint8_t *cid_prefix); int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id);
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
} // namespace shrpx } // namespace shrpx

View File

@ -519,10 +519,51 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
} }
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
if (conn_handler->create_quic_secret() != 0) { auto &quicconf = config->quic;
std::shared_ptr<QUICKeyingMaterials> qkms;
if (!quicconf.upstream.secret_file.empty()) {
qkms = read_quic_secret_file(quicconf.upstream.secret_file);
if (!qkms) {
LOG(WARN) << "Use QUIC keying materials generated internally";
}
}
if (!qkms) {
qkms = std::make_shared<QUICKeyingMaterials>();
qkms->keying_materials.resize(1);
auto &qkm = qkms->keying_materials.front();
if (RAND_bytes(qkm.reserved.data(), qkm.reserved.size()) != 1) {
LOG(ERROR) << "Failed to generate QUIC secret reserved data";
return -1; return -1;
} }
if (RAND_bytes(qkm.secret.data(), qkm.secret.size()) != 1) {
LOG(ERROR) << "Failed to generate QUIC secret";
return -1;
}
if (RAND_bytes(qkm.salt.data(), qkm.salt.size()) != 1) {
LOG(ERROR) << "Failed to generate QUIC salt";
return -1;
}
}
for (auto &qkm : qkms->keying_materials) {
if (generate_quic_connection_id_encryption_key(
qkm.cid_encryption_key.data(), qkm.cid_encryption_key.size(),
qkm.secret.data(), qkm.secret.size(), qkm.salt.data(),
qkm.salt.size()) != 0) {
LOG(ERROR) << "Failed to generate QUIC Connection ID encryption key";
return -1;
}
}
conn_handler->set_quic_keying_materials(std::move(qkms));
conn_handler->set_cid_prefixes(wpconf->cid_prefixes); conn_handler->set_cid_prefixes(wpconf->cid_prefixes);
conn_handler->set_quic_lingering_worker_processes( conn_handler->set_quic_lingering_worker_processes(
wpconf->quic_lingering_worker_processes); wpconf->quic_lingering_worker_processes);
@ -562,6 +603,10 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
#endif // !NOTHREADS #endif // !NOTHREADS
} }
#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
conn_handler->unload_bpf_objects();
#endif // defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF)
drop_privileges( drop_privileges(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
nb.get() nb.get()

View File

@ -26,20 +26,20 @@
# include <openssl/opensslv.h> # include <openssl/opensslv.h>
# if defined(LIBRESSL_VERSION_NUMBER) # ifdef LIBRESSL_VERSION_NUMBER
# define OPENSSL_1_1_API 0 # define OPENSSL_1_1_API 0
# define OPENSSL_1_1_1_API 0 # define OPENSSL_1_1_1_API 0
# define OPENSSL_3_0_0_API 0 # define OPENSSL_3_0_0_API 0
# define LIBRESSL_IN_USE 1 # define LIBRESSL_IN_USE 1
# define LIBRESSL_LEGACY_API (LIBRESSL_VERSION_NUMBER < 0x20700000L) # define LIBRESSL_LEGACY_API (LIBRESSL_VERSION_NUMBER < 0x20700000L)
# define LIBRESSL_2_7_API (LIBRESSL_VERSION_NUMBER >= 0x20700000L) # define LIBRESSL_2_7_API (LIBRESSL_VERSION_NUMBER >= 0x20700000L)
# else // !defined(LIBRESSL_VERSION_NUMBER) # else // !LIBRESSL_VERSION_NUMBER
# define OPENSSL_1_1_API (OPENSSL_VERSION_NUMBER >= 0x1010000fL) # define OPENSSL_1_1_API (OPENSSL_VERSION_NUMBER >= 0x1010000fL)
# define OPENSSL_1_1_1_API (OPENSSL_VERSION_NUMBER >= 0x10101000L) # define OPENSSL_1_1_1_API (OPENSSL_VERSION_NUMBER >= 0x10101000L)
# define OPENSSL_3_0_0_API (OPENSSL_VERSION_NUMBER >= 0x30000000L) # define OPENSSL_3_0_0_API (OPENSSL_VERSION_NUMBER >= 0x30000000L)
# define LIBRESSL_IN_USE 0 # define LIBRESSL_IN_USE 0
# define LIBRESSL_LEGACY_API 0 # define LIBRESSL_LEGACY_API 0
# define LIBRESSL_2_7_API 0 # define LIBRESSL_2_7_API 0
# endif // !defined(LIBRESSL_VERSION_NUMBER) # endif // !LIBRESSL_VERSION_NUMBER
#endif // OPENSSL_COMPAT_H #endif // OPENSSL_COMPAT_H

View File

@ -57,11 +57,15 @@ constexpr char DEFAULT_CIPHER_LIST[] =
"AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"; "AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256";
constexpr char DEFAULT_TLS13_CIPHER_LIST[] = constexpr char DEFAULT_TLS13_CIPHER_LIST[] =
#if OPENSSL_1_1_1_API #if OPENSSL_3_0_0_API
"TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
#elif OPENSSL_1_1_1_API && !defined(OPENSSL_IS_BORINGSSL)
TLS_DEFAULT_CIPHERSUITES TLS_DEFAULT_CIPHERSUITES
#else // !OPENSSL_1_1_1_API #else // !OPENSSL_3_0_0_API && !(OPENSSL_1_1_1_API &&
// !defined(OPENSSL_IS_BORINGSSL))
"" ""
#endif // !OPENSSL_1_1_1_API #endif // !OPENSSL_3_0_0_API && !(OPENSSL_1_1_1_API &&
// !defined(OPENSSL_IS_BORINGSSL))
; ;
constexpr auto NGHTTP2_TLS_MIN_VERSION = TLS1_VERSION; constexpr auto NGHTTP2_TLS_MIN_VERSION = TLS1_VERSION;

View File

@ -1365,81 +1365,14 @@ std::string dtos(double n) {
StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host, StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host,
uint16_t port) { uint16_t port) {
if (port != 80 && port != 443) { auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1);
return make_hostport(balloc, host, port); return make_http_hostport(iov.base, host, port);
}
auto ipv6 = ipv6_numeric_addr(host.c_str());
auto iov = make_byte_ref(balloc, host.size() + (ipv6 ? 2 : 0) + 1);
auto p = iov.base;
if (ipv6) {
*p++ = '[';
}
p = std::copy(std::begin(host), std::end(host), p);
if (ipv6) {
*p++ = ']';
}
*p = '\0';
return StringRef{iov.base, p};
}
std::string make_hostport(const StringRef &host, uint16_t port) {
auto ipv6 = ipv6_numeric_addr(host.c_str());
auto serv = utos(port);
std::string hostport;
hostport.resize(host.size() + (ipv6 ? 2 : 0) + 1 + serv.size());
auto p = &hostport[0];
if (ipv6) {
*p++ = '[';
}
p = std::copy_n(host.c_str(), host.size(), p);
if (ipv6) {
*p++ = ']';
}
*p++ = ':';
std::copy_n(serv.c_str(), serv.size(), p);
return hostport;
} }
StringRef make_hostport(BlockAllocator &balloc, const StringRef &host, StringRef make_hostport(BlockAllocator &balloc, const StringRef &host,
uint16_t port) { uint16_t port) {
auto ipv6 = ipv6_numeric_addr(host.c_str()); auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1);
auto serv = utos(port); return make_hostport(iov.base, host, port);
auto iov =
make_byte_ref(balloc, host.size() + (ipv6 ? 2 : 0) + 1 + serv.size());
auto p = iov.base;
if (ipv6) {
*p++ = '[';
}
p = std::copy(std::begin(host), std::end(host), p);
if (ipv6) {
*p++ = ']';
}
*p++ = ':';
p = std::copy(std::begin(serv), std::end(serv), p);
*p = '\0';
return StringRef{iov.base, p};
} }
namespace { namespace {
@ -1756,7 +1689,7 @@ std::mt19937 make_mt19937() {
} }
int daemonize(int nochdir, int noclose) { int daemonize(int nochdir, int noclose) {
#if defined(__APPLE__) #ifdef __APPLE__
pid_t pid; pid_t pid;
pid = fork(); pid = fork();
if (pid == -1) { if (pid == -1) {
@ -1792,9 +1725,9 @@ int daemonize(int nochdir, int noclose) {
return 0; return 0;
#elif defined(_MSC_VER) #elif defined(_MSC_VER)
return -1; return -1;
#else // !defined(__APPLE__) #else // !__APPLE__ && !_MSC_VER
return daemon(nochdir, noclose); return daemon(nochdir, noclose);
#endif // !defined(__APPLE__) #endif // !__APPLE__
} }
#ifdef ENABLE_HTTP3 #ifdef ENABLE_HTTP3
@ -1833,6 +1766,53 @@ int msghdr_get_local_addr(Address &dest, msghdr *msg, int family) {
} }
#endif #endif
unsigned int msghdr_get_ecn(msghdr *msg, int family) {
switch (family) {
case AF_INET:
for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS &&
cmsg->cmsg_len) {
return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg));
}
}
return 0;
case AF_INET6:
for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS &&
cmsg->cmsg_len) {
return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg));
}
}
return 0;
}
return 0;
}
int fd_set_send_ecn(int fd, int family, unsigned int ecn) {
switch (family) {
case AF_INET:
if (setsockopt(fd, IPPROTO_IP, IP_TOS, &ecn,
static_cast<socklen_t>(sizeof(ecn))) == -1) {
return -1;
}
return 0;
case AF_INET6:
if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &ecn,
static_cast<socklen_t>(sizeof(ecn))) == -1) {
return -1;
}
return 0;
}
return -1;
}
#endif // ENABLE_HTTP3
} // namespace util } // namespace util
} // namespace nghttp2 } // namespace nghttp2

Some files were not shown because too many files have changed in this diff Show More