Latest...

This commit is contained in:
2022-11-01 09:49:32 +09:00
parent c52bc5abfc
commit f4b94143ad
94 changed files with 25370 additions and 169 deletions
+250
View File
@@ -0,0 +1,250 @@
Experimental QUIC support for nginx
-----------------------------------
1. Introduction
2. Installing
3. Configuration
4. Clients
5. Troubleshooting
6. Contributing
7. Links
1. Introduction
This is an experimental QUIC [1] / HTTP/3 [2] support for nginx.
The code is developed in a separate "quic" branch available
at https://hg.nginx.org/nginx-quic. Currently it is based
on nginx mainline 1.23.x. We merge new nginx releases into
this branch regularly.
The project code base is under the same BSD license as nginx.
The code is currently at a beta level of quality, however
there are several production deployments with it.
NGINX Development Team is working on improving HTTP/3 support to
integrate it into the main NGINX codebase. Thus, expect further
updates of this code, including features, changes in behaviour,
bug fixes, and refactoring. NGINX Development team will be
grateful for any feedback and code submissions.
Please contact NGINX Development Team via nginx-devel mailing list [3].
What works now:
IETF QUIC version 1 is supported. Internet drafts are no longer supported.
nginx should be able to respond to HTTP/3 requests over QUIC and
it should be possible to upload and download big files without errors.
+ The handshake completes successfully
+ One endpoint can update keys and its peer responds correctly
+ 0-RTT data is being received and acted on
+ Connection is established using TLS Resume Ticket
+ A handshake that includes a Retry packet completes successfully
+ Stream data is being exchanged and ACK'ed
+ An H3 transaction succeeded
+ One or both endpoints insert entries into dynamic table and
subsequently reference them from header blocks
+ Version Negotiation packet is sent to client with unknown version
+ Lost packets are detected and retransmitted properly
+ Clients may migrate to new address
2. Installing
A library that provides QUIC support is required to build nginx, there
are several of those available on the market:
+ BoringSSL [4]
+ LibreSSL [5]
+ QuicTLS [6]
Clone the NGINX QUIC repository
$ hg clone -b quic https://hg.nginx.org/nginx-quic
$ cd nginx-quic
Use the following command to configure nginx with BoringSSL [4]
$ ./auto/configure --with-debug --with-http_v3_module \
--with-cc-opt="-I../boringssl/include" \
--with-ld-opt="-L../boringssl/build/ssl \
-L../boringssl/build/crypto"
$ make
Alternatively, nginx can be configured with QuicTLS [6]
$ ./auto/configure --with-debug --with-http_v3_module \
--with-cc-opt="-I../quictls/build/include" \
--with-ld-opt="-L../quictls/build/lib"
Alternatively, nginx can be configured with a modern version
of LibreSSL [7]
$ ./auto/configure --with-debug --with-http_v3_module \
--with-cc-opt="-I../libressl/build/include" \
--with-ld-opt="-L../libressl/build/lib"
When configuring nginx, it's possible to enable QUIC and HTTP/3
using the following new configuration options:
--with-http_v3_module - enable QUIC and HTTP/3
--with-stream_quic_module - enable QUIC in Stream
3. Configuration
The HTTP "listen" directive got a new option "http3" which enables
HTTP/3 over QUIC on the specified port.
The Stream "listen" directive got a new option "quic" which enables
QUIC as client transport protocol instead of TCP or plain UDP.
Along with "http3" or "quic", it's also possible to specify "reuseport"
option [8] to make it work properly with multiple workers.
To enable address validation:
quic_retry on;
To enable 0-RTT:
ssl_early_data on;
Make sure that TLS 1.3 is configured which is required for QUIC:
ssl_protocols TLSv1.3;
To enable GSO (Generic Segmentation Offloading):
quic_gso on;
To limit maximum UDP payload size on receive path:
quic_mtu <size>;
To set host key for various tokens:
quic_host_key <filename>;
By default, GSO Linux-specific optimization [10] is disabled.
Enable it in case a corresponding network interface is configured to
support GSO.
A number of directives were added that configure HTTP/3:
http3_stream_buffer_size
http3_max_concurrent_pushes
http3_max_concurrent_streams
http3_push
http3_push_preload
http3_hq (requires NGX_HTTP_V3_HQ macro)
In http, an additional variable is available: $http3.
The value of $http3 is "h3" for HTTP/3 connections,
"hq" for hq connections, or an empty string otherwise.
In stream, an additional variable is available: $quic.
The value of $quic is "quic" if QUIC connection is used,
or an empty string otherwise.
Example configuration:
http {
log_format quic '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$http3"';
access_log logs/access.log quic;
server {
# for better compatibility it's recommended
# to use the same port for quic and https
listen 8443 http3 reuseport;
listen 8443 ssl;
ssl_certificate certs/example.com.crt;
ssl_certificate_key certs/example.com.key;
ssl_protocols TLSv1.3;
location / {
# required for browsers to direct them into quic port
add_header Alt-Svc 'h3=":8443"; ma=86400';
}
}
}
4. Clients
* Browsers
Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1)
Beware of strange issues: sometimes browser may decide to ignore QUIC
Cache clearing/restart might help. Always check access.log and
error.log to make sure the browser is using HTTP/3 and not TCP https.
* Console clients
Known to work: ngtcp2, firefox's neqo and chromium's console clients:
$ examples/client 127.0.0.1 8443 https://example.com:8443/index.html
$ ./neqo-client https://127.0.0.1:8443/
$ chromium-build/out/my_build/quic_client http://example.com:8443
In case everyhing is right, the access log should show something like:
127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-"
"nghttp3/ngtcp2 client" "quic"
5. Troubleshooting
Here are some tips that may help to identify problems:
+ Ensure nginx is built with proper SSL library that supports QUIC
+ Ensure nginx is using the proper SSL library in runtime
(`nginx -V` shows what it's using)
+ Ensure a client is actually sending requests over QUIC
(see "Clients" section about browsers and cache)
We recommend to start with simple console client like ngtcp2
to ensure the server is configured properly before trying
with real browsers that may be very picky with certificates,
for example.
+ Build nginx with debug support [9] and check the debug log.
It should contain all details about connection and why it
failed. All related messages contain "quic " prefix and can
be easily filtered out.
+ For a deeper investigation, please enable additional debugging
in src/event/quic/ngx_event_quic_connection.h:
#define NGX_QUIC_DEBUG_PACKETS
#define NGX_QUIC_DEBUG_FRAMES
#define NGX_QUIC_DEBUG_ALLOC
#define NGX_QUIC_DEBUG_CRYPTO
6. Contributing
Please refer to
http://nginx.org/en/docs/contributing_changes.html
7. Links
[1] https://datatracker.ietf.org/doc/html/rfc9000
[2] https://datatracker.ietf.org/doc/html/rfc9114
[3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/
[4] https://boringssl.googlesource.com/boringssl/
[5] https://www.libressl.org/
[6] https://github.com/quictls/openssl
[7] https://github.com/libressl-portable/portable/releases/tag/v3.6.0
[8] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen
[9] https://nginx.org/en/docs/debugging_log.html
[10] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf
Executable
+182
View File
@@ -0,0 +1,182 @@
#!/bin/sh
### Read config
if [ ! -f "config.inc" ]; then
echo "--- The configuration file (config.inc) could not be found. Apply as default setting. ---"
echo "--- All additional modules are not used. ---"
else
. ./config.inc
fi
### If the value is incorrect, convert to normal data.
if [ ! "$SERVER_HEADER" ]; then SERVER_HEADER="hakase"; fi
if [ "$BITCHK" != 32 ] && [ "$BITCHK" != 64 ]; then BITCHK=32; fi
if [ ! "$LTO" ]; then LTO=0; fi
if [ ! "$BUILD_MTS" ]; then BUILD_MTS="-j2"; fi
if [ ! "$NGX_PREFIX" ]; then NGX_PREFIX="/usr/local/nginx"; fi
if [ ! "$NGX_SBIN_PATH" ]; then NGX_SBIN_PATH="/usr/sbin/nginx"; fi
if [ ! "$NGX_CONF" ]; then NGX_CONF="/etc/nginx/nginx.conf"; fi
if [ ! "$NGX_LIB" ]; then NGX_LIB="/var/lib/nginx"; fi
if [ ! "$NGX_LOG" ]; then NGX_LOG="/var/log/nginx"; fi
if [ ! "$NGX_PID" ]; then NGX_PID="/var/run/nginx.pid"; fi
if [ ! "$NGX_LOCK" ]; then NGX_LOCK="/var/lock/nginx.lock"; fi
### Remove Old file
rm -f ${NGX_SBIN_PATH}.old
### Multithread build
BUILD_MTS="-j$(expr $(nproc) \+ 1)"
git submodule update --init --recursive
### PCRE reconf
if [ ! -f "lib/pcre/configure" ]; then
cd lib/pcre
autoreconf -f -i
cd ../..
fi
### ZLIB reconf
if [ "$BITCHK" = 64 ]; then
if [ ! -f "lib/zlib/Makefile" ]; then
cd lib/zlib
./configure --64
cd ../..
fi
else
if [ ! -f "lib/zlib_x86/Makefile" ]; then
git submodule add --force https://github.com/madler/zlib.git lib/zlib_x86
cd lib/zlib_x86
./configure
cd ../..
fi
fi
### PSOL Download (PageSpeed)
if [ ! -d "lib/pagespeed" ] && [ "$PAGESPEED" = 1 ]; then
### Download pagespeed
cd lib
wget -c https://github.com/apache/incubator-pagespeed-ngx/archive/v1.13.35.2-stable.zip
unzip v1.13.35.2-stable.zip
rm -f v1.13.35.2-stable.zip
cd incubator-pagespeed-ngx-1.13.35.2-stable
### Download psol
curl "$(scripts/format_binary_url.sh PSOL_BINARY_URL)" | tar xz
cd ..
mv incubator-pagespeed-ngx-1.13.35.2-stable pagespeed
cd ..
fi
### x86, x64 Check (Configuration)
if [ "$BITCHK" = 64 ]; then
BUILD_BIT="-m64 "
BUILD_ZLIB="./lib/zlib"
BUILD_LD="-lrt -ljemalloc -Wl,-z,relro -Wl,-z,now -fPIC"
else
BUILD_BIT=""
BUILD_ZLIB="./lib/zlib_x86"
BUILD_LD=""
fi
### LTO Build
if [ "$LTO" = 1 ]; then
BUILD_LTO="-flto -ffat-lto-objects"
BUILD_OPENSSL_LTO="-flto -ffat-lto-objects"
else
BUILD_LTO=""
BUILD_OPENSSL_LTO=""
fi
### Temporary Ubuntu/Debian build error (libxslt/libxml2)
### URL : https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=721602
TEMP_OPT="-lm"
### Module check
if [ "$PAGESPEED" = 1 ]; then BUILD_MODULES="--add-module=./lib/pagespeed ${PS_NGX_EXTRA_FLAGS}"; fi
if [ "$FLV" = 1 ]; then BUILD_MODULES="${BUILD_MODULES} --add-module=./lib/nginx-http-flv-module"; fi
if [ "$NAXSI" = 1 ]; then BUILD_MODULES="${BUILD_MODULES} --add-module=./lib/naxsi/naxsi_src"; fi
if [ "$DAV_EXT" = 1 ]; then BUILD_MODULES="${BUILD_MODULES} --add-module=./lib/nginx-dav-ext-module"; fi
if [ "$FANCYINDEX" = 1 ]; then BUILD_MODULES="${BUILD_MODULES} --add-module=./lib/ngx-fancyindex"; fi
if [ "$GEOIP2" = 1 ]; then BUILD_MODULES="${BUILD_MODULES} --add-module=./lib/ngx_http_geoip2_module"; fi
if [ "$VTS" = 1 ]; then BUILD_MODULES="${BUILD_MODULES} --add-module=./lib/nginx-module-vts"; fi
auto/configure \
--with-cc-opt="-Wno-stringop-truncation -DTCP_FASTOPEN=23 ${BUILD_BIT}${BUILD_LTO} ${TEMP_OPT} -g -O3 -march=native -fstack-protector-strong -fuse-ld=gold -fuse-linker-plugin --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Wno-strict-aliasing -Wp,-D_FORTIFY_SOURCE=2 -gsplit-dwarf -DNGX_HTTP_HEADERS" \
--with-ld-opt="${BUILD_LD} ${BUILD_LTO}" \
--with-openssl-opt="no-cmp enable-weak-ssl-ciphers no-ssl3-method -march=native -ljemalloc ${BUILD_OPENSSL_LTO}" \
--builddir=objs --prefix=${NGX_PREFIX} \
--conf-path=${NGX_CONF} \
--pid-path=${NGX_PID} \
--lock-path=${NGX_LOCK} \
--http-log-path=${NGX_LOG}/access.log \
--error-log-path=${NGX_LOG}/error.log \
--sbin-path=${NGX_SBIN_PATH} \
--http-client-body-temp-path=${NGX_LIB}/client_body_temp \
--http-proxy-temp-path=${NGX_LIB}/proxy_temp \
--http-fastcgi-temp-path=${NGX_LIB}/fastcgi_temp \
--http-scgi-temp-path=${NGX_LIB}/scgi_temp \
--http-uwsgi-temp-path=${NGX_LIB}/uwsgi_temp \
--with-pcre=./lib/pcre \
--with-pcre-jit \
--with-zlib=${BUILD_ZLIB} \
--with-openssl=./lib/openssl \
--with-http_realip_module \
--with-http_addition_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_stub_status_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_slice_module \
--with-http_gzip_static_module \
--with-http_auth_request_module \
--with-http_dav_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_image_filter_module \
--with-file-aio \
--with-threads \
--with-libatomic \
--with-mail \
--with-compat \
--with-stream \
--with-http_ssl_module \
--with-mail_ssl_module \
--with-http_v2_module \
--with-http_v2_hpack_enc \
--with-stream_ssl_module \
--with-stream_realip_module \
--with-stream_ssl_preread_module \
--add-module=./lib/ngx_devel_kit \
--add-module=./lib/ngx_brotli \
--add-module=./lib/headers-more-nginx-module \
${BUILD_MODULES}
### OpenSSL Skip
### Do not use it for the FIRST BUILD.
#touch lib/openssl/.openssl/include/openssl/ssl.h
### SERVER HEADER CONFIG
NGX_AUTO_CONFIG_H="objs/ngx_auto_config.h";have="NGINX_SERVER";value="\"${SERVER_HEADER}\""; . auto/define
### Install
make $BUILD_MTS install
### Make directory NGX_LIB
mkdir -p ${NGX_LIB}
### Check for old files
if [ -f "${NGX_SBIN_PATH}.old" ]; then
### Test nginx configuration.
"$NGX_SBIN_PATH" -t > /dev/null 2>&1
if test $? -ne 0; then
echo "Failed nginx configuration test."
exit 1
fi
sleep 1
rm ${NGX_SBIN_PATH}.old
systemctl restart nginx
fi
+31 -6
View File
@@ -5,12 +5,16 @@
if [ $OPENSSL != NONE ]; then
have=NGX_OPENSSL . auto/have
have=NGX_SSL . auto/have
if [ $USE_OPENSSL_QUIC = YES ]; then
have=NGX_QUIC . auto/have
fi
case "$CC" in
cl | bcc32)
have=NGX_OPENSSL . auto/have
have=NGX_SSL . auto/have
CFLAGS="$CFLAGS -DNO_SYS_TYPES_H"
CORE_INCS="$CORE_INCS $OPENSSL/openssl/include"
@@ -33,9 +37,6 @@ if [ $OPENSSL != NONE ]; then
;;
*)
have=NGX_OPENSSL . auto/have
have=NGX_SSL . auto/have
CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include"
CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h"
CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a"
@@ -139,4 +140,28 @@ END
exit 1
fi
if [ $USE_OPENSSL_QUIC = YES ]; then
ngx_feature="OpenSSL QUIC support"
ngx_feature_name="NGX_QUIC"
ngx_feature_run=no
ngx_feature_incs="#include <openssl/ssl.h>"
ngx_feature_path=
ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD"
ngx_feature_test="SSL_set_quic_method(NULL, NULL)"
. auto/feature
if [ $ngx_found = no ]; then
cat << END
$0: error: certain modules require OpenSSL QUIC support.
You can either do not enable the modules, or install the OpenSSL library with
QUIC support into the system, or build the OpenSSL library with QUIC support
statically from the source with nginx by using --with-openssl=<path> option.
END
exit 1
fi
fi
fi
+3 -2
View File
@@ -6,9 +6,10 @@
echo "creating $NGX_MAKEFILE"
mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \
$NGX_OBJS/src/event/quic \
$NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \
$NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \
$NGX_OBJS/src/http/modules/perl \
$NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \
$NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \
$NGX_OBJS/src/mail \
$NGX_OBJS/src/stream \
$NGX_OBJS/src/misc
+114 -1
View File
@@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then
fi
if [ $HTTP_V2 = YES ]; then
if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then
HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS"
fi
@@ -124,6 +124,7 @@ if [ $HTTP = YES ]; then
# ngx_http_header_filter
# ngx_http_chunked_filter
# ngx_http_v2_filter
# ngx_http_v3_filter
# ngx_http_range_header_filter
# ngx_http_gzip_filter
# ngx_http_postpone_filter
@@ -156,6 +157,7 @@ if [ $HTTP = YES ]; then
ngx_http_header_filter_module \
ngx_http_chunked_filter_module \
ngx_http_v2_filter_module \
ngx_http_v3_filter_module \
ngx_http_range_header_filter_module \
ngx_http_gzip_filter_module \
ngx_http_postpone_filter_module \
@@ -217,6 +219,17 @@ if [ $HTTP = YES ]; then
. auto/module
fi
if [ $HTTP_V3 = YES ]; then
ngx_module_name=ngx_http_v3_filter_module
ngx_module_incs=
ngx_module_deps=
ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c
ngx_module_libs=
ngx_module_link=$HTTP_V3
. auto/module
fi
if :; then
ngx_module_name=ngx_http_range_header_filter_module
ngx_module_incs=
@@ -426,6 +439,37 @@ if [ $HTTP = YES ]; then
. auto/module
fi
if [ $HTTP_V3 = YES ]; then
USE_OPENSSL_QUIC=YES
HTTP_SSL=YES
have=NGX_HTTP_V3 . auto/have
have=NGX_HTTP_HEADERS . auto/have
ngx_module_name=ngx_http_v3_module
ngx_module_incs=src/http/v3
ngx_module_deps="src/http/v3/ngx_http_v3.h \
src/http/v3/ngx_http_v3_encode.h \
src/http/v3/ngx_http_v3_parse.h \
src/http/v3/ngx_http_v3_table.h \
src/http/v3/ngx_http_v3_uni.h"
ngx_module_srcs="src/http/v3/ngx_http_v3.c \
src/http/v3/ngx_http_v3_encode.c \
src/http/v3/ngx_http_v3_parse.c \
src/http/v3/ngx_http_v3_table.c \
src/http/v3/ngx_http_v3_uni.c \
src/http/v3/ngx_http_v3_request.c \
src/http/v3/ngx_http_v3_module.c"
ngx_module_libs=
ngx_module_link=$HTTP_V3
. auto/module
fi
if [ $HTTP_V2_HPACK_ENC = YES ]; then
have=NGX_HTTP_V2_HPACK_ENC . auto/have
fi
if :; then
ngx_module_name=ngx_http_static_module
ngx_module_incs=
@@ -1035,6 +1079,20 @@ if [ $STREAM != NO ]; then
ngx_module_incs=
if [ $STREAM_QUIC = YES ]; then
USE_OPENSSL_QUIC=YES
have=NGX_STREAM_QUIC . auto/have
STREAM_SSL=YES
ngx_module_name=ngx_stream_quic_module
ngx_module_deps=src/stream/ngx_stream_quic_module.h
ngx_module_srcs=src/stream/ngx_stream_quic_module.c
ngx_module_libs=
ngx_module_link=$STREAM_QUIC
. auto/module
fi
if [ $STREAM_SSL = YES ]; then
USE_OPENSSL=YES
have=NGX_STREAM_SSL . auto/have
@@ -1272,6 +1330,61 @@ if [ $USE_OPENSSL = YES ]; then
fi
if [ $USE_OPENSSL_QUIC = YES ]; then
ngx_module_type=CORE
ngx_module_name=ngx_quic_module
ngx_module_incs=
ngx_module_deps="src/event/quic/ngx_event_quic.h \
src/event/quic/ngx_event_quic_transport.h \
src/event/quic/ngx_event_quic_protection.h \
src/event/quic/ngx_event_quic_connection.h \
src/event/quic/ngx_event_quic_frames.h \
src/event/quic/ngx_event_quic_connid.h \
src/event/quic/ngx_event_quic_migration.h \
src/event/quic/ngx_event_quic_streams.h \
src/event/quic/ngx_event_quic_ssl.h \
src/event/quic/ngx_event_quic_tokens.h \
src/event/quic/ngx_event_quic_ack.h \
src/event/quic/ngx_event_quic_output.h \
src/event/quic/ngx_event_quic_socket.h"
ngx_module_srcs="src/event/quic/ngx_event_quic.c \
src/event/quic/ngx_event_quic_udp.c \
src/event/quic/ngx_event_quic_transport.c \
src/event/quic/ngx_event_quic_protection.c \
src/event/quic/ngx_event_quic_frames.c \
src/event/quic/ngx_event_quic_connid.c \
src/event/quic/ngx_event_quic_migration.c \
src/event/quic/ngx_event_quic_streams.c \
src/event/quic/ngx_event_quic_ssl.c \
src/event/quic/ngx_event_quic_tokens.c \
src/event/quic/ngx_event_quic_ack.c \
src/event/quic/ngx_event_quic_output.c \
src/event/quic/ngx_event_quic_socket.c"
ngx_module_libs=
ngx_module_link=YES
ngx_module_order=
. auto/module
if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then
ngx_module_type=CORE
ngx_module_name=ngx_quic_bpf_module
ngx_module_incs=
ngx_module_deps=
ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \
src/event/quic/ngx_event_quic_bpf_code.c"
ngx_module_libs=
ngx_module_link=YES
ngx_module_order=
. auto/module
have=NGX_QUIC_BPF . auto/have
fi
fi
if [ $USE_PCRE = YES ]; then
ngx_module_type=CORE
ngx_module_name=ngx_regex_module
+18
View File
@@ -45,6 +45,8 @@ USE_THREADS=NO
NGX_FILE_AIO=NO
QUIC_BPF=NO
HTTP=YES
NGX_HTTP_LOG_PATH=
@@ -59,6 +61,8 @@ HTTP_CHARSET=YES
HTTP_GZIP=YES
HTTP_SSL=NO
HTTP_V2=NO
HTTP_V2_HPACK_ENC=NO
HTTP_V3=NO
HTTP_SSI=YES
HTTP_REALIP=NO
HTTP_XSLT=NO
@@ -116,6 +120,7 @@ MAIL_SMTP=YES
STREAM=NO
STREAM_SSL=NO
STREAM_QUIC=NO
STREAM_REALIP=NO
STREAM_LIMIT_CONN=YES
STREAM_ACCESS=YES
@@ -149,6 +154,7 @@ PCRE_JIT=NO
PCRE2=YES
USE_OPENSSL=NO
USE_OPENSSL_QUIC=NO
OPENSSL=NONE
USE_ZLIB=NO
@@ -166,6 +172,8 @@ USE_GEOIP=NO
NGX_GOOGLE_PERFTOOLS=NO
NGX_CPP_TEST=NO
SO_COOKIE_FOUND=NO
NGX_LIBATOMIC=NO
NGX_CPU_CACHE_LINE=
@@ -211,6 +219,8 @@ do
--with-file-aio) NGX_FILE_AIO=YES ;;
--without-quic_bpf_module) QUIC_BPF=NONE ;;
--with-ipv6)
NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
$0: warning: the \"--with-ipv6\" option is deprecated"
@@ -228,6 +238,8 @@ $0: warning: the \"--with-ipv6\" option is deprecated"
--with-http_ssl_module) HTTP_SSL=YES ;;
--with-http_v2_module) HTTP_V2=YES ;;
--with-http_v2_hpack_enc) HTTP_V2_HPACK_ENC=YES ;;
--with-http_v3_module) HTTP_V3=YES ;;
--with-http_realip_module) HTTP_REALIP=YES ;;
--with-http_addition_module) HTTP_ADDITION=YES ;;
--with-http_xslt_module) HTTP_XSLT=YES ;;
@@ -314,6 +326,7 @@ use the \"--with-mail_ssl_module\" option instead"
--with-stream) STREAM=YES ;;
--with-stream=dynamic) STREAM=DYNAMIC ;;
--with-stream_ssl_module) STREAM_SSL=YES ;;
--with-stream_quic_module) STREAM_QUIC=YES ;;
--with-stream_realip_module) STREAM_REALIP=YES ;;
--with-stream_geoip_module) STREAM_GEOIP=YES ;;
--with-stream_geoip_module=dynamic)
@@ -443,8 +456,12 @@ cat << END
--with-file-aio enable file AIO support
--without-quic_bpf_module disable ngx_quic_bpf_module
--with-http_ssl_module enable ngx_http_ssl_module
--with-http_v2_module enable ngx_http_v2_module
--with-http_v2_hpack_enc enable ngx_http_v2_hpack_enc
--with-http_v3_module enable ngx_http_v3_module
--with-http_realip_module enable ngx_http_realip_module
--with-http_addition_module enable ngx_http_addition_module
--with-http_xslt_module enable ngx_http_xslt_module
@@ -533,6 +550,7 @@ cat << END
--with-stream enable TCP/UDP proxy module
--with-stream=dynamic enable dynamic TCP/UDP proxy module
--with-stream_ssl_module enable ngx_stream_ssl_module
--with-stream_quic_module enable ngx_stream_quic_module
--with-stream_realip_module enable ngx_stream_realip_module
--with-stream_geoip_module enable ngx_stream_geoip_module
--with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module
+44
View File
@@ -232,6 +232,50 @@ ngx_feature_test="struct crypt_data cd;
ngx_include="sys/vfs.h"; . auto/include
# BPF sockhash
ngx_feature="BPF sockhash"
ngx_feature_name="NGX_HAVE_BPF"
ngx_feature_run=no
ngx_feature_incs="#include <linux/bpf.h>
#include <sys/syscall.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="union bpf_attr attr = { 0 };
attr.map_flags = 0;
attr.map_type = BPF_MAP_TYPE_SOCKHASH;
syscall(__NR_bpf, 0, &attr, 0);"
. auto/feature
if [ $ngx_found = yes ]; then
CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c"
CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h"
if [ $QUIC_BPF != NONE ]; then
QUIC_BPF=YES
fi
fi
ngx_feature="SO_COOKIE"
ngx_feature_name="NGX_HAVE_SO_COOKIE"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
#include <stdint.h>"
ngx_feature_path=
ngx_feature_libs=
ngx_feature_test="socklen_t optlen = sizeof(uint64_t);
uint64_t cookie;
getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)"
. auto/feature
if [ $ngx_found = yes ]; then
SO_COOKIE_FOUND=YES
fi
# UDP segmentation offloading
ngx_feature="UDP_SEGMENT"
+1 -1
View File
@@ -83,7 +83,7 @@ CORE_SRCS="src/core/nginx.c \
EVENT_MODULES="ngx_events_module ngx_event_core_module"
EVENT_INCS="src/event src/event/modules"
EVENT_INCS="src/event src/event/modules src/event/quic"
EVENT_DEPS="src/event/ngx_event.h \
src/event/ngx_event_timer.h \
+77
View File
@@ -0,0 +1,77 @@
#!/bin/sh
######################
### Default Config ###
######################
### SERVER HEADER!!!
### Please edit this
SERVER_HEADER="hakase"
### x86, x64 check
### If you do not want optimization, uncomment it.
### (Comment out existing sources.)
### Do not modify it under normal circumstances.
BITCHK=`getconf LONG_BIT`
#BITCHK=32
### Multithread build
### Do not modify it under normal circumstances.
BUILD_MTS="-j$(expr $(nproc) \+ 1)"
### LTO build
### View : https://gcc.gnu.org/wiki/LinkTimeOptimization#Using_LTO
LTO=0
#########################
### Select add module ###
#########################
### Enter 0 for the module that is not to be used.
### PageSpeed
PAGESPEED=0
### nginx-http-flv-module
FLV=0
### naxsi
NAXSI=1
### nginx-dav-ext-module
DAV_EXT=1
### fancyindex
FANCYINDEX=1
### GEOIP2
GEOIP2=1
### nginx-module-vts
VTS=0
##########################
### nginx install path ###
##########################
### Prefix
NGX_PREFIX="/usr/local/nginx"
### Binary
NGX_SBIN_PATH="/usr/sbin/nginx"
### Config Path
NGX_CONF="/etc/nginx/nginx.conf"
### Temp Path (client_body_temp, proxy_temp, ...)
NGX_LIB="/var/lib/nginx"
### Log path
NGX_LOG="/var/log/nginx"
### PID file
NGX_PID="/var/run/nginx.pid"
### Lock file
NGX_LOCK="/var/lock/nginx.lock"
+3
View File
@@ -680,6 +680,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv)
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
if (ls[i].ignore) {
continue;
}
p = ngx_sprintf(p, "%ud;", ls[i].fd);
}
+12 -3
View File
@@ -11,12 +11,21 @@
#define nginx_version 1023002
#define NGINX_VERSION "1.23.2"
#define NGINX_VER "nginx/" NGINX_VERSION
#define NGINX_VER "nginx/" NGINX_VERSION " by Hakase"
#ifndef NGINX_SERVER
#define NGINX_SERVER "hakase"
#endif
#define NGINX_SERVER_FULL NGINX_SERVER "/" NGINX_VERSION
#define NGINX_ORIG "nginx/" NGINX_VERSION
#ifdef NGX_BUILD
#define NGINX_VER_BUILD NGINX_VER " (" NGX_BUILD ")"
#define NGINX_SERVER_FULL_BUILD NGINX_SERVER_FULL " (" NGX_BUILD ")"
#define NGINX_VER_BUILD NGINX_VER " (" NGX_BUILD ")"
#else
#define NGINX_VER_BUILD NGINX_VER
#define NGINX_SERVER_FULL_BUILD NGINX_SERVER_FULL
#define NGINX_VER_BUILD NGINX_VER
#endif
#define NGINX_VAR "NGINX"
+143
View File
@@ -0,0 +1,143 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_BPF_LOGBUF_SIZE (16 * 1024)
static ngx_inline int
ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
{
return syscall(__NR_bpf, cmd, attr, size);
}
void
ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd)
{
ngx_uint_t i;
ngx_bpf_reloc_t *rl;
rl = program->relocs;
for (i = 0; i < program->nrelocs; i++) {
if (ngx_strcmp(rl[i].name, symbol) == 0) {
program->ins[rl[i].offset].src_reg = 1;
program->ins[rl[i].offset].imm = fd;
}
}
}
int
ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program)
{
int fd;
union bpf_attr attr;
#if (NGX_DEBUG)
char buf[NGX_BPF_LOGBUF_SIZE];
#endif
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.license = (uintptr_t) program->license;
attr.prog_type = program->type;
attr.insns = (uintptr_t) program->ins;
attr.insn_cnt = program->nins;
#if (NGX_DEBUG)
/* for verifier errors */
attr.log_buf = (uintptr_t) buf;
attr.log_size = NGX_BPF_LOGBUF_SIZE;
attr.log_level = 1;
#endif
fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
if (fd < 0) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"failed to load BPF program");
ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
"bpf verifier: %s", buf);
return -1;
}
return fd;
}
int
ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
int value_size, int max_entries, uint32_t map_flags)
{
int fd;
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_type = type;
attr.key_size = key_size;
attr.value_size = value_size;
attr.max_entries = max_entries;
attr.map_flags = map_flags;
fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
if (fd < 0) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"failed to create BPF map");
return NGX_ERROR;
}
return fd;
}
int
ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags)
{
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_fd = fd;
attr.key = (uintptr_t) key;
attr.value = (uintptr_t) value;
attr.flags = flags;
return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}
int
ngx_bpf_map_delete(int fd, const void *key)
{
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_fd = fd;
attr.key = (uintptr_t) key;
return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
}
int
ngx_bpf_map_lookup(int fd, const void *key, void *value)
{
union bpf_attr attr;
ngx_memzero(&attr, sizeof(union bpf_attr));
attr.map_fd = fd;
attr.key = (uintptr_t) key;
attr.value = (uintptr_t) value;
return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_BPF_H_INCLUDED_
#define _NGX_BPF_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <linux/bpf.h>
typedef struct {
char *name;
int offset;
} ngx_bpf_reloc_t;
typedef struct {
char *license;
enum bpf_prog_type type;
struct bpf_insn *ins;
size_t nins;
ngx_bpf_reloc_t *relocs;
size_t nrelocs;
} ngx_bpf_program_t;
void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol,
int fd);
int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program);
int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
int value_size, int max_entries, uint32_t map_flags);
int ngx_bpf_map_update(int fd, const void *key, const void *value,
uint64_t flags);
int ngx_bpf_map_delete(int fd, const void *key);
int ngx_bpf_map_lookup(int fd, const void *key, void *value);
#endif /* _NGX_BPF_H_INCLUDED_ */
+6 -4
View File
@@ -72,10 +72,6 @@ ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr,
ngx_memcpy(ls->addr_text.data, text, len);
#if !(NGX_WIN32)
ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ngx_udp_rbtree_insert_value);
#endif
ls->fd = (ngx_socket_t) -1;
ls->type = SOCK_STREAM;
@@ -1037,6 +1033,12 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle)
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
#if (NGX_QUIC)
if (ls[i].quic) {
continue;
}
#endif
c = ls[i].connection;
if (c) {
+5
View File
@@ -73,6 +73,7 @@ struct ngx_listening_s {
unsigned reuseport:1;
unsigned add_reuseport:1;
unsigned keepalive:2;
unsigned quic:1;
unsigned deferred_accept:1;
unsigned delete_deferred:1;
@@ -147,6 +148,10 @@ struct ngx_connection_s {
ngx_proxy_protocol_t *proxy_protocol;
#if (NGX_QUIC || NGX_COMPAT)
ngx_quic_stream_t *quic;
#endif
#if (NGX_SSL || NGX_COMPAT)
ngx_ssl_connection_t *ssl;
#endif
+7
View File
@@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx_connection_t;
typedef struct ngx_thread_task_s ngx_thread_task_t;
typedef struct ngx_ssl_s ngx_ssl_t;
typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;
typedef struct ngx_quic_stream_s ngx_quic_stream_t;
typedef struct ngx_ssl_connection_s ngx_ssl_connection_t;
typedef struct ngx_udp_connection_s ngx_udp_connection_t;
@@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
#include <ngx_resolver.h>
#if (NGX_OPENSSL)
#include <ngx_event_openssl.h>
#if (NGX_QUIC)
#include <ngx_event_quic.h>
#endif
#endif
#include <ngx_process_cycle.h>
#include <ngx_conf_file.h>
@@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
#include <ngx_connection.h>
#include <ngx_syslog.h>
#include <ngx_proxy_protocol.h>
#if (NGX_HAVE_BPF)
#include <ngx_bpf.h>
#endif
#define LF (u_char) '\n'
+60
View File
@@ -50,3 +50,63 @@ ngx_murmur_hash2(u_char *data, size_t len)
return h;
}
uint64_t
ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed)
{
uint64_t h, k;
h = seed ^ len;
while (len >= 8) {
k = data[0];
k |= data[1] << 8;
k |= data[2] << 16;
k |= data[3] << 24;
k |= (uint64_t)data[4] << 32;
k |= (uint64_t)data[5] << 40;
k |= (uint64_t)data[6] << 48;
k |= (uint64_t)data[7] << 56;
k *= 0xc6a4a7935bd1e995ull;
k ^= k >> 47;
k *= 0xc6a4a7935bd1e995ull;
h ^= k;
h *= 0xc6a4a7935bd1e995ull;
data += 8;
len -= 8;
}
switch (len) {
case 7:
h ^= (uint64_t)data[6] << 48;
/* fall through */
case 6:
h ^= (uint64_t)data[5] << 40;
/* fall through */
case 5:
h ^= (uint64_t)data[4] << 32;
/* fall through */
case 4:
h ^= data[3] << 24;
/* fall through */
case 3:
h ^= data[2] << 16;
/* fall through */
case 2:
h ^= data[1] << 8;
/* fall through */
case 1:
h ^= data[0];
h *= 0xc6a4a7935bd1e995ull;
}
h ^= h >> 47;
h *= 0xc6a4a7935bd1e995ull;
h ^= h >> 47;
return h;
}
+2
View File
@@ -15,5 +15,7 @@
uint32_t ngx_murmur_hash2(u_char *data, size_t len);
uint64_t ngx_murmur_hash2_64(u_char *data, size_t len, uint64_t seed);
#endif /* _NGX_MURMURHASH_H_INCLUDED_ */
+30 -4
View File
@@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle)
ngx_int_t
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
#if (NGX_QUIC)
ngx_connection_t *c;
c = rev->data;
if (c->quic) {
return ngx_quic_handle_read_event(rev, flags);
}
#endif
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
@@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
{
ngx_connection_t *c;
if (lowat) {
c = wev->data;
c = wev->data;
#if (NGX_QUIC)
if (c->quic) {
return ngx_quic_handle_write_event(wev, lowat);
}
#endif
if (lowat) {
if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
return NGX_ERROR;
}
@@ -868,8 +886,16 @@ ngx_event_process_init(ngx_cycle_t *cycle)
#else
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg;
if (c->type == SOCK_STREAM) {
rev->handler = ngx_event_accept;
#if (NGX_QUIC)
} else if (ls[i].quic) {
rev->handler = ngx_quic_recvmsg;
#endif
} else {
rev->handler = ngx_event_recvmsg;
}
#if (NGX_HAVE_REUSEPORT)
+7
View File
@@ -3197,6 +3197,13 @@ ngx_ssl_shutdown(ngx_connection_t *c)
ngx_err_t err;
ngx_uint_t tries;
#if (NGX_QUIC)
if (c->quic) {
/* QUIC streams inherit SSL object */
return NGX_OK;
}
#endif
rc = NGX_OK;
ngx_ssl_ocsp_cleanup(c);
+8
View File
@@ -24,6 +24,14 @@
#include <openssl/engine.h>
#endif
#include <openssl/evp.h>
#if (NGX_QUIC)
#ifdef OPENSSL_IS_BORINGSSL
#include <openssl/hkdf.h>
#include <openssl/chacha.h>
#else
#include <openssl/kdf.h>
#endif
#endif
#include <openssl/hmac.h>
#ifndef OPENSSL_NO_OCSP
#include <openssl/ocsp.h>
+67
View File
@@ -2709,13 +2709,80 @@ ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len)
#else
static ngx_int_t ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl, X509 *cert,
ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify);
ngx_int_t
ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file,
ngx_str_t *responder, ngx_uint_t verify)
{
X509 *cert;
for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
cert;
cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
{
if (file->len) {
if (ngx_ssl_stapling_file(cf, ssl, cert, file, responder, verify)
!= NGX_OK)
{
return NGX_ERROR;
}
}
}
// SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback);
return NGX_OK;
}
ngx_int_t
ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl, X509 *cert,
ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify)
{
#ifdef BORINGSSL_MAKE_DELETER
/*ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
"using boringssl, currently only \"ssl_stapling_file\" is supported. use it as your own risk");*/
BIO *bio;
int len;
u_char buf[2048];
if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
return NGX_ERROR;
}
bio = BIO_new_file((char *) file->data, "r");
if (bio == NULL) {
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
"BIO_new_file(\"%s\") failed", file->data);
return NGX_ERROR;
}
len = BIO_read(bio, buf, sizeof(buf) / sizeof(u_char));
BIO_free(bio);
bio = NULL;
if (len <= 0) {
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
"Read OCSP response file \"%s\" failed: %d", file->data, len);
return NGX_ERROR;
}
if (len >= 2000) {
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
"Unexpected OCSP response file length: %d", len);
return NGX_ERROR;
}
if (!SSL_CTX_set_ocsp_response(ssl->ctx, buf, len)) {
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
"SSL_CTX_set_ocsp_response(ssl->ctx, buf, %d) failed", len);
return NGX_ERROR;
}
#else
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
"\"ssl_stapling\" ignored, not supported");
#endif
return NGX_OK;
}
-7
View File
@@ -12,13 +12,6 @@
#if !(NGX_WIN32)
struct ngx_udp_connection_s {
ngx_rbtree_node_t node;
ngx_connection_t *connection;
ngx_buf_t *buffer;
};
static void ngx_close_accepted_udp_connection(ngx_connection_t *c);
static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf,
size_t size);
+7
View File
@@ -23,6 +23,13 @@
#endif
struct ngx_udp_connection_s {
ngx_rbtree_node_t node;
ngx_connection_t *connection;
ngx_buf_t *buffer;
};
#if (NGX_HAVE_ADDRINFO_CMSG)
typedef union {
+113
View File
@@ -0,0 +1,113 @@
#!/bin/bash
export LANG=C
set -e
if [ $# -lt 1 ]; then
echo "Usage: PROGNAME=foo LICENSE=bar $0 <bpf object file>"
exit 1
fi
self=$0
filename=$1
funcname=$PROGNAME
generate_head()
{
cat << END
/* AUTO-GENERATED, DO NOT EDIT. */
#include <stddef.h>
#include <stdint.h>
#include "ngx_bpf.h"
END
}
generate_tail()
{
cat << END
ngx_bpf_program_t $PROGNAME = {
.relocs = bpf_reloc_prog_$funcname,
.nrelocs = sizeof(bpf_reloc_prog_$funcname)
/ sizeof(bpf_reloc_prog_$funcname[0]),
.ins = bpf_insn_prog_$funcname,
.nins = sizeof(bpf_insn_prog_$funcname)
/ sizeof(bpf_insn_prog_$funcname[0]),
.license = "$LICENSE",
.type = BPF_PROG_TYPE_SK_REUSEPORT,
};
END
}
process_relocations()
{
echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {"
objdump -r $filename | awk '{
if (enabled && $NF > 0) {
off = strtonum(sprintf("0x%s", $1));
name = $3;
printf(" { \"%s\", %d },\n", name, off/8);
}
if ($1 == "OFFSET") {
enabled=1;
}
}'
echo "};"
echo
}
process_section()
{
echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {"
echo " /* opcode dst src offset imm */"
section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname")
# dd doesn't know hex
length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3))
offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6))
for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8)
do
opcode=0x${ins:0:2}
srcdst=0x${ins:2:2}
# bytes are dumped in LE order
offset=0x${ins:6:2}${ins:4:2} # short
immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int
dst="$(($srcdst & 0xF))"
src="$(($srcdst & 0xF0))"
src="$(($src >> 4))"
opcode=$(printf "0x%x" $opcode)
dst=$(printf "BPF_REG_%d" $dst)
src=$(printf "BPF_REG_%d" $src)
offset=$(printf "%d" $offset)
immedi=$(printf "0x%x" $immedi)
printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi
done
cat << END
};
END
}
generate_head
process_relocations
process_section
generate_tail
+30
View File
@@ -0,0 +1,30 @@
CFLAGS=-O2 -Wall
LICENSE=BSD
PROGNAME=ngx_quic_reuseport_helper
RESULT=ngx_event_quic_bpf_code
DEST=../$(RESULT).c
all: $(RESULT)
$(RESULT): $(PROGNAME).o
LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@
DEFS=-DPROGNAME=\"$(PROGNAME)\" \
-DLICENSE_$(LICENSE) \
-DLICENSE=\"$(LICENSE)\" \
$(PROGNAME).o: $(PROGNAME).c
clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@
install: $(RESULT)
cp $(RESULT) $(DEST)
clean:
@rm -f $(RESULT) *.o
debug: $(PROGNAME).o
llvm-objdump -S -no-show-raw-insn $<
.DELETE_ON_ERROR:
@@ -0,0 +1,140 @@
#include <errno.h>
#include <linux/string.h>
#include <linux/udp.h>
#include <linux/bpf.h>
/*
* the bpf_helpers.h is not included into linux-headers, only available
* with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf.
*/
#include <bpf/bpf_helpers.h>
#if !defined(SEC)
#define SEC(NAME) __attribute__((section(NAME), used))
#endif
#if defined(LICENSE_GPL)
/*
* To see debug:
*
* echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable
* cat /sys/kernel/debug/tracing/trace_pipe
* echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable
*/
#define debugmsg(fmt, ...) \
do { \
char __buf[] = fmt; \
bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \
} while (0)
#else
#define debugmsg(fmt, ...)
#endif
char _license[] SEC("license") = LICENSE;
/*****************************************************************************/
#define NGX_QUIC_PKT_LONG 0x80 /* header form */
#define NGX_QUIC_SERVER_CID_LEN 20
#define advance_data(nbytes) \
offset += nbytes; \
if (start + offset > end) { \
debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \
goto failed; \
} \
data = start + offset - 1;
#define ngx_quic_parse_uint64(p) \
(((__u64)(p)[0] << 56) | \
((__u64)(p)[1] << 48) | \
((__u64)(p)[2] << 40) | \
((__u64)(p)[3] << 32) | \
((__u64)(p)[4] << 24) | \
((__u64)(p)[5] << 16) | \
((__u64)(p)[6] << 8) | \
((__u64)(p)[7]))
/*
* actual map object is created by the "bpf" system call,
* all pointers to this variable are replaced by the bpf loader
*/
struct bpf_map_def SEC("maps") ngx_quic_sockmap;
SEC(PROGNAME)
int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx)
{
int rc;
__u64 key;
size_t len, offset;
unsigned char *start, *end, *data, *dcid;
start = ctx->data;
end = (unsigned char *) ctx->data_end;
offset = 0;
advance_data(sizeof(struct udphdr)); /* data at UDP header */
advance_data(1); /* data at QUIC flags */
if (data[0] & NGX_QUIC_PKT_LONG) {
advance_data(4); /* data at QUIC version */
advance_data(1); /* data at DCID len */
len = data[0]; /* read DCID length */
if (len < 8) {
/* it's useless to search for key in such short DCID */
return SK_PASS;
}
} else {
len = NGX_QUIC_SERVER_CID_LEN;
}
dcid = &data[1];
advance_data(len); /* we expect the packet to have full DCID */
/* make verifier happy */
if (dcid + sizeof(__u64) > end) {
goto failed;
}
key = ngx_quic_parse_uint64(dcid);
rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0);
switch (rc) {
case 0:
debugmsg("nginx quic socket selected by key 0x%llx", key);
return SK_PASS;
/* kernel returns positive error numbers, errno.h defines positive */
case -ENOENT:
debugmsg("nginx quic default route for key 0x%llx", key);
/* let the default reuseport logic decide which socket to choose */
return SK_PASS;
default:
debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx",
rc, key);
goto failed;
}
failed:
/*
* SK_DROP will generate ICMP, but we may want to process "invalid" packet
* in userspace quic to investigate further and finally react properly
* (maybe ignore, maybe send something in response or close connection)
*/
return SK_PASS;
}
+1459
View File
@@ -0,0 +1,1459 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c,
ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static void ngx_quic_input_handler(ngx_event_t *rev);
static void ngx_quic_close_timer_handler(ngx_event_t *ev);
static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b,
ngx_quic_conf_t *conf);
static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c,
ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static void ngx_quic_push_handler(ngx_event_t *ev);
static ngx_core_module_t ngx_quic_module_ctx = {
ngx_string("quic"),
NULL,
NULL
};
ngx_module_t ngx_quic_module = {
NGX_MODULE_V1,
&ngx_quic_module_ctx, /* module context */
NULL, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
#if (NGX_DEBUG)
void
ngx_quic_connstate_dbg(ngx_connection_t *c)
{
u_char *p, *last;
ngx_quic_connection_t *qc;
u_char buf[NGX_MAX_ERROR_STR];
p = buf;
last = p + sizeof(buf);
qc = ngx_quic_get_connection(c);
p = ngx_slprintf(p, last, "state:");
if (qc) {
if (qc->error) {
p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : "");
p = ngx_slprintf(p, last, " error:%ui", qc->error);
if (qc->error_reason) {
p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason);
}
}
p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : "");
p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : "");
p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : "");
p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : "");
} else {
p = ngx_slprintf(p, last, " early");
}
if (c->read->timer_set) {
p = ngx_slprintf(p, last,
qc && qc->send_timer_set ? " send:%M" : " read:%M",
c->read->timer.key - ngx_current_msec);
}
if (qc) {
if (qc->push.timer_set) {
p = ngx_slprintf(p, last, " push:%M",
qc->push.timer.key - ngx_current_msec);
}
if (qc->pto.timer_set) {
p = ngx_slprintf(p, last, " pto:%M",
qc->pto.timer.key - ngx_current_msec);
}
if (qc->close.timer_set) {
p = ngx_slprintf(p, last, " close:%M",
qc->close.timer.key - ngx_current_msec);
}
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic %*s", p - buf, buf);
}
#endif
ngx_int_t
ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp)
{
ngx_str_t scid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
scid.data = qc->path->cid->id;
scid.len = qc->path->cid->len;
if (scid.len != ctp->initial_scid.len
|| ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic client initial_source_connection_id mismatch");
return NGX_ERROR;
}
if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE
|| ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
{
qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
qc->error_reason = "invalid maximum packet size";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic maximum packet size is invalid");
return NGX_ERROR;
} else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) {
ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic client maximum packet size truncated");
}
if (ctp->active_connection_id_limit < 2) {
qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
qc->error_reason = "invalid active_connection_id_limit";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic active_connection_id_limit is invalid");
return NGX_ERROR;
}
if (ctp->ack_delay_exponent > 20) {
qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
qc->error_reason = "invalid ack_delay_exponent";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic ack_delay_exponent is invalid");
return NGX_ERROR;
}
if (ctp->max_ack_delay >= 16384) {
qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
qc->error_reason = "invalid max_ack_delay";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic max_ack_delay is invalid");
return NGX_ERROR;
}
if (ctp->max_idle_timeout > 0
&& ctp->max_idle_timeout < qc->tp.max_idle_timeout)
{
qc->tp.max_idle_timeout = ctp->max_idle_timeout;
}
qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi;
qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni;
ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t));
return NGX_OK;
}
void
ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf)
{
ngx_int_t rc;
ngx_quic_connection_t *qc;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run");
rc = ngx_quic_handle_datagram(c, c->buffer, conf);
if (rc != NGX_OK) {
ngx_quic_close_connection(c, rc);
return;
}
/* quic connection is now created */
qc = ngx_quic_get_connection(c);
ngx_add_timer(c->read, qc->tp.max_idle_timeout);
ngx_quic_connstate_dbg(c);
c->read->handler = ngx_quic_input_handler;
return;
}
static ngx_quic_connection_t *
ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
ngx_quic_header_t *pkt)
{
ngx_uint_t i;
ngx_quic_tp_t *ctp;
ngx_quic_connection_t *qc;
qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
if (qc == NULL) {
return NULL;
}
qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t));
if (qc->keys == NULL) {
return NULL;
}
qc->version = pkt->version;
ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
ngx_quic_rbtree_insert_stream);
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ngx_queue_init(&qc->send_ctx[i].frames);
ngx_queue_init(&qc->send_ctx[i].sending);
ngx_queue_init(&qc->send_ctx[i].sent);
qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN;
qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN;
qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN;
qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN;
}
qc->send_ctx[0].level = ssl_encryption_initial;
qc->send_ctx[1].level = ssl_encryption_handshake;
qc->send_ctx[2].level = ssl_encryption_application;
ngx_queue_init(&qc->free_frames);
qc->avg_rtt = NGX_QUIC_INITIAL_RTT;
qc->rttvar = NGX_QUIC_INITIAL_RTT / 2;
qc->min_rtt = NGX_TIMER_INFINITE;
qc->first_rtt = NGX_TIMER_INFINITE;
/*
* qc->latest_rtt = 0
*/
qc->pto.log = c->log;
qc->pto.data = c;
qc->pto.handler = ngx_quic_pto_handler;
qc->pto.cancelable = 1;
qc->push.log = c->log;
qc->push.data = c;
qc->push.handler = ngx_quic_push_handler;
qc->push.cancelable = 1;
qc->path_validation.log = c->log;
qc->path_validation.data = c;
qc->path_validation.handler = ngx_quic_path_validation_handler;
qc->path_validation.cancelable = 1;
qc->conf = conf;
if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) {
return NULL;
}
ctp = &qc->ctp;
/* defaults to be used before actual client parameters are received */
ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
ctp->active_connection_id_limit = 2;
ngx_queue_init(&qc->streams.uninitialized);
ngx_queue_init(&qc->streams.free);
qc->streams.recv_max_data = qc->tp.initial_max_data;
qc->streams.recv_window = qc->streams.recv_max_data;
qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni;
qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi;
qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
ngx_max(2 * qc->tp.max_udp_payload_size,
14720));
qc->congestion.ssthresh = (size_t) -1;
qc->congestion.recovery_start = ngx_current_msec;
if (pkt->validated && pkt->retried) {
qc->tp.retry_scid.len = pkt->dcid.len;
qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid);
if (qc->tp.retry_scid.data == NULL) {
return NULL;
}
}
if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log)
!= NGX_OK)
{
return NULL;
}
qc->validated = pkt->validated;
if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) {
return NULL;
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic connection created");
return qc;
}
static ngx_int_t
ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char *tail, ch;
ngx_uint_t i;
ngx_queue_t *q;
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
/* A stateless reset uses an entire UDP datagram */
if (!pkt->first) {
return NGX_DECLINED;
}
tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN;
for (q = ngx_queue_head(&qc->client_ids);
q != ngx_queue_sentinel(&qc->client_ids);
q = ngx_queue_next(q))
{
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
if (cid->seqnum == 0 || !cid->used) {
/*
* No stateless reset token in initial connection id.
* Don't accept a token from an unused connection id.
*/
continue;
}
/* constant time comparison */
for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) {
ch |= tail[i] ^ cid->sr_token[i];
}
if (ch == 0) {
return NGX_OK;
}
}
return NGX_DECLINED;
}
static void
ngx_quic_input_handler(ngx_event_t *rev)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_connection_t *c;
ngx_quic_connection_t *qc;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler");
c = rev->data;
qc = ngx_quic_get_connection(c);
c->log->action = "handling quic input";
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
"quic client timed out");
ngx_quic_close_connection(c, NGX_DONE);
return;
}
if (c->close) {
qc->error_reason = "graceful shutdown";
ngx_quic_close_connection(c, NGX_OK);
return;
}
if (!rev->ready) {
if (qc->closing) {
ngx_quic_close_connection(c, NGX_OK);
} else if (qc->shutdown) {
ngx_quic_shutdown_quic(c);
}
return;
}
b = c->udp->buffer;
rc = ngx_quic_handle_datagram(c, b, NULL);
if (rc == NGX_ERROR) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
if (rc == NGX_DONE) {
return;
}
/* rc == NGX_OK */
qc->send_timer_set = 0;
ngx_add_timer(rev, qc->tp.max_idle_timeout);
ngx_quic_connstate_dbg(c);
}
void
ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc)
{
ngx_uint_t i;
ngx_pool_t *pool;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (qc == NULL) {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet rejected rc:%i, cleanup connection", rc);
goto quic_done;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic close %s rc:%i",
qc->closing ? "resumed": "initiated", rc);
if (!qc->closing) {
/* drop packets from retransmit queues, no ack is expected */
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ngx_quic_free_frames(c, &qc->send_ctx[i].sent);
}
if (rc == NGX_DONE) {
/*
* RFC 9000, 10.1. Idle Timeout
*
* If a max_idle_timeout is specified by either endpoint in its
* transport parameters (Section 18.2), the connection is silently
* closed and its state is discarded when it remains idle
*/
/* this case also handles some errors from ngx_quic_run() */
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic close silent drain:%d timedout:%d",
qc->draining, c->read->timedout);
} else {
/*
* RFC 9000, 10.2. Immediate Close
*
* An endpoint sends a CONNECTION_CLOSE frame (Section 19.19)
* to terminate the connection immediately.
*/
qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection)
: ssl_encryption_initial;
if (rc == NGX_OK) {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic close immediate drain:%d",
qc->draining);
qc->close.log = c->log;
qc->close.data = c;
qc->close.handler = ngx_quic_close_timer_handler;
qc->close.cancelable = 1;
ctx = ngx_quic_get_send_ctx(qc, qc->error_level);
ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx));
qc->error = NGX_QUIC_ERR_NO_ERROR;
} else {
if (qc->error == 0 && !qc->error_app) {
qc->error = NGX_QUIC_ERR_INTERNAL_ERROR;
}
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic close immediate due to %serror: %ui %s",
qc->error_app ? "app " : "", qc->error,
qc->error_reason ? qc->error_reason : "");
}
(void) ngx_quic_send_cc(c);
if (qc->error_level == ssl_encryption_handshake) {
/* for clients that might not have handshake keys */
qc->error_level = ssl_encryption_initial;
(void) ngx_quic_send_cc(c);
}
}
qc->closing = 1;
}
if (rc == NGX_ERROR && qc->close.timer_set) {
/* do not wait for timer in case of fatal error */
ngx_del_timer(&qc->close);
}
if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) {
return;
}
if (qc->push.timer_set) {
ngx_del_timer(&qc->push);
}
if (qc->pto.timer_set) {
ngx_del_timer(&qc->pto);
}
if (qc->path_validation.timer_set) {
ngx_del_timer(&qc->path_validation);
}
if (qc->push.posted) {
ngx_delete_posted_event(&qc->push);
}
if (qc->close.timer_set) {
return;
}
ngx_quic_close_sockets(c);
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed");
/* may be tested from SSL callback during SSL shutdown */
c->udp = NULL;
quic_done:
if (c->ssl) {
(void) ngx_ssl_shutdown(c);
}
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif
c->destroyed = 1;
pool = c->pool;
ngx_close_connection(c);
ngx_destroy_pool(pool);
}
void
ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
const char *reason)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
qc->error = err;
qc->error_reason = reason;
qc->error_app = 1;
qc->error_ftype = 0;
ngx_quic_close_connection(c, NGX_ERROR);
}
void
ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err,
const char *reason)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
qc->shutdown = 1;
qc->shutdown_code = err;
qc->shutdown_reason = reason;
ngx_quic_shutdown_quic(c);
}
static void
ngx_quic_close_timer_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close timer");
c = ev->data;
ngx_quic_close_connection(c, NGX_DONE);
}
static ngx_int_t
ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b,
ngx_quic_conf_t *conf)
{
size_t size;
u_char *p, *start;
ngx_int_t rc;
ngx_uint_t good;
ngx_quic_path_t *path;
ngx_quic_header_t pkt;
ngx_quic_connection_t *qc;
good = 0;
path = NULL;
size = b->last - b->pos;
p = start = b->pos;
while (p < b->last) {
ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
pkt.raw = b;
pkt.data = p;
pkt.len = b->last - p;
pkt.log = c->log;
pkt.first = (p == start) ? 1 : 0;
pkt.path = path;
pkt.flags = p[0];
pkt.raw->pos++;
rc = ngx_quic_handle_packet(c, conf, &pkt);
#if (NGX_DEBUG)
if (pkt.parsed) {
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet done rc:%i level:%s"
" decr:%d pn:%L perr:%ui",
rc, ngx_quic_level_name(pkt.level),
pkt.decrypted, pkt.pn, pkt.error);
} else {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet done rc:%i parse failed", rc);
}
#endif
if (rc == NGX_ERROR || rc == NGX_DONE) {
return rc;
}
if (rc == NGX_OK) {
good = 1;
}
path = pkt.path; /* preserve packet path from 1st packet */
/* NGX_OK || NGX_DECLINED */
/*
* we get NGX_DECLINED when there are no keys [yet] available
* to decrypt packet.
* Instead of queueing it, we ignore it and rely on the sender's
* retransmission:
*
* RFC 9000, 12.2. Coalescing Packets
*
* For example, if decryption fails (because the keys are
* not available or for any other reason), the receiver MAY either
* discard or buffer the packet for later processing and MUST
* attempt to process the remaining packets.
*
* We also skip packets that don't match connection state
* or cannot be parsed properly.
*/
/* b->pos is at header end, adjust by actual packet length */
b->pos = pkt.data + pkt.len;
p = b->pos;
}
if (!good) {
return NGX_DONE;
}
qc = ngx_quic_get_connection(c);
if (qc) {
qc->received += size;
if ((uint64_t) (c->sent + qc->received) / 8 >
(qc->streams.sent + qc->streams.recv_last) + 1048576)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected");
qc->error = NGX_QUIC_ERR_NO_ERROR;
qc->error_reason = "QUIC flood detected";
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
ngx_quic_header_t *pkt)
{
ngx_int_t rc;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
c->log->action = "parsing quic packet";
rc = ngx_quic_parse_packet(pkt);
if (rc == NGX_ERROR) {
return NGX_DECLINED;
}
pkt->parsed = 1;
c->log->action = "handling quic packet";
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet rx dcid len:%uz %xV",
pkt->dcid.len, &pkt->dcid);
#if (NGX_DEBUG)
if (pkt->level != ssl_encryption_application) {
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet rx scid len:%uz %xV",
pkt->scid.len, &pkt->scid);
}
if (pkt->level == ssl_encryption_initial) {
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic address validation token len:%uz %xV",
pkt->token.len, &pkt->token);
}
#endif
qc = ngx_quic_get_connection(c);
if (qc) {
if (rc == NGX_ABORT) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic unsupported version: 0x%xD", pkt->version);
return NGX_DECLINED;
}
if (pkt->level != ssl_encryption_application) {
if (pkt->version != qc->version) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic version mismatch: 0x%xD", pkt->version);
return NGX_DECLINED;
}
if (pkt->first) {
qsock = ngx_quic_get_socket(c);
if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen,
qc->path->sockaddr, qc->path->socklen, 1)
!= NGX_OK)
{
/* packet comes from unknown path, possibly migration */
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic too early migration attempt");
return NGX_DONE;
}
}
if (ngx_quic_check_csid(qc, pkt) != NGX_OK) {
return NGX_DECLINED;
}
}
rc = ngx_quic_handle_payload(c, pkt);
if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) {
if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic stateless reset packet detected");
qc->draining = 1;
ngx_quic_close_connection(c, NGX_OK);
return NGX_OK;
}
}
return rc;
}
/* packet does not belong to a connection */
if (rc == NGX_ABORT) {
return ngx_quic_negotiate_version(c, pkt);
}
if (pkt->level == ssl_encryption_application) {
return ngx_quic_send_stateless_reset(c, conf, pkt);
}
if (pkt->level != ssl_encryption_initial) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic expected initial, got handshake");
return NGX_ERROR;
}
c->log->action = "handling initial packet";
if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) {
/* RFC 9000, 7.2. Negotiating Connection IDs */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic too short dcid in initial"
" packet: len:%i", pkt->dcid.len);
return NGX_ERROR;
}
/* process retry and initialize connection IDs */
if (pkt->token.len) {
rc = ngx_quic_validate_token(c, conf->av_token_key, pkt);
if (rc == NGX_ERROR) {
/* internal error */
return NGX_ERROR;
} else if (rc == NGX_ABORT) {
/* token cannot be decrypted */
return ngx_quic_send_early_cc(c, pkt,
NGX_QUIC_ERR_INVALID_TOKEN,
"cannot decrypt token");
} else if (rc == NGX_DECLINED) {
/* token is invalid */
if (pkt->retried) {
/* invalid address validation token */
return ngx_quic_send_early_cc(c, pkt,
NGX_QUIC_ERR_INVALID_TOKEN,
"invalid address validation token");
} else if (conf->retry) {
/* invalid NEW_TOKEN */
return ngx_quic_send_retry(c, conf, pkt);
}
}
/* NGX_OK */
} else if (conf->retry) {
return ngx_quic_send_retry(c, conf, pkt);
} else {
pkt->odcid = pkt->dcid;
}
if (ngx_terminate || ngx_exiting) {
if (conf->retry) {
return ngx_quic_send_retry(c, conf, pkt);
}
return NGX_ERROR;
}
c->log->action = "creating quic connection";
qc = ngx_quic_new_connection(c, conf, pkt);
if (qc == NULL) {
return NGX_ERROR;
}
return ngx_quic_handle_payload(c, pkt);
}
static ngx_int_t
ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
ngx_int_t rc;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
qc = ngx_quic_get_connection(c);
qc->error = 0;
qc->error_reason = 0;
c->log->action = "decrypting packet";
if (!ngx_quic_keys_available(qc->keys, pkt->level)) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic no %s keys, ignoring packet",
ngx_quic_level_name(pkt->level));
return NGX_DECLINED;
}
#if !defined (OPENSSL_IS_BORINGSSL)
/* OpenSSL provides read keys for an application level before it's ready */
if (pkt->level == ssl_encryption_application
&& SSL_quic_read_level(c->ssl->connection)
< ssl_encryption_application)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic no %s keys ready, ignoring packet",
ngx_quic_level_name(pkt->level));
return NGX_DECLINED;
}
#endif
pkt->keys = qc->keys;
pkt->key_phase = qc->key_phase;
pkt->plaintext = buf;
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
rc = ngx_quic_decrypt(pkt, &ctx->largest_pn);
if (rc != NGX_OK) {
qc->error = pkt->error;
qc->error_reason = "failed to decrypt packet";
return rc;
}
pkt->decrypted = 1;
c->log->action = "handling decrypted packet";
if (pkt->path == NULL) {
rc = ngx_quic_set_path(c, pkt);
if (rc != NGX_OK) {
return rc;
}
}
if (c->ssl == NULL) {
if (ngx_quic_init_connection(c) != NGX_OK) {
return NGX_ERROR;
}
}
if (pkt->level == ssl_encryption_handshake) {
/*
* RFC 9001, 4.9.1. Discarding Initial Keys
*
* The successful use of Handshake packets indicates
* that no more Initial packets need to be exchanged
*/
ngx_quic_discard_ctx(c, ssl_encryption_initial);
if (!qc->path->validated) {
qc->path->validated = 1;
qc->path->limited = 0;
ngx_quic_path_dbg(c, "in handshake", qc->path);
ngx_post_event(&qc->push, &ngx_posted_events);
}
}
if (qc->closing) {
/*
* RFC 9000, 10.2. Immediate Close
*
* ... delayed or reordered packets are properly discarded.
*
* In the closing state, an endpoint retains only enough information
* to generate a packet containing a CONNECTION_CLOSE frame and to
* identify packets as belonging to the connection.
*/
qc->error_level = pkt->level;
qc->error = NGX_QUIC_ERR_NO_ERROR;
qc->error_reason = "connection is closing, packet discarded";
qc->error_ftype = 0;
qc->error_app = 0;
return ngx_quic_send_cc(c);
}
pkt->received = ngx_current_msec;
c->log->action = "handling payload";
if (pkt->level != ssl_encryption_application) {
return ngx_quic_handle_frames(c, pkt);
}
if (!pkt->key_update) {
return ngx_quic_handle_frames(c, pkt);
}
/* switch keys and generate next on Key Phase change */
qc->key_phase ^= 1;
ngx_quic_keys_switch(c, qc->keys);
rc = ngx_quic_handle_frames(c, pkt);
if (rc != NGX_OK) {
return rc;
}
return ngx_quic_keys_update(c, qc->keys);
}
void
ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level)
{
ngx_queue_t *q;
ngx_quic_frame_t *f;
ngx_quic_socket_t *qsock;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!ngx_quic_keys_available(qc->keys, level)) {
return;
}
ngx_quic_keys_discard(qc->keys, level);
qc->pto_count = 0;
ctx = ngx_quic_get_send_ctx(qc, level);
ngx_quic_free_buffer(c, &ctx->crypto);
while (!ngx_queue_empty(&ctx->sent)) {
q = ngx_queue_head(&ctx->sent);
ngx_queue_remove(q);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_quic_congestion_ack(c, f);
ngx_quic_free_frame(c, f);
}
while (!ngx_queue_empty(&ctx->frames)) {
q = ngx_queue_head(&ctx->frames);
ngx_queue_remove(q);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_quic_free_frame(c, f);
}
if (level == ssl_encryption_initial) {
/* close temporary listener with odcid */
qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN);
if (qsock) {
ngx_quic_close_socket(c, qsock);
}
}
ctx->send_ack = 0;
ngx_quic_set_lost_timer(c);
}
static ngx_int_t
ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt)
{
ngx_queue_t *q;
ngx_quic_client_id_t *cid;
for (q = ngx_queue_head(&qc->client_ids);
q != ngx_queue_sentinel(&qc->client_ids);
q = ngx_queue_next(q))
{
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
if (pkt->scid.len == cid->len
&& ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0)
{
return NGX_OK;
}
}
ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid");
return NGX_ERROR;
}
static ngx_int_t
ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char *end, *p;
ssize_t len;
ngx_buf_t buf;
ngx_uint_t do_close, nonprobing;
ngx_chain_t chain;
ngx_quic_frame_t frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
p = pkt->payload.data;
end = p + pkt->payload.len;
do_close = 0;
nonprobing = 0;
while (p < end) {
c->log->action = "parsing frames";
ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
ngx_memzero(&buf, sizeof(ngx_buf_t));
buf.temporary = 1;
chain.buf = &buf;
chain.next = NULL;
frame.data = &chain;
len = ngx_quic_parse_frame(pkt, p, end, &frame);
if (len < 0) {
qc->error = pkt->error;
return NGX_ERROR;
}
ngx_quic_log_frame(c->log, &frame, 0);
c->log->action = "handling frames";
p += len;
switch (frame.type) {
/* probing frames */
case NGX_QUIC_FT_PADDING:
case NGX_QUIC_FT_PATH_CHALLENGE:
case NGX_QUIC_FT_PATH_RESPONSE:
case NGX_QUIC_FT_NEW_CONNECTION_ID:
break;
/* non-probing frames */
default:
nonprobing = 1;
break;
}
switch (frame.type) {
case NGX_QUIC_FT_ACK:
if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) {
return NGX_ERROR;
}
continue;
case NGX_QUIC_FT_PADDING:
/* no action required */
continue;
case NGX_QUIC_FT_CONNECTION_CLOSE:
case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
do_close = 1;
continue;
}
/* got there with ack-eliciting packet */
pkt->need_ack = 1;
switch (frame.type) {
case NGX_QUIC_FT_CRYPTO:
if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_PING:
break;
case NGX_QUIC_FT_STREAM:
if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_MAX_DATA:
if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_STREAMS_BLOCKED:
case NGX_QUIC_FT_STREAMS_BLOCKED2:
if (ngx_quic_handle_streams_blocked_frame(c, pkt,
&frame.u.streams_blocked)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_DATA_BLOCKED:
if (ngx_quic_handle_data_blocked_frame(c, pkt,
&frame.u.data_blocked)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
if (ngx_quic_handle_stream_data_blocked_frame(c, pkt,
&frame.u.stream_data_blocked)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_MAX_STREAM_DATA:
if (ngx_quic_handle_max_stream_data_frame(c, pkt,
&frame.u.max_stream_data)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_RESET_STREAM:
if (ngx_quic_handle_reset_stream_frame(c, pkt,
&frame.u.reset_stream)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_STOP_SENDING:
if (ngx_quic_handle_stop_sending_frame(c, pkt,
&frame.u.stop_sending)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_MAX_STREAMS:
case NGX_QUIC_FT_MAX_STREAMS2:
if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_PATH_CHALLENGE:
if (ngx_quic_handle_path_challenge_frame(c, pkt,
&frame.u.path_challenge)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_PATH_RESPONSE:
if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_NEW_CONNECTION_ID:
if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
if (ngx_quic_handle_retire_connection_id_frame(c,
&frame.u.retire_cid)
!= NGX_OK)
{
return NGX_ERROR;
}
break;
default:
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic missing frame handler");
return NGX_ERROR;
}
}
if (p != end) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic trailing garbage in payload:%ui bytes", end - p);
qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
return NGX_ERROR;
}
if (do_close) {
qc->draining = 1;
ngx_quic_close_connection(c, NGX_OK);
}
if (pkt->path != qc->path && nonprobing) {
/*
* RFC 9000, 9.2. Initiating Connection Migration
*
* An endpoint can migrate a connection to a new local
* address by sending packets containing non-probing frames
* from that address.
*/
if (ngx_quic_handle_migration(c, pkt) != NGX_OK) {
return NGX_ERROR;
}
}
if (ngx_quic_ack_packet(c, pkt) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
static void
ngx_quic_push_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler");
c = ev->data;
if (ngx_quic_output(c) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
ngx_quic_connstate_dbg(c);
}
void
ngx_quic_shutdown_quic(ngx_connection_t *c)
{
ngx_rbtree_t *tree;
ngx_rbtree_node_t *node;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (qc->closing) {
return;
}
tree = &qc->streams.tree;
if (tree->root != tree->sentinel) {
for (node = ngx_rbtree_min(tree->root, tree->sentinel);
node;
node = ngx_rbtree_next(tree, node))
{
qs = (ngx_quic_stream_t *) node;
if (!qs->cancelable) {
return;
}
}
}
ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason);
}
+123
View File
@@ -0,0 +1,123 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
#define _NGX_EVENT_QUIC_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527
#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32
#define NGX_QUIC_SR_KEY_LEN 32
#define NGX_QUIC_AV_KEY_LEN 32
#define NGX_QUIC_SR_TOKEN_LEN 16
#define NGX_QUIC_MIN_INITIAL_SIZE 1200
#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01
#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02
typedef enum {
NGX_QUIC_STREAM_SEND_READY = 0,
NGX_QUIC_STREAM_SEND_SEND,
NGX_QUIC_STREAM_SEND_DATA_SENT,
NGX_QUIC_STREAM_SEND_DATA_RECVD,
NGX_QUIC_STREAM_SEND_RESET_SENT,
NGX_QUIC_STREAM_SEND_RESET_RECVD
} ngx_quic_stream_send_state_e;
typedef enum {
NGX_QUIC_STREAM_RECV_RECV = 0,
NGX_QUIC_STREAM_RECV_SIZE_KNOWN,
NGX_QUIC_STREAM_RECV_DATA_RECVD,
NGX_QUIC_STREAM_RECV_DATA_READ,
NGX_QUIC_STREAM_RECV_RESET_RECVD,
NGX_QUIC_STREAM_RECV_RESET_READ
} ngx_quic_stream_recv_state_e;
typedef struct {
uint64_t size;
uint64_t offset;
uint64_t last_offset;
ngx_chain_t *chain;
ngx_chain_t *last_chain;
} ngx_quic_buffer_t;
typedef struct {
ngx_ssl_t *ssl;
ngx_flag_t retry;
ngx_flag_t gso_enabled;
ngx_flag_t disable_active_migration;
ngx_msec_t timeout;
ngx_str_t host_key;
size_t mtu;
size_t stream_buffer_size;
ngx_uint_t max_concurrent_streams_bidi;
ngx_uint_t max_concurrent_streams_uni;
ngx_uint_t active_connection_id_limit;
ngx_int_t stream_close_code;
ngx_int_t stream_reject_code_uni;
ngx_int_t stream_reject_code_bidi;
u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
} ngx_quic_conf_t;
struct ngx_quic_stream_s {
ngx_rbtree_node_t node;
ngx_queue_t queue;
ngx_connection_t *parent;
ngx_connection_t *connection;
uint64_t id;
uint64_t acked;
uint64_t send_max_data;
uint64_t send_offset;
uint64_t send_final_size;
uint64_t recv_max_data;
uint64_t recv_offset;
uint64_t recv_window;
uint64_t recv_last;
uint64_t recv_final_size;
ngx_quic_buffer_t send;
ngx_quic_buffer_t recv;
ngx_quic_stream_send_state_e send_state;
ngx_quic_stream_recv_state_e recv_state;
ngx_uint_t cancelable; /* unsigned cancelable:1; */
};
void ngx_quic_recvmsg(ngx_event_t *ev);
void ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf);
ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi);
void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
const char *reason);
void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err,
const char *reason);
ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err);
ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how);
ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat);
ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len,
ngx_str_t *dcid);
ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label,
ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len);
#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */
+1193
View File
@@ -0,0 +1,1193 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_MAX_ACK_GAP 2
/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */
#define NGX_QUIC_PKT_THR 3 /* packets */
/* RFC 9002, 6.1.2. Time Threshold: kGranularity */
#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */
/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */
#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3
/* send time of ACK'ed packets */
typedef struct {
ngx_msec_t max_pn;
ngx_msec_t oldest;
ngx_msec_t newest;
} ngx_quic_ack_stat_t;
static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc);
static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
enum ssl_encryption_level_t level, ngx_msec_t send_time);
static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max,
ngx_quic_ack_stat_t *st);
static void ngx_quic_drop_ack_ranges(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx, uint64_t pn);
static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c,
ngx_quic_ack_stat_t *st);
static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c);
static void ngx_quic_persistent_congestion(ngx_connection_t *c);
static void ngx_quic_congestion_lost(ngx_connection_t *c,
ngx_quic_frame_t *frame);
static void ngx_quic_lost_handler(ngx_event_t *ev);
/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */
static ngx_inline ngx_msec_t
ngx_quic_lost_threshold(ngx_quic_connection_t *qc)
{
ngx_msec_t thr;
thr = ngx_max(qc->latest_rtt, qc->avg_rtt);
thr += thr >> 3;
return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY);
}
ngx_int_t
ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
ngx_quic_frame_t *f)
{
ssize_t n;
u_char *pos, *end;
uint64_t min, max, gap, range;
ngx_uint_t i;
ngx_quic_ack_stat_t send_time;
ngx_quic_send_ctx_t *ctx;
ngx_quic_ack_frame_t *ack;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_handle_ack_frame level:%d", pkt->level);
ack = &f->u.ack;
/*
* RFC 9000, 19.3.1. ACK Ranges
*
* If any computed packet number is negative, an endpoint MUST
* generate a connection error of type FRAME_ENCODING_ERROR.
*/
if (ack->first_range > ack->largest) {
qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic invalid first range in ack frame");
return NGX_ERROR;
}
min = ack->largest - ack->first_range;
max = ack->largest;
send_time.oldest = NGX_TIMER_INFINITE;
send_time.newest = NGX_TIMER_INFINITE;
if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
!= NGX_OK)
{
return NGX_ERROR;
}
/* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */
if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) {
ctx->largest_ack = max;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic updated largest received ack:%uL", max);
/*
* RFC 9002, 5.1. Generating RTT Samples
*
* An endpoint generates an RTT sample on receiving an
* ACK frame that meets the following two conditions:
*
* - the largest acknowledged packet number is newly acknowledged
* - at least one of the newly acknowledged packets was ack-eliciting.
*/
if (send_time.max_pn != NGX_TIMER_INFINITE) {
ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn);
}
}
if (f->data) {
pos = f->data->buf->pos;
end = f->data->buf->last;
} else {
pos = NULL;
end = NULL;
}
for (i = 0; i < ack->range_count; i++) {
n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range);
if (n == NGX_ERROR) {
return NGX_ERROR;
}
pos += n;
if (gap + 2 > min) {
qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic invalid range:%ui in ack frame", i);
return NGX_ERROR;
}
max = min - gap - 2;
if (range > max) {
qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic invalid range:%ui in ack frame", i);
return NGX_ERROR;
}
min = max - range;
if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
!= NGX_OK)
{
return NGX_ERROR;
}
}
return ngx_quic_detect_lost(c, &send_time);
}
static void
ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
enum ssl_encryption_level_t level, ngx_msec_t send_time)
{
ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
latest_rtt = ngx_current_msec - send_time;
qc->latest_rtt = latest_rtt;
if (qc->min_rtt == NGX_TIMER_INFINITE) {
qc->min_rtt = latest_rtt;
qc->avg_rtt = latest_rtt;
qc->rttvar = latest_rtt / 2;
qc->first_rtt = ngx_current_msec;
} else {
qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt);
ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000;
if (c->ssl->handshaked) {
ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay);
}
adjusted_rtt = latest_rtt;
if (qc->min_rtt + ack_delay < latest_rtt) {
adjusted_rtt -= ack_delay;
}
qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3);
rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt));
qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2);
}
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic rtt sample latest:%M min:%M avg:%M var:%M",
latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar);
}
static ngx_int_t
ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st)
{
ngx_uint_t found;
ngx_queue_t *q;
ngx_quic_frame_t *f;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
st->max_pn = NGX_TIMER_INFINITE;
found = 0;
q = ngx_queue_head(&ctx->sent);
while (q != ngx_queue_sentinel(&ctx->sent)) {
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
q = ngx_queue_next(q);
if (f->pnum > max) {
break;
}
if (f->pnum >= min) {
ngx_quic_congestion_ack(c, f);
switch (f->type) {
case NGX_QUIC_FT_ACK:
case NGX_QUIC_FT_ACK_ECN:
ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest);
break;
case NGX_QUIC_FT_STREAM:
ngx_quic_handle_stream_ack(c, f);
break;
}
if (f->pnum == max) {
st->max_pn = f->last;
}
/* save earliest and latest send times of frames ack'ed */
if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) {
st->oldest = f->last;
}
if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) {
st->newest = f->last;
}
ngx_queue_remove(&f->queue);
ngx_quic_free_frame(c, f);
found = 1;
}
}
if (!found) {
if (max < ctx->pnum) {
/* duplicate ACK or ACK for non-ack-eliciting frame */
return NGX_OK;
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic ACK for the packet not sent");
qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
qc->error_ftype = NGX_QUIC_FT_ACK;
qc->error_reason = "unknown packet number";
return NGX_ERROR;
}
if (!qc->push.timer_set) {
ngx_post_event(&qc->push, &ngx_posted_events);
}
qc->pto_count = 0;
return NGX_OK;
}
void
ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
{
ngx_uint_t blocked;
ngx_msec_t timer;
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
if (f->plen == 0) {
return;
}
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
blocked = (cg->in_flight >= cg->window) ? 1 : 0;
cg->in_flight -= f->plen;
timer = f->last - cg->recovery_start;
if ((ngx_msec_int_t) timer <= 0) {
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion ack recovery win:%uz ss:%z if:%uz",
cg->window, cg->ssthresh, cg->in_flight);
goto done;
}
if (cg->window < cg->ssthresh) {
cg->window += f->plen;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion slow start win:%uz ss:%z if:%uz",
cg->window, cg->ssthresh, cg->in_flight);
} else {
cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion avoidance win:%uz ss:%z if:%uz",
cg->window, cg->ssthresh, cg->in_flight);
}
/* prevent recovery_start from wrapping */
timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2;
if ((ngx_msec_int_t) timer < 0) {
cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2;
}
done:
if (blocked && cg->in_flight < cg->window) {
ngx_post_event(&qc->push, &ngx_posted_events);
}
}
static void
ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
uint64_t pn)
{
uint64_t base;
ngx_uint_t i, smallest, largest;
ngx_quic_ack_range_t *r;
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL"
" fr:%uL nranges:%ui", pn, ctx->largest_range,
ctx->first_range, ctx->nranges);
base = ctx->largest_range;
if (base == NGX_QUIC_UNSET_PN) {
return;
}
if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) {
ctx->pending_ack = NGX_QUIC_UNSET_PN;
}
largest = base;
smallest = largest - ctx->first_range;
if (pn >= largest) {
ctx->largest_range = NGX_QUIC_UNSET_PN;
ctx->first_range = 0;
ctx->nranges = 0;
return;
}
if (pn >= smallest) {
ctx->first_range = largest - pn - 1;
ctx->nranges = 0;
return;
}
for (i = 0; i < ctx->nranges; i++) {
r = &ctx->ranges[i];
largest = smallest - r->gap - 2;
smallest = largest - r->range;
if (pn >= largest) {
ctx->nranges = i;
return;
}
if (pn >= smallest) {
r->range = largest - pn - 1;
ctx->nranges = i + 1;
return;
}
}
}
static ngx_int_t
ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st)
{
ngx_uint_t i, nlost;
ngx_msec_t now, wait, thr, oldest, newest;
ngx_queue_t *q;
ngx_quic_frame_t *start;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
now = ngx_current_msec;
thr = ngx_quic_lost_threshold(qc);
/* send time of lost packets across all send contexts */
oldest = NGX_TIMER_INFINITE;
newest = NGX_TIMER_INFINITE;
nlost = 0;
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ctx = &qc->send_ctx[i];
if (ctx->largest_ack == NGX_QUIC_UNSET_PN) {
continue;
}
while (!ngx_queue_empty(&ctx->sent)) {
q = ngx_queue_head(&ctx->sent);
start = ngx_queue_data(q, ngx_quic_frame_t, queue);
if (start->pnum > ctx->largest_ack) {
break;
}
wait = start->last + thr - now;
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic detect_lost pnum:%uL thr:%M wait:%i level:%d",
start->pnum, thr, (ngx_int_t) wait, start->level);
if ((ngx_msec_int_t) wait > 0
&& ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR)
{
break;
}
if (start->last > qc->first_rtt) {
if (oldest == NGX_TIMER_INFINITE || start->last < oldest) {
oldest = start->last;
}
if (newest == NGX_TIMER_INFINITE || start->last > newest) {
newest = start->last;
}
nlost++;
}
ngx_quic_resend_frames(c, ctx);
}
}
/* RFC 9002, 7.6.2. Establishing Persistent Congestion */
/*
* Once acknowledged, packets are no longer tracked. Thus no send time
* information is available for such packets. This limits persistent
* congestion algorithm to packets mentioned within ACK ranges of the
* latest ACK frame.
*/
if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) {
if (newest - oldest > ngx_quic_pcg_duration(c)) {
ngx_quic_persistent_congestion(c);
}
}
ngx_quic_set_lost_timer(c);
return NGX_OK;
}
static ngx_msec_t
ngx_quic_pcg_duration(ngx_connection_t *c)
{
ngx_msec_t duration;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
duration = qc->avg_rtt;
duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
duration += qc->ctp.max_ack_delay;
duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR;
return duration;
}
static void
ngx_quic_persistent_congestion(ngx_connection_t *c)
{
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
cg->recovery_start = ngx_current_msec;
cg->window = qc->tp.max_udp_payload_size * 2;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic persistent congestion win:%uz", cg->window);
}
void
ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
{
ngx_queue_t *q;
ngx_quic_frame_t *f, *start;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
q = ngx_queue_head(&ctx->sent);
start = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic resend packet pnum:%uL", start->pnum);
ngx_quic_congestion_lost(c, start);
do {
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
if (f->pnum != start->pnum) {
break;
}
q = ngx_queue_next(q);
ngx_queue_remove(&f->queue);
switch (f->type) {
case NGX_QUIC_FT_ACK:
case NGX_QUIC_FT_ACK_ECN:
if (ctx->level == ssl_encryption_application) {
/* force generation of most recent acknowledgment */
ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
}
ngx_quic_free_frame(c, f);
break;
case NGX_QUIC_FT_PING:
case NGX_QUIC_FT_PATH_RESPONSE:
case NGX_QUIC_FT_CONNECTION_CLOSE:
ngx_quic_free_frame(c, f);
break;
case NGX_QUIC_FT_MAX_DATA:
f->u.max_data.max_data = qc->streams.recv_max_data;
ngx_quic_queue_frame(qc, f);
break;
case NGX_QUIC_FT_MAX_STREAMS:
case NGX_QUIC_FT_MAX_STREAMS2:
f->u.max_streams.limit = f->u.max_streams.bidi
? qc->streams.client_max_streams_bidi
: qc->streams.client_max_streams_uni;
ngx_quic_queue_frame(qc, f);
break;
case NGX_QUIC_FT_MAX_STREAM_DATA:
qs = ngx_quic_find_stream(&qc->streams.tree,
f->u.max_stream_data.id);
if (qs == NULL) {
ngx_quic_free_frame(c, f);
break;
}
f->u.max_stream_data.limit = qs->recv_max_data;
ngx_quic_queue_frame(qc, f);
break;
case NGX_QUIC_FT_STREAM:
qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
if (qs) {
if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT
|| qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD)
{
ngx_quic_free_frame(c, f);
break;
}
}
/* fall through */
default:
ngx_queue_insert_tail(&ctx->frames, &f->queue);
}
} while (q != ngx_queue_sentinel(&ctx->sent));
if (qc->closing) {
return;
}
ngx_post_event(&qc->push, &ngx_posted_events);
}
static void
ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f)
{
ngx_uint_t blocked;
ngx_msec_t timer;
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
if (f->plen == 0) {
return;
}
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
blocked = (cg->in_flight >= cg->window) ? 1 : 0;
cg->in_flight -= f->plen;
f->plen = 0;
timer = f->last - cg->recovery_start;
if ((ngx_msec_int_t) timer <= 0) {
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion lost recovery win:%uz ss:%z if:%uz",
cg->window, cg->ssthresh, cg->in_flight);
goto done;
}
cg->recovery_start = ngx_current_msec;
cg->window /= 2;
if (cg->window < qc->tp.max_udp_payload_size * 2) {
cg->window = qc->tp.max_udp_payload_size * 2;
}
cg->ssthresh = cg->window;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion lost win:%uz ss:%z if:%uz",
cg->window, cg->ssthresh, cg->in_flight);
done:
if (blocked && cg->in_flight < cg->window) {
ngx_post_event(&qc->push, &ngx_posted_events);
}
}
void
ngx_quic_set_lost_timer(ngx_connection_t *c)
{
ngx_uint_t i;
ngx_msec_t now;
ngx_queue_t *q;
ngx_msec_int_t lost, pto, w;
ngx_quic_frame_t *f;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
now = ngx_current_msec;
lost = -1;
pto = -1;
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ctx = &qc->send_ctx[i];
if (ngx_queue_empty(&ctx->sent)) {
continue;
}
if (ctx->largest_ack != NGX_QUIC_UNSET_PN) {
q = ngx_queue_head(&ctx->sent);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now);
if (f->pnum <= ctx->largest_ack) {
if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) {
w = 0;
}
if (lost == -1 || w < lost) {
lost = w;
}
}
}
q = ngx_queue_last(&ctx->sent);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now);
if (w < 0) {
w = 0;
}
if (pto == -1 || w < pto) {
pto = w;
}
}
if (qc->pto.timer_set) {
ngx_del_timer(&qc->pto);
}
if (lost != -1) {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic lost timer lost:%M", lost);
qc->pto.handler = ngx_quic_lost_handler;
ngx_add_timer(&qc->pto, lost);
return;
}
if (pto != -1) {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic lost timer pto:%M", pto);
qc->pto.handler = ngx_quic_pto_handler;
ngx_add_timer(&qc->pto, pto);
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset");
}
ngx_msec_t
ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
{
ngx_msec_t duration;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
/* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */
duration = qc->avg_rtt;
duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
duration <<= qc->pto_count;
if (qc->congestion.in_flight == 0) { /* no in-flight packets */
return duration;
}
if (ctx->level == ssl_encryption_application && c->ssl->handshaked) {
duration += qc->ctp.max_ack_delay << qc->pto_count;
}
return duration;
}
static
void ngx_quic_lost_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer");
c = ev->data;
if (ngx_quic_detect_lost(c, NULL) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
}
ngx_quic_connstate_dbg(c);
}
void
ngx_quic_pto_handler(ngx_event_t *ev)
{
ngx_uint_t i;
ngx_msec_t now;
ngx_queue_t *q, *next;
ngx_connection_t *c;
ngx_quic_frame_t *f;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer");
c = ev->data;
qc = ngx_quic_get_connection(c);
now = ngx_current_msec;
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ctx = &qc->send_ctx[i];
if (ngx_queue_empty(&ctx->sent)) {
continue;
}
q = ngx_queue_head(&ctx->sent);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
if (f->pnum <= ctx->largest_ack
&& ctx->largest_ack != NGX_QUIC_UNSET_PN)
{
continue;
}
if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) {
continue;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic pto %s pto_count:%ui",
ngx_quic_level_name(ctx->level), qc->pto_count);
for (q = ngx_queue_head(&ctx->frames);
q != ngx_queue_sentinel(&ctx->frames);
/* void */)
{
next = ngx_queue_next(q);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
if (f->type == NGX_QUIC_FT_PING) {
ngx_queue_remove(q);
ngx_quic_free_frame(c, f);
}
q = next;
}
for (q = ngx_queue_head(&ctx->sent);
q != ngx_queue_sentinel(&ctx->sent);
/* void */)
{
next = ngx_queue_next(q);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
if (f->type == NGX_QUIC_FT_PING) {
ngx_quic_congestion_lost(c, f);
ngx_queue_remove(q);
ngx_quic_free_frame(c, f);
}
q = next;
}
/* enforce 2 udp datagrams */
f = ngx_quic_alloc_frame(c);
if (f == NULL) {
break;
}
f->level = ctx->level;
f->type = NGX_QUIC_FT_PING;
f->flush = 1;
ngx_quic_queue_frame(qc, f);
f = ngx_quic_alloc_frame(c);
if (f == NULL) {
break;
}
f->level = ctx->level;
f->type = NGX_QUIC_FT_PING;
ngx_quic_queue_frame(qc, f);
}
qc->pto_count++;
ngx_quic_connstate_dbg(c);
}
ngx_int_t
ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
uint64_t base, largest, smallest, gs, ge, gap, range, pn;
uint64_t prev_pending;
ngx_uint_t i, nr;
ngx_quic_send_ctx_t *ctx;
ngx_quic_ack_range_t *r;
ngx_quic_connection_t *qc;
c->log->action = "preparing ack";
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL"
" nranges:%ui", pkt->pn, (int64_t) ctx->largest_range,
ctx->first_range, ctx->nranges);
prev_pending = ctx->pending_ack;
if (pkt->need_ack) {
ngx_post_event(&qc->push, &ngx_posted_events);
if (ctx->send_ack == 0) {
ctx->ack_delay_start = ngx_current_msec;
}
ctx->send_ack++;
if (ctx->pending_ack == NGX_QUIC_UNSET_PN
|| ctx->pending_ack < pkt->pn)
{
ctx->pending_ack = pkt->pn;
}
}
base = ctx->largest_range;
pn = pkt->pn;
if (base == NGX_QUIC_UNSET_PN) {
ctx->largest_range = pn;
ctx->largest_received = pkt->received;
return NGX_OK;
}
if (base == pn) {
return NGX_OK;
}
largest = base;
smallest = largest - ctx->first_range;
if (pn > base) {
if (pn - base == 1) {
ctx->first_range++;
ctx->largest_range = pn;
ctx->largest_received = pkt->received;
return NGX_OK;
} else {
/* new gap in front of current largest */
/* no place for new range, send current range as is */
if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
if (prev_pending != NGX_QUIC_UNSET_PN) {
if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
return NGX_ERROR;
}
}
if (prev_pending == ctx->pending_ack || !pkt->need_ack) {
ctx->pending_ack = NGX_QUIC_UNSET_PN;
}
}
gap = pn - base - 2;
range = ctx->first_range;
ctx->first_range = 0;
ctx->largest_range = pn;
ctx->largest_received = pkt->received;
/* packet is out of order, force send */
if (pkt->need_ack) {
ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
}
i = 0;
goto insert;
}
}
/* pn < base, perform lookup in existing ranges */
/* packet is out of order */
if (pkt->need_ack) {
ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
}
if (pn >= smallest && pn <= largest) {
return NGX_OK;
}
#if (NGX_SUPPRESS_WARN)
r = NULL;
#endif
for (i = 0; i < ctx->nranges; i++) {
r = &ctx->ranges[i];
ge = smallest - 1;
gs = ge - r->gap;
if (pn >= gs && pn <= ge) {
if (gs == ge) {
/* gap size is exactly one packet, now filled */
/* data moves to previous range, current is removed */
if (i == 0) {
ctx->first_range += r->range + 2;
} else {
ctx->ranges[i - 1].range += r->range + 2;
}
nr = ctx->nranges - i - 1;
if (nr) {
ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1],
sizeof(ngx_quic_ack_range_t) * nr);
}
ctx->nranges--;
} else if (pn == gs) {
/* current gap shrinks from tail (current range grows) */
r->gap--;
r->range++;
} else if (pn == ge) {
/* current gap shrinks from head (previous range grows) */
r->gap--;
if (i == 0) {
ctx->first_range++;
} else {
ctx->ranges[i - 1].range++;
}
} else {
/* current gap is split into two parts */
gap = ge - pn - 1;
range = 0;
if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
if (prev_pending != NGX_QUIC_UNSET_PN) {
if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
return NGX_ERROR;
}
}
if (prev_pending == ctx->pending_ack || !pkt->need_ack) {
ctx->pending_ack = NGX_QUIC_UNSET_PN;
}
}
r->gap = pn - gs - 1;
goto insert;
}
return NGX_OK;
}
largest = smallest - r->gap - 2;
smallest = largest - r->range;
if (pn >= smallest && pn <= largest) {
/* this packet number is already known */
return NGX_OK;
}
}
if (pn == smallest - 1) {
/* extend first or last range */
if (i == 0) {
ctx->first_range++;
} else {
r->range++;
}
return NGX_OK;
}
/* nothing found, add new range at the tail */
if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
/* packet is too old to keep it */
if (pkt->need_ack) {
return ngx_quic_send_ack_range(c, ctx, pn, pn);
}
return NGX_OK;
}
gap = smallest - 2 - pn;
range = 0;
insert:
if (ctx->nranges < NGX_QUIC_MAX_RANGES) {
ctx->nranges++;
}
ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i],
sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1));
ctx->ranges[i].gap = gap;
ctx->ranges[i].range = range;
return NGX_OK;
}
ngx_int_t
ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
{
ngx_msec_t delay;
ngx_quic_connection_t *qc;
if (!ctx->send_ack) {
return NGX_OK;
}
if (ctx->level == ssl_encryption_application) {
delay = ngx_current_msec - ctx->ack_delay_start;
qc = ngx_quic_get_connection(c);
if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP
&& delay < qc->tp.max_ack_delay)
{
if (!qc->push.timer_set && !qc->closing) {
ngx_add_timer(&qc->push,
qc->tp.max_ack_delay - delay);
}
return NGX_OK;
}
}
if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
return NGX_ERROR;
}
ctx->send_ack = 0;
return NGX_OK;
}
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_
#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_frame_t *f);
void ngx_quic_congestion_ack(ngx_connection_t *c,
ngx_quic_frame_t *frame);
void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
void ngx_quic_set_lost_timer(ngx_connection_t *c);
void ngx_quic_pto_handler(ngx_event_t *ev);
ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c,
ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx);
#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */
+657
View File
@@ -0,0 +1,657 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS"
#define NGX_QUIC_BPF_VARSEP ';'
#define NGX_QUIC_BPF_ADDRSEP '#'
#define ngx_quic_bpf_get_conf(cycle) \
(ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module)
#define ngx_quic_bpf_get_old_conf(cycle) \
cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \
: NULL
#define ngx_core_get_conf(cycle) \
(ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module)
typedef struct {
ngx_queue_t queue;
int map_fd;
struct sockaddr *sockaddr;
socklen_t socklen;
ngx_uint_t unused; /* unsigned unused:1; */
} ngx_quic_sock_group_t;
typedef struct {
ngx_flag_t enabled;
ngx_uint_t map_size;
ngx_queue_t groups; /* of ngx_quic_sock_group_t */
} ngx_quic_bpf_conf_t;
static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle);
static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle);
static void ngx_quic_bpf_cleanup(void *data);
static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd,
const char *name);
static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf,
ngx_listening_t *ls);
static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle,
struct sockaddr *sa, socklen_t socklen);
static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle,
ngx_listening_t *ls);
static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle,
ngx_listening_t *ls);
static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle,
ngx_listening_t *ls);
static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log);
static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle);
static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle);
extern ngx_bpf_program_t ngx_quic_reuseport_helper;
static ngx_command_t ngx_quic_bpf_commands[] = {
{ ngx_string("quic_bpf"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_quic_bpf_conf_t, enabled),
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_quic_bpf_module_ctx = {
ngx_string("quic_bpf"),
ngx_quic_bpf_create_conf,
NULL
};
ngx_module_t ngx_quic_bpf_module = {
NGX_MODULE_V1,
&ngx_quic_bpf_module_ctx, /* module context */
ngx_quic_bpf_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
ngx_quic_bpf_module_init, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void *
ngx_quic_bpf_create_conf(ngx_cycle_t *cycle)
{
ngx_quic_bpf_conf_t *bcf;
bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t));
if (bcf == NULL) {
return NULL;
}
bcf->enabled = NGX_CONF_UNSET;
bcf->map_size = NGX_CONF_UNSET_UINT;
ngx_queue_init(&bcf->groups);
return bcf;
}
static ngx_int_t
ngx_quic_bpf_module_init(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_listening_t *ls;
ngx_core_conf_t *ccf;
ngx_pool_cleanup_t *cln;
ngx_quic_bpf_conf_t *bcf;
if (ngx_test_config) {
/*
* during config test, SO_REUSEPORT socket option is
* not set, thus making further processing meaningless
*/
return NGX_OK;
}
ccf = ngx_core_get_conf(cycle);
bcf = ngx_quic_bpf_get_conf(cycle);
ngx_conf_init_value(bcf->enabled, 0);
bcf->map_size = ccf->worker_processes * 4;
cln = ngx_pool_cleanup_add(cycle->pool, 0);
if (cln == NULL) {
goto failed;
}
cln->data = bcf;
cln->handler = ngx_quic_bpf_cleanup;
if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) {
if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) {
goto failed;
}
}
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
if (ls[i].quic && ls[i].reuseport) {
if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) {
goto failed;
}
}
}
if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) {
goto failed;
}
return NGX_OK;
failed:
if (ngx_is_init_cycle(cycle->old_cycle)) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"ngx_quic_bpf_module failed to initialize, check limits");
/* refuse to start */
return NGX_ERROR;
}
/*
* returning error now will lead to master process exiting immediately
* leaving worker processes orphaned, what is really unexpected.
* Instead, just issue a not about failed initialization and try
* to cleanup a bit. Still program can be already loaded to kernel
* for some reuseport groups, and there is no way to revert, so
* behaviour may be inconsistent.
*/
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"ngx_quic_bpf_module failed to initialize properly, ignored."
"please check limits and note that nginx state now "
"can be inconsistent and restart may be required");
return NGX_OK;
}
static void
ngx_quic_bpf_cleanup(void *data)
{
ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data;
ngx_queue_t *q;
ngx_quic_sock_group_t *grp;
for (q = ngx_queue_head(&bcf->groups);
q != ngx_queue_sentinel(&bcf->groups);
q = ngx_queue_next(q))
{
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map");
}
}
static ngx_inline void
ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name)
{
if (close(fd) != -1) {
return;
}
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"quic bpf close %s fd:%d failed", name, fd);
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls)
{
ngx_queue_t *q;
ngx_quic_sock_group_t *grp;
for (q = ngx_queue_head(&bcf->groups);
q != ngx_queue_sentinel(&bcf->groups);
q = ngx_queue_next(q))
{
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen,
grp->sockaddr, grp->socklen, 1)
== NGX_OK)
{
return grp;
}
}
return NULL;
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa,
socklen_t socklen)
{
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
bcf = ngx_quic_bpf_get_conf(cycle);
grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t));
if (grp == NULL) {
return NULL;
}
grp->socklen = socklen;
grp->sockaddr = ngx_palloc(cycle->pool, socklen);
if (grp->sockaddr == NULL) {
return NULL;
}
ngx_memcpy(grp->sockaddr, sa, socklen);
ngx_queue_insert_tail(&bcf->groups, &grp->queue);
return grp;
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
int progfd, failed, flags, rc;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
bcf = ngx_quic_bpf_get_conf(cycle);
if (!bcf->enabled) {
return NULL;
}
grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
if (grp == NULL) {
return NULL;
}
grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH,
sizeof(uint64_t), sizeof(uint64_t),
bcf->map_size, 0);
if (grp->map_fd == -1) {
goto failed;
}
flags = fcntl(grp->map_fd, F_GETFD);
if (flags == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
"quic bpf getfd failed");
goto failed;
}
/* need to inherit map during binary upgrade after exec */
flags &= ~FD_CLOEXEC;
rc = fcntl(grp->map_fd, F_SETFD, flags);
if (rc == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
"quic bpf setfd failed");
goto failed;
}
ngx_bpf_program_link(&ngx_quic_reuseport_helper,
"ngx_quic_sockmap", grp->map_fd);
progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper);
if (progfd < 0) {
goto failed;
}
failed = 0;
if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF,
&progfd, sizeof(int))
== -1)
{
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
"quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed");
failed = 1;
}
ngx_quic_bpf_close(cycle->log, progfd, "program");
if (failed) {
goto failed;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap created fd:%d", grp->map_fd);
return grp;
failed:
if (grp->map_fd != -1) {
ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
}
ngx_queue_remove(&grp->queue);
return NULL;
}
static ngx_quic_sock_group_t *
ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
ngx_quic_bpf_conf_t *bcf, *old_bcf;
ngx_quic_sock_group_t *grp, *ogrp;
bcf = ngx_quic_bpf_get_conf(cycle);
grp = ngx_quic_bpf_find_group(bcf, ls);
if (grp) {
return grp;
}
old_bcf = ngx_quic_bpf_get_old_conf(cycle);
if (old_bcf == NULL) {
return ngx_quic_bpf_create_group(cycle, ls);
}
ogrp = ngx_quic_bpf_find_group(old_bcf, ls);
if (ogrp == NULL) {
return ngx_quic_bpf_create_group(cycle, ls);
}
grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
if (grp == NULL) {
return NULL;
}
grp->map_fd = dup(ogrp->map_fd);
if (grp->map_fd == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"quic bpf failed to duplicate bpf map descriptor");
ngx_queue_remove(&grp->queue);
return NULL;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap fd duplicated old:%d new:%d",
ogrp->map_fd, grp->map_fd);
return grp;
}
static ngx_int_t
ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
uint64_t cookie;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
bcf = ngx_quic_bpf_get_conf(cycle);
grp = ngx_quic_bpf_get_group(cycle, ls);
if (grp == NULL) {
if (!bcf->enabled) {
return NGX_OK;
}
return NGX_ERROR;
}
grp->unused = 0;
cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log);
if (cookie == (uint64_t) NGX_ERROR) {
return NGX_ERROR;
}
/* map[cookie] = socket; for use in kernel helper */
if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"quic bpf failed to update socket map key=%xL", cookie);
return NGX_ERROR;
}
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui",
grp->map_fd, ls->fd, cookie, ls->worker);
/* do not inherit this socket */
ls->ignore = 1;
return NGX_OK;
}
static uint64_t
ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log)
{
uint64_t cookie;
socklen_t optlen;
optlen = sizeof(cookie);
if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
"quic bpf getsockopt(SO_COOKIE) failed");
return (ngx_uint_t) NGX_ERROR;
}
return cookie;
}
static ngx_int_t
ngx_quic_bpf_export_maps(ngx_cycle_t *cycle)
{
u_char *p, *buf;
size_t len;
ngx_str_t *var;
ngx_queue_t *q;
ngx_core_conf_t *ccf;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
ccf = ngx_core_get_conf(cycle);
bcf = ngx_quic_bpf_get_conf(cycle);
len = sizeof(NGX_QUIC_BPF_VARNAME) + 1;
q = ngx_queue_head(&bcf->groups);
while (q != ngx_queue_sentinel(&bcf->groups)) {
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
q = ngx_queue_next(q);
if (grp->unused) {
/*
* map was inherited, but it is not used in this configuration;
* do not pass such map further and drop the group to prevent
* interference with changes during reload
*/
ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
ngx_queue_remove(&grp->queue);
continue;
}
len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1;
}
len++;
buf = ngx_palloc(cycle->pool, len);
if (buf == NULL) {
return NGX_ERROR;
}
p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=",
sizeof(NGX_QUIC_BPF_VARNAME));
for (q = ngx_queue_head(&bcf->groups);
q != ngx_queue_sentinel(&bcf->groups);
q = ngx_queue_next(q))
{
grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
p = ngx_sprintf(p, "%ud", grp->map_fd);
*p++ = NGX_QUIC_BPF_ADDRSEP;
p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p,
NGX_SOCKADDR_STRLEN, 1);
*p++ = NGX_QUIC_BPF_VARSEP;
}
*p = '\0';
var = ngx_array_push(&ccf->env);
if (var == NULL) {
return NGX_ERROR;
}
var->data = buf;
var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1;
return NGX_OK;
}
static ngx_int_t
ngx_quic_bpf_import_maps(ngx_cycle_t *cycle)
{
int s;
u_char *inherited, *p, *v;
ngx_uint_t in_fd;
ngx_addr_t tmp;
ngx_quic_bpf_conf_t *bcf;
ngx_quic_sock_group_t *grp;
inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME);
if (inherited == NULL) {
return NGX_OK;
}
bcf = ngx_quic_bpf_get_conf(cycle);
#if (NGX_SUPPRESS_WARN)
s = -1;
#endif
in_fd = 1;
for (p = inherited, v = p; *p; p++) {
switch (*p) {
case NGX_QUIC_BPF_ADDRSEP:
if (!in_fd) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited env");
return NGX_ERROR;
}
in_fd = 0;
s = ngx_atoi(v, p - v);
if (s == NGX_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited map fd");
return NGX_ERROR;
}
v = p + 1;
break;
case NGX_QUIC_BPF_VARSEP:
if (in_fd) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited env");
return NGX_ERROR;
}
in_fd = 1;
grp = ngx_pcalloc(cycle->pool,
sizeof(ngx_quic_sock_group_t));
if (grp == NULL) {
return NGX_ERROR;
}
grp->map_fd = s;
if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"quic bpf failed to parse inherited"
" address '%*s'", p - v , v);
ngx_quic_bpf_close(cycle->log, s, "inherited map");
return NGX_ERROR;
}
grp->sockaddr = tmp.sockaddr;
grp->socklen = tmp.socklen;
grp->unused = 1;
ngx_queue_insert_tail(&bcf->groups, &grp->queue);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"quic bpf sockmap inherited with "
"fd:%d address:%*s",
grp->map_fd, p - v, v);
v = p + 1;
break;
default:
break;
}
}
return NGX_OK;
}
+88
View File
@@ -0,0 +1,88 @@
/* AUTO-GENERATED, DO NOT EDIT. */
#include <stddef.h>
#include <stdint.h>
#include "ngx_bpf.h"
static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = {
{ "ngx_quic_sockmap", 55 },
};
static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = {
/* opcode dst src offset imm */
{ 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 },
{ 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 },
{ 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 },
{ 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 },
{ 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 },
{ 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 },
{ 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 },
{ 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 },
{ 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
{ 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
{ 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff },
{ 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd },
{ 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 },
{ 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe },
{ 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 },
{ 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe },
{ 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 },
{ 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 },
{ 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 },
{ 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 },
{ 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 },
{ 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 },
{ 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 },
{ 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 },
{ 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 },
{ 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
{ 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 },
{ 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 },
{ 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 },
{ 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 },
{ 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 },
{ 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 },
{ 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 },
{ 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 },
{ 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
};
ngx_bpf_program_t ngx_quic_reuseport_helper = {
.relocs = bpf_reloc_prog_ngx_quic_reuseport_helper,
.nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper)
/ sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]),
.ins = bpf_insn_prog_ngx_quic_reuseport_helper,
.nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper)
/ sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]),
.license = "BSD",
.type = BPF_PROG_TYPE_SK_REUSEPORT,
};
+276
View File
@@ -0,0 +1,276 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */
/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */
/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */
/* #define NGX_QUIC_DEBUG_CRYPTO */
typedef struct ngx_quic_connection_s ngx_quic_connection_t;
typedef struct ngx_quic_server_id_s ngx_quic_server_id_t;
typedef struct ngx_quic_client_id_s ngx_quic_client_id_t;
typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t;
typedef struct ngx_quic_socket_s ngx_quic_socket_t;
typedef struct ngx_quic_path_s ngx_quic_path_t;
typedef struct ngx_quic_keys_s ngx_quic_keys_t;
#include <ngx_event_quic_transport.h>
#include <ngx_event_quic_protection.h>
#include <ngx_event_quic_frames.h>
#include <ngx_event_quic_migration.h>
#include <ngx_event_quic_connid.h>
#include <ngx_event_quic_streams.h>
#include <ngx_event_quic_ssl.h>
#include <ngx_event_quic_tokens.h>
#include <ngx_event_quic_ack.h>
#include <ngx_event_quic_output.h>
#include <ngx_event_quic_socket.h>
/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */
#define NGX_QUIC_INITIAL_RTT 333 /* ms */
#define NGX_QUIC_UNSET_PN (uint64_t) -1
#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1)
/* 0-RTT and 1-RTT data exist in the same packet number space,
* so we have 3 packet number spaces:
*
* 0 - Initial
* 1 - Handshake
* 2 - 0-RTT and 1-RTT
*/
#define ngx_quic_get_send_ctx(qc, level) \
((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \
: (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \
: &((qc)->send_ctx[2]))
#define ngx_quic_get_connection(c) \
(((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL)
#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp))
struct ngx_quic_client_id_s {
ngx_queue_t queue;
uint64_t seqnum;
size_t len;
u_char id[NGX_QUIC_CID_LEN_MAX];
u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
ngx_uint_t used; /* unsigned used:1; */
};
struct ngx_quic_server_id_s {
uint64_t seqnum;
size_t len;
u_char id[NGX_QUIC_CID_LEN_MAX];
};
struct ngx_quic_path_s {
ngx_queue_t queue;
struct sockaddr *sockaddr;
ngx_sockaddr_t sa;
socklen_t socklen;
ngx_quic_client_id_t *cid;
ngx_msec_t expires;
ngx_uint_t tries;
ngx_uint_t tag;
off_t sent;
off_t received;
u_char challenge1[8];
u_char challenge2[8];
uint64_t seqnum;
ngx_str_t addr_text;
u_char text[NGX_SOCKADDR_STRLEN];
unsigned validated:1;
unsigned validating:1;
unsigned limited:1;
};
struct ngx_quic_socket_s {
ngx_udp_connection_t udp;
ngx_quic_connection_t *quic;
ngx_queue_t queue;
ngx_quic_server_id_t sid;
ngx_sockaddr_t sockaddr;
socklen_t socklen;
ngx_uint_t used; /* unsigned used:1; */
};
typedef struct {
ngx_rbtree_t tree;
ngx_rbtree_node_t sentinel;
ngx_queue_t uninitialized;
ngx_queue_t free;
uint64_t sent;
uint64_t recv_offset;
uint64_t recv_window;
uint64_t recv_last;
uint64_t recv_max_data;
uint64_t send_offset;
uint64_t send_max_data;
uint64_t server_max_streams_uni;
uint64_t server_max_streams_bidi;
uint64_t server_streams_uni;
uint64_t server_streams_bidi;
uint64_t client_max_streams_uni;
uint64_t client_max_streams_bidi;
uint64_t client_streams_uni;
uint64_t client_streams_bidi;
ngx_uint_t initialized;
/* unsigned initialized:1; */
} ngx_quic_streams_t;
typedef struct {
size_t in_flight;
size_t window;
size_t ssthresh;
ngx_msec_t recovery_start;
} ngx_quic_congestion_t;
/*
* RFC 9000, 12.3. Packet Numbers
*
* Conceptually, a packet number space is the context in which a packet
* can be processed and acknowledged. Initial packets can only be sent
* with Initial packet protection keys and acknowledged in packets that
* are also Initial packets.
*/
struct ngx_quic_send_ctx_s {
enum ssl_encryption_level_t level;
ngx_quic_buffer_t crypto;
uint64_t crypto_sent;
uint64_t pnum; /* to be sent */
uint64_t largest_ack; /* received from peer */
uint64_t largest_pn; /* received from peer */
ngx_queue_t frames; /* generated frames */
ngx_queue_t sending; /* frames assigned to pkt */
ngx_queue_t sent; /* frames waiting ACK */
uint64_t pending_ack; /* non sent ack-eliciting */
uint64_t largest_range;
uint64_t first_range;
ngx_msec_t largest_received;
ngx_msec_t ack_delay_start;
ngx_uint_t nranges;
ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES];
ngx_uint_t send_ack;
};
struct ngx_quic_connection_s {
uint32_t version;
ngx_quic_path_t *path;
ngx_queue_t sockets;
ngx_queue_t paths;
ngx_queue_t client_ids;
ngx_queue_t free_sockets;
ngx_queue_t free_paths;
ngx_queue_t free_client_ids;
ngx_uint_t nsockets;
ngx_uint_t nclient_ids;
uint64_t max_retired_seqnum;
uint64_t client_seqnum;
uint64_t server_seqnum;
uint64_t path_seqnum;
ngx_quic_tp_t tp;
ngx_quic_tp_t ctp;
ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST];
ngx_quic_keys_t *keys;
ngx_quic_conf_t *conf;
ngx_event_t push;
ngx_event_t pto;
ngx_event_t close;
ngx_event_t path_validation;
ngx_msec_t last_cc;
ngx_msec_t first_rtt;
ngx_msec_t latest_rtt;
ngx_msec_t avg_rtt;
ngx_msec_t min_rtt;
ngx_msec_t rttvar;
ngx_uint_t pto_count;
ngx_queue_t free_frames;
ngx_buf_t *free_bufs;
ngx_buf_t *free_shadow_bufs;
ngx_uint_t nframes;
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_uint_t nbufs;
ngx_uint_t nshadowbufs;
#endif
ngx_quic_streams_t streams;
ngx_quic_congestion_t congestion;
off_t received;
ngx_uint_t error;
enum ssl_encryption_level_t error_level;
ngx_uint_t error_ftype;
const char *error_reason;
ngx_uint_t shutdown_code;
const char *shutdown_reason;
unsigned error_app:1;
unsigned send_timer_set:1;
unsigned closing:1;
unsigned shutdown:1;
unsigned draining:1;
unsigned key_phase:1;
unsigned validated:1;
unsigned client_tp_done:1;
};
ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c,
ngx_quic_tp_t *ctp);
void ngx_quic_discard_ctx(ngx_connection_t *c,
enum ssl_encryption_level_t level);
void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc);
void ngx_quic_shutdown_quic(ngx_connection_t *c);
#if (NGX_DEBUG)
void ngx_quic_connstate_dbg(ngx_connection_t *c);
#else
#define ngx_quic_connstate_dbg(c)
#endif
#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */
+502
View File
@@ -0,0 +1,502 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_MAX_SERVER_IDS 8
#if (NGX_QUIC_BPF)
static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id);
#endif
static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c,
ngx_quic_client_id_t *cid);
static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c,
ngx_quic_connection_t *qc);
static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c,
ngx_quic_server_id_t *sid);
ngx_int_t
ngx_quic_create_server_id(ngx_connection_t *c, u_char *id)
{
if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) {
return NGX_ERROR;
}
#if (NGX_QUIC_BPF)
if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"quic bpf failed to generate socket key");
/* ignore error, things still may work */
}
#endif
return NGX_OK;
}
#if (NGX_QUIC_BPF)
static ngx_int_t
ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id)
{
int fd;
uint64_t cookie;
socklen_t optlen;
fd = c->listening->fd;
optlen = sizeof(cookie);
if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno,
"quic getsockopt(SO_COOKIE) failed");
return NGX_ERROR;
}
ngx_quic_dcid_encode_key(id, cookie);
return NGX_OK;
}
#endif
ngx_int_t
ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
ngx_quic_new_conn_id_frame_t *f)
{
ngx_str_t id;
ngx_queue_t *q;
ngx_quic_frame_t *frame;
ngx_quic_client_id_t *cid, *item;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (f->seqnum < qc->max_retired_seqnum) {
/*
* RFC 9000, 19.15. NEW_CONNECTION_ID Frame
*
* An endpoint that receives a NEW_CONNECTION_ID frame with
* a sequence number smaller than the Retire Prior To field
* of a previously received NEW_CONNECTION_ID frame MUST send
* a corresponding RETIRE_CONNECTION_ID frame that retires
* the newly received connection ID, unless it has already
* done so for that sequence number.
*/
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
frame->u.retire_cid.sequence_number = f->seqnum;
ngx_quic_queue_frame(qc, frame);
goto retire;
}
cid = NULL;
for (q = ngx_queue_head(&qc->client_ids);
q != ngx_queue_sentinel(&qc->client_ids);
q = ngx_queue_next(q))
{
item = ngx_queue_data(q, ngx_quic_client_id_t, queue);
if (item->seqnum == f->seqnum) {
cid = item;
break;
}
}
if (cid) {
/*
* Transmission errors, timeouts, and retransmissions might cause the
* same NEW_CONNECTION_ID frame to be received multiple times.
*/
if (cid->len != f->len
|| ngx_strncmp(cid->id, f->cid, f->len) != 0
|| ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0)
{
/*
* ..if a sequence number is used for different connection IDs,
* the endpoint MAY treat that receipt as a connection error
* of type PROTOCOL_VIOLATION.
*/
qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
qc->error_reason = "seqnum refers to different connection id/token";
return NGX_ERROR;
}
} else {
id.data = f->cid;
id.len = f->len;
if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) {
return NGX_ERROR;
}
}
retire:
if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) {
/*
* Once a sender indicates a Retire Prior To value, smaller values sent
* in subsequent NEW_CONNECTION_ID frames have no effect. A receiver
* MUST ignore any Retire Prior To fields that do not increase the
* largest received Retire Prior To value.
*/
goto done;
}
qc->max_retired_seqnum = f->retire;
q = ngx_queue_head(&qc->client_ids);
while (q != ngx_queue_sentinel(&qc->client_ids)) {
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
q = ngx_queue_next(q);
if (cid->seqnum >= f->retire) {
continue;
}
if (ngx_quic_retire_client_id(c, cid) != NGX_OK) {
return NGX_ERROR;
}
}
done:
if (qc->nclient_ids > qc->tp.active_connection_id_limit) {
/*
* RFC 9000, 5.1.1. Issuing Connection IDs
*
* After processing a NEW_CONNECTION_ID frame and
* adding and retiring active connection IDs, if the number of active
* connection IDs exceeds the value advertised in its
* active_connection_id_limit transport parameter, an endpoint MUST
* close the connection with an error of type CONNECTION_ID_LIMIT_ERROR.
*/
qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
qc->error_reason = "too many connection ids received";
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
{
ngx_queue_t *q;
ngx_quic_path_t *path;
ngx_quic_client_id_t *new_cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!cid->used) {
return ngx_quic_free_client_id(c, cid);
}
/* we are going to retire client id which is in use */
q = ngx_queue_head(&qc->paths);
while (q != ngx_queue_sentinel(&qc->paths)) {
path = ngx_queue_data(q, ngx_quic_path_t, queue);
q = ngx_queue_next(q);
if (path->cid != cid) {
continue;
}
if (path == qc->path) {
/* this is the active path: update it with new CID */
new_cid = ngx_quic_next_client_id(c);
if (new_cid == NULL) {
return NGX_ERROR;
}
qc->path->cid = new_cid;
new_cid->used = 1;
return ngx_quic_free_client_id(c, cid);
}
return ngx_quic_free_path(c, path);
}
return NGX_OK;
}
static ngx_quic_client_id_t *
ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc)
{
ngx_queue_t *q;
ngx_quic_client_id_t *cid;
if (!ngx_queue_empty(&qc->free_client_ids)) {
q = ngx_queue_head(&qc->free_client_ids);
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
ngx_queue_remove(&cid->queue);
ngx_memzero(cid, sizeof(ngx_quic_client_id_t));
} else {
cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t));
if (cid == NULL) {
return NULL;
}
}
return cid;
}
ngx_quic_client_id_t *
ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id,
uint64_t seqnum, u_char *token)
{
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
cid = ngx_quic_alloc_client_id(c, qc);
if (cid == NULL) {
return NULL;
}
cid->seqnum = seqnum;
cid->len = id->len;
ngx_memcpy(cid->id, id->data, id->len);
if (token) {
ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN);
}
ngx_queue_insert_tail(&qc->client_ids, &cid->queue);
qc->nclient_ids++;
if (seqnum > qc->client_seqnum) {
qc->client_seqnum = seqnum;
}
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic cid seq:%uL received id:%uz:%xV:%*xs",
cid->seqnum, id->len, id,
(size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token);
return cid;
}
ngx_quic_client_id_t *
ngx_quic_next_client_id(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
for (q = ngx_queue_head(&qc->client_ids);
q != ngx_queue_sentinel(&qc->client_ids);
q = ngx_queue_next(q))
{
cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
if (!cid->used) {
return cid;
}
}
return NULL;
}
ngx_int_t
ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
ngx_quic_retire_cid_frame_t *f)
{
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (f->sequence_number >= qc->server_seqnum) {
/*
* RFC 9000, 19.16.
*
* Receipt of a RETIRE_CONNECTION_ID frame containing a sequence
* number greater than any previously sent to the peer MUST be
* treated as a connection error of type PROTOCOL_VIOLATION.
*/
qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
qc->error_reason = "sequence number of id to retire was never issued";
return NGX_ERROR;
}
qsock = ngx_quic_get_socket(c);
if (qsock->sid.seqnum == f->sequence_number) {
/*
* RFC 9000, 19.16.
*
* The sequence number specified in a RETIRE_CONNECTION_ID frame MUST
* NOT refer to the Destination Connection ID field of the packet in
* which the frame is contained. The peer MAY treat this as a
* connection error of type PROTOCOL_VIOLATION.
*/
qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
qc->error_reason = "sequence number of id to retire refers DCID";
return NGX_ERROR;
}
qsock = ngx_quic_find_socket(c, f->sequence_number);
if (qsock == NULL) {
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%uL is retired", qsock->sid.seqnum);
ngx_quic_close_socket(c, qsock);
/* restore socket count up to a limit after deletion */
if (ngx_quic_create_sockets(c) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_quic_create_sockets(ngx_connection_t *c)
{
ngx_uint_t n;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic create sockets has:%ui max:%ui", qc->nsockets, n);
while (qc->nsockets < n) {
qsock = ngx_quic_create_socket(c, qc);
if (qsock == NULL) {
return NGX_ERROR;
}
if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid)
{
ngx_str_t dcid;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
dcid.len = sid->len;
dcid.data = sid->id;
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID;
frame->u.ncid.seqnum = sid->seqnum;
frame->u.ncid.retire = 0;
frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN;
ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN);
if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key,
frame->u.ncid.srt)
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
ngx_int_t
ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
{
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
frame->u.retire_cid.sequence_number = cid->seqnum;
ngx_quic_queue_frame(qc, frame);
/* we are no longer going to use this client id */
ngx_queue_remove(&cid->queue);
ngx_queue_insert_head(&qc->free_client_ids, &cid->queue);
qc->nclient_ids--;
return NGX_OK;
}
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
ngx_quic_retire_cid_frame_t *f);
ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
ngx_quic_new_conn_id_frame_t *f);
ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c);
ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id);
ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c,
ngx_str_t *id, uint64_t seqnum, u_char *token);
ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c);
ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c,
ngx_quic_client_id_t *cid);
#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */
+844
View File
@@ -0,0 +1,844 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_BUFFER_SIZE 4096
#define ngx_quic_buf_refs(b) (b)->shadow->num
#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++
#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)--
#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v
static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c);
static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b);
static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b);
static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl,
off_t offset);
static ngx_buf_t *
ngx_quic_alloc_buf(ngx_connection_t *c)
{
u_char *p;
ngx_buf_t *b;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
b = qc->free_bufs;
if (b) {
qc->free_bufs = b->shadow;
p = b->start;
} else {
b = qc->free_shadow_bufs;
if (b) {
qc->free_shadow_bufs = b->shadow;
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic use shadow buffer n:%ui %ui",
++qc->nbufs, --qc->nshadowbufs);
#endif
} else {
b = ngx_palloc(c->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NULL;
}
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic new buffer n:%ui", ++qc->nbufs);
#endif
}
p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE);
if (p == NULL) {
return NULL;
}
}
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b);
#endif
ngx_memzero(b, sizeof(ngx_buf_t));
b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf;
b->temporary = 1;
b->shadow = b;
b->start = p;
b->pos = p;
b->last = p;
b->end = p + NGX_QUIC_BUFFER_SIZE;
ngx_quic_buf_set_refs(b, 1);
return b;
}
static void
ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b)
{
ngx_buf_t *shadow;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_quic_buf_dec_refs(b);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic free buffer %p r:%ui",
b, (ngx_uint_t) ngx_quic_buf_refs(b));
#endif
shadow = b->shadow;
if (ngx_quic_buf_refs(b) == 0) {
shadow->shadow = qc->free_bufs;
qc->free_bufs = shadow;
}
if (b != shadow) {
b->shadow = qc->free_shadow_bufs;
qc->free_shadow_bufs = b;
}
}
static ngx_buf_t *
ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b)
{
ngx_buf_t *nb;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
nb = qc->free_shadow_bufs;
if (nb) {
qc->free_shadow_bufs = nb->shadow;
} else {
nb = ngx_palloc(c->pool, sizeof(ngx_buf_t));
if (nb == NULL) {
return NULL;
}
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic new shadow buffer n:%ui", ++qc->nshadowbufs);
#endif
}
*nb = *b;
ngx_quic_buf_inc_refs(b);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic clone buffer %p %p r:%ui",
b, nb, (ngx_uint_t) ngx_quic_buf_refs(b));
#endif
return nb;
}
static ngx_int_t
ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset)
{
ngx_buf_t *b, *tb;
ngx_chain_t *tail;
b = cl->buf;
tail = ngx_alloc_chain_link(c->pool);
if (tail == NULL) {
return NGX_ERROR;
}
tb = ngx_quic_clone_buf(c, b);
if (tb == NULL) {
return NGX_ERROR;
}
tail->buf = tb;
tb->pos += offset;
b->last = tb->pos;
b->last_buf = 0;
tail->next = cl->next;
cl->next = tail;
return NGX_OK;
}
ngx_quic_frame_t *
ngx_quic_alloc_frame(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!ngx_queue_empty(&qc->free_frames)) {
q = ngx_queue_head(&qc->free_frames);
frame = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_queue_remove(&frame->queue);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic reuse frame n:%ui", qc->nframes);
#endif
} else if (qc->nframes < 10000) {
frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t));
if (frame == NULL) {
return NULL;
}
++qc->nframes;
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic alloc frame n:%ui", qc->nframes);
#endif
} else {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected");
return NULL;
}
ngx_memzero(frame, sizeof(ngx_quic_frame_t));
return frame;
}
void
ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (frame->data) {
ngx_quic_free_chain(c, frame->data);
}
ngx_queue_insert_head(&qc->free_frames, &frame->queue);
#ifdef NGX_QUIC_DEBUG_ALLOC
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic free frame n:%ui", qc->nframes);
#endif
}
void
ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in)
{
ngx_chain_t *cl;
while (in) {
cl = in;
in = in->next;
ngx_quic_free_buf(c, cl->buf);
ngx_free_chain(c->pool, cl);
}
}
void
ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames)
{
ngx_queue_t *q;
ngx_quic_frame_t *f;
do {
q = ngx_queue_head(frames);
if (q == ngx_queue_sentinel(frames)) {
break;
}
ngx_queue_remove(q);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_quic_free_frame(c, f);
} while (1);
}
void
ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame)
{
ngx_quic_send_ctx_t *ctx;
ctx = ngx_quic_get_send_ctx(qc, frame->level);
ngx_queue_insert_tail(&ctx->frames, &frame->queue);
frame->len = ngx_quic_create_frame(NULL, frame);
/* always succeeds */
if (qc->closing) {
return;
}
ngx_post_event(&qc->push, &ngx_posted_events);
}
ngx_int_t
ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len)
{
size_t shrink;
ngx_quic_frame_t *nf;
ngx_quic_buffer_t qb;
ngx_quic_ordered_frame_t *of, *onf;
switch (f->type) {
case NGX_QUIC_FT_CRYPTO:
case NGX_QUIC_FT_STREAM:
break;
default:
return NGX_DECLINED;
}
if ((size_t) f->len <= len) {
return NGX_OK;
}
shrink = f->len - len;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic split frame now:%uz need:%uz shrink:%uz",
f->len, len, shrink);
of = &f->u.ord;
if (of->length <= shrink) {
return NGX_DECLINED;
}
of->length -= shrink;
f->len = ngx_quic_create_frame(NULL, f);
if ((size_t) f->len > len) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame");
return NGX_ERROR;
}
ngx_memzero(&qb, sizeof(ngx_quic_buffer_t));
qb.chain = f->data;
f->data = ngx_quic_read_buffer(c, &qb, of->length);
if (f->data == NGX_CHAIN_ERROR) {
return NGX_ERROR;
}
nf = ngx_quic_alloc_frame(c);
if (nf == NULL) {
return NGX_ERROR;
}
*nf = *f;
onf = &nf->u.ord;
onf->offset += of->length;
onf->length = shrink;
nf->len = ngx_quic_create_frame(NULL, nf);
nf->data = qb.chain;
if (f->type == NGX_QUIC_FT_STREAM) {
f->u.stream.fin = 0;
}
ngx_queue_insert_after(&f->queue, &nf->queue);
return NGX_OK;
}
ngx_chain_t *
ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit)
{
uint64_t n;
ngx_buf_t *b;
ngx_chain_t *out, **ll;
out = qb->chain;
for (ll = &out; *ll; ll = &(*ll)->next) {
b = (*ll)->buf;
if (b->sync) {
/* hole */
break;
}
if (limit == 0) {
break;
}
n = b->last - b->pos;
if (n > limit) {
if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
n = limit;
}
limit -= n;
qb->offset += n;
}
if (qb->offset >= qb->last_offset) {
qb->last_chain = NULL;
}
qb->chain = *ll;
*ll = NULL;
return out;
}
void
ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
uint64_t offset)
{
size_t n;
ngx_buf_t *b;
ngx_chain_t *cl;
while (qb->chain) {
if (qb->offset >= offset) {
break;
}
cl = qb->chain;
b = cl->buf;
n = b->last - b->pos;
if (qb->offset + n > offset) {
n = offset - qb->offset;
b->pos += n;
qb->offset += n;
break;
}
qb->offset += n;
qb->chain = cl->next;
cl->next = NULL;
ngx_quic_free_chain(c, cl);
}
if (qb->chain == NULL) {
qb->offset = offset;
}
if (qb->offset >= qb->last_offset) {
qb->last_chain = NULL;
}
}
ngx_chain_t *
ngx_quic_alloc_chain(ngx_connection_t *c)
{
ngx_chain_t *cl;
cl = ngx_alloc_chain_link(c->pool);
if (cl == NULL) {
return NULL;
}
cl->buf = ngx_quic_alloc_buf(c);
if (cl->buf == NULL) {
return NULL;
}
return cl;
}
ngx_chain_t *
ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
ngx_chain_t *in, uint64_t limit, uint64_t offset)
{
u_char *p;
uint64_t n, base;
ngx_buf_t *b;
ngx_chain_t *cl, **chain;
if (qb->last_chain && offset >= qb->last_offset) {
base = qb->last_offset;
chain = &qb->last_chain;
} else {
base = qb->offset;
chain = &qb->chain;
}
while (in && limit) {
if (offset < base) {
n = ngx_min((uint64_t) (in->buf->last - in->buf->pos),
ngx_min(base - offset, limit));
in->buf->pos += n;
offset += n;
limit -= n;
if (in->buf->pos == in->buf->last) {
in = in->next;
}
continue;
}
cl = *chain;
if (cl == NULL) {
cl = ngx_quic_alloc_chain(c);
if (cl == NULL) {
return NGX_CHAIN_ERROR;
}
cl->buf->last = cl->buf->end;
cl->buf->sync = 1; /* hole */
cl->next = NULL;
*chain = cl;
}
b = cl->buf;
n = b->last - b->pos;
if (base + n <= offset) {
base += n;
chain = &cl->next;
continue;
}
if (b->sync && offset > base) {
if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
continue;
}
p = b->pos + (offset - base);
while (in) {
if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) {
in = in->next;
continue;
}
if (p == b->last || limit == 0) {
break;
}
n = ngx_min(b->last - p, in->buf->last - in->buf->pos);
n = ngx_min(n, limit);
if (b->sync) {
ngx_memcpy(p, in->buf->pos, n);
qb->size += n;
}
p += n;
in->buf->pos += n;
offset += n;
limit -= n;
}
if (b->sync && p == b->last) {
b->sync = 0;
continue;
}
if (b->sync && p != b->pos) {
if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
b->sync = 0;
}
}
qb->last_offset = base;
qb->last_chain = *chain;
return in;
}
void
ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb)
{
ngx_quic_free_chain(c, qb->chain);
qb->chain = NULL;
}
#if (NGX_DEBUG)
void
ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx)
{
u_char *p, *last, *pos, *end;
ssize_t n;
uint64_t gap, range, largest, smallest;
ngx_uint_t i;
u_char buf[NGX_MAX_ERROR_STR];
p = buf;
last = buf + sizeof(buf);
switch (f->type) {
case NGX_QUIC_FT_CRYPTO:
p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL",
f->u.crypto.length, f->u.crypto.offset);
#ifdef NGX_QUIC_DEBUG_FRAMES
{
ngx_chain_t *cl;
p = ngx_slprintf(p, last, " data:");
for (cl = f->data; cl; cl = cl->next) {
p = ngx_slprintf(p, last, "%*xs",
cl->buf->last - cl->buf->pos, cl->buf->pos);
}
}
#endif
break;
case NGX_QUIC_FT_PADDING:
p = ngx_slprintf(p, last, "PADDING");
break;
case NGX_QUIC_FT_ACK:
case NGX_QUIC_FT_ACK_ECN:
p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ",
f->u.ack.range_count, f->u.ack.delay);
if (f->data) {
pos = f->data->buf->pos;
end = f->data->buf->last;
} else {
pos = NULL;
end = NULL;
}
largest = f->u.ack.largest;
smallest = f->u.ack.largest - f->u.ack.first_range;
if (largest == smallest) {
p = ngx_slprintf(p, last, "%uL", largest);
} else {
p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest);
}
for (i = 0; i < f->u.ack.range_count; i++) {
n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range);
if (n == NGX_ERROR) {
break;
}
pos += n;
largest = smallest - gap - 2;
smallest = largest - range;
if (largest == smallest) {
p = ngx_slprintf(p, last, " %uL", largest);
} else {
p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest);
}
}
if (f->type == NGX_QUIC_FT_ACK_ECN) {
p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL",
f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
}
break;
case NGX_QUIC_FT_PING:
p = ngx_slprintf(p, last, "PING");
break;
case NGX_QUIC_FT_NEW_CONNECTION_ID:
p = ngx_slprintf(p, last,
"NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud",
f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len);
break;
case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL",
f->u.retire_cid.sequence_number);
break;
case NGX_QUIC_FT_CONNECTION_CLOSE:
case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui",
f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP",
f->u.close.error_code);
if (f->u.close.reason.len) {
p = ngx_slprintf(p, last, " %V", &f->u.close.reason);
}
if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type);
}
break;
case NGX_QUIC_FT_STREAM:
p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id);
if (f->u.stream.off) {
p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset);
}
if (f->u.stream.len) {
p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length);
}
if (f->u.stream.fin) {
p = ngx_slprintf(p, last, " fin:1");
}
#ifdef NGX_QUIC_DEBUG_FRAMES
{
ngx_chain_t *cl;
p = ngx_slprintf(p, last, " data:");
for (cl = f->data; cl; cl = cl->next) {
p = ngx_slprintf(p, last, "%*xs",
cl->buf->last - cl->buf->pos, cl->buf->pos);
}
}
#endif
break;
case NGX_QUIC_FT_MAX_DATA:
p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv",
f->u.max_data.max_data);
break;
case NGX_QUIC_FT_RESET_STREAM:
p = ngx_slprintf(p, last, "RESET_STREAM"
" id:0x%xL error_code:0x%xL final_size:0x%xL",
f->u.reset_stream.id, f->u.reset_stream.error_code,
f->u.reset_stream.final_size);
break;
case NGX_QUIC_FT_STOP_SENDING:
p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL",
f->u.stop_sending.id, f->u.stop_sending.error_code);
break;
case NGX_QUIC_FT_STREAMS_BLOCKED:
case NGX_QUIC_FT_STREAMS_BLOCKED2:
p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui",
f->u.streams_blocked.limit, f->u.streams_blocked.bidi);
break;
case NGX_QUIC_FT_MAX_STREAMS:
case NGX_QUIC_FT_MAX_STREAMS2:
p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui",
f->u.max_streams.limit, f->u.max_streams.bidi);
break;
case NGX_QUIC_FT_MAX_STREAM_DATA:
p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL",
f->u.max_stream_data.id, f->u.max_stream_data.limit);
break;
case NGX_QUIC_FT_DATA_BLOCKED:
p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL",
f->u.data_blocked.limit);
break;
case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL",
f->u.stream_data_blocked.id,
f->u.stream_data_blocked.limit);
break;
case NGX_QUIC_FT_PATH_CHALLENGE:
p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs",
sizeof(f->u.path_challenge.data),
f->u.path_challenge.data);
break;
case NGX_QUIC_FT_PATH_RESPONSE:
p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs",
sizeof(f->u.path_challenge.data),
f->u.path_challenge.data);
break;
case NGX_QUIC_FT_NEW_TOKEN:
p = ngx_slprintf(p, last, "NEW_TOKEN");
break;
case NGX_QUIC_FT_HANDSHAKE_DONE:
p = ngx_slprintf(p, last, "HANDSHAKE DONE");
break;
default:
p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type);
break;
}
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s",
tx ? "tx" : "rx", ngx_quic_level_name(f->level),
p - buf, buf);
}
#endif
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c,
ngx_quic_frame_t *frame, void *data);
ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c);
void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame);
void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames);
void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame);
ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f,
size_t len);
ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c);
void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in);
ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
uint64_t limit);
ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
ngx_chain_t *in, uint64_t limit, uint64_t offset);
void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
uint64_t offset);
void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb);
#if (NGX_DEBUG)
void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx);
#else
#define ngx_quic_log_frame(log, f, tx)
#endif
#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */
+671
View File
@@ -0,0 +1,671 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
static void ngx_quic_set_connection_path(ngx_connection_t *c,
ngx_quic_path_t *path);
static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
ngx_quic_path_t *path);
static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
ngx_quic_path_t *path);
static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
ngx_int_t
ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
{
ngx_quic_frame_t frame, *fp;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
frame.level = ssl_encryption_application;
frame.type = NGX_QUIC_FT_PATH_RESPONSE;
frame.u.path_response = *f;
/*
* RFC 9000, 8.2.2. Path Validation Responses
*
* A PATH_RESPONSE frame MUST be sent on the network path where the
* PATH_CHALLENGE frame was received.
*/
/*
* An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
* to at least the smallest allowed maximum datagram size of 1200 bytes.
*/
if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) {
return NGX_ERROR;
}
if (pkt->path == qc->path) {
/*
* RFC 9000, 9.3.3. Off-Path Packet Forwarding
*
* An endpoint that receives a PATH_CHALLENGE on an active path SHOULD
* send a non-probing packet in response.
*/
fp = ngx_quic_alloc_frame(c);
if (fp == NULL) {
return NGX_ERROR;
}
fp->level = ssl_encryption_application;
fp->type = NGX_QUIC_FT_PING;
ngx_quic_queue_frame(qc, fp);
}
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_path_response_frame(ngx_connection_t *c,
ngx_quic_path_challenge_frame_t *f)
{
ngx_uint_t rst;
ngx_queue_t *q;
ngx_quic_path_t *path, *prev;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
/*
* RFC 9000, 8.2.3. Successful Path Validation
*
* A PATH_RESPONSE frame received on any network path validates the path
* on which the PATH_CHALLENGE was sent.
*/
for (q = ngx_queue_head(&qc->paths);
q != ngx_queue_sentinel(&qc->paths);
q = ngx_queue_next(q))
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
if (!path->validating) {
continue;
}
if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0
|| ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0)
{
goto valid;
}
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stale PATH_RESPONSE ignored");
return NGX_OK;
valid:
/*
* RFC 9000, 9.4. Loss Detection and Congestion Control
*
* On confirming a peer's ownership of its new address,
* an endpoint MUST immediately reset the congestion controller
* and round-trip time estimator for the new path to initial values
* unless the only change in the peer's address is its port number.
*/
rst = 1;
prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
if (prev != NULL) {
if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen,
path->sockaddr, path->socklen, 0)
== NGX_OK)
{
/* address did not change */
rst = 0;
}
}
if (rst) {
ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t));
qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
ngx_max(2 * qc->tp.max_udp_payload_size,
14720));
qc->congestion.ssthresh = (size_t) -1;
qc->congestion.recovery_start = ngx_current_msec;
}
/*
* RFC 9000, 9.3. Responding to Connection Migration
*
* After verifying a new client address, the server SHOULD
* send new address validation tokens (Section 8) to the client.
*/
if (ngx_quic_send_new_token(c, path) != NGX_OK) {
return NGX_ERROR;
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic path seq:%uL addr:%V successfully validated",
path->seqnum, &path->addr_text);
ngx_quic_path_dbg(c, "is validated", path);
path->validated = 1;
path->validating = 0;
path->limited = 0;
return NGX_OK;
}
ngx_quic_path_t *
ngx_quic_new_path(ngx_connection_t *c,
struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid)
{
ngx_queue_t *q;
ngx_quic_path_t *path;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!ngx_queue_empty(&qc->free_paths)) {
q = ngx_queue_head(&qc->free_paths);
path = ngx_queue_data(q, ngx_quic_path_t, queue);
ngx_queue_remove(&path->queue);
ngx_memzero(path, sizeof(ngx_quic_path_t));
} else {
path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t));
if (path == NULL) {
return NULL;
}
}
ngx_queue_insert_tail(&qc->paths, &path->queue);
path->cid = cid;
cid->used = 1;
path->limited = 1;
path->seqnum = qc->path_seqnum++;
path->sockaddr = &path->sa.sockaddr;
path->socklen = socklen;
ngx_memcpy(path->sockaddr, sockaddr, socklen);
path->addr_text.data = path->text;
path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
NGX_SOCKADDR_STRLEN, 1);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%uL created addr:%V",
path->seqnum, &path->addr_text);
return path;
}
static ngx_quic_path_t *
ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag)
{
ngx_queue_t *q;
ngx_quic_path_t *path;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
for (q = ngx_queue_head(&qc->paths);
q != ngx_queue_sentinel(&qc->paths);
q = ngx_queue_next(q))
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
if (path->tag == tag) {
return path;
}
}
return NULL;
}
ngx_int_t
ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
off_t len;
ngx_queue_t *q;
ngx_quic_path_t *path, *probe;
ngx_quic_socket_t *qsock;
ngx_quic_send_ctx_t *ctx;
ngx_quic_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
qsock = ngx_quic_get_socket(c);
len = pkt->raw->last - pkt->raw->start;
if (c->udp->buffer == NULL) {
/* first ever packet in connection, path already exists */
path = qc->path;
goto update;
}
probe = NULL;
for (q = ngx_queue_head(&qc->paths);
q != ngx_queue_sentinel(&qc->paths);
q = ngx_queue_next(q))
{
path = ngx_queue_data(q, ngx_quic_path_t, queue);
if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen,
path->sockaddr, path->socklen, 1)
== NGX_OK)
{
goto update;
}
if (path->tag == NGX_QUIC_PATH_PROBE) {
probe = path;
}
}
/* packet from new path, drop current probe, if any */
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
/*
* only accept highest-numbered packets to prevent connection id
* exhaustion by excessive probing packets from unknown paths
*/
if (pkt->pn != ctx->largest_pn) {
return NGX_DONE;
}
if (probe && ngx_quic_free_path(c, probe) != NGX_OK) {
return NGX_ERROR;
}
/* new path requires new client id */
cid = ngx_quic_next_client_id(c);
if (cid == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic no available client ids for new path");
/* stop processing of this datagram */
return NGX_DONE;
}
path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid);
if (path == NULL) {
return NGX_ERROR;
}
path->tag = NGX_QUIC_PATH_PROBE;
/*
* client arrived using new path and previously seen DCID,
* this indicates NAT rebinding (or bad client)
*/
if (qsock->used) {
pkt->rebound = 1;
}
update:
qsock->used = 1;
pkt->path = path;
/* TODO: this may be too late in some cases;
* for example, if error happens during decrypt(), we cannot
* send CC, if error happens in 1st packet, due to amplification
* limit, because path->received = 0
*
* should we account garbage as received or only decrypting packets?
*/
path->received += len;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet len:%O via sock seq:%L path seq:%uL",
len, (int64_t) qsock->sid.seqnum, path->seqnum);
ngx_quic_path_dbg(c, "status", path);
return NGX_OK;
}
ngx_int_t
ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_queue_remove(&path->queue);
ngx_queue_insert_head(&qc->free_paths, &path->queue);
/*
* invalidate CID that is no longer usable for any other path;
* this also requests new CIDs from client
*/
if (path->cid) {
if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) {
return NGX_ERROR;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%uL addr:%V retired",
path->seqnum, &path->addr_text);
return NGX_OK;
}
static void
ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path)
{
size_t len;
ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen);
c->socklen = path->socklen;
if (c->addr_text.data) {
len = ngx_min(c->addr_text.len, path->addr_text.len);
ngx_memcpy(c->addr_text.data, path->addr_text.data, len);
c->addr_text.len = len;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic send path set to seq:%uL addr:%V",
path->seqnum, &path->addr_text);
}
ngx_int_t
ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
ngx_quic_path_t *next, *bkp;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
/* got non-probing packet via non-active path */
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
/*
* RFC 9000, 9.3. Responding to Connection Migration
*
* An endpoint only changes the address to which it sends packets in
* response to the highest-numbered non-probing packet.
*/
if (pkt->pn != ctx->largest_pn) {
return NGX_OK;
}
next = pkt->path;
/*
* RFC 9000, 9.3.3:
*
* In response to an apparent migration, endpoints MUST validate the
* previously active path using a PATH_CHALLENGE frame.
*/
if (pkt->rebound) {
/* NAT rebinding: client uses new path with old SID */
if (ngx_quic_validate_path(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
if (qc->path->validated) {
if (next->tag != NGX_QUIC_PATH_BACKUP) {
/* can delete backup path, if any */
bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) {
return NGX_ERROR;
}
}
qc->path->tag = NGX_QUIC_PATH_BACKUP;
ngx_quic_path_dbg(c, "is now backup", qc->path);
} else {
if (ngx_quic_free_path(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
/* switch active path to migrated */
qc->path = next;
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
ngx_quic_set_connection_path(c, next);
if (!next->validated && !next->validating) {
if (ngx_quic_validate_path(c, next) != NGX_OK) {
return NGX_ERROR;
}
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic migrated to path seq:%uL addr:%V",
qc->path->seqnum, &qc->path->addr_text);
ngx_quic_path_dbg(c, "is now active", qc->path);
return NGX_OK;
}
static ngx_int_t
ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_msec_t pto;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic initiated validation of path seq:%uL", path->seqnum);
path->validating = 1;
if (RAND_bytes(path->challenge1, 8) != 1) {
return NGX_ERROR;
}
if (RAND_bytes(path->challenge2, 8) != 1) {
return NGX_ERROR;
}
if (ngx_quic_send_path_challenge(c, path) != NGX_OK) {
return NGX_ERROR;
}
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
pto = ngx_quic_pto(c, ctx);
path->expires = ngx_current_msec + pto;
path->tries = NGX_QUIC_PATH_RETRIES;
if (!qc->path_validation.timer_set) {
ngx_add_timer(&qc->path_validation, pto);
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_quic_frame_t frame;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%uL send path_challenge tries:%ui",
path->seqnum, path->tries);
ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
frame.level = ssl_encryption_application;
frame.type = NGX_QUIC_FT_PATH_CHALLENGE;
ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8);
/*
* RFC 9000, 8.2.1. Initiating Path Validation
*
* An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame
* to at least the smallest allowed maximum datagram size of 1200 bytes,
* unless the anti-amplification limit for the path does not permit
* sending a datagram of this size.
*/
/* same applies to PATH_RESPONSE frames */
if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
return NGX_ERROR;
}
ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8);
if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
void
ngx_quic_path_validation_handler(ngx_event_t *ev)
{
ngx_msec_t now;
ngx_queue_t *q;
ngx_msec_int_t left, next, pto;
ngx_quic_path_t *path, *bkp;
ngx_connection_t *c;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
c = ev->data;
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
pto = ngx_quic_pto(c, ctx);
next = -1;
now = ngx_current_msec;
q = ngx_queue_head(&qc->paths);
while (q != ngx_queue_sentinel(&qc->paths)) {
path = ngx_queue_data(q, ngx_quic_path_t, queue);
q = ngx_queue_next(q);
if (!path->validating) {
continue;
}
left = path->expires - now;
if (left > 0) {
if (next == -1 || left < next) {
next = left;
}
continue;
}
if (--path->tries) {
path->expires = ngx_current_msec + pto;
if (next == -1 || pto < next) {
next = pto;
}
/* retransmit */
(void) ngx_quic_send_path_challenge(c, path);
continue;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"quic path seq:%uL validation failed", path->seqnum);
/* found expired path */
path->validated = 0;
path->validating = 0;
path->limited = 1;
/* RFC 9000, 9.3.2. On-Path Address Spoofing
*
* To protect the connection from failing due to such a spurious
* migration, an endpoint MUST revert to using the last validated
* peer address when validation of a new peer address fails.
*/
if (qc->path == path) {
/* active path validation failed */
bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
if (bkp == NULL) {
qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
qc->error_reason = "no viable path";
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
qc->path = bkp;
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
ngx_quic_set_connection_path(c, qc->path);
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic path seq:%uL addr:%V is restored from backup",
qc->path->seqnum, &qc->path->addr_text);
ngx_quic_path_dbg(c, "is active", qc->path);
}
if (ngx_quic_free_path(c, path) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
}
if (next != -1) {
ngx_add_timer(&qc->path_validation, next);
}
}
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_PATH_RETRIES 3
#define NGX_QUIC_PATH_PROBE 0
#define NGX_QUIC_PATH_ACTIVE 1
#define NGX_QUIC_PATH_BACKUP 2
#define ngx_quic_path_dbg(c, msg, path) \
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \
"quic path seq:%uL %s sent:%O recvd:%O state:%s%s%s", \
path->seqnum, msg, path->sent, path->received, \
path->limited ? "L" : "", path->validated ? "V": "N", \
path->validating ? "R": "");
ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f);
ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c,
ngx_quic_path_challenge_frame_t *f);
ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c,
struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid);
ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path);
ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c,
ngx_quic_header_t *pkt);
void ngx_quic_path_validation_handler(ngx_event_t *ev);
#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */
+1292
View File
@@ -0,0 +1,1292 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252
#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232
#define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */
#define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */
#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */
#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */
#define NGX_QUIC_RETRY_BUFFER_SIZE 256
/* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */
/*
* RFC 9000, 10.3. Stateless Reset
*
* Endpoints MUST discard packets that are too small to be valid QUIC
* packets. With the set of AEAD functions defined in [QUIC-TLS],
* short header packets that are smaller than 21 bytes are never valid.
*/
#define NGX_QUIC_MIN_PKT_LEN 21
#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */
#define NGX_QUIC_MAX_SR_PACKET 1200
#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */
#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */
static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c);
static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
uint64_t pnum);
#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c);
static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c);
static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf,
size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment);
#endif
static ssize_t ngx_quic_output_packet(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min);
static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
ngx_quic_header_t *pkt, ngx_quic_path_t *path);
static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c);
static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
struct sockaddr *sockaddr, socklen_t socklen);
static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt,
ngx_quic_send_ctx_t *ctx);
static size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path,
size_t size);
size_t
ngx_quic_max_udp_payload(ngx_connection_t *c)
{
/* TODO: path MTU discovery */
#if (NGX_HAVE_INET6)
if (c->sockaddr->sa_family == AF_INET6) {
return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6;
}
#endif
return NGX_QUIC_MAX_UDP_PAYLOAD_OUT;
}
ngx_int_t
ngx_quic_output(ngx_connection_t *c)
{
size_t in_flight;
ngx_int_t rc;
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
c->log->action = "sending frames";
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
in_flight = cg->in_flight;
#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
if (ngx_quic_allow_segmentation(c)) {
rc = ngx_quic_create_segments(c);
} else
#endif
{
rc = ngx_quic_create_datagrams(c);
}
if (rc != NGX_OK) {
return NGX_ERROR;
}
if (in_flight == cg->in_flight || qc->closing) {
/* no ack-eliciting data was sent or we are done */
return NGX_OK;
}
if (!qc->send_timer_set) {
qc->send_timer_set = 1;
ngx_add_timer(c->read, qc->tp.max_idle_timeout);
}
ngx_quic_set_lost_timer(c);
return NGX_OK;
}
static ngx_int_t
ngx_quic_create_datagrams(ngx_connection_t *c)
{
size_t len, min;
ssize_t n;
u_char *p;
uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST];
ngx_uint_t i, pad;
ngx_quic_path_t *path;
ngx_quic_send_ctx_t *ctx;
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
path = qc->path;
while (cg->in_flight < cg->window) {
p = dst;
len = ngx_min(qc->ctp.max_udp_payload_size,
NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
len = ngx_quic_path_limit(c, path, len);
pad = ngx_quic_get_padding_level(c);
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ctx = &qc->send_ctx[i];
preserved_pnum[i] = ctx->pnum;
if (ngx_quic_generate_ack(c, ctx) != NGX_OK) {
return NGX_ERROR;
}
min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE)
? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0;
if (min > len) {
/* padding can't be applied - avoid sending the packet */
while (i-- > 0) {
ctx = &qc->send_ctx[i];
ngx_quic_revert_send(c, ctx, preserved_pnum[i]);
}
return NGX_OK;
}
n = ngx_quic_output_packet(c, ctx, p, len, min);
if (n == NGX_ERROR) {
return NGX_ERROR;
}
p += n;
len -= n;
}
len = p - dst;
if (len == 0) {
break;
}
n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen);
if (n == NGX_ERROR) {
return NGX_ERROR;
}
if (n == NGX_AGAIN) {
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]);
}
ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY);
break;
}
for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
ngx_quic_commit_send(c, &qc->send_ctx[i]);
}
path->sent += len;
}
return NGX_OK;
}
static void
ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
{
ngx_queue_t *q;
ngx_quic_frame_t *f;
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
while (!ngx_queue_empty(&ctx->sending)) {
q = ngx_queue_head(&ctx->sending);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
ngx_queue_remove(q);
if (f->pkt_need_ack && !qc->closing) {
ngx_queue_insert_tail(&ctx->sent, q);
cg->in_flight += f->plen;
} else {
ngx_quic_free_frame(c, f);
}
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion send if:%uz", cg->in_flight);
}
static void
ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
uint64_t pnum)
{
ngx_queue_t *q;
while (!ngx_queue_empty(&ctx->sending)) {
q = ngx_queue_last(&ctx->sending);
ngx_queue_remove(q);
ngx_queue_insert_head(&ctx->frames, q);
}
ctx->pnum = pnum;
}
#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
static ngx_uint_t
ngx_quic_allow_segmentation(ngx_connection_t *c)
{
size_t bytes, len;
ngx_queue_t *q;
ngx_quic_frame_t *f;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (!qc->conf->gso_enabled) {
return 0;
}
if (qc->path->limited) {
/* don't even try to be faster on non-validated paths */
return 0;
}
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial);
if (!ngx_queue_empty(&ctx->frames)) {
return 0;
}
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
if (!ngx_queue_empty(&ctx->frames)) {
return 0;
}
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
bytes = 0;
len = ngx_min(qc->ctp.max_udp_payload_size,
NGX_QUIC_MAX_UDP_SEGMENT_BUF);
for (q = ngx_queue_head(&ctx->frames);
q != ngx_queue_sentinel(&ctx->frames);
q = ngx_queue_next(q))
{
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
bytes += f->len;
if (bytes > len * 3) {
/* require at least ~3 full packets to batch */
return 1;
}
}
return 0;
}
static ngx_int_t
ngx_quic_create_segments(ngx_connection_t *c)
{
size_t len, segsize;
ssize_t n;
u_char *p, *end;
uint64_t preserved_pnum;
ngx_uint_t nseg;
ngx_quic_path_t *path;
ngx_quic_send_ctx_t *ctx;
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF];
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
path = qc->path;
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
if (ngx_quic_generate_ack(c, ctx) != NGX_OK) {
return NGX_ERROR;
}
segsize = ngx_min(qc->ctp.max_udp_payload_size,
NGX_QUIC_MAX_UDP_SEGMENT_BUF);
p = dst;
end = dst + sizeof(dst);
nseg = 0;
preserved_pnum = ctx->pnum;
for ( ;; ) {
len = ngx_min(segsize, (size_t) (end - p));
if (len && cg->in_flight < cg->window) {
n = ngx_quic_output_packet(c, ctx, p, len, len);
if (n == NGX_ERROR) {
return NGX_ERROR;
}
if (n) {
p += n;
nseg++;
}
} else {
n = 0;
}
if (p == dst) {
break;
}
if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) {
n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr,
path->socklen, segsize);
if (n == NGX_ERROR) {
return NGX_ERROR;
}
if (n == NGX_AGAIN) {
ngx_quic_revert_send(c, ctx, preserved_pnum);
ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY);
break;
}
ngx_quic_commit_send(c, ctx);
path->sent += n;
p = dst;
nseg = 0;
preserved_pnum = ctx->pnum;
}
}
return NGX_OK;
}
static ssize_t
ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len,
struct sockaddr *sockaddr, socklen_t socklen, size_t segment)
{
size_t clen;
ssize_t n;
uint16_t *valp;
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsg;
#if (NGX_HAVE_ADDRINFO_CMSG)
char msg_control[CMSG_SPACE(sizeof(uint16_t))
+ CMSG_SPACE(sizeof(ngx_addrinfo_t))];
#else
char msg_control[CMSG_SPACE(sizeof(uint16_t))];
#endif
ngx_memzero(&msg, sizeof(struct msghdr));
ngx_memzero(msg_control, sizeof(msg_control));
iov.iov_len = len;
iov.iov_base = buf;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_name = sockaddr;
msg.msg_namelen = socklen;
msg.msg_control = msg_control;
msg.msg_controllen = sizeof(msg_control);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_UDP;
cmsg->cmsg_type = UDP_SEGMENT;
cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t));
clen = CMSG_SPACE(sizeof(uint16_t));
valp = (void *) CMSG_DATA(cmsg);
*valp = segment;
#if (NGX_HAVE_ADDRINFO_CMSG)
if (c->listening && c->listening->wildcard && c->local_sockaddr) {
cmsg = CMSG_NXTHDR(&msg, cmsg);
clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
}
#endif
msg.msg_controllen = clen;
n = ngx_sendmsg(c, &msg, 0);
if (n < 0) {
return n;
}
c->sent += n;
return n;
}
#endif
static ngx_uint_t
ngx_quic_get_padding_level(ngx_connection_t *c)
{
ngx_uint_t i;
ngx_queue_t *q;
ngx_quic_frame_t *f;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
/*
* RFC 9000, 14.1. Initial Datagram Size
*
* Similarly, a server MUST expand the payload of all UDP datagrams
* carrying ack-eliciting Initial packets to at least the smallest
* allowed maximum datagram size of 1200 bytes.
*/
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial);
for (q = ngx_queue_head(&ctx->frames);
q != ngx_queue_sentinel(&ctx->frames);
q = ngx_queue_next(q))
{
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
if (f->need_ack) {
for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) {
ctx = &qc->send_ctx[i + 1];
if (ngx_queue_empty(&ctx->frames)) {
break;
}
}
return i;
}
}
return NGX_QUIC_SEND_CTX_LAST;
}
static ssize_t
ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
u_char *data, size_t max, size_t min)
{
size_t len, pad, min_payload, max_payload;
u_char *p;
ssize_t flen;
ngx_str_t res;
ngx_int_t rc;
ngx_uint_t nframes, expand;
ngx_msec_t now;
ngx_queue_t *q;
ngx_quic_frame_t *f;
ngx_quic_header_t pkt;
ngx_quic_connection_t *qc;
static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
if (ngx_queue_empty(&ctx->frames)) {
return 0;
}
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic output %s packet max:%uz min:%uz",
ngx_quic_level_name(ctx->level), max, min);
qc = ngx_quic_get_connection(c);
ngx_quic_init_packet(c, ctx, &pkt, qc->path);
min_payload = ngx_quic_payload_size(&pkt, min);
max_payload = ngx_quic_payload_size(&pkt, max);
/* RFC 9001, 5.4.2. Header Protection Sample */
pad = 4 - pkt.num_len;
min_payload = ngx_max(min_payload, pad);
if (min_payload > max_payload) {
return 0;
}
now = ngx_current_msec;
nframes = 0;
p = src;
len = 0;
expand = 0;
for (q = ngx_queue_head(&ctx->frames);
q != ngx_queue_sentinel(&ctx->frames);
q = ngx_queue_next(q))
{
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
if (!expand && (f->type == NGX_QUIC_FT_PATH_RESPONSE
|| f->type == NGX_QUIC_FT_PATH_CHALLENGE))
{
/*
* RFC 9000, 8.2.1. Initiating Path Validation
*
* An endpoint MUST expand datagrams that contain a
* PATH_CHALLENGE frame to at least the smallest allowed
* maximum datagram size of 1200 bytes...
*
* (same applies to PATH_RESPONSE frames)
*/
if (max < 1200) {
/* expanded packet will not fit */
break;
}
if (min < 1200) {
min = 1200;
min_payload = ngx_quic_payload_size(&pkt, min);
}
expand = 1;
}
if (len >= max_payload) {
break;
}
if (len + f->len > max_payload) {
rc = ngx_quic_split_frame(c, f, max_payload - len);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (rc == NGX_DECLINED) {
break;
}
}
if (f->need_ack) {
pkt.need_ack = 1;
}
ngx_quic_log_frame(c->log, f, 1);
flen = ngx_quic_create_frame(p, f);
if (flen == -1) {
return NGX_ERROR;
}
len += flen;
p += flen;
f->pnum = ctx->pnum;
f->first = now;
f->last = now;
f->plen = 0;
nframes++;
if (f->flush) {
break;
}
}
if (nframes == 0) {
return 0;
}
if (len < min_payload) {
ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len);
len = min_payload;
}
pkt.payload.data = src;
pkt.payload.len = len;
res.data = data;
ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet tx %s bytes:%ui"
" need_ack:%d number:%L encoded nl:%d trunc:0x%xD",
ngx_quic_level_name(ctx->level), pkt.payload.len,
pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc);
if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
return NGX_ERROR;
}
ctx->pnum++;
if (pkt.need_ack) {
q = ngx_queue_head(&ctx->frames);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
f->plen = res.len;
}
while (nframes--) {
q = ngx_queue_head(&ctx->frames);
f = ngx_queue_data(q, ngx_quic_frame_t, queue);
f->pkt_need_ack = pkt.need_ack;
ngx_queue_remove(q);
ngx_queue_insert_tail(&ctx->sending, q);
}
return res.len;
}
static void
ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
ngx_quic_header_t *pkt, ngx_quic_path_t *path)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_memzero(pkt, sizeof(ngx_quic_header_t));
pkt->flags = NGX_QUIC_PKT_FIXED_BIT;
if (ctx->level == ssl_encryption_initial) {
pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL;
} else if (ctx->level == ssl_encryption_handshake) {
pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE;
} else {
if (qc->key_phase) {
pkt->flags |= NGX_QUIC_PKT_KPHASE;
}
}
pkt->dcid.data = path->cid->id;
pkt->dcid.len = path->cid->len;
pkt->scid = qc->tp.initial_scid;
pkt->version = qc->version;
pkt->log = c->log;
pkt->level = ctx->level;
pkt->keys = qc->keys;
ngx_quic_set_packet_number(pkt, ctx);
}
static ssize_t
ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
struct sockaddr *sockaddr, socklen_t socklen)
{
ssize_t n;
struct iovec iov;
struct msghdr msg;
#if (NGX_HAVE_ADDRINFO_CMSG)
struct cmsghdr *cmsg;
char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
#endif
ngx_memzero(&msg, sizeof(struct msghdr));
iov.iov_len = len;
iov.iov_base = buf;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_name = sockaddr;
msg.msg_namelen = socklen;
#if (NGX_HAVE_ADDRINFO_CMSG)
if (c->listening && c->listening->wildcard && c->local_sockaddr) {
msg.msg_control = msg_control;
msg.msg_controllen = sizeof(msg_control);
ngx_memzero(msg_control, sizeof(msg_control));
cmsg = CMSG_FIRSTHDR(&msg);
msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
}
#endif
n = ngx_sendmsg(c, &msg, 0);
if (n < 0) {
return n;
}
c->sent += n;
return n;
}
static void
ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx)
{
uint64_t delta;
delta = ctx->pnum - ctx->largest_ack;
pkt->number = ctx->pnum;
if (delta <= 0x7F) {
pkt->num_len = 1;
pkt->trunc = ctx->pnum & 0xff;
} else if (delta <= 0x7FFF) {
pkt->num_len = 2;
pkt->flags |= 0x1;
pkt->trunc = ctx->pnum & 0xffff;
} else if (delta <= 0x7FFFFF) {
pkt->num_len = 3;
pkt->flags |= 0x2;
pkt->trunc = ctx->pnum & 0xffffff;
} else {
pkt->num_len = 4;
pkt->flags |= 0x3;
pkt->trunc = ctx->pnum & 0xffffffff;
}
}
ngx_int_t
ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt)
{
size_t len;
ngx_quic_header_t pkt;
static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"sending version negotiation packet");
pkt.log = c->log;
pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT;
pkt.dcid = inpkt->scid;
pkt.scid = inpkt->dcid;
len = ngx_quic_create_version_negotiation(&pkt, buf);
#ifdef NGX_QUIC_DEBUG_PACKETS
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic vnego packet to send len:%uz %*xs", len, len, buf);
#endif
(void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
return NGX_DONE;
}
ngx_int_t
ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf,
ngx_quic_header_t *pkt)
{
u_char *token;
size_t len, max;
uint16_t rndbytes;
u_char buf[NGX_QUIC_MAX_SR_PACKET];
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic handle stateless reset output");
if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) {
return NGX_DECLINED;
}
if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) {
len = pkt->len - 1;
} else {
max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3);
if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) {
return NGX_ERROR;
}
len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1))
+ NGX_QUIC_MIN_SR_PACKET;
}
if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) {
return NGX_ERROR;
}
buf[0] &= ~NGX_QUIC_PKT_LONG;
buf[0] |= NGX_QUIC_PKT_FIXED_BIT;
token = &buf[len - NGX_QUIC_SR_TOKEN_LEN];
if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token)
!= NGX_OK)
{
return NGX_ERROR;
}
(void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
return NGX_DECLINED;
}
ngx_int_t
ngx_quic_send_cc(ngx_connection_t *c)
{
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (qc->draining) {
return NGX_OK;
}
if (qc->closing
&& ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL)
{
/* dot not send CC too often */
return NGX_OK;
}
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = qc->error_level;
frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP
: NGX_QUIC_FT_CONNECTION_CLOSE;
frame->u.close.error_code = qc->error;
frame->u.close.frame_type = qc->error_ftype;
if (qc->error_reason) {
frame->u.close.reason.len = ngx_strlen(qc->error_reason);
frame->u.close.reason.data = (u_char *) qc->error_reason;
}
ngx_quic_queue_frame(qc, frame);
qc->last_cc = ngx_current_msec;
return ngx_quic_output(c);
}
ngx_int_t
ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt,
ngx_uint_t err, const char *reason)
{
ssize_t len;
ngx_str_t res;
ngx_quic_keys_t keys;
ngx_quic_frame_t frame;
ngx_quic_header_t pkt;
static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
frame.level = inpkt->level;
frame.type = NGX_QUIC_FT_CONNECTION_CLOSE;
frame.u.close.error_code = err;
frame.u.close.reason.data = (u_char *) reason;
frame.u.close.reason.len = ngx_strlen(reason);
len = ngx_quic_create_frame(NULL, &frame);
if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
return NGX_ERROR;
}
ngx_quic_log_frame(c->log, &frame, 1);
len = ngx_quic_create_frame(src, &frame);
if (len == -1) {
return NGX_ERROR;
}
ngx_memzero(&keys, sizeof(ngx_quic_keys_t));
pkt.keys = &keys;
if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log)
!= NGX_OK)
{
return NGX_ERROR;
}
pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG
| NGX_QUIC_PKT_INITIAL;
pkt.num_len = 1;
/*
* pkt.num = 0;
* pkt.trunc = 0;
*/
pkt.version = inpkt->version;
pkt.log = c->log;
pkt.level = inpkt->level;
pkt.dcid = inpkt->scid;
pkt.scid = inpkt->dcid;
pkt.payload.data = src;
pkt.payload.len = len;
res.data = dst;
if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) {
return NGX_ERROR;
}
return NGX_DONE;
}
ngx_int_t
ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf,
ngx_quic_header_t *inpkt)
{
time_t expires;
ssize_t len;
ngx_str_t res, token;
ngx_quic_header_t pkt;
u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
u_char dcid[NGX_QUIC_SERVER_CID_LEN];
u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE];
expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME;
token.data = tbuf;
token.len = NGX_QUIC_TOKEN_BUF_SIZE;
if (ngx_quic_new_token(c->log, c->sockaddr, c->socklen, conf->av_token_key,
&token, &inpkt->dcid, expires, 1)
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY;
pkt.version = inpkt->version;
pkt.log = c->log;
pkt.odcid = inpkt->dcid;
pkt.dcid = inpkt->scid;
/* TODO: generate routable dcid */
if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) {
return NGX_ERROR;
}
pkt.scid.len = NGX_QUIC_SERVER_CID_LEN;
pkt.scid.data = dcid;
pkt.token = token;
res.data = buf;
if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
return NGX_ERROR;
}
#ifdef NGX_QUIC_DEBUG_PACKETS
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet to send len:%uz %xV", res.len, &res);
#endif
len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen);
if (len < 0) {
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic retry packet sent to %xV", &pkt.dcid);
/*
* RFC 9000, 17.2.5.1. Sending a Retry Packet
*
* A server MUST NOT send more than one Retry
* packet in response to a single UDP datagram.
* NGX_DONE will stop quic_input() from processing further
*/
return NGX_DONE;
}
ngx_int_t
ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path)
{
time_t expires;
ngx_str_t token;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE];
qc = ngx_quic_get_connection(c);
expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME;
token.data = tbuf;
token.len = NGX_QUIC_TOKEN_BUF_SIZE;
if (ngx_quic_new_token(c->log, path->sockaddr, path->socklen,
qc->conf->av_token_key, &token, NULL, expires, 0)
!= NGX_OK)
{
return NGX_ERROR;
}
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_NEW_TOKEN;
frame->u.token.length = token.len;
frame->u.token.data = token.data;
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
ngx_int_t
ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
{
size_t len, left;
uint64_t ack_delay;
ngx_buf_t *b;
ngx_uint_t i;
ngx_chain_t *cl, **ll;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ack_delay = ngx_current_msec - ctx->largest_received;
ack_delay *= 1000;
ack_delay >>= qc->tp.ack_delay_exponent;
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
ll = &frame->data;
b = NULL;
for (i = 0; i < ctx->nranges; i++) {
len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap,
ctx->ranges[i].range);
left = b ? b->end - b->last : 0;
if (left < len) {
cl = ngx_quic_alloc_chain(c);
if (cl == NULL) {
return NGX_ERROR;
}
*ll = cl;
ll = &cl->next;
b = cl->buf;
left = b->end - b->last;
if (left < len) {
return NGX_ERROR;
}
}
b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap,
ctx->ranges[i].range);
frame->u.ack.ranges_length += len;
}
*ll = NULL;
frame->level = ctx->level;
frame->type = NGX_QUIC_FT_ACK;
frame->u.ack.largest = ctx->largest_range;
frame->u.ack.delay = ack_delay;
frame->u.ack.range_count = ctx->nranges;
frame->u.ack.first_range = ctx->first_range;
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
ngx_int_t
ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
uint64_t smallest, uint64_t largest)
{
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ctx->level;
frame->type = NGX_QUIC_FT_ACK;
frame->u.ack.largest = largest;
frame->u.ack.delay = 0;
frame->u.ack.range_count = 0;
frame->u.ack.first_range = largest - smallest;
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
ngx_int_t
ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
size_t min, ngx_quic_path_t *path)
{
size_t min_payload, pad;
ssize_t len, sent;
ngx_str_t res;
ngx_quic_header_t pkt;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
ngx_quic_init_packet(c, ctx, &pkt, path);
min = ngx_quic_path_limit(c, path, min);
min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0;
pad = 4 - pkt.num_len;
min_payload = ngx_max(min_payload, pad);
len = ngx_quic_create_frame(NULL, frame);
if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
return NGX_ERROR;
}
ngx_quic_log_frame(c->log, frame, 1);
len = ngx_quic_create_frame(src, frame);
if (len == -1) {
return NGX_ERROR;
}
if (len < (ssize_t) min_payload) {
ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len);
len = min_payload;
}
pkt.payload.data = src;
pkt.payload.len = len;
res.data = dst;
if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
return NGX_ERROR;
}
ctx->pnum++;
sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen);
if (sent < 0) {
return NGX_ERROR;
}
path->sent += sent;
return NGX_OK;
}
static size_t
ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size)
{
off_t max;
if (path->limited) {
max = path->received * 3;
max = (path->sent >= max) ? 0 : max - path->sent;
if ((off_t) size > max) {
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path limit %uz - %O", size, max);
return max;
}
}
return size;
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
ngx_int_t ngx_quic_output(ngx_connection_t *c);
ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
ngx_quic_header_t *inpkt);
ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c,
ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_send_cc(ngx_connection_t *c);
ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c,
ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason);
ngx_int_t ngx_quic_send_retry(ngx_connection_t *c,
ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path);
ngx_int_t ngx_quic_send_ack(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx);
ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest);
ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
size_t min, ngx_quic_path_t *path);
#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */
+1126
View File
@@ -0,0 +1,1126 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */
#define NGX_QUIC_HP_LEN 5
#define NGX_QUIC_AES_128_KEY_LEN 16
#ifndef TLS1_3_CK_AES_128_GCM_SHA256
#define TLS1_3_CK_AES_128_GCM_SHA256 0x03001301
#define TLS1_3_CK_AES_256_GCM_SHA384 0x03001302
#define TLS1_3_CK_CHACHA20_POLY1305_SHA256 \
0x03001303
#endif
#ifdef OPENSSL_IS_BORINGSSL
#define ngx_quic_cipher_t EVP_AEAD
#else
#define ngx_quic_cipher_t EVP_CIPHER
#endif
typedef struct {
const ngx_quic_cipher_t *c;
const EVP_CIPHER *hp;
const EVP_MD *d;
} ngx_quic_ciphers_t;
typedef struct {
size_t out_len;
u_char *out;
size_t prk_len;
const uint8_t *prk;
size_t label_len;
const u_char *label;
} ngx_quic_hkdf_t;
#define ngx_quic_hkdf_set(label, out, prk) \
{ \
(out)->len, (out)->data, \
(prk)->len, (prk)->data, \
(sizeof(label) - 1), (u_char *)(label), \
}
static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len,
const EVP_MD *digest, const u_char *prk, size_t prk_len,
const u_char *info, size_t info_len);
static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len,
const EVP_MD *digest, const u_char *secret, size_t secret_len,
const u_char *salt, size_t salt_len);
static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
uint64_t *largest_pn);
static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
static ngx_int_t ngx_quic_ciphers(ngx_uint_t id,
ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level);
static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher,
ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
ngx_str_t *ad, ngx_log_t *log);
static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher,
ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
ngx_str_t *ad, ngx_log_t *log);
static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
ngx_quic_secret_t *s, u_char *out, u_char *in);
static ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf,
const EVP_MD *digest, ngx_log_t *log);
static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt,
ngx_str_t *res);
static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt,
ngx_str_t *res);
static ngx_int_t
ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers,
enum ssl_encryption_level_t level)
{
ngx_int_t len;
if (level == ssl_encryption_initial) {
id = TLS1_3_CK_AES_128_GCM_SHA256;
}
switch (id) {
case TLS1_3_CK_AES_128_GCM_SHA256:
#ifdef OPENSSL_IS_BORINGSSL
ciphers->c = EVP_aead_aes_128_gcm();
#else
ciphers->c = EVP_aes_128_gcm();
#endif
ciphers->hp = EVP_aes_128_ctr();
ciphers->d = EVP_sha256();
len = 16;
break;
case TLS1_3_CK_AES_256_GCM_SHA384:
#ifdef OPENSSL_IS_BORINGSSL
ciphers->c = EVP_aead_aes_256_gcm();
#else
ciphers->c = EVP_aes_256_gcm();
#endif
ciphers->hp = EVP_aes_256_ctr();
ciphers->d = EVP_sha384();
len = 32;
break;
case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
#ifdef OPENSSL_IS_BORINGSSL
ciphers->c = EVP_aead_chacha20_poly1305();
#else
ciphers->c = EVP_chacha20_poly1305();
#endif
#ifdef OPENSSL_IS_BORINGSSL
ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305();
#else
ciphers->hp = EVP_chacha20();
#endif
ciphers->d = EVP_sha256();
len = 32;
break;
default:
return NGX_ERROR;
}
return len;
}
ngx_int_t
ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret,
ngx_log_t *log)
{
size_t is_len;
uint8_t is[SHA256_DIGEST_LENGTH];
ngx_uint_t i;
const EVP_MD *digest;
ngx_quic_secret_t *client, *server;
static const uint8_t salt[20] =
"\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17"
"\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a";
client = &keys->secrets[ssl_encryption_initial].client;
server = &keys->secrets[ssl_encryption_initial].server;
/*
* RFC 9001, section 5. Packet Protection
*
* Initial packets use AEAD_AES_128_GCM. The hash function
* for HKDF when deriving initial secrets and keys is SHA-256.
*/
digest = EVP_sha256();
is_len = SHA256_DIGEST_LENGTH;
if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
salt, sizeof(salt))
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_str_t iss = {
.data = is,
.len = is_len
};
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0,
"quic ngx_quic_set_initial_secret");
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
"quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
"quic initial secret len:%uz %*xs", is_len, is_len, is);
#endif
client->secret.len = SHA256_DIGEST_LENGTH;
server->secret.len = SHA256_DIGEST_LENGTH;
client->key.len = NGX_QUIC_AES_128_KEY_LEN;
server->key.len = NGX_QUIC_AES_128_KEY_LEN;
client->hp.len = NGX_QUIC_AES_128_KEY_LEN;
server->hp.len = NGX_QUIC_AES_128_KEY_LEN;
client->iv.len = NGX_QUIC_IV_LEN;
server->iv.len = NGX_QUIC_IV_LEN;
ngx_quic_hkdf_t seq[] = {
/* labels per RFC 9001, 5.1. Packet Protection Keys */
ngx_quic_hkdf_set("tls13 client in", &client->secret, &iss),
ngx_quic_hkdf_set("tls13 quic key", &client->key, &client->secret),
ngx_quic_hkdf_set("tls13 quic iv", &client->iv, &client->secret),
ngx_quic_hkdf_set("tls13 quic hp", &client->hp, &client->secret),
ngx_quic_hkdf_set("tls13 server in", &server->secret, &iss),
ngx_quic_hkdf_set("tls13 quic key", &server->key, &server->secret),
ngx_quic_hkdf_set("tls13 quic iv", &server->iv, &server->secret),
ngx_quic_hkdf_set("tls13 quic hp", &server->hp, &server->secret),
};
for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log)
{
size_t info_len;
uint8_t *p;
uint8_t info[20];
info_len = 2 + 1 + h->label_len + 1;
info[0] = 0;
info[1] = h->out_len;
info[2] = h->label_len;
p = ngx_cpymem(&info[3], h->label, h->label_len);
*p = '\0';
if (ngx_hkdf_expand(h->out, h->out_len, digest,
h->prk, h->prk_len, info, info_len)
!= NGX_OK)
{
ngx_ssl_error(NGX_LOG_INFO, log, 0,
"ngx_hkdf_expand(%*s) failed", h->label_len, h->label);
return NGX_ERROR;
}
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0,
"quic expand \"%*s\" len:%uz %*xs",
h->label_len, h->label, h->out_len, h->out_len, h->out);
#endif
return NGX_OK;
}
static ngx_int_t
ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest,
const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len)
{
#ifdef OPENSSL_IS_BORINGSSL
if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len)
== 0)
{
return NGX_ERROR;
}
return NGX_OK;
#else
EVP_PKEY_CTX *pctx;
pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (pctx == NULL) {
return NGX_ERROR;
}
if (EVP_PKEY_derive_init(pctx) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) {
goto failed;
}
if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) {
goto failed;
}
EVP_PKEY_CTX_free(pctx);
return NGX_OK;
failed:
EVP_PKEY_CTX_free(pctx);
return NGX_ERROR;
#endif
}
static ngx_int_t
ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest,
const u_char *secret, size_t secret_len, const u_char *salt,
size_t salt_len)
{
#ifdef OPENSSL_IS_BORINGSSL
if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt,
salt_len)
== 0)
{
return NGX_ERROR;
}
return NGX_OK;
#else
EVP_PKEY_CTX *pctx;
pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if (pctx == NULL) {
return NGX_ERROR;
}
if (EVP_PKEY_derive_init(pctx) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) {
goto failed;
}
if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) {
goto failed;
}
if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) {
goto failed;
}
EVP_PKEY_CTX_free(pctx);
return NGX_OK;
failed:
EVP_PKEY_CTX_free(pctx);
return NGX_ERROR;
#endif
}
static ngx_int_t
ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad,
ngx_log_t *log)
{
#ifdef OPENSSL_IS_BORINGSSL
EVP_AEAD_CTX *ctx;
ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
EVP_AEAD_DEFAULT_TAG_LENGTH);
if (ctx == NULL) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
return NGX_ERROR;
}
if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
in->data, in->len, ad->data, ad->len)
!= 1)
{
EVP_AEAD_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed");
return NGX_ERROR;
}
EVP_AEAD_CTX_free(ctx);
#else
int len;
u_char *tag;
EVP_CIPHER_CTX *ctx;
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
return NGX_ERROR;
}
if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
return NGX_ERROR;
}
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
== 0)
{
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0,
"EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
return NGX_ERROR;
}
if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
return NGX_ERROR;
}
if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
return NGX_ERROR;
}
if (EVP_DecryptUpdate(ctx, out->data, &len, in->data,
in->len - EVP_GCM_TLS_TAG_LEN)
!= 1)
{
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
return NGX_ERROR;
}
out->len = len;
tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN;
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag)
== 0)
{
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0,
"EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed");
return NGX_ERROR;
}
if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed");
return NGX_ERROR;
}
out->len += len;
EVP_CIPHER_CTX_free(ctx);
#endif
return NGX_OK;
}
static ngx_int_t
ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log)
{
#ifdef OPENSSL_IS_BORINGSSL
EVP_AEAD_CTX *ctx;
ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
EVP_AEAD_DEFAULT_TAG_LENGTH);
if (ctx == NULL) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
return NGX_ERROR;
}
if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
in->data, in->len, ad->data, ad->len)
!= 1)
{
EVP_AEAD_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed");
return NGX_ERROR;
}
EVP_AEAD_CTX_free(ctx);
#else
int len;
EVP_CIPHER_CTX *ctx;
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
return NGX_ERROR;
}
if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
return NGX_ERROR;
}
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
== 0)
{
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0,
"EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
return NGX_ERROR;
}
if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
return NGX_ERROR;
}
if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
return NGX_ERROR;
}
if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
return NGX_ERROR;
}
out->len = len;
if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) {
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed");
return NGX_ERROR;
}
out->len += len;
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN,
out->data + in->len)
== 0)
{
EVP_CIPHER_CTX_free(ctx);
ngx_ssl_error(NGX_LOG_INFO, log, 0,
"EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed");
return NGX_ERROR;
}
EVP_CIPHER_CTX_free(ctx);
out->len += EVP_GCM_TLS_TAG_LEN;
#endif
return NGX_OK;
}
static ngx_int_t
ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
ngx_quic_secret_t *s, u_char *out, u_char *in)
{
int outlen;
EVP_CIPHER_CTX *ctx;
u_char zero[NGX_QUIC_HP_LEN] = {0};
#ifdef OPENSSL_IS_BORINGSSL
uint32_t cnt;
ngx_memcpy(&cnt, in, sizeof(uint32_t));
if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) {
CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt);
return NGX_OK;
}
#endif
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
return NGX_ERROR;
}
if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
goto failed;
}
if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
goto failed;
}
if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed");
goto failed;
}
EVP_CIPHER_CTX_free(ctx);
return NGX_OK;
failed:
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
ngx_int_t
ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write,
ngx_quic_keys_t *keys, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
{
ngx_int_t key_len;
ngx_str_t secret_str;
ngx_uint_t i;
ngx_quic_secret_t *peer_secret;
ngx_quic_ciphers_t ciphers;
peer_secret = is_write ? &keys->secrets[level].server
: &keys->secrets[level].client;
keys->cipher = SSL_CIPHER_get_id(cipher);
key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
if (key_len == NGX_ERROR) {
ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher");
return NGX_ERROR;
}
if (sizeof(peer_secret->secret.data) < secret_len) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"unexpected secret len: %uz", secret_len);
return NGX_ERROR;
}
peer_secret->secret.len = secret_len;
ngx_memcpy(peer_secret->secret.data, secret, secret_len);
peer_secret->key.len = key_len;
peer_secret->iv.len = NGX_QUIC_IV_LEN;
peer_secret->hp.len = key_len;
secret_str.len = secret_len;
secret_str.data = (u_char *) secret;
ngx_quic_hkdf_t seq[] = {
ngx_quic_hkdf_set("tls13 quic key", &peer_secret->key, &secret_str),
ngx_quic_hkdf_set("tls13 quic iv", &peer_secret->iv, &secret_str),
ngx_quic_hkdf_set("tls13 quic hp", &peer_secret->hp, &secret_str),
};
for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
ngx_uint_t
ngx_quic_keys_available(ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level)
{
return keys->secrets[level].client.key.len != 0;
}
void
ngx_quic_keys_discard(ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level)
{
keys->secrets[level].client.key.len = 0;
}
void
ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys)
{
ngx_quic_secrets_t *current, *next, tmp;
current = &keys->secrets[ssl_encryption_application];
next = &keys->next_key;
tmp = *current;
*current = *next;
*next = tmp;
}
ngx_int_t
ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys)
{
ngx_uint_t i;
ngx_quic_ciphers_t ciphers;
ngx_quic_secrets_t *current, *next;
current = &keys->secrets[ssl_encryption_application];
next = &keys->next_key;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update");
if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application)
== NGX_ERROR)
{
return NGX_ERROR;
}
next->client.secret.len = current->client.secret.len;
next->client.key.len = current->client.key.len;
next->client.iv.len = NGX_QUIC_IV_LEN;
next->client.hp = current->client.hp;
next->server.secret.len = current->server.secret.len;
next->server.key.len = current->server.key.len;
next->server.iv.len = NGX_QUIC_IV_LEN;
next->server.hp = current->server.hp;
ngx_quic_hkdf_t seq[] = {
ngx_quic_hkdf_set("tls13 quic ku",
&next->client.secret, &current->client.secret),
ngx_quic_hkdf_set("tls13 quic key",
&next->client.key, &next->client.secret),
ngx_quic_hkdf_set("tls13 quic iv",
&next->client.iv, &next->client.secret),
ngx_quic_hkdf_set("tls13 quic ku",
&next->server.secret, &current->server.secret),
ngx_quic_hkdf_set("tls13 quic key",
&next->server.key, &next->server.secret),
ngx_quic_hkdf_set("tls13 quic iv",
&next->server.iv, &next->server.secret),
};
for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
{
u_char *pnp, *sample;
ngx_str_t ad, out;
ngx_uint_t i;
ngx_quic_secret_t *secret;
ngx_quic_ciphers_t ciphers;
u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN];
ad.data = res->data;
ad.len = ngx_quic_create_header(pkt, ad.data, &pnp);
out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
out.data = res->data + ad.len;
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic ad len:%uz %xV", ad.len, &ad);
#endif
if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
{
return NGX_ERROR;
}
secret = &pkt->keys->secrets[pkt->level].server;
ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number);
if (ngx_quic_tls_seal(ciphers.c, secret, &out,
nonce, &pkt->payload, &ad, pkt->log)
!= NGX_OK)
{
return NGX_ERROR;
}
sample = &out.data[4 - pkt->num_len];
if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
!= NGX_OK)
{
return NGX_ERROR;
}
/* RFC 9001, 5.4.1. Header Protection Application */
ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags);
for (i = 0; i < pkt->num_len; i++) {
pnp[i] ^= mask[i + 1];
}
res->len = ad.len + out.len;
return NGX_OK;
}
static ngx_int_t
ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
{
u_char *start;
ngx_str_t ad, itag;
ngx_quic_secret_t secret;
ngx_quic_ciphers_t ciphers;
/* 5.8. Retry Packet Integrity */
static u_char key[16] =
"\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e";
static u_char nonce[NGX_QUIC_IV_LEN] =
"\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb";
static ngx_str_t in = ngx_string("");
ad.data = res->data;
ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start);
itag.data = ad.data + ad.len;
itag.len = EVP_GCM_TLS_TAG_LEN;
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic retry itag len:%uz %xV", ad.len, &ad);
#endif
if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) {
return NGX_ERROR;
}
secret.key.len = sizeof(key);
ngx_memcpy(secret.key.data, key, sizeof(key));
secret.iv.len = NGX_QUIC_IV_LEN;
if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log)
!= NGX_OK)
{
return NGX_ERROR;
}
res->len = itag.data + itag.len - start;
res->data = start;
return NGX_OK;
}
ngx_int_t
ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret,
ngx_str_t *salt, u_char *out, size_t len)
{
size_t is_len, info_len;
uint8_t *p;
const EVP_MD *digest;
uint8_t is[SHA256_DIGEST_LENGTH];
uint8_t info[20];
digest = EVP_sha256();
is_len = SHA256_DIGEST_LENGTH;
if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
salt->data, salt->len)
!= NGX_OK)
{
ngx_ssl_error(NGX_LOG_INFO, log, 0,
"ngx_hkdf_extract(%s) failed", label);
return NGX_ERROR;
}
info[0] = 0;
info[1] = len;
info[2] = ngx_strlen(label);
info_len = 2 + 1 + info[2] + 1;
if (info_len >= 20) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"ngx_quic_create_key label \"%s\" too long", label);
return NGX_ERROR;
}
p = ngx_cpymem(&info[3], label, info[2]);
*p = '\0';
if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK)
{
ngx_ssl_error(NGX_LOG_INFO, log, 0,
"ngx_hkdf_expand(%s) failed", label);
return NGX_ERROR;
}
return NGX_OK;
}
static uint64_t
ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
uint64_t *largest_pn)
{
u_char *p;
uint64_t truncated_pn, expected_pn, candidate_pn;
uint64_t pn_nbits, pn_win, pn_hwin, pn_mask;
pn_nbits = ngx_min(len * 8, 62);
p = *pos;
truncated_pn = *p++ ^ *mask++;
while (--len) {
truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++);
}
*pos = p;
expected_pn = *largest_pn + 1;
pn_win = 1ULL << pn_nbits;
pn_hwin = pn_win / 2;
pn_mask = pn_win - 1;
candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin)
&& candidate_pn < (1ULL << 62) - pn_win)
{
candidate_pn += pn_win;
} else if (candidate_pn > expected_pn + pn_hwin
&& candidate_pn >= pn_win)
{
candidate_pn -= pn_win;
}
*largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn);
return candidate_pn;
}
static void
ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn)
{
nonce[len - 4] ^= (pn & 0xff000000) >> 24;
nonce[len - 3] ^= (pn & 0x00ff0000) >> 16;
nonce[len - 2] ^= (pn & 0x0000ff00) >> 8;
nonce[len - 1] ^= (pn & 0x000000ff);
}
ngx_int_t
ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res)
{
if (ngx_quic_pkt_retry(pkt->flags)) {
return ngx_quic_create_retry_packet(pkt, res);
}
return ngx_quic_create_packet(pkt, res);
}
ngx_int_t
ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn)
{
u_char *p, *sample;
size_t len;
uint64_t pn, lpn;
ngx_int_t pnl, rc, key_phase;
ngx_str_t in, ad;
ngx_quic_secret_t *secret;
ngx_quic_ciphers_t ciphers;
uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN];
if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
{
return NGX_ERROR;
}
secret = &pkt->keys->secrets[pkt->level].client;
p = pkt->raw->pos;
len = pkt->data + pkt->len - p;
/*
* RFC 9001, 5.4.2. Header Protection Sample
* 5.4.3. AES-Based Header Protection
* 5.4.4. ChaCha20-Based Header Protection
*
* the Packet Number field is assumed to be 4 bytes long
* AES and ChaCha20 algorithms sample 16 bytes
*/
if (len < EVP_GCM_TLS_TAG_LEN + 4) {
return NGX_DECLINED;
}
sample = p + 4;
/* header protection */
if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
!= NGX_OK)
{
return NGX_DECLINED;
}
pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags);
if (ngx_quic_short_pkt(pkt->flags)) {
key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0;
if (key_phase != pkt->key_phase) {
secret = &pkt->keys->next_key.client;
pkt->key_update = 1;
}
}
lpn = *largest_pn;
pnl = (pkt->flags & 0x03) + 1;
pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn);
pkt->pn = pn;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic packet rx clearflags:%xd", pkt->flags);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic packet rx number:%uL len:%xi", pn, pnl);
/* packet protection */
in.data = p;
in.len = len - pnl;
ad.len = p - pkt->data;
ad.data = pkt->plaintext;
ngx_memcpy(ad.data, pkt->data, ad.len);
ad.data[0] = pkt->flags;
do {
ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256;
} while (--pnl);
ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
ngx_quic_compute_nonce(nonce, sizeof(nonce), pn);
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic ad len:%uz %xV", ad.len, &ad);
#endif
pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN;
pkt->payload.data = pkt->plaintext + ad.len;
rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload,
nonce, &in, &ad, pkt->log);
if (rc != NGX_OK) {
return NGX_DECLINED;
}
if (pkt->payload.len == 0) {
/*
* RFC 9000, 12.4. Frames and Frame Types
*
* An endpoint MUST treat receipt of a packet containing no
* frames as a connection error of type PROTOCOL_VIOLATION.
*/
ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet");
pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
return NGX_ERROR;
}
if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) {
/*
* RFC 9000, Reserved Bits
*
* An endpoint MUST treat receipt of a packet that has
* a non-zero value for these bits, after removing both
* packet and header protection, as a connection error
* of type PROTOCOL_VIOLATION.
*/
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic reserved bit set in packet");
pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
return NGX_ERROR;
}
#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic packet payload len:%uz %xV",
pkt->payload.len, &pkt->payload);
#endif
*largest_pn = lpn;
return NGX_OK;
}
@@ -0,0 +1,75 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event_quic_transport.h>
#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1)
/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */
#define NGX_QUIC_IV_LEN 12
/* largest hash used in TLS is SHA-384 */
#define NGX_QUIC_MAX_MD_SIZE 48
typedef struct {
size_t len;
u_char data[NGX_QUIC_MAX_MD_SIZE];
} ngx_quic_md_t;
typedef struct {
size_t len;
u_char data[NGX_QUIC_IV_LEN];
} ngx_quic_iv_t;
typedef struct {
ngx_quic_md_t secret;
ngx_quic_md_t key;
ngx_quic_iv_t iv;
ngx_quic_md_t hp;
} ngx_quic_secret_t;
typedef struct {
ngx_quic_secret_t client;
ngx_quic_secret_t server;
} ngx_quic_secrets_t;
struct ngx_quic_keys_s {
ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST];
ngx_quic_secrets_t next_key;
ngx_uint_t cipher;
};
ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys,
ngx_str_t *secret, ngx_log_t *log);
ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log,
ngx_uint_t is_write, ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *secret, size_t secret_len);
ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level);
void ngx_quic_keys_discard(ngx_quic_keys_t *keys,
enum ssl_encryption_level_t level);
void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys);
ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys);
ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res);
ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn);
#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */
+237
View File
@@ -0,0 +1,237 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
ngx_int_t
ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc,
ngx_quic_header_t *pkt)
{
ngx_quic_socket_t *qsock, *tmp;
ngx_quic_client_id_t *cid;
/*
* qc->path = NULL
*
* qc->nclient_ids = 0
* qc->nsockets = 0
* qc->max_retired_seqnum = 0
* qc->client_seqnum = 0
*/
ngx_queue_init(&qc->sockets);
ngx_queue_init(&qc->free_sockets);
ngx_queue_init(&qc->paths);
ngx_queue_init(&qc->free_paths);
ngx_queue_init(&qc->client_ids);
ngx_queue_init(&qc->free_client_ids);
qc->tp.original_dcid.len = pkt->odcid.len;
qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid);
if (qc->tp.original_dcid.data == NULL) {
return NGX_ERROR;
}
/* socket to use for further processing (id auto-generated) */
qsock = ngx_quic_create_socket(c, qc);
if (qsock == NULL) {
return NGX_ERROR;
}
/* socket is listening at new server id */
if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
return NGX_ERROR;
}
qsock->used = 1;
qc->tp.initial_scid.len = qsock->sid.len;
qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len);
if (qc->tp.initial_scid.data == NULL) {
goto failed;
}
ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len);
/* for all packets except first, this is set at udp layer */
c->udp = &qsock->udp;
/* ngx_quic_get_connection(c) macro is now usable */
/* we have a client identified by scid */
cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL);
if (cid == NULL) {
goto failed;
}
/* path of the first packet is our initial active path */
qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid);
if (qc->path == NULL) {
goto failed;
}
qc->path->tag = NGX_QUIC_PATH_ACTIVE;
if (pkt->validated) {
qc->path->validated = 1;
qc->path->limited = 0;
}
ngx_quic_path_dbg(c, "set active", qc->path);
tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
if (tmp == NULL) {
goto failed;
}
tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */
ngx_memcpy(tmp->sid.id, pkt->odcid.data, pkt->odcid.len);
tmp->sid.len = pkt->odcid.len;
if (ngx_quic_listen(c, qc, tmp) != NGX_OK) {
goto failed;
}
return NGX_OK;
failed:
ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
c->udp = NULL;
return NGX_ERROR;
}
ngx_quic_socket_t *
ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc)
{
ngx_queue_t *q;
ngx_quic_socket_t *sock;
if (!ngx_queue_empty(&qc->free_sockets)) {
q = ngx_queue_head(&qc->free_sockets);
sock = ngx_queue_data(q, ngx_quic_socket_t, queue);
ngx_queue_remove(&sock->queue);
ngx_memzero(sock, sizeof(ngx_quic_socket_t));
} else {
sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
if (sock == NULL) {
return NULL;
}
}
sock->sid.len = NGX_QUIC_SERVER_CID_LEN;
if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) {
return NULL;
}
sock->sid.seqnum = qc->server_seqnum++;
return sock;
}
void
ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_queue_remove(&qsock->queue);
ngx_queue_insert_head(&qc->free_sockets, &qsock->queue);
ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
qc->nsockets--;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%L closed nsock:%ui",
(int64_t) qsock->sid.seqnum, qc->nsockets);
}
ngx_int_t
ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
ngx_quic_socket_t *qsock)
{
ngx_str_t id;
ngx_quic_server_id_t *sid;
sid = &qsock->sid;
id.data = sid->id;
id.len = sid->len;
qsock->udp.connection = c;
qsock->udp.node.key = ngx_crc32_long(id.data, id.len);
ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node);
ngx_queue_insert_tail(&qc->sockets, &qsock->queue);
qc->nsockets++;
qsock->quic = qc;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%L listening at sid:%xV nsock:%ui",
(int64_t) sid->seqnum, &id, qc->nsockets);
return NGX_OK;
}
void
ngx_quic_close_sockets(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
while (!ngx_queue_empty(&qc->sockets)) {
q = ngx_queue_head(&qc->sockets);
qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
ngx_quic_close_socket(c, qsock);
}
}
ngx_quic_socket_t *
ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum)
{
ngx_queue_t *q;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
for (q = ngx_queue_head(&qc->sockets);
q != ngx_queue_sentinel(&qc->sockets);
q = ngx_queue_next(q))
{
qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
if (qsock->sid.seqnum == seqnum) {
return qsock;
}
}
return NULL;
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c,
ngx_quic_connection_t *qc, ngx_quic_header_t *pkt);
void ngx_quic_close_sockets(ngx_connection_t *c);
ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c,
ngx_quic_connection_t *qc);
ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
ngx_quic_socket_t *qsock);
void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock);
ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum);
#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */
+610
View File
@@ -0,0 +1,610 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
/*
* RFC 9000, 7.5. Cryptographic Message Buffering
*
* Implementations MUST support buffering at least 4096 bytes of data
*/
#define NGX_QUIC_MAX_BUFFERED 65535
#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER
static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *secret, size_t secret_len);
static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *secret, size_t secret_len);
#else
static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *read_secret,
const uint8_t *write_secret, size_t secret_len);
#endif
static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *data, size_t len);
static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, uint8_t alert);
static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data);
static SSL_QUIC_METHOD quic_method = {
#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER
.set_read_secret = ngx_quic_set_read_secret,
.set_write_secret = ngx_quic_set_write_secret,
#else
.set_encryption_secrets = ngx_quic_set_encryption_secrets,
#endif
.add_handshake_data = ngx_quic_add_handshake_data,
.flush_flight = ngx_quic_flush_flight,
.send_alert = ngx_quic_send_alert,
};
#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER
static int
ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *rsecret, size_t secret_len)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_set_read_secret() level:%d", level);
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic read secret len:%uz %*xs", secret_len,
secret_len, rsecret);
#endif
if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level,
cipher, rsecret, secret_len)
!= NGX_OK)
{
return 0;
}
if (level == ssl_encryption_early_data) {
if (ngx_quic_init_streams(c) != NGX_OK) {
return 0;
}
}
return 1;
}
static int
ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
const uint8_t *wsecret, size_t secret_len)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_set_write_secret() level:%d", level);
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic write secret len:%uz %*xs", secret_len,
secret_len, wsecret);
#endif
if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
cipher, wsecret, secret_len)
!= NGX_OK)
{
return 0;
}
return 1;
}
#else
static int
ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *rsecret,
const uint8_t *wsecret, size_t secret_len)
{
ngx_connection_t *c;
const SSL_CIPHER *cipher;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_set_encryption_secrets() level:%d", level);
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic read secret len:%uz %*xs", secret_len,
secret_len, rsecret);
#endif
cipher = SSL_get_current_cipher(ssl_conn);
if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level,
cipher, rsecret, secret_len)
!= NGX_OK)
{
return 0;
}
if (level == ssl_encryption_early_data) {
if (ngx_quic_init_streams(c) != NGX_OK) {
return 0;
}
return 1;
}
#ifdef NGX_QUIC_DEBUG_CRYPTO
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic write secret len:%uz %*xs", secret_len,
secret_len, wsecret);
#endif
if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
cipher, wsecret, secret_len)
!= NGX_OK)
{
return 0;
}
return 1;
}
#endif
static int
ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, const uint8_t *data, size_t len)
{
u_char *p, *end;
size_t client_params_len;
ngx_buf_t buf;
ngx_chain_t *out, cl;
const uint8_t *client_params;
ngx_quic_tp_t ctp;
ngx_quic_frame_t *frame;
ngx_connection_t *c;
ngx_quic_buffer_t qb;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
unsigned int alpn_len;
const unsigned char *alpn_data;
#endif
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
qc = ngx_quic_get_connection(c);
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_add_handshake_data");
if (!qc->client_tp_done) {
/*
* things to do once during handshake: check ALPN and transport
* parameters; we want to break handshake if something is wrong
* here;
*/
#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len);
if (alpn_len == 0) {
qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL;
qc->error_reason = "unsupported protocol in ALPN extension";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic unsupported protocol in ALPN extension");
return 0;
}
#endif
SSL_get_peer_quic_transport_params(ssl_conn, &client_params,
&client_params_len);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic SSL_get_peer_quic_transport_params():"
" params_len:%ui", client_params_len);
if (client_params_len == 0) {
/* RFC 9001, 8.2. QUIC Transport Parameters Extension */
qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION);
qc->error_reason = "missing transport parameters";
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"missing transport parameters");
return 0;
}
p = (u_char *) client_params;
end = p + client_params_len;
/* defaults for parameters not sent by client */
ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t));
if (ngx_quic_parse_transport_params(p, end, &ctp, c->log)
!= NGX_OK)
{
qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
qc->error_reason = "failed to process transport parameters";
return 0;
}
if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) {
return 0;
}
qc->client_tp_done = 1;
}
ctx = ngx_quic_get_send_ctx(qc, level);
ngx_memzero(&buf, sizeof(ngx_buf_t));
buf.pos = (u_char *) data;
buf.last = buf.pos + len;
buf.temporary = 1;
cl.buf = &buf;
cl.next = NULL;
ngx_memzero(&qb, sizeof(ngx_quic_buffer_t));
if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) {
return 0;
}
out = ngx_quic_read_buffer(c, &qb, len);
if (out == NGX_CHAIN_ERROR) {
return 0;
}
ngx_quic_free_buffer(c, &qb);
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return 0;
}
frame->data = out;
frame->level = level;
frame->type = NGX_QUIC_FT_CRYPTO;
frame->u.crypto.offset = ctx->crypto_sent;
frame->u.crypto.length = len;
ctx->crypto_sent += len;
ngx_quic_queue_frame(qc, frame);
return 1;
}
static int
ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
{
#if (NGX_DEBUG)
ngx_connection_t *c;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_flush_flight()");
#endif
return 1;
}
static int
ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
uint8_t alert)
{
ngx_connection_t *c;
ngx_quic_connection_t *qc;
c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ngx_quic_send_alert() level:%s alert:%d",
ngx_quic_level_name(level), (int) alert);
/* already closed on regular shutdown */
qc = ngx_quic_get_connection(c);
if (qc == NULL) {
return 1;
}
qc->error = NGX_QUIC_ERR_CRYPTO(alert);
return 1;
}
ngx_int_t
ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
ngx_quic_frame_t *frame)
{
uint64_t last;
ngx_chain_t *cl;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
ngx_quic_crypto_frame_t *f;
qc = ngx_quic_get_connection(c);
ctx = ngx_quic_get_send_ctx(qc, pkt->level);
f = &frame->u.crypto;
/* no overflow since both values are 62-bit */
last = f->offset + f->length;
if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) {
qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED;
return NGX_ERROR;
}
if (last <= ctx->crypto.offset) {
if (pkt->level == ssl_encryption_initial) {
/* speeding up handshake completion */
if (!ngx_queue_empty(&ctx->sent)) {
ngx_quic_resend_frames(c, ctx);
ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
while (!ngx_queue_empty(&ctx->sent)) {
ngx_quic_resend_frames(c, ctx);
}
}
}
return NGX_OK;
}
if (f->offset == ctx->crypto.offset) {
if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) {
return NGX_ERROR;
}
ngx_quic_skip_buffer(c, &ctx->crypto, last);
} else {
if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length,
f->offset)
== NGX_CHAIN_ERROR)
{
return NGX_ERROR;
}
}
cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1);
if (cl) {
if (ngx_quic_crypto_input(c, cl) != NGX_OK) {
return NGX_ERROR;
}
ngx_quic_free_chain(c, cl);
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data)
{
int n, sslerr;
ngx_buf_t *b;
ngx_chain_t *cl;
ngx_ssl_conn_t *ssl_conn;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ssl_conn = c->ssl->connection;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
(int) SSL_quic_read_level(ssl_conn),
(int) SSL_quic_write_level(ssl_conn));
for (cl = data; cl; cl = cl->next) {
b = cl->buf;
if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn),
b->pos, b->last - b->pos))
{
ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
"SSL_provide_quic_data() failed");
return NGX_ERROR;
}
}
n = SSL_do_handshake(ssl_conn);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
(int) SSL_quic_read_level(ssl_conn),
(int) SSL_quic_write_level(ssl_conn));
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
if (n <= 0) {
sslerr = SSL_get_error(ssl_conn, n);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
sslerr);
if (sslerr != SSL_ERROR_WANT_READ) {
ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
qc->error_reason = "handshake failed";
return NGX_ERROR;
}
return NGX_OK;
}
if (SSL_in_init(ssl_conn)) {
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic ssl cipher:%s", SSL_get_cipher(ssl_conn));
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic handshake completed successfully");
c->ssl->handshaked = 1;
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_HANDSHAKE_DONE;
ngx_quic_queue_frame(qc, frame);
if (qc->conf->retry) {
if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
/*
* RFC 9001, 9.5. Header Protection Timing Side Channels
*
* Generating next keys before a key update is received.
*/
if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) {
return NGX_ERROR;
}
/*
* RFC 9001, 4.9.2. Discarding Handshake Keys
*
* An endpoint MUST discard its Handshake keys
* when the TLS handshake is confirmed.
*/
ngx_quic_discard_ctx(c, ssl_encryption_handshake);
/* start accepting clients on negotiated number of server ids */
if (ngx_quic_create_sockets(c) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_init_streams(c) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_quic_init_connection(ngx_connection_t *c)
{
u_char *p;
size_t clen;
ssize_t len;
ngx_str_t dcid;
ngx_ssl_conn_t *ssl_conn;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) {
return NGX_ERROR;
}
c->ssl->no_wait_shutdown = 1;
ssl_conn = c->ssl->connection;
if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic SSL_set_quic_method() failed");
return NGX_ERROR;
}
#ifdef OPENSSL_INFO_QUIC
if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) {
SSL_set_quic_early_data_enabled(ssl_conn, 1);
}
#endif
qsock = ngx_quic_get_socket(c);
dcid.data = qsock->sid.id;
dcid.len = qsock->sid.len;
if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token)
!= NGX_OK)
{
return NGX_ERROR;
}
len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen);
/* always succeeds */
p = ngx_pnalloc(c->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL);
if (len < 0) {
return NGX_ERROR;
}
#ifdef NGX_QUIC_DEBUG_PACKETS
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic transport parameters len:%uz %*xs", len, len, p);
#endif
if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic SSL_set_quic_transport_params() failed");
return NGX_ERROR;
}
#ifdef OPENSSL_IS_BORINGSSL
if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic SSL_set_quic_early_data_context() failed");
return NGX_ERROR;
}
#endif
return NGX_OK;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_
#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */
+1654
View File
@@ -0,0 +1,1654 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_STREAM_GONE (void *) -1
static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs,
ngx_uint_t err);
static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c);
static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c);
static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id);
static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id);
static void ngx_quic_init_stream_handler(ngx_event_t *ev);
static void ngx_quic_init_streams_handler(ngx_connection_t *c);
static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c,
uint64_t id);
static void ngx_quic_empty_handler(ngx_event_t *ev);
static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf,
size_t size);
static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf,
size_t size);
static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c,
ngx_chain_t *in, off_t limit);
static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs);
static void ngx_quic_stream_cleanup_handler(void *data);
static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs);
static ngx_int_t ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last);
static ngx_int_t ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last);
static ngx_int_t ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs);
static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c);
static void ngx_quic_set_event(ngx_event_t *ev);
ngx_connection_t *
ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi)
{
uint64_t id;
ngx_connection_t *pc;
ngx_quic_stream_t *nqs;
ngx_quic_connection_t *qc;
pc = c->quic ? c->quic->parent : c;
qc = ngx_quic_get_connection(pc);
if (bidi) {
if (qc->streams.server_streams_bidi
>= qc->streams.server_max_streams_bidi)
{
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic too many server bidi streams:%uL",
qc->streams.server_streams_bidi);
return NULL;
}
id = (qc->streams.server_streams_bidi << 2)
| NGX_QUIC_STREAM_SERVER_INITIATED;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic creating server bidi stream"
" streams:%uL max:%uL id:0x%xL",
qc->streams.server_streams_bidi,
qc->streams.server_max_streams_bidi, id);
qc->streams.server_streams_bidi++;
} else {
if (qc->streams.server_streams_uni
>= qc->streams.server_max_streams_uni)
{
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic too many server uni streams:%uL",
qc->streams.server_streams_uni);
return NULL;
}
id = (qc->streams.server_streams_uni << 2)
| NGX_QUIC_STREAM_SERVER_INITIATED
| NGX_QUIC_STREAM_UNIDIRECTIONAL;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic creating server uni stream"
" streams:%uL max:%uL id:0x%xL",
qc->streams.server_streams_uni,
qc->streams.server_max_streams_uni, id);
qc->streams.server_streams_uni++;
}
nqs = ngx_quic_create_stream(pc, id);
if (nqs == NULL) {
return NULL;
}
return nqs->connection;
}
void
ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
ngx_rbtree_node_t **p;
ngx_quic_stream_t *qn, *qnt;
for ( ;; ) {
qn = (ngx_quic_stream_t *) node;
qnt = (ngx_quic_stream_t *) temp;
p = (qn->id < qnt->id) ? &temp->left : &temp->right;
if (*p == sentinel) {
break;
}
temp = *p;
}
*p = node;
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}
ngx_quic_stream_t *
ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id)
{
ngx_rbtree_node_t *node, *sentinel;
ngx_quic_stream_t *qn;
node = rbtree->root;
sentinel = rbtree->sentinel;
while (node != sentinel) {
qn = (ngx_quic_stream_t *) node;
if (id == qn->id) {
return qn;
}
node = (id < qn->id) ? node->left : node->right;
}
return NULL;
}
ngx_int_t
ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc)
{
ngx_pool_t *pool;
ngx_queue_t *q;
ngx_rbtree_t *tree;
ngx_rbtree_node_t *node;
ngx_quic_stream_t *qs;
#if (NGX_DEBUG)
ngx_uint_t ns;
#endif
while (!ngx_queue_empty(&qc->streams.uninitialized)) {
q = ngx_queue_head(&qc->streams.uninitialized);
ngx_queue_remove(q);
qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
pool = qs->connection->pool;
ngx_close_connection(qs->connection);
ngx_destroy_pool(pool);
}
tree = &qc->streams.tree;
if (tree->root == tree->sentinel) {
return NGX_OK;
}
#if (NGX_DEBUG)
ns = 0;
#endif
node = ngx_rbtree_min(tree->root, tree->sentinel);
while (node) {
qs = (ngx_quic_stream_t *) node;
node = ngx_rbtree_next(tree, node);
qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;
qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT;
if (qs->connection == NULL) {
ngx_quic_close_stream(qs);
continue;
}
ngx_quic_set_event(qs->connection->read);
ngx_quic_set_event(qs->connection->write);
#if (NGX_DEBUG)
ns++;
#endif
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic connection has %ui active streams", ns);
return NGX_AGAIN;
}
ngx_int_t
ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err)
{
return ngx_quic_do_reset_stream(c->quic, err);
}
static ngx_int_t
ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err)
{
ngx_connection_t *pc;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD
|| qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT
|| qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD)
{
return NGX_OK;
}
qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT;
qs->send_final_size = qs->send_offset;
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL reset", qs->id);
frame = ngx_quic_alloc_frame(pc);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_RESET_STREAM;
frame->u.reset_stream.id = qs->id;
frame->u.reset_stream.error_code = err;
frame->u.reset_stream.final_size = qs->send_offset;
ngx_quic_queue_frame(qc, frame);
ngx_quic_free_buffer(pc, &qs->send);
return NGX_OK;
}
ngx_int_t
ngx_quic_shutdown_stream(ngx_connection_t *c, int how)
{
if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) {
if (ngx_quic_shutdown_stream_send(c) != NGX_OK) {
return NGX_ERROR;
}
}
if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) {
if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_shutdown_stream_send(ngx_connection_t *c)
{
ngx_quic_stream_t *qs;
qs = c->quic;
if (qs->send_state != NGX_QUIC_STREAM_SEND_READY
&& qs->send_state != NGX_QUIC_STREAM_SEND_SEND)
{
return NGX_OK;
}
qs->send_state = NGX_QUIC_STREAM_SEND_SEND;
qs->send_final_size = c->sent;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0,
"quic stream id:0x%xL send shutdown", qs->id);
return ngx_quic_stream_flush(qs);
}
static ngx_int_t
ngx_quic_shutdown_stream_recv(ngx_connection_t *c)
{
ngx_connection_t *pc;
ngx_quic_frame_t *frame;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qs = c->quic;
if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV
&& qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN)
{
return NGX_OK;
}
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
if (qc->conf->stream_close_code == 0) {
return NGX_OK;
}
frame = ngx_quic_alloc_frame(pc);
if (frame == NULL) {
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL recv shutdown", qs->id);
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_STOP_SENDING;
frame->u.stop_sending.id = qs->id;
frame->u.stop_sending.error_code = qc->conf->stream_close_code;
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
static ngx_quic_stream_t *
ngx_quic_get_stream(ngx_connection_t *c, uint64_t id)
{
uint64_t min_id;
ngx_event_t *rev;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
qs = ngx_quic_find_stream(&qc->streams.tree, id);
if (qs) {
return qs;
}
if (qc->shutdown || qc->closing) {
return NGX_QUIC_STREAM_GONE;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stream id:0x%xL is missing", id);
if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
if ((id >> 2) < qc->streams.server_streams_uni) {
return NGX_QUIC_STREAM_GONE;
}
qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
return NULL;
}
if ((id >> 2) < qc->streams.client_streams_uni) {
return NGX_QUIC_STREAM_GONE;
}
if ((id >> 2) >= qc->streams.client_max_streams_uni) {
qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
return NULL;
}
min_id = (qc->streams.client_streams_uni << 2)
| NGX_QUIC_STREAM_UNIDIRECTIONAL;
qc->streams.client_streams_uni = (id >> 2) + 1;
} else {
if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
if ((id >> 2) < qc->streams.server_streams_bidi) {
return NGX_QUIC_STREAM_GONE;
}
qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
return NULL;
}
if ((id >> 2) < qc->streams.client_streams_bidi) {
return NGX_QUIC_STREAM_GONE;
}
if ((id >> 2) >= qc->streams.client_max_streams_bidi) {
qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
return NULL;
}
min_id = (qc->streams.client_streams_bidi << 2);
qc->streams.client_streams_bidi = (id >> 2) + 1;
}
/*
* RFC 9000, 2.1. Stream Types and Identifiers
*
* successive streams of each type are created with numerically increasing
* stream IDs. A stream ID that is used out of order results in all
* streams of that type with lower-numbered stream IDs also being opened.
*/
#if (NGX_SUPPRESS_WARN)
qs = NULL;
#endif
for ( /* void */ ; min_id <= id; min_id += 0x04) {
qs = ngx_quic_create_stream(c, min_id);
if (qs == NULL) {
if (ngx_quic_reject_stream(c, min_id) != NGX_OK) {
return NULL;
}
continue;
}
ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue);
rev = qs->connection->read;
rev->handler = ngx_quic_init_stream_handler;
if (qc->streams.initialized) {
ngx_post_event(rev, &ngx_posted_events);
}
}
if (qs == NULL) {
return NGX_QUIC_STREAM_GONE;
}
return qs;
}
static ngx_int_t
ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id)
{
uint64_t code;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
? qc->conf->stream_reject_code_uni
: qc->conf->stream_reject_code_bidi;
if (code == 0) {
return NGX_DECLINED;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stream id:0x%xL reject err:0x%xL", id, code);
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_RESET_STREAM;
frame->u.reset_stream.id = id;
frame->u.reset_stream.error_code = code;
frame->u.reset_stream.final_size = 0;
ngx_quic_queue_frame(qc, frame);
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_STOP_SENDING;
frame->u.stop_sending.id = id;
frame->u.stop_sending.error_code = code;
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
static void
ngx_quic_init_stream_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_quic_stream_t *qs;
c = ev->data;
qs = c->quic;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream");
ngx_queue_remove(&qs->queue);
c->listening->handler(c);
}
ngx_int_t
ngx_quic_init_streams(ngx_connection_t *c)
{
ngx_int_t rc;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (qc->streams.initialized) {
return NGX_OK;
}
rc = ngx_ssl_ocsp_validate(c);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (rc == NGX_AGAIN) {
c->ssl->handler = ngx_quic_init_streams_handler;
return NGX_OK;
}
ngx_quic_init_streams_handler(c);
return NGX_OK;
}
static void
ngx_quic_init_streams_handler(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams");
qc = ngx_quic_get_connection(c);
for (q = ngx_queue_head(&qc->streams.uninitialized);
q != ngx_queue_sentinel(&qc->streams.uninitialized);
q = ngx_queue_next(q))
{
qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
ngx_post_event(qs->connection->read, &ngx_posted_events);
}
qc->streams.initialized = 1;
}
static ngx_quic_stream_t *
ngx_quic_create_stream(ngx_connection_t *c, uint64_t id)
{
ngx_log_t *log;
ngx_pool_t *pool;
ngx_queue_t *q;
ngx_connection_t *sc;
ngx_quic_stream_t *qs;
ngx_pool_cleanup_t *cln;
ngx_quic_connection_t *qc;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stream id:0x%xL create", id);
qc = ngx_quic_get_connection(c);
if (!ngx_queue_empty(&qc->streams.free)) {
q = ngx_queue_head(&qc->streams.free);
qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
ngx_queue_remove(&qs->queue);
} else {
/*
* the number of streams is limited by transport
* parameters and application requirements
*/
qs = ngx_palloc(c->pool, sizeof(ngx_quic_stream_t));
if (qs == NULL) {
return NULL;
}
}
ngx_memzero(qs, sizeof(ngx_quic_stream_t));
qs->node.key = id;
qs->parent = c;
qs->id = id;
qs->send_final_size = (uint64_t) -1;
qs->recv_final_size = (uint64_t) -1;
pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log);
if (pool == NULL) {
ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
return NULL;
}
log = ngx_palloc(pool, sizeof(ngx_log_t));
if (log == NULL) {
ngx_destroy_pool(pool);
ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
return NULL;
}
*log = *c->log;
pool->log = log;
sc = ngx_get_connection(c->fd, log);
if (sc == NULL) {
ngx_destroy_pool(pool);
ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
return NULL;
}
qs->connection = sc;
sc->quic = qs;
sc->shared = 1;
sc->type = SOCK_STREAM;
sc->pool = pool;
sc->ssl = c->ssl;
sc->sockaddr = c->sockaddr;
sc->listening = c->listening;
sc->addr_text = c->addr_text;
sc->local_sockaddr = c->local_sockaddr;
sc->local_socklen = c->local_socklen;
sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
sc->recv = ngx_quic_stream_recv;
sc->send = ngx_quic_stream_send;
sc->send_chain = ngx_quic_stream_send_chain;
sc->read->log = log;
sc->write->log = log;
sc->read->handler = ngx_quic_empty_handler;
sc->write->handler = ngx_quic_empty_handler;
log->connection = sc->number;
if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0
|| (id & NGX_QUIC_STREAM_SERVER_INITIATED))
{
sc->write->ready = 1;
}
if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
qs->send_max_data = qc->ctp.initial_max_stream_data_uni;
qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ;
qs->send_state = NGX_QUIC_STREAM_SEND_READY;
} else {
qs->recv_max_data = qc->tp.initial_max_stream_data_uni;
qs->recv_state = NGX_QUIC_STREAM_RECV_RECV;
qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD;
}
} else {
if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote;
qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local;
} else {
qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local;
qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote;
}
qs->recv_state = NGX_QUIC_STREAM_RECV_RECV;
qs->send_state = NGX_QUIC_STREAM_SEND_READY;
}
qs->recv_window = qs->recv_max_data;
cln = ngx_pool_cleanup_add(pool, 0);
if (cln == NULL) {
ngx_close_connection(sc);
ngx_destroy_pool(pool);
ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
return NULL;
}
cln->handler = ngx_quic_stream_cleanup_handler;
cln->data = sc;
ngx_rbtree_insert(&qc->streams.tree, &qs->node);
return qs;
}
static void
ngx_quic_empty_handler(ngx_event_t *ev)
{
}
static ssize_t
ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size)
{
ssize_t len;
ngx_buf_t *b;
ngx_chain_t *cl, *in;
ngx_event_t *rev;
ngx_connection_t *pc;
ngx_quic_stream_t *qs;
qs = c->quic;
pc = qs->parent;
rev = c->read;
if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
|| qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
{
qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ;
rev->error = 1;
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL recv buf:%uz", qs->id, size);
if (size == 0) {
return 0;
}
in = ngx_quic_read_buffer(pc, &qs->recv, size);
if (in == NGX_CHAIN_ERROR) {
return NGX_ERROR;
}
len = 0;
for (cl = in; cl; cl = cl->next) {
b = cl->buf;
len += b->last - b->pos;
buf = ngx_cpymem(buf, b->pos, b->last - b->pos);
}
ngx_quic_free_chain(pc, in);
if (len == 0) {
rev->ready = 0;
if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD
&& qs->recv_offset == qs->recv_final_size)
{
qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ;
}
if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) {
rev->eof = 1;
return 0;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stream id:0x%xL recv() not ready", qs->id);
return NGX_AGAIN;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stream id:0x%xL recv len:%z", qs->id, len);
if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) {
return NGX_ERROR;
}
return len;
}
static ssize_t
ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size)
{
ngx_buf_t b;
ngx_chain_t cl;
ngx_memzero(&b, sizeof(ngx_buf_t));
b.memory = 1;
b.pos = buf;
b.last = buf + size;
cl.buf = &b;
cl.next = NULL;
if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) {
return NGX_ERROR;
}
if (b.pos == buf) {
return NGX_AGAIN;
}
return b.pos - buf;
}
static ngx_chain_t *
ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
{
uint64_t n, flow;
ngx_event_t *wev;
ngx_connection_t *pc;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qs = c->quic;
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
wev = c->write;
if (qs->send_state != NGX_QUIC_STREAM_SEND_READY
&& qs->send_state != NGX_QUIC_STREAM_SEND_SEND)
{
wev->error = 1;
return NGX_CHAIN_ERROR;
}
qs->send_state = NGX_QUIC_STREAM_SEND_SEND;
flow = qs->acked + qc->conf->stream_buffer_size - c->sent;
if (flow == 0) {
wev->ready = 0;
return in;
}
if (limit == 0 || limit > (off_t) flow) {
limit = flow;
}
n = qs->send.size;
in = ngx_quic_write_buffer(pc, &qs->send, in, limit, c->sent);
if (in == NGX_CHAIN_ERROR) {
return NGX_CHAIN_ERROR;
}
n = qs->send.size - n;
c->sent += n;
qc->streams.sent += n;
if (flow == n) {
wev->ready = 0;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic send_chain sent:%uL", n);
if (ngx_quic_stream_flush(qs) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
return in;
}
static ngx_int_t
ngx_quic_stream_flush(ngx_quic_stream_t *qs)
{
off_t limit, len;
ngx_uint_t last;
ngx_chain_t *out;
ngx_quic_frame_t *frame;
ngx_connection_t *pc;
ngx_quic_connection_t *qc;
if (qs->send_state != NGX_QUIC_STREAM_SEND_SEND) {
return NGX_OK;
}
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
if (qc->streams.send_max_data == 0) {
qc->streams.send_max_data = qc->ctp.initial_max_data;
}
limit = ngx_min(qc->streams.send_max_data - qc->streams.send_offset,
qs->send_max_data - qs->send_offset);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL flush limit:%O", qs->id, limit);
len = qs->send.offset;
out = ngx_quic_read_buffer(pc, &qs->send, limit);
if (out == NGX_CHAIN_ERROR) {
return NGX_ERROR;
}
len = qs->send.offset - len;
last = 0;
if (qs->send_final_size != (uint64_t) -1
&& qs->send_final_size == qs->send.offset)
{
qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT;
last = 1;
}
if (len == 0 && !last) {
return NGX_OK;
}
frame = ngx_quic_alloc_frame(pc);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_STREAM;
frame->data = out;
frame->u.stream.off = 1;
frame->u.stream.len = 1;
frame->u.stream.fin = last;
frame->u.stream.stream_id = qs->id;
frame->u.stream.offset = qs->send_offset;
frame->u.stream.length = len;
ngx_quic_queue_frame(qc, frame);
qs->send_offset += len;
qc->streams.send_offset += len;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL flush len:%O last:%ui",
qs->id, len, last);
if (qs->connection == NULL) {
return ngx_quic_close_stream(qs);
}
return NGX_OK;
}
static void
ngx_quic_stream_cleanup_handler(void *data)
{
ngx_connection_t *c = data;
ngx_quic_stream_t *qs;
qs = c->quic;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0,
"quic stream id:0x%xL cleanup", qs->id);
if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
qs->connection = NULL;
if (ngx_quic_close_stream(qs) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
}
static ngx_int_t
ngx_quic_close_stream(ngx_quic_stream_t *qs)
{
ngx_connection_t *pc;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
if (!qc->closing) {
/* make sure everything is sent and final size is received */
if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV
|| qs->send_state == NGX_QUIC_STREAM_SEND_READY
|| qs->send_state == NGX_QUIC_STREAM_SEND_SEND)
{
return NGX_OK;
}
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL close", qs->id);
ngx_quic_free_buffer(pc, &qs->send);
ngx_quic_free_buffer(pc, &qs->recv);
ngx_rbtree_delete(&qc->streams.tree, &qs->node);
ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
if (qc->closing) {
/* schedule handler call to continue ngx_quic_close_connection() */
ngx_post_event(pc->read, &ngx_posted_events);
return NGX_OK;
}
if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) {
frame = ngx_quic_alloc_frame(pc);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_MAX_STREAMS;
if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni;
frame->u.max_streams.bidi = 0;
} else {
frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi;
frame->u.max_streams.bidi = 1;
}
ngx_quic_queue_frame(qc, frame);
}
if (qc->shutdown) {
ngx_post_event(pc->read, &ngx_posted_events);
}
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
ngx_quic_frame_t *frame)
{
uint64_t last;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
ngx_quic_stream_frame_t *f;
qc = ngx_quic_get_connection(c);
f = &frame->u.stream;
if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
&& (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED))
{
qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
return NGX_ERROR;
}
/* no overflow since both values are 62-bit */
last = f->offset + f->length;
qs = ngx_quic_get_stream(c, f->stream_id);
if (qs == NULL) {
return NGX_ERROR;
}
if (qs == NGX_QUIC_STREAM_GONE) {
return NGX_OK;
}
if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV
&& qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN)
{
return NGX_OK;
}
if (ngx_quic_control_flow(qs, last) != NGX_OK) {
return NGX_ERROR;
}
if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) {
qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
return NGX_ERROR;
}
if (last < qs->recv_offset) {
return NGX_OK;
}
if (f->fin) {
if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last)
{
qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
return NGX_ERROR;
}
if (qs->recv_last > last) {
qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
return NGX_ERROR;
}
qs->recv_final_size = last;
qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN;
}
if (ngx_quic_write_buffer(c, &qs->recv, frame->data, f->length, f->offset)
== NGX_CHAIN_ERROR)
{
return NGX_ERROR;
}
if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN
&& qs->recv.size == qs->recv_final_size)
{
qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD;
}
if (qs->connection == NULL) {
return ngx_quic_close_stream(qs);
}
if (f->offset == qs->recv_offset) {
ngx_quic_set_event(qs->connection->read);
}
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_max_data_frame(ngx_connection_t *c,
ngx_quic_max_data_frame_t *f)
{
ngx_rbtree_t *tree;
ngx_rbtree_node_t *node;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
tree = &qc->streams.tree;
if (f->max_data <= qc->streams.send_max_data) {
return NGX_OK;
}
if (tree->root == tree->sentinel
|| qc->streams.send_offset < qc->streams.send_max_data)
{
/* not blocked on MAX_DATA */
qc->streams.send_max_data = f->max_data;
return NGX_OK;
}
qc->streams.send_max_data = f->max_data;
node = ngx_rbtree_min(tree->root, tree->sentinel);
while (node && qc->streams.send_offset < qc->streams.send_max_data) {
qs = (ngx_quic_stream_t *) node;
node = ngx_rbtree_next(tree, node);
if (ngx_quic_stream_flush(qs) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f)
{
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_data_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f)
{
return ngx_quic_update_max_data(c);
}
ngx_int_t
ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f)
{
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
&& (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
{
qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
return NGX_ERROR;
}
qs = ngx_quic_get_stream(c, f->id);
if (qs == NULL) {
return NGX_ERROR;
}
if (qs == NGX_QUIC_STREAM_GONE) {
return NGX_OK;
}
return ngx_quic_update_max_stream_data(qs);
}
ngx_int_t
ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f)
{
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
&& (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
{
qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
return NGX_ERROR;
}
qs = ngx_quic_get_stream(c, f->id);
if (qs == NULL) {
return NGX_ERROR;
}
if (qs == NGX_QUIC_STREAM_GONE) {
return NGX_OK;
}
if (f->limit <= qs->send_max_data) {
return NGX_OK;
}
if (qs->send_offset < qs->send_max_data) {
/* not blocked on MAX_STREAM_DATA */
qs->send_max_data = f->limit;
return NGX_OK;
}
qs->send_max_data = f->limit;
return ngx_quic_stream_flush(qs);
}
ngx_int_t
ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f)
{
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
&& (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
{
qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
return NGX_ERROR;
}
qs = ngx_quic_get_stream(c, f->id);
if (qs == NULL) {
return NGX_ERROR;
}
if (qs == NGX_QUIC_STREAM_GONE) {
return NGX_OK;
}
if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
|| qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
{
return NGX_OK;
}
qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;
if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) {
return NGX_ERROR;
}
if (qs->recv_final_size != (uint64_t) -1
&& qs->recv_final_size != f->final_size)
{
qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
return NGX_ERROR;
}
if (qs->recv_last > f->final_size) {
qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
return NGX_ERROR;
}
qs->recv_final_size = f->final_size;
if (ngx_quic_update_flow(qs, qs->recv_final_size) != NGX_OK) {
return NGX_ERROR;
}
if (qs->connection == NULL) {
return ngx_quic_close_stream(qs);
}
ngx_quic_set_event(qs->connection->read);
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f)
{
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
&& (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
{
qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
return NGX_ERROR;
}
qs = ngx_quic_get_stream(c, f->id);
if (qs == NULL) {
return NGX_ERROR;
}
if (qs == NGX_QUIC_STREAM_GONE) {
return NGX_OK;
}
if (ngx_quic_do_reset_stream(qs, f->error_code) != NGX_OK) {
return NGX_ERROR;
}
if (qs->connection == NULL) {
return ngx_quic_close_stream(qs);
}
ngx_quic_set_event(qs->connection->write);
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f)
{
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
if (f->bidi) {
if (qc->streams.server_max_streams_bidi < f->limit) {
qc->streams.server_max_streams_bidi = f->limit;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic max_streams_bidi:%uL", f->limit);
}
} else {
if (qc->streams.server_max_streams_uni < f->limit) {
qc->streams.server_max_streams_uni = f->limit;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic max_streams_uni:%uL", f->limit);
}
}
return NGX_OK;
}
void
ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
{
uint64_t sent, unacked;
ngx_quic_stream_t *qs;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
if (qs == NULL) {
return;
}
if (qs->connection == NULL) {
qs->acked += f->u.stream.length;
return;
}
sent = qs->connection->sent;
unacked = sent - qs->acked;
qs->acked += f->u.stream.length;
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stream id:0x%xL ack len:%uL acked:%uL unacked:%uL",
qs->id, f->u.stream.length, qs->acked, sent - qs->acked);
if (unacked != qc->conf->stream_buffer_size) {
/* not blocked on buffer size */
return;
}
ngx_quic_set_event(qs->connection->write);
}
static ngx_int_t
ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last)
{
uint64_t len;
ngx_connection_t *pc;
ngx_quic_connection_t *qc;
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
if (last <= qs->recv_last) {
return NGX_OK;
}
len = last - qs->recv_last;
ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL flow control msd:%uL/%uL md:%uL/%uL",
qs->id, last, qs->recv_max_data, qc->streams.recv_last + len,
qc->streams.recv_max_data);
qs->recv_last += len;
if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV
&& qs->recv_last > qs->recv_max_data)
{
qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR;
return NGX_ERROR;
}
qc->streams.recv_last += len;
if (qc->streams.recv_last > qc->streams.recv_max_data) {
qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR;
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last)
{
uint64_t len;
ngx_connection_t *pc;
ngx_quic_connection_t *qc;
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
if (last <= qs->recv_offset) {
return NGX_OK;
}
len = last - qs->recv_offset;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL flow update %uL", qs->id, last);
qs->recv_offset += len;
if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) {
if (ngx_quic_update_max_stream_data(qs) != NGX_OK) {
return NGX_ERROR;
}
}
qc->streams.recv_offset += len;
if (qc->streams.recv_max_data
<= qc->streams.recv_offset + qc->streams.recv_window / 2)
{
if (ngx_quic_update_max_data(pc) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs)
{
uint64_t recv_max_data;
ngx_connection_t *pc;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
pc = qs->parent;
qc = ngx_quic_get_connection(pc);
if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) {
return NGX_OK;
}
recv_max_data = qs->recv_offset + qs->recv_window;
if (qs->recv_max_data == recv_max_data) {
return NGX_OK;
}
qs->recv_max_data = recv_max_data;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
"quic stream id:0x%xL flow update msd:%uL",
qs->id, qs->recv_max_data);
frame = ngx_quic_alloc_frame(pc);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_MAX_STREAM_DATA;
frame->u.max_stream_data.id = qs->id;
frame->u.max_stream_data.limit = qs->recv_max_data;
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
static ngx_int_t
ngx_quic_update_max_data(ngx_connection_t *c)
{
uint64_t recv_max_data;
ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
recv_max_data = qc->streams.recv_offset + qc->streams.recv_window;
if (qc->streams.recv_max_data == recv_max_data) {
return NGX_OK;
}
qc->streams.recv_max_data = recv_max_data;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic flow update md:%uL", qc->streams.recv_max_data);
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_MAX_DATA;
frame->u.max_data.max_data = qc->streams.recv_max_data;
ngx_quic_queue_frame(qc, frame);
return NGX_OK;
}
static void
ngx_quic_set_event(ngx_event_t *ev)
{
ev->ready = 1;
if (ev->active) {
ngx_post_event(ev, &ngx_posted_events);
}
}
ngx_int_t
ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
if (!rev->active && !rev->ready) {
rev->active = 1;
} else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) {
rev->active = 0;
}
return NGX_OK;
}
ngx_int_t
ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat)
{
if (!wev->active && !wev->ready) {
wev->active = 1;
} else if (wev->active && wev->ready) {
wev->active = 0;
}
return NGX_OK;
}
+44
View File
@@ -0,0 +1,44 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
void ngx_quic_handle_stream_ack(ngx_connection_t *c,
ngx_quic_frame_t *f);
ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c,
ngx_quic_max_data_frame_t *f);
ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f);
ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f);
ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f);
ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f);
ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f);
ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f);
ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f);
ngx_int_t ngx_quic_init_streams(ngx_connection_t *c);
void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree,
uint64_t id);
ngx_int_t ngx_quic_close_streams(ngx_connection_t *c,
ngx_quic_connection_t *qc);
#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */
+285
View File
@@ -0,0 +1,285 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_sha1.h>
#include <ngx_event_quic_connection.h>
static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
ngx_uint_t no_port, u_char buf[20]);
ngx_int_t
ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret,
u_char *token)
{
ngx_str_t tmp;
tmp.data = secret;
tmp.len = NGX_QUIC_SR_KEY_LEN;
if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token,
NGX_QUIC_SR_TOKEN_LEN)
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stateless reset token %*xs",
(size_t) NGX_QUIC_SR_TOKEN_LEN, token);
return NGX_OK;
}
ngx_int_t
ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr,
socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
time_t exp, ngx_uint_t is_retry)
{
int len, iv_len;
u_char *p, *iv;
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher;
u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
ngx_quic_address_hash(sockaddr, socklen, !is_retry, in);
p = in + 20;
p = ngx_cpymem(p, &exp, sizeof(time_t));
*p++ = is_retry ? 1 : 0;
if (odcid) {
*p++ = odcid->len;
p = ngx_cpymem(p, odcid->data, odcid->len);
} else {
*p++ = 0;
}
len = p - in;
cipher = EVP_aes_256_cbc();
iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
if ((size_t) (iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) > token->len)
{
ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small");
return NGX_ERROR;
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
return NGX_ERROR;
}
iv = token->data;
if (RAND_bytes(iv, iv_len) <= 0
|| !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv))
{
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
token->len = iv_len;
if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) {
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
token->len += len;
if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) {
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
token->len += len;
EVP_CIPHER_CTX_free(ctx);
#ifdef NGX_QUIC_DEBUG_PACKETS
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
"quic new token len:%uz %xV", token->len, token);
#endif
return NGX_OK;
}
static void
ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
ngx_uint_t no_port, u_char buf[20])
{
size_t len;
u_char *data;
ngx_sha1_t sha1;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
len = (size_t) socklen;
data = (u_char *) sockaddr;
if (no_port) {
switch (sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sockaddr;
len = sizeof(struct in6_addr);
data = sin6->sin6_addr.s6_addr;
break;
#endif
case AF_INET:
sin = (struct sockaddr_in *) sockaddr;
len = sizeof(in_addr_t);
data = (u_char *) &sin->sin_addr;
break;
}
}
ngx_sha1_init(&sha1);
ngx_sha1_update(&sha1, data, len);
ngx_sha1_final(buf, &sha1);
}
ngx_int_t
ngx_quic_validate_token(ngx_connection_t *c, u_char *key,
ngx_quic_header_t *pkt)
{
int len, tlen, iv_len;
u_char *iv, *p;
time_t now, exp;
size_t total;
ngx_str_t odcid;
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher;
u_char addr_hash[20];
u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
/* Retry token or NEW_TOKEN in a previous connection */
cipher = EVP_aes_256_cbc();
iv = pkt->token.data;
iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
/* sanity checks */
if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) {
goto garbage;
}
if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
goto garbage;
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
return NGX_ERROR;
}
if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {
EVP_CIPHER_CTX_free(ctx);
return NGX_ERROR;
}
p = pkt->token.data + iv_len;
len = pkt->token.len - iv_len;
if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) {
EVP_CIPHER_CTX_free(ctx);
goto garbage;
}
total = len;
if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
EVP_CIPHER_CTX_free(ctx);
goto garbage;
}
total += tlen;
EVP_CIPHER_CTX_free(ctx);
if (total < (20 + sizeof(time_t) + 2)) {
goto garbage;
}
p = tdec + 20;
ngx_memcpy(&exp, p, sizeof(time_t));
p += sizeof(time_t);
pkt->retried = (*p++ == 1);
ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash);
if (ngx_memcmp(tdec, addr_hash, 20) != 0) {
goto bad_token;
}
odcid.len = *p++;
if (odcid.len) {
if (odcid.len > NGX_QUIC_MAX_CID_LEN) {
goto bad_token;
}
if ((size_t)(tdec + total - p) < odcid.len) {
goto bad_token;
}
odcid.data = p;
}
now = ngx_time();
if (now > exp) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token");
return NGX_DECLINED;
}
if (odcid.len) {
pkt->odcid.len = odcid.len;
pkt->odcid.data = pkt->odcid_buf;
ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len);
} else {
pkt->odcid = pkt->dcid;
}
pkt->validated = 1;
return NGX_OK;
garbage:
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token");
return NGX_ABORT;
bad_token:
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
return NGX_DECLINED;
}
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_MAX_TOKEN_SIZE 64
/* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */
/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */
#define NGX_QUIC_AES_256_CBC_IV_LEN 16
#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16
#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_CBC_IV_LEN \
+ NGX_QUIC_MAX_TOKEN_SIZE \
+ NGX_QUIC_AES_256_CBC_BLOCK_SIZE)
ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid,
u_char *secret, u_char *token);
ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr,
socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
time_t expires, ngx_uint_t is_retry);
ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
u_char *key, ngx_quic_header_t *pkt);
#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */
+2164
View File
@@ -0,0 +1,2164 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5
#define NGX_QUIC_LONG_DCID_OFFSET 6
#define NGX_QUIC_SHORT_DCID_OFFSET 1
#define NGX_QUIC_STREAM_FRAME_FIN 0x01
#define NGX_QUIC_STREAM_FRAME_LEN 0x02
#define NGX_QUIC_STREAM_FRAME_OFF 0x04
#if (NGX_HAVE_NONALIGNED)
#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p))
#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p))
#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned
#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned
#else
#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1])
#define ngx_quic_parse_uint32(p) \
((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
#define ngx_quic_write_uint16(p, s) \
((p)[0] = (u_char) ((s) >> 8), \
(p)[1] = (u_char) (s), \
(p) + sizeof(uint16_t))
#define ngx_quic_write_uint32(p, s) \
((p)[0] = (u_char) ((s) >> 24), \
(p)[1] = (u_char) ((s) >> 16), \
(p)[2] = (u_char) ((s) >> 8), \
(p)[3] = (u_char) (s), \
(p) + sizeof(uint32_t))
#endif
#define ngx_quic_write_uint64(p, s) \
((p)[0] = (u_char) ((s) >> 56), \
(p)[1] = (u_char) ((s) >> 48), \
(p)[2] = (u_char) ((s) >> 40), \
(p)[3] = (u_char) ((s) >> 32), \
(p)[4] = (u_char) ((s) >> 24), \
(p)[5] = (u_char) ((s) >> 16), \
(p)[6] = (u_char) ((s) >> 8), \
(p)[7] = (u_char) (s), \
(p) + sizeof(uint64_t))
#define ngx_quic_write_uint24(p, s) \
((p)[0] = (u_char) ((s) >> 16), \
(p)[1] = (u_char) ((s) >> 8), \
(p)[2] = (u_char) (s), \
(p) + 3)
#define ngx_quic_write_uint16_aligned(p, s) \
(*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
#define ngx_quic_write_uint32_aligned(p, s) \
(*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
#define ngx_quic_build_int_set(p, value, len, bits) \
(*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6))
static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out);
static ngx_uint_t ngx_quic_varint_len(uint64_t value);
static void ngx_quic_build_int(u_char **pos, uint64_t value);
static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value);
static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value);
static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len,
u_char **out);
static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len,
u_char *dst);
static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt,
size_t dcid_len);
static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_supported_version(uint32_t version);
static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt);
static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
u_char **pnp);
static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
u_char **pnp);
static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt,
ngx_uint_t frame_type);
static size_t ngx_quic_create_ping(u_char *p);
static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack,
ngx_chain_t *ranges);
static size_t ngx_quic_create_reset_stream(u_char *p,
ngx_quic_reset_stream_frame_t *rs);
static size_t ngx_quic_create_stop_sending(u_char *p,
ngx_quic_stop_sending_frame_t *ss);
static size_t ngx_quic_create_crypto(u_char *p,
ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data);
static size_t ngx_quic_create_hs_done(u_char *p);
static size_t ngx_quic_create_new_token(u_char *p,
ngx_quic_new_token_frame_t *token);
static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf,
ngx_chain_t *data);
static size_t ngx_quic_create_max_streams(u_char *p,
ngx_quic_max_streams_frame_t *ms);
static size_t ngx_quic_create_max_stream_data(u_char *p,
ngx_quic_max_stream_data_frame_t *ms);
static size_t ngx_quic_create_max_data(u_char *p,
ngx_quic_max_data_frame_t *md);
static size_t ngx_quic_create_path_challenge(u_char *p,
ngx_quic_path_challenge_frame_t *pc);
static size_t ngx_quic_create_path_response(u_char *p,
ngx_quic_path_challenge_frame_t *pc);
static size_t ngx_quic_create_new_connection_id(u_char *p,
ngx_quic_new_conn_id_frame_t *rcid);
static size_t ngx_quic_create_retire_connection_id(u_char *p,
ngx_quic_retire_cid_frame_t *rcid);
static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f);
static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end,
uint16_t id, ngx_quic_tp_t *dst);
uint32_t ngx_quic_versions[] = {
/* QUICv1 */
0x00000001,
};
#define NGX_QUIC_NVERSIONS \
(sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0]))
static ngx_inline u_char *
ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out)
{
u_char *p;
uint64_t value;
ngx_uint_t len;
if (pos >= end) {
return NULL;
}
p = pos;
len = 1 << (*p >> 6);
value = *p++ & 0x3f;
if ((size_t)(end - p) < (len - 1)) {
return NULL;
}
while (--len) {
value = (value << 8) + *p++;
}
*out = value;
return p;
}
static ngx_inline u_char *
ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value)
{
if ((size_t)(end - pos) < 1) {
return NULL;
}
*value = *pos;
return pos + 1;
}
static ngx_inline u_char *
ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value)
{
if ((size_t)(end - pos) < sizeof(uint32_t)) {
return NULL;
}
*value = ngx_quic_parse_uint32(pos);
return pos + sizeof(uint32_t);
}
static ngx_inline u_char *
ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out)
{
if ((size_t)(end - pos) < len) {
return NULL;
}
*out = pos;
return pos + len;
}
static u_char *
ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst)
{
if ((size_t)(end - pos) < len) {
return NULL;
}
ngx_memcpy(dst, pos, len);
return pos + len;
}
static ngx_inline ngx_uint_t
ngx_quic_varint_len(uint64_t value)
{
if (value < (1 << 6)) {
return 1;
}
if (value < (1 << 14)) {
return 2;
}
if (value < (1 << 30)) {
return 4;
}
return 8;
}
static ngx_inline void
ngx_quic_build_int(u_char **pos, uint64_t value)
{
u_char *p;
p = *pos;
if (value < (1 << 6)) {
ngx_quic_build_int_set(p, value, 0, 0);
} else if (value < (1 << 14)) {
ngx_quic_build_int_set(p, value, 1, 1);
ngx_quic_build_int_set(p, value, 0, 0);
} else if (value < (1 << 30)) {
ngx_quic_build_int_set(p, value, 3, 2);
ngx_quic_build_int_set(p, value, 2, 0);
ngx_quic_build_int_set(p, value, 1, 0);
ngx_quic_build_int_set(p, value, 0, 0);
} else {
ngx_quic_build_int_set(p, value, 7, 3);
ngx_quic_build_int_set(p, value, 6, 0);
ngx_quic_build_int_set(p, value, 5, 0);
ngx_quic_build_int_set(p, value, 4, 0);
ngx_quic_build_int_set(p, value, 3, 0);
ngx_quic_build_int_set(p, value, 2, 0);
ngx_quic_build_int_set(p, value, 1, 0);
ngx_quic_build_int_set(p, value, 0, 0);
}
*pos = p;
}
ngx_int_t
ngx_quic_parse_packet(ngx_quic_header_t *pkt)
{
if (!ngx_quic_long_pkt(pkt->flags)) {
pkt->level = ssl_encryption_application;
if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
return NGX_ERROR;
}
if (!ngx_quic_supported_version(pkt->version)) {
return NGX_ABORT;
}
if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len)
{
u_char *p, *end;
p = pkt->raw->pos;
end = pkt->data + pkt->len;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic packet rx short flags:%xd", pkt->flags);
if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
return NGX_ERROR;
}
pkt->dcid.len = dcid_len;
p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet is too small to read dcid");
return NGX_ERROR;
}
pkt->raw->pos = p;
return NGX_OK;
}
static ngx_int_t
ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
{
u_char *p, *end;
uint8_t idlen;
p = pkt->raw->pos;
end = pkt->data + pkt->len;
p = ngx_quic_read_uint32(p, end, &pkt->version);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet is too small to read version");
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic packet rx long flags:%xd version:%xD",
pkt->flags, pkt->version);
if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
return NGX_ERROR;
}
p = ngx_quic_read_uint8(p, end, &idlen);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet is too small to read dcid len");
return NGX_ERROR;
}
if (idlen > NGX_QUIC_CID_LEN_MAX) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet dcid is too long");
return NGX_ERROR;
}
pkt->dcid.len = idlen;
p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet is too small to read dcid");
return NGX_ERROR;
}
p = ngx_quic_read_uint8(p, end, &idlen);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet is too small to read scid len");
return NGX_ERROR;
}
if (idlen > NGX_QUIC_CID_LEN_MAX) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet scid is too long");
return NGX_ERROR;
}
pkt->scid.len = idlen;
p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet is too small to read scid");
return NGX_ERROR;
}
pkt->raw->pos = p;
return NGX_OK;
}
static ngx_int_t
ngx_quic_supported_version(uint32_t version)
{
ngx_uint_t i;
for (i = 0; i < NGX_QUIC_NVERSIONS; i++) {
if (ngx_quic_versions[i] == version) {
return 1;
}
}
return 0;
}
static ngx_int_t
ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt)
{
u_char *p, *end;
uint64_t varint;
p = pkt->raw->pos;
end = pkt->raw->last;
pkt->log->action = "parsing quic long header";
if (ngx_quic_pkt_in(pkt->flags)) {
if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic UDP datagram is too small for initial packet");
return NGX_DECLINED;
}
p = ngx_quic_parse_int(p, end, &varint);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic failed to parse token length");
return NGX_ERROR;
}
pkt->token.len = varint;
p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic packet too small to read token data");
return NGX_ERROR;
}
pkt->level = ssl_encryption_initial;
} else if (ngx_quic_pkt_zrtt(pkt->flags)) {
pkt->level = ssl_encryption_early_data;
} else if (ngx_quic_pkt_hs(pkt->flags)) {
pkt->level = ssl_encryption_handshake;
} else {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic bad packet type");
return NGX_DECLINED;
}
p = ngx_quic_parse_int(p, end, &varint);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length");
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic packet rx %s len:%uL",
ngx_quic_level_name(pkt->level), varint);
if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) {
ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet",
ngx_quic_level_name(pkt->level));
return NGX_ERROR;
}
pkt->raw->pos = p;
pkt->len = p + varint - pkt->data;
return NGX_OK;
}
ngx_int_t
ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n,
ngx_str_t *dcid)
{
size_t len, offset;
if (n == 0) {
goto failed;
}
if (ngx_quic_long_pkt(*data)) {
if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) {
goto failed;
}
len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET];
offset = NGX_QUIC_LONG_DCID_OFFSET;
} else {
len = NGX_QUIC_SERVER_CID_LEN;
offset = NGX_QUIC_SHORT_DCID_OFFSET;
}
if (n < len + offset) {
goto failed;
}
dcid->len = len;
dcid->data = &data[offset];
return NGX_OK;
failed:
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet");
return NGX_ERROR;
}
size_t
ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out)
{
u_char *p, *start;
ngx_uint_t i;
p = start = out;
*p++ = pkt->flags;
/*
* The Version field of a Version Negotiation packet
* MUST be set to 0x00000000
*/
p = ngx_quic_write_uint32(p, 0);
*p++ = pkt->dcid.len;
p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
*p++ = pkt->scid.len;
p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
for (i = 0; i < NGX_QUIC_NVERSIONS; i++) {
p = ngx_quic_write_uint32(p, ngx_quic_versions[i]);
}
return p - start;
}
/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */
size_t
ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len)
{
size_t len;
if ngx_quic_short_pkt(pkt->flags) {
len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN;
if (len > pkt_len) {
return 0;
}
return pkt_len - len;
}
/* flags, version, dcid and scid with lengths and zero-length token */
len = 5 + 2 + pkt->dcid.len + pkt->scid.len
+ (pkt->level == ssl_encryption_initial ? 1 : 0);
if (len > pkt_len) {
return 0;
}
/* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */
len += ngx_quic_varint_len(pkt_len - len)
+ pkt->num_len + EVP_GCM_TLS_TAG_LEN;
if (len > pkt_len) {
return 0;
}
return pkt_len - len;
}
size_t
ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp)
{
return ngx_quic_short_pkt(pkt->flags)
? ngx_quic_create_short_header(pkt, out, pnp)
: ngx_quic_create_long_header(pkt, out, pnp);
}
static size_t
ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
u_char **pnp)
{
size_t rem_len;
u_char *p, *start;
rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
if (out == NULL) {
return 5 + 2 + pkt->dcid.len + pkt->scid.len
+ ngx_quic_varint_len(rem_len) + pkt->num_len
+ (pkt->level == ssl_encryption_initial ? 1 : 0);
}
p = start = out;
*p++ = pkt->flags;
p = ngx_quic_write_uint32(p, pkt->version);
*p++ = pkt->dcid.len;
p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
*p++ = pkt->scid.len;
p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
if (pkt->level == ssl_encryption_initial) {
ngx_quic_build_int(&p, 0);
}
ngx_quic_build_int(&p, rem_len);
*pnp = p;
switch (pkt->num_len) {
case 1:
*p++ = pkt->trunc;
break;
case 2:
p = ngx_quic_write_uint16(p, pkt->trunc);
break;
case 3:
p = ngx_quic_write_uint24(p, pkt->trunc);
break;
case 4:
p = ngx_quic_write_uint32(p, pkt->trunc);
break;
}
return p - start;
}
static size_t
ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
u_char **pnp)
{
u_char *p, *start;
if (out == NULL) {
return 1 + pkt->dcid.len + pkt->num_len;
}
p = start = out;
*p++ = pkt->flags;
p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
*pnp = p;
switch (pkt->num_len) {
case 1:
*p++ = pkt->trunc;
break;
case 2:
p = ngx_quic_write_uint16(p, pkt->trunc);
break;
case 3:
p = ngx_quic_write_uint24(p, pkt->trunc);
break;
case 4:
p = ngx_quic_write_uint32(p, pkt->trunc);
break;
}
return p - start;
}
size_t
ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
u_char **start)
{
u_char *p;
p = out;
*p++ = pkt->odcid.len;
p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len);
*start = p;
*p++ = 0xff;
p = ngx_quic_write_uint32(p, pkt->version);
*p++ = pkt->dcid.len;
p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
*p++ = pkt->scid.len;
p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
p = ngx_cpymem(p, pkt->token.data, pkt->token.len);
return p - out;
}
ssize_t
ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
ngx_quic_frame_t *f)
{
u_char *p;
uint64_t varint;
ngx_buf_t *b;
ngx_uint_t i;
b = f->data->buf;
p = start;
p = ngx_quic_parse_int(p, end, &varint);
if (p == NULL) {
pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic failed to obtain quic frame type");
return NGX_ERROR;
}
if (varint > NGX_QUIC_FT_LAST) {
pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic unknown frame type 0x%xL", varint);
return NGX_ERROR;
}
f->type = varint;
if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) {
pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
return NGX_ERROR;
}
switch (f->type) {
case NGX_QUIC_FT_CRYPTO:
p = ngx_quic_parse_int(p, end, &f->u.crypto.offset);
if (p == NULL) {
goto error;
}
p = ngx_quic_parse_int(p, end, &f->u.crypto.length);
if (p == NULL) {
goto error;
}
p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos);
if (p == NULL) {
goto error;
}
b->last = p;
break;
case NGX_QUIC_FT_PADDING:
while (p < end && *p == NGX_QUIC_FT_PADDING) {
p++;
}
break;
case NGX_QUIC_FT_ACK:
case NGX_QUIC_FT_ACK_ECN:
if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest))
&& (p = ngx_quic_parse_int(p, end, &f->u.ack.delay))
&& (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count))
&& (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range))))
{
goto error;
}
b->pos = p;
/* process all ranges to get bounds, values are ignored */
for (i = 0; i < f->u.ack.range_count; i++) {
p = ngx_quic_parse_int(p, end, &varint);
if (p) {
p = ngx_quic_parse_int(p, end, &varint);
}
if (p == NULL) {
goto error;
}
}
b->last = p;
f->u.ack.ranges_length = b->last - b->pos;
if (f->type == NGX_QUIC_FT_ACK_ECN) {
if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0))
&& (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1))
&& (p = ngx_quic_parse_int(p, end, &f->u.ack.ce))))
{
goto error;
}
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
"quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL",
f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
}
break;
case NGX_QUIC_FT_PING:
break;
case NGX_QUIC_FT_NEW_CONNECTION_ID:
p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum);
if (p == NULL) {
goto error;
}
p = ngx_quic_parse_int(p, end, &f->u.ncid.retire);
if (p == NULL) {
goto error;
}
if (f->u.ncid.retire > f->u.ncid.seqnum) {
goto error;
}
p = ngx_quic_read_uint8(p, end, &f->u.ncid.len);
if (p == NULL) {
goto error;
}
if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) {
goto error;
}
p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid);
if (p == NULL) {
goto error;
}
p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_CONNECTION_CLOSE:
case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
p = ngx_quic_parse_int(p, end, &f->u.close.error_code);
if (p == NULL) {
goto error;
}
if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
p = ngx_quic_parse_int(p, end, &f->u.close.frame_type);
if (p == NULL) {
goto error;
}
}
p = ngx_quic_parse_int(p, end, &varint);
if (p == NULL) {
goto error;
}
f->u.close.reason.len = varint;
p = ngx_quic_read_bytes(p, end, f->u.close.reason.len,
&f->u.close.reason.data);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_STREAM:
case NGX_QUIC_FT_STREAM1:
case NGX_QUIC_FT_STREAM2:
case NGX_QUIC_FT_STREAM3:
case NGX_QUIC_FT_STREAM4:
case NGX_QUIC_FT_STREAM5:
case NGX_QUIC_FT_STREAM6:
case NGX_QUIC_FT_STREAM7:
f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0;
p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id);
if (p == NULL) {
goto error;
}
if (f->type & NGX_QUIC_STREAM_FRAME_OFF) {
f->u.stream.off = 1;
p = ngx_quic_parse_int(p, end, &f->u.stream.offset);
if (p == NULL) {
goto error;
}
} else {
f->u.stream.off = 0;
f->u.stream.offset = 0;
}
if (f->type & NGX_QUIC_STREAM_FRAME_LEN) {
f->u.stream.len = 1;
p = ngx_quic_parse_int(p, end, &f->u.stream.length);
if (p == NULL) {
goto error;
}
} else {
f->u.stream.len = 0;
f->u.stream.length = end - p; /* up to packet end */
}
p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos);
if (p == NULL) {
goto error;
}
b->last = p;
f->type = NGX_QUIC_FT_STREAM;
break;
case NGX_QUIC_FT_MAX_DATA:
p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_RESET_STREAM:
if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id))
&& (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code))
&& (p = ngx_quic_parse_int(p, end,
&f->u.reset_stream.final_size))))
{
goto error;
}
break;
case NGX_QUIC_FT_STOP_SENDING:
p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id);
if (p == NULL) {
goto error;
}
p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_STREAMS_BLOCKED:
case NGX_QUIC_FT_STREAMS_BLOCKED2:
p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit);
if (p == NULL) {
goto error;
}
if (f->u.streams_blocked.limit > 0x1000000000000000) {
goto error;
}
f->u.streams_blocked.bidi =
(f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0;
break;
case NGX_QUIC_FT_MAX_STREAMS:
case NGX_QUIC_FT_MAX_STREAMS2:
p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit);
if (p == NULL) {
goto error;
}
if (f->u.max_streams.limit > 0x1000000000000000) {
goto error;
}
f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0;
break;
case NGX_QUIC_FT_MAX_STREAM_DATA:
p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id);
if (p == NULL) {
goto error;
}
p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_DATA_BLOCKED:
p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id);
if (p == NULL) {
goto error;
}
p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_PATH_CHALLENGE:
p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data);
if (p == NULL) {
goto error;
}
break;
case NGX_QUIC_FT_PATH_RESPONSE:
p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data);
if (p == NULL) {
goto error;
}
break;
default:
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic unknown frame type 0x%xi", f->type);
return NGX_ERROR;
}
f->level = pkt->level;
return p - start;
error:
pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic failed to parse frame type:0x%xi", f->type);
return NGX_ERROR;
}
static ngx_int_t
ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type)
{
uint8_t ptype;
/*
* RFC 9000, 12.4. Frames and Frame Types: Table 3
*
* Frame permissions per packet: 4 bits: IH01
*/
static uint8_t ngx_quic_frame_masks[] = {
/* PADDING */ 0xF,
/* PING */ 0xF,
/* ACK */ 0xD,
/* ACK_ECN */ 0xD,
/* RESET_STREAM */ 0x3,
/* STOP_SENDING */ 0x3,
/* CRYPTO */ 0xD,
/* NEW_TOKEN */ 0x0, /* only sent by server */
/* STREAM */ 0x3,
/* STREAM1 */ 0x3,
/* STREAM2 */ 0x3,
/* STREAM3 */ 0x3,
/* STREAM4 */ 0x3,
/* STREAM5 */ 0x3,
/* STREAM6 */ 0x3,
/* STREAM7 */ 0x3,
/* MAX_DATA */ 0x3,
/* MAX_STREAM_DATA */ 0x3,
/* MAX_STREAMS */ 0x3,
/* MAX_STREAMS2 */ 0x3,
/* DATA_BLOCKED */ 0x3,
/* STREAM_DATA_BLOCKED */ 0x3,
/* STREAMS_BLOCKED */ 0x3,
/* STREAMS_BLOCKED2 */ 0x3,
/* NEW_CONNECTION_ID */ 0x3,
/* RETIRE_CONNECTION_ID */ 0x3,
/* PATH_CHALLENGE */ 0x3,
/* PATH_RESPONSE */ 0x1,
/* CONNECTION_CLOSE */ 0xF,
/* CONNECTION_CLOSE2 */ 0x3,
/* HANDSHAKE_DONE */ 0x0, /* only sent by server */
};
if (ngx_quic_long_pkt(pkt->flags)) {
if (ngx_quic_pkt_in(pkt->flags)) {
ptype = 8; /* initial */
} else if (ngx_quic_pkt_hs(pkt->flags)) {
ptype = 4; /* handshake */
} else {
ptype = 2; /* zero-rtt */
}
} else {
ptype = 1; /* application data */
}
if (ptype & ngx_quic_frame_masks[frame_type]) {
return NGX_OK;
}
ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
"quic frame type 0x%xi is not "
"allowed in packet with flags 0x%xd",
frame_type, pkt->flags);
return NGX_DECLINED;
}
ssize_t
ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end,
uint64_t *gap, uint64_t *range)
{
u_char *p;
p = start;
p = ngx_quic_parse_int(p, end, gap);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic failed to parse ack frame gap");
return NGX_ERROR;
}
p = ngx_quic_parse_int(p, end, range);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic failed to parse ack frame range");
return NGX_ERROR;
}
return p - start;
}
size_t
ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(gap);
len += ngx_quic_varint_len(range);
return len;
}
start = p;
ngx_quic_build_int(&p, gap);
ngx_quic_build_int(&p, range);
return p - start;
}
ssize_t
ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f)
{
/*
* RFC 9002, 2. Conventions and Definitions
*
* Ack-eliciting frames: All frames other than ACK, PADDING, and
* CONNECTION_CLOSE are considered ack-eliciting.
*/
f->need_ack = 1;
switch (f->type) {
case NGX_QUIC_FT_PING:
return ngx_quic_create_ping(p);
case NGX_QUIC_FT_ACK:
f->need_ack = 0;
return ngx_quic_create_ack(p, &f->u.ack, f->data);
case NGX_QUIC_FT_RESET_STREAM:
return ngx_quic_create_reset_stream(p, &f->u.reset_stream);
case NGX_QUIC_FT_STOP_SENDING:
return ngx_quic_create_stop_sending(p, &f->u.stop_sending);
case NGX_QUIC_FT_CRYPTO:
return ngx_quic_create_crypto(p, &f->u.crypto, f->data);
case NGX_QUIC_FT_HANDSHAKE_DONE:
return ngx_quic_create_hs_done(p);
case NGX_QUIC_FT_NEW_TOKEN:
return ngx_quic_create_new_token(p, &f->u.token);
case NGX_QUIC_FT_STREAM:
return ngx_quic_create_stream(p, &f->u.stream, f->data);
case NGX_QUIC_FT_CONNECTION_CLOSE:
case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
f->need_ack = 0;
return ngx_quic_create_close(p, f);
case NGX_QUIC_FT_MAX_STREAMS:
return ngx_quic_create_max_streams(p, &f->u.max_streams);
case NGX_QUIC_FT_MAX_STREAM_DATA:
return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data);
case NGX_QUIC_FT_MAX_DATA:
return ngx_quic_create_max_data(p, &f->u.max_data);
case NGX_QUIC_FT_PATH_CHALLENGE:
return ngx_quic_create_path_challenge(p, &f->u.path_challenge);
case NGX_QUIC_FT_PATH_RESPONSE:
return ngx_quic_create_path_response(p, &f->u.path_response);
case NGX_QUIC_FT_NEW_CONNECTION_ID:
return ngx_quic_create_new_connection_id(p, &f->u.ncid);
case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid);
default:
/* BUG: unsupported frame type generated */
return NGX_ERROR;
}
}
static size_t
ngx_quic_create_ping(u_char *p)
{
u_char *start;
if (p == NULL) {
return ngx_quic_varint_len(NGX_QUIC_FT_PING);
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_PING);
return p - start;
}
static size_t
ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges)
{
size_t len;
u_char *start;
ngx_buf_t *b;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_ACK);
len += ngx_quic_varint_len(ack->largest);
len += ngx_quic_varint_len(ack->delay);
len += ngx_quic_varint_len(ack->range_count);
len += ngx_quic_varint_len(ack->first_range);
len += ack->ranges_length;
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_ACK);
ngx_quic_build_int(&p, ack->largest);
ngx_quic_build_int(&p, ack->delay);
ngx_quic_build_int(&p, ack->range_count);
ngx_quic_build_int(&p, ack->first_range);
while (ranges) {
b = ranges->buf;
p = ngx_cpymem(p, b->pos, b->last - b->pos);
ranges = ranges->next;
}
return p - start;
}
static size_t
ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM);
len += ngx_quic_varint_len(rs->id);
len += ngx_quic_varint_len(rs->error_code);
len += ngx_quic_varint_len(rs->final_size);
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM);
ngx_quic_build_int(&p, rs->id);
ngx_quic_build_int(&p, rs->error_code);
ngx_quic_build_int(&p, rs->final_size);
return p - start;
}
static size_t
ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING);
len += ngx_quic_varint_len(ss->id);
len += ngx_quic_varint_len(ss->error_code);
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING);
ngx_quic_build_int(&p, ss->id);
ngx_quic_build_int(&p, ss->error_code);
return p - start;
}
static size_t
ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto,
ngx_chain_t *data)
{
size_t len;
u_char *start;
ngx_buf_t *b;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO);
len += ngx_quic_varint_len(crypto->offset);
len += ngx_quic_varint_len(crypto->length);
len += crypto->length;
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO);
ngx_quic_build_int(&p, crypto->offset);
ngx_quic_build_int(&p, crypto->length);
while (data) {
b = data->buf;
p = ngx_cpymem(p, b->pos, b->last - b->pos);
data = data->next;
}
return p - start;
}
static size_t
ngx_quic_create_hs_done(u_char *p)
{
u_char *start;
if (p == NULL) {
return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE);
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE);
return p - start;
}
static size_t
ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN);
len += ngx_quic_varint_len(token->length);
len += token->length;
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN);
ngx_quic_build_int(&p, token->length);
p = ngx_cpymem(p, token->data, token->length);
return p - start;
}
static size_t
ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf,
ngx_chain_t *data)
{
size_t len;
u_char *start, type;
ngx_buf_t *b;
type = NGX_QUIC_FT_STREAM;
if (sf->off) {
type |= NGX_QUIC_STREAM_FRAME_OFF;
}
if (sf->len) {
type |= NGX_QUIC_STREAM_FRAME_LEN;
}
if (sf->fin) {
type |= NGX_QUIC_STREAM_FRAME_FIN;
}
if (p == NULL) {
len = ngx_quic_varint_len(type);
len += ngx_quic_varint_len(sf->stream_id);
if (sf->off) {
len += ngx_quic_varint_len(sf->offset);
}
if (sf->len) {
len += ngx_quic_varint_len(sf->length);
}
len += sf->length;
return len;
}
start = p;
ngx_quic_build_int(&p, type);
ngx_quic_build_int(&p, sf->stream_id);
if (sf->off) {
ngx_quic_build_int(&p, sf->offset);
}
if (sf->len) {
ngx_quic_build_int(&p, sf->length);
}
while (data) {
b = data->buf;
p = ngx_cpymem(p, b->pos, b->last - b->pos);
data = data->next;
}
return p - start;
}
static size_t
ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms)
{
size_t len;
u_char *start;
ngx_uint_t type;
type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2;
if (p == NULL) {
len = ngx_quic_varint_len(type);
len += ngx_quic_varint_len(ms->limit);
return len;
}
start = p;
ngx_quic_build_int(&p, type);
ngx_quic_build_int(&p, ms->limit);
return p - start;
}
static ngx_int_t
ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id,
ngx_quic_tp_t *dst)
{
uint64_t varint;
ngx_str_t str;
varint = 0;
ngx_str_null(&str);
switch (id) {
case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION:
/* zero-length option */
if (end - p != 0) {
return NGX_ERROR;
}
dst->disable_active_migration = 1;
return NGX_OK;
case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
case NGX_QUIC_TP_INITIAL_MAX_DATA:
case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
case NGX_QUIC_TP_MAX_ACK_DELAY:
case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
p = ngx_quic_parse_int(p, end, &varint);
if (p == NULL) {
return NGX_ERROR;
}
break;
case NGX_QUIC_TP_INITIAL_SCID:
str.len = end - p;
str.data = p;
break;
default:
return NGX_DECLINED;
}
switch (id) {
case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
dst->max_idle_timeout = varint;
break;
case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
dst->max_udp_payload_size = varint;
break;
case NGX_QUIC_TP_INITIAL_MAX_DATA:
dst->initial_max_data = varint;
break;
case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
dst->initial_max_stream_data_bidi_local = varint;
break;
case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
dst->initial_max_stream_data_bidi_remote = varint;
break;
case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
dst->initial_max_stream_data_uni = varint;
break;
case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
dst->initial_max_streams_bidi = varint;
break;
case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
dst->initial_max_streams_uni = varint;
break;
case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
dst->ack_delay_exponent = varint;
break;
case NGX_QUIC_TP_MAX_ACK_DELAY:
dst->max_ack_delay = varint;
break;
case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
dst->active_connection_id_limit = varint;
break;
case NGX_QUIC_TP_INITIAL_SCID:
dst->initial_scid = str;
break;
default:
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp,
ngx_log_t *log)
{
uint64_t id, len;
ngx_int_t rc;
while (p < end) {
p = ngx_quic_parse_int(p, end, &id);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic failed to parse transport param id");
return NGX_ERROR;
}
switch (id) {
case NGX_QUIC_TP_ORIGINAL_DCID:
case NGX_QUIC_TP_PREFERRED_ADDRESS:
case NGX_QUIC_TP_RETRY_SCID:
case NGX_QUIC_TP_SR_TOKEN:
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic client sent forbidden transport param"
" id:0x%xL", id);
return NGX_ERROR;
}
p = ngx_quic_parse_int(p, end, &len);
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic failed to parse"
" transport param id:0x%xL length", id);
return NGX_ERROR;
}
rc = ngx_quic_parse_transport_param(p, p + len, id, tp);
if (rc == NGX_ERROR) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic failed to parse"
" transport param id:0x%xL data", id);
return NGX_ERROR;
}
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic %s transport param id:0x%xL, skipped",
(id % 31 == 27) ? "reserved" : "unknown", id);
}
p += len;
}
if (p != end) {
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic trailing garbage in"
" transport parameters: bytes:%ui",
end - p);
return NGX_ERROR;
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0,
"quic transport parameters parsed ok");
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp disable active migration: %ui",
tp->disable_active_migration);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui",
tp->max_idle_timeout);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp max_udp_payload_size:%ui",
tp->max_udp_payload_size);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui",
tp->initial_max_data);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp max_stream_data_bidi_local:%ui",
tp->initial_max_stream_data_bidi_local);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp max_stream_data_bidi_remote:%ui",
tp->initial_max_stream_data_bidi_remote);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp max_stream_data_uni:%ui",
tp->initial_max_stream_data_uni);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp initial_max_streams_bidi:%ui",
tp->initial_max_streams_bidi);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp initial_max_streams_uni:%ui",
tp->initial_max_streams_uni);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp ack_delay_exponent:%ui",
tp->ack_delay_exponent);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui",
tp->max_ack_delay);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp active_connection_id_limit:%ui",
tp->active_connection_id_limit);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
"quic tp initial source_connection_id len:%uz %xV",
tp->initial_scid.len, &tp->initial_scid);
return NGX_OK;
}
static size_t
ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA);
len += ngx_quic_varint_len(ms->id);
len += ngx_quic_varint_len(ms->limit);
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA);
ngx_quic_build_int(&p, ms->id);
ngx_quic_build_int(&p, ms->limit);
return p - start;
}
static size_t
ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA);
len += ngx_quic_varint_len(md->max_data);
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA);
ngx_quic_build_int(&p, md->max_data);
return p - start;
}
static size_t
ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE);
len += sizeof(pc->data);
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE);
p = ngx_cpymem(p, &pc->data, sizeof(pc->data));
return p - start;
}
static size_t
ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE);
len += sizeof(pc->data);
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE);
p = ngx_cpymem(p, &pc->data, sizeof(pc->data));
return p - start;
}
static size_t
ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID);
len += ngx_quic_varint_len(ncid->seqnum);
len += ngx_quic_varint_len(ncid->retire);
len++;
len += ncid->len;
len += NGX_QUIC_SR_TOKEN_LEN;
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID);
ngx_quic_build_int(&p, ncid->seqnum);
ngx_quic_build_int(&p, ncid->retire);
*p++ = ncid->len;
p = ngx_cpymem(p, ncid->cid, ncid->len);
p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN);
return p - start;
}
static size_t
ngx_quic_create_retire_connection_id(u_char *p,
ngx_quic_retire_cid_frame_t *rcid)
{
size_t len;
u_char *start;
if (p == NULL) {
len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID);
len += ngx_quic_varint_len(rcid->sequence_number);
return len;
}
start = p;
ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID);
ngx_quic_build_int(&p, rcid->sequence_number);
return p - start;
}
ngx_int_t
ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf)
{
ngx_uint_t nstreams;
ngx_memzero(tp, sizeof(ngx_quic_tp_t));
/*
* set by ngx_memzero():
*
* tp->disable_active_migration = 0;
* tp->original_dcid = { 0, NULL };
* tp->initial_scid = { 0, NULL };
* tp->retry_scid = { 0, NULL };
* tp->sr_token = { 0 }
* tp->sr_enabled = 0
* tp->preferred_address = NULL
*/
tp->max_idle_timeout = qcf->timeout;
tp->max_udp_payload_size = qcf->mtu;
nstreams = qcf->max_concurrent_streams_bidi
+ qcf->max_concurrent_streams_uni;
tp->initial_max_data = nstreams * qcf->stream_buffer_size;
tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size;
tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size;
tp->initial_max_stream_data_uni = qcf->stream_buffer_size;
tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi;
tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni;
tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
tp->active_connection_id_limit = qcf->active_connection_id_limit;
tp->disable_active_migration = qcf->disable_active_migration;
return NGX_OK;
}
ssize_t
ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp,
size_t *clen)
{
u_char *p;
size_t len;
#define ngx_quic_tp_len(id, value) \
ngx_quic_varint_len(id) \
+ ngx_quic_varint_len(value) \
+ ngx_quic_varint_len(ngx_quic_varint_len(value))
#define ngx_quic_tp_vint(id, value) \
do { \
ngx_quic_build_int(&p, id); \
ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \
ngx_quic_build_int(&p, value); \
} while (0)
#define ngx_quic_tp_strlen(id, value) \
ngx_quic_varint_len(id) \
+ ngx_quic_varint_len(value.len) \
+ value.len
#define ngx_quic_tp_str(id, value) \
do { \
ngx_quic_build_int(&p, id); \
ngx_quic_build_int(&p, value.len); \
p = ngx_cpymem(p, value.data, value.len); \
} while (0)
len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data);
len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
tp->initial_max_streams_uni);
len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
tp->initial_max_streams_bidi);
len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
tp->initial_max_stream_data_bidi_local);
len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
tp->initial_max_stream_data_bidi_remote);
len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
tp->initial_max_stream_data_uni);
len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
tp->max_idle_timeout);
len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE,
tp->max_udp_payload_size);
if (tp->disable_active_migration) {
len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
len += ngx_quic_varint_len(0);
}
len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
tp->active_connection_id_limit);
/* transport parameters listed above will be saved in 0-RTT context */
if (clen) {
*clen = len;
}
len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY,
tp->max_ack_delay);
len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT,
tp->ack_delay_exponent);
len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
if (tp->retry_scid.len) {
len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
}
len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN);
len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN);
len += NGX_QUIC_SR_TOKEN_LEN;
if (pos == NULL) {
return len;
}
p = pos;
ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA,
tp->initial_max_data);
ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
tp->initial_max_streams_uni);
ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
tp->initial_max_streams_bidi);
ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
tp->initial_max_stream_data_bidi_local);
ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
tp->initial_max_stream_data_bidi_remote);
ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
tp->initial_max_stream_data_uni);
ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
tp->max_idle_timeout);
ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE,
tp->max_udp_payload_size);
if (tp->disable_active_migration) {
ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
ngx_quic_build_int(&p, 0);
}
ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
tp->active_connection_id_limit);
ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY,
tp->max_ack_delay);
ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT,
tp->ack_delay_exponent);
ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
if (tp->retry_scid.len) {
ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
}
ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN);
ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN);
p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN);
return p - pos;
}
static size_t
ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f)
{
size_t len;
u_char *start;
ngx_quic_close_frame_t *cl;
cl = &f->u.close;
if (p == NULL) {
len = ngx_quic_varint_len(f->type);
len += ngx_quic_varint_len(cl->error_code);
if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) {
len += ngx_quic_varint_len(cl->frame_type);
}
len += ngx_quic_varint_len(cl->reason.len);
len += cl->reason.len;
return len;
}
start = p;
ngx_quic_build_int(&p, f->type);
ngx_quic_build_int(&p, cl->error_code);
if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) {
ngx_quic_build_int(&p, cl->frame_type);
}
ngx_quic_build_int(&p, cl->reason.len);
p = ngx_cpymem(p, cl->reason.data, cl->reason.len);
return p - start;
}
void
ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key)
{
(void) ngx_quic_write_uint64(dcid, key);
}
+398
View File
@@ -0,0 +1,398 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
/*
* RFC 9000, 17.2. Long Header Packets
* 17.3. Short Header Packets
*
* QUIC flags in first byte
*/
#define NGX_QUIC_PKT_LONG 0x80 /* header form */
#define NGX_QUIC_PKT_FIXED_BIT 0x40
#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */
#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */
#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG)
#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0)
/* Long packet types */
#define NGX_QUIC_PKT_INITIAL 0x00
#define NGX_QUIC_PKT_ZRTT 0x10
#define NGX_QUIC_PKT_HANDSHAKE 0x20
#define NGX_QUIC_PKT_RETRY 0x30
#define ngx_quic_pkt_in(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL)
#define ngx_quic_pkt_zrtt(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT)
#define ngx_quic_pkt_hs(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE)
#define ngx_quic_pkt_retry(flags) \
(((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY)
#define ngx_quic_pkt_rb_mask(flags) \
(ngx_quic_long_pkt(flags) ? 0x0C : 0x18)
#define ngx_quic_pkt_hp_mask(flags) \
(ngx_quic_long_pkt(flags) ? 0x0F : 0x1F)
#define ngx_quic_level_name(lvl) \
(lvl == ssl_encryption_application) ? "app" \
: (lvl == ssl_encryption_initial) ? "init" \
: (lvl == ssl_encryption_handshake) ? "hs" : "early"
#define NGX_QUIC_MAX_CID_LEN 20
#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN
/* 12.4. Frames and Frame Types */
#define NGX_QUIC_FT_PADDING 0x00
#define NGX_QUIC_FT_PING 0x01
#define NGX_QUIC_FT_ACK 0x02
#define NGX_QUIC_FT_ACK_ECN 0x03
#define NGX_QUIC_FT_RESET_STREAM 0x04
#define NGX_QUIC_FT_STOP_SENDING 0x05
#define NGX_QUIC_FT_CRYPTO 0x06
#define NGX_QUIC_FT_NEW_TOKEN 0x07
#define NGX_QUIC_FT_STREAM 0x08
#define NGX_QUIC_FT_STREAM1 0x09
#define NGX_QUIC_FT_STREAM2 0x0A
#define NGX_QUIC_FT_STREAM3 0x0B
#define NGX_QUIC_FT_STREAM4 0x0C
#define NGX_QUIC_FT_STREAM5 0x0D
#define NGX_QUIC_FT_STREAM6 0x0E
#define NGX_QUIC_FT_STREAM7 0x0F
#define NGX_QUIC_FT_MAX_DATA 0x10
#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11
#define NGX_QUIC_FT_MAX_STREAMS 0x12
#define NGX_QUIC_FT_MAX_STREAMS2 0x13
#define NGX_QUIC_FT_DATA_BLOCKED 0x14
#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15
#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16
#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17
#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18
#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19
#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A
#define NGX_QUIC_FT_PATH_RESPONSE 0x1B
#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C
#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D
#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E
#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE
/* 22.5. QUIC Transport Error Codes Registry */
#define NGX_QUIC_ERR_NO_ERROR 0x00
#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01
#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02
#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03
#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04
#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05
#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06
#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07
#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08
#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09
#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A
#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B
#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C
#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D
#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E
#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F
#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10
#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100
#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e))
/* 22.3. QUIC Transport Parameters Registry */
#define NGX_QUIC_TP_ORIGINAL_DCID 0x00
#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01
#define NGX_QUIC_TP_SR_TOKEN 0x02
#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03
#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04
#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05
#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06
#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07
#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08
#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09
#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A
#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B
#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C
#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D
#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E
#define NGX_QUIC_TP_INITIAL_SCID 0x0F
#define NGX_QUIC_TP_RETRY_SCID 0x10
#define NGX_QUIC_CID_LEN_MIN 8
#define NGX_QUIC_CID_LEN_MAX 20
#define NGX_QUIC_MAX_RANGES 10
typedef struct {
uint64_t gap;
uint64_t range;
} ngx_quic_ack_range_t;
typedef struct {
uint64_t largest;
uint64_t delay;
uint64_t range_count;
uint64_t first_range;
uint64_t ect0;
uint64_t ect1;
uint64_t ce;
uint64_t ranges_length;
} ngx_quic_ack_frame_t;
typedef struct {
uint64_t seqnum;
uint64_t retire;
uint8_t len;
u_char cid[NGX_QUIC_CID_LEN_MAX];
u_char srt[NGX_QUIC_SR_TOKEN_LEN];
} ngx_quic_new_conn_id_frame_t;
typedef struct {
uint64_t length;
u_char *data;
} ngx_quic_new_token_frame_t;
/*
* common layout for CRYPTO and STREAM frames;
* conceptually, CRYPTO frame is also a stream
* frame lacking some properties
*/
typedef struct {
uint64_t offset;
uint64_t length;
} ngx_quic_ordered_frame_t;
typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t;
typedef struct {
/* initial fields same as in ngx_quic_ordered_frame_t */
uint64_t offset;
uint64_t length;
uint64_t stream_id;
unsigned off:1;
unsigned len:1;
unsigned fin:1;
} ngx_quic_stream_frame_t;
typedef struct {
uint64_t max_data;
} ngx_quic_max_data_frame_t;
typedef struct {
uint64_t error_code;
uint64_t frame_type;
ngx_str_t reason;
} ngx_quic_close_frame_t;
typedef struct {
uint64_t id;
uint64_t error_code;
uint64_t final_size;
} ngx_quic_reset_stream_frame_t;
typedef struct {
uint64_t id;
uint64_t error_code;
} ngx_quic_stop_sending_frame_t;
typedef struct {
uint64_t limit;
ngx_uint_t bidi; /* unsigned: bidi:1 */
} ngx_quic_streams_blocked_frame_t;
typedef struct {
uint64_t limit;
ngx_uint_t bidi; /* unsigned: bidi:1 */
} ngx_quic_max_streams_frame_t;
typedef struct {
uint64_t id;
uint64_t limit;
} ngx_quic_max_stream_data_frame_t;
typedef struct {
uint64_t limit;
} ngx_quic_data_blocked_frame_t;
typedef struct {
uint64_t id;
uint64_t limit;
} ngx_quic_stream_data_blocked_frame_t;
typedef struct {
uint64_t sequence_number;
} ngx_quic_retire_cid_frame_t;
typedef struct {
u_char data[8];
} ngx_quic_path_challenge_frame_t;
typedef struct ngx_quic_frame_s ngx_quic_frame_t;
struct ngx_quic_frame_s {
ngx_uint_t type;
enum ssl_encryption_level_t level;
ngx_queue_t queue;
uint64_t pnum;
size_t plen;
ngx_msec_t first;
ngx_msec_t last;
ssize_t len;
unsigned need_ack:1;
unsigned pkt_need_ack:1;
unsigned flush:1;
ngx_chain_t *data;
union {
ngx_quic_ack_frame_t ack;
ngx_quic_crypto_frame_t crypto;
ngx_quic_ordered_frame_t ord;
ngx_quic_new_conn_id_frame_t ncid;
ngx_quic_new_token_frame_t token;
ngx_quic_stream_frame_t stream;
ngx_quic_max_data_frame_t max_data;
ngx_quic_close_frame_t close;
ngx_quic_reset_stream_frame_t reset_stream;
ngx_quic_stop_sending_frame_t stop_sending;
ngx_quic_streams_blocked_frame_t streams_blocked;
ngx_quic_max_streams_frame_t max_streams;
ngx_quic_max_stream_data_frame_t max_stream_data;
ngx_quic_data_blocked_frame_t data_blocked;
ngx_quic_stream_data_blocked_frame_t stream_data_blocked;
ngx_quic_retire_cid_frame_t retire_cid;
ngx_quic_path_challenge_frame_t path_challenge;
ngx_quic_path_challenge_frame_t path_response;
} u;
};
typedef struct {
ngx_log_t *log;
ngx_quic_path_t *path;
ngx_quic_keys_t *keys;
ngx_msec_t received;
uint64_t number;
uint8_t num_len;
uint32_t trunc;
uint8_t flags;
uint32_t version;
ngx_str_t token;
enum ssl_encryption_level_t level;
ngx_uint_t error;
/* filled in by parser */
ngx_buf_t *raw; /* udp datagram */
u_char *data; /* quic packet */
size_t len;
/* cleartext fields */
ngx_str_t odcid; /* retry packet tag */
u_char odcid_buf[NGX_QUIC_MAX_CID_LEN];
ngx_str_t dcid;
ngx_str_t scid;
uint64_t pn;
u_char *plaintext;
ngx_str_t payload; /* decrypted data */
unsigned need_ack:1;
unsigned key_phase:1;
unsigned key_update:1;
unsigned parsed:1;
unsigned decrypted:1;
unsigned validated:1;
unsigned retried:1;
unsigned first:1;
unsigned rebound:1;
} ngx_quic_header_t;
typedef struct {
ngx_msec_t max_idle_timeout;
ngx_msec_t max_ack_delay;
size_t max_udp_payload_size;
size_t initial_max_data;
size_t initial_max_stream_data_bidi_local;
size_t initial_max_stream_data_bidi_remote;
size_t initial_max_stream_data_uni;
ngx_uint_t initial_max_streams_bidi;
ngx_uint_t initial_max_streams_uni;
ngx_uint_t ack_delay_exponent;
ngx_uint_t active_connection_id_limit;
ngx_flag_t disable_active_migration;
ngx_str_t original_dcid;
ngx_str_t initial_scid;
ngx_str_t retry_scid;
u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
/* TODO */
void *preferred_address;
} ngx_quic_tp_t;
ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt);
size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out);
size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len);
size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out,
u_char **pnp);
size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
u_char **start);
ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
ngx_quic_frame_t *frame);
ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f);
ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start,
u_char *end, uint64_t *gap, uint64_t *range);
size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range);
ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp,
ngx_quic_conf_t *qcf);
ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end,
ngx_quic_tp_t *tp, ngx_log_t *log);
ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end,
ngx_quic_tp_t *tp, size_t *clen);
void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key);
#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */
+473
View File
@@ -0,0 +1,473 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
static void ngx_quic_close_accepted_connection(ngx_connection_t *c);
static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls,
ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen);
void
ngx_quic_recvmsg(ngx_event_t *ev)
{
ssize_t n;
ngx_str_t key;
ngx_buf_t buf;
ngx_log_t *log;
ngx_err_t err;
socklen_t socklen, local_socklen;
ngx_event_t *rev, *wev;
struct iovec iov[1];
struct msghdr msg;
ngx_sockaddr_t sa, lsa;
struct sockaddr *sockaddr, *local_sockaddr;
ngx_listening_t *ls;
ngx_event_conf_t *ecf;
ngx_connection_t *c, *lc;
ngx_quic_socket_t *qsock;
static u_char buffer[65535];
#if (NGX_HAVE_ADDRINFO_CMSG)
u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
#endif
if (ev->timedout) {
if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
return;
}
ev->timedout = 0;
}
ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
ev->available = ecf->multi_accept;
}
lc = ev->data;
ls = lc->listening;
ev->ready = 0;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"quic recvmsg on %V, ready: %d",
&ls->addr_text, ev->available);
do {
ngx_memzero(&msg, sizeof(struct msghdr));
iov[0].iov_base = (void *) buffer;
iov[0].iov_len = sizeof(buffer);
msg.msg_name = &sa;
msg.msg_namelen = sizeof(ngx_sockaddr_t);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
#if (NGX_HAVE_ADDRINFO_CMSG)
if (ls->wildcard) {
msg.msg_control = &msg_control;
msg.msg_controllen = sizeof(msg_control);
ngx_memzero(&msg_control, sizeof(msg_control));
}
#endif
n = recvmsg(lc->fd, &msg, 0);
if (n == -1) {
err = ngx_socket_errno;
if (err == NGX_EAGAIN) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
"quic recvmsg() not ready");
return;
}
ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed");
return;
}
#if (NGX_HAVE_ADDRINFO_CMSG)
if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
"quic recvmsg() truncated data");
continue;
}
#endif
sockaddr = msg.msg_name;
socklen = msg.msg_namelen;
if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
socklen = sizeof(ngx_sockaddr_t);
}
#if (NGX_HAVE_UNIX_DOMAIN)
if (sockaddr->sa_family == AF_UNIX) {
struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr;
if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path)
|| saun->sun_path[0] == '\0')
{
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
"unbound unix socket");
goto next;
}
}
#endif
local_sockaddr = ls->sockaddr;
local_socklen = ls->socklen;
#if (NGX_HAVE_ADDRINFO_CMSG)
if (ls->wildcard) {
struct cmsghdr *cmsg;
ngx_memcpy(&lsa, local_sockaddr, local_socklen);
local_sockaddr = &lsa.sockaddr;
for (cmsg = CMSG_FIRSTHDR(&msg);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg))
{
if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) {
break;
}
}
}
#endif
if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) {
goto next;
}
c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen);
if (c) {
#if (NGX_DEBUG)
if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
ngx_log_handler_pt handler;
handler = c->log->handler;
c->log->handler = NULL;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic recvmsg: fd:%d n:%z", c->fd, n);
c->log->handler = handler;
}
#endif
ngx_memzero(&buf, sizeof(ngx_buf_t));
buf.pos = buffer;
buf.last = buffer + n;
buf.start = buf.pos;
buf.end = buffer + sizeof(buffer);
qsock = ngx_quic_get_socket(c);
ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen);
qsock->socklen = socklen;
c->udp->buffer = &buf;
rev = c->read;
rev->ready = 1;
rev->active = 0;
rev->handler(rev);
if (c->udp) {
c->udp->buffer = NULL;
}
rev->ready = 0;
rev->active = 1;
goto next;
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
#endif
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
c = ngx_get_connection(lc->fd, ev->log);
if (c == NULL) {
return;
}
c->shared = 1;
c->type = SOCK_DGRAM;
c->socklen = socklen;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
c->pool = ngx_create_pool(ls->pool_size, ev->log);
if (c->pool == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN);
if (c->sockaddr == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
ngx_memcpy(c->sockaddr, sockaddr, socklen);
log = ngx_palloc(c->pool, sizeof(ngx_log_t));
if (log == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
*log = ls->log;
c->log = log;
c->pool->log = log;
c->listening = ls;
if (local_sockaddr == &lsa.sockaddr) {
local_sockaddr = ngx_palloc(c->pool, local_socklen);
if (local_sockaddr == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
ngx_memcpy(local_sockaddr, &lsa, local_socklen);
}
c->local_sockaddr = local_sockaddr;
c->local_socklen = local_socklen;
c->buffer = ngx_create_temp_buf(c->pool, n);
if (c->buffer == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n);
rev = c->read;
wev = c->write;
rev->active = 1;
wev->ready = 1;
rev->log = log;
wev->log = log;
/*
* TODO: MT: - ngx_atomic_fetch_add()
* or protection by critical section or light mutex
*
* TODO: MP: - allocated in a shared memory
* - ngx_atomic_fetch_add()
* or protection by critical section or light mutex
*/
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
c->start_time = ngx_current_msec;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
#endif
if (ls->addr_ntop) {
c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
if (c->addr_text.data == NULL) {
ngx_quic_close_accepted_connection(c);
return;
}
c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
c->addr_text.data,
ls->addr_text_max_len, 0);
if (c->addr_text.len == 0) {
ngx_quic_close_accepted_connection(c);
return;
}
}
#if (NGX_DEBUG)
{
ngx_str_t addr;
u_char text[NGX_SOCKADDR_STRLEN];
ngx_debug_accepted_connection(ecf, c);
if (log->log_level & NGX_LOG_DEBUG_EVENT) {
addr.data = text;
addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
NGX_SOCKADDR_STRLEN, 1);
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0,
"*%uA quic recvmsg: %V fd:%d n:%z",
c->number, &addr, c->fd, n);
}
}
#endif
log->data = NULL;
log->handler = NULL;
ls->handler(c);
next:
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
ev->available -= n;
}
} while (ev->available);
}
static void
ngx_quic_close_accepted_connection(ngx_connection_t *c)
{
ngx_free_connection(c);
c->fd = (ngx_socket_t) -1;
if (c->pool) {
ngx_destroy_pool(c->pool);
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif
}
void
ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
ngx_int_t rc;
ngx_connection_t *c, *ct;
ngx_rbtree_node_t **p;
ngx_quic_socket_t *qsock, *qsockt;
for ( ;; ) {
if (node->key < temp->key) {
p = &temp->left;
} else if (node->key > temp->key) {
p = &temp->right;
} else { /* node->key == temp->key */
qsock = (ngx_quic_socket_t *) node;
c = qsock->udp.connection;
qsockt = (ngx_quic_socket_t *) temp;
ct = qsockt->udp.connection;
rc = ngx_memn2cmp(qsock->sid.id, qsockt->sid.id,
qsock->sid.len, qsockt->sid.len);
if (rc == 0 && c->listening->wildcard) {
rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen,
ct->local_sockaddr, ct->local_socklen, 1);
}
p = (rc < 0) ? &temp->left : &temp->right;
}
if (*p == sentinel) {
break;
}
temp = *p;
}
*p = node;
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}
static ngx_connection_t *
ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key,
struct sockaddr *local_sockaddr, socklen_t local_socklen)
{
uint32_t hash;
ngx_int_t rc;
ngx_connection_t *c;
ngx_rbtree_node_t *node, *sentinel;
ngx_quic_socket_t *qsock;
if (key->len == 0) {
return NULL;
}
node = ls->rbtree.root;
sentinel = ls->rbtree.sentinel;
hash = ngx_crc32_long(key->data, key->len);
while (node != sentinel) {
if (hash < node->key) {
node = node->left;
continue;
}
if (hash > node->key) {
node = node->right;
continue;
}
/* hash == node->key */
qsock = (ngx_quic_socket_t *) node;
rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len);
c = qsock->udp.connection;
if (rc == 0 && ls->wildcard) {
rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen,
c->local_sockaddr, c->local_socklen, 1);
}
if (rc == 0) {
c->udp = &qsock->udp;
return c;
}
node = (rc < 0) ? node->left : node->right;
}
return NULL;
}
+55 -14
View File
@@ -15,6 +15,7 @@ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5"
// #define NGX_DEFAULT_CIPHERS "[TLS13+AESGCM+AES128|TLS13+CHACHA20]:TLS13+AESGCM+AES256:[EECDH+ECDSA+AESGCM+AES128|EECDH+ECDSA+CHACHA20]:EECDH+ECDSA+AESGCM+AES256:EECDH+ECDSA+AES128+SHA:EECDH+ECDSA+AES256+SHA:[EECDH+aRSA+AESGCM+AES128|EECDH+aRSA+CHACHA20]:EECDH+aRSA+AESGCM+AES256:EECDH+aRSA+AES128+SHA:EECDH+aRSA+AES256+SHA"
#define NGX_DEFAULT_ECDH_CURVE "auto"
#define NGX_HTTP_ALPN_PROTOS "\x08http/1.1\x08http/1.0\x08http/0.9"
@@ -419,16 +420,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned int inlen,
void *arg)
{
unsigned int srvlen;
unsigned char *srv;
unsigned int srvlen;
unsigned char *srv;
#if (NGX_DEBUG)
unsigned int i;
unsigned int i;
#endif
#if (NGX_HTTP_V2)
ngx_http_connection_t *hc;
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
ngx_http_connection_t *hc;
#endif
#if (NGX_HTTP_V2 || NGX_DEBUG)
ngx_connection_t *c;
#if (NGX_HTTP_V3 && NGX_HTTP_V3_HQ)
ngx_http_v3_srv_conf_t *h3scf;
#endif
#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG)
ngx_connection_t *c;
c = ngx_ssl_get_connection(ssl_conn);
#endif
@@ -441,13 +445,33 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
}
#endif
#if (NGX_HTTP_V2)
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
hc = c->data;
#endif
#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS;
srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1;
} else
#endif
#if (NGX_HTTP_V3)
if (hc->addr_conf->http3) {
#if (NGX_HTTP_V3_HQ)
h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
if (h3scf->hq) {
srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO;
srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1;
} else
#endif
{
srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO;
srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1;
}
} else
#endif
{
srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS;
@@ -623,7 +647,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
}
ngx_conf_merge_value(conf->session_timeout,
prev->session_timeout, 300);
prev->session_timeout, 64800);
ngx_conf_merge_value(conf->prefer_server_ciphers,
prev->prefer_server_ciphers, 0);
@@ -1240,6 +1264,7 @@ static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
{
ngx_uint_t a, p, s;
const char *name;
ngx_http_conf_addr_t *addr;
ngx_http_conf_port_t *port;
ngx_http_ssl_srv_conf_t *sscf;
@@ -1289,22 +1314,38 @@ ngx_http_ssl_init(ngx_conf_t *cf)
addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {
if (!addr[a].opt.ssl) {
if (!addr[a].opt.ssl && !addr[a].opt.http3) {
continue;
}
if (addr[a].opt.http3) {
name = "http3";
} else {
name = "ssl";
}
cscf = addr[a].default_server;
sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
if (sscf->certificates) {
if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"\"ssl_protocols\" must enable TLSv1.3 for "
"the \"listen ... %s\" directive in %s:%ui",
name, cscf->file_name, cscf->line);
return NGX_ERROR;
}
continue;
}
if (!sscf->reject_handshake) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"no \"ssl_certificate\" is defined for "
"the \"listen ... ssl\" directive in %s:%ui",
cscf->file_name, cscf->line);
"the \"listen ... %s\" directive in %s:%ui",
name, cscf->file_name, cscf->line);
return NGX_ERROR;
}
@@ -1325,8 +1366,8 @@ ngx_http_ssl_init(ngx_conf_t *cf)
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"no \"ssl_certificate\" is defined for "
"the \"listen ... ssl\" directive in %s:%ui",
cscf->file_name, cscf->line);
"the \"listen ... %s\" directive in %s:%ui",
name, cscf->file_name, cscf->line);
return NGX_ERROR;
}
}
+34 -1
View File
@@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
port = cmcf->ports->elts;
for (i = 0; i < cmcf->ports->nelts; i++) {
if (p != port[i].port || sa->sa_family != port[i].family) {
if (p != port[i].port
|| lsopt->type != port[i].type
|| sa->sa_family != port[i].family)
{
continue;
}
@@ -1217,6 +1220,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
}
port->family = sa->sa_family;
port->type = lsopt->type;
port->port = p;
port->addrs.elts = NULL;
@@ -1236,6 +1240,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
ngx_uint_t http2;
#endif
#if (NGX_HTTP_V3)
ngx_uint_t http3;
#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@@ -1271,6 +1278,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
http2 = lsopt->http2 || addr[i].opt.http2;
#endif
#if (NGX_HTTP_V3)
http3 = lsopt->http3 || addr[i].opt.http3;
#endif
if (lsopt->set) {
@@ -1307,6 +1317,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
addr[i].opt.http2 = http2;
#endif
#if (NGX_HTTP_V3)
addr[i].opt.http3 = http3;
#endif
return NGX_OK;
}
@@ -1770,6 +1783,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
}
#endif
ls->type = addr->opt.type;
ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf;
@@ -1803,6 +1817,19 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
#if (NGX_HAVE_REUSEPORT)
ls->reuseport = addr->opt.reuseport;
#endif
ls->wildcard = addr->opt.wildcard;
#if (NGX_HTTP_V3)
ls->quic = addr->opt.http3;
if (ls->quic) {
ngx_rbtree_init(&ls->rbtree, &ls->sentinel,
ngx_quic_rbtree_insert_value);
}
#endif
return ls;
@@ -1836,6 +1863,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
#endif
#if (NGX_HTTP_V2)
addrs[i].conf.http2 = addr[i].opt.http2;
#endif
#if (NGX_HTTP_V3)
addrs[i].conf.http3 = addr[i].opt.http3;
#endif
addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
@@ -1901,6 +1931,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
#endif
#if (NGX_HTTP_V2)
addrs6[i].conf.http2 = addr[i].opt.http2;
#endif
#if (NGX_HTTP_V3)
addrs6[i].conf.http3 = addr[i].opt.http3;
#endif
addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+11 -1
View File
@@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ngx_http_file_cache_t;
typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t;
typedef struct ngx_http_chunked_s ngx_http_chunked_t;
typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t;
typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t;
typedef struct ngx_http_v3_session_s ngx_http_v3_session_t;
typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
@@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
#if (NGX_HTTP_V2)
#include <ngx_http_v2.h>
#endif
#if (NGX_HTTP_V3)
#include <ngx_http_v3.h>
#endif
#if (NGX_HTTP_CACHE)
#include <ngx_http_cache.h>
#endif
@@ -124,6 +129,11 @@ void ngx_http_handler(ngx_http_request_t *r);
void ngx_http_run_posted_requests(ngx_connection_t *c);
ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
ngx_http_posted_request_t *pr);
ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
ngx_str_t *host);
ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
ngx_uint_t alloc);
void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc);
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc);
@@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *);
#endif
#if (NGX_HTTP_V2)
#if (NGX_HTTP_V2 || NGX_HTTP_V3)
ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len,
u_char **dst, ngx_uint_t last, ngx_log_t *log);
size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,
+20
View File
@@ -3989,6 +3989,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.type = SOCK_STREAM;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
@@ -4187,6 +4188,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
if (ngx_strcmp(value[n].data, "http3") == 0) {
#if (NGX_HTTP_V3)
lsopt.http3 = 1;
lsopt.type = SOCK_DGRAM;
continue;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the \"http3\" parameter requires "
"ngx_http_v3_module");
return NGX_CONF_ERROR;
#endif
}
if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) {
if (ngx_strcmp(&value[n].data[13], "on") == 0) {
@@ -4288,6 +4302,12 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_ERROR;
}
#if (NGX_HTTP_SSL && NGX_HTTP_V3)
if (lsopt.ssl && lsopt.http3) {
return "\"ssl\" parameter is incompatible with \"http3\"";
}
#endif
for (n = 0; n < u.naddrs; n++) {
lsopt.sockaddr = u.addrs[n].sockaddr;
lsopt.socklen = u.addrs[n].socklen;
+4
View File
@@ -75,6 +75,7 @@ typedef struct {
unsigned wildcard:1;
unsigned ssl:1;
unsigned http2:1;
unsigned http3:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@@ -86,6 +87,7 @@ typedef struct {
int backlog;
int rcvbuf;
int sndbuf;
int type;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
@@ -237,6 +239,7 @@ struct ngx_http_addr_conf_s {
unsigned ssl:1;
unsigned http2:1;
unsigned http3:1;
unsigned proxy_protocol:1;
};
@@ -266,6 +269,7 @@ typedef struct {
typedef struct {
ngx_int_t family;
ngx_int_t type;
in_port_t port;
ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;
+3 -3
View File
@@ -46,9 +46,9 @@ ngx_module_t ngx_http_header_filter_module = {
};
static u_char ngx_http_server_string[] = "Server: nginx" CRLF;
static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;
static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF;
static u_char ngx_http_server_string[] = "Server: " NGINX_SERVER CRLF;
static u_char ngx_http_server_full_string[] = "Server: " NGINX_SERVER_FULL CRLF;
static u_char ngx_http_server_build_string[] = "Server: " NGINX_SERVER_FULL_BUILD CRLF;
static ngx_str_t ngx_http_status_lines[] = {
+51 -9
View File
@@ -29,10 +29,6 @@ static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r,
static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
ngx_table_elt_t *h, ngx_uint_t offset);
static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
ngx_uint_t alloc);
static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
ngx_str_t *host);
static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
@@ -50,7 +46,6 @@ static void ngx_http_keepalive_handler(ngx_event_t *ev);
static void ngx_http_set_lingering_close(ngx_connection_t *c);
static void ngx_http_lingering_close_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
static void ngx_http_log_request(ngx_http_request_t *r);
static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
@@ -329,6 +324,13 @@ ngx_http_init_connection(ngx_connection_t *c)
}
#endif
#if (NGX_HTTP_V3)
if (hc->addr_conf->http3) {
ngx_http_v3_init(c);
return;
}
#endif
#if (NGX_HTTP_SSL)
{
ngx_http_ssl_srv_conf_t *sscf;
@@ -949,6 +951,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
#ifdef SSL_OP_NO_RENEGOTIATION
SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION);
#endif
#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
#if (NGX_HTTP_V3)
if (c->listening->quic) {
SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
}
#endif
#endif
}
@@ -2095,7 +2105,7 @@ ngx_http_process_request(ngx_http_request_t *r)
}
static ngx_int_t
ngx_int_t
ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
{
u_char *h, ch;
@@ -2187,7 +2197,7 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
}
static ngx_int_t
ngx_int_t
ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
{
ngx_int_t rc;
@@ -2710,6 +2720,13 @@ ngx_http_finalize_connection(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->connection->quic) {
ngx_http_close_request(r, 0);
return;
}
#endif
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->main->count != 1) {
@@ -2924,6 +2941,20 @@ ngx_http_test_reading(ngx_http_request_t *r)
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
if (rev->error) {
c->error = 1;
err = 0;
goto closed;
}
return;
}
#endif
#if (NGX_HAVE_KQUEUE)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
@@ -3588,7 +3619,7 @@ ngx_http_post_action(ngx_http_request_t *r)
}
static void
void
ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_connection_t *c;
@@ -3675,7 +3706,12 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
log->action = "closing request";
if (r->connection->timedout) {
if (r->connection->timedout
#if (NGX_HTTP_V3)
&& r->connection->quic == NULL
#endif
)
{
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->reset_timedout_connection) {
@@ -3748,6 +3784,12 @@ ngx_http_close_connection(ngx_connection_t *c)
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
ngx_http_v3_reset_connection(c);
}
#endif
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif
+7
View File
@@ -24,6 +24,7 @@
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
#define NGX_HTTP_VERSION_20 2000
#define NGX_HTTP_VERSION_30 3000
#define NGX_HTTP_UNKNOWN 0x00000001
#define NGX_HTTP_GET 0x00000002
@@ -323,6 +324,10 @@ typedef struct {
#endif
#endif
#if (NGX_HTTP_V3 || NGX_COMPAT)
ngx_http_v3_session_t *v3_session;
#endif
ngx_chain_t *busy;
ngx_int_t nbusy;
@@ -451,6 +456,7 @@ struct ngx_http_request_s {
ngx_http_connection_t *http_connection;
ngx_http_v2_stream_t *stream;
ngx_http_v3_parse_t *v3_parse;
ngx_http_log_handler_pt log_handler;
@@ -543,6 +549,7 @@ struct ngx_http_request_s {
unsigned request_complete:1;
unsigned request_output:1;
unsigned header_sent:1;
unsigned response_sent:1;
unsigned expect_tested:1;
unsigned root_tested:1;
unsigned done:1;
+28
View File
@@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_http_request_t *r,
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
rc = ngx_http_v3_read_request_body(r);
goto done;
}
#endif
preread = r->header_in->last - r->header_in->pos;
if (preread) {
@@ -238,6 +245,18 @@ ngx_http_read_unbuffered_request_body(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
rc = ngx_http_v3_read_unbuffered_request_body(r);
if (rc == NGX_OK) {
r->reading_body = 0;
}
return rc;
}
#endif
if (r->connection->read->timedout) {
r->connection->timedout = 1;
return NGX_HTTP_REQUEST_TIME_OUT;
@@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
return NGX_OK;
}
#endif
if (ngx_http_test_expect(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
@@ -920,6 +945,9 @@ ngx_http_test_expect(ngx_http_request_t *r)
|| r->http_version < NGX_HTTP_VERSION_11
#if (NGX_HTTP_V2)
|| r->stream != NULL
#endif
#if (NGX_HTTP_V3)
|| r->connection->quic != NULL
#endif
)
{
+3 -3
View File
@@ -19,21 +19,21 @@ static ngx_int_t ngx_http_send_refresh(ngx_http_request_t *r);
static u_char ngx_http_error_full_tail[] =
"<hr><center>" NGINX_VER "</center>" CRLF
"<hr><center>" NGINX_SERVER_FULL "</center>" CRLF
"</body>" CRLF
"</html>" CRLF
;
static u_char ngx_http_error_build_tail[] =
"<hr><center>" NGINX_VER_BUILD "</center>" CRLF
"<hr><center>" NGINX_SERVER_FULL_BUILD "</center>" CRLF
"</body>" CRLF
"</html>" CRLF
;
static u_char ngx_http_error_tail[] =
"<hr><center>nginx</center>" CRLF
"<hr><center>" NGINX_SERVER "</center>" CRLF
"</body>" CRLF
"</html>" CRLF
;
+20
View File
@@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
ngx_http_upstream_init_request(r);
return;
}
#endif
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
@@ -1354,6 +1361,19 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
}
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
if (c->write->error) {
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_CLIENT_CLOSED_REQUEST);
}
return;
}
#endif
#if (NGX_HAVE_KQUEUE)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+8
View File
@@ -240,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
r->out = NULL;
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
if (last) {
r->response_sent = 1;
}
return NGX_OK;
}
@@ -346,6 +350,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
if (last) {
r->response_sent = 1;
}
if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
return NGX_AGAIN;
}
+10 -1
View File
@@ -274,6 +274,8 @@ ngx_http_v2_init(ngx_event_t *rev)
h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE;
h2c->max_hpack_table_size = NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE;
h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module);
h2c->concurrent_pushes = h2scf->concurrent_pushes;
@@ -2282,7 +2284,14 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos,
case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING:
h2c->table_update = 1;
if (value > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) {
h2c->max_hpack_table_size = NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE;
} else {
h2c->max_hpack_table_size = value;
}
h2c->indicate_resize = 1;
h2c->table_update = 1;
break;
default:
+88
View File
@@ -51,6 +51,14 @@
#define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1)
#define NGX_HTTP_V2_DEFAULT_WINDOW 65535
#define HPACK_ENC_HTABLE_SZ 128 /* better to keep a PoT < 64k */
#define HPACK_ENC_HTABLE_ENTRIES ((HPACK_ENC_HTABLE_SZ * 100) / 128)
#define HPACK_ENC_DYNAMIC_KEY_TBL_SZ 10 /* 10 is sufficient for most */
#define HPACK_ENC_MAX_ENTRY 512 /* longest header size to match */
#define NGX_HTTP_V2_DEFAULT_HPACK_TABLE_SIZE 4096
#define NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE 16384 /* < 64k */
#define NGX_HTTP_V2_DEFAULT_WEIGHT 16
@@ -114,6 +122,46 @@ typedef struct {
} ngx_http_v2_hpack_t;
#if (NGX_HTTP_V2_HPACK_ENC)
typedef struct {
uint64_t hash_val;
uint32_t index;
uint16_t pos;
uint16_t klen, vlen;
uint16_t size;
uint16_t next;
} ngx_http_v2_hpack_enc_entry_t;
typedef struct {
uint64_t hash_val;
uint32_t index;
uint16_t pos;
uint16_t klen;
} ngx_http_v2_hpack_name_entry_t;
typedef struct {
size_t size; /* size as defined in RFC 7541 */
uint32_t top; /* the last entry */
uint32_t pos;
uint16_t n_elems; /* number of elements */
uint16_t base; /* index of the oldest entry */
uint16_t last; /* index of the newest entry */
/* hash table for dynamic entries, instead using a generic hash table,
which would be too slow to process a significant amount of headers,
this table is not determenistic, and might ocasionally fail to insert
a value, at the cost of slightly worse compression, but significantly
faster performance */
ngx_http_v2_hpack_enc_entry_t htable[HPACK_ENC_HTABLE_SZ];
ngx_http_v2_hpack_name_entry_t heads[HPACK_ENC_DYNAMIC_KEY_TBL_SZ];
u_char storage[NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE +
HPACK_ENC_MAX_ENTRY];
} ngx_http_v2_hpack_enc_t;
#endif
struct ngx_http_v2_connection_s {
ngx_connection_t *connection;
ngx_http_connection_t *http_connection;
@@ -135,6 +183,8 @@ struct ngx_http_v2_connection_s {
size_t frame_size;
size_t max_hpack_table_size;
ngx_queue_t waiting;
ngx_http_v2_state_t state;
@@ -164,6 +214,11 @@ struct ngx_http_v2_connection_s {
unsigned blocked:1;
unsigned goaway:1;
unsigned push_disabled:1;
unsigned indicate_resize:1;
#if (NGX_HTTP_V2_HPACK_ENC)
ngx_http_v2_hpack_enc_t hpack_enc;
#endif
};
@@ -207,6 +262,8 @@ struct ngx_http_v2_stream_s {
ngx_array_t *cookies;
size_t header_limit;
ngx_pool_t *pool;
unsigned waiting:1;
@@ -413,4 +470,35 @@ u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
u_char *tmp, ngx_uint_t lower);
u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len,
u_char *tmp, ngx_uint_t lower);
u_char *
ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value);
#define ngx_http_v2_write_name(dst, src, len, tmp) \
ngx_http_v2_string_encode(dst, src, len, tmp, 1)
#define ngx_http_v2_write_value(dst, src, len, tmp) \
ngx_http_v2_string_encode(dst, src, len, tmp, 0)
u_char *
ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
u_char *key, size_t key_len, u_char *value, size_t value_len,
u_char *tmp);
void
ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c);
#define ngx_http_v2_write_header_str(key, value) \
ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
(u_char *) value, sizeof(value) - 1, tmp);
#define ngx_http_v2_write_header_tbl(key, val) \
ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
val.data, val.len, tmp);
#define ngx_http_v2_write_header_pot(key, val) \
ngx_http_v2_write_header(h2c, pos, (u_char *) key, sizeof(key) - 1, \
val->data, val->len, tmp);
#endif /* _NGX_HTTP_V2_H_INCLUDED_ */
+2 -2
View File
@@ -10,7 +10,7 @@
#include <ngx_http.h>
static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix,
ngx_uint_t value);
@@ -40,7 +40,7 @@ ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp,
}
static u_char *
u_char *
ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value)
{
if (value < prefix) {
+104 -107
View File
@@ -23,10 +23,53 @@
#define ngx_http_v2_literal_size(h) \
(ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1)
#define ngx_http_v2_indexed(i) (128 + (i))
#define ngx_http_v2_inc_indexed(i) (64 + (i))
#define NGX_HTTP_V2_ENCODE_RAW 0
#define NGX_HTTP_V2_ENCODE_HUFF 0x80
#define NGX_HTTP_V2_AUTHORITY_INDEX 1
#define NGX_HTTP_V2_METHOD_GET_INDEX 2
#define NGX_HTTP_V2_PATH_INDEX 4
#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6
#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7
#define NGX_HTTP_V2_STATUS_INDEX 8
#define NGX_HTTP_V2_STATUS_200_INDEX 8
#define NGX_HTTP_V2_STATUS_204_INDEX 9
#define NGX_HTTP_V2_STATUS_206_INDEX 10
#define NGX_HTTP_V2_STATUS_304_INDEX 11
#define NGX_HTTP_V2_STATUS_400_INDEX 12
#define NGX_HTTP_V2_STATUS_404_INDEX 13
#define NGX_HTTP_V2_STATUS_500_INDEX 14
#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16
#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17
#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28
#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31
#define NGX_HTTP_V2_DATE_INDEX 33
#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44
#define NGX_HTTP_V2_LOCATION_INDEX 46
#define NGX_HTTP_V2_SERVER_INDEX 54
#define NGX_HTTP_V2_USER_AGENT_INDEX 58
#define NGX_HTTP_V2_VARY_INDEX 59
#define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1
static const struct {
u_char *name;
u_char const len;
} push_header[] = {
{ (u_char*)":authority" , 10 },
{ (u_char*)"accept-encoding" , 15 },
{ (u_char*)"accept-language" , 15 },
{ (u_char*)"user-agent" , 10 }
};
typedef struct {
ngx_str_t name;
u_char index;
@@ -148,18 +191,17 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
ngx_http_core_srv_conf_t *cscf;
u_char addr[NGX_SOCKADDR_STRLEN];
static const u_char nginx[5] = "\x84\xaa\x63\x55\xe7";
#if (NGX_HTTP_GZIP)
static const u_char accept_encoding[12] =
"\x8b\x84\x84\x2d\x69\x5b\x05\x44\x3c\x86\xaa\x6f";
#endif
static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER);
static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)];
static size_t nginx_len = ngx_http_v2_literal_size(NGINX_SERVER);
static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_SERVER_FULL);
static size_t nginx_ver_build_len =
ngx_http_v2_literal_size(NGINX_VER_BUILD);
static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)];
ngx_http_v2_literal_size(NGINX_SERVER_FULL_BUILD);
stream = r->stream;
@@ -268,7 +310,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
len += 1 + nginx_ver_build_len;
} else {
len += 1 + sizeof(nginx);
len += 1 + nginx_len;
}
}
@@ -435,7 +477,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
}
tmp = ngx_palloc(r->pool, tmp_len);
pos = ngx_pnalloc(r->pool, len);
pos = ngx_pnalloc(r->pool, len + 15 + 1);
if (pos == NULL || tmp == NULL) {
return NGX_ERROR;
@@ -443,11 +485,16 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
start = pos;
if (h2c->table_update) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 table size update: 0");
*pos++ = (1 << 5) | 0;
h2c->table_update = 0;
h2c = r->stream->connection;
if (h2c->indicate_resize) {
*pos = 32;
pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5),
h2c->max_hpack_table_size);
h2c->indicate_resize = 0;
#if (NGX_HTTP_V2_HPACK_ENC)
ngx_http_v2_table_resize(h2c);
#endif
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
@@ -458,9 +505,9 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
*pos++ = status;
} else {
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX);
*pos++ = NGX_HTTP_V2_ENCODE_RAW | 3;
pos = ngx_sprintf(pos, "%03ui", r->headers_out.status);
ngx_sprintf(pos + 8, "%O3ui", r->headers_out.status);
pos = ngx_http_v2_write_header(h2c, pos, (u_char *)":status",
sizeof(":status") - 1, pos + 8, 3, tmp);
}
if (r->headers_out.server == NULL) {
@@ -468,41 +515,27 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"server: %s\"",
NGINX_VER);
NGINX_SERVER_FULL);
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"server: %s\"",
NGINX_VER_BUILD);
NGINX_SERVER_FULL_BUILD);
} else {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"server: nginx\"");
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"server: %s\"",
NGINX_SERVER);
}
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX);
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
if (nginx_ver[0] == '\0') {
p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER,
sizeof(NGINX_VER) - 1, tmp);
nginx_ver_len = p - nginx_ver;
}
pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len);
pos = ngx_http_v2_write_header_str("server", NGINX_SERVER_FULL);
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
if (nginx_ver_build[0] == '\0') {
p = ngx_http_v2_write_value(nginx_ver_build,
(u_char *) NGINX_VER_BUILD,
sizeof(NGINX_VER_BUILD) - 1, tmp);
nginx_ver_build_len = p - nginx_ver_build;
}
pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len);
pos = ngx_http_v2_write_header_str("server", NGINX_SERVER_FULL_BUILD);
} else {
pos = ngx_cpymem(pos, nginx, sizeof(nginx));
pos = ngx_http_v2_write_header_str("server", NGINX_SERVER);
}
}
@@ -511,14 +544,10 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
"http2 output header: \"date: %V\"",
&ngx_cached_http_time);
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX);
pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data,
ngx_cached_http_time.len, tmp);
pos = ngx_http_v2_write_header_tbl("date", ngx_cached_http_time);
}
if (r->headers_out.content_type.len) {
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX);
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
@@ -544,64 +573,36 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
r->headers_out.content_type.data = p - len;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"content-type: %V\"",
&r->headers_out.content_type);
pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data,
r->headers_out.content_type.len, tmp);
pos = ngx_http_v2_write_header_tbl("content-type",
r->headers_out.content_type);
}
if (r->headers_out.content_length == NULL
&& r->headers_out.content_length_n >= 0)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"content-length: %O\"",
r->headers_out.content_length_n);
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX);
p = pos;
pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n);
*p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1);
p = ngx_sprintf(pos + 15, "%O", r->headers_out.content_length_n);
pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"content-length",
sizeof("content-length") - 1, pos + 15,
p - (pos + 15), tmp);
}
if (r->headers_out.last_modified == NULL
&& r->headers_out.last_modified_time != -1)
{
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX);
ngx_http_time(pos, r->headers_out.last_modified_time);
ngx_http_time(pos + 14, r->headers_out.last_modified_time);
len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"last-modified: %*s\"",
len, pos);
/*
* Date will always be encoded using huffman in the temporary buffer,
* so it's safe here to use src and dst pointing to the same address.
*/
pos = ngx_http_v2_write_value(pos, pos, len, tmp);
pos = ngx_http_v2_write_header(h2c, pos, (u_char *)"last-modified",
sizeof("last-modified") - 1, pos + 14,
len, tmp);
}
if (r->headers_out.location && r->headers_out.location->value.len) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"location: %V\"",
&r->headers_out.location->value);
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX);
pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data,
r->headers_out.location->value.len, tmp);
pos = ngx_http_v2_write_header_tbl("location", r->headers_out.location->value);
}
#if (NGX_HTTP_GZIP)
if (r->gzip_vary) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"vary: Accept-Encoding\"");
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX);
pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding));
pos = ngx_http_v2_write_header_str("vary", "Accept-Encoding");
}
#endif
@@ -624,23 +625,10 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
continue;
}
#if (NGX_DEBUG)
if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
ngx_strlow(tmp, header[i].key.data, header[i].key.len);
pos = ngx_http_v2_write_header(h2c, pos, header[i].key.data,
header[i].key.len, header[i].value.data,
header[i].value.len, tmp);
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 output header: \"%*s: %V\"",
header[i].key.len, tmp, &header[i].value);
}
#endif
*pos++ = 0;
pos = ngx_http_v2_write_name(pos, header[i].key.data,
header[i].key.len, tmp);
pos = ngx_http_v2_write_value(pos, header[i].value.data,
header[i].value.len, tmp);
}
fin = r->header_only
@@ -997,6 +985,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) {
len += binary[i].len;
len += push_header[i].len + 1;
}
pos = ngx_pnalloc(r->pool, len);
@@ -1006,12 +995,17 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
start = pos;
if (h2c->table_update) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 table size update: 0");
*pos++ = (1 << 5) | 0;
h2c->table_update = 0;
}
h2c = r->stream->connection;
if (h2c->indicate_resize) {
*pos = 32;
pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(5),
h2c->max_hpack_table_size);
h2c->indicate_resize = 0;
#if (NGX_HTTP_V2_HPACK_ENC)
ngx_http_v2_table_resize(h2c);
#endif
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":method: GET\"");
@@ -1021,8 +1015,7 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":path: %V\"", path);
*pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX);
pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp);
pos = ngx_http_v2_write_header_pot(":path", path);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \":scheme: %V\"", &r->schema);
@@ -1047,11 +1040,15 @@ ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path,
continue;
}
value = &(*h)->value;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0,
"http2 push header: \"%V: %V\"",
&ph[i].name, &(*h)->value);
pos = ngx_cpymem(pos, binary[i].data, binary[i].len);
pos = ngx_http_v2_write_header(h2c, pos,
push_header[i].name, push_header[i].len, value->data, value->len,
tmp);
}
frame = ngx_http_v2_create_push_frame(r, start, pos);
+431
View File
@@ -361,3 +361,434 @@ ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size)
return NGX_OK;
}
#if (NGX_HTTP_V2_HPACK_ENC)
static ngx_int_t
hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len);
static ngx_int_t
hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash,
uint8_t *key, size_t key_len);
void
ngx_http_v2_table_resize(ngx_http_v2_connection_t *h2c)
{
ngx_http_v2_hpack_enc_entry_t *table;
uint64_t idx;
table = h2c->hpack_enc.htable;
while (h2c->hpack_enc.size > h2c->max_hpack_table_size) {
idx = h2c->hpack_enc.base;
h2c->hpack_enc.base = table[idx].next;
h2c->hpack_enc.size -= table[idx].size;
table[idx].hash_val = 0;
h2c->hpack_enc.n_elems--;
}
}
/* checks if a header is in the hpack table - if so returns the table entry,
otherwise encodes and inserts into the table and returns 0,
if failed to insert into table, returns -1 */
static ngx_int_t
ngx_http_v2_table_encode_strings(ngx_http_v2_connection_t *h2c,
size_t key_len, size_t val_len, uint8_t *key, uint8_t *val,
ngx_int_t *header_idx)
{
uint64_t hash_val, key_hash, idx, lru;
int i;
size_t size = key_len + val_len + 32;
uint8_t *storage = h2c->hpack_enc.storage;
ngx_http_v2_hpack_enc_entry_t *table;
ngx_http_v2_hpack_name_entry_t *name;
*header_idx = NGX_ERROR;
/* step 1: compute the hash value of header */
if (size > HPACK_ENC_MAX_ENTRY || size > h2c->max_hpack_table_size) {
return NGX_ERROR;
}
key_hash = ngx_murmur_hash2_64(key, key_len, 0x01234);
hash_val = ngx_murmur_hash2_64(val, val_len, key_hash);
if (hash_val == 0) {
return NGX_ERROR;
}
/* step 2: check if full header in the table */
idx = hash_val;
i = -1;
while (idx) {
/* at most 8 locations are checked, but most will be done in 1 or 2 */
table = &h2c->hpack_enc.htable[idx % HPACK_ENC_HTABLE_SZ];
if (table->hash_val == hash_val
&& table->klen == key_len
&& table->vlen == val_len
&& ngx_memcmp(key, storage + table->pos, key_len) == 0
&& ngx_memcmp(val, storage + table->pos + key_len, val_len) == 0)
{
return (h2c->hpack_enc.top - table->index) + 61;
}
if (table->hash_val == 0 && i == -1) {
i = idx % HPACK_ENC_HTABLE_SZ;
break;
}
idx >>= 8;
}
/* step 3: check if key is in one of the tables */
*header_idx = hpack_get_static_index(h2c, key, key_len);
if (i == -1) {
return NGX_ERROR;
}
if (*header_idx == NGX_ERROR) {
*header_idx = hpack_get_dynamic_index(h2c, key_hash, key, key_len);
}
/* step 4: store the new entry */
table = h2c->hpack_enc.htable;
if (h2c->hpack_enc.top == 0xffffffff) {
/* just to be on the safe side, avoid overflow */
ngx_memset(&h2c->hpack_enc, 0, sizeof(ngx_http_v2_hpack_enc_t));
}
while ((h2c->hpack_enc.size + size > h2c->max_hpack_table_size)
|| h2c->hpack_enc.n_elems == HPACK_ENC_HTABLE_ENTRIES) {
/* make space for the new entry first */
idx = h2c->hpack_enc.base;
h2c->hpack_enc.base = table[idx].next;
h2c->hpack_enc.size -= table[idx].size;
table[idx].hash_val = 0;
h2c->hpack_enc.n_elems--;
}
table[i] = (ngx_http_v2_hpack_enc_entry_t){.hash_val = hash_val,
.index = h2c->hpack_enc.top,
.pos = h2c->hpack_enc.pos,
.klen = key_len,
.vlen = val_len,
.size = size,
.next = 0};
table[h2c->hpack_enc.last].next = i;
if (h2c->hpack_enc.n_elems == 0) {
h2c->hpack_enc.base = i;
}
h2c->hpack_enc.last = i;
h2c->hpack_enc.top++;
h2c->hpack_enc.size += size;
h2c->hpack_enc.n_elems++;
/* update header name lookup */
if (*header_idx == NGX_ERROR ) {
lru = h2c->hpack_enc.top;
for (i=0; i<HPACK_ENC_DYNAMIC_KEY_TBL_SZ; i++) {
name = &h2c->hpack_enc.heads[i];
if ( name->hash_val == 0 || (name->hash_val == key_hash
&& ngx_memcmp(storage + name->pos, key, key_len) == 0) )
{
name->hash_val = key_hash;
name->pos = h2c->hpack_enc.pos;
name->index = h2c->hpack_enc.top - 1;
break;
}
if (lru > name->index) {
lru = name->index;
idx = i;
}
}
if (i == HPACK_ENC_DYNAMIC_KEY_TBL_SZ) {
name = &h2c->hpack_enc.heads[idx];
name->hash_val = hash_val;
name->pos = h2c->hpack_enc.pos;
name->index = h2c->hpack_enc.top - 1;
}
}
ngx_memcpy(storage + h2c->hpack_enc.pos, key, key_len);
ngx_memcpy(storage + h2c->hpack_enc.pos + key_len, val, val_len);
h2c->hpack_enc.pos += size;
if (h2c->hpack_enc.pos > NGX_HTTP_V2_MAX_HPACK_TABLE_SIZE) {
h2c->hpack_enc.pos = 0;
}
return NGX_OK;
}
u_char *
ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
u_char *key, size_t key_len,
u_char *value, size_t value_len,
u_char *tmp)
{
ngx_int_t idx, header_idx;
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
"http2 output header: %*s: %*s", key_len, key, value_len,
value);
/* attempt to find the value in the dynamic table */
idx = ngx_http_v2_table_encode_strings(h2c, key_len, value_len, key, value,
&header_idx);
if (idx > 0) {
/* positive index indicates success */
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
"http2 hpack encode: Indexed Header Field: %ud", idx);
*pos = 128;
pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(7), idx);
} else {
if (header_idx == NGX_ERROR) { /* if key is not present */
if (idx == NGX_ERROR) { /* if header was not added */
*pos++ = 0;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
"http2 hpack encode: Literal Header Field without"
" Indexing — New Name");
} else { /* if header was added */
*pos++ = 64;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
"http2 hpack encode: Literal Header Field with "
"Incremental Indexing — New Name");
}
pos = ngx_http_v2_write_name(pos, key, key_len, tmp);
} else { /* if key is present */
if (idx == NGX_ERROR) {
*pos = 0;
pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(4), header_idx);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
"http2 hpack encode: Literal Header Field without"
" Indexing — Indexed Name: %ud", header_idx);
} else {
*pos = 64;
pos = ngx_http_v2_write_int(pos, ngx_http_v2_prefix(6), header_idx);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
"http2 hpack encode: Literal Header Field with "
"Incremental Indexing — Indexed Name: %ud", header_idx);
}
}
pos = ngx_http_v2_write_value(pos, value, value_len, tmp);
}
return pos;
}
static ngx_int_t
hpack_get_dynamic_index(ngx_http_v2_connection_t *h2c, uint64_t key_hash,
uint8_t *key, size_t key_len)
{
ngx_http_v2_hpack_name_entry_t *name;
int i;
for (i=0; i<HPACK_ENC_DYNAMIC_KEY_TBL_SZ; i++) {
name = &h2c->hpack_enc.heads[i];
if (name->hash_val == key_hash
&& ngx_memcmp(h2c->hpack_enc.storage + name->pos, key, key_len) == 0)
{
if (name->index >= h2c->hpack_enc.top - h2c->hpack_enc.n_elems) {
return (h2c->hpack_enc.top - name->index) + 61;
}
break;
}
}
return NGX_ERROR;
}
/* decide if a given header is present in the static dictionary, this could be
done in several ways, but it seems the fastest one is "exhaustive" search */
static ngx_int_t
hpack_get_static_index(ngx_http_v2_connection_t *h2c, u_char *val, size_t len)
{
/* the static dictionary of response only headers,
although response headers can be put by origin,
that would be rare */
static const struct {
u_char len;
const u_char val[28];
u_char idx;
} server_headers[] = {
{ 3, "age", 21},//0
{ 3, "via", 60},
{ 4, "date", 33},//2
{ 4, "etag", 34},
{ 4, "link", 45},
{ 4, "vary", 59},
{ 5, "allow", 22},//6
{ 6, "server", 54},//7
{ 7, "expires", 36},//8
{ 7, "refresh", 52},
{ 8, "location", 46},//10
{10, "set-cookie", 55},//11
{11, "retry-after", 53},//12
{12, "content-type", 31},//13
{13, "content-range", 30},//14
{13, "accept-ranges", 18},
{13, "cache-control", 24},
{13, "last-modified", 44},
{14, "content-length", 28},//18
{16, "content-encoding", 26},//19
{16, "content-language", 27},
{16, "content-location", 29},
{16, "www-authenticate", 61},
{17, "transfer-encoding", 57},//23
{18, "proxy-authenticate", 48},//24
{19, "content-disposition", 25},//25
{25, "strict-transport-security", 56},//26
{27, "access-control-allow-origin", 20},//27
{99, "", 99},
}, *header;
/* for a given length, where to start the search
since minimal length is 3, the table has a -3
offset */
static const int8_t start_at[] = {
[3-3] = 0,
[4-3] = 2,
[5-3] = 6,
[6-3] = 7,
[7-3] = 8,
[8-3] = 10,
[9-3] = -1,
[10-3] = 11,
[11-3] = 12,
[12-3] = 13,
[13-3] = 14,
[14-3] = 18,
[15-3] = -1,
[16-3] = 19,
[17-3] = 23,
[18-3] = 24,
[19-3] = 25,
[20-3] = -1,
[21-3] = -1,
[22-3] = -1,
[23-3] = -1,
[24-3] = -1,
[25-3] = 26,
[26-3] = -1,
[27-3] = 27,
};
uint64_t pref;
size_t save_len = len, i;
int8_t start;
/* early exit for out of bounds lengths */
if (len < 3 || len > 27) {
return NGX_ERROR;
}
start = start_at[len - 3];
if (start == -1) {
/* exit for non existent lengths */
return NGX_ERROR;
}
header = &server_headers[start_at[len - 3]];
/* load first 8 bytes of key, for fast comparison */
if (len < 8) {
pref = 0;
if (len >= 4) {
pref = *(uint32_t *)(val + len - 4) | 0x20202020;
len -= 4;
}
while (len > 0) { /* 3 iterations at most */
pref = (pref << 8) ^ (val[len - 1] | 0x20);
len--;
}
} else {
pref = *(uint64_t *)val | 0x2020202020202020;
len -= 8;
}
/* iterate over headers with the right length */
while (header->len == save_len) {
/* quickly compare the first 8 bytes, most tests will end here */
if (pref != *(uint64_t *) header->val) {
header++;
continue;
}
if (len == 0) {
/* len == 0, indicates prefix held the entire key */
return header->idx;
}
/* for longer keys compare the rest */
i = 1 + (save_len + 7) % 8; /* align so we can compare in quadwords */
while (i + 8 <= save_len) { /* 3 iterations at most */
if ( *(uint64_t *)&header->val[i]
!= (*(uint64_t *) &val[i]| 0x2020202020202020) )
{
header++;
i = 0;
break;
}
i += 8;
}
if (i == 0) {
continue;
}
/* found the corresponding entry in the static dictionary */
return header->idx;
}
return NGX_ERROR;
}
#else
u_char *
ngx_http_v2_write_header(ngx_http_v2_connection_t *h2c, u_char *pos,
u_char *key, size_t key_len,
u_char *value, size_t value_len,
u_char *tmp)
{
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0,
"http2 output header: %*s: %*s", key_len, key, value_len,
value);
*pos++ = 64;
pos = ngx_http_v2_write_name(pos, key, key_len, tmp);
pos = ngx_http_v2_write_value(pos, value, value_len, tmp);
return pos;
}
#endif
+123
View File
@@ -0,0 +1,123 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static void ngx_http_v3_keepalive_handler(ngx_event_t *ev);
static void ngx_http_v3_cleanup_session(void *data);
ngx_int_t
ngx_http_v3_init_session(ngx_connection_t *c)
{
ngx_connection_t *pc;
ngx_pool_cleanup_t *cln;
ngx_http_connection_t *hc;
ngx_http_v3_session_t *h3c;
pc = c->quic->parent;
hc = pc->data;
if (hc->v3_session) {
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t));
if (h3c == NULL) {
goto failed;
}
h3c->max_push_id = (uint64_t) -1;
h3c->goaway_push_id = (uint64_t) -1;
ngx_queue_init(&h3c->blocked);
ngx_queue_init(&h3c->pushing);
h3c->keepalive.log = pc->log;
h3c->keepalive.data = pc;
h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
h3c->keepalive.cancelable = 1;
h3c->table.send_insert_count.log = pc->log;
h3c->table.send_insert_count.data = pc;
h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler;
cln = ngx_pool_cleanup_add(pc->pool, 0);
if (cln == NULL) {
goto failed;
}
cln->handler = ngx_http_v3_cleanup_session;
cln->data = h3c;
hc->v3_session = h3c;
return ngx_http_v3_send_settings(c);
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
"failed to create http3 session");
return NGX_ERROR;
}
static void
ngx_http_v3_keepalive_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
c = ev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
"keepalive timeout");
}
static void
ngx_http_v3_cleanup_session(void *data)
{
ngx_http_v3_session_t *h3c = data;
ngx_http_v3_cleanup_table(h3c);
if (h3c->keepalive.timer_set) {
ngx_del_timer(&h3c->keepalive);
}
if (h3c->table.send_insert_count.posted) {
ngx_delete_posted_event(&h3c->table.send_insert_count);
}
}
ngx_int_t
ngx_http_v3_check_flood(ngx_connection_t *c)
{
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(c);
if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
"HTTP/3 flood detected");
return NGX_ERROR;
}
return NGX_OK;
}
+166
View File
@@ -0,0 +1,166 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_H_INCLUDED_
#define _NGX_HTTP_V3_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_http_v3_parse.h>
#include <ngx_http_v3_encode.h>
#include <ngx_http_v3_uni.h>
#include <ngx_http_v3_table.h>
#define NGX_HTTP_V3_ALPN_PROTO "\x02h3"
#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop"
#define NGX_HTTP_V3_VARLEN_INT_LEN 4
#define NGX_HTTP_V3_PREFIX_INT_LEN 11
#define NGX_HTTP_V3_STREAM_CONTROL 0x00
#define NGX_HTTP_V3_STREAM_PUSH 0x01
#define NGX_HTTP_V3_STREAM_ENCODER 0x02
#define NGX_HTTP_V3_STREAM_DECODER 0x03
#define NGX_HTTP_V3_FRAME_DATA 0x00
#define NGX_HTTP_V3_FRAME_HEADERS 0x01
#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03
#define NGX_HTTP_V3_FRAME_SETTINGS 0x04
#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05
#define NGX_HTTP_V3_FRAME_GOAWAY 0x07
#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d
#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01
#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06
#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07
#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096
#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0
#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1
#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2
#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3
#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4
#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5
#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6
#define NGX_HTTP_V3_MAX_UNI_STREAMS 3
/* HTTP/3 errors */
#define NGX_HTTP_V3_ERR_NO_ERROR 0x100
#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101
#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102
#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103
#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104
#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105
#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106
#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107
#define NGX_HTTP_V3_ERR_ID_ERROR 0x108
#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109
#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a
#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b
#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c
#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d
#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f
#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110
/* QPACK errors */
#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200
#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201
#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202
#define ngx_http_quic_get_connection(c) \
((ngx_http_connection_t *) ((c)->quic ? (c)->quic->parent->data \
: (c)->data))
#define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session
#define ngx_http_v3_get_module_loc_conf(c, module) \
ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \
module)
#define ngx_http_v3_get_module_srv_conf(c, module) \
ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \
module)
#define ngx_http_v3_finalize_connection(c, code, reason) \
ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \
code, reason)
#define ngx_http_v3_shutdown_connection(c, code, reason) \
ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \
code, reason)
typedef struct {
size_t max_table_capacity;
ngx_uint_t max_blocked_streams;
ngx_uint_t max_concurrent_pushes;
ngx_uint_t max_concurrent_streams;
#if (NGX_HTTP_V3_HQ)
ngx_flag_t hq;
#endif
ngx_quic_conf_t quic;
} ngx_http_v3_srv_conf_t;
typedef struct {
ngx_flag_t push_preload;
ngx_flag_t push;
ngx_array_t *pushes;
} ngx_http_v3_loc_conf_t;
struct ngx_http_v3_parse_s {
size_t header_limit;
ngx_http_v3_parse_headers_t headers;
ngx_http_v3_parse_data_t body;
ngx_array_t *cookies;
};
struct ngx_http_v3_session_s {
ngx_http_v3_dynamic_table_t table;
ngx_event_t keepalive;
ngx_uint_t nrequests;
ngx_queue_t blocked;
ngx_uint_t nblocked;
ngx_queue_t pushing;
ngx_uint_t npushing;
uint64_t next_push_id;
uint64_t max_push_id;
uint64_t goaway_push_id;
off_t total_bytes;
off_t payload_bytes;
ngx_uint_t goaway; /* unsigned goaway:1; */
ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
};
void ngx_http_v3_init(ngx_connection_t *c);
void ngx_http_v3_reset_connection(ngx_connection_t *c);
ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c);
ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c);
ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r);
ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r);
extern ngx_module_t ngx_http_v3_module;
#endif /* _NGX_HTTP_V3_H_INCLUDED_ */
+304
View File
@@ -0,0 +1,304 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
uintptr_t
ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value)
{
if (value <= 63) {
if (p == NULL) {
return 1;
}
*p++ = value;
return (uintptr_t) p;
}
if (value <= 16383) {
if (p == NULL) {
return 2;
}
*p++ = 0x40 | (value >> 8);
*p++ = value;
return (uintptr_t) p;
}
if (value <= 1073741823) {
if (p == NULL) {
return 4;
}
*p++ = 0x80 | (value >> 24);
*p++ = (value >> 16);
*p++ = (value >> 8);
*p++ = value;
return (uintptr_t) p;
}
if (p == NULL) {
return 8;
}
*p++ = 0xc0 | (value >> 56);
*p++ = (value >> 48);
*p++ = (value >> 40);
*p++ = (value >> 32);
*p++ = (value >> 24);
*p++ = (value >> 16);
*p++ = (value >> 8);
*p++ = value;
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix)
{
ngx_uint_t thresh, n;
thresh = (1 << prefix) - 1;
if (value < thresh) {
if (p == NULL) {
return 1;
}
*p++ |= value;
return (uintptr_t) p;
}
value -= thresh;
if (p == NULL) {
for (n = 2; value >= 128; n++) {
value >>= 7;
}
return n;
}
*p++ |= thresh;
while (value >= 128) {
*p++ = 0x80 | value;
value >>= 7;
}
*p++ = value;
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count,
ngx_uint_t sign, ngx_uint_t delta_base)
{
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8)
+ ngx_http_v3_encode_prefix_int(NULL, delta_base, 7);
}
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8);
*p = sign ? 0x80 : 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7);
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index)
{
/* Indexed Field Line */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 6);
}
*p = dynamic ? 0x80 : 0xc0;
return ngx_http_v3_encode_prefix_int(p, index, 6);
}
uintptr_t
ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index,
u_char *data, size_t len)
{
size_t hlen;
u_char *p1, *p2;
/* Literal Field Line With Name Reference */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 4)
+ ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ len;
}
*p = dynamic ? 0x40 : 0x50;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4);
p1 = p;
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
if (data) {
p2 = p;
hlen = ngx_http_huff_encode(data, len, p, 0);
if (hlen) {
p = p1;
*p = 0x80;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
p = ngx_cpymem(p, data, len);
}
}
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value)
{
size_t hlen;
u_char *p1, *p2;
/* Literal Field Line With Literal Name */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, name->len, 3)
+ name->len
+ ngx_http_v3_encode_prefix_int(NULL, value->len, 7)
+ value->len;
}
p1 = p;
*p = 0x20;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3);
p2 = p;
hlen = ngx_http_huff_encode(name->data, name->len, p, 1);
if (hlen) {
p = p1;
*p = 0x28;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
ngx_strlow(p, name->data, name->len);
p += name->len;
}
p1 = p;
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);
p2 = p;
hlen = ngx_http_huff_encode(value->data, value->len, p, 0);
if (hlen) {
p = p1;
*p = 0x80;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
p = ngx_cpymem(p, value->data, value->len);
}
return (uintptr_t) p;
}
uintptr_t
ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index)
{
/* Indexed Field Line With Post-Base Index */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 4);
}
*p = 0x10;
return ngx_http_v3_encode_prefix_int(p, index, 4);
}
uintptr_t
ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data,
size_t len)
{
size_t hlen;
u_char *p1, *p2;
/* Literal Field Line With Post-Base Name Reference */
if (p == NULL) {
return ngx_http_v3_encode_prefix_int(NULL, index, 3)
+ ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ len;
}
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3);
p1 = p;
*p = 0;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
if (data) {
p2 = p;
hlen = ngx_http_huff_encode(data, len, p, 0);
if (hlen) {
p = p1;
*p = 0x80;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
if (p != p2) {
ngx_memmove(p, p2, hlen);
}
p += hlen;
} else {
p = ngx_cpymem(p, data, len);
}
}
return (uintptr_t) p;
}
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_
#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value);
uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value,
ngx_uint_t prefix);
uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p,
ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base);
uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic,
ngx_uint_t index);
uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic,
ngx_uint_t index, u_char *data, size_t len);
uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name,
ngx_str_t *value);
uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index);
uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index,
u_char *data, size_t len);
#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */
+1536
View File
@@ -0,0 +1,1536 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/* static table indices */
#define NGX_HTTP_V3_HEADER_AUTHORITY 0
#define NGX_HTTP_V3_HEADER_PATH_ROOT 1
#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4
#define NGX_HTTP_V3_HEADER_DATE 6
#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10
#define NGX_HTTP_V3_HEADER_LOCATION 12
#define NGX_HTTP_V3_HEADER_METHOD_GET 17
#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22
#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23
#define NGX_HTTP_V3_HEADER_STATUS_200 25
#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31
#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53
#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59
#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72
#define NGX_HTTP_V3_HEADER_SERVER 92
#define NGX_HTTP_V3_HEADER_USER_AGENT 95
typedef struct {
ngx_chain_t *free;
ngx_chain_t *busy;
} ngx_http_v3_filter_ctx_t;
static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r,
ngx_chain_t ***out);
static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r,
ngx_str_t *path, ngx_chain_t ***out);
static ngx_int_t ngx_http_v3_create_push_request(
ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id);
static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r,
const char *name, ngx_str_t *value);
static void ngx_http_v3_push_request_handler(ngx_event_t *ev);
static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r,
ngx_str_t *path, uint64_t push_id);
static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r,
ngx_chain_t *in);
static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r,
ngx_http_v3_filter_ctx_t *ctx);
static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf);
static ngx_http_module_t ngx_http_v3_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_v3_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_v3_filter_module = {
NGX_MODULE_V1,
&ngx_http_v3_filter_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static ngx_int_t
ngx_http_v3_header_filter(ngx_http_request_t *r)
{
u_char *p;
size_t len, n;
ngx_buf_t *b;
ngx_str_t host, location;
ngx_uint_t i, port;
ngx_chain_t *out, *hl, *cl, **ll;
ngx_list_part_t *part;
ngx_table_elt_t *header;
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_v3_filter_ctx_t *ctx;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
u_char addr[NGX_SOCKADDR_STRLEN];
if (r->http_version != NGX_HTTP_VERSION_30) {
return ngx_http_next_header_filter(r);
}
if (r->header_sent) {
return NGX_OK;
}
r->header_sent = 1;
if (r != r->main) {
return NGX_OK;
}
h3c = ngx_http_v3_get_session(r->connection);
if (r->method == NGX_HTTP_HEAD) {
r->header_only = 1;
}
if (r->headers_out.last_modified_time != -1) {
if (r->headers_out.status != NGX_HTTP_OK
&& r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
&& r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
{
r->headers_out.last_modified_time = -1;
r->headers_out.last_modified = NULL;
}
}
if (r->headers_out.status == NGX_HTTP_NO_CONTENT) {
r->header_only = 1;
ngx_str_null(&r->headers_out.content_type);
r->headers_out.last_modified_time = -1;
r->headers_out.last_modified = NULL;
r->headers_out.content_length = NULL;
r->headers_out.content_length_n = -1;
}
if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
r->header_only = 1;
}
c = r->connection;
out = NULL;
ll = &out;
if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0
&& r->method != NGX_HTTP_HEAD)
{
if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) {
return NGX_ERROR;
}
}
len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
if (r->headers_out.status == NGX_HTTP_OK) {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_STATUS_200);
} else {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_STATUS_200,
NULL, 3);
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->headers_out.server == NULL) {
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
n = sizeof(NGINX_SERVER_FULL) - 1;
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
n = sizeof(NGINX_SERVER_FULL_BUILD) - 1;
} else {
n = sizeof(NGINX_SERVER) - 1;
}
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_SERVER,
NULL, n);
}
if (r->headers_out.date == NULL) {
len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE,
NULL, ngx_cached_http_time.len);
}
if (r->headers_out.content_type.len) {
n = r->headers_out.content_type.len;
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
}
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
NULL, n);
}
if (r->headers_out.content_length == NULL) {
if (r->headers_out.content_length_n > 0) {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
NULL, NGX_OFF_T_LEN);
} else if (r->headers_out.content_length_n == 0) {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
}
}
if (r->headers_out.last_modified == NULL
&& r->headers_out.last_modified_time != -1)
{
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL,
sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
}
if (r->headers_out.location && r->headers_out.location->value.len) {
if (r->headers_out.location->value.data[0] == '/'
&& clcf->absolute_redirect)
{
if (clcf->server_name_in_redirect) {
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
host = cscf->server_name;
} else if (r->headers_in.server.len) {
host = r->headers_in.server;
} else {
host.len = NGX_SOCKADDR_STRLEN;
host.data = addr;
if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
return NGX_ERROR;
}
}
port = ngx_inet_get_port(c->local_sockaddr);
location.len = sizeof("https://") - 1 + host.len
+ r->headers_out.location->value.len;
if (clcf->port_in_redirect) {
port = (port == 443) ? 0 : port;
} else {
port = 0;
}
if (port) {
location.len += sizeof(":65535") - 1;
}
location.data = ngx_pnalloc(r->pool, location.len);
if (location.data == NULL) {
return NGX_ERROR;
}
p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1);
p = ngx_cpymem(p, host.data, host.len);
if (port) {
p = ngx_sprintf(p, ":%ui", port);
}
p = ngx_cpymem(p, r->headers_out.location->value.data,
r->headers_out.location->value.len);
/* update r->headers_out.location->value for possible logging */
r->headers_out.location->value.len = p - location.data;
r->headers_out.location->value.data = location.data;
ngx_str_set(&r->headers_out.location->key, "Location");
}
r->headers_out.location->hash = 0;
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_LOCATION, NULL,
r->headers_out.location->value.len);
}
#if (NGX_HTTP_GZIP)
if (r->gzip_vary) {
if (clcf->gzip_vary) {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
} else {
r->gzip_vary = 0;
}
}
#endif
part = &r->headers_out.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
len += ngx_http_v3_encode_field_l(NULL, &header[i].key,
&header[i].value);
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last,
0, 0, 0);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \":status: %03ui\"",
r->headers_out.status);
if (r->headers_out.status == NGX_HTTP_OK) {
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_STATUS_200);
} else {
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_STATUS_200,
NULL, 3);
b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status);
}
if (r->headers_out.server == NULL) {
if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
p = (u_char *) NGINX_SERVER;
n = sizeof(NGINX_SERVER) - 1;
} else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
p = (u_char *) NGINX_SERVER_FULL_BUILD;
n = sizeof(NGINX_SERVER_FULL_BUILD) - 1;
} else {
p = (u_char *) NGINX_SERVER;
n = sizeof(NGINX_SERVER) - 1;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"server: %*s\"", n, p);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_SERVER,
p, n);
}
if (r->headers_out.date == NULL) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"date: %V\"",
&ngx_cached_http_time);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_DATE,
ngx_cached_http_time.data,
ngx_cached_http_time.len);
}
if (r->headers_out.content_type.len) {
if (r->headers_out.content_type_len == r->headers_out.content_type.len
&& r->headers_out.charset.len)
{
n = r->headers_out.content_type.len + sizeof("; charset=") - 1
+ r->headers_out.charset.len;
p = ngx_pnalloc(r->pool, n);
if (p == NULL) {
return NGX_ERROR;
}
p = ngx_cpymem(p, r->headers_out.content_type.data,
r->headers_out.content_type.len);
p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
p = ngx_cpymem(p, r->headers_out.charset.data,
r->headers_out.charset.len);
/* updated r->headers_out.content_type is also needed for logging */
r->headers_out.content_type.len = n;
r->headers_out.content_type.data = p - n;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"content-type: %V\"",
&r->headers_out.content_type);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
r->headers_out.content_type.data,
r->headers_out.content_type.len);
}
if (r->headers_out.content_length == NULL
&& r->headers_out.content_length_n >= 0)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"content-length: %O\"",
r->headers_out.content_length_n);
if (r->headers_out.content_length_n > 0) {
p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n);
n = p - b->last;
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
NULL, n);
b->last = ngx_sprintf(b->last, "%O",
r->headers_out.content_length_n);
} else {
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
}
}
if (r->headers_out.last_modified == NULL
&& r->headers_out.last_modified_time != -1)
{
n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
p = ngx_pnalloc(r->pool, n);
if (p == NULL) {
return NGX_ERROR;
}
ngx_http_time(p, r->headers_out.last_modified_time);
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"last-modified: %*s\"", n, p);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_LAST_MODIFIED,
p, n);
}
if (r->headers_out.location && r->headers_out.location->value.len) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"location: %V\"",
&r->headers_out.location->value);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_LOCATION,
r->headers_out.location->value.data,
r->headers_out.location->value.len);
}
#if (NGX_HTTP_GZIP)
if (r->gzip_vary) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"vary: Accept-Encoding\"");
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
}
#endif
part = &r->headers_out.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 output header: \"%V: %V\"",
&header[i].key, &header[i].value);
b->last = (u_char *) ngx_http_v3_encode_field_l(b->last,
&header[i].key,
&header[i].value);
}
if (r->header_only) {
b->last_buf = 1;
}
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = NULL;
n = b->last - b->pos;
h3c->payload_bytes += n;
len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS)
+ ngx_http_v3_encode_varlen_int(NULL, n);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
NGX_HTTP_V3_FRAME_HEADERS);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
hl = ngx_alloc_chain_link(r->pool);
if (hl == NULL) {
return NGX_ERROR;
}
hl->buf = b;
hl->next = cl;
*ll = hl;
ll = &cl->next;
if (r->headers_out.content_length_n >= 0
&& !r->header_only && !r->expect_trailers)
{
len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA)
+ ngx_http_v3_encode_varlen_int(NULL,
r->headers_out.content_length_n);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
NGX_HTTP_V3_FRAME_DATA);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
r->headers_out.content_length_n);
h3c->payload_bytes += r->headers_out.content_length_n;
h3c->total_bytes += r->headers_out.content_length_n;
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = NULL;
*ll = cl;
} else {
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module);
}
for (cl = out; cl; cl = cl->next) {
h3c->total_bytes += cl->buf->last - cl->buf->pos;
}
return ngx_http_write_filter(r, out);
}
static ngx_int_t
ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out)
{
u_char *start, *end, *last;
ngx_str_t path;
ngx_int_t rc;
ngx_uint_t i, push;
ngx_table_elt_t *h;
ngx_http_v3_loc_conf_t *h3lcf;
ngx_http_complex_value_t *pushes;
h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module);
if (h3lcf->pushes) {
pushes = h3lcf->pushes->elts;
for (i = 0; i < h3lcf->pushes->nelts; i++) {
if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) {
return NGX_ERROR;
}
if (path.len == 0) {
continue;
}
if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) {
continue;
}
rc = ngx_http_v3_push_resource(r, &path, out);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (rc == NGX_ABORT) {
return NGX_OK;
}
/* NGX_OK, NGX_DECLINED */
}
}
if (!h3lcf->push_preload) {
return NGX_OK;
}
for (h = r->headers_out.link; h; h = h->next) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 parse link: \"%V\"", &h->value);
start = h->value.data;
end = h->value.data + h->value.len;
next_link:
while (start < end && *start == ' ') { start++; }
if (start == end || *start++ != '<') {
continue;
}
while (start < end && *start == ' ') { start++; }
for (last = start; last < end && *last != '>'; last++) {
/* void */
}
if (last == start || last == end) {
continue;
}
path.len = last - start;
path.data = start;
start = last + 1;
while (start < end && *start == ' ') { start++; }
if (start == end) {
continue;
}
if (*start == ',') {
start++;
goto next_link;
}
if (*start++ != ';') {
continue;
}
last = ngx_strlchr(start, end, ',');
if (last == NULL) {
last = end;
}
push = 0;
for ( ;; ) {
while (start < last && *start == ' ') { start++; }
if (last - start >= 6
&& ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0)
{
start += 6;
if (start == last || *start == ' ' || *start == ';') {
push = 0;
break;
}
goto next_param;
}
if (last - start >= 11
&& ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0)
{
start += 11;
if (start == last || *start == ' ' || *start == ';') {
push = 1;
}
goto next_param;
}
if (last - start >= 4
&& ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0)
{
start += 4;
while (start < last && *start == ' ') { start++; }
if (start == last || *start++ != '"') {
goto next_param;
}
for ( ;; ) {
while (start < last && *start == ' ') { start++; }
if (last - start >= 7
&& ngx_strncasecmp(start, (u_char *) "preload", 7) == 0)
{
start += 7;
if (start < last && (*start == ' ' || *start == '"')) {
push = 1;
break;
}
}
while (start < last && *start != ' ' && *start != '"') {
start++;
}
if (start == last) {
break;
}
if (*start == '"') {
break;
}
start++;
}
}
next_param:
start = ngx_strlchr(start, last, ';');
if (start == NULL) {
break;
}
start++;
}
if (push) {
while (path.len && path.data[path.len - 1] == ' ') {
path.len--;
}
}
if (push && path.len
&& !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/'))
{
rc = ngx_http_v3_push_resource(r, &path, out);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (rc == NGX_ABORT) {
return NGX_OK;
}
/* NGX_OK, NGX_DECLINED */
}
if (last < end) {
start = last + 1;
goto next_link;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path,
ngx_chain_t ***ll)
{
uint64_t push_id;
ngx_int_t rc;
ngx_chain_t *cl;
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_v3_srv_conf_t *h3scf;
c = r->connection;
h3c = ngx_http_v3_get_session(c);
h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 push \"%V\" pushing:%ui/%ui id:%uL/%L",
path, h3c->npushing, h3scf->max_concurrent_pushes,
h3c->next_push_id, h3c->max_push_id);
if (!ngx_path_separator(path->data[0])) {
ngx_log_error(NGX_LOG_WARN, c->log, 0,
"non-absolute path \"%V\" not pushed", path);
return NGX_DECLINED;
}
if (h3c->max_push_id == (uint64_t) -1
|| h3c->next_push_id > h3c->max_push_id)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 abort pushes due to max_push_id");
return NGX_ABORT;
}
if (h3c->goaway_push_id != (uint64_t) -1) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 abort pushes due to goaway");
return NGX_ABORT;
}
if (h3c->npushing >= h3scf->max_concurrent_pushes) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 abort pushes due to max_concurrent_pushes");
return NGX_ABORT;
}
if (r->headers_in.server.len == 0) {
return NGX_ABORT;
}
push_id = h3c->next_push_id++;
rc = ngx_http_v3_create_push_request(r, path, push_id);
if (rc != NGX_OK) {
return rc;
}
cl = ngx_http_v3_create_push_promise(r, path, push_id);
if (cl == NULL) {
return NGX_ERROR;
}
for (**ll = cl; **ll; *ll = &(**ll)->next);
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path,
uint64_t push_id)
{
ngx_connection_t *c, *pc;
ngx_http_request_t *r;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc, *phc;
ngx_http_core_srv_conf_t *cscf;
pc = pr->connection;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"http3 create push request id:%uL", push_id);
c = ngx_http_v3_create_push_stream(pc, push_id);
if (c == NULL) {
return NGX_ABORT;
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t));
if (hc == NULL) {
ngx_http_close_connection(c);
return NGX_ERROR;
}
phc = ngx_http_quic_get_connection(pc);
ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t));
c->data = hc;
ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
if (ctx == NULL) {
ngx_http_close_connection(c);
return NGX_ERROR;
}
ctx->connection = c;
ctx->request = NULL;
ctx->current_request = NULL;
c->log->handler = pc->log->handler;
c->log->data = ctx;
c->log->action = "processing pushed request headers";
c->log_error = NGX_ERROR_INFO;
r = ngx_http_create_request(c);
if (r == NULL) {
ngx_http_close_connection(c);
return NGX_ERROR;
}
c->data = r;
ngx_str_set(&r->http_protocol, "HTTP/3.0");
r->http_version = NGX_HTTP_VERSION_30;
r->method_name = ngx_http_core_get_method;
r->method = NGX_HTTP_GET;
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
r->header_in = ngx_create_temp_buf(r->pool,
cscf->client_header_buffer_size);
if (r->header_in == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (ngx_list_init(&r->headers_in.headers, r->pool, 4,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
r->schema.data = ngx_pstrdup(r->pool, &pr->schema);
if (r->schema.data == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
r->schema.len = pr->schema.len;
r->uri_start = ngx_pstrdup(r->pool, path);
if (r->uri_start == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
r->uri_end = r->uri_start + path->len;
if (ngx_http_parse_uri(r) != NGX_OK) {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
if (ngx_http_process_request_uri(r) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server)
!= NGX_OK)
{
return NGX_ERROR;
}
if (pr->headers_in.accept_encoding) {
if (ngx_http_v3_set_push_header(r, "accept-encoding",
&pr->headers_in.accept_encoding->value)
!= NGX_OK)
{
return NGX_ERROR;
}
}
if (pr->headers_in.accept_language) {
if (ngx_http_v3_set_push_header(r, "accept-language",
&pr->headers_in.accept_language->value)
!= NGX_OK)
{
return NGX_ERROR;
}
}
if (pr->headers_in.user_agent) {
if (ngx_http_v3_set_push_header(r, "user-agent",
&pr->headers_in.user_agent->value)
!= NGX_OK)
{
return NGX_ERROR;
}
}
c->read->handler = ngx_http_v3_push_request_handler;
c->read->handler = ngx_http_v3_push_request_handler;
ngx_post_event(c->read, &ngx_posted_events);
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name,
ngx_str_t *value)
{
u_char *p;
ngx_table_elt_t *h;
ngx_http_header_t *hh;
ngx_http_core_main_conf_t *cmcf;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 push header \"%s\": \"%V\"", name, value);
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
p = ngx_pnalloc(r->pool, value->len + 1);
if (p == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
ngx_memcpy(p, value->data, value->len);
p[value->len] = '\0';
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
h->key.data = (u_char *) name;
h->key.len = ngx_strlen(name);
h->hash = ngx_hash_key(h->key.data, h->key.len);
h->lowcase_key = (u_char *) name;
h->value.data = p;
h->value.len = value->len;
hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len);
if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
static void
ngx_http_v3_push_request_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_http_request_t *r;
c = ev->data;
r = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler");
ngx_http_process_request(r);
}
static ngx_chain_t *
ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path,
uint64_t push_id)
{
size_t n, len;
ngx_buf_t *b;
ngx_chain_t *hl, *cl;
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(r->connection);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 create push promise id:%uL", push_id);
len = ngx_http_v3_encode_varlen_int(NULL, push_id);
len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_METHOD_GET);
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_AUTHORITY,
NULL, r->headers_in.server.len);
if (path->len == 1 && path->data[0] == '/') {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_PATH_ROOT);
} else {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_PATH_ROOT,
NULL, path->len);
}
if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
} else if (r->schema.len == 4
&& ngx_strncmp(r->schema.data, "http", 4) == 0)
{
len += ngx_http_v3_encode_field_ri(NULL, 0,
NGX_HTTP_V3_HEADER_SCHEME_HTTP);
} else {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_SCHEME_HTTP,
NULL, r->schema.len);
}
if (r->headers_in.accept_encoding) {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL,
r->headers_in.accept_encoding->value.len);
}
if (r->headers_in.accept_language) {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL,
r->headers_in.accept_language->value.len);
}
if (r->headers_in.user_agent) {
len += ngx_http_v3_encode_field_lri(NULL, 0,
NGX_HTTP_V3_HEADER_USER_AGENT, NULL,
r->headers_in.user_agent->value.len);
}
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NULL;
}
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id);
b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last,
0, 0, 0);
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_METHOD_GET);
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_AUTHORITY,
r->headers_in.server.data,
r->headers_in.server.len);
if (path->len == 1 && path->data[0] == '/') {
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_PATH_ROOT);
} else {
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_PATH_ROOT,
path->data, path->len);
}
if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
} else if (r->schema.len == 4
&& ngx_strncmp(r->schema.data, "http", 4) == 0)
{
b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
NGX_HTTP_V3_HEADER_SCHEME_HTTP);
} else {
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_SCHEME_HTTP,
r->schema.data, r->schema.len);
}
if (r->headers_in.accept_encoding) {
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_ACCEPT_ENCODING,
r->headers_in.accept_encoding->value.data,
r->headers_in.accept_encoding->value.len);
}
if (r->headers_in.accept_language) {
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE,
r->headers_in.accept_language->value.data,
r->headers_in.accept_language->value.len);
}
if (r->headers_in.user_agent) {
b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
NGX_HTTP_V3_HEADER_USER_AGENT,
r->headers_in.user_agent->value.data,
r->headers_in.user_agent->value.len);
}
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NULL;
}
cl->buf = b;
cl->next = NULL;
n = b->last - b->pos;
h3c->payload_bytes += n;
len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE)
+ ngx_http_v3_encode_varlen_int(NULL, n);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NULL;
}
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
NGX_HTTP_V3_FRAME_PUSH_PROMISE);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
hl = ngx_alloc_chain_link(r->pool);
if (hl == NULL) {
return NULL;
}
hl->buf = b;
hl->next = cl;
return hl;
}
static ngx_int_t
ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
u_char *chunk;
off_t size;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t *out, *cl, *tl, **ll;
ngx_http_v3_session_t *h3c;
ngx_http_v3_filter_ctx_t *ctx;
if (in == NULL) {
return ngx_http_next_body_filter(r, in);
}
ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module);
if (ctx == NULL) {
return ngx_http_next_body_filter(r, in);
}
h3c = ngx_http_v3_get_session(r->connection);
out = NULL;
ll = &out;
size = 0;
cl = in;
for ( ;; ) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 chunk: %O", ngx_buf_size(cl->buf));
size += ngx_buf_size(cl->buf);
if (cl->buf->flush
|| cl->buf->sync
|| ngx_buf_in_memory(cl->buf)
|| cl->buf->in_file)
{
tl = ngx_alloc_chain_link(r->pool);
if (tl == NULL) {
return NGX_ERROR;
}
tl->buf = cl->buf;
*ll = tl;
ll = &tl->next;
}
if (cl->next == NULL) {
break;
}
cl = cl->next;
}
if (size) {
tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (tl == NULL) {
return NGX_ERROR;
}
b = tl->buf;
chunk = b->start;
if (chunk == NULL) {
chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
if (chunk == NULL) {
return NGX_ERROR;
}
b->start = chunk;
b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
}
b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
b->memory = 0;
b->temporary = 1;
b->pos = chunk;
b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk,
NGX_HTTP_V3_FRAME_DATA);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size);
tl->next = out;
out = tl;
h3c->payload_bytes += size;
}
if (cl->buf->last_buf) {
tl = ngx_http_v3_create_trailers(r, ctx);
if (tl == NULL) {
return NGX_ERROR;
}
cl->buf->last_buf = 0;
*ll = tl;
} else {
*ll = NULL;
}
for (cl = out; cl; cl = cl->next) {
h3c->total_bytes += cl->buf->last - cl->buf->pos;
}
rc = ngx_http_next_body_filter(r, out);
ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
(ngx_buf_tag_t) &ngx_http_v3_filter_module);
return rc;
}
static ngx_chain_t *
ngx_http_v3_create_trailers(ngx_http_request_t *r,
ngx_http_v3_filter_ctx_t *ctx)
{
size_t len, n;
u_char *p;
ngx_buf_t *b;
ngx_uint_t i;
ngx_chain_t *cl, *hl;
ngx_list_part_t *part;
ngx_table_elt_t *header;
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(r->connection);
len = 0;
part = &r->headers_out.trailers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
len += ngx_http_v3_encode_field_l(NULL, &header[i].key,
&header[i].value);
}
cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (cl == NULL) {
return NULL;
}
b = cl->buf;
b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
b->memory = 0;
b->last_buf = 1;
if (len == 0) {
b->temporary = 0;
b->pos = b->last = NULL;
return cl;
}
b->temporary = 1;
len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
b->pos = ngx_palloc(r->pool, len);
if (b->pos == NULL) {
return NULL;
}
b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos,
0, 0, 0);
part = &r->headers_out.trailers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].hash == 0) {
continue;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 output trailer: \"%V: %V\"",
&header[i].key, &header[i].value);
b->last = (u_char *) ngx_http_v3_encode_field_l(b->last,
&header[i].key,
&header[i].value);
}
n = b->last - b->pos;
h3c->payload_bytes += n;
hl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (hl == NULL) {
return NULL;
}
b = hl->buf;
p = b->start;
if (p == NULL) {
p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
if (p == NULL) {
return NULL;
}
b->start = p;
b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
}
b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
b->memory = 0;
b->temporary = 1;
b->pos = p;
b->last = (u_char *) ngx_http_v3_encode_varlen_int(p,
NGX_HTTP_V3_FRAME_HEADERS);
b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
hl->next = cl;
return hl;
}
static ngx_int_t
ngx_http_v3_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_v3_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_v3_body_filter;
return NGX_OK;
}
+551
View File
@@ -0,0 +1,551 @@
/*
* Copyright (C) Nginx, Inc.
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf);
static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf);
static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent,
void *child);
static char *ngx_http_quic_mtu(ngx_conf_t *cf, void *post,
void *data);
static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent,
void *child);
static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_conf_post_t ngx_http_quic_mtu_post =
{ ngx_http_quic_mtu };
static ngx_command_t ngx_http_v3_commands[] = {
{ ngx_string("http3_max_concurrent_pushes"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes),
NULL },
{ ngx_string("http3_max_concurrent_streams"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams),
NULL },
#if (NGX_HTTP_V3_HQ)
{ ngx_string("http3_hq"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, hq),
NULL },
#endif
{ ngx_string("http3_push"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_v3_push,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
{ ngx_string("http3_push_preload"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_v3_loc_conf_t, push_preload),
NULL },
{ ngx_string("http3_stream_buffer_size"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size),
NULL },
{ ngx_string("quic_retry"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.retry),
NULL },
{ ngx_string("quic_gso"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled),
NULL },
{ ngx_string("quic_mtu"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.mtu),
&ngx_http_quic_mtu_post },
{ ngx_string("quic_host_key"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_http_quic_host_key,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
{ ngx_string("quic_active_connection_id_limit"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit),
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_v3_module_ctx = {
ngx_http_v3_add_variables, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
ngx_http_v3_create_srv_conf, /* create server configuration */
ngx_http_v3_merge_srv_conf, /* merge server configuration */
ngx_http_v3_create_loc_conf, /* create location configuration */
ngx_http_v3_merge_loc_conf /* merge location configuration */
};
ngx_module_t ngx_http_v3_module = {
NGX_MODULE_V1,
&ngx_http_v3_module_ctx, /* module context */
ngx_http_v3_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_variable_t ngx_http_v3_vars[] = {
{ ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 },
ngx_http_null_variable
};
static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic");
static ngx_int_t
ngx_http_v3_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
if (r->connection->quic) {
#if (NGX_HTTP_V3_HQ)
ngx_http_v3_srv_conf_t *h3scf;
h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
if (h3scf->hq) {
v->len = sizeof("hq") - 1;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = (u_char *) "hq";
return NGX_OK;
}
#endif
v->len = sizeof("h3") - 1;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = (u_char *) "h3";
return NGX_OK;
}
*v = ngx_http_variable_null_value;
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_add_variables(ngx_conf_t *cf)
{
ngx_http_variable_t *var, *v;
for (v = ngx_http_v3_vars; v->name.len; v++) {
var = ngx_http_add_variable(cf, &v->name, v->flags);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = v->get_handler;
var->data = v->data;
}
return NGX_OK;
}
static void *
ngx_http_v3_create_srv_conf(ngx_conf_t *cf)
{
ngx_http_v3_srv_conf_t *h3scf;
h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
if (h3scf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* h3scf->quic.host_key = { 0, NULL }
* h3scf->quic.stream_reject_code_uni = 0;
* h3scf->quic.disable_active_migration = 0;
* h3scf->quic.timeout = 0;
* h3scf->max_blocked_streams = 0;
*/
h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY;
h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT;
h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT;
#if (NGX_HTTP_V3_HQ)
h3scf->hq = NGX_CONF_UNSET;
#endif
h3scf->quic.mtu = NGX_CONF_UNSET_SIZE;
h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE;
h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT;
h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS;
h3scf->quic.retry = NGX_CONF_UNSET;
h3scf->quic.gso_enabled = NGX_CONF_UNSET;
h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR;
h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED;
h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
return h3scf;
}
static char *
ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_v3_srv_conf_t *prev = parent;
ngx_http_v3_srv_conf_t *conf = child;
ngx_http_ssl_srv_conf_t *sscf;
ngx_conf_merge_uint_value(conf->max_concurrent_pushes,
prev->max_concurrent_pushes, 10);
ngx_conf_merge_uint_value(conf->max_concurrent_streams,
prev->max_concurrent_streams, 128);
conf->max_blocked_streams = conf->max_concurrent_streams;
#if (NGX_HTTP_V3_HQ)
ngx_conf_merge_value(conf->hq, prev->hq, 0);
#endif
ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu,
NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
ngx_conf_merge_size_value(conf->quic.stream_buffer_size,
prev->quic.stream_buffer_size,
65536);
conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams;
ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0);
ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0);
ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, "");
ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
prev->quic.active_connection_id_limit,
2);
if (conf->quic.host_key.len == 0) {
conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN;
conf->quic.host_key.data = ngx_palloc(cf->pool,
conf->quic.host_key.len);
if (conf->quic.host_key.data == NULL) {
return NGX_CONF_ERROR;
}
if (RAND_bytes(conf->quic.host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN)
<= 0)
{
return NGX_CONF_ERROR;
}
}
if (ngx_quic_derive_key(cf->log, "av_token_key",
&conf->quic.host_key, &ngx_http_quic_salt,
conf->quic.av_token_key, NGX_QUIC_AV_KEY_LEN)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
if (ngx_quic_derive_key(cf->log, "sr_token_key",
&conf->quic.host_key, &ngx_http_quic_salt,
conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module);
conf->quic.ssl = &sscf->ssl;
return NGX_CONF_OK;
}
static char *
ngx_http_quic_mtu(ngx_conf_t *cf, void *post, void *data)
{
size_t *sp = data;
if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
|| *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"quic_mtu\" must be between %d and %d",
NGX_QUIC_MIN_INITIAL_SIZE,
NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
static char *
ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_v3_srv_conf_t *h3scf = conf;
u_char *buf;
size_t size;
ssize_t n;
ngx_str_t *value;
ngx_file_t file;
ngx_file_info_t fi;
ngx_quic_conf_t *qcf;
qcf = &h3scf->quic;
if (qcf->host_key.len) {
return "is duplicate";
}
buf = NULL;
#if (NGX_SUPPRESS_WARN)
size = 0;
#endif
value = cf->args->elts;
if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) {
return NGX_CONF_ERROR;
}
ngx_memzero(&file, sizeof(ngx_file_t));
file.name = value[1];
file.log = cf->log;
file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
if (file.fd == NGX_INVALID_FILE) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
ngx_open_file_n " \"%V\" failed", &file.name);
return NGX_CONF_ERROR;
}
if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
ngx_fd_info_n " \"%V\" failed", &file.name);
goto failed;
}
size = ngx_file_size(&fi);
if (size == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%V\" zero key size", &file.name);
goto failed;
}
buf = ngx_pnalloc(cf->pool, size);
if (buf == NULL) {
goto failed;
}
n = ngx_read_file(&file, buf, size, 0);
if (n == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
ngx_read_file_n " \"%V\" failed", &file.name);
goto failed;
}
if ((size_t) n != size) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
ngx_read_file_n " \"%V\" returned only "
"%z bytes instead of %uz", &file.name, n, size);
goto failed;
}
qcf->host_key.data = buf;
qcf->host_key.len = n;
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &file.name);
}
return NGX_CONF_OK;
failed:
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &file.name);
}
if (buf) {
ngx_explicit_memzero(buf, size);
}
return NGX_CONF_ERROR;
}
static void *
ngx_http_v3_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_v3_loc_conf_t *h3lcf;
h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t));
if (h3lcf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* h3lcf->pushes = NULL;
*/
h3lcf->push_preload = NGX_CONF_UNSET;
h3lcf->push = NGX_CONF_UNSET;
return h3lcf;
}
static char *
ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_v3_loc_conf_t *prev = parent;
ngx_http_v3_loc_conf_t *conf = child;
ngx_conf_merge_value(conf->push, prev->push, 1);
if (conf->push && conf->pushes == NULL) {
conf->pushes = prev->pushes;
}
ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0);
return NGX_CONF_OK;
}
static char *
ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_v3_loc_conf_t *h3lcf = conf;
ngx_str_t *value;
ngx_http_complex_value_t *cv;
ngx_http_compile_complex_value_t ccv;
value = cf->args->elts;
if (ngx_strcmp(value[1].data, "off") == 0) {
if (h3lcf->pushes) {
return "\"off\" parameter cannot be used with URI";
}
if (h3lcf->push == 0) {
return "is duplicate";
}
h3lcf->push = 0;
return NGX_CONF_OK;
}
if (h3lcf->push == 0) {
return "URI cannot be used with \"off\" parameter";
}
h3lcf->push = 1;
if (h3lcf->pushes == NULL) {
h3lcf->pushes = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_complex_value_t));
if (h3lcf->pushes == NULL) {
return NGX_CONF_ERROR;
}
}
cv = ngx_array_push(h3lcf->pushes);
if (cv == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = cv;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
+2013
View File
@@ -0,0 +1,2013 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#define ngx_http_v3_is_v2_frame(type) \
((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09)
static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc,
ngx_uint_t n);
static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc,
ngx_uint_t *n);
static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length);
static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c,
ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c,
ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c,
ngx_http_v3_parse_literal_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c,
ngx_http_v3_parse_control_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c,
ngx_http_v3_parse_settings_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c,
ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c,
ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b);
static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c,
ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value);
static void
ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n)
{
*loc = *b;
if ((size_t) (loc->last - loc->pos) > n) {
loc->last = loc->pos + n;
}
}
static void
ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn)
{
*pn -= loc->pos - b->pos;
b->pos = loc->pos;
}
static ngx_int_t
ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length)
{
if ((size_t) (b->last - b->pos) < *length) {
*length -= b->last - b->pos;
b->pos = b->last;
return NGX_AGAIN;
}
b->pos += *length;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b)
{
u_char ch;
enum {
sw_start = 0,
sw_length_2,
sw_length_3,
sw_length_4,
sw_length_5,
sw_length_6,
sw_length_7,
sw_length_8
};
for ( ;; ) {
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos++;
switch (st->state) {
case sw_start:
st->value = ch;
if (st->value & 0xc0) {
st->state = sw_length_2;
break;
}
goto done;
case sw_length_2:
st->value = (st->value << 8) + ch;
if ((st->value & 0xc000) == 0x4000) {
st->value &= 0x3fff;
goto done;
}
st->state = sw_length_3;
break;
case sw_length_4:
st->value = (st->value << 8) + ch;
if ((st->value & 0xc0000000) == 0x80000000) {
st->value &= 0x3fffffff;
goto done;
}
st->state = sw_length_5;
break;
case sw_length_3:
case sw_length_5:
case sw_length_6:
case sw_length_7:
st->value = (st->value << 8) + ch;
st->state++;
break;
case sw_length_8:
st->value = (st->value << 8) + ch;
st->value &= 0x3fffffffffffffff;
goto done;
}
}
done:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse varlen int %uL", st->value);
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b)
{
u_char ch;
ngx_uint_t mask;
enum {
sw_start = 0,
sw_value
};
for ( ;; ) {
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos++;
switch (st->state) {
case sw_start:
mask = (1 << prefix) - 1;
st->value = ch & mask;
if (st->value != mask) {
goto done;
}
st->shift = 0;
st->state = sw_value;
break;
case sw_value:
st->value += (uint64_t) (ch & 0x7f) << st->shift;
if (st->shift == 56
&& ((ch & 0x80) || (st->value & 0xc000000000000000)))
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client exceeded integer size limit");
return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
}
if (ch & 0x80) {
st->shift += 7;
break;
}
goto done;
}
}
done:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse prefix int %uL", st->value);
st->state = sw_start;
return NGX_DONE;
}
ngx_int_t
ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st,
ngx_buf_t *b)
{
ngx_buf_t loc;
ngx_int_t rc;
enum {
sw_start = 0,
sw_type,
sw_length,
sw_skip,
sw_prefix,
sw_verify,
sw_field_rep,
sw_done
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse headers");
st->state = sw_type;
/* fall through */
case sw_type:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
st->type = st->vlint.value;
if (ngx_http_v3_is_v2_frame(st->type)
|| st->type == NGX_HTTP_V3_FRAME_DATA
|| st->type == NGX_HTTP_V3_FRAME_GOAWAY
|| st->type == NGX_HTTP_V3_FRAME_SETTINGS
|| st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID
|| st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH
|| st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
{
return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
}
st->state = sw_length;
break;
case sw_length:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
st->length = st->vlint.value;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse headers type:%ui, len:%ui",
st->type, st->length);
if (st->type != NGX_HTTP_V3_FRAME_HEADERS) {
st->state = st->length > 0 ? sw_skip : sw_type;
break;
}
if (st->length == 0) {
return NGX_HTTP_V3_ERR_FRAME_ERROR;
}
st->state = sw_prefix;
break;
case sw_skip:
rc = ngx_http_v3_parse_skip(b, &st->length);
if (rc != NGX_DONE) {
return rc;
}
st->state = sw_type;
break;
case sw_prefix:
ngx_http_v3_parse_start_local(b, &loc, st->length);
rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &loc);
ngx_http_v3_parse_end_local(b, &loc, &st->length);
if (st->length == 0 && rc == NGX_AGAIN) {
return NGX_HTTP_V3_ERR_FRAME_ERROR;
}
if (rc != NGX_DONE) {
return rc;
}
st->state = sw_verify;
break;
case sw_verify:
rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_field_rep;
/* fall through */
case sw_field_rep:
ngx_http_v3_parse_start_local(b, &loc, st->length);
rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base,
&loc);
ngx_http_v3_parse_end_local(b, &loc, &st->length);
if (st->length == 0 && rc == NGX_AGAIN) {
return NGX_HTTP_V3_ERR_FRAME_ERROR;
}
if (rc != NGX_DONE) {
return rc;
}
if (st->length == 0) {
goto done;
}
return NGX_OK;
}
}
done:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done");
if (st->prefix.insert_count > 0) {
if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) {
return NGX_ERROR;
}
ngx_http_v3_ack_insert_count(c, st->prefix.insert_count);
}
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c,
ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_req_insert_count,
sw_delta_base,
sw_read_delta_base
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field section prefix");
st->state = sw_req_insert_count;
/* fall through */
case sw_req_insert_count:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b);
if (rc != NGX_DONE) {
return rc;
}
st->insert_count = st->pint.value;
st->state = sw_delta_base;
break;
case sw_delta_base:
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->sign = (ch & 0x80) ? 1 : 0;
st->state = sw_read_delta_base;
/* fall through */
case sw_read_delta_base:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
if (rc != NGX_DONE) {
return rc;
}
st->delta_base = st->pint.value;
goto done;
}
}
done:
rc = ngx_http_v3_decode_insert_count(c, &st->insert_count);
if (rc != NGX_OK) {
return rc;
}
if (st->sign) {
if (st->insert_count <= st->delta_base) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent negative base");
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
st->base = st->insert_count - st->delta_base - 1;
} else {
st->base = st->insert_count + st->delta_base;
}
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field section prefix done "
"insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui",
st->insert_count, st->sign, st->delta_base, st->base);
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_rep(ngx_connection_t *c,
ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_field_ri,
sw_field_lri,
sw_field_l,
sw_field_pbi,
sw_field_lpbi
};
if (st->state == sw_start) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field representation");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t));
st->field.base = base;
if (ch & 0x80) {
/* Indexed Field Line */
st->state = sw_field_ri;
} else if (ch & 0x40) {
/* Literal Field Line With Name Reference */
st->state = sw_field_lri;
} else if (ch & 0x20) {
/* Literal Field Line With Literal Name */
st->state = sw_field_l;
} else if (ch & 0x10) {
/* Indexed Field Line With Post-Base Index */
st->state = sw_field_pbi;
} else {
/* Literal Field Line With Post-Base Name Reference */
st->state = sw_field_lpbi;
}
}
switch (st->state) {
case sw_field_ri:
rc = ngx_http_v3_parse_field_ri(c, &st->field, b);
break;
case sw_field_lri:
rc = ngx_http_v3_parse_field_lri(c, &st->field, b);
break;
case sw_field_l:
rc = ngx_http_v3_parse_field_l(c, &st->field, b);
break;
case sw_field_pbi:
rc = ngx_http_v3_parse_field_pbi(c, &st->field, b);
break;
case sw_field_lpbi:
rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b);
break;
default:
rc = NGX_OK;
}
if (rc != NGX_DONE) {
return rc;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field representation done");
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st,
ngx_buf_t *b)
{
u_char ch;
ngx_uint_t n;
ngx_http_core_srv_conf_t *cscf;
enum {
sw_start = 0,
sw_value
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse literal huff:%ui, len:%ui",
st->huffman, st->length);
n = st->length;
cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module);
if (n > cscf->large_client_header_buffers.size) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too large field line");
return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
}
if (st->huffman) {
n = n * 8 / 5;
st->huffstate = 0;
}
st->last = ngx_pnalloc(c->pool, n + 1);
if (st->last == NULL) {
return NGX_ERROR;
}
st->value.data = st->last;
st->state = sw_value;
/* fall through */
case sw_value:
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos++;
if (st->huffman) {
if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last,
st->length == 1, c->log)
!= NGX_OK)
{
return NGX_ERROR;
}
} else {
*st->last++ = ch;
}
if (--st->length) {
break;
}
st->value.len = st->last - st->value.data;
*st->last = '\0';
goto done;
}
}
done:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse literal done \"%V\"", &st->value);
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st,
ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_index
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field ri");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->dynamic = (ch & 0x40) ? 0 : 1;
st->state = sw_index;
/* fall through */
case sw_index:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
if (rc != NGX_DONE) {
return rc;
}
st->index = st->pint.value;
goto done;
}
}
done:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field ri done %s%ui]",
st->dynamic ? "dynamic[-" : "static[", st->index);
if (st->dynamic) {
st->index = st->base - st->index - 1;
}
rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name,
&st->value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_lri(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_index,
sw_value_len,
sw_read_value_len,
sw_value
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field lri");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->dynamic = (ch & 0x10) ? 0 : 1;
st->state = sw_index;
/* fall through */
case sw_index:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b);
if (rc != NGX_DONE) {
return rc;
}
st->index = st->pint.value;
st->state = sw_value_len;
break;
case sw_value_len:
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->literal.huffman = (ch & 0x80) ? 1 : 0;
st->state = sw_read_value_len;
/* fall through */
case sw_read_value_len:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
if (rc != NGX_DONE) {
return rc;
}
st->literal.length = st->pint.value;
if (st->literal.length == 0) {
goto done;
}
st->state = sw_value;
break;
case sw_value:
rc = ngx_http_v3_parse_literal(c, &st->literal, b);
if (rc != NGX_DONE) {
return rc;
}
st->value = st->literal.value;
goto done;
}
}
done:
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field lri done %s%ui] \"%V\"",
st->dynamic ? "dynamic[-" : "static[",
st->index, &st->value);
if (st->dynamic) {
st->index = st->base - st->index - 1;
}
rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_l(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_name_len,
sw_name,
sw_value_len,
sw_read_value_len,
sw_value
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->literal.huffman = (ch & 0x08) ? 1 : 0;
st->state = sw_name_len;
/* fall through */
case sw_name_len:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b);
if (rc != NGX_DONE) {
return rc;
}
st->literal.length = st->pint.value;
if (st->literal.length == 0) {
return NGX_ERROR;
}
st->state = sw_name;
break;
case sw_name:
rc = ngx_http_v3_parse_literal(c, &st->literal, b);
if (rc != NGX_DONE) {
return rc;
}
st->name = st->literal.value;
st->state = sw_value_len;
break;
case sw_value_len:
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->literal.huffman = (ch & 0x80) ? 1 : 0;
st->state = sw_read_value_len;
/* fall through */
case sw_read_value_len:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
if (rc != NGX_DONE) {
return rc;
}
st->literal.length = st->pint.value;
if (st->literal.length == 0) {
goto done;
}
st->state = sw_value;
break;
case sw_value:
rc = ngx_http_v3_parse_literal(c, &st->literal, b);
if (rc != NGX_DONE) {
return rc;
}
st->value = st->literal.value;
goto done;
}
}
done:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field l done \"%V\" \"%V\"",
&st->name, &st->value);
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_pbi(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
{
ngx_int_t rc;
enum {
sw_start = 0,
sw_index
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field pbi");
st->state = sw_index;
/* fall through */
case sw_index:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b);
if (rc != NGX_DONE) {
return rc;
}
st->index = st->pint.value;
goto done;
}
}
done:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field pbi done dynamic[+%ui]", st->index);
rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name,
&st->value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_lpbi(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_index,
sw_value_len,
sw_read_value_len,
sw_value
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field lpbi");
st->state = sw_index;
/* fall through */
case sw_index:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b);
if (rc != NGX_DONE) {
return rc;
}
st->index = st->pint.value;
st->state = sw_value_len;
break;
case sw_value_len:
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->literal.huffman = (ch & 0x80) ? 1 : 0;
st->state = sw_read_value_len;
/* fall through */
case sw_read_value_len:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
if (rc != NGX_DONE) {
return rc;
}
st->literal.length = st->pint.value;
if (st->literal.length == 0) {
goto done;
}
st->state = sw_value;
break;
case sw_value:
rc = ngx_http_v3_parse_literal(c, &st->literal, b);
if (rc != NGX_DONE) {
return rc;
}
st->value = st->literal.value;
goto done;
}
}
done:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field lpbi done dynamic[+%ui] \"%V\"",
st->index, &st->value);
rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic,
ngx_uint_t index, ngx_str_t *name, ngx_str_t *value)
{
u_char *p;
if (!dynamic) {
if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
return NGX_OK;
}
if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
if (name) {
p = ngx_pnalloc(c->pool, name->len + 1);
if (p == NULL) {
return NGX_ERROR;
}
ngx_memcpy(p, name->data, name->len);
p[name->len] = '\0';
name->data = p;
}
if (value) {
p = ngx_pnalloc(c->pool, value->len + 1);
if (p == NULL) {
return NGX_ERROR;
}
ngx_memcpy(p, value->data, value->len);
p[value->len] = '\0';
value->data = p;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st,
ngx_buf_t *b)
{
ngx_buf_t loc;
ngx_int_t rc;
enum {
sw_start = 0,
sw_first_type,
sw_type,
sw_length,
sw_cancel_push,
sw_settings,
sw_max_push_id,
sw_goaway,
sw_skip
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse control");
st->state = sw_first_type;
/* fall through */
case sw_first_type:
case sw_type:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
st->type = st->vlint.value;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse frame type:%ui", st->type);
if (st->state == sw_first_type
&& st->type != NGX_HTTP_V3_FRAME_SETTINGS)
{
return NGX_HTTP_V3_ERR_MISSING_SETTINGS;
}
if (st->state != sw_first_type
&& st->type == NGX_HTTP_V3_FRAME_SETTINGS)
{
return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
}
if (ngx_http_v3_is_v2_frame(st->type)
|| st->type == NGX_HTTP_V3_FRAME_DATA
|| st->type == NGX_HTTP_V3_FRAME_HEADERS
|| st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
{
return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
}
st->state = sw_length;
break;
case sw_length:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse frame len:%uL", st->vlint.value);
st->length = st->vlint.value;
if (st->length == 0) {
st->state = sw_type;
break;
}
switch (st->type) {
case NGX_HTTP_V3_FRAME_CANCEL_PUSH:
st->state = sw_cancel_push;
break;
case NGX_HTTP_V3_FRAME_SETTINGS:
st->state = sw_settings;
break;
case NGX_HTTP_V3_FRAME_MAX_PUSH_ID:
st->state = sw_max_push_id;
break;
case NGX_HTTP_V3_FRAME_GOAWAY:
st->state = sw_goaway;
break;
default:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse skip unknown frame");
st->state = sw_skip;
}
break;
case sw_cancel_push:
ngx_http_v3_parse_start_local(b, &loc, st->length);
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc);
ngx_http_v3_parse_end_local(b, &loc, &st->length);
if (st->length == 0 && rc == NGX_AGAIN) {
return NGX_HTTP_V3_ERR_FRAME_ERROR;
}
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_cancel_push(c, st->vlint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_type;
break;
case sw_settings:
ngx_http_v3_parse_start_local(b, &loc, st->length);
rc = ngx_http_v3_parse_settings(c, &st->settings, &loc);
ngx_http_v3_parse_end_local(b, &loc, &st->length);
if (st->length == 0 && rc == NGX_AGAIN) {
return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
}
if (rc != NGX_DONE) {
return rc;
}
if (st->length == 0) {
st->state = sw_type;
}
break;
case sw_max_push_id:
ngx_http_v3_parse_start_local(b, &loc, st->length);
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc);
ngx_http_v3_parse_end_local(b, &loc, &st->length);
if (st->length == 0 && rc == NGX_AGAIN) {
return NGX_HTTP_V3_ERR_FRAME_ERROR;
}
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_set_max_push_id(c, st->vlint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_type;
break;
case sw_goaway:
ngx_http_v3_parse_start_local(b, &loc, st->length);
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc);
ngx_http_v3_parse_end_local(b, &loc, &st->length);
if (st->length == 0 && rc == NGX_AGAIN) {
return NGX_HTTP_V3_ERR_FRAME_ERROR;
}
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_goaway(c, st->vlint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_type;
break;
case sw_skip:
rc = ngx_http_v3_parse_skip(b, &st->length);
if (rc != NGX_DONE) {
return rc;
}
st->state = sw_type;
break;
}
}
}
static ngx_int_t
ngx_http_v3_parse_settings(ngx_connection_t *c,
ngx_http_v3_parse_settings_t *st, ngx_buf_t *b)
{
ngx_int_t rc;
enum {
sw_start = 0,
sw_id,
sw_value
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse settings");
st->state = sw_id;
/* fall through */
case sw_id:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
st->id = st->vlint.value;
st->state = sw_value;
break;
case sw_value:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) {
return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
}
goto done;
}
}
done:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done");
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st,
ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_inr,
sw_iln,
sw_capacity,
sw_duplicate
};
for ( ;; ) {
if (st->state == sw_start) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse encoder instruction");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
if (ch & 0x80) {
/* Insert With Name Reference */
st->state = sw_inr;
} else if (ch & 0x40) {
/* Insert With Literal Name */
st->state = sw_iln;
} else if (ch & 0x20) {
/* Set Dynamic Table Capacity */
st->state = sw_capacity;
} else {
/* Duplicate */
st->state = sw_duplicate;
}
}
switch (st->state) {
case sw_inr:
rc = ngx_http_v3_parse_field_inr(c, &st->field, b);
if (rc != NGX_DONE) {
return rc;
}
st->state = sw_start;
break;
case sw_iln:
rc = ngx_http_v3_parse_field_iln(c, &st->field, b);
if (rc != NGX_DONE) {
return rc;
}
st->state = sw_start;
break;
case sw_capacity:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_set_capacity(c, st->pint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
break;
default: /* sw_duplicate */
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_duplicate(c, st->pint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
break;
}
}
}
static ngx_int_t
ngx_http_v3_parse_field_inr(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_name_index,
sw_value_len,
sw_read_value_len,
sw_value
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field inr");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->dynamic = (ch & 0x40) ? 0 : 1;
st->state = sw_name_index;
/* fall through */
case sw_name_index:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
if (rc != NGX_DONE) {
return rc;
}
st->index = st->pint.value;
st->state = sw_value_len;
break;
case sw_value_len:
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->literal.huffman = (ch & 0x80) ? 1 : 0;
st->state = sw_read_value_len;
/* fall through */
case sw_read_value_len:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
if (rc != NGX_DONE) {
return rc;
}
st->literal.length = st->pint.value;
if (st->literal.length == 0) {
st->value.len = 0;
goto done;
}
st->state = sw_value;
break;
case sw_value:
rc = ngx_http_v3_parse_literal(c, &st->literal, b);
if (rc != NGX_DONE) {
return rc;
}
st->value = st->literal.value;
goto done;
}
}
done:
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field inr done %s[%ui] \"%V\"",
st->dynamic ? "dynamic" : "static",
st->index, &st->value);
rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_field_iln(ngx_connection_t *c,
ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_name_len,
sw_name,
sw_value_len,
sw_read_value_len,
sw_value
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field iln");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->literal.huffman = (ch & 0x20) ? 1 : 0;
st->state = sw_name_len;
/* fall through */
case sw_name_len:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
if (rc != NGX_DONE) {
return rc;
}
st->literal.length = st->pint.value;
if (st->literal.length == 0) {
return NGX_ERROR;
}
st->state = sw_name;
break;
case sw_name:
rc = ngx_http_v3_parse_literal(c, &st->literal, b);
if (rc != NGX_DONE) {
return rc;
}
st->name = st->literal.value;
st->state = sw_value_len;
break;
case sw_value_len:
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
st->literal.huffman = (ch & 0x80) ? 1 : 0;
st->state = sw_read_value_len;
/* fall through */
case sw_read_value_len:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
if (rc != NGX_DONE) {
return rc;
}
st->literal.length = st->pint.value;
if (st->literal.length == 0) {
st->value.len = 0;
goto done;
}
st->state = sw_value;
break;
case sw_value:
rc = ngx_http_v3_parse_literal(c, &st->literal, b);
if (rc != NGX_DONE) {
return rc;
}
st->value = st->literal.value;
goto done;
}
}
done:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse field iln done \"%V\":\"%V\"",
&st->name, &st->value);
rc = ngx_http_v3_insert(c, &st->name, &st->value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
return NGX_DONE;
}
static ngx_int_t
ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st,
ngx_buf_t *b)
{
u_char ch;
ngx_int_t rc;
enum {
sw_start = 0,
sw_ack_section,
sw_cancel_stream,
sw_inc_insert_count
};
for ( ;; ) {
if (st->state == sw_start) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse decoder instruction");
if (b->pos == b->last) {
return NGX_AGAIN;
}
ch = *b->pos;
if (ch & 0x80) {
/* Section Acknowledgment */
st->state = sw_ack_section;
} else if (ch & 0x40) {
/* Stream Cancellation */
st->state = sw_cancel_stream;
} else {
/* Insert Count Increment */
st->state = sw_inc_insert_count;
}
}
switch (st->state) {
case sw_ack_section:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_ack_section(c, st->pint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
break;
case sw_cancel_stream:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_cancel_stream(c, st->pint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
break;
case sw_inc_insert_count:
rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_inc_insert_count(c, st->pint.value);
if (rc != NGX_OK) {
return rc;
}
st->state = sw_start;
break;
}
}
}
ngx_int_t
ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st,
ngx_buf_t *b)
{
ngx_int_t rc;
enum {
sw_start = 0,
sw_type,
sw_length,
sw_skip
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data");
st->state = sw_type;
/* fall through */
case sw_type:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
st->type = st->vlint.value;
if (st->type == NGX_HTTP_V3_FRAME_HEADERS) {
/* trailers */
goto done;
}
if (ngx_http_v3_is_v2_frame(st->type)
|| st->type == NGX_HTTP_V3_FRAME_GOAWAY
|| st->type == NGX_HTTP_V3_FRAME_SETTINGS
|| st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID
|| st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH
|| st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
{
return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
}
st->state = sw_length;
break;
case sw_length:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
st->length = st->vlint.value;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 parse data type:%ui, len:%ui",
st->type, st->length);
if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) {
st->state = sw_skip;
break;
}
st->state = sw_type;
return NGX_OK;
case sw_skip:
rc = ngx_http_v3_parse_skip(b, &st->length);
if (rc != NGX_DONE) {
return rc;
}
st->state = sw_type;
break;
}
}
done:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done");
st->state = sw_start;
return NGX_DONE;
}
ngx_int_t
ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st,
ngx_buf_t *b)
{
ngx_int_t rc;
enum {
sw_start = 0,
sw_type,
sw_control,
sw_encoder,
sw_decoder,
sw_unknown
};
for ( ;; ) {
switch (st->state) {
case sw_start:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni");
st->state = sw_type;
/* fall through */
case sw_type:
rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
if (rc != NGX_DONE) {
return rc;
}
rc = ngx_http_v3_register_uni_stream(c, st->vlint.value);
if (rc != NGX_OK) {
return rc;
}
switch (st->vlint.value) {
case NGX_HTTP_V3_STREAM_CONTROL:
st->state = sw_control;
break;
case NGX_HTTP_V3_STREAM_ENCODER:
st->state = sw_encoder;
break;
case NGX_HTTP_V3_STREAM_DECODER:
st->state = sw_decoder;
break;
default:
st->state = sw_unknown;
}
break;
case sw_control:
return ngx_http_v3_parse_control(c, &st->u.control, b);
case sw_encoder:
return ngx_http_v3_parse_encoder(c, &st->u.encoder, b);
case sw_decoder:
return ngx_http_v3_parse_decoder(c, &st->u.decoder, b);
case sw_unknown:
b->pos = b->last;
return NGX_AGAIN;
}
}
}
+146
View File
@@ -0,0 +1,146 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_
#define _NGX_HTTP_V3_PARSE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_uint_t state;
uint64_t value;
} ngx_http_v3_parse_varlen_int_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t shift;
uint64_t value;
} ngx_http_v3_parse_prefix_int_t;
typedef struct {
ngx_uint_t state;
uint64_t id;
ngx_http_v3_parse_varlen_int_t vlint;
} ngx_http_v3_parse_settings_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t insert_count;
ngx_uint_t delta_base;
ngx_uint_t sign;
ngx_uint_t base;
ngx_http_v3_parse_prefix_int_t pint;
} ngx_http_v3_parse_field_section_prefix_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t length;
ngx_uint_t huffman;
ngx_str_t value;
u_char *last;
u_char huffstate;
} ngx_http_v3_parse_literal_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t index;
ngx_uint_t base;
ngx_uint_t dynamic;
ngx_str_t name;
ngx_str_t value;
ngx_http_v3_parse_prefix_int_t pint;
ngx_http_v3_parse_literal_t literal;
} ngx_http_v3_parse_field_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_field_t field;
} ngx_http_v3_parse_field_rep_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t type;
ngx_uint_t length;
ngx_http_v3_parse_varlen_int_t vlint;
ngx_http_v3_parse_field_section_prefix_t prefix;
ngx_http_v3_parse_field_rep_t field_rep;
} ngx_http_v3_parse_headers_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_field_t field;
ngx_http_v3_parse_prefix_int_t pint;
} ngx_http_v3_parse_encoder_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_prefix_int_t pint;
} ngx_http_v3_parse_decoder_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t type;
ngx_uint_t length;
ngx_http_v3_parse_varlen_int_t vlint;
ngx_http_v3_parse_settings_t settings;
} ngx_http_v3_parse_control_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_varlen_int_t vlint;
union {
ngx_http_v3_parse_encoder_t encoder;
ngx_http_v3_parse_decoder_t decoder;
ngx_http_v3_parse_control_t control;
} u;
} ngx_http_v3_parse_uni_t;
typedef struct {
ngx_uint_t state;
ngx_uint_t type;
ngx_uint_t length;
ngx_http_v3_parse_varlen_int_t vlint;
} ngx_http_v3_parse_data_t;
/*
* Parse functions return codes:
* NGX_DONE - parsing done
* NGX_OK - sub-element done
* NGX_AGAIN - more data expected
* NGX_BUSY - waiting for external event
* NGX_ERROR - internal error
* NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error
*/
ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c,
ngx_http_v3_parse_headers_t *st, ngx_buf_t *b);
ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c,
ngx_http_v3_parse_data_t *st, ngx_buf_t *b);
ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c,
ngx_http_v3_parse_uni_t *st, ngx_buf_t *b);
#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */
+1689
View File
@@ -0,0 +1,1689 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#if (NGX_HTTP_V3_HQ)
static void ngx_http_v3_init_hq_stream(ngx_connection_t *c);
#endif
static void ngx_http_v3_init_request_stream(ngx_connection_t *c);
static void ngx_http_v3_wait_request_handler(ngx_event_t *rev);
static void ngx_http_v3_cleanup_request(void *data);
static void ngx_http_v3_process_request(ngx_event_t *rev);
static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r,
ngx_str_t *name, ngx_str_t *value);
static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r,
ngx_str_t *name, ngx_str_t *value);
static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r,
ngx_str_t *name, ngx_str_t *value);
static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value);
static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r);
static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r,
ngx_chain_t *in);
static const struct {
ngx_str_t name;
ngx_uint_t method;
} ngx_http_v3_methods[] = {
{ ngx_string("GET"), NGX_HTTP_GET },
{ ngx_string("POST"), NGX_HTTP_POST },
{ ngx_string("HEAD"), NGX_HTTP_HEAD },
{ ngx_string("OPTIONS"), NGX_HTTP_OPTIONS },
{ ngx_string("PROPFIND"), NGX_HTTP_PROPFIND },
{ ngx_string("PUT"), NGX_HTTP_PUT },
{ ngx_string("MKCOL"), NGX_HTTP_MKCOL },
{ ngx_string("DELETE"), NGX_HTTP_DELETE },
{ ngx_string("COPY"), NGX_HTTP_COPY },
{ ngx_string("MOVE"), NGX_HTTP_MOVE },
{ ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH },
{ ngx_string("LOCK"), NGX_HTTP_LOCK },
{ ngx_string("UNLOCK"), NGX_HTTP_UNLOCK },
{ ngx_string("PATCH"), NGX_HTTP_PATCH },
{ ngx_string("TRACE"), NGX_HTTP_TRACE },
{ ngx_string("CONNECT"), NGX_HTTP_CONNECT }
};
void
ngx_http_v3_init(ngx_connection_t *c)
{
ngx_http_connection_t *hc, *phc;
ngx_http_v3_srv_conf_t *h3scf;
ngx_http_core_loc_conf_t *clcf;
hc = c->data;
hc->ssl = 1;
clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
if (c->quic == NULL) {
h3scf->quic.timeout = clcf->keepalive_timeout;
ngx_quic_run(c, &h3scf->quic);
return;
}
phc = ngx_http_quic_get_connection(c);
if (phc->ssl_servername) {
hc->ssl_servername = phc->ssl_servername;
hc->conf_ctx = phc->conf_ctx;
ngx_set_connection_log(c, clcf->error_log);
}
#if (NGX_HTTP_V3_HQ)
if (h3scf->hq) {
ngx_http_v3_init_hq_stream(c);
return;
}
#endif
if (ngx_http_v3_init_session(c) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
ngx_http_v3_init_uni_stream(c);
} else {
ngx_http_v3_init_request_stream(c);
}
}
#if (NGX_HTTP_V3_HQ)
static void
ngx_http_v3_init_hq_stream(ngx_connection_t *c)
{
uint64_t n;
ngx_event_t *rev;
ngx_http_connection_t *hc;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init hq stream");
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
hc = c->data;
/* Use HTTP/3 General Protocol Error Code 0x101 for finalization */
if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
ngx_quic_finalize_connection(c->quic->parent,
NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR,
"unexpected uni stream");
ngx_http_close_connection(c);
return;
}
clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
n = c->quic->id >> 2;
if (n >= clcf->keepalive_requests) {
ngx_quic_finalize_connection(c->quic->parent,
NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR,
"reached maximum number of requests");
ngx_http_close_connection(c);
return;
}
if (ngx_current_msec - c->quic->parent->start_time
> clcf->keepalive_time)
{
ngx_quic_finalize_connection(c->quic->parent,
NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR,
"reached maximum time for requests");
ngx_http_close_connection(c);
return;
}
rev = c->read;
if (rev->ready) {
rev->handler(rev);
return;
}
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
ngx_add_timer(rev, cscf->client_header_timeout);
ngx_reusable_connection(c, 1);
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
}
#endif
static void
ngx_http_v3_init_request_stream(ngx_connection_t *c)
{
uint64_t n;
ngx_event_t *rev;
ngx_http_connection_t *hc;
ngx_http_v3_session_t *h3c;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream");
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
hc = c->data;
clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
n = c->quic->id >> 2;
if (n >= clcf->keepalive_requests * 2) {
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"too many requests per connection");
ngx_http_close_connection(c);
return;
}
h3c = ngx_http_v3_get_session(c);
if (h3c->goaway) {
c->close = 1;
ngx_http_close_connection(c);
return;
}
if (n + 1 == clcf->keepalive_requests
|| ngx_current_msec - c->quic->parent->start_time
> clcf->keepalive_time)
{
h3c->goaway = 1;
if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
"reached maximum number of requests");
}
rev = c->read;
rev->handler = ngx_http_v3_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
if (rev->ready) {
rev->handler(rev);
return;
}
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
ngx_add_timer(rev, cscf->client_header_timeout);
ngx_reusable_connection(c, 1);
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
}
static void
ngx_http_v3_wait_request_handler(ngx_event_t *rev)
{
size_t size;
ssize_t n;
ngx_buf_t *b;
ngx_connection_t *c;
ngx_pool_cleanup_t *cln;
ngx_http_request_t *r;
ngx_http_connection_t *hc;
ngx_http_v3_session_t *h3c;
ngx_http_core_srv_conf_t *cscf;
c = rev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler");
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_connection(c);
return;
}
if (c->close) {
ngx_http_close_connection(c);
return;
}
hc = c->data;
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
size = cscf->client_header_buffer_size;
b = c->buffer;
if (b == NULL) {
b = ngx_create_temp_buf(c->pool, size);
if (b == NULL) {
ngx_http_close_connection(c);
return;
}
c->buffer = b;
} else if (b->start == NULL) {
b->start = ngx_palloc(c->pool, size);
if (b->start == NULL) {
ngx_http_close_connection(c);
return;
}
b->pos = b->start;
b->last = b->start;
b->end = b->last + size;
}
n = c->recv(c, b->last, size);
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
ngx_add_timer(rev, cscf->client_header_timeout);
ngx_reusable_connection(c, 1);
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
/*
* We are trying to not hold c->buffer's memory for an idle connection.
*/
if (ngx_pfree(c->pool, b->start) == NGX_OK) {
b->start = NULL;
}
return;
}
if (n == NGX_ERROR) {
ngx_http_close_connection(c);
return;
}
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client closed connection");
ngx_http_close_connection(c);
return;
}
b->last += n;
c->log->action = "reading client request";
ngx_reusable_connection(c, 0);
r = ngx_http_create_request(c);
if (r == NULL) {
ngx_http_close_connection(c);
return;
}
r->http_version = NGX_HTTP_VERSION_30;
r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t));
if (r->v3_parse == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
r->v3_parse->header_limit = cscf->large_client_header_buffers.size
* cscf->large_client_header_buffers.num;
c->data = r;
c->requests = (c->quic->id >> 2) + 1;
cln = ngx_pool_cleanup_add(r->pool, 0);
if (cln == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
cln->handler = ngx_http_v3_cleanup_request;
cln->data = r;
h3c = ngx_http_v3_get_session(c);
h3c->nrequests++;
if (h3c->keepalive.timer_set) {
ngx_del_timer(&h3c->keepalive);
}
rev->handler = ngx_http_v3_process_request;
ngx_http_v3_process_request(rev);
}
void
ngx_http_v3_reset_connection(ngx_connection_t *c)
{
ngx_http_v3_srv_conf_t *h3scf;
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
#if (NGX_HTTP_V3_HQ)
if (h3scf->hq) {
return;
}
#endif
if (h3scf->max_table_capacity > 0 && !c->read->eof
&& (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0)
{
(void) ngx_http_v3_send_cancel_stream(c, c->quic->id);
}
if (c->timedout) {
ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR);
} else if (c->close) {
ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED);
} else if (c->requests == 0 || c->error) {
ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR);
}
}
static void
ngx_http_v3_cleanup_request(void *data)
{
ngx_http_request_t *r = data;
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
if (!r->response_sent) {
c->error = 1;
}
h3c = ngx_http_v3_get_session(c);
if (--h3c->nrequests == 0) {
clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module);
ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout);
}
}
static void
ngx_http_v3_process_request(ngx_event_t *rev)
{
u_char *p;
ssize_t n;
ngx_buf_t *b;
ngx_int_t rc;
ngx_connection_t *c;
ngx_http_request_t *r;
ngx_http_v3_session_t *h3c;
ngx_http_core_srv_conf_t *cscf;
ngx_http_v3_parse_headers_t *st;
c = rev->data;
r = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request");
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
h3c = ngx_http_v3_get_session(c);
st = &r->v3_parse->headers;
b = r->header_in;
for ( ;; ) {
if (b->pos == b->last) {
if (rev->ready) {
n = c->recv(c, b->start, b->end - b->start);
} else {
n = NGX_AGAIN;
}
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
cscf = ngx_http_get_module_srv_conf(r,
ngx_http_core_module);
ngx_add_timer(rev, cscf->client_header_timeout);
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
break;
}
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client prematurely closed connection");
}
if (n == 0 || n == NGX_ERROR) {
c->error = 1;
c->log->action = "reading client request";
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
break;
}
b->pos = b->start;
b->last = b->start + n;
}
p = b->pos;
rc = ngx_http_v3_parse_headers(c, st, b);
if (rc > 0) {
ngx_quic_reset_stream(c, rc);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client sent invalid header");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
break;
}
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
break;
}
r->request_length += b->pos - p;
h3c->total_bytes += b->pos - p;
if (ngx_http_v3_check_flood(c) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_CLOSE);
break;
}
if (rc == NGX_BUSY) {
if (rev->error) {
ngx_http_close_request(r, NGX_HTTP_CLOSE);
break;
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
break;
}
if (rc == NGX_AGAIN) {
continue;
}
/* rc == NGX_OK || rc == NGX_DONE */
h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL,
&st->field_rep.field.name,
&st->field_rep.field.value);
if (ngx_http_v3_process_header(r, &st->field_rep.field.name,
&st->field_rep.field.value)
!= NGX_OK)
{
break;
}
if (rc == NGX_DONE) {
if (ngx_http_v3_process_request_header(r) != NGX_OK) {
break;
}
ngx_http_process_request(r);
break;
}
}
ngx_http_run_posted_requests(c);
return;
}
static ngx_int_t
ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name,
ngx_str_t *value)
{
size_t len;
ngx_table_elt_t *h;
ngx_http_header_t *hh;
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_main_conf_t *cmcf;
static ngx_str_t cookie = ngx_string("cookie");
len = name->len + value->len;
if (len > r->v3_parse->header_limit) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent too large header");
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
return NGX_ERROR;
}
r->v3_parse->header_limit -= len;
if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
if (r->invalid_header) {
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
if (cscf->ignore_invalid_headers) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent invalid header: \"%V\"", name);
return NGX_OK;
}
}
if (name->len && name->data[0] == ':') {
return ngx_http_v3_process_pseudo_header(r, name, value);
}
if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
return NGX_ERROR;
}
if (name->len == cookie.len
&& ngx_memcmp(name->data, cookie.data, cookie.len) == 0)
{
if (ngx_http_v3_cookie(r, value) != NGX_OK) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
} else {
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
h->key = *name;
h->value = *value;
h->lowcase_key = h->key.data;
h->hash = ngx_hash_key(h->key.data, h->key.len);
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len);
if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
return NGX_ERROR;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 header: \"%V: %V\"", name, value);
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name,
ngx_str_t *value)
{
u_char ch;
ngx_uint_t i;
ngx_http_core_srv_conf_t *cscf;
r->invalid_header = 0;
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
for (i = (name->data[0] == ':'); i != name->len; i++) {
ch = name->data[i];
if ((ch >= 'a' && ch <= 'z')
|| (ch == '-')
|| (ch >= '0' && ch <= '9')
|| (ch == '_' && cscf->underscores_in_headers))
{
continue;
}
if (ch <= 0x20 || ch == 0x7f || ch == ':'
|| (ch >= 'A' && ch <= 'Z'))
{
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent invalid header name: \"%V\"", name);
return NGX_ERROR;
}
r->invalid_header = 1;
}
for (i = 0; i != value->len; i++) {
ch = value->data[i];
if (ch == '\0' || ch == LF || ch == CR) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent header \"%V\" with "
"invalid value: \"%V\"", name, value);
return NGX_ERROR;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name,
ngx_str_t *value)
{
u_char ch, c;
ngx_uint_t i;
if (r->request_line.len) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent out of order pseudo-headers");
goto failed;
}
if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) {
if (r->method_name.len) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent duplicate \":method\" header");
goto failed;
}
if (value->len == 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent empty \":method\" header");
goto failed;
}
r->method_name = *value;
for (i = 0; i < sizeof(ngx_http_v3_methods)
/ sizeof(ngx_http_v3_methods[0]); i++)
{
if (value->len == ngx_http_v3_methods[i].name.len
&& ngx_strncmp(value->data,
ngx_http_v3_methods[i].name.data, value->len)
== 0)
{
r->method = ngx_http_v3_methods[i].method;
break;
}
}
for (i = 0; i < value->len; i++) {
ch = value->data[i];
if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent invalid method: \"%V\"", value);
goto failed;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 method \"%V\" %ui", value, r->method);
return NGX_OK;
}
if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) {
if (r->uri_start) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent duplicate \":path\" header");
goto failed;
}
if (value->len == 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent empty \":path\" header");
goto failed;
}
r->uri_start = value->data;
r->uri_end = value->data + value->len;
if (ngx_http_parse_uri(r) != NGX_OK) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent invalid \":path\" header: \"%V\"",
value);
goto failed;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 path \"%V\"", value);
return NGX_OK;
}
if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) {
if (r->schema.len) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent duplicate \":scheme\" header");
goto failed;
}
if (value->len == 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent empty \":scheme\" header");
goto failed;
}
for (i = 0; i < value->len; i++) {
ch = value->data[i];
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
continue;
}
if (((ch >= '0' && ch <= '9')
|| ch == '+' || ch == '-' || ch == '.')
&& i > 0)
{
continue;
}
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent invalid \":scheme\" header: \"%V\"",
value);
goto failed;
}
r->schema = *value;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 schema \"%V\"", value);
return NGX_OK;
}
if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) {
if (r->host_start) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent duplicate \":authority\" header");
goto failed;
}
r->host_start = value->data;
r->host_end = value->data + value->len;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 authority \"%V\"", value);
return NGX_OK;
}
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent unknown pseudo-header \"%V\"", name);
failed:
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
static ngx_int_t
ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r)
{
size_t len;
u_char *p;
ngx_int_t rc;
ngx_str_t host;
if (r->request_line.len) {
return NGX_OK;
}
if (r->method_name.len == 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent no \":method\" header");
goto failed;
}
if (r->schema.len == 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent no \":scheme\" header");
goto failed;
}
if (r->uri_start == NULL) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent no \":path\" header");
goto failed;
}
len = r->method_name.len + 1
+ (r->uri_end - r->uri_start) + 1
+ sizeof("HTTP/3.0") - 1;
p = ngx_pnalloc(r->pool, len);
if (p == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
r->request_line.data = p;
p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
*p++ = ' ';
p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start);
*p++ = ' ';
p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1);
r->request_line.len = p - r->request_line.data;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 request line: \"%V\"", &r->request_line);
ngx_str_set(&r->http_protocol, "HTTP/3.0");
if (ngx_http_process_request_uri(r) != NGX_OK) {
return NGX_ERROR;
}
if (r->host_end) {
host.len = r->host_end - r->host_start;
host.data = r->host_start;
rc = ngx_http_validate_host(&host, r->pool, 0);
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent invalid host in request line");
goto failed;
}
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
return NGX_ERROR;
}
r->headers_in.server = host;
}
if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
return NGX_OK;
failed:
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
static ngx_int_t
ngx_http_v3_process_request_header(ngx_http_request_t *r)
{
ssize_t n;
ngx_buf_t *b;
ngx_connection_t *c;
c = r->connection;
if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) {
return NGX_ERROR;
}
if (r->headers_in.server.len == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent neither \":authority\" nor \"Host\" header");
goto failed;
}
if (r->headers_in.host) {
if (r->headers_in.host->value.len != r->headers_in.server.len
|| ngx_memcmp(r->headers_in.host->value.data,
r->headers_in.server.data,
r->headers_in.server.len)
!= 0)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent \":authority\" and \"Host\" headers "
"with different values");
goto failed;
}
}
if (r->headers_in.content_length) {
r->headers_in.content_length_n =
ngx_atoof(r->headers_in.content_length->value.data,
r->headers_in.content_length->value.len);
if (r->headers_in.content_length_n == NGX_ERROR) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid \"Content-Length\" header");
goto failed;
}
} else {
b = r->header_in;
n = b->last - b->pos;
if (n == 0) {
n = c->recv(c, b->start, b->end - b->start);
if (n == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (n > 0) {
b->pos = b->start;
b->last = b->start + n;
}
}
if (n != 0) {
r->headers_in.chunked = 1;
}
}
if (r->method == NGX_HTTP_CONNECT) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method");
ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
return NGX_ERROR;
}
if (r->method == NGX_HTTP_TRACE) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method");
ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
return NGX_ERROR;
}
return NGX_OK;
failed:
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
static ngx_int_t
ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value)
{
ngx_str_t *val;
ngx_array_t *cookies;
cookies = r->v3_parse->cookies;
if (cookies == NULL) {
cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t));
if (cookies == NULL) {
return NGX_ERROR;
}
r->v3_parse->cookies = cookies;
}
val = ngx_array_push(cookies);
if (val == NULL) {
return NGX_ERROR;
}
*val = *value;
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_construct_cookie_header(ngx_http_request_t *r)
{
u_char *buf, *p, *end;
size_t len;
ngx_str_t *vals;
ngx_uint_t i;
ngx_array_t *cookies;
ngx_table_elt_t *h;
ngx_http_header_t *hh;
ngx_http_core_main_conf_t *cmcf;
static ngx_str_t cookie = ngx_string("cookie");
cookies = r->v3_parse->cookies;
if (cookies == NULL) {
return NGX_OK;
}
vals = cookies->elts;
i = 0;
len = 0;
do {
len += vals[i].len + 2;
} while (++i != cookies->nelts);
len -= 2;
buf = ngx_pnalloc(r->pool, len + 1);
if (buf == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
p = buf;
end = buf + len;
for (i = 0; /* void */ ; i++) {
p = ngx_cpymem(p, vals[i].data, vals[i].len);
if (p == end) {
*p = '\0';
break;
}
*p++ = ';'; *p++ = ' ';
}
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e');
h->key.len = cookie.len;
h->key.data = cookie.data;
h->value.len = len;
h->value.data = buf;
h->lowcase_key = cookie.data;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len);
if (hh == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_ERROR;
}
if (hh->handler(r, h, hh->offset) != NGX_OK) {
/*
* request has been finalized already
* in ngx_http_process_multi_header_lines()
*/
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_read_request_body(ngx_http_request_t *r)
{
size_t preread;
ngx_int_t rc;
ngx_chain_t *cl, out;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
rb = r->request_body;
preread = r->header_in->last - r->header_in->pos;
if (preread) {
/* there is the pre-read part of the request body */
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 client request body preread %uz", preread);
out.buf = r->header_in;
out.next = NULL;
cl = &out;
} else {
cl = NULL;
}
rc = ngx_http_v3_request_body_filter(r, cl);
if (rc != NGX_OK) {
return rc;
}
if (rb->rest == 0 && rb->last_saved) {
/* the whole request body was pre-read */
r->request_body_no_buffering = 0;
rb->post_handler(r);
return NGX_OK;
}
if (rb->rest < 0) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"negative request body rest");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size);
if (rb->buf == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
r->read_event_handler = ngx_http_v3_read_client_request_body_handler;
r->write_event_handler = ngx_http_request_empty_handler;
return ngx_http_v3_do_read_client_request_body(r);
}
static void
ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
if (r->connection->read->timedout) {
r->connection->timedout = 1;
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
rc = ngx_http_v3_do_read_client_request_body(r);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
ngx_http_finalize_request(r, rc);
}
}
ngx_int_t
ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r)
{
ngx_int_t rc;
if (r->connection->read->timedout) {
r->connection->timedout = 1;
return NGX_HTTP_REQUEST_TIME_OUT;
}
rc = ngx_http_v3_do_read_client_request_body(r);
if (rc == NGX_OK) {
r->reading_body = 0;
}
return rc;
}
static ngx_int_t
ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r)
{
off_t rest;
size_t size;
ssize_t n;
ngx_int_t rc;
ngx_uint_t flush;
ngx_chain_t out;
ngx_connection_t *c;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
rb = r->request_body;
flush = 1;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 read client request body");
for ( ;; ) {
for ( ;; ) {
if (rb->rest == 0) {
break;
}
if (rb->buf->last == rb->buf->end) {
/* update chains */
rc = ngx_http_v3_request_body_filter(r, NULL);
if (rc != NGX_OK) {
return rc;
}
if (rb->busy != NULL) {
if (r->request_body_no_buffering) {
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
if (rb->filter_need_buffering) {
clcf = ngx_http_get_module_loc_conf(r,
ngx_http_core_module);
ngx_add_timer(c->read, clcf->client_body_timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"busy buffers after request body flush");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
flush = 0;
rb->buf->pos = rb->buf->start;
rb->buf->last = rb->buf->start;
}
size = rb->buf->end - rb->buf->last;
rest = rb->rest - (rb->buf->last - rb->buf->pos);
if ((off_t) size > rest) {
size = (size_t) rest;
}
if (size == 0) {
break;
}
n = c->recv(c, rb->buf->last, size);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 client request body recv %z", n);
if (n == NGX_AGAIN) {
break;
}
if (n == 0) {
rb->buf->last_buf = 1;
}
if (n == NGX_ERROR) {
c->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
rb->buf->last += n;
/* pass buffer to request body filter chain */
flush = 0;
out.buf = rb->buf;
out.next = NULL;
rc = ngx_http_v3_request_body_filter(r, &out);
if (rc != NGX_OK) {
return rc;
}
if (rb->rest == 0) {
break;
}
if (rb->buf->last < rb->buf->end) {
break;
}
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 client request body rest %O", rb->rest);
if (flush) {
rc = ngx_http_v3_request_body_filter(r, NULL);
if (rc != NGX_OK) {
return rc;
}
}
if (rb->rest == 0 && rb->last_saved) {
break;
}
if (!c->read->ready || rb->rest == 0) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_add_timer(c->read, clcf->client_body_timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
}
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (!r->request_body_no_buffering) {
r->read_event_handler = ngx_http_block_reading;
rb->post_handler(r);
}
return NGX_OK;
}
static ngx_int_t
ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
off_t max;
size_t size;
u_char *p;
ngx_int_t rc;
ngx_buf_t *b;
ngx_uint_t last;
ngx_chain_t *cl, *out, *tl, **ll;
ngx_http_v3_session_t *h3c;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t *cscf;
ngx_http_v3_parse_data_t *st;
rb = r->request_body;
st = &r->v3_parse->body;
h3c = ngx_http_v3_get_session(r->connection);
if (rb->rest == -1) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 request body filter");
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
rb->rest = cscf->large_client_header_buffers.size;
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
max = r->headers_in.content_length_n;
if (max == -1 && clcf->client_max_body_size) {
max = clcf->client_max_body_size;
}
out = NULL;
ll = &out;
last = 0;
for (cl = in; cl; cl = cl->next) {
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
"http3 body buf "
"t:%d f:%d %p, pos %p, size: %z file: %O, size: %O",
cl->buf->temporary, cl->buf->in_file,
cl->buf->start, cl->buf->pos,
cl->buf->last - cl->buf->pos,
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
if (cl->buf->last_buf) {
last = 1;
}
b = NULL;
while (cl->buf->pos < cl->buf->last) {
if (st->length == 0) {
p = cl->buf->pos;
rc = ngx_http_v3_parse_data(r->connection, st, cl->buf);
r->request_length += cl->buf->pos - p;
h3c->total_bytes += cl->buf->pos - p;
if (ngx_http_v3_check_flood(r->connection) != NGX_OK) {
return NGX_HTTP_CLOSE;
}
if (rc == NGX_AGAIN) {
continue;
}
if (rc == NGX_DONE) {
last = 1;
goto done;
}
if (rc > 0) {
ngx_quic_reset_stream(r->connection, rc);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client sent invalid body");
return NGX_HTTP_BAD_REQUEST;
}
if (rc == NGX_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* rc == NGX_OK */
if (max != -1 && (uint64_t) (max - rb->received) < st->length) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client intended to send too large "
"body: %O+%ui bytes",
rb->received, st->length);
return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
}
continue;
}
if (b
&& st->length <= 128
&& (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length)
{
rb->received += st->length;
r->request_length += st->length;
h3c->total_bytes += st->length;
h3c->payload_bytes += st->length;
if (st->length < 8) {
while (st->length) {
*b->last++ = *cl->buf->pos++;
st->length--;
}
} else {
ngx_memmove(b->last, cl->buf->pos, st->length);
b->last += st->length;
cl->buf->pos += st->length;
st->length = 0;
}
continue;
}
tl = ngx_chain_get_free_buf(r->pool, &rb->free);
if (tl == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b = tl->buf;
ngx_memzero(b, sizeof(ngx_buf_t));
b->temporary = 1;
b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body;
b->start = cl->buf->pos;
b->pos = cl->buf->pos;
b->last = cl->buf->last;
b->end = cl->buf->end;
b->flush = r->request_body_no_buffering;
*ll = tl;
ll = &tl->next;
size = cl->buf->last - cl->buf->pos;
if (size > st->length) {
cl->buf->pos += (size_t) st->length;
rb->received += st->length;
r->request_length += st->length;
h3c->total_bytes += st->length;
h3c->payload_bytes += st->length;
st->length = 0;
} else {
st->length -= size;
rb->received += size;
r->request_length += size;
h3c->total_bytes += size;
h3c->payload_bytes += size;
cl->buf->pos = cl->buf->last;
}
b->last = cl->buf->pos;
}
}
done:
if (last) {
if (st->length > 0) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client prematurely closed stream");
r->connection->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
if (r->headers_in.content_length_n == -1) {
r->headers_in.content_length_n = rb->received;
} else if (r->headers_in.content_length_n != rb->received) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent less body data than expected: "
"%O out of %O bytes of request body received",
rb->received, r->headers_in.content_length_n);
return NGX_HTTP_BAD_REQUEST;
}
rb->rest = 0;
tl = ngx_chain_get_free_buf(r->pool, &rb->free);
if (tl == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b = tl->buf;
ngx_memzero(b, sizeof(ngx_buf_t));
b->last_buf = 1;
*ll = tl;
} else {
/* set rb->rest, amount of data we want to see next time */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
rb->rest = (off_t) cscf->large_client_header_buffers.size;
}
rc = ngx_http_top_request_body_filter(r, out);
ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out,
(ngx_buf_tag_t) &ngx_http_read_client_request_body);
return rc;
}
+720
View File
@@ -0,0 +1,720 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need);
static void ngx_http_v3_unblock(void *data);
static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c);
typedef struct {
ngx_queue_t queue;
ngx_connection_t *connection;
ngx_uint_t *nblocked;
} ngx_http_v3_block_t;
static ngx_http_v3_field_t ngx_http_v3_static_table[] = {
{ ngx_string(":authority"), ngx_string("") },
{ ngx_string(":path"), ngx_string("/") },
{ ngx_string("age"), ngx_string("0") },
{ ngx_string("content-disposition"), ngx_string("") },
{ ngx_string("content-length"), ngx_string("0") },
{ ngx_string("cookie"), ngx_string("") },
{ ngx_string("date"), ngx_string("") },
{ ngx_string("etag"), ngx_string("") },
{ ngx_string("if-modified-since"), ngx_string("") },
{ ngx_string("if-none-match"), ngx_string("") },
{ ngx_string("last-modified"), ngx_string("") },
{ ngx_string("link"), ngx_string("") },
{ ngx_string("location"), ngx_string("") },
{ ngx_string("referer"), ngx_string("") },
{ ngx_string("set-cookie"), ngx_string("") },
{ ngx_string(":method"), ngx_string("CONNECT") },
{ ngx_string(":method"), ngx_string("DELETE") },
{ ngx_string(":method"), ngx_string("GET") },
{ ngx_string(":method"), ngx_string("HEAD") },
{ ngx_string(":method"), ngx_string("OPTIONS") },
{ ngx_string(":method"), ngx_string("POST") },
{ ngx_string(":method"), ngx_string("PUT") },
{ ngx_string(":scheme"), ngx_string("http") },
{ ngx_string(":scheme"), ngx_string("https") },
{ ngx_string(":status"), ngx_string("103") },
{ ngx_string(":status"), ngx_string("200") },
{ ngx_string(":status"), ngx_string("304") },
{ ngx_string(":status"), ngx_string("404") },
{ ngx_string(":status"), ngx_string("503") },
{ ngx_string("accept"), ngx_string("*/*") },
{ ngx_string("accept"),
ngx_string("application/dns-message") },
{ ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") },
{ ngx_string("accept-ranges"), ngx_string("bytes") },
{ ngx_string("access-control-allow-headers"),
ngx_string("cache-control") },
{ ngx_string("access-control-allow-headers"),
ngx_string("content-type") },
{ ngx_string("access-control-allow-origin"),
ngx_string("*") },
{ ngx_string("cache-control"), ngx_string("max-age=0") },
{ ngx_string("cache-control"), ngx_string("max-age=2592000") },
{ ngx_string("cache-control"), ngx_string("max-age=604800") },
{ ngx_string("cache-control"), ngx_string("no-cache") },
{ ngx_string("cache-control"), ngx_string("no-store") },
{ ngx_string("cache-control"),
ngx_string("public, max-age=31536000") },
{ ngx_string("content-encoding"), ngx_string("br") },
{ ngx_string("content-encoding"), ngx_string("gzip") },
{ ngx_string("content-type"),
ngx_string("application/dns-message") },
{ ngx_string("content-type"),
ngx_string("application/javascript") },
{ ngx_string("content-type"), ngx_string("application/json") },
{ ngx_string("content-type"),
ngx_string("application/x-www-form-urlencoded") },
{ ngx_string("content-type"), ngx_string("image/gif") },
{ ngx_string("content-type"), ngx_string("image/jpeg") },
{ ngx_string("content-type"), ngx_string("image/png") },
{ ngx_string("content-type"), ngx_string("text/css") },
{ ngx_string("content-type"),
ngx_string("text/html;charset=utf-8") },
{ ngx_string("content-type"), ngx_string("text/plain") },
{ ngx_string("content-type"),
ngx_string("text/plain;charset=utf-8") },
{ ngx_string("range"), ngx_string("bytes=0-") },
{ ngx_string("strict-transport-security"),
ngx_string("max-age=31536000") },
{ ngx_string("strict-transport-security"),
ngx_string("max-age=31536000;includesubdomains") },
{ ngx_string("strict-transport-security"),
ngx_string("max-age=31536000;includesubdomains;preload") },
{ ngx_string("vary"), ngx_string("accept-encoding") },
{ ngx_string("vary"), ngx_string("origin") },
{ ngx_string("x-content-type-options"),
ngx_string("nosniff") },
{ ngx_string("x-xss-protection"), ngx_string("1;mode=block") },
{ ngx_string(":status"), ngx_string("100") },
{ ngx_string(":status"), ngx_string("204") },
{ ngx_string(":status"), ngx_string("206") },
{ ngx_string(":status"), ngx_string("302") },
{ ngx_string(":status"), ngx_string("400") },
{ ngx_string(":status"), ngx_string("403") },
{ ngx_string(":status"), ngx_string("421") },
{ ngx_string(":status"), ngx_string("425") },
{ ngx_string(":status"), ngx_string("500") },
{ ngx_string("accept-language"), ngx_string("") },
{ ngx_string("access-control-allow-credentials"),
ngx_string("FALSE") },
{ ngx_string("access-control-allow-credentials"),
ngx_string("TRUE") },
{ ngx_string("access-control-allow-headers"),
ngx_string("*") },
{ ngx_string("access-control-allow-methods"),
ngx_string("get") },
{ ngx_string("access-control-allow-methods"),
ngx_string("get, post, options") },
{ ngx_string("access-control-allow-methods"),
ngx_string("options") },
{ ngx_string("access-control-expose-headers"),
ngx_string("content-length") },
{ ngx_string("access-control-request-headers"),
ngx_string("content-type") },
{ ngx_string("access-control-request-method"),
ngx_string("get") },
{ ngx_string("access-control-request-method"),
ngx_string("post") },
{ ngx_string("alt-svc"), ngx_string("clear") },
{ ngx_string("authorization"), ngx_string("") },
{ ngx_string("content-security-policy"),
ngx_string("script-src 'none';object-src 'none';base-uri 'none'") },
{ ngx_string("early-data"), ngx_string("1") },
{ ngx_string("expect-ct"), ngx_string("") },
{ ngx_string("forwarded"), ngx_string("") },
{ ngx_string("if-range"), ngx_string("") },
{ ngx_string("origin"), ngx_string("") },
{ ngx_string("purpose"), ngx_string("prefetch") },
{ ngx_string("server"), ngx_string("") },
{ ngx_string("timing-allow-origin"), ngx_string("*") },
{ ngx_string("upgrade-insecure-requests"),
ngx_string("1") },
{ ngx_string("user-agent"), ngx_string("") },
{ ngx_string("x-forwarded-for"), ngx_string("") },
{ ngx_string("x-frame-options"), ngx_string("deny") },
{ ngx_string("x-frame-options"), ngx_string("sameorigin") }
};
ngx_int_t
ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
ngx_uint_t index, ngx_str_t *value)
{
ngx_str_t name;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
if (dynamic) {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 ref insert dynamic[%ui] \"%V\"", index, value);
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->base + dt->nelts <= index) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
index = dt->base + dt->nelts - 1 - index;
if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
} else {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 ref insert static[%ui] \"%V\"", index, value);
if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
}
return ngx_http_v3_insert(c, &name, value);
}
ngx_int_t
ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value)
{
u_char *p;
size_t size;
ngx_http_v3_field_t *field;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
size = ngx_http_v3_table_entry_size(name, value);
if (ngx_http_v3_evict(c, size) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 insert [%ui] \"%V\":\"%V\", size:%uz",
dt->base + dt->nelts, name, value, size);
p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len,
c->log);
if (p == NULL) {
return NGX_ERROR;
}
field = (ngx_http_v3_field_t *) p;
field->name.data = p + sizeof(ngx_http_v3_field_t);
field->name.len = name->len;
field->value.data = ngx_cpymem(field->name.data, name->data, name->len);
field->value.len = value->len;
ngx_memcpy(field->value.data, value->data, value->len);
dt->elts[dt->nelts++] = field;
dt->size += size;
dt->insert_count++;
ngx_post_event(&dt->send_insert_count, &ngx_posted_events);
if (ngx_http_v3_new_entry(c) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
void
ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
c = ev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 inc insert count handler");
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->insert_count > dt->ack_insert_count) {
if (ngx_http_v3_send_inc_insert_count(c,
dt->insert_count - dt->ack_insert_count)
!= NGX_OK)
{
return;
}
dt->ack_insert_count = dt->insert_count;
}
}
ngx_int_t
ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
{
ngx_uint_t max, prev_max;
ngx_http_v3_field_t **elts;
ngx_http_v3_session_t *h3c;
ngx_http_v3_srv_conf_t *h3scf;
ngx_http_v3_dynamic_table_t *dt;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 set capacity %ui", capacity);
h3c = ngx_http_v3_get_session(c);
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
if (capacity > h3scf->max_table_capacity) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client exceeded http3_max_table_capacity limit");
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
dt = &h3c->table;
if (dt->size > capacity) {
if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
}
max = capacity / 32;
prev_max = dt->capacity / 32;
if (max > prev_max) {
elts = ngx_alloc(max * sizeof(void *), c->log);
if (elts == NULL) {
return NGX_ERROR;
}
if (dt->elts) {
ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *));
ngx_free(dt->elts);
}
dt->elts = elts;
}
dt->capacity = capacity;
return NGX_OK;
}
void
ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c)
{
ngx_uint_t n;
ngx_http_v3_dynamic_table_t *dt;
dt = &h3c->table;
if (dt->elts == NULL) {
return;
}
for (n = 0; n < dt->nelts; n++) {
ngx_free(dt->elts[n]);
}
ngx_free(dt->elts);
}
static ngx_int_t
ngx_http_v3_evict(ngx_connection_t *c, size_t need)
{
size_t size, target;
ngx_uint_t n;
ngx_http_v3_field_t *field;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (need > dt->capacity) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"not enough dynamic table capacity");
return NGX_ERROR;
}
target = dt->capacity - need;
n = 0;
while (dt->size > target) {
field = dt->elts[n++];
size = ngx_http_v3_table_entry_size(&field->name, &field->value);
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 evict [%ui] \"%V\":\"%V\" size:%uz",
dt->base, &field->name, &field->value, size);
ngx_free(field);
dt->size -= size;
}
if (n) {
dt->nelts -= n;
dt->base += n;
ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *));
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
{
ngx_str_t name, value;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->base + dt->nelts <= index) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
index = dt->base + dt->nelts - 1 - index;
if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) {
return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
}
return ngx_http_v3_insert(c, &name, &value);
}
ngx_int_t
ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 ack section %ui", stream_id);
/* we do not use dynamic tables */
return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
}
ngx_int_t
ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 increment insert count %ui", inc);
/* we do not use dynamic tables */
return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
}
ngx_int_t
ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
ngx_str_t *name, ngx_str_t *value)
{
ngx_uint_t nelts;
ngx_http_v3_field_t *field;
nelts = sizeof(ngx_http_v3_static_table)
/ sizeof(ngx_http_v3_static_table[0]);
if (index >= nelts) {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 static[%ui] lookup out of bounds: %ui",
index, nelts);
return NGX_ERROR;
}
field = &ngx_http_v3_static_table[index];
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 static[%ui] lookup \"%V\":\"%V\"",
index, &field->name, &field->value);
if (name) {
*name = field->name;
}
if (value) {
*value = field->value;
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name,
ngx_str_t *value)
{
ngx_http_v3_field_t *field;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (index < dt->base || index - dt->base >= dt->nelts) {
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]",
index, dt->base, dt->base + dt->nelts);
return NGX_ERROR;
}
field = dt->elts[index - dt->base];
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 dynamic[%ui] lookup \"%V\":\"%V\"",
index, &field->name, &field->value);
if (name) {
*name = field->name;
}
if (value) {
*value = field->value;
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count)
{
ngx_uint_t max_entries, full_range, max_value,
max_wrapped, req_insert_count;
ngx_http_v3_srv_conf_t *h3scf;
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
/* QPACK 4.5.1.1. Required Insert Count */
if (*insert_count == 0) {
return NGX_OK;
}
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
max_entries = h3scf->max_table_capacity / 32;
full_range = 2 * max_entries;
if (*insert_count > full_range) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
max_value = dt->base + dt->nelts + max_entries;
max_wrapped = (max_value / full_range) * full_range;
req_insert_count = max_wrapped + *insert_count - 1;
if (req_insert_count > max_value) {
if (req_insert_count <= full_range) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
req_insert_count -= full_range;
}
if (req_insert_count == 0) {
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 decode insert_count %ui -> %ui",
*insert_count, req_insert_count);
*insert_count = req_insert_count;
return NGX_OK;
}
ngx_int_t
ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
{
size_t n;
ngx_pool_cleanup_t *cln;
ngx_http_v3_block_t *block;
ngx_http_v3_session_t *h3c;
ngx_http_v3_srv_conf_t *h3scf;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
n = dt->base + dt->nelts;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 check insert count req:%ui, have:%ui",
insert_count, n);
if (n >= insert_count) {
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream");
block = NULL;
for (cln = c->pool->cleanup; cln; cln = cln->next) {
if (cln->handler == ngx_http_v3_unblock) {
block = cln->data;
break;
}
}
if (block == NULL) {
cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t));
if (cln == NULL) {
return NGX_ERROR;
}
cln->handler = ngx_http_v3_unblock;
block = cln->data;
block->queue.prev = NULL;
block->connection = c;
block->nblocked = &h3c->nblocked;
}
if (block->queue.prev == NULL) {
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
if (h3c->nblocked == h3scf->max_blocked_streams) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client exceeded http3_max_blocked_streams limit");
ngx_http_v3_finalize_connection(c,
NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED,
"too many blocked streams");
return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
}
h3c->nblocked++;
ngx_queue_insert_tail(&h3c->blocked, &block->queue);
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 blocked:%ui", h3c->nblocked);
return NGX_BUSY;
}
void
ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count)
{
ngx_http_v3_session_t *h3c;
ngx_http_v3_dynamic_table_t *dt;
h3c = ngx_http_v3_get_session(c);
dt = &h3c->table;
if (dt->ack_insert_count < insert_count) {
dt->ack_insert_count = insert_count;
}
}
static void
ngx_http_v3_unblock(void *data)
{
ngx_http_v3_block_t *block = data;
if (block->queue.prev) {
ngx_queue_remove(&block->queue);
block->queue.prev = NULL;
(*block->nblocked)--;
}
}
static ngx_int_t
ngx_http_v3_new_entry(ngx_connection_t *c)
{
ngx_queue_t *q;
ngx_connection_t *bc;
ngx_http_v3_block_t *block;
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(c);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 new dynamic entry, blocked:%ui", h3c->nblocked);
while (!ngx_queue_empty(&h3c->blocked)) {
q = ngx_queue_head(&h3c->blocked);
block = (ngx_http_v3_block_t *) q;
bc = block->connection;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream");
ngx_http_v3_unblock(block);
ngx_post_event(bc->read, &ngx_posted_events);
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value)
{
switch (id) {
case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value);
break;
case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL",
value);
break;
case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param QPACK_BLOCKED_STREAMS:%uL", value);
break;
default:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param #%uL:%uL", id, value);
}
return NGX_OK;
}
+58
View File
@@ -0,0 +1,58 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_
#define _NGX_HTTP_V3_TABLE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_str_t name;
ngx_str_t value;
} ngx_http_v3_field_t;
typedef struct {
ngx_http_v3_field_t **elts;
ngx_uint_t nelts;
ngx_uint_t base;
size_t size;
size_t capacity;
uint64_t insert_count;
uint64_t ack_insert_count;
ngx_event_t send_insert_count;
} ngx_http_v3_dynamic_table_t;
void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev);
void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c);
ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
ngx_uint_t index, ngx_str_t *value);
ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
ngx_str_t *value);
ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity);
ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index);
ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc);
ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
ngx_str_t *name, ngx_str_t *value);
ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index,
ngx_str_t *name, ngx_str_t *value);
ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c,
ngx_uint_t *insert_count);
ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
ngx_uint_t insert_count);
void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count);
ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
uint64_t value);
#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */
+760
View File
@@ -0,0 +1,760 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_http_v3_parse_uni_t parse;
ngx_int_t index;
} ngx_http_v3_uni_stream_t;
typedef struct {
ngx_queue_t queue;
uint64_t id;
ngx_connection_t *connection;
ngx_uint_t *npushing;
} ngx_http_v3_push_t;
static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev);
static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev);
static void ngx_http_v3_push_cleanup(void *data);
static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
ngx_uint_t type);
void
ngx_http_v3_init_uni_stream(ngx_connection_t *c)
{
uint64_t n;
ngx_http_v3_uni_stream_t *us;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream");
n = c->quic->id >> 2;
if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) {
ngx_http_v3_finalize_connection(c,
NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
"reached maximum number of uni streams");
c->data = NULL;
ngx_http_v3_close_uni_stream(c);
return;
}
c->quic->cancelable = 1;
us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
if (us == NULL) {
ngx_http_v3_finalize_connection(c,
NGX_HTTP_V3_ERR_INTERNAL_ERROR,
"memory allocation error");
c->data = NULL;
ngx_http_v3_close_uni_stream(c);
return;
}
us->index = -1;
c->data = us;
c->read->handler = ngx_http_v3_uni_read_handler;
c->write->handler = ngx_http_v3_uni_dummy_write_handler;
ngx_http_v3_uni_read_handler(c->read);
}
static void
ngx_http_v3_close_uni_stream(ngx_connection_t *c)
{
ngx_pool_t *pool;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");
us = c->data;
if (us && us->index >= 0) {
h3c = ngx_http_v3_get_session(c);
h3c->known_streams[us->index] = NULL;
}
c->destroyed = 1;
pool = c->pool;
ngx_close_connection(c);
ngx_destroy_pool(pool);
}
ngx_int_t
ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type)
{
ngx_int_t index;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
h3c = ngx_http_v3_get_session(c);
switch (type) {
case NGX_HTTP_V3_STREAM_ENCODER:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 encoder stream");
index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER;
break;
case NGX_HTTP_V3_STREAM_DECODER:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 decoder stream");
index = NGX_HTTP_V3_STREAM_CLIENT_DECODER;
break;
case NGX_HTTP_V3_STREAM_CONTROL:
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 control stream");
index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL;
break;
default:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 stream 0x%02xL", type);
if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL
|| h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL
|| h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream");
return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
}
index = -1;
}
if (index >= 0) {
if (h3c->known_streams[index]) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
}
h3c->known_streams[index] = c;
us = c->data;
us->index = index;
}
return NGX_OK;
}
static void
ngx_http_v3_uni_read_handler(ngx_event_t *rev)
{
u_char buf[128];
ssize_t n;
ngx_buf_t b;
ngx_int_t rc;
ngx_connection_t *c;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
c = rev->data;
us = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler");
ngx_memzero(&b, sizeof(ngx_buf_t));
while (rev->ready) {
n = c->recv(c, buf, sizeof(buf));
if (n == NGX_ERROR) {
rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
goto failed;
}
if (n == 0) {
if (us->index >= 0) {
rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM;
goto failed;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof");
ngx_http_v3_close_uni_stream(c);
return;
}
if (n == NGX_AGAIN) {
break;
}
b.pos = buf;
b.last = buf + n;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (ngx_http_v3_check_flood(c) != NGX_OK) {
ngx_http_v3_close_uni_stream(c);
return;
}
rc = ngx_http_v3_parse_uni(c, &us->parse, &b);
if (rc == NGX_DONE) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 read done");
ngx_http_v3_close_uni_stream(c);
return;
}
if (rc > 0) {
goto failed;
}
if (rc != NGX_AGAIN) {
rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
goto failed;
}
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
goto failed;
}
return;
failed:
ngx_http_v3_finalize_connection(c, rc, "stream error");
ngx_http_v3_close_uni_stream(c);
}
static void
ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev)
{
u_char ch;
ngx_connection_t *c;
c = rev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler");
if (rev->ready) {
if (c->recv(c, &ch, 1) != 0) {
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL);
ngx_http_v3_close_uni_stream(c);
return;
}
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
NULL);
ngx_http_v3_close_uni_stream(c);
}
}
static void
ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev)
{
ngx_connection_t *c;
c = wev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
NULL);
ngx_http_v3_close_uni_stream(c);
}
}
ngx_connection_t *
ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id)
{
u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2];
size_t n;
ngx_connection_t *sc;
ngx_pool_cleanup_t *cln;
ngx_http_v3_push_t *push;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 create push stream id:%uL", push_id);
sc = ngx_quic_open_stream(c, 0);
if (sc == NULL) {
goto failed;
}
p = buf;
p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id);
n = p - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (sc->send(sc, buf, n) != (ssize_t) n) {
goto failed;
}
cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t));
if (cln == NULL) {
goto failed;
}
h3c->npushing++;
cln->handler = ngx_http_v3_push_cleanup;
push = cln->data;
push->id = push_id;
push->connection = sc;
push->npushing = &h3c->npushing;
ngx_queue_insert_tail(&h3c->pushing, &push->queue);
return sc;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
"failed to create push stream");
if (sc) {
ngx_http_v3_close_uni_stream(sc);
}
return NULL;
}
static void
ngx_http_v3_push_cleanup(void *data)
{
ngx_http_v3_push_t *push = data;
ngx_queue_remove(&push->queue);
(*push->npushing)--;
}
static ngx_connection_t *
ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type)
{
u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN];
size_t n;
ngx_int_t index;
ngx_connection_t *sc;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
switch (type) {
case NGX_HTTP_V3_STREAM_ENCODER:
index = NGX_HTTP_V3_STREAM_SERVER_ENCODER;
break;
case NGX_HTTP_V3_STREAM_DECODER:
index = NGX_HTTP_V3_STREAM_SERVER_DECODER;
break;
case NGX_HTTP_V3_STREAM_CONTROL:
index = NGX_HTTP_V3_STREAM_SERVER_CONTROL;
break;
default:
index = -1;
}
h3c = ngx_http_v3_get_session(c);
if (index >= 0) {
if (h3c->known_streams[index]) {
return h3c->known_streams[index];
}
}
sc = ngx_quic_open_stream(c, 0);
if (sc == NULL) {
goto failed;
}
sc->quic->cancelable = 1;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 create uni stream, type:%ui", type);
us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
if (us == NULL) {
goto failed;
}
us->index = index;
sc->data = us;
sc->read->handler = ngx_http_v3_uni_dummy_read_handler;
sc->write->handler = ngx_http_v3_uni_dummy_write_handler;
if (index >= 0) {
h3c->known_streams[index] = sc;
}
n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (sc->send(sc, buf, n) != (ssize_t) n) {
goto failed;
}
ngx_post_event(sc->read, &ngx_posted_events);
return sc;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
"failed to create server stream");
if (sc) {
ngx_http_v3_close_uni_stream(sc);
}
return NULL;
}
ngx_int_t
ngx_http_v3_send_settings(ngx_connection_t *c)
{
u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6];
size_t n;
ngx_connection_t *cc;
ngx_http_v3_session_t *h3c;
ngx_http_v3_srv_conf_t *h3scf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings");
cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
if (cc == NULL) {
return NGX_ERROR;
}
h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
n = ngx_http_v3_encode_varlen_int(NULL,
NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity);
n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams);
p = (u_char *) ngx_http_v3_encode_varlen_int(buf,
NGX_HTTP_V3_FRAME_SETTINGS);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
p = (u_char *) ngx_http_v3_encode_varlen_int(p,
NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity);
p = (u_char *) ngx_http_v3_encode_varlen_int(p,
NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams);
n = p - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (cc->send(cc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send settings");
ngx_http_v3_close_uni_stream(cc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id)
{
u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3];
size_t n;
ngx_connection_t *cc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id);
cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
if (cc == NULL) {
return NGX_ERROR;
}
n = ngx_http_v3_encode_varlen_int(NULL, id);
p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
p = (u_char *) ngx_http_v3_encode_varlen_int(p, id);
n = p - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (cc->send(cc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send goaway");
ngx_http_v3_close_uni_stream(cc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
{
u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
size_t n;
ngx_connection_t *dc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 send section acknowledgement %ui", stream_id);
dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
if (dc == NULL) {
return NGX_ERROR;
}
buf[0] = 0x80;
n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (dc->send(dc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"failed to send section acknowledgement");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send section acknowledgement");
ngx_http_v3_close_uni_stream(dc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
{
u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
size_t n;
ngx_connection_t *dc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 send stream cancellation %ui", stream_id);
dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
if (dc == NULL) {
return NGX_ERROR;
}
buf[0] = 0x40;
n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (dc->send(dc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send stream cancellation");
ngx_http_v3_close_uni_stream(dc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
{
u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
size_t n;
ngx_connection_t *dc;
ngx_http_v3_session_t *h3c;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 send insert count increment %ui", inc);
dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
if (dc == NULL) {
return NGX_ERROR;
}
buf[0] = 0;
n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf;
h3c = ngx_http_v3_get_session(c);
h3c->total_bytes += n;
if (dc->send(dc, buf, n) != (ssize_t) n) {
goto failed;
}
return NGX_OK;
failed:
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"failed to send insert count increment");
ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
"failed to send insert count increment");
ngx_http_v3_close_uni_stream(dc);
return NGX_ERROR;
}
ngx_int_t
ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id)
{
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(c);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 MAX_PUSH_ID:%uL", max_push_id);
if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) {
return NGX_HTTP_V3_ERR_ID_ERROR;
}
h3c->max_push_id = max_push_id;
return NGX_OK;
}
ngx_int_t
ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id)
{
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(c);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id);
h3c->goaway_push_id = push_id;
return NGX_OK;
}
ngx_int_t
ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id)
{
ngx_queue_t *q;
ngx_http_request_t *r;
ngx_http_v3_push_t *push;
ngx_http_v3_session_t *h3c;
h3c = ngx_http_v3_get_session(c);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 CANCEL_PUSH:%uL", push_id);
if (push_id >= h3c->next_push_id) {
return NGX_HTTP_V3_ERR_ID_ERROR;
}
for (q = ngx_queue_head(&h3c->pushing);
q != ngx_queue_sentinel(&h3c->pushing);
q = ngx_queue_next(&h3c->pushing))
{
push = (ngx_http_v3_push_t *) q;
if (push->id != push_id) {
continue;
}
r = push->connection->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http3 cancel push");
ngx_http_finalize_request(r, NGX_HTTP_CLOSE);
break;
}
return NGX_OK;
}
ngx_int_t
ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 cancel stream %ui", stream_id);
/* we do not use dynamic tables */
return NGX_OK;
}
+38
View File
@@ -0,0 +1,38 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_
#define _NGX_HTTP_V3_UNI_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
void ngx_http_v3_init_uni_stream(ngx_connection_t *c);
ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type);
ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c,
uint64_t push_id);
ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c,
uint64_t max_push_id);
ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id);
ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id);
ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id);
ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c,
ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c,
ngx_uint_t stream_id);
ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c,
ngx_uint_t inc);
#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */
+2
View File
@@ -13,6 +13,8 @@
#define NGX_WRITE_SHUTDOWN SHUT_WR
#define NGX_READ_SHUTDOWN SHUT_RD
#define NGX_RDWR_SHUTDOWN SHUT_RDWR
typedef int ngx_socket_t;
+24
View File
@@ -518,6 +518,24 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
ls->reuseport = addr[i].opt.reuseport;
#endif
#if (NGX_STREAM_QUIC)
ls->quic = addr[i].opt.quic;
if (ls->quic) {
ngx_rbtree_init(&ls->rbtree, &ls->sentinel,
ngx_quic_rbtree_insert_value);
}
#endif
#if !(NGX_WIN32)
if (!ls->quic) {
ngx_rbtree_init(&ls->rbtree, &ls->sentinel,
ngx_udp_rbtree_insert_value);
}
#endif
stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t));
if (stport == NULL) {
return NGX_CONF_ERROR;
@@ -575,6 +593,9 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport,
addrs[i].conf.ctx = addr[i].opt.ctx;
#if (NGX_STREAM_SSL)
addrs[i].conf.ssl = addr[i].opt.ssl;
#endif
#if (NGX_STREAM_QUIC)
addrs[i].conf.quic = addr[i].opt.quic;
#endif
addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
addrs[i].conf.addr_text = addr[i].opt.addr_text;
@@ -610,6 +631,9 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport,
addrs6[i].conf.ctx = addr[i].opt.ctx;
#if (NGX_STREAM_SSL)
addrs6[i].conf.ssl = addr[i].opt.ssl;
#endif
#if (NGX_STREAM_QUIC)
addrs6[i].conf.quic = addr[i].opt.quic;
#endif
addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
addrs6[i].conf.addr_text = addr[i].opt.addr_text;
+6
View File
@@ -16,6 +16,10 @@
#include <ngx_stream_ssl_module.h>
#endif
#if (NGX_STREAM_QUIC)
#include <ngx_stream_quic_module.h>
#endif
typedef struct ngx_stream_session_s ngx_stream_session_t;
@@ -51,6 +55,7 @@ typedef struct {
unsigned bind:1;
unsigned wildcard:1;
unsigned ssl:1;
unsigned quic:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@@ -76,6 +81,7 @@ typedef struct {
ngx_stream_conf_ctx_t *ctx;
ngx_str_t addr_text;
unsigned ssl:1;
unsigned quic:1;
unsigned proxy_protocol:1;
} ngx_stream_addr_conf_t;
+29
View File
@@ -760,6 +760,29 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
if (ngx_strcmp(value[i].data, "quic") == 0) {
#if (NGX_STREAM_QUIC)
ngx_stream_ssl_conf_t *sslcf;
sslcf = ngx_stream_conf_get_module_srv_conf(cf,
ngx_stream_ssl_module);
sslcf->listen = 1;
sslcf->file = cf->conf_file->file.name.data;
sslcf->line = cf->conf_file->line;
ls->quic = 1;
ls->type = SOCK_DGRAM;
continue;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the \"quic\" parameter requires "
"ngx_stream_quic_module");
return NGX_CONF_ERROR;
#endif
}
if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) {
if (ngx_strcmp(&value[i].data[13], "on") == 0) {
@@ -871,6 +894,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}
#endif
#if (NGX_STREAM_SSL && NGX_STREAM_QUIC)
if (ls->ssl && ls->quic) {
return "\"ssl\" parameter is incompatible with \"quic\"";
}
#endif
if (ls->so_keepalive) {
return "\"so_keepalive\" parameter is incompatible with \"udp\"";
}
+19
View File
@@ -129,6 +129,10 @@ ngx_stream_init_connection(ngx_connection_t *c)
s->ssl = addr_conf->ssl;
#endif
#if (NGX_STREAM_QUIC)
s->ssl |= addr_conf->quic;
#endif
if (c->buffer) {
s->received += c->buffer->last - c->buffer->pos;
}
@@ -173,6 +177,21 @@ ngx_stream_init_connection(ngx_connection_t *c)
s->start_sec = tp->sec;
s->start_msec = tp->msec;
#if (NGX_STREAM_QUIC)
if (addr_conf->quic) {
ngx_quic_conf_t *qcf;
if (c->quic == NULL) {
qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx,
ngx_stream_quic_module);
ngx_quic_run(c, qcf);
return;
}
}
#endif
rev = c->read;
rev->handler = ngx_stream_session_handler;
+15
View File
@@ -1771,6 +1771,21 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
if (dst->type == SOCK_STREAM && pscf->half_close
&& src->read->eof && !u->half_closed && !dst->buffered)
{
#if (NGX_STREAM_QUIC)
if (dst->quic) {
if (ngx_quic_shutdown_stream(dst, NGX_WRITE_SHUTDOWN)
!= NGX_OK)
{
ngx_stream_proxy_finalize(s,
NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}
} else
#endif
if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) {
ngx_connection_error(c, ngx_socket_errno,
ngx_shutdown_socket_n " failed");
+377
View File
@@ -0,0 +1,377 @@
/*
* Copyright (C) Nginx, Inc.
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>
static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf);
static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf);
static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent,
void *child);
static char *ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data);
static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_conf_post_t ngx_stream_quic_mtu_post =
{ ngx_stream_quic_mtu };
static ngx_command_t ngx_stream_quic_commands[] = {
{ ngx_string("quic_timeout"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_quic_conf_t, timeout),
NULL },
{ ngx_string("quic_mtu"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_quic_conf_t, mtu),
&ngx_stream_quic_mtu_post },
{ ngx_string("quic_stream_buffer_size"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_quic_conf_t, stream_buffer_size),
NULL },
{ ngx_string("quic_retry"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_quic_conf_t, retry),
NULL },
{ ngx_string("quic_gso"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_quic_conf_t, gso_enabled),
NULL },
{ ngx_string("quic_host_key"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
ngx_stream_quic_host_key,
NGX_STREAM_SRV_CONF_OFFSET,
0,
NULL },
{ ngx_string("quic_active_connection_id_limit"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_STREAM_SRV_CONF_OFFSET,
offsetof(ngx_quic_conf_t, active_connection_id_limit),
NULL },
ngx_null_command
};
static ngx_stream_module_t ngx_stream_quic_module_ctx = {
ngx_stream_quic_add_variables, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
ngx_stream_quic_create_srv_conf, /* create server configuration */
ngx_stream_quic_merge_srv_conf, /* merge server configuration */
};
ngx_module_t ngx_stream_quic_module = {
NGX_MODULE_V1,
&ngx_stream_quic_module_ctx, /* module context */
ngx_stream_quic_commands, /* module directives */
NGX_STREAM_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_stream_variable_t ngx_stream_quic_vars[] = {
{ ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 },
ngx_stream_null_variable
};
static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic");
static ngx_int_t
ngx_stream_variable_quic(ngx_stream_session_t *s,
ngx_stream_variable_value_t *v, uintptr_t data)
{
if (s->connection->quic) {
v->len = 4;
v->valid = 1;
v->no_cacheable = 1;
v->not_found = 0;
v->data = (u_char *) "quic";
return NGX_OK;
}
v->not_found = 1;
return NGX_OK;
}
static ngx_int_t
ngx_stream_quic_add_variables(ngx_conf_t *cf)
{
ngx_stream_variable_t *var, *v;
for (v = ngx_stream_quic_vars; v->name.len; v++) {
var = ngx_stream_add_variable(cf, &v->name, v->flags);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = v->get_handler;
var->data = v->data;
}
return NGX_OK;
}
static void *
ngx_stream_quic_create_srv_conf(ngx_conf_t *cf)
{
ngx_quic_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t));
if (conf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* conf->host_key = { 0, NULL }
* conf->stream_close_code = 0;
* conf->stream_reject_code_uni = 0;
* conf->stream_reject_code_bidi= 0;
*/
conf->timeout = NGX_CONF_UNSET_MSEC;
conf->mtu = NGX_CONF_UNSET_SIZE;
conf->stream_buffer_size = NGX_CONF_UNSET_SIZE;
conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT;
conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT;
conf->retry = NGX_CONF_UNSET;
conf->gso_enabled = NGX_CONF_UNSET;
conf->active_connection_id_limit = NGX_CONF_UNSET_UINT;
return conf;
}
static char *
ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_quic_conf_t *prev = parent;
ngx_quic_conf_t *conf = child;
ngx_stream_ssl_conf_t *scf;
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
ngx_conf_merge_size_value(conf->mtu, prev->mtu,
NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
ngx_conf_merge_size_value(conf->stream_buffer_size,
prev->stream_buffer_size,
65536);
ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi,
prev->max_concurrent_streams_bidi, 16);
ngx_conf_merge_uint_value(conf->max_concurrent_streams_uni,
prev->max_concurrent_streams_uni, 3);
ngx_conf_merge_value(conf->retry, prev->retry, 0);
ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0);
ngx_conf_merge_str_value(conf->host_key, prev->host_key, "");
ngx_conf_merge_uint_value(conf->active_connection_id_limit,
conf->active_connection_id_limit,
2);
if (conf->host_key.len == 0) {
conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN;
conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len);
if (conf->host_key.data == NULL) {
return NGX_CONF_ERROR;
}
if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN)
<= 0)
{
return NGX_CONF_ERROR;
}
}
if (ngx_quic_derive_key(cf->log, "av_token_key",
&conf->host_key, &ngx_stream_quic_salt,
conf->av_token_key, NGX_QUIC_AV_KEY_LEN)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
if (ngx_quic_derive_key(cf->log, "sr_token_key",
&conf->host_key, &ngx_stream_quic_salt,
conf->sr_token_key, NGX_QUIC_SR_KEY_LEN)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module);
conf->ssl = &scf->ssl;
return NGX_CONF_OK;
}
static char *
ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data)
{
size_t *sp = data;
if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
|| *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"quic_mtu\" must be between %d and %d",
NGX_QUIC_MIN_INITIAL_SIZE,
NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
static char *
ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_quic_conf_t *qcf = conf;
u_char *buf;
size_t size;
ssize_t n;
ngx_str_t *value;
ngx_file_t file;
ngx_file_info_t fi;
if (qcf->host_key.len) {
return "is duplicate";
}
buf = NULL;
#if (NGX_SUPPRESS_WARN)
size = 0;
#endif
value = cf->args->elts;
if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) {
return NGX_CONF_ERROR;
}
ngx_memzero(&file, sizeof(ngx_file_t));
file.name = value[1];
file.log = cf->log;
file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
if (file.fd == NGX_INVALID_FILE) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
ngx_open_file_n " \"%V\" failed", &file.name);
return NGX_CONF_ERROR;
}
if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
ngx_fd_info_n " \"%V\" failed", &file.name);
goto failed;
}
size = ngx_file_size(&fi);
if (size == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%V\" zero key size", &file.name);
goto failed;
}
buf = ngx_pnalloc(cf->pool, size);
if (buf == NULL) {
goto failed;
}
n = ngx_read_file(&file, buf, size, 0);
if (n == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
ngx_read_file_n " \"%V\" failed", &file.name);
goto failed;
}
if ((size_t) n != size) {
ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
ngx_read_file_n " \"%V\" returned only "
"%z bytes instead of %uz", &file.name, n, size);
goto failed;
}
qcf->host_key.data = buf;
qcf->host_key.len = n;
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &file.name);
}
return NGX_CONF_OK;
failed:
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &file.name);
}
if (buf) {
ngx_explicit_memzero(buf, size);
}
return NGX_CONF_ERROR;
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_STREAM_QUIC_H_INCLUDED_
#define _NGX_STREAM_QUIC_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>
extern ngx_module_t ngx_stream_quic_module;
#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */
+21
View File
@@ -1194,7 +1194,10 @@ ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data)
static ngx_int_t
ngx_stream_ssl_init(ngx_conf_t *cf)
{
ngx_uint_t i;
ngx_stream_listen_t *listen;
ngx_stream_handler_pt *h;
ngx_stream_ssl_conf_t *scf;
ngx_stream_core_main_conf_t *cmcf;
cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
@@ -1206,5 +1209,23 @@ ngx_stream_ssl_init(ngx_conf_t *cf)
*h = ngx_stream_ssl_handler;
listen = cmcf->listen.elts;
for (i = 0; i < cmcf->listen.nelts; i++) {
if (!listen[i].quic) {
continue;
}
scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index];
if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"\"ssl_protocols\" must enable TLSv1.3 for "
"the \"listen ... quic\" directive in %s:%ui",
scf->file, scf->line);
return NGX_ERROR;
}
}
return NGX_OK;
}