Compare commits
110 Commits
master
...
nghttpx-ht
Author | SHA1 | Date |
---|---|---|
Tatsuhiro Tsujikawa | b5c5bf3162 | |
Tatsuhiro Tsujikawa | 37e6857307 | |
Tatsuhiro Tsujikawa | 0b7ad5bfa4 | |
Tatsuhiro Tsujikawa | e2edcc50f4 | |
Tatsuhiro Tsujikawa | 739dd025a0 | |
Tatsuhiro Tsujikawa | 93a44d4c4c | |
Tatsuhiro Tsujikawa | 61aae5b927 | |
Tatsuhiro Tsujikawa | 028996aff8 | |
Tatsuhiro Tsujikawa | e820239bba | |
Tatsuhiro Tsujikawa | abe06b4389 | |
Tatsuhiro Tsujikawa | 204b3884d1 | |
Tatsuhiro Tsujikawa | 544882ca0e | |
Tatsuhiro Tsujikawa | 387d3aab09 | |
Tatsuhiro Tsujikawa | df1eaa0245 | |
Tatsuhiro Tsujikawa | 9b69069fa3 | |
Tatsuhiro Tsujikawa | 3e79ea5c52 | |
Tatsuhiro Tsujikawa | c2de00576c | |
Tatsuhiro Tsujikawa | 81089e7eb6 | |
Tatsuhiro Tsujikawa | 414ff91229 | |
Tatsuhiro Tsujikawa | 261ef47a68 | |
Tatsuhiro Tsujikawa | 25f29e7634 | |
Tatsuhiro Tsujikawa | 19cf303828 | |
Tatsuhiro Tsujikawa | 217d2fc13a | |
Tatsuhiro Tsujikawa | 07edcb9ffe | |
Tatsuhiro Tsujikawa | cf9a7df327 | |
Tatsuhiro Tsujikawa | 4cbbff3ca7 | |
Tatsuhiro Tsujikawa | 9f1fc4810b | |
Tatsuhiro Tsujikawa | 2321253545 | |
Tatsuhiro Tsujikawa | dc3ca53a8e | |
Tatsuhiro Tsujikawa | 44869a4924 | |
Tatsuhiro Tsujikawa | 6862916306 | |
Tatsuhiro Tsujikawa | 2917f6d8e8 | |
Hajime Fujita | 7d60389596 | |
Tatsuhiro Tsujikawa | a37117a98b | |
Tatsuhiro Tsujikawa | eb1607890a | |
Tatsuhiro Tsujikawa | fadedc46f8 | |
Tatsuhiro Tsujikawa | 0362bd6e00 | |
Tatsuhiro Tsujikawa | 25d3323c8a | |
Tatsuhiro Tsujikawa | e7f35e879b | |
Tatsuhiro Tsujikawa | 2ca1ba9f72 | |
Tatsuhiro Tsujikawa | 8affef1061 | |
Tatsuhiro Tsujikawa | 7006bf04e3 | |
Tatsuhiro Tsujikawa | 132cd21c12 | |
Tatsuhiro Tsujikawa | 863434fa01 | |
Tatsuhiro Tsujikawa | 4660252b32 | |
Tatsuhiro Tsujikawa | 51510f1710 | |
Tatsuhiro Tsujikawa | 51d4e419cf | |
Tatsuhiro Tsujikawa | 3912e965fb | |
Tatsuhiro Tsujikawa | 4b2f528719 | |
Tatsuhiro Tsujikawa | c5102d3f81 | |
Tatsuhiro Tsujikawa | 4bedc9a074 | |
Tatsuhiro Tsujikawa | 89aa449ddf | |
George Liu | 90846d8af2 | |
Tatsuhiro Tsujikawa | ae736b4054 | |
Tatsuhiro Tsujikawa | 5d9d35a6f5 | |
Tatsuhiro Tsujikawa | 31a4628034 | |
Tatsuhiro Tsujikawa | 7f949152bd | |
Tatsuhiro Tsujikawa | d4a67a6868 | |
Tatsuhiro Tsujikawa | 7d87221a8c | |
Tatsuhiro Tsujikawa | 3162ffedfc | |
Tatsuhiro Tsujikawa | 7d41e4db6b | |
Tatsuhiro Tsujikawa | aebd837790 | |
Tatsuhiro Tsujikawa | c7736c2a85 | |
Tatsuhiro Tsujikawa | 0b98685c41 | |
Tatsuhiro Tsujikawa | 39e6588fd5 | |
Tatsuhiro Tsujikawa | a1e88ad809 | |
Tatsuhiro Tsujikawa | c60ca34719 | |
Tatsuhiro Tsujikawa | a4dc6cf526 | |
Tatsuhiro Tsujikawa | 799f72b078 | |
Tatsuhiro Tsujikawa | 245dbd6511 | |
Tatsuhiro Tsujikawa | 987e700f36 | |
Tatsuhiro Tsujikawa | a26bb9c8d1 | |
Tatsuhiro Tsujikawa | d590b67dc1 | |
Tatsuhiro Tsujikawa | 5ce081ce95 | |
Tatsuhiro Tsujikawa | a995580913 | |
Tatsuhiro Tsujikawa | aa7c580bb1 | |
Tatsuhiro Tsujikawa | 1d05c6c3c5 | |
Tatsuhiro Tsujikawa | 52e4cd80c3 | |
Tatsuhiro Tsujikawa | 212635eeca | |
Tatsuhiro Tsujikawa | 2d80acfdbb | |
Dmitri Tikhonov | f8528c5080 | |
Lucas Pardue | 4733167f91 | |
Tatsuhiro Tsujikawa | 7f7979a8ae | |
Tatsuhiro Tsujikawa | 06cdc97da5 | |
Tatsuhiro Tsujikawa | dbfd59ad38 | |
Tatsuhiro Tsujikawa | dc9384dc7c | |
Tatsuhiro Tsujikawa | 3c15e85783 | |
Tatsuhiro Tsujikawa | 42f47c1920 | |
Tatsuhiro Tsujikawa | 327a7adbaa | |
Tatsuhiro Tsujikawa | 9e089521e7 | |
Tatsuhiro Tsujikawa | b912b626cd | |
Tatsuhiro Tsujikawa | 8b32ad735f | |
Tatsuhiro Tsujikawa | 8d3932d94a | |
Tatsuhiro Tsujikawa | 1a63c02c0e | |
Tatsuhiro Tsujikawa | e45b10ca20 | |
Tatsuhiro Tsujikawa | 330fe12494 | |
Tatsuhiro Tsujikawa | 06272f8365 | |
Tatsuhiro Tsujikawa | db5ad83776 | |
Tatsuhiro Tsujikawa | b558eeb861 | |
Tatsuhiro Tsujikawa | f4276ce2dc | |
Tatsuhiro Tsujikawa | 4fd9fa238a | |
Tatsuhiro Tsujikawa | 9031469735 | |
Tatsuhiro Tsujikawa | 19fb74b03f | |
Tatsuhiro Tsujikawa | 5b788f5218 | |
Tatsuhiro Tsujikawa | d64488d909 | |
Tatsuhiro Tsujikawa | daad34ab95 | |
Tatsuhiro Tsujikawa | 1bd57360c7 | |
Tatsuhiro Tsujikawa | e9d5c5a489 | |
Tatsuhiro Tsujikawa | 78974cb60b | |
Tatsuhiro Tsujikawa | c24c7ffa06 |
29
README.rst
29
README.rst
|
@ -16,6 +16,35 @@ An experimental high level C++ library is also available.
|
||||||
We have Python bindings of this library, but we do not have full
|
We have Python bindings of this library, but we do not have full
|
||||||
code coverage yet.
|
code coverage yet.
|
||||||
|
|
||||||
|
Running h2load against HTTP/3 server
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
In order to build h2load with HTTP/3 support, you have to build
|
||||||
|
ngtcp2, nghttp3 and my patched OpenSSL.
|
||||||
|
https://github.com/ngtcp2/ngtcp2#build-from-git describes how to build
|
||||||
|
these three software.
|
||||||
|
|
||||||
|
To run h2load against HTTP/3 server, specify h3 ALPN with
|
||||||
|
``--npn-list`` option like so:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ h2load --npn-list h3 https://127.0.0.1:4433
|
||||||
|
|
||||||
|
You can use Dockerfile to skip the tedious build steps to manually
|
||||||
|
pull and build dependencies. In order to build Docker image, do this:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ cd docker
|
||||||
|
$ docker build -t nghttp2-quic .
|
||||||
|
|
||||||
|
Run h2load:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ docker run --rm -it --network=host nghttp2-quic /usr/local/bin/h2load --npn-list h3 https://127.0.0.1:4433
|
||||||
|
|
||||||
Development Status
|
Development Status
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
26
configure.ac
26
configure.ac
|
@ -454,6 +454,29 @@ if test "x${request_libcares}" = "xyes" &&
|
||||||
AC_MSG_ERROR([libcares was requested (--with-libcares) but not found])
|
AC_MSG_ERROR([libcares was requested (--with-libcares) but not found])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ngtcp2 (for src)
|
||||||
|
PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 0.0.0], [have_libngtcp2=yes],
|
||||||
|
[have_libngtcp2=no])
|
||||||
|
if test "x${have_libngtcp2}" = "xno"; then
|
||||||
|
AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ngtcp2_crypto_openssl (for src)
|
||||||
|
PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_OPENSSL],
|
||||||
|
[libngtcp2_crypto_openssl >= 0.0.0],
|
||||||
|
[have_libngtcp2_crypto_openssl=yes],
|
||||||
|
[have_libngtcp2_crypto_openssl=no])
|
||||||
|
if test "x${have_libngtcp2_crypto_openssl}" = "xno"; then
|
||||||
|
AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_OPENSSL_PKG_ERRORS)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# nghttp3 (for src)
|
||||||
|
PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.0.0], [have_libnghttp3=yes],
|
||||||
|
[have_libnghttp3=no])
|
||||||
|
if test "x${have_libnghttp3}" = "xno"; then
|
||||||
|
AC_MSG_NOTICE($LIBNGHTTP3_PKT_ERRORS)
|
||||||
|
fi
|
||||||
|
|
||||||
# libevent_openssl (for examples)
|
# libevent_openssl (for examples)
|
||||||
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
||||||
have_libevent_openssl=no
|
have_libevent_openssl=no
|
||||||
|
@ -1011,6 +1034,9 @@ AC_MSG_NOTICE([summary of build options:
|
||||||
Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}')
|
Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}')
|
||||||
Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}')
|
Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}')
|
||||||
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_crypto_openssl: ${have_libngtcp2_crypto_openssl} (CFLAGS='${LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_OPENSSL_LIBS}')
|
||||||
|
libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_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}')
|
||||||
Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')
|
Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')
|
||||||
Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}')
|
Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}')
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
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_1g-quic-draft-33 https://github.com/tatsuhiro-t/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 && \
|
||||||
|
make -j$(nproc) && make install-strip && cd .. && rm -rf ngtcp2 && \
|
||||||
|
git clone --depth 1 -b quic 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 \
|
||||||
|
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"]
|
|
@ -46,6 +46,9 @@ AM_CPPFLAGS = \
|
||||||
@LIBEV_CFLAGS@ \
|
@LIBEV_CFLAGS@ \
|
||||||
@OPENSSL_CFLAGS@ \
|
@OPENSSL_CFLAGS@ \
|
||||||
@LIBCARES_CFLAGS@ \
|
@LIBCARES_CFLAGS@ \
|
||||||
|
@LIBNGHTTP3_CFLAGS@ \
|
||||||
|
@LIBNGTCP2_CRYPTO_OPENSSL_CFLAGS@ \
|
||||||
|
@LIBNGTCP2_CFLAGS@ \
|
||||||
@JANSSON_CFLAGS@ \
|
@JANSSON_CFLAGS@ \
|
||||||
@ZLIB_CFLAGS@ \
|
@ZLIB_CFLAGS@ \
|
||||||
@DEFS@
|
@DEFS@
|
||||||
|
@ -59,6 +62,9 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \
|
||||||
@LIBEV_LIBS@ \
|
@LIBEV_LIBS@ \
|
||||||
@OPENSSL_LIBS@ \
|
@OPENSSL_LIBS@ \
|
||||||
@LIBCARES_LIBS@ \
|
@LIBCARES_LIBS@ \
|
||||||
|
@LIBNGHTTP3_LIBS@ \
|
||||||
|
@LIBNGTCP2_CRYPTO_OPENSSL_LIBS@ \
|
||||||
|
@LIBNGTCP2_LIBS@ \
|
||||||
@SYSTEMD_LIBS@ \
|
@SYSTEMD_LIBS@ \
|
||||||
@JANSSON_LIBS@ \
|
@JANSSON_LIBS@ \
|
||||||
@ZLIB_LIBS@ \
|
@ZLIB_LIBS@ \
|
||||||
|
@ -97,7 +103,10 @@ h2load_SOURCES = util.cc util.h \
|
||||||
tls.cc tls.h \
|
tls.cc tls.h \
|
||||||
h2load_session.h \
|
h2load_session.h \
|
||||||
h2load_http2_session.cc h2load_http2_session.h \
|
h2load_http2_session.cc h2load_http2_session.h \
|
||||||
h2load_http1_session.cc h2load_http1_session.h
|
h2load_http1_session.cc h2load_http1_session.h \
|
||||||
|
h2load_http3_session.cc h2load_http3_session.h \
|
||||||
|
h2load_quic.cc h2load_quic.h \
|
||||||
|
quic.cc quic.h
|
||||||
|
|
||||||
NGHTTPX_SRCS = \
|
NGHTTPX_SRCS = \
|
||||||
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
|
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
|
||||||
|
@ -143,6 +152,12 @@ NGHTTPX_SRCS = \
|
||||||
shrpx_dns_resolver.cc shrpx_dns_resolver.h \
|
shrpx_dns_resolver.cc shrpx_dns_resolver.h \
|
||||||
shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \
|
shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \
|
||||||
shrpx_dns_tracker.cc shrpx_dns_tracker.h \
|
shrpx_dns_tracker.cc shrpx_dns_tracker.h \
|
||||||
|
shrpx_quic.cc shrpx_quic.h \
|
||||||
|
shrpx_quic_listener.cc shrpx_quic_listener.h \
|
||||||
|
shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \
|
||||||
|
shrpx_http3_upstream.cc shrpx_http3_upstream.h \
|
||||||
|
http3.cc http3.h \
|
||||||
|
quic.cc quic.h \
|
||||||
buffer.h memchunk.h template.h allocator.h \
|
buffer.h memchunk.h template.h allocator.h \
|
||||||
xsi_strerror.c xsi_strerror.h
|
xsi_strerror.c xsi_strerror.h
|
||||||
|
|
||||||
|
|
319
src/h2load.cc
319
src/h2load.cc
|
@ -34,6 +34,8 @@
|
||||||
#ifdef HAVE_FCNTL_H
|
#ifdef HAVE_FCNTL_H
|
||||||
# include <fcntl.h>
|
# include <fcntl.h>
|
||||||
#endif // HAVE_FCNTL_H
|
#endif // HAVE_FCNTL_H
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <netinet/udp.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
@ -48,10 +50,14 @@
|
||||||
|
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
|
||||||
#include "url-parser/url_parser.h"
|
#include "url-parser/url_parser.h"
|
||||||
|
|
||||||
#include "h2load_http1_session.h"
|
#include "h2load_http1_session.h"
|
||||||
#include "h2load_http2_session.h"
|
#include "h2load_http2_session.h"
|
||||||
|
#include "h2load_http3_session.h"
|
||||||
|
#include "h2load_quic.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
@ -71,9 +77,22 @@ bool recorded(const std::chrono::steady_clock::time_point &t) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::ofstream keylog_file;
|
||||||
|
void keylog_callback(const SSL *ssl, const char *line) {
|
||||||
|
keylog_file.write(line, strlen(line));
|
||||||
|
keylog_file.put('\n');
|
||||||
|
keylog_file.flush();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Config::Config()
|
Config::Config()
|
||||||
: ciphers(tls::DEFAULT_CIPHER_LIST),
|
: ciphers(tls::DEFAULT_CIPHER_LIST),
|
||||||
|
tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_"
|
||||||
|
"CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"),
|
||||||
|
groups("X25519:P-256:P-384:P-521"),
|
||||||
data_length(-1),
|
data_length(-1),
|
||||||
|
data(nullptr),
|
||||||
addrs(nullptr),
|
addrs(nullptr),
|
||||||
nreqs(1),
|
nreqs(1),
|
||||||
nclients(1),
|
nclients(1),
|
||||||
|
@ -92,6 +111,7 @@ Config::Config()
|
||||||
encoder_header_table_size(4_k),
|
encoder_header_table_size(4_k),
|
||||||
data_fd(-1),
|
data_fd(-1),
|
||||||
log_fd(-1),
|
log_fd(-1),
|
||||||
|
qlog_file_base(),
|
||||||
port(0),
|
port(0),
|
||||||
default_port(0),
|
default_port(0),
|
||||||
connect_to_port(0),
|
connect_to_port(0),
|
||||||
|
@ -99,7 +119,8 @@ Config::Config()
|
||||||
timing_script(false),
|
timing_script(false),
|
||||||
base_uri_unix(false),
|
base_uri_unix(false),
|
||||||
unix_addr{},
|
unix_addr{},
|
||||||
rps(0.) {}
|
rps(0.),
|
||||||
|
no_udp_gso(false) {}
|
||||||
|
|
||||||
Config::~Config() {
|
Config::~Config() {
|
||||||
if (addrs) {
|
if (addrs) {
|
||||||
|
@ -119,6 +140,10 @@ bool Config::is_rate_mode() const { return (this->rate != 0); }
|
||||||
bool Config::is_timing_based_mode() const { return (this->duration > 0); }
|
bool Config::is_timing_based_mode() const { return (this->duration > 0); }
|
||||||
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
||||||
bool Config::rps_enabled() const { return this->rps > 0.0; }
|
bool Config::rps_enabled() const { return this->rps > 0.0; }
|
||||||
|
bool Config::is_quic() const {
|
||||||
|
return !npn_list.empty() &&
|
||||||
|
(npn_list[0] == NGHTTP3_ALPN_H3 || npn_list[0] == "\x5h3-29");
|
||||||
|
}
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -138,7 +163,9 @@ Stats::Stats(size_t req_todo, size_t nclients)
|
||||||
bytes_head(0),
|
bytes_head(0),
|
||||||
bytes_head_decomp(0),
|
bytes_head_decomp(0),
|
||||||
bytes_body(0),
|
bytes_body(0),
|
||||||
status() {}
|
status(),
|
||||||
|
udp_dgram_recv(0),
|
||||||
|
udp_dgram_sent(0) {}
|
||||||
|
|
||||||
Stream::Stream() : req_stat{}, status_success(-1) {}
|
Stream::Stream() : req_stat{}, status_success(-1) {}
|
||||||
|
|
||||||
|
@ -195,8 +222,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
delete client;
|
delete client;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writecb(loop, &client->wev, revents);
|
client->signal_write();
|
||||||
// client->disconnect() and client->fail() may be called
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -409,6 +435,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
||||||
cstat{},
|
cstat{},
|
||||||
worker(worker),
|
worker(worker),
|
||||||
ssl(nullptr),
|
ssl(nullptr),
|
||||||
|
quic{},
|
||||||
next_addr(config.addrs),
|
next_addr(config.addrs),
|
||||||
current_addr(nullptr),
|
current_addr(nullptr),
|
||||||
reqidx(0),
|
reqidx(0),
|
||||||
|
@ -420,6 +447,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
||||||
req_done(0),
|
req_done(0),
|
||||||
id(id),
|
id(id),
|
||||||
fd(-1),
|
fd(-1),
|
||||||
|
local_addr{},
|
||||||
new_connection_requested(false),
|
new_connection_requested(false),
|
||||||
final(false),
|
final(false),
|
||||||
rps_duration_started(0),
|
rps_duration_started(0),
|
||||||
|
@ -449,11 +477,18 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
||||||
|
|
||||||
ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
|
ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
|
||||||
rps_watcher.data = this;
|
rps_watcher.data = this;
|
||||||
|
|
||||||
|
ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.);
|
||||||
|
quic.pkt_timer.data = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Client::~Client() {
|
Client::~Client() {
|
||||||
disconnect();
|
disconnect();
|
||||||
|
|
||||||
|
if (config.is_quic()) {
|
||||||
|
quic_free();
|
||||||
|
}
|
||||||
|
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
SSL_free(ssl);
|
SSL_free(ssl);
|
||||||
}
|
}
|
||||||
|
@ -466,26 +501,57 @@ int Client::do_read() { return readfn(*this); }
|
||||||
int Client::do_write() { return writefn(*this); }
|
int Client::do_write() { return writefn(*this); }
|
||||||
|
|
||||||
int Client::make_socket(addrinfo *addr) {
|
int Client::make_socket(addrinfo *addr) {
|
||||||
fd = util::create_nonblock_socket(addr->ai_family);
|
int rv;
|
||||||
if (fd == -1) {
|
|
||||||
return -1;
|
if (config.is_quic()) {
|
||||||
}
|
fd = util::create_nonblock_udp_socket(addr->ai_family);
|
||||||
if (config.scheme == "https") {
|
if (fd == -1) {
|
||||||
if (!ssl) {
|
return -1;
|
||||||
ssl = SSL_new(worker->ssl_ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto config = worker->config;
|
rv = util::bind_any_addr_udp(fd, addr->ai_family);
|
||||||
|
if (rv != 0) {
|
||||||
if (!util::numeric_host(config->host.c_str())) {
|
close(fd);
|
||||||
SSL_set_tlsext_host_name(ssl, config->host.c_str());
|
fd = -1;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SSL_set_fd(ssl, fd);
|
socklen_t addrlen = sizeof(local_addr.su.storage);
|
||||||
SSL_set_connect_state(ssl);
|
rv = getsockname(fd, &local_addr.su.sa, &addrlen);
|
||||||
|
if (rv == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
local_addr.len = addrlen;
|
||||||
|
|
||||||
|
if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr,
|
||||||
|
addr->ai_addrlen) != 0) {
|
||||||
|
std::cerr << "quic_init failed" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fd = util::create_nonblock_socket(addr->ai_family);
|
||||||
|
if (fd == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (config.scheme == "https") {
|
||||||
|
if (!ssl) {
|
||||||
|
ssl = SSL_new(worker->ssl_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_set_fd(ssl, fd);
|
||||||
|
SSL_set_connect_state(ssl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
|
if (ssl && !util::numeric_host(config.host.c_str())) {
|
||||||
|
SSL_set_tlsext_host_name(ssl, config.host.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.is_quic()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
|
||||||
if (rv != 0 && errno != EINPROGRESS) {
|
if (rv != 0 && errno != EINPROGRESS) {
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
SSL_free(ssl);
|
SSL_free(ssl);
|
||||||
|
@ -542,13 +608,20 @@ int Client::connect() {
|
||||||
current_addr = addr;
|
current_addr = addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
writefn = &Client::connected;
|
|
||||||
|
|
||||||
ev_io_set(&rev, fd, EV_READ);
|
ev_io_set(&rev, fd, EV_READ);
|
||||||
ev_io_set(&wev, fd, EV_WRITE);
|
ev_io_set(&wev, fd, EV_WRITE);
|
||||||
|
|
||||||
ev_io_start(worker->loop, &wev);
|
ev_io_start(worker->loop, &wev);
|
||||||
|
|
||||||
|
if (config.is_quic()) {
|
||||||
|
ev_io_start(worker->loop, &rev);
|
||||||
|
|
||||||
|
readfn = &Client::read_quic;
|
||||||
|
writefn = &Client::write_quic;
|
||||||
|
} else {
|
||||||
|
writefn = &Client::connected;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,6 +676,11 @@ void Client::fail() {
|
||||||
void Client::disconnect() {
|
void Client::disconnect() {
|
||||||
record_client_end_time();
|
record_client_end_time();
|
||||||
|
|
||||||
|
if (config.is_quic()) {
|
||||||
|
quic_close_connection();
|
||||||
|
}
|
||||||
|
|
||||||
|
ev_timer_stop(worker->loop, &quic.pkt_timer);
|
||||||
ev_timer_stop(worker->loop, &conn_inactivity_watcher);
|
ev_timer_stop(worker->loop, &conn_inactivity_watcher);
|
||||||
ev_timer_stop(worker->loop, &conn_active_watcher);
|
ev_timer_stop(worker->loop, &conn_active_watcher);
|
||||||
ev_timer_stop(worker->loop, &rps_watcher);
|
ev_timer_stop(worker->loop, &rps_watcher);
|
||||||
|
@ -765,7 +843,12 @@ void Client::report_app_info() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::terminate_session() {
|
void Client::terminate_session() {
|
||||||
session->terminate();
|
if (config.is_quic()) {
|
||||||
|
quic.close_requested = true;
|
||||||
|
}
|
||||||
|
if (session) {
|
||||||
|
session->terminate();
|
||||||
|
}
|
||||||
// http1 session needs writecb to tear down session.
|
// http1 session needs writecb to tear down session.
|
||||||
signal_write();
|
signal_write();
|
||||||
}
|
}
|
||||||
|
@ -963,7 +1046,13 @@ int Client::connection_made() {
|
||||||
|
|
||||||
if (next_proto) {
|
if (next_proto) {
|
||||||
auto proto = StringRef{next_proto, next_proto_len};
|
auto proto = StringRef{next_proto, next_proto_len};
|
||||||
if (util::check_h2_is_selected(proto)) {
|
if (config.is_quic()) {
|
||||||
|
assert(session);
|
||||||
|
if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) &&
|
||||||
|
!util::streq_l("h3-29", proto)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (util::check_h2_is_selected(proto)) {
|
||||||
session = std::make_unique<Http2Session>(this);
|
session = std::make_unique<Http2Session>(this);
|
||||||
} else if (util::streq(NGHTTP2_H1_1, proto)) {
|
} else if (util::streq(NGHTTP2_H1_1, proto)) {
|
||||||
session = std::make_unique<Http1Session>(this);
|
session = std::make_unique<Http1Session>(this);
|
||||||
|
@ -972,6 +1061,9 @@ int Client::connection_made() {
|
||||||
// Just assign next_proto to selected_proto anyway to show the
|
// Just assign next_proto to selected_proto anyway to show the
|
||||||
// negotiation result.
|
// negotiation result.
|
||||||
selected_proto = proto.str();
|
selected_proto = proto.str();
|
||||||
|
} else if (config.is_quic()) {
|
||||||
|
std::cerr << "QUIC requires ALPN negotiation" << std::endl;
|
||||||
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
std::cout << "No protocol negotiated. Fallback behaviour may be activated"
|
std::cout << "No protocol negotiated. Fallback behaviour may be activated"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
@ -1285,6 +1377,44 @@ int Client::write_tls() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Client::write_udp(const sockaddr *addr, socklen_t addrlen,
|
||||||
|
const uint8_t *data, size_t datalen, size_t gso_size) {
|
||||||
|
iovec msg_iov;
|
||||||
|
msg_iov.iov_base = const_cast<uint8_t *>(data);
|
||||||
|
msg_iov.iov_len = datalen;
|
||||||
|
|
||||||
|
msghdr msg{};
|
||||||
|
msg.msg_name = const_cast<sockaddr *>(addr);
|
||||||
|
msg.msg_namelen = addrlen;
|
||||||
|
msg.msg_iov = &msg_iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
|
||||||
|
#ifdef UDP_SEGMENT
|
||||||
|
std::array<uint8_t, CMSG_SPACE(sizeof(uint16_t))> msg_ctrl{};
|
||||||
|
if (gso_size && datalen > gso_size) {
|
||||||
|
msg.msg_control = msg_ctrl.data();
|
||||||
|
msg.msg_controllen = msg_ctrl.size();
|
||||||
|
|
||||||
|
auto cm = CMSG_FIRSTHDR(&msg);
|
||||||
|
cm->cmsg_level = SOL_UDP;
|
||||||
|
cm->cmsg_type = UDP_SEGMENT;
|
||||||
|
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
|
||||||
|
*(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size;
|
||||||
|
}
|
||||||
|
#endif // UDP_SEGMENT
|
||||||
|
|
||||||
|
auto nwrite = sendmsg(fd, &msg, 0);
|
||||||
|
if (nwrite < 0) {
|
||||||
|
std::cerr << "sendto: errno=" << errno << std::endl;
|
||||||
|
} else {
|
||||||
|
++worker->stats.udp_dgram_sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Client::record_request_time(RequestStat *req_stat) {
|
void Client::record_request_time(RequestStat *req_stat) {
|
||||||
req_stat->request_time = std::chrono::steady_clock::now();
|
req_stat->request_time = std::chrono::steady_clock::now();
|
||||||
req_stat->request_wall_time = std::chrono::system_clock::now();
|
req_stat->request_wall_time = std::chrono::system_clock::now();
|
||||||
|
@ -1403,7 +1533,7 @@ Worker::~Worker() {
|
||||||
|
|
||||||
void Worker::stop_all_clients() {
|
void Worker::stop_all_clients() {
|
||||||
for (auto client : clients) {
|
for (auto client : clients) {
|
||||||
if (client && client->session) {
|
if (client) {
|
||||||
client->terminate_session();
|
client->terminate_session();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1938,6 +2068,7 @@ Options:
|
||||||
Default: 1
|
Default: 1
|
||||||
-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).
|
||||||
Default: )"
|
Default: )"
|
||||||
<< config.window_bits << R"(
|
<< config.window_bits << R"(
|
||||||
-W, --connection-window-bits=<N>
|
-W, --connection-window-bits=<N>
|
||||||
|
@ -1948,10 +2079,15 @@ 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. The format of the string is
|
Set allowed cipher list for TLSv1.2 or ealier. The
|
||||||
described in OpenSSL ciphers(1).
|
format of the string is described in OpenSSL ciphers(1).
|
||||||
Default: )"
|
Default: )"
|
||||||
<< config.ciphers << R"(
|
<< config.ciphers << R"(
|
||||||
|
--tls13-ciphers=<SUITE>
|
||||||
|
Set allowed cipher list for TLSv1.3. The format of the
|
||||||
|
string is described in OpenSSL ciphers(1).
|
||||||
|
Default: )"
|
||||||
|
<< config.tls13_ciphers << R"(
|
||||||
-p, --no-tls-proto=<PROTOID>
|
-p, --no-tls-proto=<PROTOID>
|
||||||
Specify ALPN identifier of the protocol to be used when
|
Specify ALPN identifier of the protocol to be used when
|
||||||
accessing http URI without SSL/TLS.
|
accessing http URI without SSL/TLS.
|
||||||
|
@ -2065,11 +2201,24 @@ Options:
|
||||||
response time when using one worker thread, but may
|
response time when using one worker thread, but may
|
||||||
appear slightly out of order with multiple threads due
|
appear slightly out of order with multiple threads due
|
||||||
to buffering. Status code is -1 for failed streams.
|
to buffering. Status code is -1 for failed streams.
|
||||||
|
--qlog-file-base=<PATH>
|
||||||
|
Enable qlog output and specify base file name for qlogs.
|
||||||
|
Qlog is emitted for each connection.
|
||||||
|
For a given base name "base", each output file name
|
||||||
|
becomes "base.M.N.qlog" where M is worker ID and N is
|
||||||
|
client ID (e.g. "base.0.3.qlog").
|
||||||
|
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>.
|
||||||
--rps=<N> Specify request per second for each client. --rps and
|
--rps=<N> Specify request per second for each client. --rps and
|
||||||
--timing-script-file are mutually exclusive.
|
--timing-script-file are mutually exclusive.
|
||||||
|
--groups=<GROUPS>
|
||||||
|
Specify the supported groups.
|
||||||
|
Default: )"
|
||||||
|
<< config.groups << R"(
|
||||||
|
--no-udp-gso
|
||||||
|
Disable UDP GSO.
|
||||||
-v, --verbose
|
-v, --verbose
|
||||||
Output debug information.
|
Output debug information.
|
||||||
--version Display version information and exit.
|
--version Display version information and exit.
|
||||||
|
@ -2097,6 +2246,7 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
std::string datafile;
|
std::string datafile;
|
||||||
std::string logfile;
|
std::string logfile;
|
||||||
|
std::string qlog_base;
|
||||||
bool nreqs_set_manually = false;
|
bool nreqs_set_manually = false;
|
||||||
while (1) {
|
while (1) {
|
||||||
static int flag = 0;
|
static int flag = 0;
|
||||||
|
@ -2130,6 +2280,10 @@ int main(int argc, char **argv) {
|
||||||
{"log-file", required_argument, &flag, 10},
|
{"log-file", required_argument, &flag, 10},
|
||||||
{"connect-to", required_argument, &flag, 11},
|
{"connect-to", required_argument, &flag, 11},
|
||||||
{"rps", required_argument, &flag, 12},
|
{"rps", required_argument, &flag, 12},
|
||||||
|
{"groups", required_argument, &flag, 13},
|
||||||
|
{"tls13-ciphers", required_argument, &flag, 14},
|
||||||
|
{"no-udp-gso", no_argument, &flag, 15},
|
||||||
|
{"qlog-file-base", required_argument, &flag, 16},
|
||||||
{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,
|
||||||
|
@ -2380,6 +2534,22 @@ int main(int argc, char **argv) {
|
||||||
config.rps = v;
|
config.rps = v;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 13:
|
||||||
|
// --groups
|
||||||
|
config.groups = optarg;
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
// --tls13-ciphers
|
||||||
|
config.tls13_ciphers = optarg;
|
||||||
|
break;
|
||||||
|
case 15:
|
||||||
|
// --no-udp-gso
|
||||||
|
config.no_udp_gso = true;
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
// --qlog-file-base
|
||||||
|
qlog_base = optarg;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -2546,6 +2716,13 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
config.data_length = data_stat.st_size;
|
config.data_length = data_stat.st_size;
|
||||||
|
auto addr = mmap(nullptr, config.data_length, PROT_READ, MAP_SHARED,
|
||||||
|
config.data_fd, 0);
|
||||||
|
if (addr == MAP_FAILED) {
|
||||||
|
std::cerr << "-d: Could not mmap file " << datafile << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
config.data = static_cast<uint8_t *>(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!logfile.empty()) {
|
if (!logfile.empty()) {
|
||||||
|
@ -2557,6 +2734,16 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!qlog_base.empty()) {
|
||||||
|
if (!config.is_quic()) {
|
||||||
|
std::cerr
|
||||||
|
<< "Warning: --qlog-file-base: only effective in quic, ignoring."
|
||||||
|
<< std::endl;
|
||||||
|
} else {
|
||||||
|
config.qlog_file_base = qlog_base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct sigaction act {};
|
struct sigaction act {};
|
||||||
act.sa_handler = SIG_IGN;
|
act.sa_handler = SIG_IGN;
|
||||||
sigaction(SIGPIPE, &act, nullptr);
|
sigaction(SIGPIPE, &act, nullptr);
|
||||||
|
@ -2576,9 +2763,12 @@ int main(int argc, char **argv) {
|
||||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
||||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||||
|
|
||||||
if (nghttp2::tls::ssl_ctx_set_proto_versions(
|
if (config.is_quic()) {
|
||||||
ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
|
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||||
nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
|
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||||
|
} else if (nghttp2::tls::ssl_ctx_set_proto_versions(
|
||||||
|
ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
|
||||||
|
nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
|
||||||
std::cerr << "Could not set TLS versions" << std::endl;
|
std::cerr << "Could not set TLS versions" << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
@ -2590,6 +2780,18 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) {
|
||||||
|
std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers
|
||||||
|
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr)
|
||||||
|
<< std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) {
|
||||||
|
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
#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,
|
||||||
nullptr);
|
nullptr);
|
||||||
|
@ -2604,6 +2806,14 @@ int main(int argc, char **argv) {
|
||||||
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
||||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
|
||||||
|
auto keylog_filename = getenv("SSLKEYLOGFILE");
|
||||||
|
if (keylog_filename) {
|
||||||
|
keylog_file.open(keylog_filename, std::ios_base::app);
|
||||||
|
if (keylog_file) {
|
||||||
|
SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
|
std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
|
||||||
Headers shared_nva;
|
Headers shared_nva;
|
||||||
shared_nva.emplace_back(":scheme", config.scheme);
|
shared_nva.emplace_back(":scheme", config.scheme);
|
||||||
|
@ -2822,6 +3032,8 @@ int main(int argc, char **argv) {
|
||||||
stats.bytes_head += s.bytes_head;
|
stats.bytes_head += s.bytes_head;
|
||||||
stats.bytes_head_decomp += s.bytes_head_decomp;
|
stats.bytes_head_decomp += s.bytes_head_decomp;
|
||||||
stats.bytes_body += s.bytes_body;
|
stats.bytes_body += s.bytes_body;
|
||||||
|
stats.udp_dgram_recv += s.udp_dgram_recv;
|
||||||
|
stats.udp_dgram_sent += s.udp_dgram_sent;
|
||||||
|
|
||||||
for (size_t i = 0; i < stats.status.size(); ++i) {
|
for (size_t i = 0; i < stats.status.size(); ++i) {
|
||||||
stats.status[i] += s.status[i];
|
stats.status[i] += s.status[i];
|
||||||
|
@ -2880,30 +3092,35 @@ traffic: )" << util::utos_funit(stats.bytes_total)
|
||||||
<< util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head
|
<< util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head
|
||||||
<< ") headers (space savings " << header_space_savings * 100
|
<< ") headers (space savings " << header_space_savings * 100
|
||||||
<< "%), " << util::utos_funit(stats.bytes_body) << "B ("
|
<< "%), " << util::utos_funit(stats.bytes_body) << "B ("
|
||||||
<< stats.bytes_body << R"() data
|
<< stats.bytes_body << R"() data)" << std::endl;
|
||||||
min max mean sd +/- sd
|
if (config.is_quic()) {
|
||||||
|
std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, "
|
||||||
|
<< stats.udp_dgram_recv << " received" << std::endl;
|
||||||
|
}
|
||||||
|
std::cout
|
||||||
|
<< R"( min max mean sd +/- sd
|
||||||
time for request: )"
|
time for request: )"
|
||||||
<< std::setw(10) << util::format_duration(ts.request.min) << " "
|
<< std::setw(10) << util::format_duration(ts.request.min) << " "
|
||||||
<< std::setw(10) << util::format_duration(ts.request.max) << " "
|
<< std::setw(10) << util::format_duration(ts.request.max) << " "
|
||||||
<< std::setw(10) << util::format_duration(ts.request.mean) << " "
|
<< std::setw(10) << util::format_duration(ts.request.mean) << " "
|
||||||
<< std::setw(10) << util::format_duration(ts.request.sd)
|
<< std::setw(10) << util::format_duration(ts.request.sd) << std::setw(9)
|
||||||
<< std::setw(9) << util::dtos(ts.request.within_sd) << "%"
|
<< util::dtos(ts.request.within_sd) << "%"
|
||||||
<< "\ntime for connect: " << std::setw(10)
|
<< "\ntime for connect: " << std::setw(10)
|
||||||
<< util::format_duration(ts.connect.min) << " " << std::setw(10)
|
<< util::format_duration(ts.connect.min) << " " << std::setw(10)
|
||||||
<< util::format_duration(ts.connect.max) << " " << std::setw(10)
|
<< util::format_duration(ts.connect.max) << " " << std::setw(10)
|
||||||
<< util::format_duration(ts.connect.mean) << " " << std::setw(10)
|
<< util::format_duration(ts.connect.mean) << " " << std::setw(10)
|
||||||
<< util::format_duration(ts.connect.sd) << std::setw(9)
|
<< util::format_duration(ts.connect.sd) << std::setw(9)
|
||||||
<< util::dtos(ts.connect.within_sd) << "%"
|
<< util::dtos(ts.connect.within_sd) << "%"
|
||||||
<< "\ntime to 1st byte: " << std::setw(10)
|
<< "\ntime to 1st byte: " << std::setw(10)
|
||||||
<< util::format_duration(ts.ttfb.min) << " " << std::setw(10)
|
<< util::format_duration(ts.ttfb.min) << " " << std::setw(10)
|
||||||
<< util::format_duration(ts.ttfb.max) << " " << std::setw(10)
|
<< util::format_duration(ts.ttfb.max) << " " << std::setw(10)
|
||||||
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
|
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
|
||||||
<< util::format_duration(ts.ttfb.sd) << std::setw(9)
|
<< util::format_duration(ts.ttfb.sd) << std::setw(9)
|
||||||
<< util::dtos(ts.ttfb.within_sd) << "%"
|
<< util::dtos(ts.ttfb.within_sd) << "%"
|
||||||
<< "\nreq/s : " << std::setw(10) << ts.rps.min << " "
|
<< "\nreq/s : " << std::setw(10) << ts.rps.min << " "
|
||||||
<< std::setw(10) << ts.rps.max << " " << std::setw(10)
|
<< std::setw(10) << ts.rps.max << " " << std::setw(10) << ts.rps.mean
|
||||||
<< ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
|
<< " " << std::setw(10) << ts.rps.sd << std::setw(9)
|
||||||
<< util::dtos(ts.rps.within_sd) << "%" << std::endl;
|
<< util::dtos(ts.rps.within_sd) << "%" << std::endl;
|
||||||
|
|
||||||
SSL_CTX_free(ssl_ctx);
|
SSL_CTX_free(ssl_ctx);
|
||||||
|
|
||||||
|
|
56
src/h2load.h
56
src/h2load.h
|
@ -45,11 +45,15 @@
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
#include <ngtcp2/ngtcp2_crypto.h>
|
||||||
|
|
||||||
#include <ev.h>
|
#include <ev.h>
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
|
#include "quic.h"
|
||||||
#include "memchunk.h"
|
#include "memchunk.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
|
||||||
|
@ -72,8 +76,13 @@ struct Config {
|
||||||
std::string connect_to_host;
|
std::string connect_to_host;
|
||||||
std::string ifile;
|
std::string ifile;
|
||||||
std::string ciphers;
|
std::string ciphers;
|
||||||
|
std::string tls13_ciphers;
|
||||||
|
// supported groups (or curves).
|
||||||
|
std::string groups;
|
||||||
// length of upload data
|
// length of upload data
|
||||||
int64_t data_length;
|
int64_t data_length;
|
||||||
|
// memory mapped upload data
|
||||||
|
uint8_t *data;
|
||||||
addrinfo *addrs;
|
addrinfo *addrs;
|
||||||
size_t nreqs;
|
size_t nreqs;
|
||||||
size_t nclients;
|
size_t nclients;
|
||||||
|
@ -100,6 +109,8 @@ struct Config {
|
||||||
int data_fd;
|
int data_fd;
|
||||||
// file descriptor to write per-request stats to.
|
// file descriptor to write per-request stats to.
|
||||||
int log_fd;
|
int log_fd;
|
||||||
|
// base file name of qlog output files
|
||||||
|
std::string qlog_file_base;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
uint16_t default_port;
|
uint16_t default_port;
|
||||||
uint16_t connect_to_port;
|
uint16_t connect_to_port;
|
||||||
|
@ -116,6 +127,8 @@ struct Config {
|
||||||
std::vector<std::string> npn_list;
|
std::vector<std::string> npn_list;
|
||||||
// The number of request per second for each client.
|
// The number of request per second for each client.
|
||||||
double rps;
|
double rps;
|
||||||
|
// Disables GSO for UDP connections.
|
||||||
|
bool no_udp_gso;
|
||||||
|
|
||||||
Config();
|
Config();
|
||||||
~Config();
|
~Config();
|
||||||
|
@ -124,6 +137,7 @@ struct Config {
|
||||||
bool is_timing_based_mode() const;
|
bool is_timing_based_mode() const;
|
||||||
bool has_base_uri() const;
|
bool has_base_uri() const;
|
||||||
bool rps_enabled() const;
|
bool rps_enabled() const;
|
||||||
|
bool is_quic() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RequestStat {
|
struct RequestStat {
|
||||||
|
@ -220,6 +234,10 @@ struct Stats {
|
||||||
std::vector<RequestStat> req_stats;
|
std::vector<RequestStat> req_stats;
|
||||||
// The statistics per client
|
// The statistics per client
|
||||||
std::vector<ClientStat> client_stats;
|
std::vector<ClientStat> client_stats;
|
||||||
|
// The number of UDP datagrams received.
|
||||||
|
size_t udp_dgram_recv;
|
||||||
|
// The number of UDP datagrams sent.
|
||||||
|
size_t udp_dgram_sent;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
|
enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
|
||||||
|
@ -309,6 +327,14 @@ struct Client {
|
||||||
std::function<int(Client &)> readfn, writefn;
|
std::function<int(Client &)> readfn, writefn;
|
||||||
Worker *worker;
|
Worker *worker;
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
|
struct {
|
||||||
|
ev_timer pkt_timer;
|
||||||
|
ngtcp2_conn *conn;
|
||||||
|
quic::Error last_error;
|
||||||
|
size_t max_pktlen;
|
||||||
|
bool close_requested;
|
||||||
|
FILE *qlog_file;
|
||||||
|
} quic;
|
||||||
ev_timer request_timeout_watcher;
|
ev_timer request_timeout_watcher;
|
||||||
addrinfo *next_addr;
|
addrinfo *next_addr;
|
||||||
// Address for the current address. When try_new_connection() is
|
// Address for the current address. When try_new_connection() is
|
||||||
|
@ -332,6 +358,7 @@ struct Client {
|
||||||
// The client id per worker
|
// The client id per worker
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
int fd;
|
int fd;
|
||||||
|
Address local_addr;
|
||||||
ev_timer conn_active_watcher;
|
ev_timer conn_active_watcher;
|
||||||
ev_timer conn_inactivity_watcher;
|
ev_timer conn_inactivity_watcher;
|
||||||
std::string selected_proto;
|
std::string selected_proto;
|
||||||
|
@ -419,6 +446,35 @@ struct Client {
|
||||||
void record_client_end_time();
|
void record_client_end_time();
|
||||||
|
|
||||||
void signal_write();
|
void signal_write();
|
||||||
|
|
||||||
|
// QUIC
|
||||||
|
int quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
|
||||||
|
const sockaddr *remote_addr, socklen_t remote_addrlen);
|
||||||
|
void quic_free();
|
||||||
|
int read_quic();
|
||||||
|
int write_quic();
|
||||||
|
int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data,
|
||||||
|
size_t datalen, size_t gso_size);
|
||||||
|
void quic_close_connection();
|
||||||
|
|
||||||
|
int quic_handshake_completed();
|
||||||
|
int quic_recv_stream_data(uint32_t flags, int64_t stream_id,
|
||||||
|
const uint8_t *data, size_t datalen);
|
||||||
|
int quic_acked_stream_data_offset(int64_t stream_id, size_t datalen);
|
||||||
|
int quic_stream_close(int64_t stream_id, uint64_t app_error_code);
|
||||||
|
int quic_stream_reset(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_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret,
|
||||||
|
const uint8_t *tx_secret, size_t secretlen);
|
||||||
|
void quic_set_tls_alert(uint8_t alert);
|
||||||
|
|
||||||
|
void quic_write_client_handshake(ngtcp2_crypto_level level,
|
||||||
|
const uint8_t *data, size_t datalen);
|
||||||
|
int quic_pkt_timeout();
|
||||||
|
void quic_restart_pkt_timer();
|
||||||
|
void quic_write_qlog(const void *data, size_t datalen);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace h2load
|
} // namespace h2load
|
||||||
|
|
|
@ -0,0 +1,427 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 nghttp2 contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "h2load_http3_session.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
|
||||||
|
#include "h2load.h"
|
||||||
|
|
||||||
|
namespace h2load {
|
||||||
|
|
||||||
|
Http3Session::Http3Session(Client *client)
|
||||||
|
: client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {}
|
||||||
|
|
||||||
|
Http3Session::~Http3Session() { nghttp3_conn_del(conn_); }
|
||||||
|
|
||||||
|
void Http3Session::on_connect() {}
|
||||||
|
|
||||||
|
int Http3Session::submit_request() {
|
||||||
|
if (npending_request_) {
|
||||||
|
++npending_request_;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto config = client_->worker->config;
|
||||||
|
reqidx_ = client_->reqidx;
|
||||||
|
|
||||||
|
if (++client_->reqidx == config->nva.size()) {
|
||||||
|
client_->reqidx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stream_id = submit_request_internal();
|
||||||
|
if (stream_id < 0) {
|
||||||
|
if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
|
||||||
|
++npending_request_;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
|
||||||
|
size_t veccnt, uint32_t *pflags, void *user_data,
|
||||||
|
void *stream_user_data) {
|
||||||
|
auto s = static_cast<Http3Session *>(user_data);
|
||||||
|
|
||||||
|
s->read_data(vec, veccnt, pflags);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Http3Session::read_data(nghttp3_vec *vec, size_t veccnt,
|
||||||
|
uint32_t *pflags) {
|
||||||
|
assert(veccnt > 0);
|
||||||
|
|
||||||
|
auto config = client_->worker->config;
|
||||||
|
|
||||||
|
vec[0].base = config->data;
|
||||||
|
vec[0].len = config->data_length;
|
||||||
|
*pflags |= NGHTTP3_DATA_FLAG_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Http3Session::submit_request_internal() {
|
||||||
|
int rv;
|
||||||
|
int64_t stream_id;
|
||||||
|
|
||||||
|
auto config = client_->worker->config;
|
||||||
|
auto &nva = config->nva[reqidx_];
|
||||||
|
|
||||||
|
rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr);
|
||||||
|
if (rv != 0) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
nghttp3_data_reader dr{};
|
||||||
|
dr.read_data = h2load::read_data;
|
||||||
|
|
||||||
|
rv = nghttp3_conn_submit_request(
|
||||||
|
conn_, stream_id, reinterpret_cast<nghttp3_nv *>(nva.data()), nva.size(),
|
||||||
|
config->data_fd == -1 ? nullptr : &dr, nullptr);
|
||||||
|
if (rv != 0) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_->on_request(stream_id);
|
||||||
|
auto req_stat = client_->get_req_stat(stream_id);
|
||||||
|
assert(req_stat);
|
||||||
|
client_->record_request_time(req_stat);
|
||||||
|
|
||||||
|
return stream_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; }
|
||||||
|
|
||||||
|
int Http3Session::on_write() { return -1; }
|
||||||
|
|
||||||
|
void Http3Session::terminate() {}
|
||||||
|
|
||||||
|
size_t Http3Session::max_concurrent_streams() {
|
||||||
|
return (size_t)client_->worker->config->max_concurrent_streams;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
|
||||||
|
void *user_data, void *stream_user_data) {
|
||||||
|
auto s = static_cast<Http3Session *>(user_data);
|
||||||
|
if (s->stream_close(stream_id, app_error_code) != 0) {
|
||||||
|
return NGHTTP3_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) {
|
||||||
|
if (!ngtcp2_is_bidi_stream(stream_id)) {
|
||||||
|
assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
|
||||||
|
ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
|
||||||
|
}
|
||||||
|
client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
|
||||||
|
size_t datalen, void *user_data, void *stream_user_data) {
|
||||||
|
auto s = static_cast<Http3Session *>(user_data);
|
||||||
|
s->recv_data(stream_id, data, datalen);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Http3Session::recv_data(int64_t stream_id, const uint8_t *data,
|
||||||
|
size_t datalen) {
|
||||||
|
client_->record_ttfb();
|
||||||
|
client_->worker->stats.bytes_body += datalen;
|
||||||
|
consume(stream_id, datalen);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed,
|
||||||
|
void *user_data, void *stream_user_data) {
|
||||||
|
auto s = static_cast<Http3Session *>(user_data);
|
||||||
|
s->consume(stream_id, nconsumed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Http3Session::consume(int64_t stream_id, size_t nconsumed) {
|
||||||
|
ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id,
|
||||||
|
nconsumed);
|
||||||
|
ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
|
||||||
|
void *stream_user_data) {
|
||||||
|
auto s = static_cast<Http3Session *>(user_data);
|
||||||
|
s->begin_headers(stream_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Http3Session::begin_headers(int64_t stream_id) {
|
||||||
|
auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id);
|
||||||
|
assert(payloadlen > 0);
|
||||||
|
|
||||||
|
client_->worker->stats.bytes_head += payloadlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int recv_header(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 s = static_cast<Http3Session *>(user_data);
|
||||||
|
auto k = nghttp3_rcbuf_get_buf(name);
|
||||||
|
auto v = nghttp3_rcbuf_get_buf(value);
|
||||||
|
s->recv_header(stream_id, &k, &v);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name,
|
||||||
|
const nghttp3_vec *value) {
|
||||||
|
client_->on_header(stream_id, name->base, name->len, value->base, value->len);
|
||||||
|
client_->worker->stats.bytes_head_decomp += name->len + value->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int send_stop_sending(nghttp3_conn *conn, int64_t stream_id,
|
||||||
|
uint64_t app_error_code, void *user_data,
|
||||||
|
void *stream_user_data) {
|
||||||
|
auto s = static_cast<Http3Session *>(user_data);
|
||||||
|
if (s->send_stop_sending(stream_id, app_error_code) != 0) {
|
||||||
|
return NGHTTP3_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Http3Session::send_stop_sending(int64_t stream_id,
|
||||||
|
uint64_t app_error_code) {
|
||||||
|
auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, stream_id,
|
||||||
|
app_error_code);
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) {
|
||||||
|
auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code);
|
||||||
|
switch (rv) {
|
||||||
|
case 0:
|
||||||
|
return 0;
|
||||||
|
case NGHTTP3_ERR_STREAM_NOT_FOUND:
|
||||||
|
if (!ngtcp2_is_bidi_stream(stream_id)) {
|
||||||
|
assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
|
||||||
|
ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::shutdown_stream_read(int64_t stream_id) {
|
||||||
|
auto rv = nghttp3_conn_shutdown_stream_read(conn_, stream_id);
|
||||||
|
if (rv != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::extend_max_local_streams() {
|
||||||
|
auto config = client_->worker->config;
|
||||||
|
|
||||||
|
for (; npending_request_; --npending_request_) {
|
||||||
|
auto stream_id = submit_request_internal();
|
||||||
|
if (stream_id < 0) {
|
||||||
|
if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++reqidx_ == config->nva.size()) {
|
||||||
|
reqidx_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::init_conn() {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
assert(conn_ == nullptr);
|
||||||
|
|
||||||
|
if (ngtcp2_conn_get_max_local_streams_uni(client_->quic.conn) < 3) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nghttp3_callbacks callbacks{
|
||||||
|
nullptr, // acked_stream_data
|
||||||
|
h2load::stream_close,
|
||||||
|
h2load::recv_data,
|
||||||
|
h2load::deferred_consume,
|
||||||
|
h2load::begin_headers,
|
||||||
|
h2load::recv_header,
|
||||||
|
nullptr, // end_headers
|
||||||
|
nullptr, // begin_trailers
|
||||||
|
h2load::recv_header,
|
||||||
|
nullptr, // end_trailers
|
||||||
|
h2load::send_stop_sending,
|
||||||
|
};
|
||||||
|
|
||||||
|
auto config = client_->worker->config;
|
||||||
|
|
||||||
|
nghttp3_settings settings;
|
||||||
|
nghttp3_settings_default(&settings);
|
||||||
|
settings.qpack_max_table_capacity = config->header_table_size;
|
||||||
|
settings.qpack_blocked_streams = 100;
|
||||||
|
|
||||||
|
auto mem = nghttp3_mem_default();
|
||||||
|
|
||||||
|
rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this);
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv)
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t ctrl_stream_id;
|
||||||
|
|
||||||
|
rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, NULL);
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id);
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t qpack_enc_stream_id, qpack_dec_stream_id;
|
||||||
|
|
||||||
|
rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id,
|
||||||
|
NULL);
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id,
|
||||||
|
NULL);
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id,
|
||||||
|
qpack_dec_stream_id);
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
|
||||||
|
<< std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id,
|
||||||
|
const uint8_t *data, size_t datalen) {
|
||||||
|
auto nconsumed = nghttp3_conn_read_stream(
|
||||||
|
conn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
|
||||||
|
if (nconsumed < 0) {
|
||||||
|
std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
|
||||||
|
<< std::endl;
|
||||||
|
client_->quic.last_error = quic::err_application(nconsumed);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return nconsumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin,
|
||||||
|
nghttp3_vec *vec, size_t veccnt) {
|
||||||
|
auto sveccnt =
|
||||||
|
nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt);
|
||||||
|
if (sveccnt < 0) {
|
||||||
|
client_->quic.last_error = quic::err_application(sveccnt);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return sveccnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::block_stream(int64_t stream_id) {
|
||||||
|
auto rv = nghttp3_conn_block_stream(conn_, stream_id);
|
||||||
|
if (rv != 0) {
|
||||||
|
client_->quic.last_error = quic::err_application(rv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::shutdown_stream_write(int64_t stream_id) {
|
||||||
|
auto rv = nghttp3_conn_shutdown_stream_write(conn_, stream_id);
|
||||||
|
if (rv != 0) {
|
||||||
|
client_->quic.last_error = quic::err_application(rv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) {
|
||||||
|
auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen);
|
||||||
|
if (rv != 0) {
|
||||||
|
client_->quic.last_error = quic::err_application(rv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) {
|
||||||
|
auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen);
|
||||||
|
if (rv != 0) {
|
||||||
|
client_->quic.last_error = quic::err_application(rv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace h2load
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 nghttp2 contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef H2LOAD_HTTP3_SESSION_H
|
||||||
|
#define H2LOAD_HTTP3_SESSION_H
|
||||||
|
|
||||||
|
#include "h2load_session.h"
|
||||||
|
|
||||||
|
#include <nghttp3/nghttp3.h>
|
||||||
|
|
||||||
|
namespace h2load {
|
||||||
|
|
||||||
|
struct Client;
|
||||||
|
|
||||||
|
class Http3Session : public Session {
|
||||||
|
public:
|
||||||
|
Http3Session(Client *client);
|
||||||
|
virtual ~Http3Session();
|
||||||
|
virtual void on_connect();
|
||||||
|
virtual int submit_request();
|
||||||
|
virtual int on_read(const uint8_t *data, size_t len);
|
||||||
|
virtual int on_write();
|
||||||
|
virtual void terminate();
|
||||||
|
virtual size_t max_concurrent_streams();
|
||||||
|
|
||||||
|
int init_conn();
|
||||||
|
int stream_close(int64_t stream_id, uint64_t app_error_code);
|
||||||
|
void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen);
|
||||||
|
void consume(int64_t stream_id, size_t nconsumed);
|
||||||
|
void begin_headers(int64_t stream_id);
|
||||||
|
void recv_header(int64_t stream_id, const nghttp3_vec *name,
|
||||||
|
const nghttp3_vec *value);
|
||||||
|
int send_stop_sending(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 extend_max_local_streams();
|
||||||
|
int64_t submit_request_internal();
|
||||||
|
|
||||||
|
ssize_t read_stream(uint32_t flags, int64_t stream_id, const uint8_t *data,
|
||||||
|
size_t datalen);
|
||||||
|
ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec,
|
||||||
|
size_t veccnt);
|
||||||
|
int block_stream(int64_t stream_id);
|
||||||
|
int shutdown_stream_write(int64_t stream_id);
|
||||||
|
int add_write_offset(int64_t stream_id, size_t ndatalen);
|
||||||
|
int add_ack_offset(int64_t stream_id, size_t datalen);
|
||||||
|
|
||||||
|
void read_data(nghttp3_vec *vec, size_t veccnt, uint32_t *pflags);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Client *client_;
|
||||||
|
nghttp3_conn *conn_;
|
||||||
|
size_t npending_request_;
|
||||||
|
size_t reqidx_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace h2load
|
||||||
|
|
||||||
|
#endif // H2LOAD_HTTP3_SESSION_H
|
|
@ -0,0 +1,679 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 nghttp2 contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "h2load_quic.h"
|
||||||
|
|
||||||
|
#include <netinet/udp.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2_crypto_openssl.h>
|
||||||
|
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
#include "h2load_http3_session.h"
|
||||||
|
|
||||||
|
namespace h2load {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
auto randgen = util::make_mt19937();
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int handshake_completed(ngtcp2_conn *conn, void *user_data) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
|
||||||
|
if (c->quic_handshake_completed() != 0) {
|
||||||
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Client::quic_handshake_completed() { return connection_made(); }
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
|
||||||
|
uint64_t offset, const uint8_t *data, size_t datalen,
|
||||||
|
void *user_data, void *stream_user_data) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
if (c->quic_recv_stream_data(flags, stream_id, data, datalen) != 0) {
|
||||||
|
// TODO Better to do this gracefully rather than
|
||||||
|
// NGTCP2_ERR_CALLBACK_FAILURE. Perhaps, call
|
||||||
|
// ngtcp2_conn_write_application_close() ?
|
||||||
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Client::quic_recv_stream_data(uint32_t flags, int64_t stream_id,
|
||||||
|
const uint8_t *data, size_t datalen) {
|
||||||
|
if (worker->current_phase == Phase::MAIN_DURATION) {
|
||||||
|
worker->stats.bytes_total += datalen;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto s = static_cast<Http3Session *>(session.get());
|
||||||
|
auto nconsumed = s->read_stream(flags, stream_id, data, datalen);
|
||||||
|
if (nconsumed == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngtcp2_conn_extend_max_stream_offset(quic.conn, stream_id, nconsumed);
|
||||||
|
ngtcp2_conn_extend_max_offset(quic.conn, nconsumed);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
|
||||||
|
uint64_t offset, uint64_t datalen, void *user_data,
|
||||||
|
void *stream_user_data) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
if (c->quic_acked_stream_data_offset(stream_id, datalen) != 0) {
|
||||||
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Client::quic_acked_stream_data_offset(int64_t stream_id, size_t datalen) {
|
||||||
|
auto s = static_cast<Http3Session *>(session.get());
|
||||||
|
if (s->add_ack_offset(stream_id, datalen) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int stream_close(ngtcp2_conn *conn, int64_t stream_id, uint64_t app_error_code,
|
||||||
|
void *user_data, void *stream_user_data) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
if (c->quic_stream_close(stream_id, app_error_code) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Client::quic_stream_close(int64_t stream_id, uint64_t app_error_code) {
|
||||||
|
auto s = static_cast<Http3Session *>(session.get());
|
||||||
|
if (s->close_stream(stream_id, app_error_code == 0 ? NGHTTP3_H3_NO_ERROR
|
||||||
|
: app_error_code) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
|
||||||
|
uint64_t app_error_code, void *user_data,
|
||||||
|
void *stream_user_data) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
if (c->quic_stream_reset(stream_id, app_error_code) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Client::quic_stream_reset(int64_t stream_id, uint64_t app_error_code) {
|
||||||
|
auto s = static_cast<Http3Session *>(session.get());
|
||||||
|
if (s->shutdown_stream_read(stream_id) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
|
||||||
|
uint64_t app_error_code, void *user_data,
|
||||||
|
void *stream_user_data) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
if (c->quic_stream_stop_sending(stream_id, app_error_code) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Client::quic_stream_stop_sending(int64_t stream_id,
|
||||||
|
uint64_t app_error_code) {
|
||||||
|
auto s = static_cast<Http3Session *>(session.get());
|
||||||
|
if (s->shutdown_stream_read(stream_id) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int extend_max_local_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
|
||||||
|
void *user_data) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
|
||||||
|
if (c->quic_extend_max_local_streams() != 0) {
|
||||||
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int Client::quic_extend_max_local_streams() {
|
||||||
|
auto s = static_cast<Http3Session *>(session.get());
|
||||||
|
if (s->extend_max_local_streams() != 0) {
|
||||||
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
|
||||||
|
size_t cidlen, void *user_data) {
|
||||||
|
auto dis = std::uniform_int_distribution<uint8_t>(
|
||||||
|
0, std::numeric_limits<uint8_t>::max());
|
||||||
|
auto f = [&dis]() { return dis(randgen); };
|
||||||
|
|
||||||
|
std::generate_n(cid->data, cidlen, f);
|
||||||
|
cid->datalen = cidlen;
|
||||||
|
std::generate_n(token, NGTCP2_STATELESS_RESET_TOKENLEN, f);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void debug_log_printf(void *user_data, const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vfprintf(stderr, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void generate_cid(ngtcp2_cid &dest) {
|
||||||
|
auto dis = std::uniform_int_distribution<uint8_t>(
|
||||||
|
0, std::numeric_limits<uint8_t>::max());
|
||||||
|
dest.datalen = 8;
|
||||||
|
std::generate_n(dest.data, dest.datalen, [&dis]() { return dis(randgen); });
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
ngtcp2_tstamp timestamp(struct ev_loop *loop) {
|
||||||
|
return ev_now(loop) * NGTCP2_SECONDS;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
|
||||||
|
const uint8_t *rx_secret, const uint8_t *tx_secret,
|
||||||
|
size_t secret_len) {
|
||||||
|
auto c = static_cast<Client *>(SSL_get_app_data(ssl));
|
||||||
|
|
||||||
|
if (c->quic_on_key(
|
||||||
|
ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level),
|
||||||
|
rx_secret, tx_secret, secret_len) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_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_openssl_from_ossl_encryption_level(ossl_level), data, len);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int flush_flight(SSL *ssl) { return 1; }
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int send_alert(SSL *ssl, enum 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_encryption_secrets,
|
||||||
|
add_handshake_data,
|
||||||
|
flush_flight,
|
||||||
|
send_alert,
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// qlog write callback -- excerpted from ngtcp2/examples/client_base.cc
|
||||||
|
namespace {
|
||||||
|
void qlog_write_cb(void *user_data, uint32_t flags, const void *data,
|
||||||
|
size_t datalen) {
|
||||||
|
auto c = static_cast<Client *>(user_data);
|
||||||
|
c->quic_write_qlog(data, datalen);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Client::quic_write_qlog(const void *data, size_t datalen) {
|
||||||
|
assert(quic.qlog_file != nullptr);
|
||||||
|
fwrite(data, 1, datalen, quic.qlog_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
|
||||||
|
const sockaddr *remote_addr, socklen_t remote_addrlen) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
if (!ssl) {
|
||||||
|
ssl = SSL_new(worker->ssl_ctx);
|
||||||
|
|
||||||
|
SSL_set_app_data(ssl, this);
|
||||||
|
SSL_set_connect_state(ssl);
|
||||||
|
SSL_set_quic_method(ssl, &quic_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (remote_addr->sa_family) {
|
||||||
|
case AF_INET:
|
||||||
|
quic.max_pktlen = NGTCP2_MAX_PKTLEN_IPV4;
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
quic.max_pktlen = NGTCP2_MAX_PKTLEN_IPV6;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto callbacks = ngtcp2_callbacks{
|
||||||
|
ngtcp2_crypto_client_initial_cb,
|
||||||
|
nullptr, // recv_client_initial
|
||||||
|
ngtcp2_crypto_recv_crypto_data_cb,
|
||||||
|
h2load::handshake_completed,
|
||||||
|
nullptr, // recv_version_negotiation
|
||||||
|
ngtcp2_crypto_encrypt_cb,
|
||||||
|
ngtcp2_crypto_decrypt_cb,
|
||||||
|
ngtcp2_crypto_hp_mask_cb,
|
||||||
|
h2load::recv_stream_data,
|
||||||
|
h2load::acked_stream_data_offset,
|
||||||
|
nullptr, // stream_open
|
||||||
|
h2load::stream_close,
|
||||||
|
nullptr, // recv_stateless_reset
|
||||||
|
ngtcp2_crypto_recv_retry_cb,
|
||||||
|
h2load::extend_max_local_streams_bidi,
|
||||||
|
nullptr, // extend_max_local_streams_uni
|
||||||
|
nullptr, // rand
|
||||||
|
get_new_connection_id,
|
||||||
|
nullptr, // remove_connection_id
|
||||||
|
ngtcp2_crypto_update_key_cb,
|
||||||
|
nullptr, // path_validation
|
||||||
|
nullptr, // select_preferred_addr
|
||||||
|
h2load::stream_reset,
|
||||||
|
nullptr, // extend_max_remote_streams_bidi
|
||||||
|
nullptr, // extend_max_remote_streams_uni
|
||||||
|
nullptr, // extend_max_stream_data
|
||||||
|
nullptr, // dcid_status
|
||||||
|
nullptr, // handshake_confirmed
|
||||||
|
nullptr, // recv_new_token
|
||||||
|
ngtcp2_crypto_delete_crypto_aead_ctx_cb,
|
||||||
|
ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
|
||||||
|
nullptr, // recv_datagram
|
||||||
|
nullptr, // ack_datagram
|
||||||
|
nullptr, // lost_datagram
|
||||||
|
nullptr, // get_path_challenge_data
|
||||||
|
h2load::stream_stop_sending,
|
||||||
|
};
|
||||||
|
|
||||||
|
ngtcp2_cid scid, dcid;
|
||||||
|
generate_cid(scid);
|
||||||
|
generate_cid(dcid);
|
||||||
|
|
||||||
|
auto config = worker->config;
|
||||||
|
|
||||||
|
ngtcp2_settings settings;
|
||||||
|
ngtcp2_settings_default(&settings);
|
||||||
|
if (config->verbose) {
|
||||||
|
settings.log_printf = debug_log_printf;
|
||||||
|
}
|
||||||
|
settings.initial_ts = timestamp(worker->loop);
|
||||||
|
if (!config->qlog_file_base.empty()) {
|
||||||
|
assert(quic.qlog_file == nullptr);
|
||||||
|
auto path = config->qlog_file_base;
|
||||||
|
path += '.';
|
||||||
|
path += util::utos(worker->id);
|
||||||
|
path += '.';
|
||||||
|
path += util::utos(id);
|
||||||
|
path += ".qlog";
|
||||||
|
quic.qlog_file = fopen(path.c_str(), "w");
|
||||||
|
if (quic.qlog_file == nullptr) {
|
||||||
|
std::cerr << "Failed to open a qlog file: " << path << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
settings.qlog.write = qlog_write_cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngtcp2_transport_params params;
|
||||||
|
ngtcp2_transport_params_default(¶ms);
|
||||||
|
auto max_stream_data =
|
||||||
|
std::min((1 << 26) - 1, (1 << config->window_bits) - 1);
|
||||||
|
params.initial_max_stream_data_bidi_local = max_stream_data;
|
||||||
|
params.initial_max_stream_data_uni = max_stream_data;
|
||||||
|
params.initial_max_data = (1 << config->connection_window_bits) - 1;
|
||||||
|
params.initial_max_streams_bidi = 0;
|
||||||
|
params.initial_max_streams_uni = 100;
|
||||||
|
params.max_idle_timeout = 30 * NGTCP2_SECONDS;
|
||||||
|
|
||||||
|
auto path = ngtcp2_path{
|
||||||
|
{local_addrlen, const_cast<sockaddr *>(local_addr)},
|
||||||
|
{remote_addrlen, const_cast<sockaddr *>(remote_addr)},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(config->npn_list.size());
|
||||||
|
|
||||||
|
uint32_t quic_version;
|
||||||
|
|
||||||
|
if (config->npn_list[0] == NGHTTP3_ALPN_H3) {
|
||||||
|
quic_version = NGTCP2_PROTO_VER_V1;
|
||||||
|
} else {
|
||||||
|
quic_version = NGTCP2_PROTO_VER_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, quic_version,
|
||||||
|
&callbacks, &settings, ¶ms, nullptr, this);
|
||||||
|
if (rv != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngtcp2_conn_set_tls_native_handle(quic.conn, ssl);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::quic_free() {
|
||||||
|
ngtcp2_conn_del(quic.conn);
|
||||||
|
if (quic.qlog_file != nullptr) {
|
||||||
|
fclose(quic.qlog_file);
|
||||||
|
quic.qlog_file = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::quic_close_connection() {
|
||||||
|
if (!quic.conn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, 1500> buf;
|
||||||
|
ngtcp2_ssize nwrite;
|
||||||
|
ngtcp2_path_storage ps;
|
||||||
|
ngtcp2_path_storage_zero(&ps);
|
||||||
|
|
||||||
|
switch (quic.last_error.type) {
|
||||||
|
case quic::ErrorType::TransportVersionNegotiation:
|
||||||
|
return;
|
||||||
|
case quic::ErrorType::Transport:
|
||||||
|
nwrite = ngtcp2_conn_write_connection_close(
|
||||||
|
quic.conn, &ps.path, nullptr, buf.data(), quic.max_pktlen,
|
||||||
|
quic.last_error.code, timestamp(worker->loop));
|
||||||
|
break;
|
||||||
|
case quic::ErrorType::Application:
|
||||||
|
nwrite = ngtcp2_conn_write_application_close(
|
||||||
|
quic.conn, &ps.path, nullptr, buf.data(), quic.max_pktlen,
|
||||||
|
quic.last_error.code, timestamp(worker->loop));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nwrite < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_udp(reinterpret_cast<sockaddr *>(ps.path.remote.addr),
|
||||||
|
ps.path.remote.addrlen, buf.data(), nwrite, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::quic_on_key(ngtcp2_crypto_level level, const uint8_t *rx_secret,
|
||||||
|
const uint8_t *tx_secret, size_t secretlen) {
|
||||||
|
if (ngtcp2_crypto_derive_and_install_rx_key(quic.conn, nullptr, nullptr,
|
||||||
|
nullptr, level, rx_secret,
|
||||||
|
secretlen) != 0) {
|
||||||
|
std::cerr << "ngtcp2_crypto_derive_and_install_rx_key() failed"
|
||||||
|
<< std::endl;
|
||||||
|
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) {
|
||||||
|
auto s = std::make_unique<Http3Session>(this);
|
||||||
|
if (s->init_conn() == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
session = std::move(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::quic_set_tls_alert(uint8_t alert) {
|
||||||
|
quic.last_error = quic::err_transport_tls(alert);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::quic_write_client_handshake(ngtcp2_crypto_level level,
|
||||||
|
const uint8_t *data, size_t datalen) {
|
||||||
|
assert(level < 2);
|
||||||
|
|
||||||
|
ngtcp2_conn_submit_crypto_data(quic.conn, level, data, datalen);
|
||||||
|
}
|
||||||
|
|
||||||
|
void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto c = static_cast<Client *>(w->data);
|
||||||
|
|
||||||
|
if (c->quic_pkt_timeout() != 0) {
|
||||||
|
c->fail();
|
||||||
|
c->worker->free_client(c);
|
||||||
|
delete c;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::quic_pkt_timeout() {
|
||||||
|
int rv;
|
||||||
|
auto now = timestamp(worker->loop);
|
||||||
|
|
||||||
|
rv = ngtcp2_conn_handle_expiry(quic.conn, now);
|
||||||
|
if (rv != 0) {
|
||||||
|
quic.last_error = quic::err_transport(NGTCP2_ERR_INTERNAL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return write_quic();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::quic_restart_pkt_timer() {
|
||||||
|
auto expiry = ngtcp2_conn_get_expiry(quic.conn);
|
||||||
|
auto now = timestamp(worker->loop);
|
||||||
|
auto t = expiry > now ? static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS
|
||||||
|
: 1e-9;
|
||||||
|
quic.pkt_timer.repeat = t;
|
||||||
|
ev_timer_again(worker->loop, &quic.pkt_timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::read_quic() {
|
||||||
|
std::array<uint8_t, 65536> buf;
|
||||||
|
sockaddr_union su;
|
||||||
|
socklen_t addrlen = sizeof(su);
|
||||||
|
int rv;
|
||||||
|
size_t pktcnt = 0;
|
||||||
|
ngtcp2_pkt_info pi{};
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
auto nread =
|
||||||
|
recvfrom(fd, buf.data(), buf.size(), MSG_DONTWAIT, &su.sa, &addrlen);
|
||||||
|
if (nread == -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(quic.conn);
|
||||||
|
|
||||||
|
++worker->stats.udp_dgram_recv;
|
||||||
|
|
||||||
|
auto path = ngtcp2_path{
|
||||||
|
{local_addr.len, &local_addr.su.sa},
|
||||||
|
{addrlen, &su.sa},
|
||||||
|
};
|
||||||
|
|
||||||
|
rv = ngtcp2_conn_read_pkt(quic.conn, &path, &pi, buf.data(), nread,
|
||||||
|
timestamp(worker->loop));
|
||||||
|
if (rv != 0) {
|
||||||
|
std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++pktcnt == 100) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::write_quic() {
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
|
||||||
|
if (quic.close_requested) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<nghttp3_vec, 16> vec;
|
||||||
|
size_t pktcnt = 0;
|
||||||
|
size_t max_pktcnt =
|
||||||
|
#ifdef UDP_SEGMENT
|
||||||
|
worker->config->no_udp_gso
|
||||||
|
? 1
|
||||||
|
: std::min(static_cast<size_t>(10),
|
||||||
|
static_cast<size_t>(64_k / quic.max_pktlen));
|
||||||
|
#else // !UDP_SEGMENT
|
||||||
|
1;
|
||||||
|
#endif // !UDP_SEGMENT
|
||||||
|
std::array<uint8_t, 64_k> buf;
|
||||||
|
uint8_t *bufpos = buf.data();
|
||||||
|
ngtcp2_path_storage ps;
|
||||||
|
|
||||||
|
ngtcp2_path_storage_zero(&ps);
|
||||||
|
|
||||||
|
auto s = static_cast<Http3Session *>(session.get());
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int64_t stream_id = -1;
|
||||||
|
int fin = 0;
|
||||||
|
ssize_t sveccnt = 0;
|
||||||
|
|
||||||
|
if (session && ngtcp2_conn_get_max_data_left(quic.conn)) {
|
||||||
|
sveccnt = s->write_stream(stream_id, fin, vec.data(), vec.size());
|
||||||
|
if (sveccnt == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngtcp2_ssize ndatalen;
|
||||||
|
auto v = vec.data();
|
||||||
|
auto vcnt = static_cast<size_t>(sveccnt);
|
||||||
|
|
||||||
|
uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
|
||||||
|
if (fin) {
|
||||||
|
flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nwrite = ngtcp2_conn_writev_stream(
|
||||||
|
quic.conn, &ps.path, nullptr, bufpos, quic.max_pktlen, &ndatalen, flags,
|
||||||
|
stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt,
|
||||||
|
timestamp(worker->loop));
|
||||||
|
if (nwrite < 0) {
|
||||||
|
switch (nwrite) {
|
||||||
|
case NGTCP2_ERR_STREAM_DATA_BLOCKED:
|
||||||
|
assert(ndatalen == -1);
|
||||||
|
if (s->block_stream(stream_id) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
case NGTCP2_ERR_STREAM_SHUT_WR:
|
||||||
|
assert(ndatalen == -1);
|
||||||
|
if (s->shutdown_stream_write(stream_id) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
case NGTCP2_ERR_WRITE_MORE:
|
||||||
|
assert(ndatalen >= 0);
|
||||||
|
if (s->add_write_offset(stream_id, ndatalen) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
quic.last_error = quic::err_transport(nwrite);
|
||||||
|
return -1;
|
||||||
|
} else if (ndatalen >= 0 && s->add_write_offset(stream_id, ndatalen) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
quic_restart_pkt_timer();
|
||||||
|
|
||||||
|
if (nwrite == 0) {
|
||||||
|
if (bufpos - buf.data()) {
|
||||||
|
write_udp(ps.path.remote.addr, ps.path.remote.addrlen, buf.data(),
|
||||||
|
bufpos - buf.data(), quic.max_pktlen);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufpos += nwrite;
|
||||||
|
|
||||||
|
// Assume that the path does not change.
|
||||||
|
if (++pktcnt == max_pktcnt ||
|
||||||
|
static_cast<size_t>(nwrite) < quic.max_pktlen) {
|
||||||
|
write_udp(ps.path.remote.addr, ps.path.remote.addrlen, buf.data(),
|
||||||
|
bufpos - buf.data(), quic.max_pktlen);
|
||||||
|
signal_write();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace h2load
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 nghttp2 contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef H2LOAD_QUIC_H
|
||||||
|
#define H2LOAD_QUIC_H
|
||||||
|
|
||||||
|
#include "nghttp2_config.h"
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
|
#include "h2load.h"
|
||||||
|
|
||||||
|
namespace h2load {
|
||||||
|
void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents);
|
||||||
|
} // namespace h2load
|
||||||
|
|
||||||
|
#endif // H2LOAD_QUIC_H
|
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "http3.h"
|
||||||
|
|
||||||
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
namespace http3 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
nghttp3_nv make_nv_internal(const std::string &name, const std::string &value,
|
||||||
|
bool never_index, uint8_t nv_flags) {
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
flags = nv_flags |
|
||||||
|
(never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
|
||||||
|
|
||||||
|
return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
|
||||||
|
value.size(), flags};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
nghttp3_nv make_nv_internal(const StringRef &name, const StringRef &value,
|
||||||
|
bool never_index, uint8_t nv_flags) {
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
flags = nv_flags |
|
||||||
|
(never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
|
||||||
|
|
||||||
|
return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
|
||||||
|
value.size(), flags};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
nghttp3_nv make_nv(const std::string &name, const std::string &value,
|
||||||
|
bool never_index) {
|
||||||
|
return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
|
||||||
|
bool never_index) {
|
||||||
|
return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
|
||||||
|
bool never_index) {
|
||||||
|
return make_nv_internal(name, value, never_index,
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME |
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
|
||||||
|
bool never_index) {
|
||||||
|
return make_nv_internal(name, value, never_index,
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME |
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void copy_headers_to_nva_internal(std::vector<nghttp3_nv> &nva,
|
||||||
|
const HeaderRefs &headers, uint8_t nv_flags,
|
||||||
|
uint32_t flags) {
|
||||||
|
auto it_forwarded = std::end(headers);
|
||||||
|
auto it_xff = std::end(headers);
|
||||||
|
auto it_xfp = std::end(headers);
|
||||||
|
auto it_via = std::end(headers);
|
||||||
|
|
||||||
|
for (auto it = std::begin(headers); it != std::end(headers); ++it) {
|
||||||
|
auto kv = &(*it);
|
||||||
|
if (kv->name.empty() || kv->name[0] == ':') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (kv->token) {
|
||||||
|
case http2::HD_COOKIE:
|
||||||
|
case http2::HD_CONNECTION:
|
||||||
|
case http2::HD_HOST:
|
||||||
|
case http2::HD_HTTP2_SETTINGS:
|
||||||
|
case http2::HD_KEEP_ALIVE:
|
||||||
|
case http2::HD_PROXY_CONNECTION:
|
||||||
|
case http2::HD_SERVER:
|
||||||
|
case http2::HD_TE:
|
||||||
|
case http2::HD_TRANSFER_ENCODING:
|
||||||
|
case http2::HD_UPGRADE:
|
||||||
|
continue;
|
||||||
|
case http2::HD_EARLY_DATA:
|
||||||
|
if (flags & http2::HDOP_STRIP_EARLY_DATA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case http2::HD_SEC_WEBSOCKET_ACCEPT:
|
||||||
|
if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case http2::HD_SEC_WEBSOCKET_KEY:
|
||||||
|
if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_KEY) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case http2::HD_FORWARDED:
|
||||||
|
if (flags & http2::HDOP_STRIP_FORWARDED) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it_forwarded == std::end(headers)) {
|
||||||
|
it_forwarded = it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kv = &(*it_forwarded);
|
||||||
|
it_forwarded = it;
|
||||||
|
break;
|
||||||
|
case http2::HD_X_FORWARDED_FOR:
|
||||||
|
if (flags & http2::HDOP_STRIP_X_FORWARDED_FOR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it_xff == std::end(headers)) {
|
||||||
|
it_xff = it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kv = &(*it_xff);
|
||||||
|
it_xff = it;
|
||||||
|
break;
|
||||||
|
case http2::HD_X_FORWARDED_PROTO:
|
||||||
|
if (flags & http2::HDOP_STRIP_X_FORWARDED_PROTO) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it_xfp == std::end(headers)) {
|
||||||
|
it_xfp = it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kv = &(*it_xfp);
|
||||||
|
it_xfp = it;
|
||||||
|
break;
|
||||||
|
case http2::HD_VIA:
|
||||||
|
if (flags & http2::HDOP_STRIP_VIA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it_via == std::end(headers)) {
|
||||||
|
it_via = it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kv = &(*it_via);
|
||||||
|
it_via = it;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nva.push_back(
|
||||||
|
make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
|
||||||
|
const HeaderRefs &headers, uint32_t flags) {
|
||||||
|
copy_headers_to_nva_internal(nva, headers, NGHTTP3_NV_FLAG_NONE, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
|
||||||
|
const HeaderRefs &headers, uint32_t flags) {
|
||||||
|
copy_headers_to_nva_internal(
|
||||||
|
nva, headers,
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
|
||||||
|
size_t valuelen) {
|
||||||
|
if (!nghttp3_check_header_name(name, namelen)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!nghttp3_check_header_value(value, valuelen)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen) {
|
||||||
|
auto end = nva + nvlen;
|
||||||
|
for (; nva != end; ++nva) {
|
||||||
|
fprintf(out, "%s: %s\n", nva->name, nva->value);
|
||||||
|
}
|
||||||
|
fputc('\n', out);
|
||||||
|
fflush(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace http3
|
||||||
|
|
||||||
|
} // namespace nghttp2
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef HTTP3_H
|
||||||
|
#define HTTP3_H
|
||||||
|
|
||||||
|
#include "nghttp2_config.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <nghttp3/nghttp3.h>
|
||||||
|
|
||||||
|
#include "http2.h"
|
||||||
|
#include "template.h"
|
||||||
|
|
||||||
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
namespace http3 {
|
||||||
|
|
||||||
|
// Creates nghttp3_nv using |name| and |value| and returns it. The
|
||||||
|
// returned value only references the data pointer to name.c_str() and
|
||||||
|
// value.c_str(). If |no_index| is true, nghttp3_nv flags member has
|
||||||
|
// NGHTTP3_NV_FLAG_NEVER_INDEX flag set.
|
||||||
|
nghttp3_nv make_nv(const std::string &name, const std::string &value,
|
||||||
|
bool never_index = false);
|
||||||
|
|
||||||
|
nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
|
||||||
|
bool never_index = false);
|
||||||
|
|
||||||
|
nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
|
||||||
|
bool never_index = false);
|
||||||
|
|
||||||
|
nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
|
||||||
|
bool never_index = false);
|
||||||
|
|
||||||
|
// Create nghttp3_nv from string literal |name| and |value|.
|
||||||
|
template <size_t N, size_t M>
|
||||||
|
constexpr nghttp3_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) {
|
||||||
|
return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create nghttp3_nv from string literal |name| and c-string |value|.
|
||||||
|
template <size_t N>
|
||||||
|
nghttp3_nv make_nv_lc(const char (&name)[N], const char *value) {
|
||||||
|
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
nghttp3_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) {
|
||||||
|
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create nghttp3_nv from string literal |name| and std::string
|
||||||
|
// |value|.
|
||||||
|
template <size_t N>
|
||||||
|
nghttp3_nv make_nv_ls(const char (&name)[N], const std::string &value) {
|
||||||
|
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) {
|
||||||
|
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) {
|
||||||
|
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
|
||||||
|
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appends headers in |headers| to |nv|. |headers| must be indexed
|
||||||
|
// before this call (its element's token field is assigned). Certain
|
||||||
|
// headers, including disallowed headers in HTTP/3 spec and headers
|
||||||
|
// which require special handling (i.e. via), are not copied. |flags|
|
||||||
|
// is one or more of HeaderBuildOp flags. They tell function that
|
||||||
|
// certain header fields should not be added.
|
||||||
|
void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
|
||||||
|
const HeaderRefs &headers, uint32_t flags);
|
||||||
|
|
||||||
|
// Just like copy_headers_to_nva(), but this adds
|
||||||
|
// NGHTTP3_NV_FLAG_NO_COPY_NAME and NGHTTP3_NV_FLAG_NO_COPY_VALUE.
|
||||||
|
void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
|
||||||
|
const HeaderRefs &headers, uint32_t flags);
|
||||||
|
|
||||||
|
// Dumps name/value pairs in |nva| to |out|.
|
||||||
|
void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen);
|
||||||
|
|
||||||
|
// Checks the header name/value pair using nghttp3_check_header_name()
|
||||||
|
// and nghttp3_check_header_value(). If both function returns nonzero,
|
||||||
|
// this function returns nonzero.
|
||||||
|
int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
|
||||||
|
size_t valuelen);
|
||||||
|
|
||||||
|
// Dumps name/value pairs in |nva| to |out|.
|
||||||
|
void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen);
|
||||||
|
|
||||||
|
} // namespace http3
|
||||||
|
|
||||||
|
} // namespace nghttp2
|
||||||
|
|
||||||
|
#endif // HTTP3_H
|
|
@ -113,13 +113,22 @@ template <typename T> struct Pool {
|
||||||
|
|
||||||
template <typename Memchunk> struct Memchunks {
|
template <typename Memchunk> struct Memchunks {
|
||||||
Memchunks(Pool<Memchunk> *pool)
|
Memchunks(Pool<Memchunk> *pool)
|
||||||
: pool(pool), head(nullptr), tail(nullptr), len(0) {}
|
: pool(pool),
|
||||||
|
head(nullptr),
|
||||||
|
tail(nullptr),
|
||||||
|
len(0),
|
||||||
|
mark(nullptr),
|
||||||
|
mark_pos(nullptr),
|
||||||
|
mark_offset(0) {}
|
||||||
Memchunks(const Memchunks &) = delete;
|
Memchunks(const Memchunks &) = delete;
|
||||||
Memchunks(Memchunks &&other) noexcept
|
Memchunks(Memchunks &&other) noexcept
|
||||||
: pool{other.pool}, // keep other.pool
|
: pool{other.pool}, // keep other.pool
|
||||||
head{std::exchange(other.head, nullptr)},
|
head{std::exchange(other.head, nullptr)},
|
||||||
tail{std::exchange(other.tail, nullptr)},
|
tail{std::exchange(other.tail, nullptr)},
|
||||||
len{std::exchange(other.len, 0)} {}
|
len{std::exchange(other.len, 0)},
|
||||||
|
mark{std::exchange(other.mark, nullptr)},
|
||||||
|
mark_pos{std::exchange(other.mark_pos, nullptr)},
|
||||||
|
mark_offset{std::exchange(other.mark_offset, 0)} {}
|
||||||
Memchunks &operator=(const Memchunks &) = delete;
|
Memchunks &operator=(const Memchunks &) = delete;
|
||||||
Memchunks &operator=(Memchunks &&other) noexcept {
|
Memchunks &operator=(Memchunks &&other) noexcept {
|
||||||
if (this == &other) {
|
if (this == &other) {
|
||||||
|
@ -132,6 +141,9 @@ template <typename Memchunk> struct Memchunks {
|
||||||
head = std::exchange(other.head, nullptr);
|
head = std::exchange(other.head, nullptr);
|
||||||
tail = std::exchange(other.tail, nullptr);
|
tail = std::exchange(other.tail, nullptr);
|
||||||
len = std::exchange(other.len, 0);
|
len = std::exchange(other.len, 0);
|
||||||
|
mark = std::exchange(other.mark, nullptr);
|
||||||
|
mark_pos = std::exchange(other.mark_pos, nullptr);
|
||||||
|
mark_offset = std::exchange(other.mark_offset, 0);
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -200,6 +212,8 @@ template <typename Memchunk> struct Memchunks {
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
size_t remove(void *dest, size_t count) {
|
size_t remove(void *dest, size_t count) {
|
||||||
|
assert(mark == nullptr);
|
||||||
|
|
||||||
if (!tail || count == 0) {
|
if (!tail || count == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -231,6 +245,8 @@ template <typename Memchunk> struct Memchunks {
|
||||||
return first - static_cast<uint8_t *>(dest);
|
return first - static_cast<uint8_t *>(dest);
|
||||||
}
|
}
|
||||||
size_t remove(Memchunks &dest, size_t count) {
|
size_t remove(Memchunks &dest, size_t count) {
|
||||||
|
assert(mark == nullptr);
|
||||||
|
|
||||||
if (!tail || count == 0) {
|
if (!tail || count == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -262,6 +278,7 @@ template <typename Memchunk> struct Memchunks {
|
||||||
}
|
}
|
||||||
size_t remove(Memchunks &dest) {
|
size_t remove(Memchunks &dest) {
|
||||||
assert(pool == dest.pool);
|
assert(pool == dest.pool);
|
||||||
|
assert(mark == nullptr);
|
||||||
|
|
||||||
if (head == nullptr) {
|
if (head == nullptr) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -284,6 +301,8 @@ template <typename Memchunk> struct Memchunks {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
size_t drain(size_t count) {
|
size_t drain(size_t count) {
|
||||||
|
assert(mark == nullptr);
|
||||||
|
|
||||||
auto ndata = count;
|
auto ndata = count;
|
||||||
auto m = head;
|
auto m = head;
|
||||||
while (m) {
|
while (m) {
|
||||||
|
@ -305,6 +324,38 @@ template <typename Memchunk> struct Memchunks {
|
||||||
}
|
}
|
||||||
return ndata - count;
|
return ndata - count;
|
||||||
}
|
}
|
||||||
|
size_t drain_mark(size_t count) {
|
||||||
|
auto ndata = count;
|
||||||
|
auto m = head;
|
||||||
|
while (m) {
|
||||||
|
auto next = m->next;
|
||||||
|
auto n = std::min(count, m->len());
|
||||||
|
m->pos += n;
|
||||||
|
count -= n;
|
||||||
|
len -= n;
|
||||||
|
mark_offset -= n;
|
||||||
|
|
||||||
|
if (m->len() > 0) {
|
||||||
|
assert(mark != m || m->pos <= mark_pos);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mark == m) {
|
||||||
|
assert(m->pos <= mark_pos);
|
||||||
|
|
||||||
|
mark = nullptr;
|
||||||
|
mark_pos = nullptr;
|
||||||
|
mark_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->recycle(m);
|
||||||
|
m = next;
|
||||||
|
}
|
||||||
|
head = m;
|
||||||
|
if (head == nullptr) {
|
||||||
|
tail = nullptr;
|
||||||
|
}
|
||||||
|
return ndata - count;
|
||||||
|
}
|
||||||
int riovec(struct iovec *iov, int iovcnt) const {
|
int riovec(struct iovec *iov, int iovcnt) const {
|
||||||
if (!head) {
|
if (!head) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -317,7 +368,41 @@ template <typename Memchunk> struct Memchunks {
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
int riovec_mark(struct iovec *iov, int iovcnt) {
|
||||||
|
if (!head || iovcnt == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
Memchunk *m;
|
||||||
|
if (mark) {
|
||||||
|
if (mark_pos != mark->last) {
|
||||||
|
iov[0].iov_base = mark_pos;
|
||||||
|
iov[0].iov_len = mark->len() - (mark_pos - mark->pos);
|
||||||
|
|
||||||
|
mark_pos = mark->last;
|
||||||
|
mark_offset += iov[0].iov_len;
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
m = mark->next;
|
||||||
|
} else {
|
||||||
|
i = 0;
|
||||||
|
m = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < iovcnt && m; ++i, m = m->next) {
|
||||||
|
iov[i].iov_base = m->pos;
|
||||||
|
iov[i].iov_len = m->len();
|
||||||
|
|
||||||
|
mark = m;
|
||||||
|
mark_pos = m->last;
|
||||||
|
mark_offset += m->len();
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
size_t rleft() const { return len; }
|
size_t rleft() const { return len; }
|
||||||
|
size_t rleft_mark() const { return len - mark_offset; }
|
||||||
void reset() {
|
void reset() {
|
||||||
for (auto m = head; m;) {
|
for (auto m = head; m;) {
|
||||||
auto next = m->next;
|
auto next = m->next;
|
||||||
|
@ -325,12 +410,17 @@ template <typename Memchunk> struct Memchunks {
|
||||||
m = next;
|
m = next;
|
||||||
}
|
}
|
||||||
len = 0;
|
len = 0;
|
||||||
head = tail = nullptr;
|
head = tail = mark = nullptr;
|
||||||
|
mark_pos = nullptr;
|
||||||
|
mark_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pool<Memchunk> *pool;
|
Pool<Memchunk> *pool;
|
||||||
Memchunk *head, *tail;
|
Memchunk *head, *tail;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
Memchunk *mark;
|
||||||
|
uint8_t *mark_pos;
|
||||||
|
size_t mark_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrapper around Memchunks to offer "peeking" functionality.
|
// Wrapper around Memchunks to offer "peeking" functionality.
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 nghttp2 contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "quic.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
#include <nghttp3/nghttp3.h>
|
||||||
|
|
||||||
|
#include "template.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace quic {
|
||||||
|
|
||||||
|
Error err_transport(int liberr) {
|
||||||
|
if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) {
|
||||||
|
return {ErrorType::TransportVersionNegotiation, 0};
|
||||||
|
}
|
||||||
|
return {ErrorType::Transport,
|
||||||
|
ngtcp2_err_infer_quic_transport_error_code(liberr)};
|
||||||
|
}
|
||||||
|
|
||||||
|
Error err_transport_idle_timeout() {
|
||||||
|
return {ErrorType::TransportIdleTimeout, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
Error err_transport_tls(int alert) {
|
||||||
|
return {ErrorType::Transport, ngtcp2_err_infer_quic_transport_error_code(
|
||||||
|
NGTCP2_CRYPTO_ERROR | alert)};
|
||||||
|
}
|
||||||
|
|
||||||
|
Error err_application(int liberr) {
|
||||||
|
return {ErrorType::Application,
|
||||||
|
nghttp3_err_infer_quic_app_error_code(liberr)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace quic
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 nghttp2 contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef QUIC_H
|
||||||
|
#define QUIC_H
|
||||||
|
|
||||||
|
#include "nghttp2_config.h"
|
||||||
|
|
||||||
|
#include "stdint.h"
|
||||||
|
|
||||||
|
namespace quic {
|
||||||
|
|
||||||
|
enum class ErrorType {
|
||||||
|
Transport,
|
||||||
|
TransportVersionNegotiation,
|
||||||
|
TransportIdleTimeout,
|
||||||
|
Application,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Error {
|
||||||
|
Error(ErrorType type, uint64_t code) : type(type), code(code) {}
|
||||||
|
Error() : type(ErrorType::Transport), code(0) {}
|
||||||
|
|
||||||
|
ErrorType type;
|
||||||
|
uint64_t code;
|
||||||
|
};
|
||||||
|
|
||||||
|
Error err_transport(int liberr);
|
||||||
|
Error err_transport_idle_timeout();
|
||||||
|
Error err_transport_tls(int alert);
|
||||||
|
Error err_application(int liberr);
|
||||||
|
|
||||||
|
} // namespace quic
|
||||||
|
|
||||||
|
#endif // QUIC_H
|
13
src/shrpx.cc
13
src/shrpx.cc
|
@ -1548,6 +1548,19 @@ void fill_default_config(Config *config) {
|
||||||
downstreamconf.option, downstreamconf.encoder_dynamic_table_size);
|
downstreamconf.option, downstreamconf.encoder_dynamic_table_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &quicconf = config->quic;
|
||||||
|
{
|
||||||
|
quicconf.timeout.idle = 30_s;
|
||||||
|
|
||||||
|
auto &stateless_resetconf = quicconf.stateless_reset;
|
||||||
|
// TODO Find better place to do this and error handling.
|
||||||
|
if (RAND_bytes(stateless_resetconf.secret.data(),
|
||||||
|
stateless_resetconf.secret.size()) != 1) {
|
||||||
|
LOG(FATAL) << "Unable to generate stateless reset secret";
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto &loggingconf = config->logging;
|
auto &loggingconf = config->logging;
|
||||||
{
|
{
|
||||||
auto &accessconf = loggingconf.access;
|
auto &accessconf = loggingconf.access;
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
#include "shrpx_connect_blocker.h"
|
#include "shrpx_connect_blocker.h"
|
||||||
#include "shrpx_api_downstream_connection.h"
|
#include "shrpx_api_downstream_connection.h"
|
||||||
#include "shrpx_health_monitor_downstream_connection.h"
|
#include "shrpx_health_monitor_downstream_connection.h"
|
||||||
|
#include "shrpx_http3_upstream.h"
|
||||||
#include "shrpx_log.h"
|
#include "shrpx_log.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
@ -285,6 +286,17 @@ int ClientHandler::write_tls() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ClientHandler::read_quic(const UpstreamAddr *faddr,
|
||||||
|
const Address &remote_addr,
|
||||||
|
const Address &local_addr, const uint8_t *data,
|
||||||
|
size_t datalen) {
|
||||||
|
auto upstream = static_cast<Http3Upstream *>(upstream_.get());
|
||||||
|
|
||||||
|
return upstream->on_read(faddr, remote_addr, local_addr, data, datalen);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientHandler::write_quic() { return upstream_->on_write(); }
|
||||||
|
|
||||||
int ClientHandler::upstream_noop() { return 0; }
|
int ClientHandler::upstream_noop() { return 0; }
|
||||||
|
|
||||||
int ClientHandler::upstream_read() {
|
int ClientHandler::upstream_read() {
|
||||||
|
@ -401,7 +413,8 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
|
||||||
get_config()->conn.upstream.ratelimit.write,
|
get_config()->conn.upstream.ratelimit.write,
|
||||||
get_config()->conn.upstream.ratelimit.read, writecb, readcb,
|
get_config()->conn.upstream.ratelimit.read, writecb, readcb,
|
||||||
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
|
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
|
||||||
get_config()->tls.dyn_rec.idle_timeout, Proto::NONE),
|
get_config()->tls.dyn_rec.idle_timeout,
|
||||||
|
faddr->quic ? Proto::HTTP3 : Proto::NONE),
|
||||||
ipaddr_(make_string_ref(balloc_, ipaddr)),
|
ipaddr_(make_string_ref(balloc_, ipaddr)),
|
||||||
port_(make_string_ref(balloc_, port)),
|
port_(make_string_ref(balloc_, port)),
|
||||||
faddr_(faddr),
|
faddr_(faddr),
|
||||||
|
@ -417,19 +430,23 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
|
||||||
|
|
||||||
reneg_shutdown_timer_.data = this;
|
reneg_shutdown_timer_.data = this;
|
||||||
|
|
||||||
conn_.rlimit.startw();
|
if (!faddr->quic) {
|
||||||
|
conn_.rlimit.startw();
|
||||||
|
}
|
||||||
ev_timer_again(conn_.loop, &conn_.rt);
|
ev_timer_again(conn_.loop, &conn_.rt);
|
||||||
|
|
||||||
auto config = get_config();
|
auto config = get_config();
|
||||||
|
|
||||||
if (faddr_->accept_proxy_protocol ||
|
if (!faddr->quic) {
|
||||||
config->conn.upstream.accept_proxy_protocol) {
|
if (faddr_->accept_proxy_protocol ||
|
||||||
read_ = &ClientHandler::read_clear;
|
config->conn.upstream.accept_proxy_protocol) {
|
||||||
write_ = &ClientHandler::noop;
|
read_ = &ClientHandler::read_clear;
|
||||||
on_read_ = &ClientHandler::proxy_protocol_read;
|
write_ = &ClientHandler::noop;
|
||||||
on_write_ = &ClientHandler::upstream_noop;
|
on_read_ = &ClientHandler::proxy_protocol_read;
|
||||||
} else {
|
on_write_ = &ClientHandler::upstream_noop;
|
||||||
setup_upstream_io_callback();
|
} else {
|
||||||
|
setup_upstream_io_callback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &fwdconf = config->http.forwarded;
|
auto &fwdconf = config->http.forwarded;
|
||||||
|
@ -491,6 +508,13 @@ void ClientHandler::setup_upstream_io_callback() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClientHandler::setup_http3_upstream(
|
||||||
|
std::unique_ptr<Http3Upstream> &&upstream) {
|
||||||
|
upstream_ = std::move(upstream);
|
||||||
|
alpn_ = StringRef::from_lit("h3");
|
||||||
|
write_ = &ClientHandler::write_quic;
|
||||||
|
}
|
||||||
|
|
||||||
ClientHandler::~ClientHandler() {
|
ClientHandler::~ClientHandler() {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
CLOG(INFO, this) << "Deleting";
|
CLOG(INFO, this) << "Deleting";
|
||||||
|
|
|
@ -53,6 +53,7 @@ class Downstream;
|
||||||
struct WorkerStat;
|
struct WorkerStat;
|
||||||
struct DownstreamAddrGroup;
|
struct DownstreamAddrGroup;
|
||||||
struct DownstreamAddr;
|
struct DownstreamAddr;
|
||||||
|
class Http3Upstream;
|
||||||
|
|
||||||
class ClientHandler {
|
class ClientHandler {
|
||||||
public:
|
public:
|
||||||
|
@ -70,6 +71,9 @@ public:
|
||||||
int read_tls();
|
int read_tls();
|
||||||
int write_tls();
|
int write_tls();
|
||||||
|
|
||||||
|
int read_quic(const UpstreamAddr *faddr, const Address &remote_addr,
|
||||||
|
const Address &local_addr, const uint8_t *data, size_t datalen);
|
||||||
|
|
||||||
int upstream_noop();
|
int upstream_noop();
|
||||||
int upstream_read();
|
int upstream_read();
|
||||||
int upstream_http2_connhd_read();
|
int upstream_http2_connhd_read();
|
||||||
|
@ -143,6 +147,9 @@ public:
|
||||||
|
|
||||||
void setup_upstream_io_callback();
|
void setup_upstream_io_callback();
|
||||||
|
|
||||||
|
void setup_http3_upstream(std::unique_ptr<Http3Upstream> &&upstream);
|
||||||
|
int write_quic();
|
||||||
|
|
||||||
// Returns string suitable for use in "by" parameter of Forwarded
|
// Returns string suitable for use in "by" parameter of Forwarded
|
||||||
// header field.
|
// header field.
|
||||||
StringRef get_forwarded_by() const;
|
StringRef get_forwarded_by() const;
|
||||||
|
|
|
@ -785,6 +785,7 @@ struct UpstreamParams {
|
||||||
bool tls;
|
bool tls;
|
||||||
bool sni_fwd;
|
bool sni_fwd;
|
||||||
bool proxyproto;
|
bool proxyproto;
|
||||||
|
bool quic;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -819,6 +820,8 @@ int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) {
|
||||||
out.alt_mode = UpstreamAltMode::HEALTHMON;
|
out.alt_mode = UpstreamAltMode::HEALTHMON;
|
||||||
} else if (util::strieq_l("proxyproto", param)) {
|
} else if (util::strieq_l("proxyproto", param)) {
|
||||||
out.proxyproto = true;
|
out.proxyproto = true;
|
||||||
|
} else if (util::strieq_l("quic", param)) {
|
||||||
|
out.quic = true;
|
||||||
} else if (!param.empty()) {
|
} else if (!param.empty()) {
|
||||||
LOG(ERROR) << "frontend: " << param << ": unknown keyword";
|
LOG(ERROR) << "frontend: " << param << ": unknown keyword";
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -2624,7 +2627,6 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
case SHRPX_OPTID_FRONTEND: {
|
case SHRPX_OPTID_FRONTEND: {
|
||||||
auto &listenerconf = config->conn.listener;
|
|
||||||
auto &apiconf = config->api;
|
auto &apiconf = config->api;
|
||||||
|
|
||||||
auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
|
auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
|
||||||
|
@ -2642,23 +2644,32 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.quic && params.alt_mode != UpstreamAltMode::NONE) {
|
||||||
|
LOG(ERROR) << "frontend: api or healthmon cannot be used with quic";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
UpstreamAddr addr{};
|
UpstreamAddr addr{};
|
||||||
addr.fd = -1;
|
addr.fd = -1;
|
||||||
addr.tls = params.tls;
|
addr.tls = params.tls;
|
||||||
addr.sni_fwd = params.sni_fwd;
|
addr.sni_fwd = params.sni_fwd;
|
||||||
addr.alt_mode = params.alt_mode;
|
addr.alt_mode = params.alt_mode;
|
||||||
addr.accept_proxy_protocol = params.proxyproto;
|
addr.accept_proxy_protocol = params.proxyproto;
|
||||||
|
addr.quic = params.quic;
|
||||||
|
|
||||||
if (addr.alt_mode == UpstreamAltMode::API) {
|
if (addr.alt_mode == UpstreamAltMode::API) {
|
||||||
apiconf.enabled = true;
|
apiconf.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &addrs = params.quic ? config->conn.quic_listener.addrs
|
||||||
|
: config->conn.listener.addrs;
|
||||||
|
|
||||||
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||||
auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size();
|
auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size();
|
||||||
addr.host = make_string_ref(config->balloc, StringRef{path, addr_end});
|
addr.host = make_string_ref(config->balloc, StringRef{path, addr_end});
|
||||||
addr.host_unix = true;
|
addr.host_unix = true;
|
||||||
|
|
||||||
listenerconf.addrs.push_back(std::move(addr));
|
addrs.push_back(std::move(addr));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2673,21 +2684,21 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
|
|
||||||
if (util::numeric_host(host, AF_INET)) {
|
if (util::numeric_host(host, AF_INET)) {
|
||||||
addr.family = AF_INET;
|
addr.family = AF_INET;
|
||||||
listenerconf.addrs.push_back(std::move(addr));
|
addrs.push_back(std::move(addr));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util::numeric_host(host, AF_INET6)) {
|
if (util::numeric_host(host, AF_INET6)) {
|
||||||
addr.family = AF_INET6;
|
addr.family = AF_INET6;
|
||||||
listenerconf.addrs.push_back(std::move(addr));
|
addrs.push_back(std::move(addr));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
addr.family = AF_INET;
|
addr.family = AF_INET;
|
||||||
listenerconf.addrs.push_back(addr);
|
addrs.push_back(addr);
|
||||||
|
|
||||||
addr.family = AF_INET6;
|
addr.family = AF_INET6;
|
||||||
listenerconf.addrs.push_back(std::move(addr));
|
addrs.push_back(std::move(addr));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -3967,6 +3978,8 @@ StringRef strproto(Proto proto) {
|
||||||
return StringRef::from_lit("http/1.1");
|
return StringRef::from_lit("http/1.1");
|
||||||
case Proto::HTTP2:
|
case Proto::HTTP2:
|
||||||
return StringRef::from_lit("h2");
|
return StringRef::from_lit("h2");
|
||||||
|
case Proto::HTTP3:
|
||||||
|
return StringRef::from_lit("h3");
|
||||||
case Proto::MEMCACHED:
|
case Proto::MEMCACHED:
|
||||||
return StringRef::from_lit("memcached");
|
return StringRef::from_lit("memcached");
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,6 +370,7 @@ enum class Proto {
|
||||||
NONE,
|
NONE,
|
||||||
HTTP1,
|
HTTP1,
|
||||||
HTTP2,
|
HTTP2,
|
||||||
|
HTTP3,
|
||||||
MEMCACHED,
|
MEMCACHED,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -458,6 +459,7 @@ struct UpstreamAddr {
|
||||||
bool sni_fwd;
|
bool sni_fwd;
|
||||||
// true if client is supposed to send PROXY protocol v1 header.
|
// true if client is supposed to send PROXY protocol v1 header.
|
||||||
bool accept_proxy_protocol;
|
bool accept_proxy_protocol;
|
||||||
|
bool quic;
|
||||||
int fd;
|
int fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -698,6 +700,18 @@ struct TLSConfig {
|
||||||
bool no_postpone_early_data;
|
bool no_postpone_early_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct QUICConfig {
|
||||||
|
struct {
|
||||||
|
std::array<uint8_t, 32> secret;
|
||||||
|
} stateless_reset;
|
||||||
|
struct {
|
||||||
|
ev_tstamp idle;
|
||||||
|
} timeout;
|
||||||
|
struct {
|
||||||
|
bool log;
|
||||||
|
} debug;
|
||||||
|
};
|
||||||
|
|
||||||
// custom error page
|
// custom error page
|
||||||
struct ErrorPage {
|
struct ErrorPage {
|
||||||
// not NULL-terminated
|
// not NULL-terminated
|
||||||
|
@ -903,6 +917,10 @@ struct ConnectionConfig {
|
||||||
int fastopen;
|
int fastopen;
|
||||||
} listener;
|
} listener;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
std::vector<UpstreamAddr> addrs;
|
||||||
|
} quic_listener;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
struct {
|
struct {
|
||||||
ev_tstamp http2_read;
|
ev_tstamp http2_read;
|
||||||
|
@ -946,6 +964,7 @@ struct Config {
|
||||||
http{},
|
http{},
|
||||||
http2{},
|
http2{},
|
||||||
tls{},
|
tls{},
|
||||||
|
quic{},
|
||||||
logging{},
|
logging{},
|
||||||
conn{},
|
conn{},
|
||||||
api{},
|
api{},
|
||||||
|
@ -979,6 +998,7 @@ struct Config {
|
||||||
HttpConfig http;
|
HttpConfig http;
|
||||||
Http2Config http2;
|
Http2Config http2;
|
||||||
TLSConfig tls;
|
TLSConfig tls;
|
||||||
|
QUICConfig quic;
|
||||||
LoggingConfig logging;
|
LoggingConfig logging;
|
||||||
ConnectionConfig conn;
|
ConnectionConfig conn;
|
||||||
APIConfig api;
|
APIConfig api;
|
||||||
|
|
|
@ -74,7 +74,7 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
|
||||||
read_timeout(read_timeout) {
|
read_timeout(read_timeout) {
|
||||||
|
|
||||||
ev_io_init(&wev, writecb, fd, EV_WRITE);
|
ev_io_init(&wev, writecb, fd, EV_WRITE);
|
||||||
ev_io_init(&rev, readcb, fd, EV_READ);
|
ev_io_init(&rev, readcb, proto == Proto::HTTP3 ? 0 : fd, EV_READ);
|
||||||
|
|
||||||
wev.data = this;
|
wev.data = this;
|
||||||
rev.data = this;
|
rev.data = this;
|
||||||
|
@ -128,7 +128,7 @@ void Connection::disconnect() {
|
||||||
tls.early_data_finish = false;
|
tls.early_data_finish = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fd != -1) {
|
if (proto != Proto::HTTP3 && fd != -1) {
|
||||||
shutdown(fd, SHUT_WR);
|
shutdown(fd, SHUT_WR);
|
||||||
close(fd);
|
close(fd);
|
||||||
fd = -1;
|
fd = -1;
|
||||||
|
@ -312,10 +312,13 @@ BIO_METHOD *create_bio_method() {
|
||||||
void Connection::set_ssl(SSL *ssl) {
|
void Connection::set_ssl(SSL *ssl) {
|
||||||
tls.ssl = ssl;
|
tls.ssl = ssl;
|
||||||
|
|
||||||
auto &tlsconf = get_config()->tls;
|
if (proto != Proto::HTTP3) {
|
||||||
auto bio = BIO_new(tlsconf.bio_method);
|
auto &tlsconf = get_config()->tls;
|
||||||
BIO_set_data(bio, this);
|
auto bio = BIO_new(tlsconf.bio_method);
|
||||||
SSL_set_bio(tls.ssl, bio, bio);
|
BIO_set_data(bio, this);
|
||||||
|
SSL_set_bio(tls.ssl, bio, bio);
|
||||||
|
}
|
||||||
|
|
||||||
SSL_set_app_data(tls.ssl, this);
|
SSL_set_app_data(tls.ssl, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,17 @@ ConnectionHandler::~ConnectionHandler() {
|
||||||
ev_timer_stop(loop_, &ocsp_timer_);
|
ev_timer_stop(loop_, &ocsp_timer_);
|
||||||
ev_timer_stop(loop_, &disable_acceptor_timer_);
|
ev_timer_stop(loop_, &disable_acceptor_timer_);
|
||||||
|
|
||||||
|
for (auto ssl_ctx : quic_all_ssl_ctx_) {
|
||||||
|
if (ssl_ctx == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tls_ctx_data =
|
||||||
|
static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
|
||||||
|
delete tls_ctx_data;
|
||||||
|
SSL_CTX_free(ssl_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto ssl_ctx : all_ssl_ctx_) {
|
for (auto ssl_ctx : all_ssl_ctx_) {
|
||||||
auto tls_ctx_data =
|
auto tls_ctx_data =
|
||||||
static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
|
static_cast<tls::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
|
||||||
|
@ -209,6 +220,16 @@ int ConnectionHandler::create_single_worker() {
|
||||||
nb_
|
nb_
|
||||||
#endif // HAVE_NEVERBLEED
|
#endif // HAVE_NEVERBLEED
|
||||||
);
|
);
|
||||||
|
|
||||||
|
quic_cert_tree_ = tls::create_cert_lookup_tree();
|
||||||
|
auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context(
|
||||||
|
quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get()
|
||||||
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
,
|
||||||
|
nb_
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
);
|
||||||
|
|
||||||
auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
|
auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
|
||||||
#ifdef HAVE_NEVERBLEED
|
#ifdef HAVE_NEVERBLEED
|
||||||
nb_
|
nb_
|
||||||
|
@ -217,6 +238,7 @@ int ConnectionHandler::create_single_worker() {
|
||||||
|
|
||||||
if (cl_ssl_ctx) {
|
if (cl_ssl_ctx) {
|
||||||
all_ssl_ctx_.push_back(cl_ssl_ctx);
|
all_ssl_ctx_.push_back(cl_ssl_ctx);
|
||||||
|
quic_all_ssl_ctx_.push_back(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto config = get_config();
|
auto config = get_config();
|
||||||
|
@ -233,17 +255,22 @@ int ConnectionHandler::create_single_worker() {
|
||||||
tlsconf.cacert, memcachedconf.cert_file,
|
tlsconf.cacert, memcachedconf.cert_file,
|
||||||
memcachedconf.private_key_file, nullptr);
|
memcachedconf.private_key_file, nullptr);
|
||||||
all_ssl_ctx_.push_back(session_cache_ssl_ctx);
|
all_ssl_ctx_.push_back(session_cache_ssl_ctx);
|
||||||
|
quic_all_ssl_ctx_.push_back(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
single_worker_ = std::make_unique<Worker>(
|
single_worker_ = std::make_unique<Worker>(
|
||||||
loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
|
loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
|
||||||
ticket_keys_, this, config->conn.downstream);
|
quic_sv_ssl_ctx, quic_cert_tree_.get(), ticket_keys_, this,
|
||||||
|
config->conn.downstream);
|
||||||
#ifdef HAVE_MRUBY
|
#ifdef HAVE_MRUBY
|
||||||
if (single_worker_->create_mruby_context() != 0) {
|
if (single_worker_->create_mruby_context() != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif // HAVE_MRUBY
|
#endif // HAVE_MRUBY
|
||||||
|
if (single_worker_->setup_quic_server_socket() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -260,6 +287,16 @@ int ConnectionHandler::create_worker_thread(size_t num) {
|
||||||
nb_
|
nb_
|
||||||
# endif // HAVE_NEVERBLEED
|
# endif // HAVE_NEVERBLEED
|
||||||
);
|
);
|
||||||
|
|
||||||
|
quic_cert_tree_ = tls::create_cert_lookup_tree();
|
||||||
|
auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context(
|
||||||
|
quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get()
|
||||||
|
# ifdef HAVE_NEVERBLEED
|
||||||
|
,
|
||||||
|
nb_
|
||||||
|
# endif // HAVE_NEVERBLEED
|
||||||
|
);
|
||||||
|
|
||||||
auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
|
auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context(
|
||||||
# ifdef HAVE_NEVERBLEED
|
# ifdef HAVE_NEVERBLEED
|
||||||
nb_
|
nb_
|
||||||
|
@ -268,6 +305,7 @@ int ConnectionHandler::create_worker_thread(size_t num) {
|
||||||
|
|
||||||
if (cl_ssl_ctx) {
|
if (cl_ssl_ctx) {
|
||||||
all_ssl_ctx_.push_back(cl_ssl_ctx);
|
all_ssl_ctx_.push_back(cl_ssl_ctx);
|
||||||
|
quic_all_ssl_ctx_.push_back(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto config = get_config();
|
auto config = get_config();
|
||||||
|
@ -291,6 +329,7 @@ int ConnectionHandler::create_worker_thread(size_t num) {
|
||||||
tlsconf.cacert, memcachedconf.cert_file,
|
tlsconf.cacert, memcachedconf.cert_file,
|
||||||
memcachedconf.private_key_file, nullptr);
|
memcachedconf.private_key_file, nullptr);
|
||||||
all_ssl_ctx_.push_back(session_cache_ssl_ctx);
|
all_ssl_ctx_.push_back(session_cache_ssl_ctx);
|
||||||
|
quic_all_ssl_ctx_.push_back(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,12 +338,17 @@ int ConnectionHandler::create_worker_thread(size_t num) {
|
||||||
|
|
||||||
auto worker = std::make_unique<Worker>(
|
auto worker = std::make_unique<Worker>(
|
||||||
loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
|
loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
|
||||||
ticket_keys_, this, config->conn.downstream);
|
quic_sv_ssl_ctx, quic_cert_tree_.get(), ticket_keys_, this,
|
||||||
|
config->conn.downstream);
|
||||||
# ifdef HAVE_MRUBY
|
# ifdef HAVE_MRUBY
|
||||||
if (worker->create_mruby_context() != 0) {
|
if (worker->create_mruby_context() != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
# endif // HAVE_MRUBY
|
# endif // HAVE_MRUBY
|
||||||
|
if ((!apiconf.enabled || i != 0) &&
|
||||||
|
worker->setup_quic_server_socket() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
workers_.push_back(std::move(worker));
|
workers_.push_back(std::move(worker));
|
||||||
worker_loops_.push_back(loop);
|
worker_loops_.push_back(loop);
|
||||||
|
@ -602,6 +646,7 @@ void ConnectionHandler::handle_ocsp_complete() {
|
||||||
ev_child_stop(loop_, &ocsp_.chldev);
|
ev_child_stop(loop_, &ocsp_.chldev);
|
||||||
|
|
||||||
assert(ocsp_.next < all_ssl_ctx_.size());
|
assert(ocsp_.next < all_ssl_ctx_.size());
|
||||||
|
assert(all_ssl_ctx_.size() == quic_all_ssl_ctx_.size());
|
||||||
|
|
||||||
auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
|
auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
|
||||||
auto tls_ctx_data =
|
auto tls_ctx_data =
|
||||||
|
@ -629,6 +674,29 @@ void ConnectionHandler::handle_ocsp_complete() {
|
||||||
if (tlsconf.ocsp.no_verify ||
|
if (tlsconf.ocsp.no_verify ||
|
||||||
tls::verify_ocsp_response(ssl_ctx, ocsp_.resp.data(),
|
tls::verify_ocsp_response(ssl_ctx, ocsp_.resp.data(),
|
||||||
ocsp_.resp.size()) == 0) {
|
ocsp_.resp.size()) == 0) {
|
||||||
|
// We have list of SSL_CTX with the same certificate in
|
||||||
|
// quic_all_ssl_ctx_ as well. Some SSL_CTXs are missing there in
|
||||||
|
// that case we get nullptr.
|
||||||
|
auto quic_ssl_ctx = quic_all_ssl_ctx_[ocsp_.next];
|
||||||
|
if (quic_ssl_ctx) {
|
||||||
|
auto quic_tls_ctx_data = static_cast<tls::TLSContextData *>(
|
||||||
|
SSL_CTX_get_app_data(quic_ssl_ctx));
|
||||||
|
#ifndef OPENSSL_IS_BORINGSSL
|
||||||
|
# ifdef HAVE_ATOMIC_STD_SHARED_PTR
|
||||||
|
std::atomic_store_explicit(
|
||||||
|
&quic_tls_ctx_data->ocsp_data,
|
||||||
|
std::make_shared<std::vector<uint8_t>>(ocsp_.resp),
|
||||||
|
std::memory_order_release);
|
||||||
|
# else // !HAVE_ATOMIC_STD_SHARED_PTR
|
||||||
|
std::lock_guard<std::mutex> g(quic_tls_ctx_data->mu);
|
||||||
|
quic_tls_ctx_data->ocsp_data =
|
||||||
|
std::make_shared<std::vector<uint8_t>>(ocsp_.resp);
|
||||||
|
# endif // !HAVE_ATOMIC_STD_SHARED_PTR
|
||||||
|
#else // OPENSSL_IS_BORINGSSL
|
||||||
|
SSL_CTX_set_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size());
|
||||||
|
#endif // OPENSSL_IS_BORINGSSL
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef OPENSSL_IS_BORINGSSL
|
#ifndef OPENSSL_IS_BORINGSSL
|
||||||
# ifdef HAVE_ATOMIC_STD_SHARED_PTR
|
# ifdef HAVE_ATOMIC_STD_SHARED_PTR
|
||||||
std::atomic_store_explicit(
|
std::atomic_store_explicit(
|
||||||
|
@ -809,6 +877,7 @@ SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() {
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
all_ssl_ctx_.push_back(ssl_ctx);
|
all_ssl_ctx_.push_back(ssl_ctx);
|
||||||
|
quic_all_ssl_ctx_.push_back(nullptr);
|
||||||
|
|
||||||
return ssl_ctx;
|
return ssl_ctx;
|
||||||
}
|
}
|
||||||
|
@ -871,6 +940,11 @@ ConnectionHandler::get_indexed_ssl_ctx(size_t idx) const {
|
||||||
return indexed_ssl_ctx_[idx];
|
return indexed_ssl_ctx_[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<SSL_CTX *> &
|
||||||
|
ConnectionHandler::get_quic_indexed_ssl_ctx(size_t idx) const {
|
||||||
|
return quic_indexed_ssl_ctx_[idx];
|
||||||
|
}
|
||||||
|
|
||||||
void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) {
|
void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) {
|
||||||
enable_acceptor_on_ocsp_completion_ = f;
|
enable_acceptor_on_ocsp_completion_ = f;
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,7 @@ public:
|
||||||
SSL_CTX *get_ssl_ctx(size_t idx) const;
|
SSL_CTX *get_ssl_ctx(size_t idx) const;
|
||||||
|
|
||||||
const std::vector<SSL_CTX *> &get_indexed_ssl_ctx(size_t idx) const;
|
const std::vector<SSL_CTX *> &get_indexed_ssl_ctx(size_t idx) const;
|
||||||
|
const std::vector<SSL_CTX *> &get_quic_indexed_ssl_ctx(size_t idx) const;
|
||||||
|
|
||||||
#ifdef HAVE_NEVERBLEED
|
#ifdef HAVE_NEVERBLEED
|
||||||
void set_neverbleed(neverbleed_t *nb);
|
void set_neverbleed(neverbleed_t *nb);
|
||||||
|
@ -187,6 +188,8 @@ private:
|
||||||
// selection among them are performed by hostname presented by SNI,
|
// selection among them are performed by hostname presented by SNI,
|
||||||
// and signature algorithm presented by client.
|
// and signature algorithm presented by client.
|
||||||
std::vector<std::vector<SSL_CTX *>> indexed_ssl_ctx_;
|
std::vector<std::vector<SSL_CTX *>> indexed_ssl_ctx_;
|
||||||
|
std::vector<SSL_CTX *> quic_all_ssl_ctx_;
|
||||||
|
std::vector<std::vector<SSL_CTX *>> quic_indexed_ssl_ctx_;
|
||||||
OCSPUpdateContext ocsp_;
|
OCSPUpdateContext ocsp_;
|
||||||
std::mt19937 &gen_;
|
std::mt19937 &gen_;
|
||||||
// ev_loop for each worker
|
// ev_loop for each worker
|
||||||
|
@ -203,6 +206,7 @@ private:
|
||||||
// Otherwise, nullptr and workers_ has instances of Worker instead.
|
// Otherwise, nullptr and workers_ has instances of Worker instead.
|
||||||
std::unique_ptr<Worker> single_worker_;
|
std::unique_ptr<Worker> single_worker_;
|
||||||
std::unique_ptr<tls::CertLookupTree> cert_tree_;
|
std::unique_ptr<tls::CertLookupTree> cert_tree_;
|
||||||
|
std::unique_ptr<tls::CertLookupTree> quic_cert_tree_;
|
||||||
std::unique_ptr<MemcachedDispatcher> tls_ticket_key_memcached_dispatcher_;
|
std::unique_ptr<MemcachedDispatcher> tls_ticket_key_memcached_dispatcher_;
|
||||||
// Current TLS session ticket keys. Note that TLS connection does
|
// Current TLS session ticket keys. Note that TLS connection does
|
||||||
// not refer to this field directly. They use TicketKeys object in
|
// not refer to this field directly. They use TicketKeys object in
|
||||||
|
|
|
@ -113,7 +113,7 @@ void downstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
|
||||||
// upstream could be nullptr for unittests
|
// upstream could be nullptr for unittests
|
||||||
Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
|
Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
|
||||||
int32_t stream_id)
|
int64_t stream_id)
|
||||||
: dlnext(nullptr),
|
: dlnext(nullptr),
|
||||||
dlprev(nullptr),
|
dlprev(nullptr),
|
||||||
response_sent_body_length(0),
|
response_sent_body_length(0),
|
||||||
|
@ -145,7 +145,8 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
|
||||||
accesslog_written_(false),
|
accesslog_written_(false),
|
||||||
new_affinity_cookie_(false),
|
new_affinity_cookie_(false),
|
||||||
blocked_request_data_eof_(false),
|
blocked_request_data_eof_(false),
|
||||||
expect_100_continue_(false) {
|
expect_100_continue_(false),
|
||||||
|
stop_reading_(false) {
|
||||||
|
|
||||||
auto &timeoutconf = get_config()->http2.timeout;
|
auto &timeoutconf = get_config()->http2.timeout;
|
||||||
|
|
||||||
|
@ -164,6 +165,7 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
|
||||||
downstream_wtimer_.data = this;
|
downstream_wtimer_.data = this;
|
||||||
|
|
||||||
rcbufs_.reserve(32);
|
rcbufs_.reserve(32);
|
||||||
|
rcbufs3_.reserve(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
Downstream::~Downstream() {
|
Downstream::~Downstream() {
|
||||||
|
@ -203,6 +205,10 @@ Downstream::~Downstream() {
|
||||||
// explicitly.
|
// explicitly.
|
||||||
dconn_.reset();
|
dconn_.reset();
|
||||||
|
|
||||||
|
for (auto rcbuf : rcbufs3_) {
|
||||||
|
nghttp3_rcbuf_decref(rcbuf);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto rcbuf : rcbufs_) {
|
for (auto rcbuf : rcbufs_) {
|
||||||
nghttp2_rcbuf_decref(rcbuf);
|
nghttp2_rcbuf_decref(rcbuf);
|
||||||
}
|
}
|
||||||
|
@ -605,9 +611,9 @@ void Downstream::reset_upstream(Upstream *upstream) {
|
||||||
|
|
||||||
Upstream *Downstream::get_upstream() const { return upstream_; }
|
Upstream *Downstream::get_upstream() const { return upstream_; }
|
||||||
|
|
||||||
void Downstream::set_stream_id(int32_t stream_id) { stream_id_ = stream_id; }
|
void Downstream::set_stream_id(int64_t stream_id) { stream_id_ = stream_id; }
|
||||||
|
|
||||||
int32_t Downstream::get_stream_id() const { return stream_id_; }
|
int64_t Downstream::get_stream_id() const { return stream_id_; }
|
||||||
|
|
||||||
void Downstream::set_request_state(DownstreamState state) {
|
void Downstream::set_request_state(DownstreamState state) {
|
||||||
request_state_ = state;
|
request_state_ = state;
|
||||||
|
@ -904,11 +910,11 @@ StringRef Downstream::get_http2_settings() const {
|
||||||
return http2_settings->value;
|
return http2_settings->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::set_downstream_stream_id(int32_t stream_id) {
|
void Downstream::set_downstream_stream_id(int64_t stream_id) {
|
||||||
downstream_stream_id_ = stream_id;
|
downstream_stream_id_ = stream_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t Downstream::get_downstream_stream_id() const {
|
int64_t Downstream::get_downstream_stream_id() const {
|
||||||
return downstream_stream_id_;
|
return downstream_stream_id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1115,11 +1121,11 @@ DefaultMemchunks Downstream::pop_response_buf() {
|
||||||
return std::move(response_buf_);
|
return std::move(response_buf_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::set_assoc_stream_id(int32_t stream_id) {
|
void Downstream::set_assoc_stream_id(int64_t stream_id) {
|
||||||
assoc_stream_id_ = stream_id;
|
assoc_stream_id_ = stream_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; }
|
int64_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; }
|
||||||
|
|
||||||
BlockAllocator &Downstream::get_block_allocator() { return balloc_; }
|
BlockAllocator &Downstream::get_block_allocator() { return balloc_; }
|
||||||
|
|
||||||
|
@ -1128,6 +1134,11 @@ void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) {
|
||||||
rcbufs_.push_back(rcbuf);
|
rcbufs_.push_back(rcbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Downstream::add_rcbuf(nghttp3_rcbuf *rcbuf) {
|
||||||
|
nghttp3_rcbuf_incref(rcbuf);
|
||||||
|
rcbufs3_.push_back(rcbuf);
|
||||||
|
}
|
||||||
|
|
||||||
void Downstream::set_downstream_addr_group(
|
void Downstream::set_downstream_addr_group(
|
||||||
const std::shared_ptr<DownstreamAddrGroup> &group) {
|
const std::shared_ptr<DownstreamAddrGroup> &group) {
|
||||||
group_ = group;
|
group_ = group;
|
||||||
|
@ -1169,4 +1180,8 @@ bool Downstream::get_expect_100_continue() const {
|
||||||
return expect_100_continue_;
|
return expect_100_continue_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Downstream::get_stop_reading() const { return stop_reading_; }
|
||||||
|
|
||||||
|
void Downstream::set_stop_reading(bool f) { stop_reading_ = f; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -38,6 +38,8 @@
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
#include <nghttp3/nghttp3.h>
|
||||||
|
|
||||||
#include "llhttp.h"
|
#include "llhttp.h"
|
||||||
|
|
||||||
#include "shrpx_io_control.h"
|
#include "shrpx_io_control.h"
|
||||||
|
@ -319,20 +321,20 @@ enum class DispatchState {
|
||||||
|
|
||||||
class Downstream {
|
class Downstream {
|
||||||
public:
|
public:
|
||||||
Downstream(Upstream *upstream, MemchunkPool *mcpool, int32_t stream_id);
|
Downstream(Upstream *upstream, MemchunkPool *mcpool, int64_t stream_id);
|
||||||
~Downstream();
|
~Downstream();
|
||||||
void reset_upstream(Upstream *upstream);
|
void reset_upstream(Upstream *upstream);
|
||||||
Upstream *get_upstream() const;
|
Upstream *get_upstream() const;
|
||||||
void set_stream_id(int32_t stream_id);
|
void set_stream_id(int64_t stream_id);
|
||||||
int32_t get_stream_id() const;
|
int64_t get_stream_id() const;
|
||||||
void set_assoc_stream_id(int32_t stream_id);
|
void set_assoc_stream_id(int64_t stream_id);
|
||||||
int32_t get_assoc_stream_id() const;
|
int64_t get_assoc_stream_id() const;
|
||||||
void pause_read(IOCtrlReason reason);
|
void pause_read(IOCtrlReason reason);
|
||||||
int resume_read(IOCtrlReason reason, size_t consumed);
|
int resume_read(IOCtrlReason reason, size_t consumed);
|
||||||
void force_resume_read();
|
void force_resume_read();
|
||||||
// Set stream ID for downstream HTTP2 connection.
|
// Set stream ID for downstream HTTP2 connection.
|
||||||
void set_downstream_stream_id(int32_t stream_id);
|
void set_downstream_stream_id(int64_t stream_id);
|
||||||
int32_t get_downstream_stream_id() const;
|
int64_t get_downstream_stream_id() const;
|
||||||
|
|
||||||
int attach_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
|
int attach_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
|
||||||
void detach_downstream_connection();
|
void detach_downstream_connection();
|
||||||
|
@ -488,6 +490,7 @@ public:
|
||||||
BlockAllocator &get_block_allocator();
|
BlockAllocator &get_block_allocator();
|
||||||
|
|
||||||
void add_rcbuf(nghttp2_rcbuf *rcbuf);
|
void add_rcbuf(nghttp2_rcbuf *rcbuf);
|
||||||
|
void add_rcbuf(nghttp3_rcbuf *rcbuf);
|
||||||
|
|
||||||
void
|
void
|
||||||
set_downstream_addr_group(const std::shared_ptr<DownstreamAddrGroup> &group);
|
set_downstream_addr_group(const std::shared_ptr<DownstreamAddrGroup> &group);
|
||||||
|
@ -513,6 +516,9 @@ public:
|
||||||
|
|
||||||
bool get_expect_100_continue() const;
|
bool get_expect_100_continue() const;
|
||||||
|
|
||||||
|
bool get_stop_reading() const;
|
||||||
|
void set_stop_reading(bool f);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
EVENT_ERROR = 0x1,
|
EVENT_ERROR = 0x1,
|
||||||
EVENT_TIMEOUT = 0x2,
|
EVENT_TIMEOUT = 0x2,
|
||||||
|
@ -527,6 +533,7 @@ private:
|
||||||
BlockAllocator balloc_;
|
BlockAllocator balloc_;
|
||||||
|
|
||||||
std::vector<nghttp2_rcbuf *> rcbufs_;
|
std::vector<nghttp2_rcbuf *> rcbufs_;
|
||||||
|
std::vector<nghttp3_rcbuf *> rcbufs3_;
|
||||||
|
|
||||||
Request req_;
|
Request req_;
|
||||||
Response resp_;
|
Response resp_;
|
||||||
|
@ -566,12 +573,12 @@ private:
|
||||||
// How many times we tried in backend connection
|
// How many times we tried in backend connection
|
||||||
size_t num_retry_;
|
size_t num_retry_;
|
||||||
// The stream ID in frontend connection
|
// The stream ID in frontend connection
|
||||||
int32_t stream_id_;
|
int64_t stream_id_;
|
||||||
// The associated stream ID in frontend connection if this is pushed
|
// The associated stream ID in frontend connection if this is pushed
|
||||||
// stream.
|
// stream.
|
||||||
int32_t assoc_stream_id_;
|
int64_t assoc_stream_id_;
|
||||||
// stream ID in backend connection
|
// stream ID in backend connection
|
||||||
int32_t downstream_stream_id_;
|
int64_t downstream_stream_id_;
|
||||||
// RST_STREAM error_code from downstream HTTP2 connection
|
// RST_STREAM error_code from downstream HTTP2 connection
|
||||||
uint32_t response_rst_stream_error_code_;
|
uint32_t response_rst_stream_error_code_;
|
||||||
// An affinity cookie value.
|
// An affinity cookie value.
|
||||||
|
@ -606,6 +613,7 @@ private:
|
||||||
bool blocked_request_data_eof_;
|
bool blocked_request_data_eof_;
|
||||||
// true if request contains "expect: 100-continue" header field.
|
// true if request contains "expect: 100-continue" header field.
|
||||||
bool expect_100_continue_;
|
bool expect_100_continue_;
|
||||||
|
bool stop_reading_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_HTTP3_UPSTREAM_H
|
||||||
|
#define SHRPX_HTTP3_UPSTREAM_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
#include <nghttp3/nghttp3.h>
|
||||||
|
|
||||||
|
#include "shrpx_upstream.h"
|
||||||
|
#include "shrpx_downstream_queue.h"
|
||||||
|
#include "shrpx_mruby.h"
|
||||||
|
#include "quic.h"
|
||||||
|
#include "network.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
struct UpstreamAddr;
|
||||||
|
|
||||||
|
class Http3Upstream : public Upstream {
|
||||||
|
public:
|
||||||
|
Http3Upstream(ClientHandler *handler);
|
||||||
|
virtual ~Http3Upstream();
|
||||||
|
|
||||||
|
virtual int on_read();
|
||||||
|
virtual int on_write();
|
||||||
|
virtual int on_timeout(Downstream *downstream);
|
||||||
|
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||||
|
unsigned int status_code);
|
||||||
|
virtual int
|
||||||
|
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
|
||||||
|
virtual int downstream_read(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_write(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||||
|
virtual ClientHandler *get_client_handler() const;
|
||||||
|
|
||||||
|
virtual int on_downstream_header_complete(Downstream *downstream);
|
||||||
|
virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
|
||||||
|
size_t len, bool flush);
|
||||||
|
virtual int on_downstream_body_complete(Downstream *downstream);
|
||||||
|
|
||||||
|
virtual void on_handler_delete();
|
||||||
|
virtual int on_downstream_reset(Downstream *downstream, bool no_retry);
|
||||||
|
|
||||||
|
virtual void pause_read(IOCtrlReason reason);
|
||||||
|
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||||
|
size_t consumed);
|
||||||
|
virtual int send_reply(Downstream *downstream, const uint8_t *body,
|
||||||
|
size_t bodylen);
|
||||||
|
|
||||||
|
virtual int initiate_push(Downstream *downstream, const StringRef &uri);
|
||||||
|
|
||||||
|
virtual int response_riovec(struct iovec *iov, int iovcnt) const;
|
||||||
|
virtual void response_drain(size_t n);
|
||||||
|
virtual bool response_empty() const;
|
||||||
|
|
||||||
|
virtual Downstream *on_downstream_push_promise(Downstream *downstream,
|
||||||
|
int32_t promised_stream_id);
|
||||||
|
virtual int
|
||||||
|
on_downstream_push_promise_complete(Downstream *downstream,
|
||||||
|
Downstream *promised_downstream);
|
||||||
|
virtual bool push_enabled() const;
|
||||||
|
virtual void cancel_premature_downstream(Downstream *promised_downstream);
|
||||||
|
|
||||||
|
int init(const UpstreamAddr *faddr, const Address &remote_addr,
|
||||||
|
const Address &local_addr, const ngtcp2_pkt_hd &initial_hd);
|
||||||
|
|
||||||
|
int on_read(const UpstreamAddr *faddr, const Address &remote_addr,
|
||||||
|
const Address &local_addr, const uint8_t *data, size_t datalen);
|
||||||
|
|
||||||
|
int write_streams();
|
||||||
|
|
||||||
|
int on_rx_secret(ngtcp2_crypto_level level, const uint8_t *secret,
|
||||||
|
size_t secretlen);
|
||||||
|
int on_tx_secret(ngtcp2_crypto_level level, const uint8_t *secret,
|
||||||
|
size_t secretlen);
|
||||||
|
|
||||||
|
int add_crypto_data(ngtcp2_crypto_level level, const uint8_t *data,
|
||||||
|
size_t datalen);
|
||||||
|
|
||||||
|
void set_tls_alert(uint8_t alert);
|
||||||
|
|
||||||
|
int handle_error();
|
||||||
|
|
||||||
|
int handle_expiry();
|
||||||
|
void reset_idle_timer();
|
||||||
|
void reset_timer();
|
||||||
|
|
||||||
|
int setup_httpconn();
|
||||||
|
void add_pending_downstream(std::unique_ptr<Downstream> downstream);
|
||||||
|
int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
|
||||||
|
size_t datalen);
|
||||||
|
int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
|
||||||
|
int extend_max_stream_data(int64_t stream_id);
|
||||||
|
void extend_max_remote_streams_bidi(uint64_t max_streams);
|
||||||
|
int error_reply(Downstream *downstream, unsigned int status_code);
|
||||||
|
void http_begin_request_headers(int64_t stream_id);
|
||||||
|
int http_recv_request_header(Downstream *downstream, int32_t token,
|
||||||
|
nghttp3_rcbuf *name, nghttp3_rcbuf *value,
|
||||||
|
uint8_t flags);
|
||||||
|
int http_end_request_headers(Downstream *downstream);
|
||||||
|
int http_end_stream(Downstream *downstream);
|
||||||
|
void start_downstream(Downstream *downstream);
|
||||||
|
void initiate_downstream(Downstream *downstream);
|
||||||
|
int shutdown_stream(Downstream *downstream, uint64_t app_error_code);
|
||||||
|
int shutdown_stream_read(int64_t stream_id, uint64_t app_error_code);
|
||||||
|
int redirect_to_https(Downstream *downstream);
|
||||||
|
int http_stream_close(Downstream *downstream, uint64_t app_error_code);
|
||||||
|
void consume(int64_t stream_id, size_t nconsumed);
|
||||||
|
void remove_downstream(Downstream *downstream);
|
||||||
|
int stream_close(int64_t stream_id, uint64_t app_error_code);
|
||||||
|
void log_response_headers(Downstream *downstream,
|
||||||
|
const std::vector<nghttp3_nv> &nva) const;
|
||||||
|
int http_acked_stream_data(Downstream *downstream, size_t datalen);
|
||||||
|
int http_shutdown_stream_read(int64_t stream_id);
|
||||||
|
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_recv_data(Downstream *downstream, const uint8_t *data,
|
||||||
|
size_t datalen);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ClientHandler *handler_;
|
||||||
|
ev_timer timer_;
|
||||||
|
ev_timer idle_timer_;
|
||||||
|
ngtcp2_cid initial_client_dcid_;
|
||||||
|
ngtcp2_conn *conn_;
|
||||||
|
quic::Error last_error_;
|
||||||
|
uint8_t tls_alert_;
|
||||||
|
nghttp3_conn *httpconn_;
|
||||||
|
DownstreamQueue downstream_queue_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_HTTP3_UPSTREAM_H
|
|
@ -0,0 +1,309 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_quic.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/udp.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2_crypto.h>
|
||||||
|
|
||||||
|
#include <nghttp3/nghttp3.h>
|
||||||
|
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
|
#include "shrpx_config.h"
|
||||||
|
#include "shrpx_log.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "xsi_strerror.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
ngtcp2_tstamp quic_timestamp() {
|
||||||
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch())
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int create_quic_server_socket(UpstreamAddr &faddr) {
|
||||||
|
std::array<char, STRERROR_BUFSIZE> errbuf;
|
||||||
|
int fd = -1;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
auto service = util::utos(faddr.port);
|
||||||
|
addrinfo hints{};
|
||||||
|
hints.ai_family = faddr.family;
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
#ifdef AI_ADDRCONFIG
|
||||||
|
hints.ai_flags |= AI_ADDRCONFIG;
|
||||||
|
#endif // AI_ADDRCONFIG
|
||||||
|
|
||||||
|
auto node =
|
||||||
|
faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str();
|
||||||
|
|
||||||
|
addrinfo *res, *rp;
|
||||||
|
rv = getaddrinfo(node, service.c_str(), &hints, &res);
|
||||||
|
#ifdef AI_ADDRCONFIG
|
||||||
|
if (rv != 0) {
|
||||||
|
// Retry without AI_ADDRCONFIG
|
||||||
|
hints.ai_flags &= ~AI_ADDRCONFIG;
|
||||||
|
rv = getaddrinfo(node, service.c_str(), &hints, &res);
|
||||||
|
}
|
||||||
|
#endif // AI_ADDRCONFIG
|
||||||
|
if (rv != 0) {
|
||||||
|
LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6")
|
||||||
|
<< " address for " << faddr.host << ", port " << faddr.port
|
||||||
|
<< ": " << gai_strerror(rv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res_d = defer(freeaddrinfo, res);
|
||||||
|
|
||||||
|
std::array<char, NI_MAXHOST> host;
|
||||||
|
|
||||||
|
for (rp = res; rp; rp = rp->ai_next) {
|
||||||
|
rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(),
|
||||||
|
nullptr, 0, NI_NUMERICHOST);
|
||||||
|
if (rv != 0) {
|
||||||
|
LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SOCK_NONBLOCK
|
||||||
|
fd = socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC,
|
||||||
|
rp->ai_protocol);
|
||||||
|
if (fd == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN) << "socket() syscall failed: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#else // !SOCK_NONBLOCK
|
||||||
|
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||||
|
if (fd == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN) << "socket() syscall failed: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
util::make_socket_nonblocking(fd);
|
||||||
|
util::make_socket_closeonexec(fd);
|
||||||
|
#endif // !SOCK_NONBLOCK
|
||||||
|
|
||||||
|
int val = 1;
|
||||||
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
|
||||||
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val,
|
||||||
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN) << "Failed to set SO_REUSEPORT option to listener socket: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faddr.family == AF_INET6) {
|
||||||
|
#ifdef IPV6_V6ONLY
|
||||||
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
|
||||||
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif // IPV6_V6ONLY
|
||||||
|
|
||||||
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
|
||||||
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN)
|
||||||
|
<< "Failed to set IPV6_RECVPKTINFO option to listener socket: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
|
||||||
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN) << "Failed to set IP_PKTINFO option to listener socket: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Enable ECN
|
||||||
|
|
||||||
|
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
|
||||||
|
auto error = errno;
|
||||||
|
LOG(WARN) << "bind() syscall failed: "
|
||||||
|
<< xsi_strerror(error, errbuf.data(), errbuf.size());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rp) {
|
||||||
|
LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6")
|
||||||
|
<< " socket failed";
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
faddr.fd = fd;
|
||||||
|
faddr.hostport = util::make_http_hostport(mod_config()->balloc,
|
||||||
|
StringRef{host.data()}, faddr.port);
|
||||||
|
|
||||||
|
LOG(NOTICE) << "Listening on " << faddr.hostport << ", quic";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
|
||||||
|
size_t remote_salen, const sockaddr *local_sa,
|
||||||
|
size_t local_salen, const uint8_t *data, size_t datalen,
|
||||||
|
size_t gso_size) {
|
||||||
|
iovec msg_iov = {const_cast<uint8_t *>(data), datalen};
|
||||||
|
msghdr msg{};
|
||||||
|
msg.msg_name = const_cast<sockaddr *>(remote_sa);
|
||||||
|
msg.msg_namelen = remote_salen;
|
||||||
|
msg.msg_iov = &msg_iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
|
||||||
|
|
||||||
|
memset(msg_ctrl, 0, sizeof(msg_ctrl));
|
||||||
|
|
||||||
|
msg.msg_control = msg_ctrl;
|
||||||
|
msg.msg_controllen = sizeof(msg_ctrl);
|
||||||
|
|
||||||
|
size_t controllen = 0;
|
||||||
|
|
||||||
|
auto cm = CMSG_FIRSTHDR(&msg);
|
||||||
|
|
||||||
|
switch (local_sa->sa_family) {
|
||||||
|
case AF_INET: {
|
||||||
|
controllen += CMSG_SPACE(sizeof(in_pktinfo));
|
||||||
|
cm->cmsg_level = IPPROTO_IP;
|
||||||
|
cm->cmsg_type = IP_PKTINFO;
|
||||||
|
cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
|
||||||
|
auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cm));
|
||||||
|
memset(pktinfo, 0, sizeof(in_pktinfo));
|
||||||
|
auto addrin =
|
||||||
|
reinterpret_cast<sockaddr_in *>(const_cast<sockaddr *>(local_sa));
|
||||||
|
pktinfo->ipi_spec_dst = addrin->sin_addr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AF_INET6: {
|
||||||
|
controllen += CMSG_SPACE(sizeof(in6_pktinfo));
|
||||||
|
cm->cmsg_level = IPPROTO_IPV6;
|
||||||
|
cm->cmsg_type = IPV6_PKTINFO;
|
||||||
|
cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
|
||||||
|
auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cm));
|
||||||
|
memset(pktinfo, 0, sizeof(in6_pktinfo));
|
||||||
|
auto addrin =
|
||||||
|
reinterpret_cast<sockaddr_in6 *>(const_cast<sockaddr *>(local_sa));
|
||||||
|
pktinfo->ipi6_addr = addrin->sin6_addr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gso_size && datalen > gso_size) {
|
||||||
|
controllen += CMSG_SPACE(sizeof(uint16_t));
|
||||||
|
cm = CMSG_NXTHDR(&msg, cm);
|
||||||
|
cm->cmsg_level = SOL_UDP;
|
||||||
|
cm->cmsg_type = UDP_SEGMENT;
|
||||||
|
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
|
||||||
|
*(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.msg_controllen = controllen;
|
||||||
|
|
||||||
|
ssize_t nwrite;
|
||||||
|
|
||||||
|
do {
|
||||||
|
nwrite = sendmsg(faddr->fd, &msg, 0);
|
||||||
|
} while (nwrite == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (nwrite == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "QUIC sent packet: local="
|
||||||
|
<< util::to_numeric_addr(local_sa, local_salen)
|
||||||
|
<< " remote=" << util::to_numeric_addr(remote_sa, remote_salen)
|
||||||
|
<< " " << nwrite << " bytes";
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen) {
|
||||||
|
if (RAND_bytes(cid->data, cidlen) != 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cid->datalen = cidlen;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid *cid,
|
||||||
|
const uint8_t *secret,
|
||||||
|
size_t secretlen) {
|
||||||
|
ngtcp2_crypto_md md;
|
||||||
|
ngtcp2_crypto_md_init(&md, const_cast<EVP_MD *>(EVP_sha256()));
|
||||||
|
|
||||||
|
if (ngtcp2_crypto_generate_stateless_reset_token(token, &md, secret,
|
||||||
|
secretlen, cid) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_QUIC_H
|
||||||
|
#define SHRPX_QUIC_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
struct UpstreamAddr;
|
||||||
|
|
||||||
|
constexpr size_t SHRPX_QUIC_SCIDLEN = 20;
|
||||||
|
constexpr size_t SHRPX_MAX_UDP_PAYLOAD_SIZE = 1280;
|
||||||
|
|
||||||
|
ngtcp2_tstamp quic_timestamp();
|
||||||
|
|
||||||
|
int create_quic_server_socket(UpstreamAddr &addr);
|
||||||
|
|
||||||
|
int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
|
||||||
|
size_t remote_salen, const sockaddr *local_sa,
|
||||||
|
size_t local_salen, 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_stateless_reset_token(uint8_t *token, const ngtcp2_cid *cid,
|
||||||
|
const uint8_t *secret,
|
||||||
|
size_t secretlen);
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_QUIC_H
|
|
@ -0,0 +1,284 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_quic_connection_handler.h"
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
|
||||||
|
#include "shrpx_worker.h"
|
||||||
|
#include "shrpx_client_handler.h"
|
||||||
|
#include "shrpx_log.h"
|
||||||
|
#include "shrpx_quic.h"
|
||||||
|
#include "shrpx_http3_upstream.h"
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
QUICConnectionHandler::QUICConnectionHandler(Worker *worker)
|
||||||
|
: worker_{worker} {}
|
||||||
|
|
||||||
|
QUICConnectionHandler::~QUICConnectionHandler() {}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string make_cid_key(const uint8_t *dcid, size_t dcidlen) {
|
||||||
|
return std::string{dcid, dcid + dcidlen};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string make_cid_key(const ngtcp2_cid *cid) {
|
||||||
|
return make_cid_key(cid->data, cid->datalen);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
|
||||||
|
const Address &remote_addr,
|
||||||
|
const Address &local_addr,
|
||||||
|
const uint8_t *data, size_t datalen) {
|
||||||
|
int rv;
|
||||||
|
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) {
|
||||||
|
if (rv == 1) {
|
||||||
|
send_version_negotiation(faddr, version, scid, scidlen, dcid, dcidlen,
|
||||||
|
remote_addr, local_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dcid_key = make_cid_key(dcid, dcidlen);
|
||||||
|
|
||||||
|
ClientHandler *handler;
|
||||||
|
|
||||||
|
auto it = connections_.find(dcid_key);
|
||||||
|
if (it == std::end(connections_)) {
|
||||||
|
// new connection
|
||||||
|
|
||||||
|
ngtcp2_pkt_hd hd;
|
||||||
|
|
||||||
|
switch (ngtcp2_accept(&hd, data, datalen)) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case NGTCP2_ERR_RETRY:
|
||||||
|
// TODO Send retry
|
||||||
|
return 0;
|
||||||
|
case NGTCP2_ERR_VERSION_NEGOTIATION:
|
||||||
|
send_version_negotiation(faddr, version, scid, scidlen, dcid, dcidlen,
|
||||||
|
remote_addr, local_addr);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
// TODO Must be rate limited
|
||||||
|
send_stateless_reset(faddr, dcid, dcidlen, remote_addr, local_addr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = handle_new_connection(faddr, remote_addr, local_addr, hd);
|
||||||
|
if (handler == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handler = (*it).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler->read_quic(faddr, remote_addr, local_addr, data, datalen) != 0) {
|
||||||
|
delete handler;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler->signal_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientHandler *QUICConnectionHandler::handle_new_connection(
|
||||||
|
const UpstreamAddr *faddr, const Address &remote_addr,
|
||||||
|
const Address &local_addr, const ngtcp2_pkt_hd &hd) {
|
||||||
|
std::array<char, NI_MAXHOST> host;
|
||||||
|
std::array<char, NI_MAXSERV> service;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
rv = getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(),
|
||||||
|
host.size(), service.data(), service.size(),
|
||||||
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||||
|
if (rv != 0) {
|
||||||
|
LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ssl_ctx = worker_->get_quic_sv_ssl_ctx();
|
||||||
|
|
||||||
|
assert(ssl_ctx);
|
||||||
|
|
||||||
|
auto ssl = tls::create_ssl(ssl_ctx);
|
||||||
|
if (ssl == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(SSL_is_quic(ssl));
|
||||||
|
|
||||||
|
SSL_set_accept_state(ssl);
|
||||||
|
SSL_set_quic_early_data_enabled(ssl, 1);
|
||||||
|
|
||||||
|
// Disable TLS session ticket if we don't have working ticket
|
||||||
|
// keys.
|
||||||
|
if (!worker_->get_ticket_keys()) {
|
||||||
|
SSL_set_options(ssl, SSL_OP_NO_TICKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handler = std::make_unique<ClientHandler>(
|
||||||
|
worker_, faddr->fd, ssl, StringRef{host.data()},
|
||||||
|
StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr);
|
||||||
|
|
||||||
|
auto upstream = std::make_unique<Http3Upstream>(handler.get());
|
||||||
|
if (upstream->init(faddr, remote_addr, local_addr, hd) != 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler->setup_http3_upstream(std::move(upstream));
|
||||||
|
|
||||||
|
return handler.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
uint32_t generate_reserved_version(const Address &addr, uint32_t version) {
|
||||||
|
uint32_t h = 0x811C9DC5u;
|
||||||
|
const uint8_t *p = reinterpret_cast<const uint8_t *>(&addr.su.sa);
|
||||||
|
const uint8_t *ep = p + addr.len;
|
||||||
|
|
||||||
|
for (; p != ep; ++p) {
|
||||||
|
h ^= *p;
|
||||||
|
h *= 0x01000193u;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = htonl(version);
|
||||||
|
p = (const uint8_t *)&version;
|
||||||
|
ep = p + sizeof(version);
|
||||||
|
|
||||||
|
for (; p != ep; ++p) {
|
||||||
|
h ^= *p;
|
||||||
|
h *= 0x01000193u;
|
||||||
|
}
|
||||||
|
|
||||||
|
h &= 0xf0f0f0f0u;
|
||||||
|
h |= 0x0a0a0a0au;
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int QUICConnectionHandler::send_version_negotiation(
|
||||||
|
const UpstreamAddr *faddr, uint32_t version, const uint8_t *dcid,
|
||||||
|
size_t dcidlen, const uint8_t *scid, size_t scidlen,
|
||||||
|
const Address &remote_addr, const Address &local_addr) {
|
||||||
|
std::array<uint32_t, 2> sv;
|
||||||
|
|
||||||
|
sv[0] = generate_reserved_version(remote_addr, version);
|
||||||
|
sv[1] = NGTCP2_PROTO_VER_V1;
|
||||||
|
|
||||||
|
std::array<uint8_t, 1280> buf;
|
||||||
|
|
||||||
|
uint8_t rand_byte;
|
||||||
|
util::random_bytes(&rand_byte, &rand_byte + 1, worker_->get_randgen());
|
||||||
|
|
||||||
|
auto nwrite = ngtcp2_pkt_write_version_negotiation(
|
||||||
|
buf.data(), buf.size(), rand_byte, dcid, dcidlen, scid, scidlen,
|
||||||
|
sv.data(), sv.size());
|
||||||
|
if (nwrite < 0) {
|
||||||
|
LOG(ERROR) << "ngtcp2_pkt_write_version_negotiation: "
|
||||||
|
<< ngtcp2_strerror(nwrite);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
|
||||||
|
&local_addr.su.sa, local_addr.len, buf.data(), nwrite,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr,
|
||||||
|
const uint8_t *dcid,
|
||||||
|
size_t dcidlen,
|
||||||
|
const Address &remote_addr,
|
||||||
|
const Address &local_addr) {
|
||||||
|
int rv;
|
||||||
|
std::array<uint8_t, NGTCP2_STATELESS_RESET_TOKENLEN> token;
|
||||||
|
ngtcp2_cid cid;
|
||||||
|
|
||||||
|
ngtcp2_cid_init(&cid, dcid, dcidlen);
|
||||||
|
|
||||||
|
auto config = get_config();
|
||||||
|
auto &quicconf = config->quic;
|
||||||
|
auto &stateless_resetconf = quicconf.stateless_reset;
|
||||||
|
|
||||||
|
rv = generate_quic_stateless_reset_token(token.data(), &cid,
|
||||||
|
stateless_resetconf.secret.data(),
|
||||||
|
stateless_resetconf.secret.size());
|
||||||
|
if (rv != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, NGTCP2_MIN_STATELESS_RESET_RANDLEN> rand_bytes;
|
||||||
|
|
||||||
|
if (RAND_bytes(rand_bytes.data(), rand_bytes.size()) != 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, 1280> buf;
|
||||||
|
|
||||||
|
auto nwrite =
|
||||||
|
ngtcp2_pkt_write_stateless_reset(buf.data(), buf.size(), token.data(),
|
||||||
|
rand_bytes.data(), rand_bytes.size());
|
||||||
|
if (nwrite < 0) {
|
||||||
|
LOG(ERROR) << "ngtcp2_pkt_write_stateless_reset: "
|
||||||
|
<< ngtcp2_strerror(nwrite);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Send stateless_reset to remote="
|
||||||
|
<< util::to_numeric_addr(&remote_addr)
|
||||||
|
<< " dcid=" << util::format_hex(dcid, dcidlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len,
|
||||||
|
&local_addr.su.sa, local_addr.len, buf.data(), nwrite,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QUICConnectionHandler::add_connection_id(const ngtcp2_cid *cid,
|
||||||
|
ClientHandler *handler) {
|
||||||
|
auto key = make_cid_key(cid);
|
||||||
|
connections_.emplace(key, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QUICConnectionHandler::remove_connection_id(const ngtcp2_cid *cid) {
|
||||||
|
auto key = make_cid_key(cid);
|
||||||
|
connections_.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_QUIC_CONNECTION_HANDLER_H
|
||||||
|
#define SHRPX_QUIC_CONNECTION_HANDLER_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
|
||||||
|
#include "network.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
struct UpstreamAddr;
|
||||||
|
class ClientHandler;
|
||||||
|
class Worker;
|
||||||
|
|
||||||
|
class QUICConnectionHandler {
|
||||||
|
public:
|
||||||
|
QUICConnectionHandler(Worker *worker);
|
||||||
|
~QUICConnectionHandler();
|
||||||
|
int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr,
|
||||||
|
const Address &local_addr, const uint8_t *data,
|
||||||
|
size_t datalen);
|
||||||
|
int send_version_negotiation(const UpstreamAddr *faddr, uint32_t version,
|
||||||
|
const uint8_t *dcid, size_t dcidlen,
|
||||||
|
const uint8_t *scid, size_t scidlen,
|
||||||
|
const Address &remote_addr,
|
||||||
|
const Address &local_addr);
|
||||||
|
int send_stateless_reset(const UpstreamAddr *faddr, const uint8_t *dcid,
|
||||||
|
size_t dcidlen, const Address &remote_addr,
|
||||||
|
const Address &local_addr);
|
||||||
|
ClientHandler *handle_new_connection(const UpstreamAddr *faddr,
|
||||||
|
const Address &remote_addr,
|
||||||
|
const Address &local_addr,
|
||||||
|
const ngtcp2_pkt_hd &hd);
|
||||||
|
void add_connection_id(const ngtcp2_cid *cid, ClientHandler *handler);
|
||||||
|
void remove_connection_id(const ngtcp2_cid *cid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Worker *worker_;
|
||||||
|
std::unordered_map<std::string, ClientHandler *> connections_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_QUIC_CONNECTION_HANDLER_H
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_quic_listener.h"
|
||||||
|
#include "shrpx_worker.h"
|
||||||
|
#include "shrpx_config.h"
|
||||||
|
#include "shrpx_log.h"
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void readcb(struct ev_loop *loop, ev_io *w, int revent) {
|
||||||
|
auto l = static_cast<QUICListener *>(w->data);
|
||||||
|
l->on_read();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
QUICListener::QUICListener(const UpstreamAddr *faddr, Worker *worker)
|
||||||
|
: faddr_{faddr}, worker_{worker} {
|
||||||
|
ev_io_init(&rev_, readcb, faddr_->fd, EV_READ);
|
||||||
|
rev_.data = this;
|
||||||
|
ev_io_start(worker_->get_loop(), &rev_);
|
||||||
|
}
|
||||||
|
|
||||||
|
QUICListener::~QUICListener() {
|
||||||
|
ev_io_stop(worker_->get_loop(), &rev_);
|
||||||
|
close(faddr_->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QUICListener::on_read() {
|
||||||
|
sockaddr_union su;
|
||||||
|
std::array<uint8_t, 64_k> buf;
|
||||||
|
size_t pktcnt = 0;
|
||||||
|
iovec msg_iov{buf.data(), buf.size()};
|
||||||
|
|
||||||
|
msghdr msg{};
|
||||||
|
msg.msg_name = &su;
|
||||||
|
msg.msg_iov = &msg_iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
|
||||||
|
uint8_t msg_ctrl[CMSG_SPACE(sizeof(in6_pktinfo))];
|
||||||
|
msg.msg_control = msg_ctrl;
|
||||||
|
|
||||||
|
auto quic_conn_handler = worker_->get_quic_connection_handler();
|
||||||
|
|
||||||
|
for (; pktcnt < 10;) {
|
||||||
|
msg.msg_namelen = sizeof(su);
|
||||||
|
msg.msg_controllen = sizeof(msg_ctrl);
|
||||||
|
|
||||||
|
auto nread = recvmsg(faddr_->fd, &msg, 0);
|
||||||
|
if (nread == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++pktcnt;
|
||||||
|
|
||||||
|
Address local_addr{};
|
||||||
|
if (util::msghdr_get_local_addr(local_addr, &msg, su.storage.ss_family) !=
|
||||||
|
0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
util::set_port(local_addr, faddr_->port);
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "QUIC received packet: local="
|
||||||
|
<< util::to_numeric_addr(&local_addr)
|
||||||
|
<< " remote=" << util::to_numeric_addr(&su.sa, msg.msg_namelen)
|
||||||
|
<< " " << nread << " bytes";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Address remote_addr;
|
||||||
|
remote_addr.su = su;
|
||||||
|
remote_addr.len = msg.msg_namelen;
|
||||||
|
|
||||||
|
quic_conn_handler->handle_packet(faddr_, remote_addr, local_addr,
|
||||||
|
buf.data(), nread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_QUIC_LISTENER_H
|
||||||
|
#define SHRPX_QUIC_LISTENER_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
struct UpstreamAddr;
|
||||||
|
class Worker;
|
||||||
|
|
||||||
|
class QUICListener {
|
||||||
|
public:
|
||||||
|
QUICListener(const UpstreamAddr *faddr, Worker *worker);
|
||||||
|
~QUICListener();
|
||||||
|
void on_read();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const UpstreamAddr *faddr_;
|
||||||
|
Worker *worker_;
|
||||||
|
ev_io rev_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_QUIC_LISTENER_H
|
417
src/shrpx_tls.cc
417
src/shrpx_tls.cc
|
@ -51,6 +51,10 @@
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
#include <ngtcp2/ngtcp2.h>
|
||||||
|
#include <ngtcp2/ngtcp2_crypto.h>
|
||||||
|
#include <ngtcp2/ngtcp2_crypto_openssl.h>
|
||||||
|
|
||||||
#include "shrpx_log.h"
|
#include "shrpx_log.h"
|
||||||
#include "shrpx_client_handler.h"
|
#include "shrpx_client_handler.h"
|
||||||
#include "shrpx_config.h"
|
#include "shrpx_config.h"
|
||||||
|
@ -60,6 +64,7 @@
|
||||||
#include "shrpx_memcached_request.h"
|
#include "shrpx_memcached_request.h"
|
||||||
#include "shrpx_memcached_dispatcher.h"
|
#include "shrpx_memcached_dispatcher.h"
|
||||||
#include "shrpx_connection_handler.h"
|
#include "shrpx_connection_handler.h"
|
||||||
|
#include "shrpx_http3_upstream.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
@ -180,7 +185,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};
|
||||||
|
|
||||||
auto cert_tree = worker->get_cert_lookup_tree();
|
auto cert_tree = SSL_is_quic(ssl) ? worker->get_quic_cert_lookup_tree()
|
||||||
|
: worker->get_cert_lookup_tree();
|
||||||
|
|
||||||
auto idx = cert_tree->lookup(hostname);
|
auto idx = cert_tree->lookup(hostname);
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
|
@ -191,7 +197,9 @@ int servername_callback(SSL *ssl, int *al, void *arg) {
|
||||||
|
|
||||||
auto conn_handler = worker->get_connection_handler();
|
auto conn_handler = worker->get_connection_handler();
|
||||||
|
|
||||||
const auto &ssl_ctx_list = conn_handler->get_indexed_ssl_ctx(idx);
|
const auto &ssl_ctx_list = SSL_is_quic(ssl)
|
||||||
|
? conn_handler->get_quic_indexed_ssl_ctx(idx)
|
||||||
|
: conn_handler->get_indexed_ssl_ctx(idx);
|
||||||
assert(!ssl_ctx_list.empty());
|
assert(!ssl_ctx_list.empty());
|
||||||
|
|
||||||
#if !defined(OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && \
|
#if !defined(OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && \
|
||||||
|
@ -600,6 +608,32 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
|
||||||
} // namespace
|
} // namespace
|
||||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
namespace {
|
||||||
|
int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
|
||||||
|
unsigned char *outlen, const unsigned char *in,
|
||||||
|
unsigned int inlen, void *arg) {
|
||||||
|
for (auto p = in, end = in + inlen; p < end;) {
|
||||||
|
auto proto_id = p + 1;
|
||||||
|
auto proto_len = *p;
|
||||||
|
|
||||||
|
if (proto_id + proto_len <= end &&
|
||||||
|
util::streq_l("h3", StringRef{proto_id, proto_len})) {
|
||||||
|
|
||||||
|
*out = reinterpret_cast<const unsigned char *>(proto_id);
|
||||||
|
*outlen = proto_len;
|
||||||
|
|
||||||
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
p += 1 + proto_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SSL_TLSEXT_ERR_NOACK;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
|
||||||
#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
|
||||||
# ifndef TLSEXT_TYPE_signed_certificate_timestamp
|
# ifndef TLSEXT_TYPE_signed_certificate_timestamp
|
||||||
|
@ -1042,6 +1076,326 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
|
||||||
return ssl_ctx;
|
return ssl_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int quic_set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
|
||||||
|
const uint8_t *rx_secret,
|
||||||
|
const uint8_t *tx_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_openssl_from_ossl_encryption_level(ossl_level);
|
||||||
|
|
||||||
|
if (upstream->on_rx_secret(level, rx_secret, secretlen) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx_secret && upstream->on_tx_secret(level, tx_secret, secretlen) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_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_openssl_from_ossl_encryption_level(ossl_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, OSSL_ENCRYPTION_LEVEL ossl_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_encryption_secrets,
|
||||||
|
quic_add_handshake_data,
|
||||||
|
quic_flush_flight,
|
||||||
|
quic_send_alert,
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SSL_CTX *create_quic_ssl_context(const char *private_key_file,
|
||||||
|
const char *cert_file,
|
||||||
|
const std::vector<uint8_t> &sct_data
|
||||||
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
,
|
||||||
|
neverbleed_t *nb
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
) {
|
||||||
|
auto ssl_ctx = SSL_CTX_new(TLS_server_method());
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto ssl_opts =
|
||||||
|
(SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
|
||||||
|
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE |
|
||||||
|
SSL_OP_SINGLE_DH_USE |
|
||||||
|
SSL_OP_CIPHER_SERVER_PREFERENCE
|
||||||
|
#if OPENSSL_1_1_1_API
|
||||||
|
// The reason for disabling built-in anti-replay in OpenSSL is
|
||||||
|
// that it only works if client gets back to the same server.
|
||||||
|
// The freshness check described in
|
||||||
|
// https://tools.ietf.org/html/rfc8446#section-8.3 is still
|
||||||
|
// performed.
|
||||||
|
| SSL_OP_NO_ANTI_REPLAY
|
||||||
|
#endif // OPENSSL_1_1_1_API
|
||||||
|
;
|
||||||
|
|
||||||
|
auto config = mod_config();
|
||||||
|
auto &tlsconf = config->tls;
|
||||||
|
|
||||||
|
SSL_CTX_set_options(ssl_ctx, ssl_opts);
|
||||||
|
|
||||||
|
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||||
|
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||||
|
|
||||||
|
const unsigned char sid_ctx[] = "shrpx";
|
||||||
|
SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1);
|
||||||
|
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER);
|
||||||
|
|
||||||
|
if (!tlsconf.session_cache.memcached.host.empty()) {
|
||||||
|
SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_new_cb);
|
||||||
|
SSL_CTX_sess_set_get_cb(ssl_ctx, tls_session_get_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count());
|
||||||
|
|
||||||
|
if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers
|
||||||
|
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OPENSSL_1_1_1_API
|
||||||
|
if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers
|
||||||
|
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
#endif // OPENSSL_1_1_1_API
|
||||||
|
|
||||||
|
#ifndef OPENSSL_NO_EC
|
||||||
|
# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves
|
||||||
|
<< " failed";
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
# if !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API
|
||||||
|
// It looks like we need this function call for OpenSSL 1.0.2. This
|
||||||
|
// function was deprecated in OpenSSL 1.1.0 and BoringSSL.
|
||||||
|
SSL_CTX_set_ecdh_auto(ssl_ctx, 1);
|
||||||
|
# endif // !defined(OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API
|
||||||
|
# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L
|
||||||
|
// Use P-256, which is sufficiently secure at the time of this
|
||||||
|
// writing.
|
||||||
|
auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
||||||
|
if (ecdh == nullptr) {
|
||||||
|
LOG(FATAL) << "EC_KEY_new_by_curv_name failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
|
||||||
|
EC_KEY_free(ecdh);
|
||||||
|
# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L
|
||||||
|
#endif // OPENSSL_NO_EC
|
||||||
|
|
||||||
|
if (!tlsconf.dh_param_file.empty()) {
|
||||||
|
// Read DH parameters from file
|
||||||
|
auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "r");
|
||||||
|
if (bio == nullptr) {
|
||||||
|
LOG(FATAL) << "BIO_new_file() failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
|
||||||
|
if (dh == nullptr) {
|
||||||
|
LOG(FATAL) << "PEM_read_bio_DHparams() failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
SSL_CTX_set_tmp_dh(ssl_ctx, dh);
|
||||||
|
DH_free(dh);
|
||||||
|
BIO_free(bio);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||||
|
|
||||||
|
if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
|
||||||
|
LOG(WARN) << "Could not load system trusted ca certificates: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tlsconf.cacert.empty()) {
|
||||||
|
if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(),
|
||||||
|
nullptr) != 1) {
|
||||||
|
LOG(FATAL) << "Could not load trusted ca certificates from "
|
||||||
|
<< tlsconf.cacert << ": "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tlsconf.private_key_passwd.empty()) {
|
||||||
|
SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
|
||||||
|
SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_NEVERBLEED
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file,
|
||||||
|
SSL_FILETYPE_PEM) != 1) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
}
|
||||||
|
#else // HAVE_NEVERBLEED
|
||||||
|
std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
|
||||||
|
if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file,
|
||||||
|
errbuf.data()) != 1) {
|
||||||
|
LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data();
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
|
||||||
|
if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_use_certificate_file failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_check_private_key failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
if (tlsconf.client_verify.enabled) {
|
||||||
|
if (!tlsconf.client_verify.cacert.empty()) {
|
||||||
|
if (SSL_CTX_load_verify_locations(
|
||||||
|
ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) {
|
||||||
|
|
||||||
|
LOG(FATAL) << "Could not load trusted ca certificates from "
|
||||||
|
<< tlsconf.client_verify.cacert << ": "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
// It is heard that SSL_CTX_load_verify_locations() may leave
|
||||||
|
// error even though it returns success. See
|
||||||
|
// http://forum.nginx.org/read.php?29,242540
|
||||||
|
ERR_clear_error();
|
||||||
|
auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str());
|
||||||
|
if (!list) {
|
||||||
|
LOG(FATAL) << "Could not load ca certificates from "
|
||||||
|
<< tlsconf.client_verify.cacert << ": "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
SSL_CTX_set_client_CA_list(ssl_ctx, list);
|
||||||
|
}
|
||||||
|
SSL_CTX_set_verify(ssl_ctx,
|
||||||
|
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
|
||||||
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||||
|
verify_callback);
|
||||||
|
}
|
||||||
|
SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback);
|
||||||
|
SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb);
|
||||||
|
#ifndef OPENSSL_IS_BORINGSSL
|
||||||
|
SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb);
|
||||||
|
#endif // OPENSSL_IS_BORINGSSL
|
||||||
|
|
||||||
|
#ifdef OPENSSL_IS_BORINGSSL
|
||||||
|
SSL_CTX_set_early_data_enabled(ssl_ctx, 1);
|
||||||
|
#endif // OPENSSL_IS_BORINGSSL
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
// ALPN selection callback
|
||||||
|
SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr);
|
||||||
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
|
||||||
|
#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \
|
||||||
|
!defined(OPENSSL_IS_BORINGSSL)
|
||||||
|
// SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp)
|
||||||
|
// returns 1, which means OpenSSL internally handles it. But
|
||||||
|
// OpenSSL handles signed_certificate_timestamp extension specially,
|
||||||
|
// and it lets custom handler to process the extension.
|
||||||
|
if (!sct_data.empty()) {
|
||||||
|
# if OPENSSL_1_1_1_API
|
||||||
|
// It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is
|
||||||
|
// required here. sct_parse_cb is called without
|
||||||
|
// SSL_EXT_CLIENT_HELLO being set. But the passed context value
|
||||||
|
// is SSL_EXT_CLIENT_HELLO.
|
||||||
|
if (SSL_CTX_add_custom_ext(
|
||||||
|
ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp,
|
||||||
|
SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO |
|
||||||
|
SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION,
|
||||||
|
sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_add_custom_ext failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
# else // !OPENSSL_1_1_1_API
|
||||||
|
if (SSL_CTX_add_server_custom_ext(
|
||||||
|
ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp,
|
||||||
|
legacy_sct_add_cb, legacy_sct_free_cb, nullptr, legacy_sct_parse_cb,
|
||||||
|
nullptr) != 1) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_add_server_custom_ext failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
# endif // !OPENSSL_1_1_1_API
|
||||||
|
}
|
||||||
|
#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L &&
|
||||||
|
// !defined(OPENSSL_IS_BORINGSSL)
|
||||||
|
|
||||||
|
#if OPENSSL_1_1_1_API
|
||||||
|
if (SSL_CTX_set_max_early_data(ssl_ctx,
|
||||||
|
std::numeric_limits<uint32_t>::max()) != 1) {
|
||||||
|
LOG(FATAL) << "SSL_CTX_set_max_early_data failed: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
#endif // OPENSSL_1_1_1_API
|
||||||
|
|
||||||
|
#ifndef OPENSSL_NO_PSK
|
||||||
|
SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb);
|
||||||
|
#endif // !LIBRESSL_NO_PSK
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int select_h2_next_proto_cb(SSL *ssl, unsigned char **out,
|
int select_h2_next_proto_cb(SSL *ssl, unsigned char **out,
|
||||||
unsigned char *outlen, const unsigned char *in,
|
unsigned char *outlen, const unsigned char *in,
|
||||||
|
@ -1747,6 +2101,10 @@ bool in_proto_list(const std::vector<StringRef> &protos,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool upstream_tls_enabled(const ConnectionConfig &connconf) {
|
bool upstream_tls_enabled(const ConnectionConfig &connconf) {
|
||||||
|
if (connconf.quic_listener.addrs.size()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const auto &faddrs = connconf.listener.addrs;
|
const auto &faddrs = connconf.listener.addrs;
|
||||||
return std::any_of(std::begin(faddrs), std::end(faddrs),
|
return std::any_of(std::begin(faddrs), std::end(faddrs),
|
||||||
[](const UpstreamAddr &faddr) { return faddr.tls; });
|
[](const UpstreamAddr &faddr) { return faddr.tls; });
|
||||||
|
@ -1826,6 +2184,61 @@ setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
||||||
return ssl_ctx;
|
return ssl_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SSL_CTX *setup_quic_server_ssl_context(
|
||||||
|
std::vector<SSL_CTX *> &all_ssl_ctx,
|
||||||
|
std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
|
||||||
|
CertLookupTree *cert_tree
|
||||||
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
,
|
||||||
|
neverbleed_t *nb
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
) {
|
||||||
|
auto config = get_config();
|
||||||
|
|
||||||
|
if (!upstream_tls_enabled(config->conn)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &tlsconf = config->tls;
|
||||||
|
|
||||||
|
auto ssl_ctx =
|
||||||
|
create_quic_ssl_context(tlsconf.private_key_file.c_str(),
|
||||||
|
tlsconf.cert_file.c_str(), tlsconf.sct_data
|
||||||
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
,
|
||||||
|
nb
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
);
|
||||||
|
|
||||||
|
all_ssl_ctx.push_back(ssl_ctx);
|
||||||
|
|
||||||
|
assert(cert_tree);
|
||||||
|
|
||||||
|
if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) {
|
||||||
|
LOG(FATAL) << "Failed to add default certificate.";
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &c : tlsconf.subcerts) {
|
||||||
|
auto ssl_ctx = create_quic_ssl_context(c.private_key_file.c_str(),
|
||||||
|
c.cert_file.c_str(), c.sct_data
|
||||||
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
,
|
||||||
|
nb
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
);
|
||||||
|
all_ssl_ctx.push_back(ssl_ctx);
|
||||||
|
|
||||||
|
if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) ==
|
||||||
|
-1) {
|
||||||
|
LOG(FATAL) << "Failed to add sub certificate.";
|
||||||
|
DIE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssl_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
SSL_CTX *setup_downstream_client_ssl_context(
|
SSL_CTX *setup_downstream_client_ssl_context(
|
||||||
#ifdef HAVE_NEVERBLEED
|
#ifdef HAVE_NEVERBLEED
|
||||||
neverbleed_t *nb
|
neverbleed_t *nb
|
||||||
|
|
|
@ -100,6 +100,16 @@ SSL_CTX *create_ssl_client_context(
|
||||||
unsigned char *outlen, const unsigned char *in,
|
unsigned char *outlen, const unsigned char *in,
|
||||||
unsigned int inlen, void *arg));
|
unsigned int inlen, void *arg));
|
||||||
|
|
||||||
|
SSL_CTX *create_quic_ssl_client_context(
|
||||||
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
neverbleed_t *nb,
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
const StringRef &cacert, const StringRef &cert_file,
|
||||||
|
const StringRef &private_key_file,
|
||||||
|
int (*next_proto_select_cb)(SSL *s, unsigned char **out,
|
||||||
|
unsigned char *outlen, const unsigned char *in,
|
||||||
|
unsigned int inlen, void *arg));
|
||||||
|
|
||||||
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
|
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
|
||||||
int addrlen, const UpstreamAddr *faddr);
|
int addrlen, const UpstreamAddr *faddr);
|
||||||
|
|
||||||
|
@ -217,6 +227,16 @@ setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
||||||
#endif // HAVE_NEVERBLEED
|
#endif // HAVE_NEVERBLEED
|
||||||
);
|
);
|
||||||
|
|
||||||
|
SSL_CTX *setup_quic_server_ssl_context(
|
||||||
|
std::vector<SSL_CTX *> &all_ssl_ctx,
|
||||||
|
std::vector<std::vector<SSL_CTX *>> &indexed_ssl_ctx,
|
||||||
|
CertLookupTree *cert_tree
|
||||||
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
,
|
||||||
|
neverbleed_t *nb
|
||||||
|
#endif // HAVE_NEVERBLEED
|
||||||
|
);
|
||||||
|
|
||||||
// Setups client side SSL_CTX.
|
// Setups client side SSL_CTX.
|
||||||
SSL_CTX *setup_downstream_client_ssl_context(
|
SSL_CTX *setup_downstream_client_ssl_context(
|
||||||
#ifdef HAVE_NEVERBLEED
|
#ifdef HAVE_NEVERBLEED
|
||||||
|
|
|
@ -39,6 +39,8 @@
|
||||||
#ifdef HAVE_MRUBY
|
#ifdef HAVE_MRUBY
|
||||||
# include "shrpx_mruby.h"
|
# include "shrpx_mruby.h"
|
||||||
#endif // HAVE_MRUBY
|
#endif // HAVE_MRUBY
|
||||||
|
#include "shrpx_quic.h"
|
||||||
|
#include "shrpx_quic_listener.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
|
||||||
|
@ -129,18 +131,23 @@ create_downstream_key(const std::shared_ptr<SharedDownstreamAddr> &shared_addr,
|
||||||
|
|
||||||
Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
|
Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
|
||||||
SSL_CTX *tls_session_cache_memcached_ssl_ctx,
|
SSL_CTX *tls_session_cache_memcached_ssl_ctx,
|
||||||
tls::CertLookupTree *cert_tree,
|
tls::CertLookupTree *cert_tree, SSL_CTX *quic_sv_ssl_ctx,
|
||||||
|
tls::CertLookupTree *quic_cert_tree,
|
||||||
const std::shared_ptr<TicketKeys> &ticket_keys,
|
const std::shared_ptr<TicketKeys> &ticket_keys,
|
||||||
ConnectionHandler *conn_handler,
|
ConnectionHandler *conn_handler,
|
||||||
std::shared_ptr<DownstreamConfig> downstreamconf)
|
std::shared_ptr<DownstreamConfig> downstreamconf)
|
||||||
: randgen_(util::make_mt19937()),
|
: randgen_(util::make_mt19937()),
|
||||||
worker_stat_{},
|
worker_stat_{},
|
||||||
dns_tracker_(loop),
|
dns_tracker_(loop),
|
||||||
|
quic_upstream_addrs_{get_config()->conn.quic_listener.addrs},
|
||||||
loop_(loop),
|
loop_(loop),
|
||||||
sv_ssl_ctx_(sv_ssl_ctx),
|
sv_ssl_ctx_(sv_ssl_ctx),
|
||||||
cl_ssl_ctx_(cl_ssl_ctx),
|
cl_ssl_ctx_(cl_ssl_ctx),
|
||||||
cert_tree_(cert_tree),
|
cert_tree_(cert_tree),
|
||||||
conn_handler_(conn_handler),
|
conn_handler_(conn_handler),
|
||||||
|
quic_sv_ssl_ctx_{quic_sv_ssl_ctx},
|
||||||
|
quic_cert_tree_{quic_cert_tree},
|
||||||
|
quic_conn_handler_{this},
|
||||||
ticket_keys_(ticket_keys),
|
ticket_keys_(ticket_keys),
|
||||||
connect_blocker_(
|
connect_blocker_(
|
||||||
std::make_unique<ConnectBlocker>(randgen_, loop_, nullptr, nullptr)),
|
std::make_unique<ConnectBlocker>(randgen_, loop_, nullptr, nullptr)),
|
||||||
|
@ -502,6 +509,10 @@ void Worker::process_events() {
|
||||||
|
|
||||||
tls::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; }
|
tls::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; }
|
||||||
|
|
||||||
|
tls::CertLookupTree *Worker::get_quic_cert_lookup_tree() const {
|
||||||
|
return quic_cert_tree_;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<TicketKeys> Worker::get_ticket_keys() {
|
std::shared_ptr<TicketKeys> Worker::get_ticket_keys() {
|
||||||
#ifdef HAVE_ATOMIC_STD_SHARED_PTR
|
#ifdef HAVE_ATOMIC_STD_SHARED_PTR
|
||||||
return std::atomic_load_explicit(&ticket_keys_, std::memory_order_acquire);
|
return std::atomic_load_explicit(&ticket_keys_, std::memory_order_acquire);
|
||||||
|
@ -532,6 +543,8 @@ SSL_CTX *Worker::get_sv_ssl_ctx() const { return sv_ssl_ctx_; }
|
||||||
|
|
||||||
SSL_CTX *Worker::get_cl_ssl_ctx() const { return cl_ssl_ctx_; }
|
SSL_CTX *Worker::get_cl_ssl_ctx() const { return cl_ssl_ctx_; }
|
||||||
|
|
||||||
|
SSL_CTX *Worker::get_quic_sv_ssl_ctx() const { return quic_sv_ssl_ctx_; }
|
||||||
|
|
||||||
void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; }
|
void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; }
|
||||||
|
|
||||||
bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; }
|
bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; }
|
||||||
|
@ -576,8 +589,25 @@ ConnectionHandler *Worker::get_connection_handler() const {
|
||||||
return conn_handler_;
|
return conn_handler_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUICConnectionHandler *Worker::get_quic_connection_handler() {
|
||||||
|
return &quic_conn_handler_;
|
||||||
|
}
|
||||||
|
|
||||||
DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; }
|
DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; }
|
||||||
|
|
||||||
|
int Worker::setup_quic_server_socket() {
|
||||||
|
for (auto &addr : quic_upstream_addrs_) {
|
||||||
|
assert(!addr.host_unix);
|
||||||
|
if (create_quic_server_socket(addr) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
quic_listeners_.emplace_back(std::make_unique<QUICListener>(&addr, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
size_t match_downstream_addr_group_host(
|
size_t match_downstream_addr_group_host(
|
||||||
const RouterConfig &routerconf, const StringRef &host,
|
const RouterConfig &routerconf, const StringRef &host,
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
#include "shrpx_live_check.h"
|
#include "shrpx_live_check.h"
|
||||||
#include "shrpx_connect_blocker.h"
|
#include "shrpx_connect_blocker.h"
|
||||||
#include "shrpx_dns_tracker.h"
|
#include "shrpx_dns_tracker.h"
|
||||||
|
#include "shrpx_quic_connection_handler.h"
|
||||||
#include "allocator.h"
|
#include "allocator.h"
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
@ -61,6 +62,7 @@ class ConnectBlocker;
|
||||||
class MemcachedDispatcher;
|
class MemcachedDispatcher;
|
||||||
struct UpstreamAddr;
|
struct UpstreamAddr;
|
||||||
class ConnectionHandler;
|
class ConnectionHandler;
|
||||||
|
class QUICListener;
|
||||||
|
|
||||||
#ifdef HAVE_MRUBY
|
#ifdef HAVE_MRUBY
|
||||||
namespace mruby {
|
namespace mruby {
|
||||||
|
@ -267,7 +269,8 @@ class Worker {
|
||||||
public:
|
public:
|
||||||
Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
|
Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
|
||||||
SSL_CTX *tls_session_cache_memcached_ssl_ctx,
|
SSL_CTX *tls_session_cache_memcached_ssl_ctx,
|
||||||
tls::CertLookupTree *cert_tree,
|
tls::CertLookupTree *cert_tree, SSL_CTX *quic_sv_ssl_ctx,
|
||||||
|
tls::CertLookupTree *quic_cert_tree,
|
||||||
const std::shared_ptr<TicketKeys> &ticket_keys,
|
const std::shared_ptr<TicketKeys> &ticket_keys,
|
||||||
ConnectionHandler *conn_handler,
|
ConnectionHandler *conn_handler,
|
||||||
std::shared_ptr<DownstreamConfig> downstreamconf);
|
std::shared_ptr<DownstreamConfig> downstreamconf);
|
||||||
|
@ -278,6 +281,7 @@ public:
|
||||||
void send(const WorkerEvent &event);
|
void send(const WorkerEvent &event);
|
||||||
|
|
||||||
tls::CertLookupTree *get_cert_lookup_tree() const;
|
tls::CertLookupTree *get_cert_lookup_tree() const;
|
||||||
|
tls::CertLookupTree *get_quic_cert_lookup_tree() const;
|
||||||
|
|
||||||
// These 2 functions make a lock m_ to get/set ticket keys
|
// These 2 functions make a lock m_ to get/set ticket keys
|
||||||
// atomically.
|
// atomically.
|
||||||
|
@ -288,6 +292,7 @@ public:
|
||||||
struct ev_loop *get_loop() const;
|
struct ev_loop *get_loop() const;
|
||||||
SSL_CTX *get_sv_ssl_ctx() const;
|
SSL_CTX *get_sv_ssl_ctx() const;
|
||||||
SSL_CTX *get_cl_ssl_ctx() const;
|
SSL_CTX *get_cl_ssl_ctx() const;
|
||||||
|
SSL_CTX *get_quic_sv_ssl_ctx() const;
|
||||||
|
|
||||||
void set_graceful_shutdown(bool f);
|
void set_graceful_shutdown(bool f);
|
||||||
bool get_graceful_shutdown() const;
|
bool get_graceful_shutdown() const;
|
||||||
|
@ -317,8 +322,12 @@ public:
|
||||||
|
|
||||||
ConnectionHandler *get_connection_handler() const;
|
ConnectionHandler *get_connection_handler() const;
|
||||||
|
|
||||||
|
QUICConnectionHandler *get_quic_connection_handler();
|
||||||
|
|
||||||
DNSTracker *get_dns_tracker();
|
DNSTracker *get_dns_tracker();
|
||||||
|
|
||||||
|
int setup_quic_server_socket();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifndef NOTHREADS
|
#ifndef NOTHREADS
|
||||||
std::future<void> fut_;
|
std::future<void> fut_;
|
||||||
|
@ -333,6 +342,9 @@ private:
|
||||||
WorkerStat worker_stat_;
|
WorkerStat worker_stat_;
|
||||||
DNSTracker dns_tracker_;
|
DNSTracker dns_tracker_;
|
||||||
|
|
||||||
|
std::vector<UpstreamAddr> quic_upstream_addrs_;
|
||||||
|
std::vector<std::unique_ptr<QUICListener>> quic_listeners_;
|
||||||
|
|
||||||
std::shared_ptr<DownstreamConfig> downstreamconf_;
|
std::shared_ptr<DownstreamConfig> downstreamconf_;
|
||||||
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
|
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
|
||||||
#ifdef HAVE_MRUBY
|
#ifdef HAVE_MRUBY
|
||||||
|
@ -346,6 +358,10 @@ private:
|
||||||
SSL_CTX *cl_ssl_ctx_;
|
SSL_CTX *cl_ssl_ctx_;
|
||||||
tls::CertLookupTree *cert_tree_;
|
tls::CertLookupTree *cert_tree_;
|
||||||
ConnectionHandler *conn_handler_;
|
ConnectionHandler *conn_handler_;
|
||||||
|
SSL_CTX *quic_sv_ssl_ctx_;
|
||||||
|
tls::CertLookupTree *quic_cert_tree_;
|
||||||
|
|
||||||
|
QUICConnectionHandler quic_conn_handler_;
|
||||||
|
|
||||||
#ifndef HAVE_ATOMIC_STD_SHARED_PTR
|
#ifndef HAVE_ATOMIC_STD_SHARED_PTR
|
||||||
std::mutex ticket_keys_m_;
|
std::mutex ticket_keys_m_;
|
||||||
|
|
97
src/util.cc
97
src/util.cc
|
@ -686,18 +686,21 @@ std::string numeric_name(const struct sockaddr *sa, socklen_t salen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_numeric_addr(const Address *addr) {
|
std::string to_numeric_addr(const Address *addr) {
|
||||||
auto family = addr->su.storage.ss_family;
|
return to_numeric_addr(&addr->su.sa, addr->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen) {
|
||||||
|
auto family = sa->sa_family;
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
if (family == AF_UNIX) {
|
if (family == AF_UNIX) {
|
||||||
return addr->su.un.sun_path;
|
return reinterpret_cast<const sockaddr_un *>(sa)->sun_path;
|
||||||
}
|
}
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
|
|
||||||
std::array<char, NI_MAXHOST> host;
|
std::array<char, NI_MAXHOST> host;
|
||||||
std::array<char, NI_MAXSERV> serv;
|
std::array<char, NI_MAXSERV> serv;
|
||||||
auto rv =
|
auto rv = getnameinfo(sa, salen, host.data(), host.size(), serv.data(),
|
||||||
getnameinfo(&addr->su.sa, addr->len, host.data(), host.size(),
|
serv.size(), NI_NUMERICHOST | NI_NUMERICSERV);
|
||||||
serv.data(), serv.size(), NI_NUMERICHOST | NI_NUMERICSERV);
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
@ -945,6 +948,56 @@ int create_nonblock_socket(int family) {
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int create_nonblock_udp_socket(int family) {
|
||||||
|
#ifdef SOCK_NONBLOCK
|
||||||
|
auto fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||||
|
|
||||||
|
if (fd == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#else // !SOCK_NONBLOCK
|
||||||
|
auto fd = socket(family, SOCK_DGRAM, 0);
|
||||||
|
|
||||||
|
if (fd == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
make_socket_nonblocking(fd);
|
||||||
|
make_socket_closeonexec(fd);
|
||||||
|
#endif // !SOCK_NONBLOCK
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bind_any_addr_udp(int fd, int family) {
|
||||||
|
addrinfo hints{};
|
||||||
|
addrinfo *res, *rp;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
hints.ai_family = family;
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
|
||||||
|
rv = getaddrinfo(nullptr, "0", &hints, &res);
|
||||||
|
if (rv != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (rp = res; rp; rp = rp->ai_next) {
|
||||||
|
if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(res);
|
||||||
|
|
||||||
|
if (!rp) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool check_socket_connected(int fd) {
|
bool check_socket_connected(int fd) {
|
||||||
int error;
|
int error;
|
||||||
socklen_t len = sizeof(error);
|
socklen_t len = sizeof(error);
|
||||||
|
@ -1617,6 +1670,40 @@ int daemonize(int nochdir, int noclose) {
|
||||||
#endif // !defined(__APPLE__)
|
#endif // !defined(__APPLE__)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int msghdr_get_local_addr(Address &dest, 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_PKTINFO) {
|
||||||
|
auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsg));
|
||||||
|
dest.len = sizeof(dest.su.in);
|
||||||
|
auto &sa = dest.su.in;
|
||||||
|
sa.sin_family = AF_INET;
|
||||||
|
sa.sin_addr = pktinfo->ipi_addr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
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_PKTINFO) {
|
||||||
|
auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsg));
|
||||||
|
dest.len = sizeof(dest.su.in6);
|
||||||
|
auto &sa = dest.su.in6;
|
||||||
|
sa.sin6_family = AF_INET6;
|
||||||
|
sa.sin6_addr = pktinfo->ipi6_addr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -505,6 +505,8 @@ std::string numeric_name(const struct sockaddr *sa, socklen_t salen);
|
||||||
// IPv6 address, address is enclosed by square brackets ([]).
|
// IPv6 address, address is enclosed by square brackets ([]).
|
||||||
std::string to_numeric_addr(const Address *addr);
|
std::string to_numeric_addr(const Address *addr);
|
||||||
|
|
||||||
|
std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen);
|
||||||
|
|
||||||
// Sets |port| to |addr|.
|
// Sets |port| to |addr|.
|
||||||
void set_port(Address &addr, uint16_t port);
|
void set_port(Address &addr, uint16_t port);
|
||||||
|
|
||||||
|
@ -626,6 +628,9 @@ int make_socket_nonblocking(int fd);
|
||||||
int make_socket_nodelay(int fd);
|
int make_socket_nodelay(int fd);
|
||||||
|
|
||||||
int create_nonblock_socket(int family);
|
int create_nonblock_socket(int family);
|
||||||
|
int create_nonblock_udp_socket(int family);
|
||||||
|
|
||||||
|
int bind_any_addr_udp(int fd, int family);
|
||||||
|
|
||||||
bool check_socket_connected(int fd);
|
bool check_socket_connected(int fd);
|
||||||
|
|
||||||
|
@ -783,6 +788,8 @@ std::mt19937 make_mt19937();
|
||||||
// daemon() using fork().
|
// daemon() using fork().
|
||||||
int daemonize(int nochdir, int noclose);
|
int daemonize(int nochdir, int noclose);
|
||||||
|
|
||||||
|
int msghdr_get_local_addr(Address &dest, msghdr *msg, int family);
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
Loading…
Reference in New Issue