aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJochen Neumeister <joneum@FreeBSD.org>2023-04-17 10:13:29 +0000
committerJochen Neumeister <joneum@FreeBSD.org>2023-04-17 10:20:52 +0000
commit364b314d88996ec0e94b44e8b4297bb52170e4cb (patch)
treec2d7e1e5be8431023f53bae686fb5b87afb010b1
parent309ccc1a49a0d268d35e05ec42180ae92af477c9 (diff)
downloadports-364b314d88996ec0e94b44e8b4297bb52170e4cb.tar.gz
ports-364b314d88996ec0e94b44e8b4297bb52170e4cb.zip
www/nginx: Update to 1.24.0
Update NGINX to 1.24.0 Overview of the changes from nginx-devel. Osa has made many good changes in the last devel phases. These have been adopted into the nginx port (httpv3 ....). Also all modules were updated to the latest version Sponsored by: Netzkommune GmbH
-rw-r--r--www/nginx/Makefile127
-rw-r--r--www/nginx/Makefile.extmod179
-rw-r--r--www/nginx/Makefile.options.desc23
-rw-r--r--www/nginx/distinfo154
-rw-r--r--www/nginx/files/extra-patch-dynamic-tls225
-rw-r--r--www/nginx/files/extra-patch-h2-autotune449
-rw-r--r--www/nginx/files/extra-patch-httpv326668
-rw-r--r--www/nginx/files/extra-patch-masterzen-nginx-upload-progress-module9
-rw-r--r--www/nginx/files/extra-patch-naxsi_runtime.c23
-rw-r--r--www/nginx/files/extra-patch-nginx-ct-LibreSSL18
-rw-r--r--www/nginx/files/extra-patch-nginx-opentracing-opentracing-config4
-rw-r--r--www/nginx/files/extra-patch-nginx-vod-module-config17
-rw-r--r--www/nginx/files/extra-patch-ngx_dynamic_healthcheck-config11
-rw-r--r--www/nginx/files/extra-patch-ngx_http_auth_digest_module.c30
-rw-r--r--www/nginx/files/extra-patch-ngx_http_auth_ldap_module.c10
-rw-r--r--www/nginx/files/extra-patch-ngx_http_auth_spnego_module.c89
-rw-r--r--www/nginx/files/extra-patch-ngx_http_clojure_mem.h11
-rw-r--r--www/nginx/files/extra-patch-ngx_http_redis_module.c34
-rw-r--r--www/nginx/files/extra-patch-ngx_http_response-config11
-rw-r--r--www/nginx/files/extra-patch-ngx_http_uploadprogress_module.c73
-rw-r--r--www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c89
-rw-r--r--www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c.n21
-rw-r--r--www/nginx/files/extra-patch-openresty-drizzle-nginx-module-config10
-rw-r--r--www/nginx/files/extra-patch-openresty-set-misc-nginx-module-config13
-rw-r--r--www/nginx/files/extra-patch-passenger-build-nginx.rb10
-rw-r--r--www/nginx/files/extra-patch-passenger-disable-telemetry4
-rw-r--r--www/nginx/files/extra-patch-spnego-http-auth-nginx-module-config7
-rw-r--r--www/nginx/files/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c (renamed from www/nginx/files/extra-patch-src_http_modules_ngx__http__slice_read_ahead.c)0
-rw-r--r--www/nginx/files/nginx.in4
-rw-r--r--www/nginx/files/patch-conf-nginx.conf2
-rw-r--r--www/nginx/pkg-plist53
31 files changed, 27168 insertions, 1210 deletions
diff --git a/www/nginx/Makefile b/www/nginx/Makefile
index 0a498aa230a0..ee1a87c3082a 100644
--- a/www/nginx/Makefile
+++ b/www/nginx/Makefile
@@ -1,10 +1,9 @@
PORTNAME= nginx
-PORTVERSION= 1.22.1
-PORTREVISION?= 5
+PORTVERSION= 1.24.0
+PORTREVISION?= 0
PORTEPOCH= 3
CATEGORIES= www
MASTER_SITES= https://nginx.org/download/ \
- http://nginx.org/download/ \
LOCAL/joneum
DISTFILES= ${DISTNAME}${EXTRACT_SUFX}
@@ -15,14 +14,11 @@ WWW= https://nginx.com/
LICENSE= BSD2CLAUSE
LICENSE_FILE= ${WRKSRC}/LICENSE
-BROKEN_mips= error ngx_spinlock() or ngx_atomic_cmp_set() are not defined
-BROKEN_mips64= error ngx_spinlock() or ngx_atomic_cmp_set() are not defined
-
CONFLICTS_INSTALL= nginx-devel
-PORTSCOUT= limit:^1\.22\.[0-9]*
+PORTSCOUT= limit:^1\.24\.[0-9]*
-USES= compiler:c11 cpe
+USES= cpe
CPE_VENDOR= f5
CPE_PRODUCT= nginx
@@ -41,8 +37,8 @@ CONFLICTS?= nginx-devel-1.* \
nginx-full-1.* \
nginx-lite-1.* \
nginx-naxsi-1.*
-USE_RC_SUBR= nginx
-SUB_FILES= pkg-message
+USE_RC_SUBR?= nginx
+SUB_FILES?= pkg-message
SUB_LIST+= WWWOWN=${WWWOWN} \
WWWGRP=${WWWGRP} \
NGINX_RUNDIR=${NGINX_RUNDIR} \
@@ -52,7 +48,6 @@ SUB_LIST+= WWWOWN=${WWWOWN} \
HAS_CONFIGURE= yes
CONFIGURE_ARGS+=--prefix=${ETCDIR} \
--with-cc-opt="-I ${LOCALBASE}/include" \
- --with-ld-opt="-L ${LOCALBASE}/lib" \
--conf-path=${ETCDIR}/nginx.conf \
--sbin-path=${PREFIX}/sbin/nginx \
--pid-path=${NGINX_RUNDIR}/nginx.pid \
@@ -69,48 +64,28 @@ GROUPS?=${WWWGRP}
NO_OPTIONS_SORT= yes
-OPTIONS_GROUP= MAILGRP HTTPGRP STREAMGRP
+OPTIONS_GROUP= HTTPGRP MAILGRP STREAMGRP
# Modules that are part of the base nginx distribution
OPTIONS_GROUP_HTTPGRP= GOOGLE_PERFTOOLS HTTP HTTP_ADDITION HTTP_AUTH_REQ \
- HTTP_CACHE HTTP_DAV HTTP_FLV HTTP_GUNZIP_FILTER HTTP_GZIP_STATIC \
- HTTP_IMAGE_FILTER HTTP_MP4 HTTP_PERL HTTP_RANDOM_INDEX HTTP_REALIP \
- HTTP_SECURE_LINK HTTP_SLICE HTTP_SLICE_AHEAD \
- HTTP_SSL HTTP_STATUS HTTP_SUB HTTP_XSLT HTTPV2 HTTPV2_AUTOTUNE
-
-# External modules (arrayvar MUST appear after devel_kit for build-dep)
-OPTIONS_GROUP_HTTPGRP+= AJP AWS_AUTH BROTLI CACHE_PURGE CLOJURE CT DEVEL_KIT \
- ARRAYVAR DRIZZLE DYNAMIC_TLS DYNAMIC_HC DYNAMIC_UPSTREAM ECHO ENCRYPTSESSION \
- FORMINPUT GRIDFS HEADERS_MORE HTTP_ACCEPT_LANGUAGE \
- HTTP_AUTH_DIGEST HTTP_AUTH_JWT HTTP_AUTH_KRB5 HTTP_AUTH_LDAP \
- HTTP_AUTH_PAM HTTP_DAV_EXT HTTP_EVAL HTTP_FANCYINDEX HTTP_FOOTER \
- HTTP_GEOIP2 HTTP_IP2LOCATION HTTP_IP2PROXY HTTP_JSON_STATUS HTTP_MOGILEFS \
- HTTP_MP4_H264 HTTP_NOTICE HTTP_PUSH HTTP_PUSH_STREAM HTTP_REDIS \
- HTTP_RESPONSE HTTP_SUBS_FILTER HTTP_TARANTOOL HTTP_UPLOAD \
- HTTP_UPLOAD_PROGRESS HTTP_UPSTREAM_CHECK HTTP_UPSTREAM_FAIR \
- HTTP_UPSTREAM_STICKY HTTP_VIDEO_THUMBEXTRACTOR HTTP_ZIP ICONV LET LINK LUA \
- MEMC MODSECURITY3 NAXSI OPENTRACING PASSENGER POSTGRES RDS_CSV \
- RDS_JSON REDIS2 RTMP SET_MISC SFLOW SHIBBOLETH SLOWFS_CACHE \
- SMALL_LIGHT SRCACHE VOD VTS XSS WEBSOCKIFY
+ HTTP_CACHE HTTP_DAV HTTP_DEGRADATION HTTP_FLV HTTP_GUNZIP_FILTER \
+ HTTP_GZIP_STATIC HTTP_IMAGE_FILTER HTTP_MP4 HTTP_PERL \
+ HTTP_RANDOM_INDEX HTTP_REALIP HTTP_SECURE_LINK HTTP_SLICE HTTP_SSL \
+ HTTP_STATUS HTTP_SUB HTTP_XSLT HTTPV2 HTTPV3 HTTPV3_BORING HTTPV3_LSSL \
+ HTTPV3_QTLS
OPTIONS_GROUP_MAILGRP= MAIL MAIL_IMAP MAIL_POP3 MAIL_SMTP MAIL_SSL
OPTIONS_GROUP_STREAMGRP= STREAM STREAM_REALIP STREAM_SSL \
STREAM_SSL_PREREAD
-OPTIONS_DEFINE= DEBUG DEBUGLOG DSO FILE_AIO IPV6 NJS THREADS WWW
-
+OPTIONS_DEFINE= DEBUG DEBUGLOG DSO FILE_AIO IPV6 NJS NJS_XML THREADS WWW
OPTIONS_DEFAULT?= DSO FILE_AIO HTTP HTTP_ADDITION HTTP_AUTH_REQ HTTP_CACHE \
- HTTP_DAV HTTP_FLV HTTP_GZIP_STATIC HTTP_GUNZIP_FILTER \
- HTTP_MP4 HTTP_RANDOM_INDEX HTTP_REALIP HTTP_SECURE_LINK \
- HTTP_SLICE HTTP_REWRITE HTTP_SSL HTTP_STATUS HTTP_SUB \
- HTTPV2 MAIL MAIL_SSL PCRE_ONE STREAM STREAM_SSL STREAM_REALIP \
- STREAM_SSL_PREREAD THREADS WWW
-
-OPTIONS_RADIO+= PCRE
-OPTIONS_RADIO_PCRE= PCRE_ONE PCRE_TWO
-PCRE_ONE_LIB_DEPENDS= libpcre.so:devel/pcre
-PCRE_ONE_CONFIGURE_ON= --without-pcre2
-PCRE_TWO_LIB_DEPENDS= libpcre2-8.so:devel/pcre2
+ HTTP_DAV HTTP_FLV HTTP_GUNZIP_FILTER HTTP_GZIP_STATIC HTTP_MP4 \
+ HTTP_RANDOM_INDEX HTTP_REALIP HTTP_SECURE_LINK HTTP_SLICE HTTP_SSL \
+ HTTP_STATUS HTTP_SUB HTTPV2 MAIL MAIL_SSL STREAM \
+ STREAM_REALIP STREAM_SSL STREAM_SSL_PREREAD THREADS WWW
+
+LIB_DEPENDS+= libpcre2-8.so:devel/pcre2
OPTIONS_SUB= yes
@@ -130,8 +105,9 @@ ${opt}_IMPLIES= STREAM
# If the target is makesum, make sure that every distfile is fetched.
.if ${.TARGETS:Mmakesum}
-OPTIONS_DEFAULT= ${OPTIONS_DEFINE} ${OPTIONS_GROUP_HTTP} \
- ${OPTIONS_GROUP_MAIL} ${OPTIONS_GROUP_STREAM}
+OPTIONS_DEFAULT= ${OPTIONS_DEFINE} ${OPTIONS_GROUP_HTTPGRP} \
+ ${OPTIONS_GROUP_MAILGRP} ${OPTIONS_GROUP_STREAMGRP} \
+ ${OPTIONS_GROUP_THIRDPARTYGRP}
.endif
# Non-module options handling
@@ -158,6 +134,7 @@ HTTP_ADDITION_CONFIGURE_ON= --with-http_addition_module
HTTP_AUTH_REQ_CONFIGURE_ON= --with-http_auth_request_module
HTTP_CACHE_CONFIGURE_OFF= --without-http-cache
HTTP_DAV_CONFIGURE_ON= --with-http_dav_module
+HTTP_DEGRADATION_CONFIGURE_ON= --with-http_degradation_module
HTTP_FLV_CONFIGURE_ON= --with-http_flv_module
HTTP_GZIP_STATIC_CONFIGURE_ON= --with-http_gzip_static_module
HTTP_GUNZIP_FILTER_CONFIGURE_ON=--with-http_gunzip_module
@@ -180,6 +157,22 @@ HTTP_XSLT_LIB_DEPENDS= libxml2.so:textproc/libxml2 \
HTTP_XSLT_VARS= DSO_BASEMODS+=http_xslt_module
HTTPV2_IMPLIES= HTTP_SSL
HTTPV2_CONFIGURE_ON= --with-http_v2_module
+HTTPV3_CONFIGURE_ON= --build=nginx-quic \
+ --with-stream_quic_module \
+ --with-http_v3_module
+HTTPV3_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-httpv3:-p1
+HTTPV3_BORING_BUILD_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl
+HTTPV3_BORING_RUN_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl
+HTTPV3_BORING_IMPLIES= HTTPV3
+HTTPV3_BORING_PREVENTS= HTTPV3_LSSL HTTPV3_QTLS
+HTTPV3_LSSL_BUILD_DEPENDS= ${LOCALBASE}/include/tls.h:security/libressl-devel
+HTTPV3_LSSL_BUILD_DEPENDS= ${LOCALBASE}/include/tls.h:security/libressl-devel
+HTTPV3_LSSL_IMPLIES= HTTPV3
+HTTPV3_LSSL_PREVENTS= HTTPV3_BORING HTTPV3_QTLS
+HTTPV3_QTLS_BUILD_DEPENDS= ${LOCALBASE}/include/openssl/quic.h:security/openssl-quictls
+HTTPV3_QTLS_RUN_DEPENDS= ${LOCALBASE}/include/openssl/quic.h:security/openssl-quictls
+HTTPV3_QTLS_IMPLIES= HTTPV3
+HTTPV3_QTLS_PREVENTS= HTTPV3_BORING HTTPV3_LSSL
MAIL_VARS= DSO_BASEMODS+=mail
MAIL_IMAP_CONFIGURE_OFF= --without-mail_imap_module
MAIL_POP3_CONFIGURE_OFF= --without-mail_pop3_module
@@ -188,9 +181,9 @@ MAIL_SSL_USES= ssl
MAIL_SSL_CONFIGURE_ON= --with-mail_ssl_module
STREAM_VARS= DSO_BASEMODS+=stream
STREAM_REALIP_CONFIGURE_ON= --with-stream_realip_module
-STREAM_SSL_IMPLIES= HTTP_SSL
+STREAM_SSL_USES= ssl
STREAM_SSL_CONFIGURE_ON= --with-stream_ssl_module
-STREAM_SSL_PREREAD_CONFIGURE_ON= --with-stream_ssl_preread_module
+STREAM_SSL_PREREAD_CONFIGURE_ON=--with-stream_ssl_preread_module
### External modules
.include "Makefile.extmod"
@@ -232,13 +225,21 @@ CONFIGURE_ENV+= OPTIMIZE="yes"
CFLAGS+= -DNDEBUG
.endif
-.if empty(PORT_OPTIONS:MPCRE_ONE) && empty(PORT_OPTIONS:MPCRE_TWO)
-IGNORE= required at least PCRE_ONE or PCRE_TWO \
- to be defined. Please do 'make config' again
+# Fix build failure on clang >= 12
+.if ${PORT_OPTIONS:MHTTP_PERL} && ${OSVERSION} >= 1301000
+CFLAGS+= -Wno-compound-token-split-by-macro
+.endif
+
+.if empty(PORT_OPTIONS:MLUA) && empty(PORT_OPTIONS:MMODSECURITY3) && \
+ empty(PORT_OPTIONS:MPASSENGER)
+CONFIGURE_ARGS+= --with-ld-opt="-L ${LOCALBASE}/lib"
+.else
+CONFIGURE_ARGS+= --with-ld-opt="-L ${LOCALBASE}/lib -lpcre"
+LIB_DEPENDS+= libpcre.so:devel/pcre
.endif
-.if ${PORT_OPTIONS:MPCRE_ONE}
-NJS_CONFIGURE_ARGS= --no-pcre2
+.if empty(PORT_OPTIONS:MNJS_XML)
+NJS_CONFIGURE_ARGS= --no-libxml2
.endif
pre-everything::
@@ -251,9 +252,8 @@ pre-everything::
.endif
@${ECHO_MSG}
-post-extract-GRIDFS-on:
- @${RMDIR} ${WRKSRC_gridfs}/mongo-c-driver/
- @${MV} ${WRKSRC_mongo_c} ${WRKSRC_gridfs}/mongo-c-driver
+pre-patch-HTTPV3-on:
+ @${MV} ${WRKSRC}/README ${WRKSRC}/README.1st
post-patch:
@${REINPLACE_CMD} 's!%%HTTP_PORT%%!${HTTP_PORT}!; \
@@ -272,9 +272,9 @@ post-patch-FASTDFS-on:
's!%%PREFIX%%!${PREFIX}!g;s!%%LOCALBASE%%!${LOCALBASE}!g' \
${WRKSRC_fastdfs}/src/config
-# Respect CFLAGS by remove needless --std=c99 flag
post-patch-GRIDFS-on:
- @${REINPLACE_CMD} 's!--std=c99!-DMONGO_HAVE_STDINT!' ${WRKSRC_gridfs}/config
+ @${REINPLACE_CMD} 's!\/usr!${LOCALBASE}!g' \
+ ${WRKSRC_gridfs}/nginx-gridfs/config
post-patch-HTTP_AUTH_KRB5-on:
@${REINPLACE_CMD} 's!%%GSSAPILIBS%%!${GSSAPILIBS}!g; \
@@ -316,16 +316,14 @@ post-patch-SFLOW-on:
post-patch-VOD-on:
@${REINPLACE_CMD} \
- 's!%%PREFIX%%!${LOCALBASE}!g' \
- ${WRKSRC_vod}/config
+ 's!%%PREFIX%%!${LOCALBASE}!g' \
+ ${WRKSRC_vod}/config
pre-configure-SMALL_LIGHT-on:
( cd ${WRKSRC_small_light} && ./setup )
do-configure-NJS-on:
- ( cd ${WRKSRC_njs} && ${SETENV} ${CONFIGURE_ENV} ${CONFIGURE_CMD} ${NJS_CONFIGURE_ARGS} \
- && ${SETENV} ${MAKE_ENV} ${MAKE_CMD} njs \
- && ${MV} build/njs ${WRKSRC_njs} )
+ ( cd ${WRKSRC_njs} && ${SETENV} ${CONFIGURE_ENV} ${CONFIGURE_CMD} ${NJS_CONFIGURE_ARGS} )
.if !target(do-install)
do-install:
@@ -360,14 +358,13 @@ do-install-NAXSI-on:
${INSTALL_DATA} \
${WRKDIR}/naxsi-${NAXSI_NGINX_VER}/naxsi_config/naxsi_core.rules \
${STAGEDIR}${ETCDIR}
-
.endif
.if !target(post-install)
post-install:
${MKDIR} ${STAGEDIR}${PREFIX}/share/vim/vimfiles
cd ${WRKSRC}/contrib/vim && ${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/share/vim/vimfiles
- ${INSTALL_MAN} ${WRKSRC}/objs/nginx.8 ${STAGEDIR}${MAN8PREFIX}/man/man8
+ ${INSTALL_MAN} ${WRKSRC}/objs/nginx.8 ${STAGEDIR}${MAN8PREFIX}/share/man/man8
${CAT} ${WRKSRC}/conf/nginx.conf >> ${STAGEDIR}${ETCDIR}/nginx.conf-dist
post-install-WWW-on:
diff --git a/www/nginx/Makefile.extmod b/www/nginx/Makefile.extmod
index 5ad50b415eec..da16d3e86f39 100644
--- a/www/nginx/Makefile.extmod
+++ b/www/nginx/Makefile.extmod
@@ -1,6 +1,22 @@
### External modules
-AJP_GH_TUPLE= yaoweibin:nginx_ajp_module:fcbb2cc:ajp
-AJP_CONFIGURE_ON= --add-module=${WRKSRC_ajp}
+
+OPTIONS_GROUP+= THIRDPARTYGRP
+# External modules (arrayvar MUST appear after devel_kit for build-dep)
+OPTIONS_GROUP_THIRDPARTYGRP= AJP AWS_AUTH BROTLI CACHE_PURGE CLOJURE COOKIE_FLAG CT \
+ DEVEL_KIT ARRAYVAR DRIZZLE DYNAMIC_UPSTREAM ECHO ENCRYPTSESSION \
+ FIPS_CHECK FORMINPUT GRIDFS HEADERS_MORE HTTP_ACCEPT_LANGUAGE HTTP_AUTH_DIGEST \
+ HTTP_AUTH_KRB5 HTTP_AUTH_LDAP HTTP_AUTH_PAM HTTP_DAV_EXT HTTP_EVAL \
+ HTTP_FANCYINDEX HTTP_FOOTER HTTP_GEOIP2 HTTP_IP2LOCATION HTTP_IP2PROXY \
+ HTTP_JSON_STATUS HTTP_MOGILEFS HTTP_MP4_H264 HTTP_NOTICE HTTP_PUSH \
+ HTTP_PUSH_STREAM HTTP_REDIS HTTP_SLICE_AHEAD HTTP_SUBS_FILTER HTTP_TARANTOOL \
+ HTTP_UPLOAD HTTP_UPLOAD_PROGRESS HTTP_UPSTREAM_CHECK HTTP_UPSTREAM_FAIR \
+ HTTP_UPSTREAM_STICKY HTTP_VIDEO_THUMBEXTRACTOR HTTP_ZIP ICONV LET LINK LUA MEMC \
+ MODSECURITY3 NAXSI OPENTRACING PASSENGER POSTGRES RDS_CSV RDS_JSON \
+ REDIS2 RTMP SET_MISC SFLOW SHIBBOLETH SLOWFS_CACHE SRCACHE STS \
+ VOD VTS XSS WEBSOCKIFY
+
+AJP_GH_TUPLE= msva:nginx_ajp_module:fcbb2cc:ajp
+AJP_VARS= DSO_EXTMODS+=ajp
ARRAYVAR_IMPLIES= DEVEL_KIT
ARRAYVAR_GH_TUPLE= openresty:array-var-nginx-module:v0.05:arrayvar
@@ -13,80 +29,79 @@ BROTLI_LIB_DEPENDS= libbrotlicommon.so:archivers/brotli
BROTLI_GH_TUPLE= google:ngx_brotli:9aec15e:brotli
BROTLI_VARS= DSO_EXTMODS+=brotli
-CACHE_PURGE_GH_TUPLE= nginx-modules:ngx_cache_purge:4746629:cache_purge
-CACHE_PURGE_CONFIGURE_ON= --add-module=${WRKDIR}/ngx_cache_purge-4746629
+CACHE_PURGE_GH_TUPLE= torden:ngx_cache_purge:2b977cf:cache_purge
+CACHE_PURGE_VARS= DSO_EXTMODS+=cache_purge
CLOJURE_CATEGORIES+= java
CLOJURE_USE= JAVA=yes JAVA_OS=native JAVA_VERSION=1.8 \
JAVA_VENDOR=openjdk JAVA_BUILD=yes JAVA_RUN=yes
-CLOJURE_GH_TUPLE= nginx-clojure:nginx-clojure:v0.5.2:clojure
+CLOJURE_GH_TUPLE= nginx-clojure:nginx-clojure:4347955:clojure
CLOJURE_CONFIGURE_ENV= "JNI_INCS=-I${LOCALBASE}/openjdk8/include -I${LOCALBASE}/openjdk8/include/freebsd"
CLOJURE_VARS= DSO_EXTMODS+=clojure CLOJURE_SUBDIR=/src/c
+CLOJURE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_clojure_mem.h
+
+COOKIE_FLAG_GH_TUPLE= AirisX:nginx_cookie_flag_module:c4ff449:cookie_flag
+COOKIE_FLAG_VARS= DSO_EXTMODS+=cookie_flag
CT_IMPLIES= HTTP_SSL
-CT_GH_TUPLE= grahamedgecombe:nginx-ct:v1.3.2:ct
+CT_GH_TUPLE= grahamedgecombe:nginx-ct:93e9884:ct
CT_VARS= DSO_EXTMODS+=ct
CT_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-ct-LibreSSL
+ECHO_GH_TUPLE= openresty:echo-nginx-module:5a402aa:echo
+ECHO_VARS= DSO_EXTMODS+=echo
+
DRIZZLE_LIB_DEPENDS= libdrizzle.so:databases/libdrizzle
DRIZZLE_CONFIGURE_ENV= LIBDRIZZLE_INC=${LOCALBASE}/include \
LIBDRIZZLE_LIB=${LOCALBASE}/lib
-DRIZZLE_GH_TUPLE= openresty:drizzle-nginx-module:v0.1.11:drizzle
+DRIZZLE_GH_TUPLE= openresty:drizzle-nginx-module:3504fc6:drizzle
DRIZZLE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-openresty-drizzle-nginx-module-config
DRIZZLE_VARS= DSO_EXTMODS+=drizzle
-DYNAMIC_TLS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-dynamic-tls
-
+DYNAMIC_UPSTREAM_IMPLIES= STREAM
DYNAMIC_UPSTREAM_GH_TUPLE= ZigzagAK:ngx_dynamic_upstream:960eef2:dynamic_upstream
DYNAMIC_UPSTREAM_VARS= DSO_EXTMODS+=dynamic_upstream
-DYNAMIC_HC_GH_TUPLE= ZigzagAK:ngx_dynamic_healthcheck:61acf02:dynamic_hc
-DYNAMIC_HC_VARS= DSO_EXTMODS+=dynamic_hc
-DYNAMIC_HC_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_dynamic_healthcheck-config
-DYNAMIC_HC_IMPLIES= DYNAMIC_UPSTREAM
-
DEVEL_KIT_GH_TUPLE= simpl:ngx_devel_kit:v0.3.1:devel_kit
DEVEL_KIT_VARS= FIRST_DSO_EXTMODS+=devel_kit
-ECHO_GH_TUPLE= openresty:echo-nginx-module:5a402aa:echo
-ECHO_VARS= DSO_EXTMODS+=echo
-
ENCRYPTSESSION_IMPLIES= DEVEL_KIT
-ENCRYPTSESSION_GH_TUPLE= openresty:encrypted-session-nginx-module:v0.08:encryptsession
+ENCRYPTSESSION_GH_TUPLE= openresty:encrypted-session-nginx-module:v0.09:encryptsession
ENCRYPTSESSION_VARS= DSO_EXTMODS+=encryptsession
+FIPS_CHECK_GH_TUPLE= ogarrett:nginx-fips-check-module:6cb4270:fipscheck
+FIPS_CHECK_VARS= DSO_EXTMODS+=fipscheck
+
FORMINPUT_IMPLIES= DEVEL_KIT
FORMINPUT_GH_TUPLE= calio:form-input-nginx-module:v0.12:forminput
FORMINPUT_VARS= DSO_EXTMODS+=forminput
-GRIDFS_GH_TUPLE= technowledgy:nginx_http_gridfs_module:7970bab:gridfs \
- 10gen-archive:mongo-c-driver-legacy:f06669b:mongo_c
-GRIDFS_VARS= DSO_EXTMODS+=gridfs
+GRIDFS_GH_TUPLE= nieoding:nginx-gridfs:059bdc3:gridfs
+GRIDFS_LIB_DEPENDS= libbson-1.0.so:devel/libbson \
+ libmongoc-1.0.so:devel/mongo-c-driver
+GRIDFS_VARS= DSO_EXTMODS+=gridfs GRIDFS_SUBDIR=/nginx-gridfs
-HEADERS_MORE_GH_TUPLE= openresty:headers-more-nginx-module:d6d7eba:headers_more
+HEADERS_MORE_GH_TUPLE= openresty:headers-more-nginx-module:33b646d:headers_more
HEADERS_MORE_VARS= DSO_EXTMODS+=headers_more
HTTP_ACCEPT_LANGUAGE_GH_TUPLE= dvershinin:nginx_accept_language_module:5683967:accept_language
HTTP_ACCEPT_LANGUAGE_VARS= DSO_EXTMODS+=accept_language
-HTTP_AUTH_DIGEST_GH_TUPLE= atomx:nginx-http-auth-digest:cd86418:auth_digest
+HTTP_AUTH_DIGEST_GH_TUPLE= atomx:nginx-http-auth-digest:274490c:auth_digest
HTTP_AUTH_DIGEST_VARS= DSO_EXTMODS+=auth_digest
-HTTP_AUTH_JWT_GH_TUPLE= TeslaGov:ngx-http-auth-jwt-module:80d89d9:http_auth_jwt
-HTTP_AUTH_JWT_VARS= DSO_EXTMODS+=http_auth_jwt
-HTTP_AUTH_JWT_LIB_DEPENDS= libjwt.so:www/libjwt libjansson.so:devel/jansson
-
HTTP_AUTH_KRB5_GH_TUPLE= stnoonan:spnego-http-auth-nginx-module:c626163:auth_krb5
HTTP_AUTH_KRB5_VARS= DSO_EXTMODS+=auth_krb5
HTTP_AUTH_KRB5_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-spnego-http-auth-nginx-module-config \
${PATCHDIR}/extra-patch-ngx_http_auth_spnego_module.c
HTTP_AUTH_KRB5_USES= gssapi:mit
-HTTP_AUTH_LDAP_GH_TUPLE= kvspb:nginx-auth-ldap:42d195d:http_auth_ldap
+HTTP_AUTH_LDAP_GH_TUPLE= kvspb:nginx-auth-ldap:83c059b:http_auth_ldap
+HTTP_AUTH_LDAP_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_auth_ldap_module.c
HTTP_AUTH_LDAP_VARS= DSO_EXTMODS+=http_auth_ldap
HTTP_AUTH_LDAP_USES= ldap
-HTTP_AUTH_PAM_GH_TUPLE= sto:ngx_http_auth_pam_module:v1.5.1:auth_pam
+HTTP_AUTH_PAM_GH_TUPLE= sto:ngx_http_auth_pam_module:v1.5.3:auth_pam
HTTP_AUTH_PAM_VARS= DSO_EXTMODS+=auth_pam
HTTP_DAV_EXT_IMPLIES= HTTP_DAV
@@ -106,7 +121,7 @@ HTTP_FOOTER_GH_TUPLE= alibaba:nginx-http-footer-filter:1.2.2:footer
HTTP_FOOTER_VARS= DSO_EXTMODS+=footer
HTTP_FOOTER_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-http-footer-filter-config
-HTTP_GEOIP2_GH_TUPLE= leev:ngx_http_geoip2_module:3.3:geoip2
+HTTP_GEOIP2_GH_TUPLE= leev:ngx_http_geoip2_module:3.4:geoip2
HTTP_GEOIP2_CFLAGS= -I${LOCALBASE}/include
HTTP_GEOIP2_VARS= DSO_EXTMODS+=geoip2
HTTP_GEOIP2_LIB_DEPENDS= libmaxminddb.so:net/libmaxminddb
@@ -121,7 +136,7 @@ HTTP_IP2PROXY_VARS= DSO_EXTMODS+=ip2proxy
HTTP_JSON_STATUS_GH_TUPLE= nginx-modules:ngx_http_json_status_module:1d2f303:json_status
HTTP_JSON_STATUS_VARS= DSO_EXTMODS+=json_status
-HTTP_JSON_STATUS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_json_status_module-config
+HTTP_JSON_STATUS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_json_status_module-config
HTTP_MOGILEFS_MASTER_SITES= http://www.grid.net.ru/nginx/download/:mogilefs
HTTP_MOGILEFS_DISTFILES= nginx_mogilefs_module-1.0.4.tar.gz:mogilefs
@@ -130,10 +145,10 @@ HTTP_MOGILEFS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_mogilefs_module.c
HTTP_MOGILEFS_VARS= DSO_EXTDIRS+=nginx_mogilefs_module-1.0.4
HTTP_MP4_H264_MASTER_SITES= http://h264.code-shop.com/download/:mp4streaming
-HTTP_MP4_H264_CONFIGURE_ON= --with-cc-opt="-DLARGEFILE_SOURCE -DBUILDING_NGINX"
+HTTP_MP4_H264_CONFIGURE_ON= --with-cc-opt="-DLARGEFILE_SOURCE -DBUILDING_NGINX"
HTTP_MP4_H264_DISTFILES= nginx_mod_h264_streaming-2.2.7.tar.gz:mp4streaming
HTTP_MP4_H264_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_streaming_module.c \
- ${PATCHDIR}/extra-patch-nginx_mod_h264_streaming-config
+ ${PATCHDIR}/extra-patch-nginx_mod_h264_streaming-config
HTTP_MP4_H264_VARS= DSO_EXTDIRS+=nginx_mod_h264_streaming-2.2.7
HTTP_NOTICE_GH_TUPLE= kr:nginx-notice:3c95966:notice
@@ -141,25 +156,21 @@ HTTP_NOTICE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_notice_module.c \
${PATCHDIR}/extra-patch-nginx-notice-config
HTTP_NOTICE_VARS= DSO_EXTMODS+=notice
-HTTP_PUSH_GH_TUPLE= slact:nchan:v1.3.0:push
+HTTP_PUSH_GH_TUPLE= slact:nchan:v1.3.6:push
HTTP_PUSH_VARS= DSO_EXTMODS+=push
-HTTP_PUSH_STREAM_GH_TUPLE= wandenberg:nginx-push-stream-module:0.5.4:pushstream
+HTTP_PUSH_STREAM_GH_TUPLE= wandenberg:nginx-push-stream-module:8c02220:pushstream
HTTP_PUSH_STREAM_VARS= DSO_EXTMODS+=pushstream
HTTP_REDIS_MASTER_SITES= LOCAL/osa:redis
HTTP_REDIS_DISTFILES= ngx_http_redis-0.3.9.tar.gz:redis
HTTP_REDIS_VARS= DSO_EXTDIRS+=ngx_http_redis-0.3.9
+HTTP_REDIS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_redis_module.c
-HTTP_RESPONSE_MASTER_SITES= http://catap.ru/downloads/nginx/:response
-HTTP_RESPONSE_DISTFILES= ngx_http_response-0.3.tar.gz:response
-HTTP_RESPONSE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_response-config
-HTTP_RESPONSE_VARS= DSO_EXTDIRS+=ngx_http_response-0.3
+HTTP_SLICE_AHEAD_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c
-HTTP_SLICE_AHEAD_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-src_http_modules_ngx__http__slice_read_ahead.c
-
-HTTP_SUBS_FILTER_GH_TUPLE= yaoweibin:ngx_http_substitutions_filter_module:b8a71ea:subs_filter
-HTTP_SUBS_FILTER_VARS= DSO_EXTMODS+=subs_filter
+HTTP_SUBS_FILTER_GH_TUPLE= yaoweibin:ngx_http_substitutions_filter_module:c6f825f:subs_filter
+HTTP_SUBS_FILTER_VARS= DSO_EXTMODS+=subs_filter
HTTP_TARANTOOL_LIB_DEPENDS= libmsgpuck.so:devel/msgpuck \
libyajl.so:devel/yajl
@@ -167,11 +178,12 @@ HTTP_TARANTOOL_GH_TUPLE= tarantool:nginx_upstream_module:aeb8696:nginx_tarantool
HTTP_TARANTOOL_VARS= DSO_EXTMODS+=nginx_tarantool
HTTP_TARANTOOL_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_tarantool-config
-HTTP_UPLOAD_GH_TUPLE= fdintino:nginx-upload-module:aa42509:upload
+HTTP_UPLOAD_GH_TUPLE= fdintino:nginx-upload-module:643b4c1:upload
HTTP_UPLOAD_VARS= DSO_EXTMODS+=upload
-HTTP_UPLOAD_PROGRESS_GH_TUPLE= masterzen:nginx-upload-progress-module:afb2d31:uploadprogress
-HTTP_UPLOAD_PROGRESS_VARS= DSO_EXTMODS+=uploadprogress
+HTTP_UPLOAD_PROGRESS_GH_TUPLE= masterzen:nginx-upload-progress-module:68b3ab3:uploadprogress
+HTTP_UPLOAD_PROGRESS_VARS= DSO_EXTMODS+=uploadprogress
+HTTP_UPLOAD_PROGRESS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_uploadprogress_module.c
HTTP_UPSTREAM_CHECK_GH_TUPLE= yaoweibin:nginx_upstream_check_module:9aecf15:upstreamcheck
HTTP_UPSTREAM_CHECK_CONFIGURE_ON= --add-module=${WRKSRC_upstreamcheck}
@@ -185,7 +197,7 @@ HTTP_UPSTREAM_FAIR_GH_TUPLE= jaygooby:nginx-upstream-fair:10ecdcf:upstreamfair
HTTP_UPSTREAM_FAIR_VARS= DSO_EXTMODS+=upstreamfair
HTTP_UPSTREAM_STICKY_IMPLIES= HTTP_SSL
-HTTP_UPSTREAM_STICKY_GH_TUPLE= ayty-adrianomartins:nginx-sticky-module-ng:c407e0d:upstreamsticky
+HTTP_UPSTREAM_STICKY_GH_TUPLE= dvershinin:nginx-sticky-module-ng:2753211:upstreamsticky
HTTP_UPSTREAM_STICKY_VARS= DSO_EXTMODS+=upstreamsticky
HTTP_VIDEO_THUMBEXTRACTOR_LIB_DEPENDS= libavformat.so:multimedia/ffmpeg \
@@ -193,16 +205,12 @@ HTTP_VIDEO_THUMBEXTRACTOR_LIB_DEPENDS= libavformat.so:multimedia/ffmpeg \
libavutil.so:multimedia/ffmpeg \
libswscale.so:multimedia/ffmpeg
HTTP_VIDEO_THUMBEXTRACTOR_USES= jpeg
-HTTP_VIDEO_THUMBEXTRACTOR_GH_TUPLE= Novetta:nginx-video-thumbextractor-module:28861f2:vte
+HTTP_VIDEO_THUMBEXTRACTOR_GH_TUPLE= Novetta:nginx-video-thumbextractor-module:f5b5bae:vte
HTTP_VIDEO_THUMBEXTRACTOR_VARS= DSO_EXTMODS+=vte
-HTTP_ZIP_GH_TUPLE= rtm-ctrlz:mod_zip:cfd0be4:mod_zip
+HTTP_ZIP_GH_TUPLE= vince2678:mod_zip:5b2604b:mod_zip
HTTP_ZIP_VARS= DSO_EXTMODS+=mod_zip
-HTTPV2_AUTOTUNE_IMPLIES=HTTPV2
-HTTPV2_AUTOTUNE_CONFIGURE_ON= --with-http_v2_autotune_upload
-HTTPV2_AUTOTUNE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-h2-autotune
-
ICONV_IMPLIES= DEVEL_KIT
ICONV_USES= iconv
ICONV_GH_TUPLE= calio:iconv-nginx-module:v0.14:iconv
@@ -212,46 +220,50 @@ ICONV_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-calio-iconv-nginx-module-config
LET_GH_TUPLE= baysao:nginx-let-module:c1f23aa:let
LET_VARS= DSO_EXTMODS+=let
-LINK_GH_TUPLE= Taymindis:nginx-link-function:3.2.4:link
-LINK_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-link-function-config \
- ${PATCHDIR}/extra-patch-ngx_link_func_module.c
-LINK_VARS= DSO_EXTMODS+=link
-
LUA_IMPLIES= DEVEL_KIT
LUA_LIB_DEPENDS= libluajit-5.1.so:lang/luajit-openresty
LUA_RUN_DEPENDS= lua-resty-core>0:www/lua-resty-core
LUA_CONFIGURE_ENV= LUAJIT_INC=${LOCALBASE}/include/luajit-2.1 \
LUAJIT_LIB=${LOCALBASE}/lib
-LUA_GH_TUPLE= openresty:lua-nginx-module:v0.10.22:lua
+LUA_GH_TUPLE= openresty:lua-nginx-module:v0.10.24:lua
LUA_VARS= DSO_EXTMODS+=lua
+LINK_GH_TUPLE= Taymindis:nginx-link-function:3.2.4:link
+LINK_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-link-function-config \
+ ${PATCHDIR}/extra-patch-ngx_link_func_module.c
+LINK_VARS= DSO_EXTMODS+=link
+
MEMC_GH_TUPLE= openresty:memc-nginx-module:v0.19:memc
MEMC_VARS= DSO_EXTMODS+=memc
-MODSECURITY3_IMPLIES= HTTP_ADDITION HTTP_IMAGE_FILTER HTTP_GUNZIP_FILTER HTTP_XSLT
+MODSECURITY3_IMPLIES= HTTP_ADDITION HTTP_IMAGE_FILTER HTTP_GUNZIP_FILTER HTTP_XSLT
MODSECURITY3_LIB_DEPENDS= libmodsecurity.so:security/modsecurity3
-MODSECURITY3_GH_TUPLE= SpiderLabs:ModSecurity-nginx:v1.0.2:modsec
-MODSECURITY3_VARS= DSO_EXTMODS+=modsec
+MODSECURITY3_GH_TUPLE= SpiderLabs:ModSecurity-nginx:v1.0.3:modsecurity3
+MODSECURITY3_VARS= DSO_EXTMODS+=modsecurity3
-NAXSI_NGINX_VER= 1.3
+NAXSI_NGINX_VER= 29793dc
NAXSI_GH_TUPLE= nbs-system:naxsi:${NAXSI_NGINX_VER}:naxsi
NAXSI_VARS= DSO_EXTMODS+=naxsi NAXSI_SUBDIR=/naxsi_src
+NAXSI_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-naxsi_runtime.c
-NJS_GH_TUPLE= nginx:njs:0.7.9:njs
+NJS_GH_TUPLE= nginx:njs:0.7.12:njs
NJS_VARS= DSO_EXTMODS+=njs NJS_SUBDIR=/nginx
NJS_USES= libedit
-OPENTRACING_GH_TUPLE= opentracing-contrib:nginx-opentracing:2d81c29:opentracing
+NJS_XML_IMPLIES= NJS
+NJS_XML_LIB_DEPENDS= libxml2.so:textproc/libxml2
+
+OPENTRACING_GH_TUPLE= opentracing-contrib:nginx-opentracing:v0.24.0:opentracing
OPENTRACING_LIB_DEPENDS= libopentracing.so:devel/libopentracing
-OPENTRACING_VARS= DSO_EXTMODS+=opentracing OPENTRACING_SUBDIR=/opentracing
+OPENTRACING_VARS= DSO_EXTMODS+=opentracing OPENTRACING_SUBDIR=/opentracing
OPENTRACING_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-opentracing-opentracing-config
-PASSENGER_NGINX_VER= 6.0.15
+PASSENGER_NGINX_VER= 6.0.17
PASSENGER_CATEGORIES= ruby
PASSENGER_USES= ruby
PASSENGER_BUILD_DEPENDS=${LOCALBASE}/bin/rake:devel/rubygem-rake
PASSENGER_RAKE_BIN= ${LOCALBASE}/bin/rake
-PASSENGER_MASTER_SITES= http://s3.amazonaws.com/phusion-passenger/releases/:passenger
+PASSENGER_MASTER_SITES= https://s3.amazonaws.com/phusion-passenger/releases/:passenger
PASSENGER_DISTFILES= passenger-${PASSENGER_NGINX_VER}.tar.gz:passenger
PASSENGER_VARS= WRKSRC_passenger=${WRKDIR}/passenger-${PASSENGER_NGINX_VER} \
DSO_EXTDIRS+=passenger-${PASSENGER_NGINX_VER}/src/nginx_module
@@ -274,11 +286,11 @@ RDS_JSON_VARS= DSO_EXTMODS+=rdsjson
REDIS2_GH_TUPLE= openresty:redis2-nginx-module:v0.15:redis2
REDIS2_VARS= DSO_EXTMODS+=redis2
-RTMP_GH_TUPLE= sergey-dryabzhinsky:nginx-rtmp-module:8e344d7:rtmp
+RTMP_GH_TUPLE= arut:nginx-rtmp-module:v1.2.2:rtmp
RTMP_VARS= DSO_EXTMODS+=rtmp
SET_MISC_IMPLIES= DEVEL_KIT
-SET_MISC_GH_TUPLE= openresty:set-misc-nginx-module:4667684:setmisc
+SET_MISC_GH_TUPLE= openresty:set-misc-nginx-module:3937e7b:setmisc
SET_MISC_VARS= DSO_EXTMODS+=setmisc
SFLOW_GH_TUPLE= sflow:nginx-sflow-module:543c72a:sflow
@@ -286,32 +298,31 @@ SFLOW_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_sflow_config.c \
${PATCHDIR}/extra-patch-ngx_http_sflow_config.h \
${PATCHDIR}/extra-patch-ngx_http_sflow_module.c
-SHIBBOLETH_GH_TUPLE= nginx-shib:nginx-http-shibboleth:a386c18:shibboleth
+SHIBBOLETH_GH_TUPLE= nginx-shib:nginx-http-shibboleth:be12df5:shibboleth
SHIBBOLETH_VARS= DSO_EXTMODS+=shibboleth
SLOWFS_CACHE_GH_TUPLE= baysao:ngx_slowfs_cache:d011a18:slowfs_cache
SLOWFS_CACHE_VARS= DSO_EXTMODS+=slowfs_cache
-SMALL_LIGHT_USES= magick:6
-SMALL_LIGHT_LIB_DEPENDS= libpcre.so:devel/pcre
-SMALL_LIGHT_GH_TUPLE= cubicdaiya:ngx_small_light:v0.9.2:small_light
-SMALL_LIGHT_VARS= DSO_EXTMODS+=small_light
-
-SRCACHE_GH_TUPLE= openresty:srcache-nginx-module:v0.32:srcache
+SRCACHE_GH_TUPLE= openresty:srcache-nginx-module:be22ac0:srcache
SRCACHE_VARS= DSO_EXTMODS+=srcache
-VOD_GH_TUPLE= kaltura:nginx-vod-module:1.27:vod
-VOD_USE= GNOME=libxml2
+STS_IMPLIES= STREAM
+STS_GH_TUPLE= vozlt:nginx-module-sts:3c10d42:sts
+STS_VARS= DSO_EXTMODS+=sts
+
+VOD_GH_TUPLE= kaltura:nginx-vod-module:1.31:vod
+VOD_LIB_DEPENDS= libxml2.so:textproc/libxml2 \
+ libavutil.so:multimedia/ffmpeg
VOD_USES= iconv
VOD_VARS= DSO_EXTMODS+=vod
-VOD_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-vod-module-config
-VTS_GH_TUPLE= vozlt:nginx-module-vts:v0.1.18:vts
-VTS_CONFIGURE_ON= --add-module=${WRKSRC_vts}
+VTS_GH_TUPLE= vozlt:nginx-module-vts:v0.2.1:vts
+VTS_VARS= DSO_EXTMODS+=vts
XSS_GH_TUPLE= openresty:xss-nginx-module:v0.06:xss
XSS_VARS= DSO_EXTMODS+=xss
XSS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-xss-nginx-module-config
-WEBSOCKIFY_GH_TUPLE= tg123:websockify-nginx-module:e82d254:websockify
-WEBSOCKIFY_CONFIGURE_ON= --add-module=${WRKSRC_websockify}
+WEBSOCKIFY_GH_TUPLE= tg123:websockify-nginx-module:c11bc9a:websockify
+WEBSOCKIFY_VARS= DSO_EXTMODS+=websockify
diff --git a/www/nginx/Makefile.options.desc b/www/nginx/Makefile.options.desc
index dc62dde971b6..e56d3d83c6b1 100644
--- a/www/nginx/Makefile.options.desc
+++ b/www/nginx/Makefile.options.desc
@@ -4,17 +4,17 @@ AWS_AUTH_DESC= 3rd party aws auth module
BROTLI_DESC= 3rd party brotli module
CACHE_PURGE_DESC= 3rd party cache_purge module
CLOJURE_DESC= 3rd party clojure module
+COOKIE_FLAG_DESC= 3rd party cookie_flag module
CT_DESC= 3rd party cert_transparency module (SSL req.)
DEBUGLOG_DESC= Enable debug log (--with-debug)
DEVEL_KIT_DESC= 3rd party Nginx Development Kit module
DRIZZLE_DESC= 3rd party drizzle module
DSO_DESC= Enable dynamic modules support
-DYNAMIC_TLS_DESC= 3rd party dynamic tls records patch
-DYNAMIC_HC_DESC= 3rd party dynamic_healthcheck module
DYNAMIC_UPSTREAM_DESC= 3rd party dynamic_upstream module
ECHO_DESC= 3rd party echo module
ENCRYPTSESSION_DESC= 3rd party encrypted_session module
FILE_AIO_DESC= Enable file aio
+FIPS_CHECK_DESC= 3rd party fips_check module
FORMINPUT_DESC= 3rd party form_input module
GOOGLE_PERFTOOLS_DESC= Enable google perftools module
GRIDFS_DESC= 3rd party gridfs module
@@ -22,11 +22,13 @@ GSSAPI_DESC= GSSAPI implementation (imply HTTP_AUTH_KRB5)
HEADERS_MORE_DESC= 3rd party headers_more module
HTTPGRP_DESC= Modules that require HTTP module
HTTPV2_DESC= Enable HTTP/2 protocol support (SSL req.)
-HTTPV2_AUTOTUNE_DESC= Enable HTTP/2 upload auto-tuning
+HTTPV3_DESC= Enable HTTP/3 protocol support
+HTTPV3_BORING_DESC= Use security/boringssl
+HTTPV3_LSSL_DESC= Use security/libressl-devel
+HTTPV3_QTLS_DESC= Use security/openssl-quictls
HTTP_ACCEPT_LANGUAGE_DESC= 3rd party accept_language module
HTTP_ADDITION_DESC= Enable http_addition module
HTTP_AUTH_DIGEST_DESC= 3rd party http_authdigest module
-HTTP_AUTH_JWT_DESC= 3rd party http_auth_jwt module
HTTP_AUTH_KRB5_DESC= 3rd party http_auth_gss module
HTTP_AUTH_LDAP_DESC= 3rd party http_auth_ldap module
HTTP_AUTH_PAM_DESC= 3rd party http_auth_pam module
@@ -34,6 +36,7 @@ HTTP_AUTH_REQ_DESC= Enable http_auth_request module
HTTP_CACHE_DESC= Enable http_cache module
HTTP_DAV_DESC= Enable http_webdav module
HTTP_DAV_EXT_DESC= 3rd party webdav_ext module
+HTTP_DEGRADATION_DESC= Enable http_degradation module
HTTP_DESC= Enable HTTP module
HTTP_EVAL_DESC= 3rd party eval module
HTTP_FANCYINDEX_DESC= 3rd party http_fancyindex module
@@ -56,10 +59,9 @@ HTTP_PUSH_STREAM_DESC= 3rd party push stream module
HTTP_RANDOM_INDEX_DESC= Enable http_random_index module
HTTP_REALIP_DESC= Enable http_realip module
HTTP_REDIS_DESC= 3rd party http_redis module
-HTTP_RESPONSE_DESC= 3rd party http_response module
HTTP_SECURE_LINK_DESC= Enable http_secure_link module
HTTP_SLICE_DESC= Enable http_slice module
-HTTP_SLICE_AHEAD_DESC= Enable http_slice_ahead module
+HTTP_SLICE_AHEAD_DESC= 3rd party http_slice_ahead module
HTTP_SSL_DESC= Enable http_ssl module
HTTP_STATUS_DESC= Enable http_stub_status module
HTTP_SUBS_FILTER_DESC= 3rd party subs filter module
@@ -88,11 +90,10 @@ MAIL_SSL_DESC= Enable mail_ssl module
MEMC_DESC= 3rd party memc (memcached) module
MODSECURITY3_DESC= 3rd party modsecurity3 module
NAXSI_DESC= 3rd party naxsi module
-NJS_DESC= Enable http_javascript module
+NJS_DESC= Enable javascript (NJS) module
+NJS_XML_DESC= Enable XML functionality in NJS module
OPENTRACING_DESC= 3rd party opentracing module
PASSENGER_DESC= 3rd party passenger module
-PCRE_ONE_DESC= Enable PCRE1 support
-PCRE_TWO_DESC= Enable PCRE2 support
POSTGRES_DESC= 3rd party postgres module
RDS_CSV_DESC= 3rd party rds_csv module
RDS_JSON_DESC= 3rd party rds_json module
@@ -102,13 +103,15 @@ SET_MISC_DESC= 3rd party set_misc module
SFLOW_DESC= 3rd party sflow module
SHIBBOLETH_DESC= 3rd party shibboleth module
SLOWFS_CACHE_DESC= 3rd party slowfs_cache module
-SMALL_LIGHT_DESC= 3rd party small_light module
SRCACHE_DESC= 3rd party srcache module
+STREAMGRP_DESC= Modules that require STREAM module
STREAM_DESC= Enable stream module
STREAM_REALIP_DESC= Enable stream_realip module
STREAM_SSL_DESC= Enable stream_ssl module (SSL req.)
STREAM_SSL_PREREAD_DESC= Enable stream_ssl_preread module (SSL req.)
+STS_DESC= 3rd party sts module
THREADS_DESC= Enable threads support
+THIRDPARTYGRP_DESC= Third-party modules
VOD_DESC= 3rd party vod module
VTS_DESC= 3rd party vts module
WWW_DESC= Enable html sample files
diff --git a/www/nginx/distinfo b/www/nginx/distinfo
index 6522b97ab26c..6dd09eb91908 100644
--- a/www/nginx/distinfo
+++ b/www/nginx/distinfo
@@ -1,62 +1,58 @@
-TIMESTAMP = 1680519139
-SHA256 (nginx-1.22.1.tar.gz) = 9ebb333a9e82b952acd3e2b4aeb1d4ff6406f72491bab6cd9fe69f0dea737f31
-SIZE (nginx-1.22.1.tar.gz) = 1073948
+TIMESTAMP = 1681229804
+SHA256 (nginx-1.24.0.tar.gz) = 77a2541637b92a621e3ee76776c8b7b40cf6d707e69ba53a940283e30ff2f55d
+SIZE (nginx-1.24.0.tar.gz) = 1112471
SHA256 (nginx_mogilefs_module-1.0.4.tar.gz) = 7ac230d30907f013dff8d435a118619ea6168aa3714dba62c6962d350c6295ae
SIZE (nginx_mogilefs_module-1.0.4.tar.gz) = 11208
SHA256 (nginx_mod_h264_streaming-2.2.7.tar.gz) = 6d974ba630cef59de1f60996c66b401264a345d25988a76037c2856cec756c19
SIZE (nginx_mod_h264_streaming-2.2.7.tar.gz) = 44012
SHA256 (ngx_http_redis-0.3.9.tar.gz) = 21f87540f0a44b23ffa5df16fb3d788bc90803b255ef14f9c26e3847a6f26f46
SIZE (ngx_http_redis-0.3.9.tar.gz) = 13051
-SHA256 (ngx_http_response-0.3.tar.gz) = 0835584029f053051c624adbe33a826ab0205c9d85a02af6019e6b57607e9045
-SIZE (ngx_http_response-0.3.tar.gz) = 2244
-SHA256 (passenger-6.0.15.tar.gz) = 73fa22da5a11e4bc4ad6b95c13a0e393ba18109e6e07bd1953c45b2f0c0aae80
-SIZE (passenger-6.0.15.tar.gz) = 8379463
-SHA256 (yaoweibin-nginx_ajp_module-fcbb2cc_GH0.tar.gz) = 522e94c59f5783f281d868ede2adf325bf2f8ffb9e62cf8451d4b9ac0516916c
-SIZE (yaoweibin-nginx_ajp_module-fcbb2cc_GH0.tar.gz) = 110807
+SHA256 (passenger-6.0.17.tar.gz) = 385559ed1d78eb83165222d568721dcc4222bb57c1939811ecd2c4ef33937ba7
+SIZE (passenger-6.0.17.tar.gz) = 8422867
+SHA256 (msva-nginx_ajp_module-fcbb2cc_GH0.tar.gz) = 522e94c59f5783f281d868ede2adf325bf2f8ffb9e62cf8451d4b9ac0516916c
+SIZE (msva-nginx_ajp_module-fcbb2cc_GH0.tar.gz) = 110807
SHA256 (openresty-array-var-nginx-module-v0.05_GH0.tar.gz) = c949d4be6f3442c8e2937046448dc8d8def25c0e0fa6f4e805144cea45eabe80
SIZE (openresty-array-var-nginx-module-v0.05_GH0.tar.gz) = 11280
SHA256 (anomalizer-ngx_aws_auth-21931b2_GH0.tar.gz) = d8a2422da96a638e9a911e4edb592954d9c0fe1576456fec9809ef4e2a0a863d
SIZE (anomalizer-ngx_aws_auth-21931b2_GH0.tar.gz) = 15580
SHA256 (google-ngx_brotli-9aec15e_GH0.tar.gz) = 0177b1158ff7092b9996346de28a0b296dc33addb2af4e8904794d19b4a9a808
SIZE (google-ngx_brotli-9aec15e_GH0.tar.gz) = 16194
-SHA256 (nginx-modules-ngx_cache_purge-4746629_GH0.tar.gz) = 68debae40722e15a70f37e10d20a4d3b9a26597457f1dd639e903b62c0a2f689
-SIZE (nginx-modules-ngx_cache_purge-4746629_GH0.tar.gz) = 17114
-SHA256 (nginx-clojure-nginx-clojure-v0.5.2_GH0.tar.gz) = cb8bc0bef5d298df55fb625400cb77c4145dc5167483e111ff2b90d4d3396200
-SIZE (nginx-clojure-nginx-clojure-v0.5.2_GH0.tar.gz) = 755650
-SHA256 (grahamedgecombe-nginx-ct-v1.3.2_GH0.tar.gz) = b4ceae549b9dbf84b2e511633982e4efeee0388e3b7a038a8bac555008a77b88
-SIZE (grahamedgecombe-nginx-ct-v1.3.2_GH0.tar.gz) = 6844
+SHA256 (torden-ngx_cache_purge-2b977cf_GH0.tar.gz) = 9de7dad5827edf37e803435ee764f6815562053daa0821aedaf539580307ff93
+SIZE (torden-ngx_cache_purge-2b977cf_GH0.tar.gz) = 16739
+SHA256 (nginx-clojure-nginx-clojure-4347955_GH0.tar.gz) = 9f0fc3b479936fc5b20101c6d238bff5a6f4a32b28d8ec4a511f902abe2abc1f
+SIZE (nginx-clojure-nginx-clojure-4347955_GH0.tar.gz) = 756519
+SHA256 (AirisX-nginx_cookie_flag_module-c4ff449_GH0.tar.gz) = 4b8c1c1e1ed59ed85751f4bd7d68026ad5051103c8b983e05ad17eb0cdab138e
+SIZE (AirisX-nginx_cookie_flag_module-c4ff449_GH0.tar.gz) = 4713
+SHA256 (grahamedgecombe-nginx-ct-93e9884_GH0.tar.gz) = 72fdd125b9207cdda135f368095f85b943a78a4ff004d1cd217972e12b1571b2
+SIZE (grahamedgecombe-nginx-ct-93e9884_GH0.tar.gz) = 7224
SHA256 (simpl-ngx_devel_kit-v0.3.1_GH0.tar.gz) = 0e971105e210d272a497567fa2e2c256f4e39b845a5ba80d373e26ba1abfbd85
SIZE (simpl-ngx_devel_kit-v0.3.1_GH0.tar.gz) = 66542
-SHA256 (openresty-drizzle-nginx-module-v0.1.11_GH0.tar.gz) = a2f62c418078fe1271fcf68bbdf28164ae06885f17a2b4941fbf766f3ccf4a4f
-SIZE (openresty-drizzle-nginx-module-v0.1.11_GH0.tar.gz) = 50864
-SHA256 (ZigzagAK-ngx_dynamic_healthcheck-61acf02_GH0.tar.gz) = 9a6ac88bcb85cd76e56ce03cf074a78b600cc787642379f76410e78326aca5aa
-SIZE (ZigzagAK-ngx_dynamic_healthcheck-61acf02_GH0.tar.gz) = 49810
+SHA256 (openresty-drizzle-nginx-module-3504fc6_GH0.tar.gz) = 86076735597f14db28cffabc0ab1f233cd51aab7cf112c56e267783e7814fc65
+SIZE (openresty-drizzle-nginx-module-3504fc6_GH0.tar.gz) = 51596
SHA256 (ZigzagAK-ngx_dynamic_upstream-960eef2_GH0.tar.gz) = 86e7c6ed6dba2d4c5f5b87ecb91f25ccdb7a08b8a88236e632114f830b9e354b
SIZE (ZigzagAK-ngx_dynamic_upstream-960eef2_GH0.tar.gz) = 23003
SHA256 (openresty-echo-nginx-module-5a402aa_GH0.tar.gz) = bb2a4b1a0e5ffa0203c1be854e663fc92cee0d7b5e0f7a38c0e163ae9124a38f
SIZE (openresty-echo-nginx-module-5a402aa_GH0.tar.gz) = 53336
-SHA256 (openresty-encrypted-session-nginx-module-v0.08_GH0.tar.gz) = 6e526ea097c6805ec2cf1d0d3d79ed24326bc2d0babe158c29edd07d8c0d106a
-SIZE (openresty-encrypted-session-nginx-module-v0.08_GH0.tar.gz) = 11802
+SHA256 (openresty-encrypted-session-nginx-module-v0.09_GH0.tar.gz) = fe9b95acf9726aefd71bf0aca6c11bee007f1da67e64be9b21a7131f0ed75ba6
+SIZE (openresty-encrypted-session-nginx-module-v0.09_GH0.tar.gz) = 11847
SHA256 (calio-form-input-nginx-module-v0.12_GH0.tar.gz) = 5c1869d55897075adb3fdf840b21060dc54669a1f840a36d1539acc7e59dd106
SIZE (calio-form-input-nginx-module-v0.12_GH0.tar.gz) = 11090
-SHA256 (technowledgy-nginx_http_gridfs_module-7970bab_GH0.tar.gz) = 3c53190c24a578c0d6d340d290cd87f139a651372c78b7518b617a037db60582
-SIZE (technowledgy-nginx_http_gridfs_module-7970bab_GH0.tar.gz) = 20495
-SHA256 (10gen-archive-mongo-c-driver-legacy-f06669b_GH0.tar.gz) = cbd6b34aadea40b43db431590808a9e12d813f896ad2b1dcffb820872ef97b21
-SIZE (10gen-archive-mongo-c-driver-legacy-f06669b_GH0.tar.gz) = 79396
-SHA256 (openresty-headers-more-nginx-module-d6d7eba_GH0.tar.gz) = d68b8dad5921f83eb0e667e1b072c7f270043c5471ffba599cc8af5b9222ef3a
-SIZE (openresty-headers-more-nginx-module-d6d7eba_GH0.tar.gz) = 28411
+SHA256 (ogarrett-nginx-fips-check-module-6cb4270_GH0.tar.gz) = d52fbb0f2819cd91b710ad85e6c8b452fdca6a5d81b0694d6637adba3fc2382c
+SIZE (ogarrett-nginx-fips-check-module-6cb4270_GH0.tar.gz) = 6494
+SHA256 (nieoding-nginx-gridfs-059bdc3_GH0.tar.gz) = 9b059b5ae7b602d12d32d5ebe2700827ea625f22c0fb3b9956242e11de63845b
+SIZE (nieoding-nginx-gridfs-059bdc3_GH0.tar.gz) = 4674
+SHA256 (openresty-headers-more-nginx-module-33b646d_GH0.tar.gz) = 4e68ef77ce8bc3c248c04ddc112bb2230adf2de84c77430cedc8a4458ffb7369
+SIZE (openresty-headers-more-nginx-module-33b646d_GH0.tar.gz) = 28812
SHA256 (dvershinin-nginx_accept_language_module-5683967_GH0.tar.gz) = a58feb576f2231498b8a3863d3c6fba45c7d48bc48735fa714e07a7bfbedb6e3
SIZE (dvershinin-nginx_accept_language_module-5683967_GH0.tar.gz) = 3425
-SHA256 (atomx-nginx-http-auth-digest-cd86418_GH0.tar.gz) = 9858b202e5009da4047ac16f3ee02e018673237f0d9652a9e1c2c9d7917f07bf
-SIZE (atomx-nginx-http-auth-digest-cd86418_GH0.tar.gz) = 17869
-SHA256 (TeslaGov-ngx-http-auth-jwt-module-80d89d9_GH0.tar.gz) = 1b71290a0eab6a0bb6ab75e1228a302cf76c82f3284ad0eb7e8b2bfa110582e7
-SIZE (TeslaGov-ngx-http-auth-jwt-module-80d89d9_GH0.tar.gz) = 14674
+SHA256 (atomx-nginx-http-auth-digest-274490c_GH0.tar.gz) = 0839c33c2f8d519f92daae274f62cf87eb68415d562c6500ee3e3721ce80557c
+SIZE (atomx-nginx-http-auth-digest-274490c_GH0.tar.gz) = 17815
SHA256 (stnoonan-spnego-http-auth-nginx-module-c626163_GH0.tar.gz) = dac75d65453744ffe0f7af248f10f98fc89efca07303aa45a610805e57c588fc
SIZE (stnoonan-spnego-http-auth-nginx-module-c626163_GH0.tar.gz) = 24404
-SHA256 (kvspb-nginx-auth-ldap-42d195d_GH0.tar.gz) = 78d74697f89821a7ed31c6c672f6c06923bb53a681ebb203eabebb657bdafd80
-SIZE (kvspb-nginx-auth-ldap-42d195d_GH0.tar.gz) = 18455
-SHA256 (sto-ngx_http_auth_pam_module-v1.5.1_GH0.tar.gz) = 77676842919134af88a7b4bfca4470223e3a00d287d17c0dbdc9a114a685b6e7
-SIZE (sto-ngx_http_auth_pam_module-v1.5.1_GH0.tar.gz) = 6863
+SHA256 (kvspb-nginx-auth-ldap-83c059b_GH0.tar.gz) = e76e9e117ad51af578a68fa7a30c256178796bb271fa77f01c93281a92b09921
+SIZE (kvspb-nginx-auth-ldap-83c059b_GH0.tar.gz) = 18547
+SHA256 (sto-ngx_http_auth_pam_module-v1.5.3_GH0.tar.gz) = 882018fea8d6955ab3fe294aafa8ebb1fdff4eac313c29583fef02c6de76fae7
+SIZE (sto-ngx_http_auth_pam_module-v1.5.3_GH0.tar.gz) = 7084
SHA256 (arut-nginx-dav-ext-module-v3.0.0_GH0.tar.gz) = d2499d94d82d4e4eac8425d799e52883131ae86a956524040ff2fd230ef9f859
SIZE (arut-nginx-dav-ext-module-v3.0.0_GH0.tar.gz) = 14558
SHA256 (openresty-nginx-eval-module-582bd25_GH0.tar.gz) = 014bedb2b334ba8e8e23b4c660590357f8055dbed7b9b017e4cc2937876a8822
@@ -65,8 +61,8 @@ SHA256 (aperezdc-ngx-fancyindex-v0.5.2_GH0.tar.gz) = c3dd84d8ba0b8daeace3041ef59
SIZE (aperezdc-ngx-fancyindex-v0.5.2_GH0.tar.gz) = 29052
SHA256 (alibaba-nginx-http-footer-filter-1.2.2_GH0.tar.gz) = 3493b54460c59370f9f60c6e662862752f1920fc6e684f7a66bb2b3260692813
SIZE (alibaba-nginx-http-footer-filter-1.2.2_GH0.tar.gz) = 3934
-SHA256 (leev-ngx_http_geoip2_module-3.3_GH0.tar.gz) = 41378438c833e313a18869d0c4a72704b4835c30acaf7fd68013ab6732ff78a7
-SIZE (leev-ngx_http_geoip2_module-3.3_GH0.tar.gz) = 8509
+SHA256 (leev-ngx_http_geoip2_module-3.4_GH0.tar.gz) = ad72fc23348d715a330994984531fab9b3606e160483236737f9a4a6957d9452
+SIZE (leev-ngx_http_geoip2_module-3.4_GH0.tar.gz) = 8877
SHA256 (ip2location-ip2location-nginx-2df35fb_GH0.tar.gz) = 86d6d6d6b4437ecc621c5aac7bd5475dffd33afb70a51c5ea3c7f341ded46efb
SIZE (ip2location-ip2location-nginx-2df35fb_GH0.tar.gz) = 5462
SHA256 (ip2location-ip2proxy-nginx-02ce447_GH0.tar.gz) = edbafe23087f019364f9d1c1c615fdbc5116ec727c49bf442e3e4b39441fc4cc
@@ -75,46 +71,46 @@ SHA256 (nginx-modules-ngx_http_json_status_module-1d2f303_GH0.tar.gz) = fdc34e0e
SIZE (nginx-modules-ngx_http_json_status_module-1d2f303_GH0.tar.gz) = 6736
SHA256 (kr-nginx-notice-3c95966_GH0.tar.gz) = e829fc94178cc8c91fef15a1fc44ee7ac162c13eddc0bba4c9427aaa23386885
SIZE (kr-nginx-notice-3c95966_GH0.tar.gz) = 3343
-SHA256 (slact-nchan-v1.3.0_GH0.tar.gz) = 70ffed635d24aab0ff1ebb595d8b41fde105127dfd50cecac6df86b558778537
-SIZE (slact-nchan-v1.3.0_GH0.tar.gz) = 745161
-SHA256 (wandenberg-nginx-push-stream-module-0.5.4_GH0.tar.gz) = 5253bb8a804ea679e514137a234637298f044c3ef63c053670bf3802ff3535b1
-SIZE (wandenberg-nginx-push-stream-module-0.5.4_GH0.tar.gz) = 183493
-SHA256 (yaoweibin-ngx_http_substitutions_filter_module-b8a71ea_GH0.tar.gz) = 08f966328fc1b77e0dbbcacd9532c3905060c20c45b6461b6f2075cd844d0891
-SIZE (yaoweibin-ngx_http_substitutions_filter_module-b8a71ea_GH0.tar.gz) = 94039
+SHA256 (slact-nchan-v1.3.6_GH0.tar.gz) = ba0b7cc6b710a20ce1ed2554caf56154035291aaf115e407d7a6bb699fde42df
+SIZE (slact-nchan-v1.3.6_GH0.tar.gz) = 761436
+SHA256 (wandenberg-nginx-push-stream-module-8c02220_GH0.tar.gz) = ab4fbe236e8bc500f0c5e13403d6a0e2e4e4ec17b81e0fcedaf669b4339626a6
+SIZE (wandenberg-nginx-push-stream-module-8c02220_GH0.tar.gz) = 196720
+SHA256 (yaoweibin-ngx_http_substitutions_filter_module-c6f825f_GH0.tar.gz) = 4ab034f2e056148469b440394e1664c46405712ef27bc4f3197e42bf7df8460e
+SIZE (yaoweibin-ngx_http_substitutions_filter_module-c6f825f_GH0.tar.gz) = 94062
SHA256 (tarantool-nginx_upstream_module-aeb8696_GH0.tar.gz) = 514aa57155c73c2e3f7bdfe00c580183df343f2fa4b34e77f040cf6557caffae
SIZE (tarantool-nginx_upstream_module-aeb8696_GH0.tar.gz) = 75708
-SHA256 (fdintino-nginx-upload-module-aa42509_GH0.tar.gz) = 2285245bdef11656e5bcc866e693e48f84b7443d36924f45a6fb8647cd35319a
-SIZE (fdintino-nginx-upload-module-aa42509_GH0.tar.gz) = 42221
-SHA256 (masterzen-nginx-upload-progress-module-afb2d31_GH0.tar.gz) = 6318851cc3a624aaad3b1f4cd9f51f79c86575c53dfe0c28bbdcf9470b6a33e6
-SIZE (masterzen-nginx-upload-progress-module-afb2d31_GH0.tar.gz) = 17269
+SHA256 (fdintino-nginx-upload-module-643b4c1_GH0.tar.gz) = a5bb48589b5c242683da33a9f1acc7847acc3ce4f2c4213ea524858aa789a6e9
+SIZE (fdintino-nginx-upload-module-643b4c1_GH0.tar.gz) = 42571
+SHA256 (masterzen-nginx-upload-progress-module-68b3ab3_GH0.tar.gz) = 35b506e57e19e780e01ecc7c3c31a64473c35e4a022f5a3f98092a60cd1c1602
+SIZE (masterzen-nginx-upload-progress-module-68b3ab3_GH0.tar.gz) = 17322
SHA256 (yaoweibin-nginx_upstream_check_module-9aecf15_GH0.tar.gz) = 4404c64e845e19feeb07a37976347987892a8e8680a961f793ff0d3ef96c07f4
SIZE (yaoweibin-nginx_upstream_check_module-9aecf15_GH0.tar.gz) = 130039
SHA256 (jaygooby-nginx-upstream-fair-10ecdcf_GH0.tar.gz) = 93f71b7cf0db9c6dbf97e3ee11cf8efbc149946c0949d7abd19c74c7620eea50
SIZE (jaygooby-nginx-upstream-fair-10ecdcf_GH0.tar.gz) = 10433
-SHA256 (ayty-adrianomartins-nginx-sticky-module-ng-c407e0d_GH0.tar.gz) = ee7d6a6e3f4452388d1e821470c2352c5d42970f756507b878529911e79c1b60
-SIZE (ayty-adrianomartins-nginx-sticky-module-ng-c407e0d_GH0.tar.gz) = 120679
-SHA256 (Novetta-nginx-video-thumbextractor-module-28861f2_GH0.tar.gz) = 04656da527d9e64cbdf1bf475a93193fa60324ffea160d05d4cc53c864943bc1
-SIZE (Novetta-nginx-video-thumbextractor-module-28861f2_GH0.tar.gz) = 34447
-SHA256 (rtm-ctrlz-mod_zip-cfd0be4_GH0.tar.gz) = 66e867c9fd311a04f4b69a928441b49b5322d631db650d971abbc55af485cc9c
-SIZE (rtm-ctrlz-mod_zip-cfd0be4_GH0.tar.gz) = 26113
+SHA256 (dvershinin-nginx-sticky-module-ng-2753211_GH0.tar.gz) = e4a533dfa214ea28122301aeebbb1a38e1d1972edb7ee9bc72271c14f2693005
+SIZE (dvershinin-nginx-sticky-module-ng-2753211_GH0.tar.gz) = 120676
+SHA256 (Novetta-nginx-video-thumbextractor-module-f5b5bae_GH0.tar.gz) = cb4ecc14bf8503eb73c50ca5e9952ce1f99badbf2e7fdd2346c73d2e7905a5a5
+SIZE (Novetta-nginx-video-thumbextractor-module-f5b5bae_GH0.tar.gz) = 34434
+SHA256 (vince2678-mod_zip-5b2604b_GH0.tar.gz) = 4fe63be3b842882494152e586f0b87e73f51bfbfd801b78f033c71a011cba789
+SIZE (vince2678-mod_zip-5b2604b_GH0.tar.gz) = 29559
SHA256 (calio-iconv-nginx-module-v0.14_GH0.tar.gz) = b8b9f355c05c0790226512f6732348a2404d48531688a1fc04ce6768163bf462
SIZE (calio-iconv-nginx-module-v0.14_GH0.tar.gz) = 13133
SHA256 (baysao-nginx-let-module-c1f23aa_GH0.tar.gz) = 7393809d5d8877812da1bd5b5fbd1d8b00bc85e71f2f387c344f007773e49050
SIZE (baysao-nginx-let-module-c1f23aa_GH0.tar.gz) = 20617
SHA256 (Taymindis-nginx-link-function-3.2.4_GH0.tar.gz) = 20c3679199ba7efe1598f03b2fa0b13591226363c8dd7930d7f02702cd5abada
SIZE (Taymindis-nginx-link-function-3.2.4_GH0.tar.gz) = 139656
-SHA256 (openresty-lua-nginx-module-v0.10.22_GH0.tar.gz) = 294d3d4b2d14fda1b8c539ff86f90047d203df861eb9a1ac44ec5c679ef55408
-SIZE (openresty-lua-nginx-module-v0.10.22_GH0.tar.gz) = 690975
+SHA256 (openresty-lua-nginx-module-v0.10.24_GH0.tar.gz) = c5935ff25557031ab34d960d958a210613c9e6369b97db169b946717eca48fdf
+SIZE (openresty-lua-nginx-module-v0.10.24_GH0.tar.gz) = 724961
SHA256 (openresty-memc-nginx-module-v0.19_GH0.tar.gz) = 8c2bdbe875e4f5225d0778bfb09a2668f9281d7de6218c7b462a7ba2cee06fe8
SIZE (openresty-memc-nginx-module-v0.19_GH0.tar.gz) = 34654
-SHA256 (SpiderLabs-ModSecurity-nginx-v1.0.2_GH0.tar.gz) = f8d3ff15520df736c5e20e91d5852ec27e0874566c2afce7dcb979e2298d6980
-SIZE (SpiderLabs-ModSecurity-nginx-v1.0.2_GH0.tar.gz) = 33767
-SHA256 (nbs-system-naxsi-1.3_GH0.tar.gz) = 439c8677372d2597b4360bbcc10bc86490de1fc75695b193ad5df154a214d628
-SIZE (nbs-system-naxsi-1.3_GH0.tar.gz) = 235626
-SHA256 (nginx-njs-0.7.9_GH0.tar.gz) = 92cc425d0b0952bb7e2e7a396cba58feb4a90fb3cb63441c201ab4d3e0cd6403
-SIZE (nginx-njs-0.7.9_GH0.tar.gz) = 617115
-SHA256 (opentracing-contrib-nginx-opentracing-2d81c29_GH0.tar.gz) = b082bb8e0f4de2e3361c9cae79e266856de1e5b26a43611e08446b7c2bc0b2b4
-SIZE (opentracing-contrib-nginx-opentracing-2d81c29_GH0.tar.gz) = 664357
+SHA256 (SpiderLabs-ModSecurity-nginx-v1.0.3_GH0.tar.gz) = 32a42256616cc674dca24c8654397390adff15b888b77eb74e0687f023c8751b
+SIZE (SpiderLabs-ModSecurity-nginx-v1.0.3_GH0.tar.gz) = 34063
+SHA256 (nbs-system-naxsi-29793dc_GH0.tar.gz) = 579df0e50ff32464f7bb152df9d93ea18c05c4aa3966ec4d8c603b5dd629be08
+SIZE (nbs-system-naxsi-29793dc_GH0.tar.gz) = 236932
+SHA256 (nginx-njs-0.7.12_GH0.tar.gz) = 7a75a39022dfb58dbf461053903a07cc48dd4942f7d82a46601819c1b0077687
+SIZE (nginx-njs-0.7.12_GH0.tar.gz) = 662554
+SHA256 (opentracing-contrib-nginx-opentracing-v0.24.0_GH0.tar.gz) = 5328c5f37e0615b5252aed51b9cd40f3d14989d995ad54134076aeda4ab9b280
+SIZE (opentracing-contrib-nginx-opentracing-v0.24.0_GH0.tar.gz) = 679417
SHA256 (konstruxi-ngx_postgres-8aa7359_GH0.tar.gz) = c69ad4495de7c7883ebc23e1e6c4cc83a4ac6a7fddd4d5c12e49d33b65f7c50b
SIZE (konstruxi-ngx_postgres-8aa7359_GH0.tar.gz) = 48544
SHA256 (openresty-rds-csv-nginx-module-v0.09_GH0.tar.gz) = 896be99c0cad50218417800a159e43ec088d6b58c099472ed3b3d7f179d6c0ea
@@ -123,25 +119,25 @@ SHA256 (openresty-rds-json-nginx-module-v0.15_GH0.tar.gz) = eaf18f60e981ea2442a7
SIZE (openresty-rds-json-nginx-module-v0.15_GH0.tar.gz) = 34744
SHA256 (openresty-redis2-nginx-module-v0.15_GH0.tar.gz) = d255571bcfb9939b78099df39cb4d42f174d789aec8c8e5e47b93942b0299438
SIZE (openresty-redis2-nginx-module-v0.15_GH0.tar.gz) = 25471
-SHA256 (sergey-dryabzhinsky-nginx-rtmp-module-8e344d7_GH0.tar.gz) = 5eebd1ffb082987e1c7413515f64d12c12bfd1485302cb71f7b0bb5d56e226b9
-SIZE (sergey-dryabzhinsky-nginx-rtmp-module-8e344d7_GH0.tar.gz) = 556138
-SHA256 (openresty-set-misc-nginx-module-4667684_GH0.tar.gz) = c11a71bd753a7e01b428c715fb2113e3eaf71a3704e5e297f0e7d7e54c86582c
-SIZE (openresty-set-misc-nginx-module-4667684_GH0.tar.gz) = 29338
+SHA256 (arut-nginx-rtmp-module-v1.2.2_GH0.tar.gz) = 07f19b7bffec5e357bb8820c63e5281debd45f5a2e6d46b1636d9202c3e09d78
+SIZE (arut-nginx-rtmp-module-v1.2.2_GH0.tar.gz) = 519934
+SHA256 (openresty-set-misc-nginx-module-3937e7b_GH0.tar.gz) = cb3a4675ab6b8741e5847cf5bc41ee3f6ec5cbceec53188f9ae96e48feea17c5
+SIZE (openresty-set-misc-nginx-module-3937e7b_GH0.tar.gz) = 29335
SHA256 (sflow-nginx-sflow-module-543c72a_GH0.tar.gz) = 95efdb1f6cfd6c32c577707f693eb6795c6f21ae062842bf84fe762d8b842955
SIZE (sflow-nginx-sflow-module-543c72a_GH0.tar.gz) = 29504
-SHA256 (nginx-shib-nginx-http-shibboleth-a386c18_GH0.tar.gz) = f803f1fb5e32a7f392fdd391fb9d46f0de0a5ae9cef6c5edc05e4e048e7d34bb
-SIZE (nginx-shib-nginx-http-shibboleth-a386c18_GH0.tar.gz) = 23256
+SHA256 (nginx-shib-nginx-http-shibboleth-be12df5_GH0.tar.gz) = aff9830b5de78dd9ce32cd2c55c5cf9173c99fe1a1d2190407c96668e7517bab
+SIZE (nginx-shib-nginx-http-shibboleth-be12df5_GH0.tar.gz) = 23872
SHA256 (baysao-ngx_slowfs_cache-d011a18_GH0.tar.gz) = 6ae8abb01a2aff788e75ec68621cb0159148a6f73730a84b30b0bdbc6cdc1758
SIZE (baysao-ngx_slowfs_cache-d011a18_GH0.tar.gz) = 11186
-SHA256 (cubicdaiya-ngx_small_light-v0.9.2_GH0.tar.gz) = 4cf660651d11330a13aab29eb1722bf792d7c3c42e2919a36a1957c4ed0f1533
-SIZE (cubicdaiya-ngx_small_light-v0.9.2_GH0.tar.gz) = 56502
-SHA256 (openresty-srcache-nginx-module-v0.32_GH0.tar.gz) = fd80e59b672e4ff3b4e943740b3facab421c6965226b5934aed16a514baacf47
-SIZE (openresty-srcache-nginx-module-v0.32_GH0.tar.gz) = 49891
-SHA256 (kaltura-nginx-vod-module-1.27_GH0.tar.gz) = b7f0e2eecbcdb8ed1f8c90eb86c756f563fc5d21768a351abe4de63f3ddf01d6
-SIZE (kaltura-nginx-vod-module-1.27_GH0.tar.gz) = 450777
-SHA256 (vozlt-nginx-module-vts-v0.1.18_GH0.tar.gz) = 17ea41d4083f6d1ab1ab83dad9160eeca66867abe16c5a0421f85a39d7c84b65
-SIZE (vozlt-nginx-module-vts-v0.1.18_GH0.tar.gz) = 380327
-SHA256 (tg123-websockify-nginx-module-e82d254_GH0.tar.gz) = 1ea0fd35e3bed61c822aa0bfb9329a38d908a602b78a5e5fef9c7e946a26de6b
-SIZE (tg123-websockify-nginx-module-e82d254_GH0.tar.gz) = 14378
+SHA256 (openresty-srcache-nginx-module-be22ac0_GH0.tar.gz) = 5753d1ffe87b5d6f5b7a0696667bb5ff1388738136fdee26ba55bc33f5796061
+SIZE (openresty-srcache-nginx-module-be22ac0_GH0.tar.gz) = 51029
+SHA256 (vozlt-nginx-module-sts-3c10d42_GH0.tar.gz) = 748b67ceb82b3b843ae915bf7863fd08b7c2427c045e5ec540242d050f7b30d0
+SIZE (vozlt-nginx-module-sts-3c10d42_GH0.tar.gz) = 352431
+SHA256 (kaltura-nginx-vod-module-1.31_GH0.tar.gz) = ace04201cf2d2b1a3e5e732a22b92225b8ce61a494df9cc7f79d97efface8952
+SIZE (kaltura-nginx-vod-module-1.31_GH0.tar.gz) = 470904
+SHA256 (vozlt-nginx-module-vts-v0.2.1_GH0.tar.gz) = 1a63d78d3ae4df18b3a57a616eeee006cdc0bd71aa7ea2947046004b123704f4
+SIZE (vozlt-nginx-module-vts-v0.2.1_GH0.tar.gz) = 179679
+SHA256 (tg123-websockify-nginx-module-c11bc9a_GH0.tar.gz) = aca454bffcee2476dc92682ebfb8c0378a271fda178be7e945d648419d220758
+SIZE (tg123-websockify-nginx-module-c11bc9a_GH0.tar.gz) = 14646
SHA256 (openresty-xss-nginx-module-v0.06_GH0.tar.gz) = 0b12bbc53a41f3e3d6df419c173b8c87434be3e6cd255a8193aa91345a2de6cf
SIZE (openresty-xss-nginx-module-v0.06_GH0.tar.gz) = 12448
diff --git a/www/nginx/files/extra-patch-dynamic-tls b/www/nginx/files/extra-patch-dynamic-tls
deleted file mode 100644
index 86b617a55750..000000000000
--- a/www/nginx/files/extra-patch-dynamic-tls
+++ /dev/null
@@ -1,225 +0,0 @@
---- src/event/ngx_event_openssl.c.orig 2021-04-20 13:35:47 UTC
-+++ src/event/ngx_event_openssl.c
-@@ -1616,6 +1616,7 @@ ngx_ssl_create_connection(ngx_ssl_t *ssl
-
- sc->buffer = ((flags & NGX_SSL_BUFFER) != 0);
- sc->buffer_size = ssl->buffer_size;
-+ sc->dyn_rec = ssl->dyn_rec;
-
- sc->session_ctx = ssl->ctx;
-
-@@ -2555,6 +2556,41 @@ ngx_ssl_send_chain(ngx_connection_t *c,
-
- for ( ;; ) {
-
-+ /* Dynamic record resizing:
-+ We want the initial records to fit into one TCP segment
-+ so we don't get TCP HoL blocking due to TCP Slow Start.
-+ A connection always starts with small records, but after
-+ a given amount of records sent, we make the records larger
-+ to reduce header overhead.
-+ After a connection has idled for a given timeout, begin
-+ the process from the start. The actual parameters are
-+ configurable. If dyn_rec_timeout is 0, we assume dyn_rec is off. */
-+
-+ if (c->ssl->dyn_rec.timeout > 0 ) {
-+
-+ if (ngx_current_msec - c->ssl->dyn_rec_last_write >
-+ c->ssl->dyn_rec.timeout)
-+ {
-+ buf->end = buf->start + c->ssl->dyn_rec.size_lo;
-+ c->ssl->dyn_rec_records_sent = 0;
-+
-+ } else {
-+ if (c->ssl->dyn_rec_records_sent >
-+ c->ssl->dyn_rec.threshold * 2)
-+ {
-+ buf->end = buf->start + c->ssl->buffer_size;
-+
-+ } else if (c->ssl->dyn_rec_records_sent >
-+ c->ssl->dyn_rec.threshold)
-+ {
-+ buf->end = buf->start + c->ssl->dyn_rec.size_hi;
-+
-+ } else {
-+ buf->end = buf->start + c->ssl->dyn_rec.size_lo;
-+ }
-+ }
-+ }
-+
- while (in && buf->last < buf->end && send < limit) {
- if (in->buf->last_buf || in->buf->flush) {
- flush = 1;
-@@ -2662,6 +2698,9 @@ ngx_ssl_write(ngx_connection_t *c, u_cha
-
- if (n > 0) {
-
-+ c->ssl->dyn_rec_records_sent++;
-+ c->ssl->dyn_rec_last_write = ngx_current_msec;
-+
- if (c->ssl->saved_read_handler) {
-
- c->read->handler = c->ssl->saved_read_handler;
---- src/event/ngx_event_openssl.h.orig 2021-04-20 13:35:47 UTC
-+++ src/event/ngx_event_openssl.h
-@@ -66,11 +66,19 @@
-
- typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t;
-
-+typedef struct {
-+ ngx_msec_t timeout;
-+ ngx_uint_t threshold;
-+ size_t size_lo;
-+ size_t size_hi;
-+} ngx_ssl_dyn_rec_t;
-+
-
- struct ngx_ssl_s {
- SSL_CTX *ctx;
- ngx_log_t *log;
- size_t buffer_size;
-+ ngx_ssl_dyn_rec_t dyn_rec;
- };
-
-
-@@ -106,6 +114,10 @@ struct ngx_ssl_connection_s {
- unsigned in_ocsp:1;
- unsigned early_preread:1;
- unsigned write_blocked:1;
-+
-+ ngx_ssl_dyn_rec_t dyn_rec;
-+ ngx_msec_t dyn_rec_last_write;
-+ ngx_uint_t dyn_rec_records_sent;
- };
-
-
-@@ -115,7 +127,7 @@ struct ngx_ssl_connection_s {
- #define NGX_SSL_DFLT_BUILTIN_SCACHE -5
-
-
--#define NGX_SSL_MAX_SESSION_SIZE 4096
-+#define NGX_SSL_MAX_SESSION_SIZE 16384
-
- typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t;
-
---- src/http/modules/ngx_http_ssl_module.c.orig 2021-04-20 13:35:47 UTC
-+++ src/http/modules/ngx_http_ssl_module.c
-@@ -301,6 +301,41 @@ static ngx_command_t ngx_http_ssl_comma
- offsetof(ngx_http_ssl_srv_conf_t, reject_handshake),
- NULL },
-
-+ { ngx_string("ssl_dyn_rec_enable"),
-+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
-+ ngx_conf_set_flag_slot,
-+ NGX_HTTP_SRV_CONF_OFFSET,
-+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_enable),
-+ NULL },
-+
-+ { ngx_string("ssl_dyn_rec_timeout"),
-+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
-+ ngx_conf_set_msec_slot,
-+ NGX_HTTP_SRV_CONF_OFFSET,
-+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_timeout),
-+ NULL },
-+
-+ { ngx_string("ssl_dyn_rec_size_lo"),
-+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
-+ ngx_conf_set_size_slot,
-+ NGX_HTTP_SRV_CONF_OFFSET,
-+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_lo),
-+ NULL },
-+
-+ { ngx_string("ssl_dyn_rec_size_hi"),
-+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
-+ ngx_conf_set_size_slot,
-+ NGX_HTTP_SRV_CONF_OFFSET,
-+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_size_hi),
-+ NULL },
-+
-+ { ngx_string("ssl_dyn_rec_threshold"),
-+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
-+ ngx_conf_set_num_slot,
-+ NGX_HTTP_SRV_CONF_OFFSET,
-+ offsetof(ngx_http_ssl_srv_conf_t, dyn_rec_threshold),
-+ NULL },
-+
- ngx_null_command
- };
-
-@@ -637,6 +672,11 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t
- sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR;
- sscf->stapling = NGX_CONF_UNSET;
- sscf->stapling_verify = NGX_CONF_UNSET;
-+ sscf->dyn_rec_enable = NGX_CONF_UNSET;
-+ sscf->dyn_rec_timeout = NGX_CONF_UNSET_MSEC;
-+ sscf->dyn_rec_size_lo = NGX_CONF_UNSET_SIZE;
-+ sscf->dyn_rec_size_hi = NGX_CONF_UNSET_SIZE;
-+ sscf->dyn_rec_threshold = NGX_CONF_UNSET_UINT;
-
- return sscf;
- }
-@@ -712,6 +752,20 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
- ngx_conf_merge_str_value(conf->stapling_responder,
- prev->stapling_responder, "");
-
-+ ngx_conf_merge_value(conf->dyn_rec_enable, prev->dyn_rec_enable, 0);
-+ ngx_conf_merge_msec_value(conf->dyn_rec_timeout, prev->dyn_rec_timeout,
-+ 1000);
-+ /* Default sizes for the dynamic record sizes are defined to fit maximal
-+ TLS + IPv6 overhead in a single TCP segment for lo and 3 segments for hi:
-+ 1369 = 1500 - 40 (IP) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead) */
-+ ngx_conf_merge_size_value(conf->dyn_rec_size_lo, prev->dyn_rec_size_lo,
-+ 1369);
-+ /* 4229 = (1500 - 40 - 20 - 10) * 3 - 61 */
-+ ngx_conf_merge_size_value(conf->dyn_rec_size_hi, prev->dyn_rec_size_hi,
-+ 4229);
-+ ngx_conf_merge_uint_value(conf->dyn_rec_threshold, prev->dyn_rec_threshold,
-+ 40);
-+
- conf->ssl.log = cf->log;
-
- if (conf->enable) {
-@@ -943,6 +997,28 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
- return NGX_CONF_ERROR;
- }
-
-+ if (conf->dyn_rec_enable) {
-+ conf->ssl.dyn_rec.timeout = conf->dyn_rec_timeout;
-+ conf->ssl.dyn_rec.threshold = conf->dyn_rec_threshold;
-+
-+ if (conf->buffer_size > conf->dyn_rec_size_lo) {
-+ conf->ssl.dyn_rec.size_lo = conf->dyn_rec_size_lo;
-+
-+ } else {
-+ conf->ssl.dyn_rec.size_lo = conf->buffer_size;
-+ }
-+
-+ if (conf->buffer_size > conf->dyn_rec_size_hi) {
-+ conf->ssl.dyn_rec.size_hi = conf->dyn_rec_size_hi;
-+
-+ } else {
-+ conf->ssl.dyn_rec.size_hi = conf->buffer_size;
-+ }
-+
-+ } else {
-+ conf->ssl.dyn_rec.timeout = 0;
-+ }
-+
- return NGX_CONF_OK;
- }
-
---- src/http/modules/ngx_http_ssl_module.h.orig 2021-04-20 13:35:47 UTC
-+++ src/http/modules/ngx_http_ssl_module.h
-@@ -67,6 +67,12 @@ typedef struct {
-
- u_char *file;
- ngx_uint_t line;
-+
-+ ngx_flag_t dyn_rec_enable;
-+ ngx_msec_t dyn_rec_timeout;
-+ size_t dyn_rec_size_lo;
-+ size_t dyn_rec_size_hi;
-+ ngx_uint_t dyn_rec_threshold;
- } ngx_http_ssl_srv_conf_t;
-
-
diff --git a/www/nginx/files/extra-patch-h2-autotune b/www/nginx/files/extra-patch-h2-autotune
deleted file mode 100644
index 0a1bf77e00f5..000000000000
--- a/www/nginx/files/extra-patch-h2-autotune
+++ /dev/null
@@ -1,449 +0,0 @@
-diff -r 7015f26aef90 -r 1739da077a8e auto/modules
---- auto/modules Wed Jul 29 13:28:04 2020 +0300
-+++ auto/modules Wed Aug 26 15:51:41 2020 -0700
-@@ -420,6 +420,15 @@
- ngx_module_libs=
- ngx_module_link=$HTTP_V2
-
-+ if [ $HTTP_V2 = YES -a $HTTP_V2_AUTOTUNE_UPLOAD = YES ]; then
-+ have=NGX_HTTP_V2_AUTOTUNE_UPLOAD . auto/have
-+
-+ ngx_module_deps="$ngx_module_deps \
-+ src/http/v2/ngx_autotune_upload.h"
-+ ngx_module_srcs="$ngx_module_srcs \
-+ src/http/v2/ngx_autotune_upload.c"
-+ fi
-+
- . auto/module
- fi
-
-diff -r 7015f26aef90 -r 1739da077a8e auto/options
---- auto/options Wed Jul 29 13:28:04 2020 +0300
-+++ auto/options Wed Aug 26 15:51:41 2020 -0700
-@@ -59,6 +59,7 @@
- HTTP_GZIP=YES
- HTTP_SSL=NO
- HTTP_V2=NO
-+HTTP_V2_AUTOTUNE_UPLOAD=NO
- HTTP_SSI=YES
- HTTP_REALIP=NO
- HTTP_XSLT=NO
-@@ -224,6 +225,7 @@
-
- --with-http_ssl_module) HTTP_SSL=YES ;;
- --with-http_v2_module) HTTP_V2=YES ;;
-+ --with-http_v2_autotune_upload) HTTP_V2_AUTOTUNE_UPLOAD=YES;;
- --with-http_realip_module) HTTP_REALIP=YES ;;
- --with-http_addition_module) HTTP_ADDITION=YES ;;
- --with-http_xslt_module) HTTP_XSLT=YES ;;
-diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_autotune_upload.c
---- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ src/http/v2/ngx_autotune_upload.c Wed Aug 26 15:51:41 2020 -0700
-@@ -0,0 +1,198 @@
-+/*
-+ * Copyright (C) 2020 Cloudflare, Inc.
-+ */
-+
-+#include <ngx_http.h>
-+#include <ngx_http_v2_module.h>
-+#include <ngx_autotune_upload.h>
-+
-+static void *ngx_prealloc(ngx_pool_t *pool, void *p, size_t size);
-+static void *ngx_realloc(void *oldp, size_t size, ngx_log_t *log);
-+
-+static ngx_int_t ngx_resize_buf(ngx_pool_t *pool, ngx_buf_t *buf, size_t nsize);
-+
-+
-+static void *
-+ngx_prealloc(ngx_pool_t *pool, void *p, size_t size)
-+{
-+ ngx_pool_large_t *l;
-+ void *newp;
-+
-+ for (l = pool->large; l; l = l->next) {
-+ if (p == l->alloc) {
-+ ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
-+ "prealloc: %p", l->alloc);
-+
-+ newp = ngx_realloc(l->alloc, size, pool->log);
-+ if (newp) {
-+ l->alloc = newp;
-+
-+ return newp;
-+ } else {
-+ return NULL;
-+ }
-+ }
-+ }
-+
-+ /* not found */
-+ return NULL;
-+}
-+
-+
-+static void *
-+ngx_realloc(void *oldp, size_t size, ngx_log_t *log)
-+{
-+ void *newp;
-+
-+ newp = realloc(oldp, size);
-+ if (newp == NULL) {
-+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
-+ "realloc(%uz) failed", size);
-+ }
-+
-+ ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "realloc: %p:%uz", newp, size);
-+
-+ return newp;
-+}
-+
-+
-+/* resize the buffer to the new size */
-+static ngx_int_t
-+ngx_resize_buf(ngx_pool_t *pool, ngx_buf_t *buf, size_t nsize)
-+{
-+ void *nbuf = ngx_prealloc(pool, buf->start, nsize);
-+
-+ if (!nbuf) {
-+ return NGX_ERROR;
-+ }
-+
-+ /* if buf->start is moved to a new location */
-+ if (nbuf != buf->start) {
-+ buf->pos = (u_char *)nbuf + (buf->pos - buf->start);
-+ buf->last = (u_char *)nbuf + (buf->last - buf->start);
-+ }
-+
-+ /* resize buffer */
-+ buf->start = nbuf;
-+ buf->end = (u_char *)nbuf + nsize;
-+
-+ return NGX_OK;
-+}
-+
-+
-+/* get current TCP RTT (ms) of the connection */
-+ngx_int_t
-+ngx_tcp_rtt_ms(int fd)
-+{
-+#if (NGX_HAVE_TCP_INFO)
-+ struct tcp_info ti;
-+ socklen_t len;
-+
-+ len = sizeof(struct tcp_info);
-+ if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &len) == 0) {
-+ return ti.tcpi_rtt / 1000;
-+ }
-+#endif
-+
-+ return NGX_ERROR;
-+}
-+
-+
-+/* return current timestamp (ms) */
-+ngx_msec_int_t
-+ngx_timestamp_ms()
-+{
-+ ngx_time_t *tp = ngx_timeofday();
-+
-+ return tp->sec * 1000 + tp->msec;
-+}
-+
-+
-+/*
-+ * double the buffer size based on the current BDP.
-+ * returns the new window size if resized.
-+ * returns the current window size if not resized.
-+ * if resizing fails, returns 0.
-+ */
-+size_t
-+ngx_autotune_client_body_buffer(ngx_http_request_t *r,
-+ size_t window)
-+{
-+ ngx_buf_t *buf;
-+ ngx_http_v2_stream_t *stream;
-+ ngx_msec_int_t ts_now;
-+ ngx_http_v2_loc_conf_t *h2lcf;
-+ size_t max_window;
-+
-+ h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module);
-+ max_window = h2lcf->max_client_body_buffer_size;
-+
-+ /* no autotuning configured */
-+ if (!max_window) {
-+ return window;
-+ }
-+
-+ /* if max_window is smaller than the current window, do nothing */
-+ if (window >= max_window) {
-+ return window;
-+ }
-+
-+ stream = r->stream;
-+ buf = r->request_body->buf;
-+
-+ /* if rtt is not available, do nothing */
-+ if (stream->rtt == NGX_ERROR) {
-+ return window;
-+ }
-+
-+ ts_now = ngx_timestamp_ms();
-+
-+ if (ts_now >= (stream->ts_checkpoint + stream->rtt)) {
-+ size_t cur_win = (buf->end - buf->start);
-+ size_t new_win = ngx_min(cur_win * 2 , max_window);
-+
-+ /* if already on the max size, do nothing */
-+ if (cur_win >= max_window) {
-+ return window;
-+ }
-+
-+ /* min rtt is 1ms to prevent BDP from becoming zero. */
-+ ngx_uint_t rtt = ngx_max(stream->rtt, 1);
-+
-+ /*
-+ * elapsed time (ms) from last checkpoint. mininum value is 1 to
-+ * prevent from dividing by zero in BDP calculation
-+ */
-+ ngx_uint_t elapsed = ngx_max(ts_now - stream->ts_checkpoint, 1);
-+
-+ /* calculate BDP (bytes) = rtt * bw */
-+ ngx_uint_t bdp = rtt * stream->bytes_body_read / elapsed;
-+
-+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->connection->connection->log, 0,
-+ "http2 autotune sid:%ui rtt:%z bdp:%z win:%z",
-+ stream->node->id, stream->rtt, bdp, window);
-+
-+ stream->bytes_body_read = 0;
-+ stream->ts_checkpoint = ts_now;
-+
-+ /*
-+ * check if we need to bump the buffer size
-+ * based on the heuristic condition
-+ */
-+ if (bdp > (window / 4)) {
-+ if (ngx_resize_buf(r->pool, buf, new_win) != NGX_OK) {
-+ return 0;
-+ }
-+
-+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP,
-+ stream->connection->connection->log, 0,
-+ "http2 autotune sid:%ui rtt:%z resized:%z->%z",
-+ stream->node->id, stream->rtt, window,
-+ window + (new_win - cur_win));
-+
-+ return window + (new_win - cur_win);
-+ }
-+ }
-+
-+ return window;
-+}
-diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_autotune_upload.h
---- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ src/http/v2/ngx_autotune_upload.h Wed Aug 26 15:51:41 2020 -0700
-@@ -0,0 +1,25 @@
-+/*
-+ * Copyright (C) 2020 Cloudflare, Inc.
-+ */
-+
-+#ifndef _NGX_AUTOTUNE_UPLOAD_H_INCLUDED_
-+#define _NGX_AUTOTUNE_UPLOAD_H_INCLUDED_
-+
-+#include <ngx_core.h>
-+
-+
-+/* the maximum size of the receiver window */
-+#define NGX_HTTP_V2_MAX_CLIENT_BODY_BUFFER_SIZE (64*1024*1024)
-+
-+
-+/* get current TCP RTT (ms) of the connection */
-+ngx_int_t ngx_tcp_rtt_ms(int fd);
-+
-+/* return current timestamp (ms) */
-+ngx_msec_int_t ngx_timestamp_ms();
-+
-+/* auto resize the buffer */
-+size_t ngx_autotune_client_body_buffer(ngx_http_request_t *r, size_t window);
-+
-+
-+#endif
-diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2.c
---- src/http/v2/ngx_http_v2.c Wed Jul 29 13:28:04 2020 +0300
-+++ src/http/v2/ngx_http_v2.c Wed Aug 26 15:51:41 2020 -0700
-@@ -11,6 +11,10 @@
- #include <ngx_http_v2_module.h>
-
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+#include <ngx_autotune_upload.h>
-+#endif
-+
- typedef struct {
- ngx_str_t name;
- ngx_uint_t offset;
-@@ -1122,6 +1126,10 @@
- pos += size;
- h2c->state.length -= size;
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ stream->bytes_body_read += size;
-+#endif
-+
- if (h2c->state.length) {
- return ngx_http_v2_state_save(h2c, pos, end,
- ngx_http_v2_state_read_data);
-@@ -3211,6 +3219,12 @@
-
- h2c->priority_limit += h2scf->concurrent_streams;
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ stream->bytes_body_read = 0;
-+ stream->rtt = ngx_tcp_rtt_ms(r->connection->fd);
-+ stream->ts_checkpoint = ngx_timestamp_ms();
-+#endif
-+
- return stream;
- }
-
-@@ -4323,6 +4337,15 @@
- return NGX_AGAIN;
- }
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ window = ngx_autotune_client_body_buffer(r, window);
-+
-+ /* resizing failed */
-+ if (!window) {
-+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
-+ }
-+#endif
-+
- if (ngx_http_v2_send_window_update(h2c, stream->node->id,
- window - stream->recv_window)
- == NGX_ERROR)
-diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2.h
---- src/http/v2/ngx_http_v2.h Wed Jul 29 13:28:04 2020 +0300
-+++ src/http/v2/ngx_http_v2.h Wed Aug 26 15:51:41 2020 -0700
-@@ -210,6 +210,15 @@
-
- ngx_pool_t *pool;
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ /* how much client request body read */
-+ ngx_uint_t bytes_body_read;
-+ /* timestamp of next checkpoint */
-+ ngx_msec_int_t ts_checkpoint;
-+ /* rtt(ms) of the connection */
-+ ngx_int_t rtt;
-+#endif
-+
- unsigned waiting:1;
- unsigned blocked:1;
- unsigned exhausted:1;
-diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2_module.c
---- src/http/v2/ngx_http_v2_module.c Wed Jul 29 13:28:04 2020 +0300
-+++ src/http/v2/ngx_http_v2_module.c Wed Aug 26 15:51:41 2020 -0700
-@@ -10,6 +10,9 @@
- #include <ngx_http.h>
- #include <ngx_http_v2_module.h>
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+#include <ngx_autotune_upload.h>
-+#endif
-
- static ngx_int_t ngx_http_v2_add_variables(ngx_conf_t *cf);
-
-@@ -38,6 +41,10 @@
- static char *ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data);
- static char *ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd,
- void *conf);
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+static char *ngx_http_v2_max_client_body_buffer_size(ngx_conf_t *cf, void *post,
-+ void *data);
-+#endif
-
-
- static ngx_conf_post_t ngx_http_v2_recv_buffer_size_post =
-@@ -50,6 +57,10 @@
- { ngx_http_v2_streams_index_mask };
- static ngx_conf_post_t ngx_http_v2_chunk_size_post =
- { ngx_http_v2_chunk_size };
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+static ngx_conf_post_t ngx_http_v2_max_client_body_buffer_size_post =
-+ { ngx_http_v2_max_client_body_buffer_size };
-+#endif
-
-
- static ngx_command_t ngx_http_v2_commands[] = {
-@@ -208,6 +219,15 @@
- 0,
- NULL },
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ { ngx_string("http2_max_client_body_buffer_size"),
-+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
-+ ngx_conf_set_size_slot,
-+ NGX_HTTP_LOC_CONF_OFFSET,
-+ offsetof(ngx_http_v2_loc_conf_t, max_client_body_buffer_size),
-+ &ngx_http_v2_max_client_body_buffer_size_post },
-+#endif
-+
- ngx_null_command
- };
-
-@@ -423,6 +443,10 @@
- h2lcf->push_preload = NGX_CONF_UNSET;
- h2lcf->push = NGX_CONF_UNSET;
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ h2lcf->max_client_body_buffer_size = NGX_CONF_UNSET_SIZE;
-+#endif
-+
- return h2lcf;
- }
-
-@@ -443,6 +467,12 @@
-
- ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0);
-
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ /* default is 0: no auto tuning */
-+ ngx_conf_merge_size_value(conf->max_client_body_buffer_size,
-+ prev->max_client_body_buffer_size, 0);
-+#endif
-+
- return NGX_CONF_OK;
- }
-
-@@ -608,3 +638,19 @@
-
- return NGX_CONF_OK;
- }
-+
-+
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+static char *
-+ngx_http_v2_max_client_body_buffer_size(ngx_conf_t *cf, void *post,
-+ void *data)
-+{
-+ size_t *sp = data;
-+
-+ if (*sp > NGX_HTTP_V2_MAX_CLIENT_BODY_BUFFER_SIZE) {
-+ *sp = NGX_HTTP_V2_MAX_CLIENT_BODY_BUFFER_SIZE;
-+ }
-+
-+ return NGX_CONF_OK;
-+}
-+#endif
-diff -r 7015f26aef90 -r 1739da077a8e src/http/v2/ngx_http_v2_module.h
---- src/http/v2/ngx_http_v2_module.h Wed Jul 29 13:28:04 2020 +0300
-+++ src/http/v2/ngx_http_v2_module.h Wed Aug 26 15:51:41 2020 -0700
-@@ -41,6 +41,10 @@
-
- ngx_flag_t push;
- ngx_array_t *pushes;
-+
-+#if (NGX_HTTP_V2_AUTOTUNE_UPLOAD)
-+ size_t max_client_body_buffer_size;
-+#endif
- } ngx_http_v2_loc_conf_t;
diff --git a/www/nginx/files/extra-patch-httpv3 b/www/nginx/files/extra-patch-httpv3
new file mode 100644
index 000000000000..ea266e8b4764
--- /dev/null
+++ b/www/nginx/files/extra-patch-httpv3
@@ -0,0 +1,26668 @@
+diff -r ac779115ed6e README
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/README Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,389 @@
++Experimental QUIC support for nginx
++-----------------------------------
++
++1. Introduction
++2. Building from sources
++3. Configuration
++4. Directives
++5. Clients
++6. Troubleshooting
++7. Contributing
++8. 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. Building from sources
++
++ The build is configured using the configure command.
++ Refer to http://nginx.org/en/docs/configure.html for details.
++
++ 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
++
++ A library that provides QUIC support is recommended to build nginx, there
++ are several of those available on the market:
++ + BoringSSL [4]
++ + LibreSSL [5]
++ + QuicTLS [6]
++
++ Alternatively, nginx can be configured with OpenSSL compatibility
++ layer, which emulates BoringSSL QUIC API for OpenSSL. This mode is
++ enabled by default if native QUIC support is not detected.
++ 0-RTT is not supported in OpenSSL compatibility mode.
++
++ 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"
++
++3. Configuration
++
++ The HTTP "listen" directive got a new option "quic" which enables
++ QUIC as client transport protocol instead of TCP.
++
++ The Stream "listen" directive got a new option "quic" which enables
++ QUIC as client transport protocol instead of TCP or plain UDP.
++
++ Along with "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
++ http3_hq
++ http3_stream_buffer_size
++ http3_max_concurrent_pushes
++ http3_max_concurrent_streams
++ http3_push
++ http3_push_preload
++
++ 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 quic 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. Directives
++
++ Syntax: quic_bpf on | off;
++ Default: quic_bpf off;
++ Context: main
++
++ Enables routing of QUIC packets using eBPF.
++ When enabled, this allows to support QUIC connection migration.
++ The directive is only supported on Linux 5.7+.
++
++
++ Syntax: quic_retry on | off;
++ Default: quic_retry off;
++ Context: http | stream, server
++
++ Enables the QUIC Address Validation feature. This includes:
++ - sending a new token in a Retry packet or a NEW_TOKEN frame
++ - validating a token received in the Initial packet
++
++
++ Syntax: quic_gso on | off;
++ Default: quic_gso off;
++ Context: http | stream, server
++
++ Enables sending in optimized batch mode using segmentation offloading.
++ Optimized sending is only supported on Linux featuring UDP_SEGMENT.
++
++
++ Syntax: quic_mtu size;
++ Default: quic_mtu 65527;
++ Context: http | stream, server
++
++ Sets the QUIC max_udp_payload_size transport parameter value.
++ This is the maximum UDP payload that we are willing to receive.
++
++
++ Syntax: quic_host_key file;
++ Default: -
++ Context: http | stream, server
++
++ Specifies a file with the secret key used to encrypt stateless reset and
++ address validation tokens. By default, a randomly generated key is used.
++
++
++ Syntax: quic_active_connection_id_limit number;
++ Default: quic_active_connection_id_limit 2;
++ Context: http | stream, server
++
++ Sets the QUIC active_connection_id_limit transport parameter value.
++ This is the maximum number of connection IDs we are willing to store.
++
++
++ Syntax: quic_timeout time;
++ Default: quic_timeout 60s;
++ Context: stream, server
++
++ Defines a timeout used to negotiate the QUIC idle timeout.
++ In the http module, it is taken from the keepalive_timeout directive.
++
++
++ Syntax: quic_stream_buffer_size size;
++ Default: quic_stream_buffer_size 64k;
++ Context: stream, server
++
++ Syntax: http3_stream_buffer_size size;
++ Default: http3_stream_buffer_size 64k;
++ Context: http, server
++
++ Sets buffer size for reading and writing of the QUIC STREAM payload.
++ The buffer size is used to calculate initial flow control limits
++ in the following QUIC transport parameters:
++ - initial_max_data
++ - initial_max_stream_data_bidi_local
++ - initial_max_stream_data_bidi_remote
++ - initial_max_stream_data_uni
++
++
++ Syntax: http3_max_concurrent_pushes number;
++ Default: http3_max_concurrent_pushes 10;
++ Context: http, server
++
++ Limits the maximum number of concurrent push requests in a connection.
++
++
++ Syntax: http3_max_concurrent_streams number;
++ Default: http3_max_concurrent_streams 128;
++ Context: http, server
++
++ Sets the maximum number of concurrent HTTP/3 streams in a connection.
++
++
++ Syntax: http3_push uri | off;
++ Default: http3_push off;
++ Context: http, server, location
++
++ Pre-emptively sends (pushes) a request to the specified uri along with
++ the response to the original request. Only relative URIs with absolute
++ path will be processed, for example:
++
++ http3_push /static/css/main.css;
++
++ The uri value can contain variables.
++
++ Several http3_push directives can be specified on the same configuration
++ level. The off parameter cancels the effect of the http3_push directives
++ inherited from the previous configuration level.
++
++
++ Syntax: http3_push_preload on | off;
++ Default: http3_push_preload off;
++ Context: http, server, location
++
++ Enables automatic conversion of preload links specified in the “Link”
++ response header fields into push requests.
++
++
++ Syntax: http3 on | off;
++ Default: http3 on;
++ Context: http, server
++
++ Enables HTTP/3 protocol negotiation.
++
++
++ Syntax: http3_hq on | off;
++ Default: http3_hq off;
++ Context: http, server
++
++ Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests.
++
++5. 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"
++
++
++6. 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
++
++7. Contributing
++
++ Please refer to
++ http://nginx.org/en/docs/contributing_changes.html
++
++8. Links
++
++ [1] https://datatracker.ietf.org/doc/html/rfc9000
++ [2] https://datatracker.ietf.org/doc/html/rfc9114
++ [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel
++ [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
+diff -r ac779115ed6e auto/lib/openssl/conf
+--- a/auto/lib/openssl/conf Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/lib/openssl/conf Fri Mar 31 00:04:13 2023 -0400
+@@ -5,12 +5,17 @@
+
+ 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
++ have=NGX_QUIC_OPENSSL_COMPAT . 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 +38,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"
+@@ -123,6 +125,35 @@ else
+ CORE_INCS="$CORE_INCS $ngx_feature_path"
+ CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+ OPENSSL=YES
++
++ if [ $USE_OPENSSL_QUIC = YES ]; then
++
++ ngx_feature="OpenSSL QUIC support"
++ ngx_feature_name="NGX_QUIC"
++ ngx_feature_test="SSL_set_quic_method(NULL, NULL)"
++ . auto/feature
++
++ if [ $ngx_found = no ]; then
++ have=NGX_QUIC_OPENSSL_COMPAT . auto/have
++
++ ngx_feature="OpenSSL QUIC compatibility"
++ ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0,
++ NULL, NULL, NULL, NULL, NULL)"
++ . auto/feature
++ fi
++
++ 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
+ fi
+
+diff -r ac779115ed6e auto/make
+--- a/auto/make Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/make Fri Mar 31 00:04:13 2023 -0400
+@@ -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
+diff -r ac779115ed6e auto/modules
+--- a/auto/modules Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/modules Fri Mar 31 00:04:13 2023 -0400
+@@ -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,33 @@ 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 :; then
+ ngx_module_name=ngx_http_static_module
+ ngx_module_incs=
+@@ -1035,6 +1075,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 +1326,63 @@ 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 \
++ src/event/quic/ngx_event_quic_openssl_compat.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 \
++ src/event/quic/ngx_event_quic_openssl_compat.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
+diff -r ac779115ed6e auto/options
+--- a/auto/options Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/options Fri Mar 31 00:04:13 2023 -0400
+@@ -45,6 +45,8 @@ USE_THREADS=NO
+
+ NGX_FILE_AIO=NO
+
++QUIC_BPF=NO
++
+ HTTP=YES
+
+ NGX_HTTP_LOG_PATH=
+@@ -59,6 +61,7 @@ HTTP_CHARSET=YES
+ HTTP_GZIP=YES
+ HTTP_SSL=NO
+ HTTP_V2=NO
++HTTP_V3=NO
+ HTTP_SSI=YES
+ HTTP_REALIP=NO
+ HTTP_XSLT=NO
+@@ -116,6 +119,7 @@ MAIL_SMTP=YES
+
+ STREAM=NO
+ STREAM_SSL=NO
++STREAM_QUIC=NO
+ STREAM_REALIP=NO
+ STREAM_LIMIT_CONN=YES
+ STREAM_ACCESS=YES
+@@ -149,6 +153,7 @@ PCRE_JIT=NO
+ PCRE2=YES
+
+ USE_OPENSSL=NO
++USE_OPENSSL_QUIC=NO
+ OPENSSL=NONE
+
+ USE_ZLIB=NO
+@@ -166,6 +171,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 +218,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 +237,7 @@ do
+
+ --with-http_ssl_module) HTTP_SSL=YES ;;
+ --with-http_v2_module) HTTP_V2=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 +324,7 @@ use the \"--with-mail_ssl_module\" optio
+ --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 +454,11 @@ 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_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 +547,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
+diff -r ac779115ed6e auto/os/linux
+--- a/auto/os/linux Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/os/linux Fri Mar 31 00:04:13 2023 -0400
+@@ -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>
++ $NGX_INCLUDE_INTTYPES_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"
+diff -r ac779115ed6e auto/sources
+--- a/auto/sources Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/sources Fri Mar 31 00:04:13 2023 -0400
+@@ -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 \
+diff -r ac779115ed6e src/core/nginx.c
+--- a/src/core/nginx.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/nginx.c Fri Mar 31 00:04:13 2023 -0400
+@@ -680,6 +680,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle,
+
+ 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);
+ }
+
+diff -r ac779115ed6e src/core/ngx_bpf.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/core/ngx_bpf.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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));
++}
+diff -r ac779115ed6e src/core/ngx_bpf.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/core/ngx_bpf.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/core/ngx_connection.c
+--- a/src/core/ngx_connection.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/ngx_connection.c Fri Mar 31 00:04:13 2023 -0400
+@@ -72,10 +72,6 @@ ngx_create_listening(ngx_conf_t *cf, str
+
+ 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
+ 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) {
+diff -r ac779115ed6e src/core/ngx_connection.h
+--- a/src/core/ngx_connection.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/ngx_connection.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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
+diff -r ac779115ed6e src/core/ngx_core.h
+--- a/src/core/ngx_core.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/ngx_core.h Fri Mar 31 00:04:13 2023 -0400
+@@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx
+ 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
+ #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
+ #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'
+diff -r ac779115ed6e src/event/ngx_event.c
+--- a/src/event/ngx_event.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event.c Fri Mar 31 00:04:13 2023 -0400
+@@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_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_OK;
++ }
++
++#endif
++
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ /* kqueue, epoll */
+@@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev,
+ {
+ ngx_connection_t *c;
+
++ c = wev->data;
++
++#if (NGX_QUIC)
++ if (c->quic) {
++ return NGX_OK;
++ }
++#endif
++
+ if (lowat) {
+- c = wev->data;
+-
+ if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+@@ -873,8 +891,16 @@ ngx_event_process_init(ngx_cycle_t *cycl
+
+ #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)
+
+diff -r ac779115ed6e src/event/ngx_event_openssl.c
+--- a/src/event/ngx_event_openssl.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_openssl.c Fri Mar 31 00:04:13 2023 -0400
+@@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ng
+ #ifdef SSL_READ_EARLY_DATA_SUCCESS
+ static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c);
+ #endif
+-#if (NGX_DEBUG)
+-static void ngx_ssl_handshake_log(ngx_connection_t *c);
+-#endif
+ static void ngx_ssl_handshake_handler(ngx_event_t *ev);
+ #ifdef SSL_READ_EARLY_DATA_SUCCESS
+ static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf,
+@@ -2052,7 +2049,7 @@ ngx_ssl_try_early_data(ngx_connection_t
+
+ #if (NGX_DEBUG)
+
+-static void
++void
+ ngx_ssl_handshake_log(ngx_connection_t *c)
+ {
+ char buf[129], *s, *d;
+@@ -3202,6 +3199,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);
+diff -r ac779115ed6e src/event/ngx_event_openssl.h
+--- a/src/event/ngx_event_openssl.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_openssl.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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>
+@@ -302,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ng
+
+
+ ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
++#if (NGX_DEBUG)
++void ngx_ssl_handshake_log(ngx_connection_t *c);
++#endif
+ ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
+ ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit);
+diff -r ac779115ed6e src/event/ngx_event_udp.c
+--- a/src/event/ngx_event_udp.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_udp.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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);
+diff -r ac779115ed6e src/event/ngx_event_udp.h
+--- a/src/event/ngx_event_udp.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_udp.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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 {
+diff -r ac779115ed6e src/event/quic/bpf/bpfgen.sh
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/bpfgen.sh Fri Mar 31 00:04:13 2023 -0400
+@@ -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
++
+diff -r ac779115ed6e src/event/quic/bpf/makefile
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/makefile Fri Mar 31 00:04:13 2023 -0400
+@@ -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:
+diff -r ac779115ed6e src/event/quic/bpf/ngx_quic_reuseport_helper.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,1444 @@
++
++/*
++ * 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_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 != (ngx_uint_t) -1) {
++ 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->push.log = c->log;
++ qc->push.data = c;
++ qc->push.handler = ngx_quic_push_handler;
++
++ qc->close.log = c->log;
++ qc->close.data = c;
++ qc->close.handler = ngx_quic_close_handler;
++
++ qc->path_validation.log = c->log;
++ qc->path_validation.data = c;
++ qc->path_validation.handler = ngx_quic_path_validation_handler;
++
++ 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;
++ }
++
++ c->idle = 1;
++ ngx_reusable_connection(c, 1);
++
++ 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) {
++ c->close = 0;
++
++ if (!ngx_exiting) {
++ qc->error = NGX_QUIC_ERR_NO_ERROR;
++ qc->error_reason = "graceful shutdown";
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ if (!qc->closing && qc->conf->shutdown) {
++ qc->conf->shutdown(c);
++ }
++
++ return;
++ }
++
++ b = c->udp->buffer;
++ if (b == NULL) {
++ return;
++ }
++
++ 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 (qc->error == (ngx_uint_t) -1) {
++ qc->error = NGX_QUIC_ERR_INTERNAL_ERROR;
++ qc->error_app = 0;
++ }
++
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic close immediate term:%d drain:%d "
++ "%serror:%ui \"%s\"",
++ rc == NGX_ERROR ? 1 : 0, qc->draining,
++ qc->error_app ? "app " : "", qc->error,
++ qc->error_reason ? qc->error_reason : "");
++
++ if (rc == NGX_OK) {
++ ctx = ngx_quic_get_send_ctx(qc, qc->error_level);
++ ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx));
++ }
++
++ (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;
++ }
++
++ if (qc->close.posted) {
++ ngx_delete_posted_event(&qc->close);
++ }
++
++ 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);
++
++ if (qc->closing) {
++ return;
++ }
++
++ qc->error = err;
++ qc->error_reason = reason;
++ qc->error_app = 1;
++ qc->error_ftype = 0;
++
++ ngx_post_event(&qc->close, &ngx_posted_events);
++}
++
++
++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_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler");
++
++ c = ev->data;
++
++ ngx_quic_close_connection(c, NGX_OK);
++}
++
++
++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 = (ngx_uint_t) -1;
++ 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_quic_connection_t *qc;
++
++ if (c->reusable) {
++ qc = ngx_quic_get_connection(c);
++ ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason);
++ }
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic.h Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,131 @@
++
++/*
++ * 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 ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c);
++typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c);
++
++
++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;
++
++ ngx_quic_init_pt init;
++ ngx_quic_shutdown_pt shutdown;
++
++ 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 sent;
++ 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;
++ unsigned cancelable:1;
++ unsigned fin_acked: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);
++void ngx_quic_cancelable_stream(ngx_connection_t *c);
++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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ack.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ack.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,1194 @@
++
++/*
++ * 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 << 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:
++ case NGX_QUIC_FT_RESET_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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ack.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ack.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_bpf.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_bpf.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_bpf_code.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_bpf_code.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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,
++};
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_connection.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connection.h Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,283 @@
++/*
++ * 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;
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++#include <ngx_event_quic_openssl_compat.h>
++#endif
++#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
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++ ngx_quic_compat_t *compat;
++#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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_connid.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connid.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_connid.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connid.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_frames.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_frames.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,891 @@
++
++/*
++ * 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_copy_buffer(ngx_connection_t *c, u_char *data, size_t len)
++{
++ ngx_buf_t buf;
++ ngx_chain_t cl, *out;
++ ngx_quic_buffer_t qb;
++
++ ngx_memzero(&buf, sizeof(ngx_buf_t));
++
++ buf.pos = 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 NGX_CHAIN_ERROR;
++ }
++
++ out = ngx_quic_read_buffer(c, &qb, len);
++ if (out == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ ngx_quic_free_buffer(c, &qb);
++
++ return out;
++}
++
++
++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");
++
++#ifdef NGX_QUIC_DEBUG_FRAMES
++ {
++ ngx_chain_t *cl;
++
++ p = ngx_slprintf(p, last, " token:");
++
++ 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_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
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_frames.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_frames.h Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,45 @@
++
++/*
++ * 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_copy_buffer(ngx_connection_t *c, u_char *data,
++ size_t len);
++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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_migration.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_migration.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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);
++ }
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_migration.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_migration.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_openssl_compat.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_openssl_compat.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,646 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++
++#define NGX_QUIC_COMPAT_RECORD_SIZE 1024
++
++#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39
++
++#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET"
++#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET"
++#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0"
++#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0"
++
++
++typedef struct {
++ ngx_quic_secret_t secret;
++ ngx_uint_t cipher;
++} ngx_quic_compat_keys_t;
++
++
++typedef struct {
++ ngx_log_t *log;
++
++ u_char type;
++ ngx_str_t payload;
++ uint64_t number;
++ ngx_quic_compat_keys_t *keys;
++
++ enum ssl_encryption_level_t level;
++} ngx_quic_compat_record_t;
++
++
++struct ngx_quic_compat_s {
++ const SSL_QUIC_METHOD *method;
++
++ enum ssl_encryption_level_t write_level;
++ enum ssl_encryption_level_t read_level;
++
++ uint64_t read_record;
++ ngx_quic_compat_keys_t keys;
++
++ ngx_str_t tp;
++ ngx_str_t ctp;
++};
++
++
++static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line);
++static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
++ ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len);
++static int ngx_quic_compat_add_transport_params_callback(SSL *ssl,
++ unsigned int ext_type, unsigned int context, const unsigned char **out,
++ size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg);
++static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl,
++ unsigned int ext_type, unsigned int context, const unsigned char *in,
++ size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg);
++static void ngx_quic_compat_message_callback(int write_p, int version,
++ int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
++static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec,
++ u_char *out, ngx_uint_t plain);
++static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec,
++ ngx_str_t *res);
++
++
++ngx_int_t
++ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx)
++{
++ SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback);
++
++ if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) {
++ return NGX_OK;
++ }
++
++ if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT,
++ SSL_EXT_CLIENT_HELLO
++ |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
++ ngx_quic_compat_add_transport_params_callback,
++ NULL,
++ NULL,
++ ngx_quic_compat_parse_transport_params_callback,
++ NULL)
++ == 0)
++ {
++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
++ "SSL_CTX_add_custom_ext() failed");
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line)
++{
++ u_char ch, *p, *start, value;
++ size_t n;
++ ngx_uint_t write;
++ const SSL_CIPHER *cipher;
++ ngx_quic_compat_t *com;
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++ enum ssl_encryption_level_t level;
++ u_char secret[EVP_MAX_MD_SIZE];
++
++ c = ngx_ssl_get_connection(ssl);
++ if (c->type != SOCK_DGRAM) {
++ return;
++ }
++
++ p = (u_char *) line;
++
++ for (start = p; *p && *p != ' '; p++);
++
++ n = p - start;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat secret %*s", n, start);
++
++ if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0)
++ {
++ level = ssl_encryption_handshake;
++ write = 0;
++
++ } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0)
++ {
++ level = ssl_encryption_handshake;
++ write = 1;
++
++ } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n)
++ == 0)
++ {
++ level = ssl_encryption_application;
++ write = 0;
++
++ } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n)
++ == 0)
++ {
++ level = ssl_encryption_application;
++ write = 1;
++
++ } else {
++ return;
++ }
++
++ if (*p++ == '\0') {
++ return;
++ }
++
++ for ( /* void */ ; *p && *p != ' '; p++);
++
++ if (*p++ == '\0') {
++ return;
++ }
++
++ for (n = 0, start = p; *p; p++) {
++ ch = *p;
++
++ if (ch >= '0' && ch <= '9') {
++ value = ch - '0';
++ goto next;
++ }
++
++ ch = (u_char) (ch | 0x20);
++
++ if (ch >= 'a' && ch <= 'f') {
++ value = ch - 'a' + 10;
++ goto next;
++ }
++
++ ngx_log_error(NGX_LOG_EMERG, c->log, 0,
++ "invalid OpenSSL QUIC secret format");
++
++ return;
++
++ next:
++
++ if ((p - start) % 2) {
++ secret[n++] += value;
++
++ } else {
++ if (n >= EVP_MAX_MD_SIZE) {
++ ngx_log_error(NGX_LOG_EMERG, c->log, 0,
++ "too big OpenSSL QUIC secret");
++ return;
++ }
++
++ secret[n] = (value << 4);
++ }
++ }
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++ cipher = SSL_get_current_cipher(ssl);
++
++ if (write) {
++ com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n);
++ com->write_level = level;
++
++ } else {
++ com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n);
++ com->read_level = level;
++ com->read_record = 0;
++
++ (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level,
++ cipher, secret, n);
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
++ ngx_quic_compat_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_hkdf_t seq[2];
++ ngx_quic_secret_t *peer_secret;
++ ngx_quic_ciphers_t ciphers;
++
++ peer_secret = &keys->secret;
++
++ 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;
++
++ secret_str.len = secret_len;
++ secret_str.data = (u_char *) secret;
++
++ ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str);
++ ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &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;
++}
++
++
++static int
++ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type,
++ unsigned int context, const unsigned char **out, size_t *outlen, X509 *x,
++ size_t chainidx, int *al, void *add_arg)
++{
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ if (c->type != SOCK_DGRAM) {
++ return 0;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat add transport params");
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ *out = com->tp.data;
++ *outlen = com->tp.len;
++
++ return 1;
++}
++
++
++static int
++ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type,
++ unsigned int context, const unsigned char *in, size_t inlen, X509 *x,
++ size_t chainidx, int *al, void *parse_arg)
++{
++ u_char *p;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ if (c->type != SOCK_DGRAM) {
++ return 0;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat parse transport params");
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ p = ngx_pnalloc(c->pool, inlen);
++ if (p == NULL) {
++ return 0;
++ }
++
++ ngx_memcpy(p, in, inlen);
++
++ com->ctp.data = p;
++ com->ctp.len = inlen;
++
++ return 1;
++}
++
++
++int
++SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method)
++{
++ BIO *rbio, *wbio;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method");
++
++ qc = ngx_quic_get_connection(c);
++
++ qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t));
++ if (qc->compat == NULL) {
++ return 0;
++ }
++
++ com = qc->compat;
++ com->method = quic_method;
++
++ rbio = BIO_new(BIO_s_mem());
++ if (rbio == NULL) {
++ return 0;
++ }
++
++ wbio = BIO_new(BIO_s_null());
++ if (wbio == NULL) {
++ return 0;
++ }
++
++ SSL_set_bio(ssl, rbio, wbio);
++
++ SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback);
++
++ /* early data is not supported */
++ SSL_set_max_early_data(ssl, 0);
++
++ return 1;
++}
++
++
++static void
++ngx_quic_compat_message_callback(int write_p, int version, int content_type,
++ const void *buf, size_t len, SSL *ssl, void *arg)
++{
++ ngx_uint_t alert;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++ enum ssl_encryption_level_t level;
++
++ if (!write_p) {
++ return;
++ }
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++
++ if (qc == NULL) {
++ /* closing */
++ return;
++ }
++
++ com = qc->compat;
++ level = com->write_level;
++
++ switch (content_type) {
++
++ case SSL3_RT_HANDSHAKE:
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat tx %s len:%uz ",
++ ngx_quic_level_name(level), len);
++
++ (void) com->method->add_handshake_data(ssl, level, buf, len);
++
++ break;
++
++ case SSL3_RT_ALERT:
++ if (len >= 2) {
++ alert = ((u_char *) buf)[1];
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat %s alert:%ui len:%uz ",
++ ngx_quic_level_name(level), alert, len);
++
++ (void) com->method->send_alert(ssl, level, alert);
++ }
++
++ break;
++ }
++}
++
++
++int
++SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
++ const uint8_t *data, size_t len)
++{
++ BIO *rbio;
++ size_t n;
++ u_char *p;
++ ngx_str_t res;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++ ngx_quic_compat_record_t rec;
++ u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1];
++ u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1
++ + SSL3_RT_HEADER_LENGTH
++ + EVP_GCM_TLS_TAG_LEN];
++
++ c = ngx_ssl_get_connection(ssl);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz",
++ ngx_quic_level_name(level), len);
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++ rbio = SSL_get_rbio(ssl);
++
++ while (len) {
++ ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t));
++
++ rec.type = SSL3_RT_HANDSHAKE;
++ rec.log = c->log;
++ rec.number = com->read_record++;
++ rec.keys = &com->keys;
++
++ if (level == ssl_encryption_initial) {
++ n = ngx_min(len, 65535);
++
++ rec.payload.len = n;
++ rec.payload.data = (u_char *) data;
++
++ ngx_quic_compat_create_header(&rec, out, 1);
++
++ BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH);
++ BIO_write(rbio, data, n);
++
++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat record len:%uz %*xs%*xs",
++ n + SSL3_RT_HEADER_LENGTH,
++ (size_t) SSL3_RT_HEADER_LENGTH, out, n, data);
++#endif
++
++ } else {
++ n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE);
++
++ p = ngx_cpymem(in, data, n);
++ *p++ = SSL3_RT_HANDSHAKE;
++
++ rec.payload.len = p - in;
++ rec.payload.data = in;
++
++ res.data = out;
++
++ if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) {
++ return 0;
++ }
++
++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat record len:%uz %xV", res.len, &res);
++#endif
++
++ BIO_write(rbio, res.data, res.len);
++ }
++
++ data += n;
++ len -= n;
++ }
++
++ return 1;
++}
++
++
++static size_t
++ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out,
++ ngx_uint_t plain)
++{
++ u_char type;
++ size_t len;
++
++ len = rec->payload.len;
++
++ if (plain) {
++ type = rec->type;
++
++ } else {
++ type = SSL3_RT_APPLICATION_DATA;
++ len += EVP_GCM_TLS_TAG_LEN;
++ }
++
++ out[0] = type;
++ out[1] = 0x03;
++ out[2] = 0x03;
++ out[3] = (len >> 8);
++ out[4] = len;
++
++ return 5;
++}
++
++
++static ngx_int_t
++ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res)
++{
++ ngx_str_t ad, out;
++ ngx_quic_secret_t *secret;
++ ngx_quic_ciphers_t ciphers;
++ u_char nonce[NGX_QUIC_IV_LEN];
++
++ ad.data = res->data;
++ ad.len = ngx_quic_compat_create_header(rec, ad.data, 0);
++
++ out.len = rec->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, rec->log, 0,
++ "quic compat ad len:%uz %xV", ad.len, &ad);
++#endif
++
++ if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ secret = &rec->keys->secret;
++
++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
++ ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number);
++
++ if (ngx_quic_tls_seal(ciphers.c, secret, &out,
++ nonce, &rec->payload, &ad, rec->log)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ res->len = ad.len + out.len;
++
++ return NGX_OK;
++}
++
++
++enum ssl_encryption_level_t
++SSL_quic_read_level(const SSL *ssl)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++
++ return qc->compat->read_level;
++}
++
++
++enum ssl_encryption_level_t
++SSL_quic_write_level(const SSL *ssl)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++
++ return qc->compat->write_level;
++}
++
++
++int
++SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
++ size_t params_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ com->tp.len = params_len;
++ com->tp.data = (u_char *) params;
++
++ return 1;
++}
++
++
++void
++SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params,
++ size_t *out_params_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ *out_params = com->ctp.data;
++ *out_params_len = com->ctp.len;
++}
++
++#endif /* NGX_QUIC_OPENSSL_COMPAT */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_openssl_compat.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_openssl_compat.h Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,60 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_
++#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_
++
++#ifdef TLSEXT_TYPE_quic_transport_parameters
++#undef NGX_QUIC_OPENSSL_COMPAT
++#else
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++typedef struct ngx_quic_compat_s ngx_quic_compat_t;
++
++
++enum ssl_encryption_level_t {
++ ssl_encryption_initial = 0,
++ ssl_encryption_early_data,
++ ssl_encryption_handshake,
++ ssl_encryption_application
++};
++
++
++typedef struct ssl_quic_method_st {
++ int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher,
++ const uint8_t *rsecret, size_t secret_len);
++ int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher,
++ const uint8_t *wsecret, size_t secret_len);
++ int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level,
++ const uint8_t *data, size_t len);
++ int (*flush_flight)(SSL *ssl);
++ int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level,
++ uint8_t alert);
++} SSL_QUIC_METHOD;
++
++
++ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx);
++
++int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method);
++int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
++ const uint8_t *data, size_t len);
++enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl);
++enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl);
++int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
++ size_t params_len);
++void SSL_get_peer_quic_transport_params(const SSL *ssl,
++ const uint8_t **out_params, size_t *out_params_len);
++
++
++#endif /* TLSEXT_TYPE_quic_transport_parameters */
++
++#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_output.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_output.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,1298 @@
++
++/*
++ * 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_chain_t *out;
++ 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;
++ }
++
++ out = ngx_quic_copy_buffer(c, token.data, token.len);
++ if (out == NGX_CHAIN_ERROR) {
++ 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->data = out;
++ frame->u.token.length = token.len;
++
++ 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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_output.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_output.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_protection.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_protection.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,1087 @@
++
++/*
++ * 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
++
++
++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 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_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_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);
++
++
++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_str_t iss;
++ ngx_uint_t i;
++ const EVP_MD *digest;
++ ngx_quic_hkdf_t seq[8];
++ 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;
++ }
++
++ iss.len = is_len;
++ iss.data = is;
++
++ 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;
++
++ /* labels per RFC 9001, 5.1. Packet Protection Keys */
++ ngx_quic_hkdf_set(&seq[0], "tls13 client in", &client->secret, &iss);
++ ngx_quic_hkdf_set(&seq[1], "tls13 quic key", &client->key, &client->secret);
++ ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", &client->iv, &client->secret);
++ ngx_quic_hkdf_set(&seq[3], "tls13 quic hp", &client->hp, &client->secret);
++ ngx_quic_hkdf_set(&seq[4], "tls13 server in", &server->secret, &iss);
++ ngx_quic_hkdf_set(&seq[5], "tls13 quic key", &server->key, &server->secret);
++ ngx_quic_hkdf_set(&seq[6], "tls13 quic iv", &server->iv, &server->secret);
++ ngx_quic_hkdf_set(&seq[7], "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;
++}
++
++
++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;
++}
++
++
++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_hkdf_t seq[3];
++ 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_set(&seq[0], "tls13 quic key",
++ &peer_secret->key, &secret_str);
++ ngx_quic_hkdf_set(&seq[1], "tls13 quic iv", &peer_secret->iv, &secret_str);
++ ngx_quic_hkdf_set(&seq[2], "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_hkdf_t seq[6];
++ 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_set(&seq[0], "tls13 quic ku",
++ &next->client.secret, &current->client.secret);
++ ngx_quic_hkdf_set(&seq[1], "tls13 quic key",
++ &next->client.key, &next->client.secret);
++ ngx_quic_hkdf_set(&seq[2], "tls13 quic iv",
++ &next->client.iv, &next->client.secret);
++ ngx_quic_hkdf_set(&seq[3], "tls13 quic ku",
++ &next->server.secret, &current->server.secret);
++ ngx_quic_hkdf_set(&seq[4], "tls13 quic key",
++ &next->server.key, &next->server.secret);
++ ngx_quic_hkdf_set(&seq[5], "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;
++}
++
++
++void
++ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn)
++{
++ nonce[len - 8] ^= (pn >> 56) & 0x3f;
++ nonce[len - 7] ^= (pn >> 48) & 0xff;
++ nonce[len - 6] ^= (pn >> 40) & 0xff;
++ nonce[len - 5] ^= (pn >> 32) & 0xff;
++ nonce[len - 4] ^= (pn >> 24) & 0xff;
++ nonce[len - 3] ^= (pn >> 16) & 0xff;
++ nonce[len - 2] ^= (pn >> 8) & 0xff;
++ nonce[len - 1] ^= pn & 0xff;
++}
++
++
++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;
++ ngx_str_t in, ad;
++ ngx_uint_t key_phase;
++ 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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_protection.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_protection.h Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,114 @@
++
++/*
++ * 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
++
++
++#ifdef OPENSSL_IS_BORINGSSL
++#define ngx_quic_cipher_t EVP_AEAD
++#else
++#define ngx_quic_cipher_t EVP_CIPHER
++#endif
++
++
++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;
++};
++
++
++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(seq, _label, _out, _prk) \
++ (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \
++ (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \
++ (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label);
++
++
++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);
++void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
++ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers,
++ enum ssl_encryption_level_t level);
++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);
++ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest,
++ ngx_log_t *log);
++
++
++#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_socket.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_socket.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_socket.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_socket.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ssl.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ssl.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,600 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#if defined OPENSSL_IS_BORINGSSL \
++ || defined LIBRESSL_VERSION_NUMBER \
++ || NGX_QUIC_OPENSSL_COMPAT
++#define NGX_QUIC_BORINGSSL_API 1
++#endif
++
++
++/*
++ * RFC 9000, 7.5. Cryptographic Message Buffering
++ *
++ * Implementations MUST support buffering at least 4096 bytes of data
++ */
++#define NGX_QUIC_MAX_BUFFERED 65535
++
++
++#if (NGX_QUIC_BORINGSSL_API)
++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);
++
++
++#if (NGX_QUIC_BORINGSSL_API)
++
++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;
++ }
++
++ 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) {
++ 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_chain_t *out;
++ const uint8_t *client_params;
++ ngx_quic_tp_t ctp;
++ ngx_quic_frame_t *frame;
++ ngx_connection_t *c;
++ 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 = NGX_QUIC_ERR_CRYPTO(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);
++
++ out = ngx_quic_copy_buffer(c, (u_char *) data, len);
++ if (out == NGX_CHAIN_ERROR) {
++ return 0;
++ }
++
++ 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);
++ qc->error_reason = "handshake failed";
++
++ 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) {
++
++ if (c->ssl->handshake_rejected) {
++ ngx_connection_error(c, 0, "handshake rejected");
++ ERR_clear_error();
++
++ return NGX_ERROR;
++ }
++
++ ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
++ return NGX_ERROR;
++ }
++ }
++
++ if (n <= 0 || SSL_in_init(ssl_conn)) {
++ if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data)
++ && qc->client_tp_done)
++ {
++ if (ngx_quic_init_streams(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++ }
++
++#if (NGX_DEBUG)
++ ngx_ssl_handshake_log(c);
++#endif
++
++ 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;
++ static SSL_QUIC_METHOD quic_method;
++
++ 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 (!quic_method.send_alert) {
++#if (NGX_QUIC_BORINGSSL_API)
++ quic_method.set_read_secret = ngx_quic_set_read_secret;
++ quic_method.set_write_secret = ngx_quic_set_write_secret;
++#else
++ quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets;
++#endif
++ quic_method.add_handshake_data = ngx_quic_add_handshake_data;
++ quic_method.flush_flight = ngx_quic_flush_flight;
++ quic_method.send_alert = ngx_quic_send_alert;
++ }
++
++ 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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ssl.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ssl.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_streams.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_streams.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,1768 @@
++
++/*
++ * 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_int_t ngx_quic_do_init_streams(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_can_shutdown(ngx_connection_t *c);
++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, *sc;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ pc = c->quic ? c->quic->parent : c;
++ qc = ngx_quic_get_connection(pc);
++
++ if (qc->closing) {
++ return NULL;
++ }
++
++ 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++;
++ }
++
++ qs = ngx_quic_create_stream(pc, id);
++ if (qs == NULL) {
++ return NULL;
++ }
++
++ sc = qs->connection;
++
++ sc->write->active = 1;
++ sc->write->ready = 1;
++
++ if (bidi) {
++ sc->read->active = 1;
++ }
++
++ return sc;
++}
++
++
++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_connection_t *sc;
++ ngx_rbtree_node_t *node;
++ ngx_quic_stream_t *qs;
++
++ 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;
++ }
++
++ node = ngx_rbtree_min(tree->root, tree->sentinel);
++
++ while (node) {
++ qs = (ngx_quic_stream_t *) node;
++ node = ngx_rbtree_next(tree, node);
++ sc = qs->connection;
++
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;
++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT;
++
++ if (sc == NULL) {
++ ngx_quic_close_stream(qs);
++ continue;
++ }
++
++ sc->read->error = 1;
++ sc->write->error = 1;
++
++ ngx_quic_set_event(sc->read);
++ ngx_quic_set_event(sc->write);
++
++ sc->close = 1;
++ sc->read->handler(sc->read);
++ }
++
++ if (tree->root == tree->sentinel) {
++ return NGX_OK;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic connection has active streams");
++
++ 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;
++
++ if (qs->connection) {
++ qs->connection->write->error = 1;
++ }
++
++ 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");
++
++ if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) {
++ c->write->active = 1;
++ c->write->ready = 1;
++ }
++
++ c->read->active = 1;
++
++ 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;
++ }
++
++ return ngx_quic_do_init_streams(c);
++}
++
++
++static void
++ngx_quic_init_streams_handler(ngx_connection_t *c)
++{
++ if (ngx_quic_do_init_streams(c) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_do_init_streams(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);
++
++ if (qc->conf->init) {
++ if (qc->conf->init(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ 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;
++
++ return NGX_OK;
++}
++
++
++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_uint_t reusable;
++ 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;
++
++ reusable = c->reusable;
++ ngx_reusable_connection(c, 0);
++
++ sc = ngx_get_connection(c->fd, log);
++ if (sc == NULL) {
++ ngx_destroy_pool(pool);
++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
++ ngx_reusable_connection(c, reusable);
++ 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->start_time = c->start_time;
++ 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) {
++ 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);
++ ngx_reusable_connection(c, reusable);
++ return NULL;
++ }
++
++ cln->handler = ngx_quic_stream_cleanup_handler;
++ cln->data = sc;
++
++ ngx_rbtree_insert(&qc->streams.tree, &qs->node);
++
++ return qs;
++}
++
++
++void
++ngx_quic_cancelable_stream(ngx_connection_t *c)
++{
++ 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);
++
++ if (!qs->cancelable) {
++ qs->cancelable = 1;
++
++ if (ngx_quic_can_shutdown(pc) == NGX_OK) {
++ ngx_reusable_connection(pc, 1);
++
++ if (qc->shutdown) {
++ ngx_quic_shutdown_quic(pc);
++ }
++ }
++ }
++}
++
++
++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;
++ 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 - qs->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, qs->sent);
++ if (in == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ n = qs->send.size - n;
++ c->sent += n;
++ qs->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) {
++ return NGX_OK;
++ }
++
++ if (qs->send_state != NGX_QUIC_STREAM_SEND_DATA_RECVD
++ && qs->send_state != NGX_QUIC_STREAM_SEND_RESET_RECVD)
++ {
++ 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(&qc->close, &ngx_posted_events);
++ return NGX_OK;
++ }
++
++ if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) {
++ ngx_reusable_connection(pc, 1);
++ }
++
++ if (qc->shutdown) {
++ ngx_quic_shutdown_quic(pc);
++ 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);
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_can_shutdown(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);
++
++ 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_DECLINED;
++ }
++ }
++ }
++
++ 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_event_t *rev;
++ 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);
++ }
++
++ rev = qs->connection->read;
++ rev->error = 1;
++
++ ngx_quic_set_event(rev);
++
++ 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 acked;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ switch (f->type) {
++
++ case NGX_QUIC_FT_RESET_STREAM:
++
++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.reset_stream.id);
++ if (qs == NULL) {
++ return;
++ }
++
++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_RECVD;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL ack reset final_size:%uL",
++ qs->id, f->u.reset_stream.final_size);
++
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++
++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
++ if (qs == NULL) {
++ return;
++ }
++
++ acked = qs->acked;
++ qs->acked += f->u.stream.length;
++
++ if (f->u.stream.fin) {
++ qs->fin_acked = 1;
++ }
++
++ if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_SENT
++ && qs->acked == qs->sent && qs->fin_acked)
++ {
++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD;
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL ack len:%uL fin:%d unacked:%uL",
++ qs->id, f->u.stream.length, f->u.stream.fin,
++ qs->sent - qs->acked);
++
++ if (qs->connection
++ && qs->sent - acked == qc->conf->stream_buffer_size
++ && f->u.stream.length > 0)
++ {
++ ngx_quic_set_event(qs->connection->write);
++ }
++
++ break;
++
++ default:
++ return;
++ }
++
++ if (qs->connection == NULL) {
++ ngx_quic_close_stream(qs);
++ }
++}
++
++
++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);
++ }
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_streams.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_streams.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_tokens.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_tokens.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,289 @@
++
++/*
++ * 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];
++
++#if NGX_SUPPRESS_WARN
++ ngx_str_null(&odcid);
++#endif
++
++ /* 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;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_tokens.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_tokens.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_transport.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_transport.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,2199 @@
++
++/*
++ * 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, ngx_chain_t *data);
++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:
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.largest);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.delay);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.range_count);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.first_range);
++ if (p == NULL) {
++ 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 == NULL) {
++ goto error;
++ }
++
++ 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) {
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.ect0);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.ect1);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.ce);
++ if (p == NULL) {
++ 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:
++
++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size);
++ if (p == NULL) {
++ 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, f->data);
++
++ 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,
++ 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_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);
++
++ 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_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);
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_transport.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_transport.h Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,397 @@
++
++/*
++ * 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;
++} 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_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_udp.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_udp.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++}
+diff -r ac779115ed6e src/http/modules/ngx_http_ssl_module.c
+--- a/src/http/modules/ngx_http_ssl_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/modules/ngx_http_ssl_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -9,6 +9,10 @@
+ #include <ngx_core.h>
+ #include <ngx_http.h>
+
++#if (NGX_QUIC_OPENSSL_COMPAT)
++#include <ngx_event_quic_openssl_compat.h>
++#endif
++
+
+ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
+ ngx_pool_t *pool, ngx_str_t *s);
+@@ -52,6 +56,10 @@ static char *ngx_http_ssl_conf_command_c
+ void *data);
+
+ static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
++#if (NGX_QUIC_OPENSSL_COMPAT)
++static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf,
++ ngx_http_conf_addr_t *addr);
++#endif
+
+
+ static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = {
+@@ -419,16 +427,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
+ 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_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,14 +452,41 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
+ }
+ #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->quic) {
++
++ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
++
++ if (h3scf->enable && h3scf->enable_hq) {
++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO
++ NGX_HTTP_V3_HQ_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO)
++ - 1;
++
++ } else if (h3scf->enable_hq) {
++ srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1;
++
++ } else if (h3scf->enable || hc->addr_conf->http3) {
++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1;
++
++ } else {
++ return SSL_TLSEXT_ERR_ALERT_FATAL;
++ }
++
++ } else
++#endif
+ {
+ srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS;
+ srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1;
+@@ -1241,6 +1279,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;
+@@ -1290,22 +1329,44 @@ 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.quic) {
+ continue;
+ }
+
++ if (addr[a].opt.quic) {
++ name = "quic";
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++ if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) {
++ return NGX_ERROR;
++ }
++#endif
++
++ } 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.quic && !(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;
+ }
+
+@@ -1326,8 +1387,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;
+ }
+ }
+@@ -1335,3 +1396,31 @@ ngx_http_ssl_init(ngx_conf_t *cf)
+
+ return NGX_OK;
+ }
++
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++
++static ngx_int_t
++ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
++{
++ ngx_uint_t s;
++ ngx_http_ssl_srv_conf_t *sscf;
++ ngx_http_core_srv_conf_t **cscfp, *cscf;
++
++ cscfp = addr->servers.elts;
++ for (s = 0; s < addr->servers.nelts; s++) {
++
++ cscf = cscfp[s];
++ sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
++
++ if (sscf->certificates || sscf->reject_handshake) {
++ if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++ }
++
++ return NGX_OK;
++}
++
++#endif
+diff -r ac779115ed6e src/http/ngx_http.c
+--- a/src/http/ngx_http.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http.c Fri Mar 31 00:04:13 2023 -0400
+@@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_
+ 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_
+ }
+
+ port->family = sa->sa_family;
++ port->type = lsopt->type;
+ port->port = p;
+ port->addrs.elts = NULL;
+
+@@ -1237,6 +1241,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
+ #if (NGX_HTTP_V2)
+ ngx_uint_t http2;
+ #endif
++#if (NGX_HTTP_V3)
++ ngx_uint_t http3;
++ ngx_uint_t quic;
++#endif
+
+ /*
+ * we cannot compare whole sockaddr struct's as kernel
+@@ -1278,6 +1286,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
+ protocols |= lsopt->http2 << 2;
+ protocols_prev |= addr[i].opt.http2 << 2;
+ #endif
++#if (NGX_HTTP_V3)
++ http3 = lsopt->http3 || addr[i].opt.http3;
++ quic = lsopt->quic || addr[i].opt.quic;
++#endif
+
+ if (lsopt->set) {
+
+@@ -1365,6 +1377,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
+ #if (NGX_HTTP_V2)
+ addr[i].opt.http2 = http2;
+ #endif
++#if (NGX_HTTP_V3)
++ addr[i].opt.http3 = http3;
++ addr[i].opt.quic = quic;
++#endif
+
+ return NGX_OK;
+ }
+@@ -1831,6 +1847,7 @@ ngx_http_add_listening(ngx_conf_t *cf, n
+ }
+ #endif
+
++ ls->type = addr->opt.type;
+ ls->backlog = addr->opt.backlog;
+ ls->rcvbuf = addr->opt.rcvbuf;
+ ls->sndbuf = addr->opt.sndbuf;
+@@ -1866,6 +1883,19 @@ ngx_http_add_listening(ngx_conf_t *cf, n
+ ls->reuseport = addr->opt.reuseport;
+ #endif
+
++ ls->wildcard = addr->opt.wildcard;
++
++#if (NGX_HTTP_V3)
++
++ ls->quic = addr->opt.quic;
++
++ if (ls->quic) {
++ ngx_rbtree_init(&ls->rbtree, &ls->sentinel,
++ ngx_quic_rbtree_insert_value);
++ }
++
++#endif
++
+ return ls;
+ }
+
+@@ -1898,6 +1928,10 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h
+ #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;
++ addrs[i].conf.quic = addr[i].opt.quic;
++#endif
+ addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+ if (addr[i].hash.buckets == NULL
+@@ -1963,6 +1997,10 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_
+ #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;
++ addrs6[i].conf.quic = addr[i].opt.quic;
++#endif
+ addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+ if (addr[i].hash.buckets == NULL
+diff -r ac779115ed6e src/http/ngx_http.h
+--- a/src/http/ngx_http.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http.h Fri Mar 31 00:04:13 2023 -0400
+@@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ng
+ 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_p
+ #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
+ 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_r
+ #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,
+diff -r ac779115ed6e src/http/ngx_http_core_module.c
+--- a/src/http/ngx_http_core_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_core_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -3005,6 +3005,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx
+ lsopt.socklen = sizeof(struct sockaddr_in);
+
+ lsopt.backlog = NGX_LISTEN_BACKLOG;
++ lsopt.type = SOCK_STREAM;
+ lsopt.rcvbuf = -1;
+ lsopt.sndbuf = -1;
+ #if (NGX_HAVE_SETFIB)
+@@ -3986,6 +3987,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
+ 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)
+@@ -4184,6 +4186,36 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
+ #endif
+ }
+
++ if (ngx_strcmp(value[n].data, "http3") == 0) {
++#if (NGX_HTTP_V3)
++ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
++ "the \"http3\" parameter is deprecated, "
++ "use \"quic\" parameter instead");
++ lsopt.quic = 1;
++ 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_strcmp(value[n].data, "quic") == 0) {
++#if (NGX_HTTP_V3)
++ lsopt.quic = 1;
++ lsopt.type = SOCK_DGRAM;
++ continue;
++#else
++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
++ "the \"quic\" 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) {
+@@ -4285,6 +4317,28 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
+ return NGX_CONF_ERROR;
+ }
+
++#if (NGX_HTTP_V3)
++
++ if (lsopt.quic) {
++#if (NGX_HTTP_SSL)
++ if (lsopt.ssl) {
++ return "\"ssl\" parameter is incompatible with \"quic\"";
++ }
++#endif
++
++#if (NGX_HTTP_V2)
++ if (lsopt.http2) {
++ return "\"http2\" parameter is incompatible with \"quic\"";
++ }
++#endif
++
++ if (lsopt.proxy_protocol) {
++ return "\"proxy_protocol\" parameter is incompatible with \"quic\"";
++ }
++ }
++
++#endif
++
+ for (n = 0; n < u.naddrs; n++) {
+
+ for (i = 0; i < n; i++) {
+diff -r ac779115ed6e src/http/ngx_http_core_module.h
+--- a/src/http/ngx_http_core_module.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_core_module.h Fri Mar 31 00:04:13 2023 -0400
+@@ -75,6 +75,8 @@ typedef struct {
+ unsigned wildcard:1;
+ unsigned ssl:1;
+ unsigned http2:1;
++ unsigned http3:1;
++ unsigned quic:1;
+ #if (NGX_HAVE_INET6)
+ unsigned ipv6only:1;
+ #endif
+@@ -86,6 +88,7 @@ typedef struct {
+ int backlog;
+ int rcvbuf;
+ int sndbuf;
++ int type;
+ #if (NGX_HAVE_SETFIB)
+ int setfib;
+ #endif
+@@ -237,6 +240,8 @@ struct ngx_http_addr_conf_s {
+
+ unsigned ssl:1;
+ unsigned http2:1;
++ unsigned http3:1;
++ unsigned quic:1;
+ unsigned proxy_protocol:1;
+ };
+
+@@ -266,6 +271,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;
+diff -r ac779115ed6e src/http/ngx_http_request.c
+--- a/src/http/ngx_http_request.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_request.c Fri Mar 31 00:04:13 2023 -0400
+@@ -29,10 +29,6 @@ static ngx_int_t ngx_http_process_connec
+ 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(n
+ 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_
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (hc->addr_conf->quic) {
++ ngx_http_v3_init_stream(c);
++ return;
++ }
++#endif
++
+ #if (NGX_HTTP_SSL)
+ {
+ ngx_http_ssl_srv_conf_t *sscf;
+@@ -950,6 +952,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *
+ #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
+ }
+
+ done:
+@@ -2095,7 +2105,7 @@ ngx_http_process_request(ngx_http_reques
+ }
+
+
+-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,
+ }
+
+
+-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_re
+ }
+ #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) {
+@@ -2925,6 +2942,20 @@ ngx_http_test_reading(ngx_http_request_t
+
+ #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) {
+@@ -3590,7 +3621,7 @@ ngx_http_post_action(ngx_http_request_t
+ }
+
+
+-static void
++void
+ ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
+ {
+ ngx_connection_t *c;
+@@ -3677,7 +3708,12 @@ ngx_http_free_request(ngx_http_request_t
+
+ 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) {
+@@ -3750,6 +3786,12 @@ ngx_http_close_connection(ngx_connection
+
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (c->quic) {
++ ngx_http_v3_reset_stream(c);
++ }
++#endif
++
+ #if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+ #endif
+diff -r ac779115ed6e src/http/ngx_http_request.h
+--- a/src/http/ngx_http_request.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_request.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
+diff -r ac779115ed6e src/http/ngx_http_request_body.c
+--- a/src/http/ngx_http_request_body.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_request_body.c Fri Mar 31 00:04:13 2023 -0400
+@@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_ht
+ }
+ #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(ng
+ }
+ #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_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;
+ }
+@@ -921,6 +946,9 @@ ngx_http_test_expect(ngx_http_request_t
+ #if (NGX_HTTP_V2)
+ || r->stream != NULL
+ #endif
++#if (NGX_HTTP_V3)
++ || r->connection->quic != NULL
++#endif
+ )
+ {
+ return NGX_OK;
+diff -r ac779115ed6e src/http/ngx_http_upstream.c
+--- a/src/http/ngx_http_upstream.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_upstream.c Fri Mar 31 00:04:13 2023 -0400
+@@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_
+ }
+ #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_connectio
+ }
+ #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) {
+diff -r ac779115ed6e src/http/ngx_http_write_filter_module.c
+--- a/src/http/ngx_http_write_filter_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_write_filter_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -240,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t
+ 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
+
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
++ if (last) {
++ r->response_sent = 1;
++ }
++
+ if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
+ return NGX_AGAIN;
+ }
+diff -r ac779115ed6e src/http/v3/ngx_http_v3.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,116 @@
++
++/*
++ * 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_pool_cleanup_t *cln;
++ ngx_http_connection_t *hc;
++ ngx_http_v3_session_t *h3c;
++
++ hc = c->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
++
++ h3c = ngx_pcalloc(c->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 = c->log;
++ h3c->keepalive.data = c;
++ h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
++
++ h3c->table.send_insert_count.log = c->log;
++ h3c->table.send_insert_count.data = c;
++ h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler;
++
++ cln = ngx_pool_cleanup_add(c->pool, 0);
++ if (cln == NULL) {
++ goto failed;
++ }
++
++ cln->handler = ngx_http_v3_cleanup_session;
++ cln->data = h3c;
++
++ hc->v3_session = h3c;
++
++ return NGX_OK;
++
++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;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3.h Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,170 @@
++
++/*
++ * 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_HQ_PROTO "hq-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 {
++ ngx_flag_t enable;
++ ngx_flag_t enable_hq;
++ size_t max_table_capacity;
++ ngx_uint_t max_blocked_streams;
++ ngx_uint_t max_concurrent_pushes;
++ ngx_uint_t max_concurrent_streams;
++ 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;
++ uint64_t next_request_id;
++
++ off_t total_bytes;
++ off_t payload_bytes;
++
++ unsigned goaway:1;
++ unsigned hq:1;
++
++ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
++};
++
++
++void ngx_http_v3_init_stream(ngx_connection_t *c);
++void ngx_http_v3_reset_stream(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_init(ngx_connection_t *c);
++void ngx_http_v3_shutdown(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_ */
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_encode.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_encode.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_encode.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_encode.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_filter_module.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_filter_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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_VER) - 1;
++
++ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
++ n = sizeof(NGINX_VER_BUILD) - 1;
++
++ } else {
++ n = sizeof("nginx") - 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_VER;
++ n = sizeof(NGINX_VER) - 1;
++
++ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
++ p = (u_char *) NGINX_VER_BUILD;
++ n = sizeof(NGINX_VER_BUILD) - 1;
++
++ } else {
++ p = (u_char *) "nginx";
++ n = sizeof("nginx") - 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;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_module.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,554 @@
++
++/*
++ * 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"),
++ 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, enable),
++ NULL },
++
++ { 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, enable_hq),
++ NULL },
++
++ { 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 },
++
++ { 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)
++{
++ ngx_http_v3_session_t *h3c;
++
++ if (r->connection->quic) {
++ h3c = ngx_http_v3_get_session(r->connection);
++
++ if (h3c->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;
++ }
++
++ 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->enable = NGX_CONF_UNSET;
++ h3scf->enable_hq = NGX_CONF_UNSET;
++ 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;
++
++ 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;
++
++ h3scf->quic.init = ngx_http_v3_init;
++ h3scf->quic.shutdown = ngx_http_v3_shutdown;
++
++ 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_value(conf->enable, prev->enable, 1);
++
++ ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0);
++
++ 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;
++
++ 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;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_parse.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_parse.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++ }
++ }
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_parse.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_parse.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_request.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_request.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,1718 @@
++
++/*
++ * 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_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_connection(void *data);
++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_stream(ngx_connection_t *c)
++{
++ ngx_http_v3_session_t *h3c;
++ ngx_http_connection_t *hc, *phc;
++ ngx_http_v3_srv_conf_t *h3scf;
++ ngx_http_core_loc_conf_t *clcf;
++ ngx_http_core_srv_conf_t *cscf;
++
++ hc = c->data;
++
++ hc->ssl = 1;
++
++ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
++ cscf = ngx_http_get_module_srv_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) {
++ if (ngx_http_v3_init_session(c) != NGX_OK) {
++ ngx_http_close_connection(c);
++ return;
++ }
++
++ h3c = hc->v3_session;
++ ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout);
++
++ 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;
++#if (NGX_PCRE)
++ hc->ssl_servername_regex = phc->ssl_servername_regex;
++#endif
++ hc->conf_ctx = phc->conf_ctx;
++
++ ngx_set_connection_log(c, clcf->error_log);
++ }
++
++ if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++ ngx_http_v3_init_uni_stream(c);
++
++ } else {
++ ngx_http_v3_init_request_stream(c);
++ }
++}
++
++
++ngx_int_t
++ngx_http_v3_init(ngx_connection_t *c)
++{
++ unsigned int len;
++ const unsigned char *data;
++ ngx_http_v3_session_t *h3c;
++ ngx_http_v3_srv_conf_t *h3scf;
++ ngx_http_core_loc_conf_t *clcf;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init");
++
++ h3c = ngx_http_v3_get_session(c);
++ clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module);
++ ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout);
++
++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
++
++ if (h3scf->enable_hq) {
++ if (!h3scf->enable) {
++ h3c->hq = 1;
++ return NGX_OK;
++ }
++
++ SSL_get0_alpn_selected(c->ssl->connection, &data, &len);
++
++ if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1
++ && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0)
++ {
++ h3c->hq = 1;
++ return NGX_OK;
++ }
++ }
++
++ return ngx_http_v3_send_settings(c);
++}
++
++
++void
++ngx_http_v3_shutdown(ngx_connection_t *c)
++{
++ ngx_http_v3_session_t *h3c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown");
++
++ h3c = ngx_http_v3_get_session(c);
++
++ if (h3c == NULL) {
++ ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
++ "connection shutdown");
++ return;
++ }
++
++ if (!h3c->goaway) {
++ h3c->goaway = 1;
++
++ if (!h3c->hq) {
++ (void) ngx_http_v3_send_goaway(c, h3c->next_request_id);
++ }
++
++ ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
++ "connection shutdown");
++ }
++}
++
++
++static void
++ngx_http_v3_init_request_stream(ngx_connection_t *c)
++{
++ uint64_t n;
++ ngx_event_t *rev;
++ ngx_pool_cleanup_t *cln;
++ 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;
++ }
++
++ h3c->next_request_id = c->quic->id + 0x04;
++
++ if (n + 1 == clcf->keepalive_requests
++ || ngx_current_msec - c->start_time > clcf->keepalive_time)
++ {
++ h3c->goaway = 1;
++
++ if (!h3c->hq) {
++ if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != 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");
++ }
++
++ cln = ngx_pool_cleanup_add(c->pool, 0);
++ if (cln == NULL) {
++ ngx_http_close_connection(c);
++ return;
++ }
++
++ cln->handler = ngx_http_v3_cleanup_connection;
++ cln->data = c;
++
++ h3c->nrequests++;
++
++ if (h3c->keepalive.timer_set) {
++ ngx_del_timer(&h3c->keepalive);
++ }
++
++ rev = c->read;
++
++ if (!h3c->hq) {
++ 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_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;
++
++ rev->handler = ngx_http_v3_process_request;
++ ngx_http_v3_process_request(rev);
++}
++
++
++void
++ngx_http_v3_reset_stream(ngx_connection_t *c)
++{
++ ngx_http_v3_session_t *h3c;
++ ngx_http_v3_srv_conf_t *h3scf;
++
++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
++
++ h3c = ngx_http_v3_get_session(c);
++
++ if (h3scf->max_table_capacity > 0 && !c->read->eof && !h3c->hq
++ && (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_connection(void *data)
++{
++ ngx_connection_t *c = data;
++
++ ngx_http_v3_session_t *h3c;
++ ngx_http_core_loc_conf_t *clcf;
++
++ 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_cleanup_request(void *data)
++{
++ ngx_http_request_t *r = data;
++
++ if (!r->response_sent) {
++ r->connection->error = 1;
++ }
++}
++
++
++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_finalize_request(r, NGX_HTTP_BAD_REQUEST);
++ break;
++ }
++
++ 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 (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;
++ ngx_http_v3_session_t *h3c;
++ ngx_http_v3_srv_conf_t *h3scf;
++
++ c = r->connection;
++
++ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ h3c = ngx_http_v3_get_session(c);
++ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
++
++ if (!r->http_connection->addr_conf->http3) {
++ if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "client attempted to request the server name "
++ "for which the negotiated protocol is disabled");
++ ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST);
++ 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;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_table.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_table.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,715 @@
++
++/*
++ * 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 target);
++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);
++
++ h3c = ngx_http_v3_get_session(c);
++ dt = &h3c->table;
++
++ if (size > dt->capacity) {
++ ngx_log_error(NGX_LOG_ERR, c->log, 0,
++ "not enough dynamic table capacity");
++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
++ }
++
++ 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++;
++
++ if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ 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;
++ }
++
++ if (ngx_http_v3_evict(c, capacity) != NGX_OK) {
++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
++ }
++
++ dt = &h3c->table;
++ 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 target)
++{
++ size_t size;
++ 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;
++ 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;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_table.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_table.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_uni.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_uni.c Fri Mar 31 00:04:13 2023 -0400
+@@ -0,0 +1,781 @@
++
++/*
++ * 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_session_t *h3c;
++ ngx_http_v3_uni_stream_t *us;
++
++ h3c = ngx_http_v3_get_session(c);
++ if (h3c->hq) {
++ ngx_http_v3_finalize_connection(c,
++ NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
++ "uni stream in hq mode");
++ c->data = NULL;
++ ngx_http_v3_close_uni_stream(c);
++ return;
++ }
++
++ 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;
++ }
++
++ ngx_quic_cancelable_stream(c);
++
++ 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");
++
++ if (c->close) {
++ ngx_http_v3_close_uni_stream(c);
++ return;
++ }
++
++ 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 (c->close) {
++ ngx_http_v3_close_uni_stream(c);
++ return;
++ }
++
++ 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;
++ }
++
++ ngx_quic_cancelable_stream(sc);
++
++ 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;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3_uni.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_uni.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/os/unix/ngx_socket.h
+--- a/src/os/unix/ngx_socket.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/os/unix/ngx_socket.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
+
+diff -r ac779115ed6e src/os/win32/ngx_socket.h
+--- a/src/os/win32/ngx_socket.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/os/win32/ngx_socket.h Fri Mar 31 00:04:13 2023 -0400
+@@ -14,6 +14,8 @@
+
+
+ #define NGX_WRITE_SHUTDOWN SD_SEND
++#define NGX_READ_SHUTDOWN SD_RECEIVE
++#define NGX_RDWR_SHUTDOWN SD_BOTH
+
+
+ typedef SOCKET ngx_socket_t;
+diff -r ac779115ed6e src/stream/ngx_stream.c
+--- a/src/stream/ngx_stream.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/stream/ngx_stream.c Fri Mar 31 00:04:13 2023 -0400
+@@ -518,6 +518,24 @@ ngx_stream_optimize_servers(ngx_conf_t *
+ 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;
+@@ -576,6 +594,9 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx
+ #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;
+ }
+@@ -611,6 +632,9 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ng
+ #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;
+ }
+diff -r ac779115ed6e src/stream/ngx_stream.h
+--- a/src/stream/ngx_stream.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/stream/ngx_stream.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
+
+diff -r ac779115ed6e src/stream/ngx_stream_core_module.c
+--- a/src/stream/ngx_stream_core_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/stream/ngx_stream_core_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -760,6 +760,29 @@ ngx_stream_core_listen(ngx_conf_t *cf, n
+ #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, n
+ }
+ #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\"";
+ }
+diff -r ac779115ed6e src/stream/ngx_stream_handler.c
+--- a/src/stream/ngx_stream_handler.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/stream/ngx_stream_handler.c Fri Mar 31 00:04:13 2023 -0400
+@@ -129,6 +129,10 @@ ngx_stream_init_connection(ngx_connectio
+ 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_connectio
+ 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;
+
+diff -r ac779115ed6e src/stream/ngx_stream_proxy_module.c
+--- a/src/stream/ngx_stream_proxy_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/stream/ngx_stream_proxy_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -1772,6 +1772,21 @@ ngx_stream_proxy_process(ngx_stream_sess
+ 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");
+diff -r ac779115ed6e src/stream/ngx_stream_quic_module.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/stream/ngx_stream_quic_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -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;
++}
+diff -r ac779115ed6e src/stream/ngx_stream_quic_module.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/stream/ngx_stream_quic_module.h Fri Mar 31 00:04:13 2023 -0400
+@@ -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_ */
+diff -r ac779115ed6e src/stream/ngx_stream_ssl_module.c
+--- a/src/stream/ngx_stream_ssl_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/stream/ngx_stream_ssl_module.c Fri Mar 31 00:04:13 2023 -0400
+@@ -9,6 +9,10 @@
+ #include <ngx_core.h>
+ #include <ngx_stream.h>
+
++#if (NGX_QUIC_OPENSSL_COMPAT)
++#include <ngx_event_quic_openssl_compat.h>
++#endif
++
+
+ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
+ ngx_pool_t *pool, ngx_str_t *s);
+@@ -1195,7 +1199,10 @@ ngx_stream_ssl_conf_command_check(ngx_co
+ 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);
+@@ -1207,5 +1214,29 @@ 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 (NGX_QUIC_OPENSSL_COMPAT)
++ if (ngx_quic_compat_init(cf, scf->ssl.ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++#endif
++
++ 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;
+ }
diff --git a/www/nginx/files/extra-patch-masterzen-nginx-upload-progress-module b/www/nginx/files/extra-patch-masterzen-nginx-upload-progress-module
deleted file mode 100644
index 63f85c699a9b..000000000000
--- a/www/nginx/files/extra-patch-masterzen-nginx-upload-progress-module
+++ /dev/null
@@ -1,9 +0,0 @@
---- work/nginx-upload-progress-module-0.9.2/config.orig 2018-01-27 09:47:26.335521000 +0100
-+++ work/nginx-upload-progress-module-0.9.2/config 2018-01-27 09:47:53.763756000 +0100
-@@ -1,5 +1,5 @@
- ngx_addon_name=ngx_http_uploadprogress_module
--if test -n "$ngx_module_link"; then
-+if [ $ngx_module_link = DYNAMIC ] ; then
- ngx_module_type=FILTER
- ngx_module_name=ngx_http_uploadprogress_module
- ngx_module_srcs="$ngx_addon_dir/ngx_http_uploadprogress_module.c"
diff --git a/www/nginx/files/extra-patch-naxsi_runtime.c b/www/nginx/files/extra-patch-naxsi_runtime.c
new file mode 100644
index 000000000000..c08dd1f92540
--- /dev/null
+++ b/www/nginx/files/extra-patch-naxsi_runtime.c
@@ -0,0 +1,23 @@
+--- ../naxsi-29793dc/naxsi_src/naxsi_runtime.c.orig 2022-07-10 18:11:39.685243000 -0400
++++ ../naxsi-29793dc/naxsi_src/naxsi_runtime.c 2022-07-10 18:14:53.935554000 -0400
+@@ -9,6 +9,11 @@
+ #include "naxsi_macros.h"
+ #include "naxsi_net.h"
+
++#if (NGX_PCRE2)
++#include <pcre2.h>
++#else
++#include <pcre.h>
++#endif
+ /* used to store locations during the configuration time.
+ then, accessed by the hashtable building feature during "init" time. */
+
+@@ -181,7 +186,7 @@
+ unsigned char*
+ ngx_utf8_check(ngx_str_t* str);
+
+-#if defined nginx_version && (nginx_version >= 1021005)
++#if (NGX_PCRE2)
+ /*
+ * variables to use pcre2
+ */
diff --git a/www/nginx/files/extra-patch-nginx-ct-LibreSSL b/www/nginx/files/extra-patch-nginx-ct-LibreSSL
index 4dd87b8a8fae..9aa89a463a9d 100644
--- a/www/nginx/files/extra-patch-nginx-ct-LibreSSL
+++ b/www/nginx/files/extra-patch-nginx-ct-LibreSSL
@@ -1,20 +1,20 @@
---- ../ nginx-ct-1.3.2/ngx_ssl_ct_module.c.orig 2016-11-30 22:58:29.000000000 +0100
-+++ ../nginx-ct-1.3.2/ngx_ssl_ct_module.c 2017-02-11 19:42:19.741572000 +0100
-@@ -170,7 +170,7 @@
+--- ../nginx-ct-93e9884/ngx_ssl_ct_module.c.orig 2017-07-23 08:03:35.000000000 -0400
++++ ../nginx-ct-93e9884/ngx_ssl_ct_module.c 2018-04-24 16:58:27.698435000 -0400
+@@ -158,7 +158,7 @@
#endif
}
-#ifndef OPENSSL_IS_BORINGSSL
+#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER)
/* add OpenSSL TLS extension */
- if (SSL_CTX_add_server_custom_ext(ssl_ctx, NGX_SSL_CT_EXT,
- &ngx_ssl_ct_ext_cb, NULL, NULL, NULL, NULL) == 0)
-@@ -184,7 +184,7 @@
+ # if OPENSSL_VERSION_NUMBER >= 0x10101000L
+ int context = SSL_EXT_CLIENT_HELLO
+@@ -183,7 +183,7 @@
return NGX_CONF_OK;
}
-#ifndef OPENSSL_IS_BORINGSSL
+#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER)
- int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, const unsigned char **out,
- size_t *outlen, int *al, void *add_arg)
- {
+ # if OPENSSL_VERSION_NUMBER >= 0x10101000L
+ int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, unsigned int context,
+ const unsigned char **out, size_t *outlen, X509 *x, size_t chainidx,
diff --git a/www/nginx/files/extra-patch-nginx-opentracing-opentracing-config b/www/nginx/files/extra-patch-nginx-opentracing-opentracing-config
index 753be4e4e5ed..1c40dd108b95 100644
--- a/www/nginx/files/extra-patch-nginx-opentracing-opentracing-config
+++ b/www/nginx/files/extra-patch-nginx-opentracing-opentracing-config
@@ -1,5 +1,5 @@
---- ../nginx-opentracing-2d81c29/opentracing/config.orig 2020-05-07 18:34:12.853828000 -0400
-+++ ../nginx-opentracing-2d81c29/opentracing/config 2020-05-07 18:34:26.521814000 -0400
+--- ../nginx-opentracing-0.24.0/opentracing/config.orig 2020-05-07 18:34:12.853828000 -0400
++++ ../nginx-opentracing-0.24.0/opentracing/config 2020-05-07 18:34:26.521814000 -0400
@@ -34,4 +34,4 @@
. auto/module
diff --git a/www/nginx/files/extra-patch-nginx-vod-module-config b/www/nginx/files/extra-patch-nginx-vod-module-config
deleted file mode 100644
index 1f32296b0e9c..000000000000
--- a/www/nginx/files/extra-patch-nginx-vod-module-config
+++ /dev/null
@@ -1,17 +0,0 @@
---- ../nginx-vod-module-1.27/config.orig 2020-07-09 20:33:33 UTC
-+++ ../nginx-vod-module-1.27/config
-@@ -227,8 +227,12 @@ ngx_feature_name="NGX_HAVE_LIBXML2"
- ngx_feature_run=no
- ngx_feature_incs="#include <libxml/parser.h>
- #include <libxml/tree.h>"
--ngx_feature_path="/usr/include/libxml2"
--ngx_feature_libs="-lxml2"
-+ngx_feature_path="%%PREFIX%%/include/libxml2 %%PREFIX%%/include"
-+if [ $NGX_RPATH = YES ]; then
-+ ngx_feature_libs="-R%%PREFIX%%/lib -L%%PREFIX%%/lib -lxml2 -lxslt"
-+else
-+ ngx_feature_libs="-L%%PREFIX%%/lib -lxml2 -lxslt"
-+fi
- ngx_feature_test="xmlReadMemory(NULL, 0, NULL, NULL, 0);"
- . auto/feature
-
diff --git a/www/nginx/files/extra-patch-ngx_dynamic_healthcheck-config b/www/nginx/files/extra-patch-ngx_dynamic_healthcheck-config
deleted file mode 100644
index 58f1bbee672a..000000000000
--- a/www/nginx/files/extra-patch-ngx_dynamic_healthcheck-config
+++ /dev/null
@@ -1,11 +0,0 @@
---- ../ngx_dynamic_healthcheck-61acf02/config.orig 2020-08-22 16:35:36.127913000 -0400
-+++ ../ngx_dynamic_healthcheck-61acf02/config 2020-08-22 16:39:36.187267000 -0400
-@@ -31,7 +31,7 @@
- match($0, /([^ ]*\/stream-lua-nginx-module\/src)/, arr)
- print arr[1]
- }')"
--CORE_INCS="$CORE_INCS $stream_lua_nginx_module_incs"
-+CORE_INCS="$CORE_INCS src/stream $stream_lua_nginx_module_incs"
-
-
- if test -n "$ngx_module_link"; then
diff --git a/www/nginx/files/extra-patch-ngx_http_auth_digest_module.c b/www/nginx/files/extra-patch-ngx_http_auth_digest_module.c
deleted file mode 100644
index 0b98a0e67fa4..000000000000
--- a/www/nginx/files/extra-patch-ngx_http_auth_digest_module.c
+++ /dev/null
@@ -1,30 +0,0 @@
---- ../nginx-http-auth-digest-bd1c86a/ngx_http_auth_digest_module.c.orig 2011-12-29 04:00:32.000000000 +0400
-+++ ../nginx-http-auth-digest-bd1c86a/ngx_http_auth_digest_module.c 2015-06-13 20:18:59.856347000 +0300
-@@ -403,11 +403,11 @@
- if (http_method.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
- p = ngx_cpymem(http_method.data, r->method_name.data, r->method_end - r->method_name.data+1);
-
-- ha2_key.len = http_method.len + r->uri.len + 1;
-+ ha2_key.len = http_method.len + r->unparsed_uri.len + 1;
- ha2_key.data = ngx_pcalloc(r->pool, ha2_key.len);
- if (ha2_key.data==NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;
- p = ngx_cpymem(ha2_key.data, http_method.data, http_method.len-1); *p++ = ':';
-- p = ngx_cpymem(p, r->uri.data, r->uri.len);
-+ p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len);
-
- HA2.len = 33;
- HA2.data = ngx_pcalloc(r->pool, HA2.len);
-@@ -487,11 +487,11 @@
- // recalculate the digest with a modified HA2 value (for rspauth) and emit the
- // Authentication-Info header
- ngx_memset(ha2_key.data, 0, ha2_key.len);
-- p = ngx_sprintf(ha2_key.data, ":%s", r->uri.data);
-+ p = ngx_sprintf(ha2_key.data, ":%s", r->unparsed_uri.data);
-
- ngx_memset(HA2.data, 0, HA2.len);
- ngx_md5_init(&md5);
-- ngx_md5_update(&md5, ha2_key.data, r->uri.len);
-+ ngx_md5_update(&md5, ha2_key.data, r->unparsed_uri.len);
- ngx_md5_final(hash, &md5);
- ngx_hex_dump(HA2.data, hash, 16);
-
diff --git a/www/nginx/files/extra-patch-ngx_http_auth_ldap_module.c b/www/nginx/files/extra-patch-ngx_http_auth_ldap_module.c
new file mode 100644
index 000000000000..d8bc2f7f65f3
--- /dev/null
+++ b/www/nginx/files/extra-patch-ngx_http_auth_ldap_module.c
@@ -0,0 +1,10 @@
+--- ../nginx-auth-ldap-83c059b/ngx_http_auth_ldap_module.c.orig 2022-08-21 17:04:57.754760000 +0300
++++ ../nginx-auth-ldap-83c059b/ngx_http_auth_ldap_module.c 2022-08-21 17:08:46.939318000 +0300
+@@ -1779,6 +1779,7 @@
+ }
+
+ r->headers_out.www_authenticate->hash = 1;
++ r->headers_out.www_authenticate->next = NULL;
+ r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1;
+ r->headers_out.www_authenticate->key.data = (u_char *) "WWW-Authenticate";
+ r->headers_out.www_authenticate->value = *realm;
diff --git a/www/nginx/files/extra-patch-ngx_http_auth_spnego_module.c b/www/nginx/files/extra-patch-ngx_http_auth_spnego_module.c
index b14fbe108fa2..40aea7e6e875 100644
--- a/www/nginx/files/extra-patch-ngx_http_auth_spnego_module.c
+++ b/www/nginx/files/extra-patch-ngx_http_auth_spnego_module.c
@@ -1,49 +1,52 @@
---- ../spnego-http-auth-nginx-module-c626163/ngx_http_auth_spnego_module.c.orig 2022-02-19 21:05:54.082252000 +0100
-+++ ../spnego-http-auth-nginx-module-c626163/ngx_http_auth_spnego_module.c 2022-02-19 21:12:17.316744000 +0100
-@@ -63,6 +63,11 @@
- #define spnego_log_error(fmt, args...) \
- ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, fmt, ##args)
-
-+#ifndef krb5_realm_length
-+#define krb5_realm_length(r) ((r).length)
-+#define krb5_realm_data(r) ((r).data)
-+#endif
-+
- /* Module handler */
- static ngx_int_t ngx_http_auth_spnego_handler(ngx_http_request_t *);
-
-@@ -1195,12 +1200,12 @@ static krb5_error_code ngx_http_auth_spnego_verify_ser
+--- ../spnego-http-auth-nginx-module-c626163/ngx_http_auth_spnego_module.c.orig
++++ ../spnego-http-auth-nginx-module-c626163/ngx_http_auth_spnego_module.c
+@@ -502,6 +502,7 @@ ngx_http_auth_spnego_headers_basic_only(ngx_http_request_t *r,
}
- size_t tgs_principal_name_size =
-- (ngx_strlen(KRB5_TGS_NAME) + (principal->realm.length * 2) + 2) + 1;
-+ (ngx_strlen(KRB5_TGS_NAME) + (krb5_realm_length(principal->realm) * 2) + 2) + 1;
- tgs_principal_name = (char *)ngx_pcalloc(r->pool, tgs_principal_name_size);
- ngx_snprintf((u_char *)tgs_principal_name, tgs_principal_name_size,
-- "%s/%*s@%*s", KRB5_TGS_NAME, principal->realm.length,
-- principal->realm.data, principal->realm.length,
-- principal->realm.data);
-+ "%s/%*s@%*s", KRB5_TGS_NAME, krb5_realm_length(principal->realm),
-+ krb5_realm_data(principal->realm), krb5_realm_length(principal->realm),
-+ krb5_realm_data(principal->realm));
+ r->headers_out.www_authenticate->hash = 1;
++ r->headers_out.www_authenticate->next = NULL;
+ r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1;
+ r->headers_out.www_authenticate->key.data = (u_char *)"WWW-Authenticate";
+ r->headers_out.www_authenticate->value.len = value.len;
+@@ -538,6 +539,7 @@ ngx_http_auth_spnego_headers(ngx_http_request_t *r,
+ }
- if ((kerr = krb5_parse_name(kcontext, tgs_principal_name,
- &match_creds.server))) {
-@@ -1341,13 +1346,13 @@ static ngx_int_t ngx_http_auth_spnego_obtain_server_cr
- krb5_get_init_creds_opt_set_forwardable(&gicopts, 1);
+ r->headers_out.www_authenticate->hash = 1;
++ r->headers_out.www_authenticate->next = NULL;
+ r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1;
+ r->headers_out.www_authenticate->key.data = (u_char *)"WWW-Authenticate";
+ r->headers_out.www_authenticate->value.len = value.len;
+@@ -559,6 +561,7 @@ ngx_http_auth_spnego_headers(ngx_http_request_t *r,
+ }
- size_t tgs_principal_name_size =
-- (ngx_strlen(KRB5_TGS_NAME) + (principal->realm.length * 2) + 2) + 1;
-+ (ngx_strlen(KRB5_TGS_NAME) + (krb5_realm_length(principal->realm) * 2) + 2) + 1;
- tgs_principal_name = (char *)ngx_pcalloc(r->pool, tgs_principal_name_size);
+ r->headers_out.www_authenticate->hash = 2;
++ r->headers_out.www_authenticate->next = NULL;
+ r->headers_out.www_authenticate->key.len =
+ sizeof("WWW-Authenticate") - 1;
+ r->headers_out.www_authenticate->key.data =
+@@ -758,6 +761,12 @@ ngx_http_auth_spnego_store_delegated_creds(ngx_http_request_t *r,
+ char *ccname = NULL;
+ char *escaped = NULL;
- ngx_snprintf((u_char *)tgs_principal_name, tgs_principal_name_size,
-- "%s/%*s@%*s", KRB5_TGS_NAME, principal->realm.length,
-- principal->realm.data, principal->realm.length,
-- principal->realm.data);
-+ "%s/%*s@%*s", KRB5_TGS_NAME, krb5_realm_length(principal->realm),
-+ krb5_realm_data(principal->realm), krb5_realm_length(principal->realm),
-+ krb5_realm_data(principal->realm));
++ if ((kerr = krb5_init_context(&kcontext))) {
++ spnego_log_error("Kerberos error: Cannot initialize kerberos context");
++ spnego_log_krb5_error(kcontext, kerr);
++ goto done;
++ }
++
+ if (!delegated_creds.data) {
+ spnego_log_error(
+ "ngx_http_auth_spnego_store_delegated_creds() NULL credentials");
+@@ -766,12 +775,6 @@ ngx_http_auth_spnego_store_delegated_creds(ngx_http_request_t *r,
+ goto done;
+ }
- kerr = krb5_get_init_creds_keytab(kcontext, &creds, principal, keytab, 0,
- tgs_principal_name, &gicopts);
+- if ((kerr = krb5_init_context(&kcontext))) {
+- spnego_log_error("Kerberos error: Cannot initialize kerberos context");
+- spnego_log_krb5_error(kcontext, kerr);
+- goto done;
+- }
+-
+ if ((kerr = krb5_parse_name(kcontext, (char *)principal_name->data,
+ &principal))) {
+ spnego_log_error("Kerberos error: Cannot parse principal %s",
diff --git a/www/nginx/files/extra-patch-ngx_http_clojure_mem.h b/www/nginx/files/extra-patch-ngx_http_clojure_mem.h
new file mode 100644
index 000000000000..8ccf7ea4ba2b
--- /dev/null
+++ b/www/nginx/files/extra-patch-ngx_http_clojure_mem.h
@@ -0,0 +1,11 @@
+--- ../nginx-clojure-4347955/src/c/ngx_http_clojure_mem.h.orig 2022-07-11 13:38:32.426478000 -0400
++++ ../nginx-clojure-4347955/src/c/ngx_http_clojure_mem.h 2022-07-11 13:38:45.346434000 -0400
+@@ -421,7 +421,7 @@
+ #define NGX_HTTP_CLOJURE_HEADERSI_PASSWD_IDX 90
+ #define NGX_HTTP_CLOJURE_HEADERSI_PASSWD_OFFSET offsetof(ngx_http_headers_in_t, passwd)
+ #define NGX_HTTP_CLOJURE_HEADERSI_COOKIE_IDX 91
+-#define NGX_HTTP_CLOJURE_HEADERSI_COOKIE_OFFSET offsetof(ngx_http_headers_in_t, cookies)
++#define NGX_HTTP_CLOJURE_HEADERSI_COOKIE_OFFSET offsetof(ngx_http_headers_in_t, cookie)
+ #define NGX_HTTP_CLOJURE_HEADERSI_SERVER_IDX 92
+ #define NGX_HTTP_CLOJURE_HEADERSI_SERVER_OFFSET offsetof(ngx_http_headers_in_t, server)
+ #define NGX_HTTP_CLOJURE_HEADERSI_CONTENT_LENGTH_N_IDX 93
diff --git a/www/nginx/files/extra-patch-ngx_http_redis_module.c b/www/nginx/files/extra-patch-ngx_http_redis_module.c
new file mode 100644
index 000000000000..3dacd39ee6c4
--- /dev/null
+++ b/www/nginx/files/extra-patch-ngx_http_redis_module.c
@@ -0,0 +1,34 @@
+--- ../ngx_http_redis-0.3.9/ngx_http_redis_module.c.orig 2022-07-10 22:10:19.031893000 -0400
++++ ../ngx_http_redis-0.3.9/ngx_http_redis_module.c 2022-07-10 22:09:41.271731000 -0400
+@@ -562,7 +562,7 @@
+ /* if defined gzip_flag... */
+ if (rlcf->gzip_flag) {
+ /* hash init */
+- h = ngx_list_push(&r->upstream->headers_in.headers);
++ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+@@ -571,19 +571,11 @@
+ * add Content-Encoding header for future gunzipping
+ * with ngx_http_gunzip_filter module
+ */
+- h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
+- ngx_hash(ngx_hash(ngx_hash(
+- ngx_hash(ngx_hash(ngx_hash(
+- ngx_hash(ngx_hash(ngx_hash(
+- ngx_hash(ngx_hash('c', 'o'), 'n'), 't'), 'e'),
+- 'n'), 't'), '-'), 'e'), 'n'), 'c'), 'o'),
+- 'd'), 'i'), 'n'), 'g');
++ h->hash = 1;
++ h->next = NULL;
+ ngx_str_set(&h->key, "Content-Encoding");
+ ngx_str_set(&h->value, "gzip");
+- h->lowcase_key = (u_char*) "content-encoding";
+-#if (NGX_HTTP_GZIP)
+- u->headers_in.content_encoding = h;
+-#endif
++ r->headers_out.content_encoding = h;
+ }
+
+ /* try to find end of string */
diff --git a/www/nginx/files/extra-patch-ngx_http_response-config b/www/nginx/files/extra-patch-ngx_http_response-config
deleted file mode 100644
index 3b39255595cd..000000000000
--- a/www/nginx/files/extra-patch-ngx_http_response-config
+++ /dev/null
@@ -1,11 +0,0 @@
---- ../ngx_http_response-0.3/config.orig 2020-04-27 11:41:14.626983000 -0400
-+++ ../ngx_http_response-0.3/config 2020-04-27 11:41:50.008905000 -0400
-@@ -1,3 +1,6 @@
- ngx_addon_name=ngx_http_response_module
--HTTP_MODULES="$HTTP_MODULES ngx_http_response_module"
--NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_response_module.c"
-+ngx_module_name="$ngx_addon_name"
-+ngx_module_type=HTTP
-+ngx_module_srcs="$ngx_addon_dir/ngx_http_response_module.c"
-+ngx_module_deps=""
-+. auto/module
diff --git a/www/nginx/files/extra-patch-ngx_http_uploadprogress_module.c b/www/nginx/files/extra-patch-ngx_http_uploadprogress_module.c
new file mode 100644
index 000000000000..5dfbdbfea794
--- /dev/null
+++ b/www/nginx/files/extra-patch-ngx_http_uploadprogress_module.c
@@ -0,0 +1,73 @@
+--- ../nginx-upload-progress-module-68b3ab3/ngx_http_uploadprogress_module.c.orig 2021-12-24 10:53:38.000000000 -0500
++++ ../nginx-upload-progress-module-68b3ab3/ngx_http_uploadprogress_module.c 2022-07-10 22:24:32.435330000 -0400
+@@ -559,12 +559,12 @@
+ ngx_chain_t out;
+ ngx_int_t rc, found=0, done=0, err_status=0;
+ off_t rest=0, length=0;
+- ngx_uint_t len, i;
++ ngx_uint_t len;
+ ngx_slab_pool_t *shpool;
+ ngx_http_uploadprogress_conf_t *upcf;
+ ngx_http_uploadprogress_ctx_t *ctx;
+ ngx_http_uploadprogress_node_t *up;
+- ngx_table_elt_t *expires, *cc, **ccp;
++ ngx_table_elt_t *expires, *cc;
+ ngx_http_uploadprogress_state_t state;
+ ngx_http_uploadprogress_template_t *t;
+
+@@ -637,6 +637,7 @@
+ }
+
+ r->headers_out.expires = expires;
++ expires->next = NULL;
+
+ expires->hash = 1;
+ expires->key.len = sizeof("Expires") - 1;
+@@ -646,37 +647,30 @@
+ len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
+ expires->value.len = len - 1;
+
+- ccp = r->headers_out.cache_control.elts;
+- if (ccp == NULL) {
++ cc = r->headers_out.cache_control;
+
+- if (ngx_array_init(&r->headers_out.cache_control, r->pool,
+- 1, sizeof(ngx_table_elt_t *))
+- != NGX_OK) {
+- return NGX_HTTP_INTERNAL_SERVER_ERROR;
+- }
++ if (cc == NULL) {
+
+- ccp = ngx_array_push(&r->headers_out.cache_control);
+- if (ccp == NULL) {
+- return NGX_HTTP_INTERNAL_SERVER_ERROR;
+- }
+-
+ cc = ngx_list_push(&r->headers_out.headers);
+ if (cc == NULL) {
++ expires->hash = 0;
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
++ r->headers_out.cache_control = cc;
++ cc->next = NULL;
++
+ cc->hash = 1;
+ cc->key.len = sizeof("Cache-Control") - 1;
+ cc->key.data = (u_char *) "Cache-Control";
+
+- *ccp = cc;
+-
+ } else {
+- for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
+- ccp[i]->hash = 0;
++ for (cc = cc->next; cc; cc = cc->next) {
++ cc->hash = 0;
+ }
+
+- cc = ccp[0];
++ cc = r->headers_out.cache_control;
++ cc->next = NULL;
+ }
+
+ expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
diff --git a/www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c b/www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c
deleted file mode 100644
index 75671036bde2..000000000000
--- a/www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c
+++ /dev/null
@@ -1,89 +0,0 @@
---- ../nginx_upstream_fair-20090923/ngx_http_upstream_fair_module.c.orig 2009-09-24 01:38:16.000000000 +0800
-+++ ../nginx_upstream_fair-20090923/ngx_http_upstream_fair_module.c 2016-03-24 13:56:02.990728000 +0800
-@@ -9,6 +9,10 @@
- #include <ngx_core.h>
- #include <ngx_http.h>
-
-+#if (NGX_HTTP_UPSTREAM_CHECK)
-+#include "ngx_http_upstream_check_module.h"
-+#endif
-+
- typedef struct {
- ngx_uint_t nreq;
- ngx_uint_t total_req;
-@@ -42,6 +46,10 @@
- ngx_uint_t max_fails;
- time_t fail_timeout;
-
-+#if (NGX_HTTP_UPSTREAM_CHECK)
-+ ngx_uint_t check_index;
-+#endif
-+
- time_t accessed;
- ngx_uint_t down:1;
-
-@@ -474,6 +482,15 @@
- peers->peer[n].fail_timeout = server[i].fail_timeout;
- peers->peer[n].down = server[i].down;
- peers->peer[n].weight = server[i].down ? 0 : server[i].weight;
-+#if (NGX_HTTP_UPSTREAM_CHECK)
-+ if (!server[i].down) {
-+ peers->peer[n].check_index =
-+ ngx_http_upstream_check_add_peer(cf, us, &server[i].addrs[j]);
-+ }
-+ else {
-+ peers->peer[n].check_index = (ngx_uint_t) NGX_ERROR;
-+ }
-+#endif
- n++;
- }
- }
-@@ -524,6 +541,15 @@
- backup->peer[n].max_fails = server[i].max_fails;
- backup->peer[n].fail_timeout = server[i].fail_timeout;
- backup->peer[n].down = server[i].down;
-+#if (NGX_HTTP_UPSTREAM_CHECK)
-+ if (!server[i].down) {
-+ backup->peer[n].check_index =
-+ ngx_http_upstream_check_add_peer(cf, us, &server[i].addrs[j]);
-+ }
-+ else {
-+ backup->peer[n].check_index = (ngx_uint_t) NGX_ERROR;
-+ }
-+#endif
- n++;
- }
- }
-@@ -580,6 +606,9 @@
- peers->peer[i].weight = 1;
- peers->peer[i].max_fails = 1;
- peers->peer[i].fail_timeout = 10;
-+#if (NGX_HTTP_UPSTREAM_CHECK)
-+ peers->peer[i].check_index = (ngx_uint_t) NGX_ERROR;
-+#endif
- }
-
- us->peer.data = peers;
-@@ -721,6 +750,12 @@
- peer = &fp->peers->peer[peer_id];
-
- if (!peer->down) {
-+#if (NGX_HTTP_UPSTREAM_CHECK)
-+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
-+ "[upstream_fair] get fair peer, check_index: %ui",
-+ peer->check_index);
-+ if (!ngx_http_upstream_check_peer_down(peer->check_index)) {
-+#endif
- if (peer->max_fails == 0 || peer->shared->fails < peer->max_fails) {
- return NGX_OK;
- }
-@@ -731,6 +766,9 @@
- peer->shared->fails = 0;
- return NGX_OK;
- }
-+#if (NGX_HTTP_UPSTREAM_CHECK)
-+ }
-+#endif
- }
-
- return NGX_BUSY;
diff --git a/www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c.n b/www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c.n
deleted file mode 100644
index 648f056b8eb6..000000000000
--- a/www/nginx/files/extra-patch-ngx_http_upstream_fair_module.c.n
+++ /dev/null
@@ -1,21 +0,0 @@
---- ../nginx_upstream_fair-20090923/ngx_http_upstream_fair_module.c.orig 2016-11-20 15:42:48.550372000 -0500
-+++ ../nginx_upstream_fair-20090923/ngx_http_upstream_fair_module.c 2016-11-20 15:45:24.507554000 -0500
-@@ -565,8 +565,7 @@
-
-
- /* an upstream implicitly defined by proxy_pass, etc. */
--
-- if (us->port == 0 && us->default_port == 0) {
-+ if (us->port == 0) {
- ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
- "no port in upstream \"%V\" in %s:%ui",
- &us->host, us->file_name, us->line);
-@@ -576,7 +575,7 @@
- ngx_memzero(&u, sizeof(ngx_url_t));
-
- u.host = us->host;
-- u.port = (in_port_t) (us->port ? us->port : us->default_port);
-+ u.port = (in_port_t) us->port;
-
- if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
- if (u.err) {
diff --git a/www/nginx/files/extra-patch-openresty-drizzle-nginx-module-config b/www/nginx/files/extra-patch-openresty-drizzle-nginx-module-config
index d9812e754321..294d22a346cf 100644
--- a/www/nginx/files/extra-patch-openresty-drizzle-nginx-module-config
+++ b/www/nginx/files/extra-patch-openresty-drizzle-nginx-module-config
@@ -1,6 +1,6 @@
---- ../drizzle-nginx-module-0.1.11/config.orig 2020-06-04 19:19:26.245657000 +0200
-+++ ../drizzle-nginx-module-0.1.11/config 2020-06-04 19:29:28.502730000 +0200
-@@ -34,12 +34,12 @@ else
+--- ../drizzle-nginx-module-3504fc6/config.orig 2020-01-22 18:04:58.000000000 -0500
++++ ../drizzle-nginx-module-3504fc6/config 2020-04-24 06:23:01.264872000 -0400
+@@ -34,12 +34,12 @@
if [ $ngx_found = no ]; then
# FreeBSD, OpenBSD
@@ -13,11 +13,11 @@
+ ngx_feature_libs="-R%%PREFIX%%/lib -L%%PREFIX%%/lib -ldrizzle"
else
- ngx_feature_libs="-L/usr/local/lib -ldrizzle"
-+ ngx_feature_libs="-L%%PREFIX/lib -ldrizzle"
++ ngx_feature_libs="-L%%PREFIX%%/lib -ldrizzle"
fi
. auto/feature
fi
-@@ -80,8 +80,19 @@ END
+@@ -80,8 +80,19 @@
fi
ngx_addon_name=ngx_http_drizzle_module
diff --git a/www/nginx/files/extra-patch-openresty-set-misc-nginx-module-config b/www/nginx/files/extra-patch-openresty-set-misc-nginx-module-config
deleted file mode 100644
index 4acc8aee7492..000000000000
--- a/www/nginx/files/extra-patch-openresty-set-misc-nginx-module-config
+++ /dev/null
@@ -1,13 +0,0 @@
---- ../set-misc-nginx-module-0.29/config.orig 2011-12-09 10:27:53.861265188 +0300
-+++ ../set-misc-nginx-module-0.29/config 2011-12-09 10:28:07.107259301 +0300
-@@ -1,10 +1,3 @@
--if echo $HTTP_MODULES | grep " ndk_http_module" > /dev/null; then
-- echo "found ngx_devel_kit for ngx_set_misc; looks good."
--else
-- echo "error: ngx_devel_kit is required to build ngx_set_misc; please put it before ngx_set_misc." 1>&2
-- exit 1
--fi
--
- ngx_addon_name=ngx_http_set_misc_module
- HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_set_misc_module"
- NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_set_base32.c $ngx_addon_dir/src/ngx_http_set_default_value.c $ngx_addon_dir/src/ngx_http_set_hashed_upstream.c $ngx_addon_dir/src/ngx_http_set_quote_sql.c $ngx_addon_dir/src/ngx_http_set_quote_json.c $ngx_addon_dir/src/ngx_http_set_unescape_uri.c $ngx_addon_dir/src/ngx_http_set_misc_module.c $ngx_addon_dir/src/ngx_http_set_escape_uri.c $ngx_addon_dir/src/ngx_http_set_hash.c $ngx_addon_dir/src/ngx_http_set_local_today.c $ngx_addon_dir/src/ngx_http_set_hex.c $ngx_addon_dir/src/ngx_http_set_base64.c $ngx_addon_dir/src/ngx_http_set_random.c"
diff --git a/www/nginx/files/extra-patch-passenger-build-nginx.rb b/www/nginx/files/extra-patch-passenger-build-nginx.rb
index dd5fd1e7f813..8225f4f10931 100644
--- a/www/nginx/files/extra-patch-passenger-build-nginx.rb
+++ b/www/nginx/files/extra-patch-passenger-build-nginx.rb
@@ -1,6 +1,6 @@
---- ../passenger-6.0.15/build/nginx.rb.orig 2018-05-20 08:43:19.389262000 +0200
-+++ ../passenger-6.0.15/build/nginx.rb 2018-05-20 09:11:27.500253000 +0200
-@@ -39,13 +39,12 @@ auto_generated_sources = %w(
+--- ../passenger-6.0.17/build/nginx.rb.orig 2013-10-26 18:00:00.000000000 -0400
++++ ../passenger-6.0.17/build/nginx.rb 2016-05-09 18:21:22.426777000 -0400
+@@ -33,13 +33,12 @@
desc "Build Nginx support files"
task :nginx => [
:nginx_without_native_support,
@@ -15,7 +15,7 @@
].compact
# Workaround for https://github.com/jimweirich/rake/issues/274
-@@ -53,7 +52,6 @@ task :_nginx => :nginx
+@@ -47,7 +46,6 @@
task :nginx_without_native_support => [
auto_generated_sources,
@@ -23,7 +23,7 @@
COMMON_LIBRARY.only(*NGINX_LIBS_SELECTOR).link_objects
].flatten
-@@ -61,7 +59,6 @@ task :nginx_without_native_support => [
+@@ -55,7 +53,6 @@
# it also creates a namespace:clean task to clean up the output_dir
task :nginx_dynamic_without_native_support => [
auto_generated_sources,
diff --git a/www/nginx/files/extra-patch-passenger-disable-telemetry b/www/nginx/files/extra-patch-passenger-disable-telemetry
index 9749f2327db1..adfafb141d70 100644
--- a/www/nginx/files/extra-patch-passenger-disable-telemetry
+++ b/www/nginx/files/extra-patch-passenger-disable-telemetry
@@ -1,5 +1,5 @@
---- ../passenger-6.0.15/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb.orig 2018-12-03 12:23:06.980728000 -0500
-+++ ../passenger-6.0.15/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb 2018-12-03 12:23:32.978924000 -0500
+--- ../passenger-6.0.17/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb.orig 2018-12-03 12:23:06.980728000 -0500
++++ ../passenger-6.0.17/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb 2018-12-03 12:23:32.978924000 -0500
@@ -204,7 +204,7 @@
:name => 'passenger_disable_anonymous_telemetry',
:scope => :global,
diff --git a/www/nginx/files/extra-patch-spnego-http-auth-nginx-module-config b/www/nginx/files/extra-patch-spnego-http-auth-nginx-module-config
index 6ca83c52973c..7ea16b2ff99e 100644
--- a/www/nginx/files/extra-patch-spnego-http-auth-nginx-module-config
+++ b/www/nginx/files/extra-patch-spnego-http-auth-nginx-module-config
@@ -1,12 +1,11 @@
---- ../spnego-http-auth-nginx-module-c626163/config.orig 2020-10-17 22:35:57.676855000 +0200
-+++ ../spnego-http-auth-nginx-module-c626163/config 2020-10-17 22:40:49.255324000 +0200
-@@ -1,8 +1,10 @@
+--- ../spnego-http-auth-nginx-module-c626163/config.orig 2020-08-27 07:59:28.423636000 -0400
++++ ../spnego-http-auth-nginx-module-c626163/config 2020-08-27 08:01:42.152121000 -0400
+@@ -1,8 +1,9 @@
ngx_addon_name=ngx_http_auth_spnego_module
-ngx_feature_libs="-lgssapi_krb5 -lkrb5 -lcom_err"
+ngx_feature_libs="-L%%GSSAPILIBDIR%% %%GSSAPILIBS%%"
+ngx_module_incs="%%GSSAPIINCDIR%%"
-+
if uname -o | grep -q FreeBSD; then
- ngx_feature_libs="$ngx_feature_libs -lgssapi"
+ ngx_feature_libs="$ngx_feature_libs"
diff --git a/www/nginx/files/extra-patch-src_http_modules_ngx__http__slice_read_ahead.c b/www/nginx/files/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c
index b9eb1f20a439..b9eb1f20a439 100644
--- a/www/nginx/files/extra-patch-src_http_modules_ngx__http__slice_read_ahead.c
+++ b/www/nginx/files/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c
diff --git a/www/nginx/files/nginx.in b/www/nginx/files/nginx.in
index 337db1191be7..652468a97596 100644
--- a/www/nginx/files/nginx.in
+++ b/www/nginx/files/nginx.in
@@ -47,10 +47,10 @@ _tmpprefix="%%NGINX_TMPDIR%%"
required_files=%%PREFIX%%/etc/nginx/nginx.conf
extra_commands="reload configtest upgrade gracefulstop"
-[ -z "$nginx_enable" ] && nginx_enable="NO"
+[ -z "$nginx_enable" ] && nginx_enable="NO"
[ -z "$nginxlimits_enable" ] && nginxlimits_enable="NO"
[ -z "$nginxlimits_args" ] && nginxlimits_args="-e -U %%WWWOWN%%"
-[ -z "$nginx_http_accept_enable" ] && nginx_http_accept_enable="NO"
+[ -z "$nginx_http_accept_enable" ] && nginx_http_accept_enable="NO"
[ -z "$nginx_reload_quiet" ] && nginx_reload_quiet="NO"
load_rc_config $name
diff --git a/www/nginx/files/patch-conf-nginx.conf b/www/nginx/files/patch-conf-nginx.conf
index d508e6d6352a..fb38c9c3d9cc 100644
--- a/www/nginx/files/patch-conf-nginx.conf
+++ b/www/nginx/files/patch-conf-nginx.conf
@@ -1,4 +1,4 @@
---- conf/nginx.conf.orig 2016-05-31 13:47:01 UTC
+--- conf/nginx.conf.orig 2016-09-06 14:56:32 UTC
+++ conf/nginx.conf
@@ -2,9 +2,14 @@
#user nobody;
diff --git a/www/nginx/pkg-plist b/www/nginx/pkg-plist
index db67d46b9121..0898a58da48c 100644
--- a/www/nginx/pkg-plist
+++ b/www/nginx/pkg-plist
@@ -1,32 +1,34 @@
-@sample %%ETCDIR%%/fastcgi_params-dist %%ETCDIR%%/fastcgi_params
%%ETCDIR%%/koi-utf
%%ETCDIR%%/koi-win
+%%ETCDIR%%/win-utf
%%LINK%%include/ngx_link_func_module.h
%%NAXSI%%%%ETCDIR%%/naxsi_core.rules
-@sample %%ETCDIR%%/scgi_params-dist %%ETCDIR%%/scgi_params
-@sample %%ETCDIR%%/uwsgi_params-dist %%ETCDIR%%/uwsgi_params
-%%ETCDIR%%/win-utf
+@sample %%ETCDIR%%/fastcgi_params-dist %%ETCDIR%%/fastcgi_params
@sample %%ETCDIR%%/mime.types-dist %%ETCDIR%%/mime.types
@sample %%ETCDIR%%/nginx.conf-dist %%ETCDIR%%/nginx.conf
+@sample %%ETCDIR%%/scgi_params-dist %%ETCDIR%%/scgi_params
+@sample %%ETCDIR%%/uwsgi_params-dist %%ETCDIR%%/uwsgi_params
+%%DSO%%%%AJP%%libexec/nginx/ngx_http_ajp_module.so
%%DSO%%%%ARRAYVAR%%libexec/nginx/ngx_http_array_var_module.so
%%DSO%%%%AWS_AUTH%%libexec/nginx/ngx_http_aws_auth_module.so
%%DSO%%%%BROTLI%%libexec/nginx/ngx_http_brotli_filter_module.so
%%DSO%%%%BROTLI%%libexec/nginx/ngx_http_brotli_static_module.so
+%%DSO%%%%CACHE_PURGE%%libexec/nginx/ngx_http_cache_purge_module.so
%%DSO%%%%CLOJURE%%libexec/nginx/ngx_http_clojure_module.so
-%%DSO%%%%CT%%libexec/nginx/ngx_ssl_ct_module.so
+%%DSO%%%%COOKIE_FLAG%%libexec/nginx/ngx_http_cookie_flag_filter_module.so
%%DSO%%%%CT%%libexec/nginx/ngx_http_ssl_ct_module.so
-%%DSO%%%%ECHO%%libexec/nginx/ngx_http_echo_module.so
-%%DSO%%%%DYNAMIC_HC%%libexec/nginx/ngx_http_dynamic_healthcheck_module.so
-%%DSO%%%%DYNAMIC_UPSTREAM%%libexec/nginx/ngx_http_dynamic_upstream_module.so
+%%DSO%%%%CT%%libexec/nginx/ngx_ssl_ct_module.so
%%DSO%%%%DEVEL_KIT%%libexec/nginx/ndk_http_module.so
%%DSO%%%%DRIZZLE%%libexec/nginx/ngx_http_drizzle_module.so
+%%DSO%%%%DYNAMIC_UPSTREAM%%libexec/nginx/ngx_http_dynamic_upstream_module.so
+%%DSO%%%%ECHO%%libexec/nginx/ngx_http_echo_module.so
%%DSO%%%%ENCRYPTSESSION%%libexec/nginx/ngx_http_encrypted_session_module.so
+%%DSO%%%%FIPS_CHECK%%libexec/nginx/ngx_fips_check_module.so
%%DSO%%%%FORMINPUT%%libexec/nginx/ngx_http_form_input_module.so
%%DSO%%%%GRIDFS%%libexec/nginx/ngx_http_gridfs_module.so
%%DSO%%%%HEADERS_MORE%%libexec/nginx/ngx_http_headers_more_filter_module.so
%%DSO%%%%HTTP_ACCEPT_LANGUAGE%%libexec/nginx/ngx_http_accept_language_module.so
%%DSO%%%%HTTP_AUTH_DIGEST%%libexec/nginx/ngx_http_auth_digest_module.so
-%%DSO%%%%HTTP_AUTH_JWT%%libexec/nginx/ngx_http_auth_jwt_module.so
%%DSO%%%%HTTP_AUTH_KRB5%%libexec/nginx/ngx_http_auth_spnego_module.so
%%DSO%%%%HTTP_AUTH_LDAP%%libexec/nginx/ngx_http_auth_ldap_module.so
%%DSO%%%%HTTP_AUTH_PAM%%libexec/nginx/ngx_http_auth_pam_module.so
@@ -34,8 +36,6 @@
%%DSO%%%%HTTP_EVAL%%libexec/nginx/ngx_http_eval_module.so
%%DSO%%%%HTTP_FANCYINDEX%%libexec/nginx/ngx_http_fancyindex_module.so
%%DSO%%%%HTTP_FOOTER%%libexec/nginx/ngx_http_footer_filter_module.so
-%%DSO%%%%HTTP%%%%HTTP_GEOIP2%%libexec/nginx/ngx_http_geoip2_module.so
-%%DSO%%%%STREAM%%%%HTTP_GEOIP2%%libexec/nginx/ngx_stream_geoip2_module.so
%%DSO%%%%HTTP_IMAGE_FILTER%%libexec/nginx/ngx_http_image_filter_module.so
%%DSO%%%%HTTP_IP2LOCATION%%libexec/nginx/ngx_http_ip2location_module.so
%%DSO%%%%HTTP_IP2PROXY%%libexec/nginx/ngx_http_ip2proxy_module.so
@@ -47,26 +47,26 @@
%%DSO%%%%HTTP_PUSH%%libexec/nginx/ngx_nchan_module.so
%%DSO%%%%HTTP_PUSH_STREAM%%libexec/nginx/ngx_http_push_stream_module.so
%%DSO%%%%HTTP_REDIS%%libexec/nginx/ngx_http_redis_module.so
-%%DSO%%%%HTTP_RESPONSE%%libexec/nginx/ngx_http_response_module.so
%%DSO%%%%HTTP_SUBS_FILTER%%libexec/nginx/ngx_http_subs_filter_module.so
%%DSO%%%%HTTP_TARANTOOL%%libexec/nginx/ngx_http_tnt_module.so
-%%DSO%%%%HTTP_UPLOAD%%libexec/nginx/ngx_http_upload_module.so
%%DSO%%%%HTTP_UPLOAD_PROGRESS%%libexec/nginx/ngx_http_uploadprogress_module.so
+%%DSO%%%%HTTP_UPLOAD%%libexec/nginx/ngx_http_upload_module.so
%%DSO%%%%HTTP_UPSTREAM_FAIR%%libexec/nginx/ngx_http_upstream_fair_module.so
%%DSO%%%%HTTP_UPSTREAM_STICKY%%libexec/nginx/ngx_http_sticky_module.so
%%DSO%%%%HTTP_VIDEO_THUMBEXTRACTOR%%libexec/nginx/ngx_http_video_thumbextractor_module.so
%%DSO%%%%HTTP_XSLT%%libexec/nginx/ngx_http_xslt_filter_module.so
%%DSO%%%%HTTP_ZIP%%libexec/nginx/ngx_http_zip_module.so
+%%DSO%%%%HTTP%%%%HTTP_GEOIP2%%libexec/nginx/ngx_http_geoip2_module.so
+%%DSO%%%%HTTP%%%%NJS%%libexec/nginx/ngx_http_js_module.so
%%DSO%%%%ICONV%%libexec/nginx/ngx_http_iconv_module.so
%%DSO%%%%LET%%libexec/nginx/ngx_http_let_module.so
%%DSO%%%%LINK%%libexec/nginx/ngx_http_link_func_module.so
%%DSO%%%%LUA%%libexec/nginx/ngx_http_lua_module.so
%%DSO%%%%MAIL%%libexec/nginx/ngx_mail_module.so
+%%DSO%%%%MAIL%%libexec/nginx/ngx_mail_ssl_ct_module.so
%%DSO%%%%MEMC%%libexec/nginx/ngx_http_memc_module.so
%%DSO%%%%MODSECURITY3%%libexec/nginx/ngx_http_modsecurity_module.so
%%DSO%%%%NAXSI%%libexec/nginx/ngx_http_naxsi_module.so
-%%DSO%%%%HTTP%%%%NJS%%libexec/nginx/ngx_http_js_module.so
-%%DSO%%%%STREAM%%%%NJS%%libexec/nginx/ngx_stream_js_module.so
%%DSO%%%%OPENTRACING%%libexec/nginx/ngx_http_opentracing_module.so
%%DSO%%%%PASSENGER%%libexec/nginx/ngx_http_passenger_module.so
%%DSO%%%%POSTGRES%%libexec/nginx/ngx_postgres_module.so
@@ -77,11 +77,23 @@
%%DSO%%%%SET_MISC%%libexec/nginx/ngx_http_set_misc_module.so
%%DSO%%%%SHIBBOLETH%%libexec/nginx/ngx_http_shibboleth_module.so
%%DSO%%%%SLOWFS_CACHE%%libexec/nginx/ngx_http_slowfs_module.so
-%%DSO%%%%SMALL_LIGHT%%libexec/nginx/ngx_http_small_light_module.so
%%DSO%%%%SRCACHE%%libexec/nginx/ngx_http_srcache_filter_module.so
+%%DSO%%%%STREAM%%%%NJS%%libexec/nginx/ngx_stream_js_module.so
%%DSO%%%%STREAM%%libexec/nginx/ngx_stream_module.so
+%%DSO%%%%STREAM%%libexec/nginx/ngx_stream_geoip2_module.so
+%%DSO%%%%STREAM%%libexec/nginx/ngx_stream_ssl_ct_module.so
+%%DSO%%%%STS%%libexec/nginx/ngx_http_stream_server_traffic_status_module.so
%%DSO%%%%VOD%%libexec/nginx/ngx_http_vod_module.so
+%%DSO%%%%VTS%%libexec/nginx/ngx_http_vhost_traffic_status_module.so
+%%DSO%%%%WEBSOCKIFY%%libexec/nginx/ngx_http_websockify_module.so
%%DSO%%%%XSS%%libexec/nginx/ngx_http_xss_filter_module.so
+%%HTTP_PERL%%%%SITE_ARCH%%/auto/nginx/nginx.so
+%%HTTP_PERL%%%%SITE_ARCH%%/nginx.pm
+sbin/nginx
+share/vim/vimfiles/ftdetect/nginx.vim
+share/vim/vimfiles/ftplugin/nginx.vim
+share/vim/vimfiles/indent/nginx.vim
+share/vim/vimfiles/syntax/nginx.vim
%%WWW%%@postexec mkdir -p -m 755 %D/www/nginx-dist
%%WWW%%@postexec if [ ! -d %D/www/nginx/ ] ; then ln -fs %D/www/nginx-dist %D/www/nginx; fi
%%WWW%%www/nginx-dist/EXAMPLE_DIRECTORY-DONT_ADD_OR_TOUCH_ANYTHING
@@ -89,13 +101,6 @@
%%WWW%%www/nginx-dist/50x.html
%%WWW%%@postexec chmod a-w %D/www/nginx-dist
%%WWW%%@postunexec if [ -L %D/www/nginx ]; then rm -f %D/www/nginx; fi
-sbin/nginx
-share/vim/vimfiles/ftdetect/nginx.vim
-share/vim/vimfiles/ftplugin/nginx.vim
-share/vim/vimfiles/indent/nginx.vim
-share/vim/vimfiles/syntax/nginx.vim
@dir %%NGINX_TMPDIR%%
@dir %%NGINX_LOGDIR%%
-man/man8/nginx.8.gz
-%%HTTP_PERL%%%%SITE_ARCH%%/auto/nginx/nginx.so
-%%HTTP_PERL%%%%SITE_ARCH%%/nginx.pm
+share/man/man8/nginx.8.gz