diff --git a/www/nginx-full/Makefile b/www/nginx-full/Makefile index f0a4b7f91586..1818f696243e 100644 --- a/www/nginx-full/Makefile +++ b/www/nginx-full/Makefile @@ -1,44 +1,44 @@ PORTNAME= nginx -PORTREVISION= 5 +PORTREVISION= 0 PKGNAMESUFFIX= -full MAINTAINER= joneum@FreeBSD.org COMMENT= Robust and small WWW server (full package) MASTERDIR= ${.CURDIR}/../nginx CONFLICTS= nginx-1.* \ nginx-devel-1.* \ nginx-lite-1.* \ nginx-naxsi-1.* OPTIONS_DEFAULT=DSO DEBUG DEBUGLOG FILE_AIO GOOGLE_PERFTOOLS \ HTTP HTTP_ADDITION HTTP_AUTH_REQ HTTP_CACHE HTTP_DAV \ HTTP_FLV HTTP_GZIP_STATIC HTTP_GUNZIP_FILTER \ HTTP_IMAGE_FILTER HTTP_MP4 HTTP_PERL HTTP_RANDOM_INDEX \ HTTP_REALIP HTTP_REWRITE HTTP_SECURE_LINK HTTP_SLICE \ HTTP_SLICE_AHEAD HTTP_SSL HTTP_STATUS HTTP_SUB HTTP_XSLT \ MAIL MAIL_IMAP MAIL_POP3 MAIL_SMTP MAIL_SSL HTTPV2 HTTPV2_AUTOTUNE \ NJS STREAM STREAM_SSL STREAM_SSL_PREREAD THREADS WWW \ AJP AWS_AUTH CACHE_PURGE CLOJURE CT ECHO FASTDFS 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 \ ARRAYVAR BROTLI DRIZZLE DYNAMIC_TLS DYNAMIC_HC DYNAMIC_UPSTREAM \ ENCRYPTSESSION FORMINPUT GRIDFS ICONV LET LINK LUA MEMC MODSECURITY3 \ NAXSI PASSENGER PCRE_ONE POSTGRES RDS_CSV RDS_JSON REDIS2 \ RTMP SET_MISC SFLOW SHIBBOLETH SLOWFS_CACHE SMALL_LIGHT \ SRCACHE STATSD VOD VTS UDPLOG X11 XSS WEBSOCKIFY .if defined(PACKAGE_BUILDING) || defined(BATCH) # Either GSSAPI_MIT or GSSAPI_HEIMDAL must be enabled to build binary package. # There's no chance to choose GSSAPI_RADIO via dialog while package building. OPTIONS_DEFAULT+= GSSAPI_MIT .endif .include "${MASTERDIR}/Makefile" diff --git a/www/nginx-naxsi/Makefile b/www/nginx-naxsi/Makefile index d07f0690fc9a..ee3412933fba 100644 --- a/www/nginx-naxsi/Makefile +++ b/www/nginx-naxsi/Makefile @@ -1,21 +1,21 @@ PORTNAME= nginx -PORTREVISION= 1 +PORTREVISION= 0 PKGNAMESUFFIX= -naxsi MAINTAINER= joneum@FreeBSD.org COMMENT= Robust and small WWW server (plus NAXSI) MASTERDIR= ${.CURDIR}/../nginx CONFLICTS= nginx-1.* \ nginx-devel-1.* \ nginx-full-1.* \ nginx-lite-1.* 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 NAXSI STREAM PCRE_ONE STREAM_SSL THREADS WWW .include "${MASTERDIR}/Makefile" diff --git a/www/nginx/Makefile b/www/nginx/Makefile index b720743e02ba..c72c03c9e4ee 100644 --- a/www/nginx/Makefile +++ b/www/nginx/Makefile @@ -1,402 +1,383 @@ PORTNAME= nginx -PORTVERSION= 1.24.0 -PORTREVISION?= 17 +PORTVERSION= 1.26.0 +PORTREVISION= 0 PORTEPOCH= 3 CATEGORIES= www MASTER_SITES= https://nginx.org/download/ \ LOCAL/joneum DISTFILES= ${DISTNAME}${EXTRACT_SUFX} MAINTAINER?= joneum@FreeBSD.org COMMENT?= Robust and small WWW server WWW= https://nginx.com/ LICENSE= BSD2CLAUSE LICENSE_FILE= ${WRKSRC}/LICENSE CONFLICTS_INSTALL= nginx-devel -PORTSCOUT= limit:^1\.24\.[0-9]* +PORTSCOUT= limit:^1\.2[6-7]\.[0-9]* USES= cpe CPE_VENDOR= f5 CPE_PRODUCT= nginx USE_GITHUB= nodefault NGINX_VARDIR?= /var NGINX_LOGDIR?= ${NGINX_VARDIR}/log/nginx NGINX_RUNDIR?= ${NGINX_VARDIR}/run NGINX_TMPDIR?= ${NGINX_VARDIR}/tmp/nginx HTTP_PORT?= 80 NGINX_ACCESSLOG?= ${NGINX_LOGDIR}/access.log NGINX_ERRORLOG?= ${NGINX_LOGDIR}/error.log CONFLICTS?= nginx-devel-1.* \ nginx-full-1.* \ nginx-lite-1.* \ nginx-naxsi-1.* USE_RC_SUBR?= nginx SUB_FILES?= pkg-message SUB_LIST+= WWWOWN=${WWWOWN} \ WWWGRP=${WWWGRP} \ NGINX_RUNDIR=${NGINX_RUNDIR} \ NGINX_TMPDIR=${NGINX_TMPDIR} \ PREFIX=${PREFIX} HAS_CONFIGURE= yes CONFIGURE_ARGS+=--prefix=${ETCDIR} \ --with-cc-opt="-I ${LOCALBASE}/include" \ --conf-path=${ETCDIR}/nginx.conf \ --sbin-path=${PREFIX}/sbin/nginx \ --pid-path=${NGINX_RUNDIR}/nginx.pid \ --error-log-path=${NGINX_ERRORLOG} \ --user=${WWWOWN} --group=${WWWGRP} \ --with-compat \ --with-pcre ALL_TARGET= PLIST_SUB+= NGINX_TMPDIR=${NGINX_TMPDIR} NGINX_LOGDIR=${NGINX_LOGDIR} WWWOWN=${WWWOWN} WWWGRP=${WWWGRP} USERS?= ${WWWOWN} GROUPS?=${WWWGRP} NO_OPTIONS_SORT= yes 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_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 NJS_XML THREADS WWW +OPTIONS_DEFINE= DEBUG DEBUGLOG DSO FILE_AIO IPV6 NJS NJS_XML OTEL THREADS WWW OPTIONS_DEFAULT?= DSO FILE_AIO HTTP HTTP_ADDITION HTTP_AUTH_REQ HTTP_CACHE \ 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 \ + HTTP_STATUS HTTP_SUB HTTPV2 HTTPV3 MAIL MAIL_SSL STREAM \ STREAM_REALIP STREAM_SSL STREAM_SSL_PREREAD THREADS WWW LIB_DEPENDS+= libpcre2-8.so:devel/pcre2 OPTIONS_RADIO+= GSSAPI OPTIONS_RADIO_GSSAPI= GSSAPI_HEIMDAL GSSAPI_MIT GSSAPI_HEIMDAL_USES= gssapi:heimdal,flags GSSAPI_MIT_USES= gssapi:mit OPTIONS_SUB= yes .include "Makefile.options.desc" .for opt in ${OPTIONS_GROUP_MAILGRP:NMAIL} ${opt}_IMPLIES= MAIL .endfor .for opt in ${OPTIONS_GROUP_HTTPGRP:NHTTP} WWW ${opt}_IMPLIES= HTTP .endfor .for opt in ${OPTIONS_GROUP_STREAMGRP:NSTREAM} ${opt}_IMPLIES= STREAM .endfor GSSAPI_HEIMDAL_IMPLIES= HTTP_AUTH_KRB5 GSSAPI_MIT_IMPLIES= HTTP_AUTH_KRB5 # If the target is makesum, make sure that every distfile is fetched. .if ${.TARGETS:Mmakesum} OPTIONS_DEFAULT= ${OPTIONS_DEFINE} ${OPTIONS_GROUP_HTTPGRP} \ ${OPTIONS_GROUP_MAILGRP} ${OPTIONS_GROUP_STREAMGRP} \ ${OPTIONS_GROUP_THIRDPARTYGRP} .endif # Non-module options handling DEBUG_CFLAGS= -g DEBUG_VARS= STRIP=#do not strip if nginx with debug information DEBUGLOG_CONFIGURE_ON= --with-debug DSO_CONFIGURE_ON= --modules-path=${MODULESDIR} DSO_VARS= MODULESDIR=${PREFIX}/libexec/${PORTNAME} FILE_AIO_CONFIGURE_ON= --with-file-aio IPV6_CONFIGURE_OFF= --with-cc-opt="-DNGX_HAVE_INET6=0 -I ${LOCALBASE}/include" THREADS_CONFIGURE_ON= --with-threads # Bundled modules GOOGLE_PERFTOOLS_LIB_DEPENDS= libprofiler.so:devel/google-perftools GOOGLE_PERFTOOLS_CONFIGURE_ON= --with-google_perftools_module HTTP_CONFIGURE_ON= --http-client-body-temp-path=${NGINX_TMPDIR}/client_body_temp \ --http-fastcgi-temp-path=${NGINX_TMPDIR}/fastcgi_temp \ --http-proxy-temp-path=${NGINX_TMPDIR}/proxy_temp \ --http-scgi-temp-path=${NGINX_TMPDIR}/scgi_temp \ --http-uwsgi-temp-path=${NGINX_TMPDIR}/uwsgi_temp \ --http-log-path=${NGINX_ACCESSLOG} HTTP_CONFIGURE_OFF= --without-http 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 HTTP_IMAGE_FILTER_LIB_DEPENDS= libgd.so:graphics/gd HTTP_IMAGE_FILTER_VARS= DSO_BASEMODS+=http_image_filter_module HTTP_MP4_CONFIGURE_ON= --with-http_mp4_module HTTP_PERL_CATEGORIES= perl5 HTTP_PERL_USES= perl5 # Fix build failure on clang >= 12 HTTP_PERL_CFLAGS= -Wno-compound-token-split-by-macro HTTP_PERL_VARS= DSO_BASEMODS+=http_perl_module HTTP_RANDOM_INDEX_CONFIGURE_ON= --with-http_random_index_module HTTP_REALIP_CONFIGURE_ON= --with-http_realip_module HTTP_SECURE_LINK_CONFIGURE_ON= --with-http_secure_link_module HTTP_SLICE_CONFIGURE_ON= --with-http_slice_module HTTP_SSL_CONFIGURE_ON= --with-http_ssl_module HTTP_SSL_USES= ssl HTTP_STATUS_CONFIGURE_ON= --with-http_stub_status_module HTTP_SUB_CONFIGURE_ON= --with-http_sub_module HTTP_XSLT_CONFIGURE_ON= --with-http_xslt_module HTTP_XSLT_LIB_DEPENDS= libxml2.so:textproc/libxml2 \ libxslt.so:textproc/libxslt 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_CONFIGURE_ON= --with-http_v3_module 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 MAIL_SMTP_CONFIGURE_OFF= --without-mail_smtp_module MAIL_SSL_USES= ssl MAIL_SSL_CONFIGURE_ON= --with-mail_ssl_module STREAM_VARS= DSO_BASEMODS+=stream +STREAM_CONFIGURE_ON= --with-stream STREAM_REALIP_CONFIGURE_ON= --with-stream_realip_module STREAM_SSL_USES= ssl STREAM_SSL_CONFIGURE_ON= --with-stream_ssl_module STREAM_SSL_PREREAD_CONFIGURE_ON=--with-stream_ssl_preread_module ### External modules .include "Makefile.extmod" .include .if ${PORT_OPTIONS:MDSO} _addbasemod= =dynamic _addextmod= add-dynamic-module .else _addextmod= add-module .endif .for mod in ${DSO_BASEMODS} CONFIGURE_ARGS+= --with-${mod}${_addbasemod} .endfor # Some modules depend on other being there before, for example, devel_kit needs # to be there before a few other. .for mod in ${FIRST_DSO_EXTMODS} CONFIGURE_ARGS+= --${_addextmod}=${WRKSRC_${mod}}${${mod:tu}_SUBDIR} .endfor .for mod in ${DSO_EXTMODS} CONFIGURE_ARGS+= --${_addextmod}=${WRKSRC_${mod}}${${mod:tu}_SUBDIR} .endfor # For non-GitHub hosted modules .for moddir in ${DSO_EXTDIRS} CONFIGURE_ARGS+= --${_addextmod}=${WRKDIR}/${moddir} .endfor .if empty(PORT_OPTIONS:MHTTP) && empty(PORT_OPTIONS:MMAIL) IGNORE= requires at least HTTP or MAIL to \ be defined. Please do 'make config' again .endif .if ${PORT_OPTIONS:MHTTP_AUTH_KRB5} && (empty(PORT_OPTIONS:MGSSAPI_HEIMDAL) && empty(PORT_OPTIONS:MGSSAPI_MIT)) IGNORE= required at least GSSAPI_HEIMDAL or \ GSSAPI_MIT to be defined. Please do \ 'make config' again .endif .if ${PORT_OPTIONS:MPASSENGER} && empty(PORT_OPTIONS:MDEBUG) CONFIGURE_ENV+= OPTIMIZE="yes" CFLAGS+= -DNDEBUG .endif .if ${PORT_OPTIONS:MPASSENGER} CONFIGURE_ENV+= EXTRA_PRE_CXXFLAGS="-std=c++14" .endif -.if empty(PORT_OPTIONS:MLUA) && empty(PORT_OPTIONS:MMODSECURITY3) && \ - empty(PORT_OPTIONS:MPASSENGER) +.if 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:MNJS} && empty(PORT_OPTIONS:MNJS_XML) CONFIGURE_ENV+= NJS_LIBXSLT=NO NJS_CONFIGURE_ARGS= --no-libxml2 .endif pre-everything:: @${ECHO_MSG} .if ${PORT_OPTIONS:MHTTP_UPSTREAM_FAIR} @${ECHO_MSG} "Enable http_ssl module to build upstream_fair with SSL support" .endif .if ${PORT_OPTIONS:MPASSENGER} @${ECHO_MSG} "This port install Passenger module only" .endif @${ECHO_MSG} post-extract-NAXSI-on: - @${MKDIR} ${WRKDIR}/naxsi-${NAXSI_NGINX_VER} - @${MV} ${WRKDIR}/naxsi_rules ${WRKDIR}/naxsi_src \ - ${WRKDIR}/naxsi-${NAXSI_NGINX_VER} + @${RMDIR} ${WRKSRC_naxsi}/naxsi_src/libinjection + @${LN} -s ${WRKSRC_libinjection} ${WRKSRC_naxsi}/naxsi_src/libinjection pre-patch-HTTPV3-on: @${MV} ${WRKSRC}/README ${WRKSRC}/README.1st post-patch: @${REINPLACE_CMD} 's!%%HTTP_PORT%%!${HTTP_PORT}!; \ s!%%PREFIX%%!${PREFIX}!; \ s!%%NGINX_ERRORLOG%%!${NGINX_ERRORLOG}!' \ ${WRKSRC}/conf/nginx.conf post-patch-BROTLI-on: - @${REINPLACE_CMD} -E 's!^brotli=.*!brotli="${LOCALBASE}"!' ${WRKSRC_brotli}/config + @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_brotli}/filter/config post-patch-DRIZZLE-on: @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_drizzle}/config post-patch-FASTDFS-on: @${REINPLACE_CMD} \ 's!%%PREFIX%%!${PREFIX}!g;s!%%LOCALBASE%%!${LOCALBASE}!g' \ ${WRKSRC_fastdfs}/src/config post-patch-GRIDFS-on: @${REINPLACE_CMD} 's!\/usr!${LOCALBASE}!g' \ ${WRKSRC_gridfs}/nginx-gridfs/config post-patch-HTTP_AUTH_KRB5-on: @${REINPLACE_CMD} 's!%%GSSAPILIBS%%!${GSSAPILIBS}!g; \ s!%%GSSAPIINCDIR%%!${GSSAPIINCDIR}!g; \ s!%%GSSAPILIBDIR%%!${GSSAPILIBDIR}!g' ${WRKSRC_auth_krb5}/config post-patch-HTTP_TARANTOOL-on: @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_nginx_tarantool}/config -# linker error acquire if --std=c99 defined, add "static" to inline function -post-patch-HTTP_ZIP-on: - @${REINPLACE_CMD} \ - 's!^inline!static inline!' \ - ${WRKSRC_mod_zip}/ngx_http_zip_parsers.* - post-patch-ICONV-on: @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_iconv}/config -post-patch-NAXSI-on: - @${REINPLACE_CMD} 's!MSIZE!TOK_MSIZE!g' \ - ${WRKSRC_naxsi}/naxsi_src/libinjection/src/libinjection_sqli.c - post-patch-PASSENGER-on: @${REINPLACE_CMD} \ - '177,179s!true!false!' \ + '168,170s!true!false!' \ ${WRKSRC_PASSENGER}/build/basics.rb @${REINPLACE_CMD} \ 's!-I/usr/include/libev!!; \ s!-lev!!; \ s!-Iext/libev!!; \ s!-I/usr/include/libeio!!; \ s!-leio!!; \ s!-Iext/libeio!!' \ ${WRKSRC_PASSENGER}/build/common_library.rb post-patch-POSTGRES-on: @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_postgres}/config post-patch-SFLOW-on: @${REINPLACE_CMD} \ 's!%%PREFIX%%!${LOCALBASE}!g' \ ${WRKSRC_sflow}/ngx_http_sflow_config.h -post-patch-VOD-on: - @${REINPLACE_CMD} \ - '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} ) .if !target(do-install) do-install: ${MKDIR} ${STAGEDIR}${ETCDIR} ${MKDIR} ${STAGEDIR}${NGINX_TMPDIR} ${MKDIR} ${STAGEDIR}${NGINX_LOGDIR} ${INSTALL_PROGRAM} ${WRKSRC}/objs/nginx ${STAGEDIR}${PREFIX}/sbin .for i in koi-utf koi-win win-utf ${INSTALL_DATA} ${WRKSRC}/conf/${i} ${STAGEDIR}${ETCDIR} .endfor .for i in fastcgi_params mime.types scgi_params uwsgi_params ${INSTALL_DATA} ${WRKSRC}/conf/${i} ${STAGEDIR}${ETCDIR}/${i}-dist .endfor do-install-HTTP_PERL-on: ${MKDIR} ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/auto/nginx ${INSTALL_PROGRAM} ${WRKSRC}/objs/src/http/modules/perl/blib/arch/auto/nginx/nginx.so \ ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/auto/nginx ${INSTALL_DATA} ${WRKSRC}/objs/src/http/modules/perl/blib/lib/nginx.pm \ ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/ # Install dynamic modules do-install-DSO-on: ${MKDIR} ${STAGEDIR}${MODULESDIR} (cd ${WRKSRC}/objs/ && ${FIND} . -name '*.so' -maxdepth 1 -type f \ -exec ${INSTALL_PROGRAM} {} ${STAGEDIR}${MODULESDIR} \;) do-install-LINK-on: ${INSTALL_DATA} ${WRKSRC_link}/src/ngx_link_func_module.h ${STAGEDIR}${PREFIX}/include do-install-NAXSI-on: ${INSTALL_DATA} \ ${WRKDIR}/naxsi-${NAXSI_NGINX_VER}/naxsi_rules/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}${PREFIX}/share/man/man8 ${CAT} ${WRKSRC}/conf/nginx.conf >> ${STAGEDIR}${ETCDIR}/nginx.conf-dist post-install-WWW-on: ${MKDIR} ${STAGEDIR}${PREFIX}/www/nginx-dist (cd ${WRKSRC}/html && ${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/www/nginx-dist && \ ${TOUCH} ${STAGEDIR}${PREFIX}/www/nginx-dist/EXAMPLE_DIRECTORY-DONT_ADD_OR_TOUCH_ANYTHING) .endif .include diff --git a/www/nginx/Makefile.extmod b/www/nginx/Makefile.extmod index 8e1ec1dd0b48..f6054544345a 100644 --- a/www/nginx/Makefile.extmod +++ b/www/nginx/Makefile.extmod @@ -1,333 +1,328 @@ ### External modules 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 \ +OPTIONS_GROUP_THIRDPARTYGRP= AJP AWS_AUTH BROTLI CACHE_PURGE 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_PROXY_CONNECT HTTP_PUSH \ + HTTP_JSON_STATUS HTTP_MOGILEFS 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 LUASTREAM MEMC \ - MODSECURITY3 NAXSI PASSENGER POSTGRES RDS_CSV RDS_JSON REDIS2\ - RTMP SET_MISC SFLOW SHIBBOLETH SLOWFS_CACHE SRCACHE STS VOD VTS \ - XSS WEBSOCKIFY + HTTP_UPSTREAM_STICKY HTTP_VIDEO_THUMBEXTRACTOR HTTP_ZIP ICONV LET LINK LUA LUASTREAM \ + MEMC MODSECURITY3 NAXSI 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 ARRAYVAR_VARS= DSO_EXTMODS+=arrayvar AWS_AUTH_GH_TUPLE= anomalizer:ngx_aws_auth:21931b2:aws_auth AWS_AUTH_VARS= DSO_EXTMODS+=aws_auth BROTLI_LIB_DEPENDS= libbrotlicommon.so:archivers/brotli -BROTLI_GH_TUPLE= google:ngx_brotli:9aec15e:brotli +BROTLI_GH_TUPLE= google:ngx_brotli:a71f931:brotli BROTLI_VARS= DSO_EXTMODS+=brotli +BROTLI_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_brotli_filter_config CACHE_PURGE_GH_TUPLE= nginx-modules:ngx_cache_purge:a84b0f3: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.6.0:clojure -CLOJURE_CONFIGURE_ENV= "JNI_INCS=-I${LOCALBASE}/openjdk8/include -I${LOCALBASE}/openjdk8/include/freebsd" -CLOJURE_VARS= DSO_EXTMODS+=clojure CLOJURE_SUBDIR=/src/c - -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:93e9884:ct CT_VARS= DSO_EXTMODS+=ct -CT_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-ct-LibreSSL +CT_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-ct-LibreSSL \ + ${PATCHDIR}/extra-patch-ngx_stream_ssl_ct_module.c 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:3504fc6:drizzle DRIZZLE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-openresty-drizzle-nginx-module-config DRIZZLE_VARS= DSO_EXTMODS+=drizzle DYNAMIC_UPSTREAM_IMPLIES= STREAM DYNAMIC_UPSTREAM_GH_TUPLE= ZigzagAK:ngx_dynamic_upstream:960eef2:dynamic_upstream DYNAMIC_UPSTREAM_VARS= DSO_EXTMODS+=dynamic_upstream -DEVEL_KIT_GH_TUPLE= vision5:ngx_devel_kit:v0.3.2:devel_kit +DEVEL_KIT_GH_TUPLE= vision5:ngx_devel_kit:v0.3.3:devel_kit DEVEL_KIT_VARS= FIRST_DSO_EXTMODS+=devel_kit ENCRYPTSESSION_IMPLIES= DEVEL_KIT 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= 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:33b646d:headers_more +HEADERS_MORE_GH_TUPLE= openresty:headers-more-nginx-module:06dc0be: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:274490c:auth_digest HTTP_AUTH_DIGEST_VARS= DSO_EXTMODS+=auth_digest HTTP_AUTH_KRB5_GH_TUPLE= stnoonan:spnego-http-auth-nginx-module:3575542:auth_krb5 HTTP_AUTH_KRB5_VARS= DSO_EXTMODS+=auth_krb5 HTTP_AUTH_KRB5_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-spnego-http-auth-nginx-module-config 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.3:auth_pam HTTP_AUTH_PAM_VARS= DSO_EXTMODS+=auth_pam -HTTP_PROXY_CONNECT_GH_TUPLE= chobits:ngx_http_proxy_connect_module:75febef:mod_https_connect -HTTP_PROXY_CONNECT_EXTRA_PATCHES= ${WRKSRC_mod_https_connect}/patch/proxy_connect_rewrite_102101.patch:-p1 -HTTP_PROXY_CONNECT_VARS= DSO_EXTMODS+=mod_https_connect - HTTP_DAV_EXT_IMPLIES= HTTP_DAV HTTP_DAV_EXT_LIB_DEPENDS= libxml2.so:textproc/libxml2 \ libxslt.so:textproc/libxslt HTTP_DAV_EXT_GH_TUPLE= arut:nginx-dav-ext-module:v3.0.0:dav_ext HTTP_DAV_EXT_VARS= DSO_EXTMODS+=dav_ext HTTP_DAV_EXT_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_dav_ext_module.c HTTP_EVAL_GH_TUPLE= openresty:nginx-eval-module:582bd25:eval HTTP_EVAL_VARS= DSO_EXTMODS+=eval HTTP_FANCYINDEX_GH_TUPLE= aperezdc:ngx-fancyindex:v0.5.2:fancyindex HTTP_FANCYINDEX_VARS= DSO_EXTMODS+=fancyindex 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.4:geoip2 HTTP_GEOIP2_CFLAGS= -I${LOCALBASE}/include HTTP_GEOIP2_VARS= DSO_EXTMODS+=geoip2 HTTP_GEOIP2_LIB_DEPENDS= libmaxminddb.so:net/libmaxminddb HTTP_IP2LOCATION_GH_TUPLE= ip2location:ip2location-nginx:2df35fb:ip2location HTTP_IP2LOCATION_LIB_DEPENDS= libIP2Location.so:net/ip2location HTTP_IP2LOCATION_VARS= DSO_EXTMODS+=ip2location HTTP_IP2PROXY_GH_TUPLE= ip2location:ip2proxy-nginx:02ce447:ip2proxy HTTP_IP2PROXY_LIB_DEPENDS= libIP2Proxy.so:net/ip2proxy 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_MOGILEFS_MASTER_SITES= http://www.grid.net.ru/nginx/download/:mogilefs HTTP_MOGILEFS_DISTFILES= nginx_mogilefs_module-1.0.4.tar.gz:mogilefs HTTP_MOGILEFS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_mogilefs_module.c \ ${PATCHDIR}/extra-patch-nginx_mogilefs_module-config 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_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 -HTTP_MP4_H264_VARS= DSO_EXTDIRS+=nginx_mod_h264_streaming-2.2.7 - HTTP_NOTICE_GH_TUPLE= kr:nginx-notice:3c95966:notice 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.6:push HTTP_PUSH_VARS= DSO_EXTMODS+=push 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_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:c6f825f:subs_filter HTTP_SUBS_FILTER_VARS= DSO_EXTMODS+=subs_filter HTTP_TARANTOOL_LIB_DEPENDS= libmsgpuck.so:devel/msgpuck \ libyajl.so:devel/yajl 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:643b4c1:upload HTTP_UPLOAD_VARS= DSO_EXTMODS+=upload 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} HTTP_UPSTREAM_CHECK_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-src-http-modules-ngx_http_upstream_hash_module.c \ ${PATCHDIR}/extra-patch-src-http-modules-ngx_http_upstream_ip_hash_module.c \ ${PATCHDIR}/extra-patch-src-http-modules-ngx_http_upstream_least_conn_module.c \ ${PATCHDIR}/extra-patch-src-http-ngx_http_upstream_round_robin.c \ ${PATCHDIR}/extra-patch-src-http-ngx_http_upstream_round_robin.h 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= dvershinin:nginx-sticky-module-ng:2753211:upstreamsticky HTTP_UPSTREAM_STICKY_VARS= DSO_EXTMODS+=upstreamsticky HTTP_VIDEO_THUMBEXTRACTOR_LIB_DEPENDS= libavformat.so:multimedia/ffmpeg \ libavcodec.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= wandenberg:nginx-video-thumbextractor-module:e81f850:vte HTTP_VIDEO_THUMBEXTRACTOR_VARS= DSO_EXTMODS+=vte +HTTP_VIDEO_THUMBEXTRACTOR_EXTRA_PATCHES=${PATCHDIR}/extra-patch-nginx-thumbextractor-module-config -HTTP_ZIP_GH_TUPLE= evanmiller:mod_zip:39dc908:mod_zip +HTTP_ZIP_GH_TUPLE= vince2678:mod_zip:5b2604b:mod_zip HTTP_ZIP_VARS= DSO_EXTMODS+=mod_zip ICONV_IMPLIES= DEVEL_KIT ICONV_USES= iconv ICONV_GH_TUPLE= calio:iconv-nginx-module:v0.14:iconv ICONV_VARS= DSO_EXTMODS+=iconv 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 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.26:lua LUA_VARS= DSO_EXTMODS+=lua LUASTREAM_IMPLIES= DEVEL_KIT LUASTREAM_LIB_DEPENDS= libluajit-5.1.so:lang/luajit-openresty LUASTREAM_RUN_DEPENDS= lua-resty-core>0:www/lua-resty-core LUASTREAM_CONFIGURE_ENV=LUAJIT_INC=${LOCALBASE}/include/luajit-2.1 \ LUAJIT_LIB=${LOCALBASE}/lib LUASTREAM_GH_TUPLE= openresty:stream-lua-nginx-module:v0.0.14:luastream LUASTREAM_VARS= DSO_EXTMODS+=luastream 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_LIB_DEPENDS= libmodsecurity.so:security/modsecurity3 MODSECURITY3_GH_TUPLE= SpiderLabs:ModSecurity-nginx:v1.0.3:modsecurity3 MODSECURITY3_VARS= DSO_EXTMODS+=modsecurity3 NAXSI_NGINX_VER= 1.6 -NAXSI_MASTER_SITES= https://www.github.com/wargio/naxsi/releases/download/${NAXSI_NGINX_VER}/:naxsi -NAXSI_DISTFILES= naxsi-${NAXSI_NGINX_VER}-src-with-deps.tar.gz:naxsi +NAXSI_GH_TUPLE= wargio:naxsi:${NAXSI_NGINX_VER}:naxsi \ + libinjection:libinjection:4aa3894:libinjection NAXSI_VARS= DSO_EXTMODS+=naxsi NAXSI_SUBDIR=/naxsi_src -WRKSRC_naxsi= ${WRKDIR}/naxsi-${NAXSI_NGINX_VER} +NAXSI_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-naxsi-libinjection__sqli_c \ + ${PATCHDIR}/extra-patch-naxsi_config -NJS_GH_TUPLE= nginx:njs:0.8.0:njs +NJS_GH_TUPLE= nginx:njs:0.8.4:njs NJS_VARS= DSO_EXTMODS+=njs NJS_SUBDIR=/nginx +NJS_IMPLIES= STREAM -NJS_XML_IMPLIES= NJS +NJS_XML_IMPLIES= HTTP NJS NJS_XML_LIB_DEPENDS= libxml2.so:textproc/libxml2 \ libxslt.so:textproc/libxslt -PASSENGER_NGINX_VER= 6.0.17 +OTEL_GH_TUPLE= osokin:nginx-otel:8f0857d:otel +OTEL_VARS= DSO_EXTMODS+=otel OTEL_SUBDIR=/ +OTEL_LIB_DEPENDS= libabsl_base.so:devel/abseil \ + libcares.so:dns/c-ares \ + libgrpc.so:devel/grpc \ + libopentelemetry_common.so:devel/opentelemetry-cpp \ + libprotobuf.so:devel/protobuf \ + libre2.so:devel/re2 +OTEL_BUILD_DEPENDS= ${LOCALBASE}/include/opentelemetry/proto/common/v1/common.proto:devel/opentelemetry-proto +OTEL_CONFIGURE_ENV+= NGX_OTEL_PROTO_DIR=${PREFIX}/include + +PASSENGER_NGINX_VER= 6.0.20 PASSENGER_CATEGORIES= ruby PASSENGER_USES= ruby PASSENGER_BUILD_DEPENDS=${LOCALBASE}/bin/rake:devel/rubygem-rake PASSENGER_RAKE_BIN= ${LOCALBASE}/bin/rake 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 PASSENGER_EXTRA_PATCHES=${PATCHDIR}/extra-patch-passenger-build-nginx.rb \ ${PATCHDIR}/extra-patch-passenger-disable-telemetry POSTGRES_USES= pgsql POSTGRES_GH_TUPLE= konstruxi:ngx_postgres:8aa7359:postgres POSTGRES_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_postgres-config POSTGRES_VARS= DSO_EXTMODS+=postgres RDS_CSV_GH_TUPLE= openresty:rds-csv-nginx-module:v0.09:rdscsv RDS_CSV_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-rds-csv-nginx-module-config RDS_CSV_VARS= DSO_EXTMODS+=rdscsv RDS_JSON_GH_TUPLE= openresty:rds-json-nginx-module:v0.15:rdsjson RDS_JSON_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-rds-json-nginx-module-config RDS_JSON_VARS= DSO_EXTMODS+=rdsjson REDIS2_GH_TUPLE= openresty:redis2-nginx-module:v0.15:redis2 REDIS2_VARS= DSO_EXTMODS+=redis2 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:3937e7b:setmisc SET_MISC_VARS= DSO_EXTMODS+=setmisc SFLOW_GH_TUPLE= sflow:nginx-sflow-module:543c72a:sflow 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: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 SRCACHE_GH_TUPLE= openresty:srcache-nginx-module:be22ac0:srcache SRCACHE_VARS= DSO_EXTMODS+=srcache 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 VTS_GH_TUPLE= vozlt:nginx-module-vts:bf64dbf: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:c11bc9a:websockify WEBSOCKIFY_VARS= DSO_EXTMODS+=websockify diff --git a/www/nginx/Makefile.options.desc b/www/nginx/Makefile.options.desc index d299cc6b3607..9ab1054b57ef 100644 --- a/www/nginx/Makefile.options.desc +++ b/www/nginx/Makefile.options.desc @@ -1,120 +1,117 @@ AJP_DESC= 3rd party ajp module ARRAYVAR_DESC= 3rd party array_var module 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_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 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.) 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_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 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 HTTP_FLV_DESC= Enable http_flv module HTTP_FOOTER_DESC= 3rd party http_footer module HTTP_GEOIP2_DESC= 3rd party geoip2 module HTTP_GUNZIP_FILTER_DESC= Enable http_gunzip_filter module HTTP_GZIP_STATIC_DESC= Enable http_gzip_static module HTTP_IMAGE_FILTER_DESC= Enable http_image_filter module HTTP_IP2LOCATION_DESC= 3rd party ip2location-nginx module HTTP_IP2PROXY_DESC= 3rd party ip2proxy-nginx module HTTP_JSON_STATUS_DESC= 3rd party http_json_status module HTTP_MOGILEFS_DESC= 3rd party mogilefs module HTTP_MP4_DESC= Enable http_mp4 module -HTTP_MP4_H264_DESC= 3rd party mp4/h264 module HTTP_NOTICE_DESC= 3rd party notice module HTTP_PERL_DESC= Enable http_perl module -HTTP_PROXY_CONNECT_DESC= 3rd party https proxy connect module HTTP_PUSH_DESC= 3rd party push module 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_SECURE_LINK_DESC= Enable http_secure_link module HTTP_SLICE_DESC= Enable http_slice 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 HTTP_SUB_DESC= Enable http_sub module HTTP_TARANTOOL_DESC= 3rd party tarantool upstream module HTTP_UPLOAD_DESC= 3rd party upload module HTTP_UPLOAD_PROGRESS_DESC= 3rd party uploadprogress module HTTP_UPSTREAM_CHECK_DESC= 3rd party upstream check module HTTP_UPSTREAM_FAIR_DESC= 3rd party upstream fair module HTTP_UPSTREAM_STICKY_DESC= 3rd party upstream sticky module HTTP_VIDEO_DESC= 3rd party video module support HTTP_VIDEO_THUMBEXTRACTOR_DESC= 3rd party video_thumbextractor module HTTP_XSLT_DESC= Enable http_xslt module HTTP_ZIP_DESC= 3rd party http_zip module ICONV_DESC= 3rd party iconv module IPV6_DESC= Enable IPv6 support LET_DESC= 3rd party let module LINK_DESC= 3rd party link function module LUA_DESC= 3rd party lua module LUASTREAM_DESC= 3rd party lua stream module MAILGRP_DESC= Modules that require MAIL module MAIL_DESC= Enable IMAP4/POP3/SMTP proxy module MAIL_IMAP_DESC= Enable IMAP4 proxy module MAIL_POP3_DESC= Enable POP3 proxy module MAIL_SMTP_DESC= Enable SMTP proxy module 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 javascript (NJS) module NJS_XML_DESC= Enable XML functionality in NJS module +OTEL_DESC= Enable OpenTELemetry module PASSENGER_DESC= 3rd party passenger module POSTGRES_DESC= 3rd party postgres module RDS_CSV_DESC= 3rd party rds_csv module RDS_JSON_DESC= 3rd party rds_json module REDIS2_DESC= 3rd party redis2 module RTMP_DESC= 3rd party rtmp module 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 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 XSS_DESC= 3rd party xss module WEBSOCKIFY_DESC= 3rd party websockify module diff --git a/www/nginx/distinfo b/www/nginx/distinfo index c038036d9d0a..3785c6617456 100644 --- a/www/nginx/distinfo +++ b/www/nginx/distinfo @@ -1,147 +1,141 @@ -TIMESTAMP = 1708285506 -SHA256 (nginx-1.24.0.tar.gz) = 77a2541637b92a621e3ee76776c8b7b40cf6d707e69ba53a940283e30ff2f55d -SIZE (nginx-1.24.0.tar.gz) = 1112471 +TIMESTAMP = 1714506394 +SHA256 (nginx-1.26.0.tar.gz) = d2e6c8439d6c6db5015d8eaab2470ab52aef85a7bf363182879977e084370497 +SIZE (nginx-1.26.0.tar.gz) = 1244118 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 (naxsi-1.6-src-with-deps.tar.gz) = 1add95e5e473fca58b18356fd896221f98a122450d5b6e91b4352ef726f98a06 -SIZE (naxsi-1.6-src-with-deps.tar.gz) = 3352718 -SHA256 (passenger-6.0.17.tar.gz) = 385559ed1d78eb83165222d568721dcc4222bb57c1939811ecd2c4ef33937ba7 -SIZE (passenger-6.0.17.tar.gz) = 8422867 +SHA256 (passenger-6.0.20.tar.gz) = fa8d9a37edb92f4a8f064b3005b57bccf10392ce4eb067838883206060e27107 +SIZE (passenger-6.0.20.tar.gz) = 8476308 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 (google-ngx_brotli-a71f931_GH0.tar.gz) = b3312a045d5303a40d02beb34711b8ca27f7b72d647e9ee2012a8eddd14d9b22 +SIZE (google-ngx_brotli-a71f931_GH0.tar.gz) = 16376 SHA256 (nginx-modules-ngx_cache_purge-a84b0f3_GH0.tar.gz) = ddfd4fdd99075d906b7b75c49f56ec96b76df7951dfa54502e0f83890447031f SIZE (nginx-modules-ngx_cache_purge-a84b0f3_GH0.tar.gz) = 17162 -SHA256 (nginx-clojure-nginx-clojure-v0.6.0_GH0.tar.gz) = e8215cdebc3eb13f852c10e9bbbf315f2e1b75bb4dec015ca60ec29efcb86509 -SIZE (nginx-clojure-nginx-clojure-v0.6.0_GH0.tar.gz) = 786029 -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 (vision5-ngx_devel_kit-v0.3.2_GH0.tar.gz) = aa961eafb8317e0eb8da37eb6e2c9ff42267edd18b56947384e719b85188f58b -SIZE (vision5-ngx_devel_kit-v0.3.2_GH0.tar.gz) = 66551 +SHA256 (vision5-ngx_devel_kit-v0.3.3_GH0.tar.gz) = faa2fcd5168b10764d35081356511d5f84db5c526a1aa4b6add2db94b6853b2b +SIZE (vision5-ngx_devel_kit-v0.3.3_GH0.tar.gz) = 66561 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.09_GH0.tar.gz) = fe9b95acf9726aefd71bf0aca6c11bee007f1da67e64be9b21a7131f0ed75ba6 SIZE (openresty-encrypted-session-nginx-module-v0.09_GH0.tar.gz) = 11847 -SHA256 (ogarrett-nginx-fips-check-module-6cb4270_GH0.tar.gz) = d52fbb0f2819cd91b710ad85e6c8b452fdca6a5d81b0694d6637adba3fc2382c -SIZE (ogarrett-nginx-fips-check-module-6cb4270_GH0.tar.gz) = 6494 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 (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 (openresty-headers-more-nginx-module-06dc0be_GH0.tar.gz) = 883b1e31d59f3eb1e76b34259711ad65a3443102973dcf22df329397f3d5eaa4 +SIZE (openresty-headers-more-nginx-module-06dc0be_GH0.tar.gz) = 29438 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-274490c_GH0.tar.gz) = 0839c33c2f8d519f92daae274f62cf87eb68415d562c6500ee3e3721ce80557c SIZE (atomx-nginx-http-auth-digest-274490c_GH0.tar.gz) = 17815 SHA256 (stnoonan-spnego-http-auth-nginx-module-3575542_GH0.tar.gz) = 6d710f97bef58b2d5dc54445c0e48103786425f6d4ab18cf30a2168904d0ba62 SIZE (stnoonan-spnego-http-auth-nginx-module-3575542_GH0.tar.gz) = 24680 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 SIZE (openresty-nginx-eval-module-582bd25_GH0.tar.gz) = 14849 SHA256 (aperezdc-ngx-fancyindex-v0.5.2_GH0.tar.gz) = c3dd84d8ba0b8daeace3041ef5987e3fb96e9c7c17df30c9ffe2fe3aa2a0ca31 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.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 SIZE (ip2location-ip2proxy-nginx-02ce447_GH0.tar.gz) = 5177 SHA256 (nginx-modules-ngx_http_json_status_module-1d2f303_GH0.tar.gz) = fdc34e0e712d28f4452ce3858ba05a38cc00703f14502095189c4a1063a36997 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 (chobits-ngx_http_proxy_connect_module-75febef_GH0.tar.gz) = 6169361f31607af0ec8c78b356e62c2aeb128649161d688d7ea92f4d2c1c39f9 -SIZE (chobits-ngx_http_proxy_connect_module-75febef_GH0.tar.gz) = 32645 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-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 (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-28861f2_GH0.tar.gz) = 04656da527d9e64cbdf1bf475a93193fa60324ffea160d05d4cc53c864943bc1 -SIZE (Novetta-nginx-video-thumbextractor-module-28861f2_GH0.tar.gz) = 34447 -SHA256 (evanmiller-mod_zip-39dc908_GH0.tar.gz) = bc5c3d725268abbe1c5c38de5b18a4ad9dbe5821c4afeaccabd3eec38b272be4 -SIZE (evanmiller-mod_zip-39dc908_GH0.tar.gz) = 30275 +SHA256 (wandenberg-nginx-video-thumbextractor-module-e81f850_GH0.tar.gz) = 9113f887a8740fe72614ee32f481177d33e9542c3b0625627da19a1c4f3da2cb +SIZE (wandenberg-nginx-video-thumbextractor-module-e81f850_GH0.tar.gz) = 2710072 +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.26_GH0.tar.gz) = a75983287a2bdc5e964ace56a51b215dc2ec996639d4916cd393d6ebba94b565 SIZE (openresty-lua-nginx-module-v0.10.26_GH0.tar.gz) = 745785 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.3_GH0.tar.gz) = 32a42256616cc674dca24c8654397390adff15b888b77eb74e0687f023c8751b SIZE (SpiderLabs-ModSecurity-nginx-v1.0.3_GH0.tar.gz) = 34063 -SHA256 (nginx-njs-0.8.0_GH0.tar.gz) = b98033fff6aadcbb8e108b96e80c0d94c6e2103bcbe75846b5ae0b560696084b -SIZE (nginx-njs-0.8.0_GH0.tar.gz) = 715391 -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 (wargio-naxsi-1.6_GH0.tar.gz) = e5920fdd09cae155b89eb21a94a21c029ebfdb056c284130221525be54044aae +SIZE (wargio-naxsi-1.6_GH0.tar.gz) = 1116227 +SHA256 (libinjection-libinjection-4aa3894_GH0.tar.gz) = ededea133e89e238ef2e60d0d62ef7ef9e741449eed8c5d856007132505bcd5b +SIZE (libinjection-libinjection-4aa3894_GH0.tar.gz) = 2218294 +SHA256 (nginx-njs-0.8.4_GH0.tar.gz) = fe197e254204c15e9f1df0acf375add57be3416901ec8d7b87319dccb490f90d +SIZE (nginx-njs-0.8.4_GH0.tar.gz) = 743910 +SHA256 (osokin-nginx-otel-8f0857d_GH0.tar.gz) = bbf93813928460bdaf78f752f74ecc6c34d13078e97fdffcaa29dbd8689314fc +SIZE (osokin-nginx-otel-8f0857d_GH0.tar.gz) = 30197 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 SIZE (openresty-rds-csv-nginx-module-v0.09_GH0.tar.gz) = 20531 SHA256 (openresty-rds-json-nginx-module-v0.15_GH0.tar.gz) = eaf18f60e981ea2442a7902689a26eba6cf6f36ebee712feeb1f4429eb654bdc 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 (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-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 (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-bf64dbf_GH0.tar.gz) = d2782c75e39cb2ecf68453922b43ab2295adb6a35fa6a0f9c14173f70d22d7b1 SIZE (vozlt-nginx-module-vts-bf64dbf_GH0.tar.gz) = 180394 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 SHA256 (openresty-stream-lua-nginx-module-v0.0.14_GH0.tar.gz) = 8e2ff6ad5f91127da3c01757e7e654f1addf9769450d9159601d2cc153953c47 SIZE (openresty-stream-lua-nginx-module-v0.0.14_GH0.tar.gz) = 381313 diff --git a/www/nginx/files/extra-patch-httpv3 b/www/nginx/files/extra-patch-httpv3 deleted file mode 100644 index c49f591c25d5..000000000000 --- a/www/nginx/files/extra-patch-httpv3 +++ /dev/null @@ -1,26867 +0,0 @@ -diff -r ac779115ed6e README ---- /dev/null Thu Jan 01 00:00:00 1970 +0000 -+++ b/README Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,386 @@ -+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; -+ -+ To enable GSO (Generic Segmentation Offloading): -+ -+ quic_gso on; -+ -+ To limit maximum UDP payload size on receive path: -+ -+ quic_mtu ; -+ -+ To set host key for various tokens: -+ -+ quic_host_key ; -+ -+ QUIC requires TLSv1.3 protocol, which is enabled by the default -+ by "ssl_protocols" directive. -+ -+ 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; -+ -+ 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 Thu May 11 11:48:37 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= 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 -+ #include " -+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 -+ $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 Thu May 11 11:48:37 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 auto/unix ---- a/auto/unix Tue Mar 28 18:01:53 2023 +0300 -+++ b/auto/unix Thu May 11 11:48:37 2023 -0400 -@@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_ - . auto/feature - - -+# IP packet fragmentation -+ -+ngx_feature="IP_MTU_DISCOVER" -+ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER" -+ngx_feature_run=no -+ngx_feature_incs="#include -+ #include " -+ngx_feature_path= -+ngx_feature_libs= -+ngx_feature_test="(void) IP_PMTUDISC_DO; -+ setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)" -+. auto/feature -+ -+ -+ngx_feature="IPV6_MTU_DISCOVER" -+ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER" -+ngx_feature_run=no -+ngx_feature_incs="#include -+ #include " -+ngx_feature_path= -+ngx_feature_libs= -+ngx_feature_test="(void) IPV6_PMTUDISC_DO; -+ setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)" -+. auto/feature -+ -+ -+ngx_feature="IP_DONTFRAG" -+ngx_feature_name="NGX_HAVE_IP_DONTFRAG" -+ngx_feature_run=no -+ngx_feature_incs="#include -+ #include " -+ngx_feature_path= -+ngx_feature_libs= -+ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" -+. auto/feature -+ -+ -+ngx_feature="IPV6_DONTFRAG" -+ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" -+ngx_feature_run=no -+ngx_feature_incs="#include -+ #include " -+ngx_feature_path= -+ngx_feature_libs= -+ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)" -+. auto/feature -+ -+ - ngx_feature="TCP_DEFER_ACCEPT" - ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" - ngx_feature_run=no -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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,143 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,43 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#ifndef _NGX_BPF_H_INCLUDED_ -+#define _NGX_BPF_H_INCLUDED_ -+ -+ -+#include -+#include -+ -+#include -+ -+ -+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 Thu May 11 11:48:37 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; - -@@ -1014,6 +1010,78 @@ ngx_configure_listening_sockets(ngx_cycl - } - - #endif -+ -+#if (NGX_HAVE_IP_MTU_DISCOVER) -+ -+ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { -+ value = IP_PMTUDISC_DO; -+ -+ if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER, -+ (const void *) &value, sizeof(int)) -+ == -1) -+ { -+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, -+ "setsockopt(IP_MTU_DISCOVER) " -+ "for %V failed, ignored", -+ &ls[i].addr_text); -+ } -+ } -+ -+#elif (NGX_HAVE_IP_DONTFRAG) -+ -+ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { -+ value = 1; -+ -+ if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, -+ (const void *) &value, sizeof(int)) -+ == -1) -+ { -+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, -+ "setsockopt(IP_DONTFRAG) " -+ "for %V failed, ignored", -+ &ls[i].addr_text); -+ } -+ } -+ -+#endif -+ -+#if (NGX_HAVE_INET6) -+ -+#if (NGX_HAVE_IPV6_MTU_DISCOVER) -+ -+ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { -+ value = IPV6_PMTUDISC_DO; -+ -+ if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, -+ (const void *) &value, sizeof(int)) -+ == -1) -+ { -+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, -+ "setsockopt(IPV6_MTU_DISCOVER) " -+ "for %V failed, ignored", -+ &ls[i].addr_text); -+ } -+ } -+ -+#elif (NGX_HAVE_IP_DONTFRAG) -+ -+ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { -+ value = 1; -+ -+ if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, -+ (const void *) &value, sizeof(int)) -+ == -1) -+ { -+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, -+ "setsockopt(IPV6_DONTFRAG) " -+ "for %V failed, ignored", -+ &ls[i].addr_text); -+ } -+ } -+ -+#endif -+ -+#endif - } - - return; -@@ -1037,6 +1105,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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 - #if (NGX_OPENSSL) - #include -+#if (NGX_QUIC) -+#include -+#endif - #endif - #include - #include -@@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt - #include - #include - #include -+#if (NGX_HAVE_BPF) -+#include -+#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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 2023 -0400 -@@ -24,6 +24,14 @@ - #include - #endif - #include -+#if (NGX_QUIC) -+#ifdef OPENSSL_IS_BORINGSSL -+#include -+#include -+#else -+#include -+#endif -+#endif - #include - #ifndef OPENSSL_NO_OCSP - #include -@@ -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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,113 @@ -+#!/bin/bash -+ -+export LANG=C -+ -+set -e -+ -+if [ $# -lt 1 ]; then -+ echo "Usage: PROGNAME=foo LICENSE=bar $0 " -+ exit 1 -+fi -+ -+ -+self=$0 -+filename=$1 -+funcname=$PROGNAME -+ -+generate_head() -+{ -+ cat << END -+/* AUTO-GENERATED, DO NOT EDIT. */ -+ -+#include -+#include -+ -+#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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,140 @@ -+#include -+#include -+#include -+#include -+/* -+ * 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 -+ -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,1445 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+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].frames); -+ 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,131 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ -+#define _NGX_EVENT_QUIC_H_INCLUDED_ -+ -+ -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,1192 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+#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) << qc->pto_count) -+ - 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); -+ -+ if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { -+ duration += qc->ctp.max_ack_delay; -+ } -+ -+ 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) << qc->pto_count) -+ - 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 Thu May 11 11:48:37 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 -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,657 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,88 @@ -+/* AUTO-GENERATED, DO NOT EDIT. */ -+ -+#include -+#include -+ -+#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 Thu May 11 11:48:37 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 -+#include -+#include -+ -+ -+/* #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 -+#endif -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+ -+/* 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,502 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+#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 Thu May 11 11:48:37 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 -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,894 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+#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_chain_t *out; -+ 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; -+ -+ out = ngx_quic_read_buffer(c, &qb, of->length); -+ if (out == NGX_CHAIN_ERROR) { -+ return NGX_ERROR; -+ } -+ -+ f->data = out; -+ -+ 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 Thu May 11 11:48:37 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 -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,714 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+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 void ngx_quic_set_path_timer(ngx_connection_t *c); -+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; -+ -+ ngx_quic_set_path_timer(c); -+ -+ 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; -+ path->tries = 0; -+ -+ 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_max(ngx_quic_pto(c, ctx), 1000); -+ -+ path->expires = ngx_current_msec + pto; -+ -+ ngx_quic_set_path_timer(c); -+ -+ 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; -+} -+ -+ -+static void -+ngx_quic_set_path_timer(ngx_connection_t *c) -+{ -+ ngx_msec_t now; -+ ngx_queue_t *q; -+ ngx_msec_int_t left, next; -+ ngx_quic_path_t *path; -+ ngx_quic_connection_t *qc; -+ -+ qc = ngx_quic_get_connection(c); -+ -+ now = ngx_current_msec; -+ next = -1; -+ -+ 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; -+ } -+ -+ left = path->expires - now; -+ left = ngx_max(left, 1); -+ -+ if (next == -1 || left < next) { -+ next = left; -+ } -+ } -+ -+ if (next != -1) { -+ ngx_add_timer(&qc->path_validation, next); -+ -+ } else if (qc->path_validation.timer_set) { -+ ngx_del_timer(&qc->path_validation); -+ } -+} -+ -+ -+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); -+ -+ 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 < NGX_QUIC_PATH_RETRIES) { -+ pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << 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 Thu May 11 11:48:37 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 -+#include -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,646 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 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 -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,1293 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+#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; -+ } -+ -+ ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); -+ -+ 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; -+ } -+ -+ qc->last_cc = ngx_current_msec; -+ -+ return ngx_quic_frame_sendto(c, &frame, 0, qc->path); -+} -+ -+ -+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, frame->level); -+ -+ 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 Thu May 11 11:48:37 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 -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,1087 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+/* 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, ¤t->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, ¤t->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 Thu May 11 11:48:37 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 -+#include -+ -+#include -+ -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,237 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 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 -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,600 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 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 -+#include -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,1779 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+#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 (qc->push.posted) { -+ /* -+ * The posted stream can produce output immediately. -+ * By postponing the push event, we coalesce the stream -+ * output with queued frames in one UDP datagram. -+ */ -+ -+ ngx_delete_posted_event(&qc->push); -+ ngx_post_event(&qc->push, &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 Thu May 11 11:48:37 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 -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,289 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 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 -+#include -+ -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,2199 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 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 -+#include -+ -+ -+/* -+ * 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,473 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -9,6 +9,10 @@ - #include - #include - -+#if (NGX_QUIC_OPENSSL_COMPAT) -+#include -+#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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 - #endif -+#if (NGX_HTTP_V3) -+#include -+#endif - #if (NGX_HTTP_CACHE) - #include - #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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,113 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+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"); -+ 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 Thu May 11 11:48:37 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 -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,304 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 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 -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,1536 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+/* 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,554 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ * Copyright (C) Roman Arutyunyan -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,2013 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 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 -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,1718 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,715 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+#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 Thu May 11 11:48:37 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 -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,781 @@ -+ -+/* -+ * Copyright (C) Roman Arutyunyan -+ * Copyright (C) Nginx, Inc. -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+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(q)) -+ { -+ 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 Thu May 11 11:48:37 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 -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 2023 -0400 -@@ -16,6 +16,10 @@ - #include - #endif - -+#if (NGX_STREAM_QUIC) -+#include -+#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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 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 Thu May 11 11:48:37 2023 -0400 -@@ -0,0 +1,377 @@ -+ -+/* -+ * Copyright (C) Nginx, Inc. -+ * Copyright (C) Roman Arutyunyan -+ */ -+ -+ -+#include -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 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 -+#include -+#include -+ -+ -+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 Thu May 11 11:48:37 2023 -0400 -@@ -9,6 +9,10 @@ - #include - #include - -+#if (NGX_QUIC_OPENSSL_COMPAT) -+#include -+#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 -r ac779115ed6e src/stream/ngx_stream_write_filter_module.c ---- a/src/stream/ngx_stream_write_filter_module.c Tue Mar 28 18:01:53 2023 +0300 -+++ b/src/stream/ngx_stream_write_filter_module.c Thu May 11 11:48:37 2023 -0400 -@@ -277,7 +277,12 @@ ngx_stream_write_filter(ngx_stream_sessi - *out = chain; - - if (chain) { -- if (c->shared) { -+ if (c->shared -+#if (NGX_STREAM_QUIC) -+ && c->quic == NULL -+#endif -+ ) -+ { - ngx_log_error(NGX_LOG_ALERT, c->log, 0, - "shared connection is busy"); - return NGX_ERROR; diff --git a/www/nginx/files/extra-patch-naxsi-libinjection__sqli_c b/www/nginx/files/extra-patch-naxsi-libinjection__sqli_c new file mode 100644 index 000000000000..bdd074b620dd --- /dev/null +++ b/www/nginx/files/extra-patch-naxsi-libinjection__sqli_c @@ -0,0 +1,13 @@ +--- ../libinjection-4aa3894/src/libinjection_sqli.c.orig 2023-05-30 15:47:57.333208000 -0400 ++++ ../libinjection-4aa3894/src/libinjection_sqli.c 2023-05-30 15:49:52.273873000 -0400 +@@ -305,8 +303,8 @@ + static void st_assign(stoken_t * st, const char stype, + size_t pos, size_t len, const char* value) + { +- const size_t MSIZE = LIBINJECTION_SQLI_TOKEN_SIZE; +- size_t last = len < MSIZE ? len : (MSIZE - 1); ++ const size_t NAXSI_MSIZE = LIBINJECTION_SQLI_TOKEN_SIZE; ++ size_t last = len < NAXSI_MSIZE ? len : (NAXSI_MSIZE - 1); + st->type = (char) stype; + st->pos = pos; + st->len = last; diff --git a/www/nginx/files/extra-patch-naxsi_config b/www/nginx/files/extra-patch-naxsi_config new file mode 100644 index 000000000000..8fa8a4785527 --- /dev/null +++ b/www/nginx/files/extra-patch-naxsi_config @@ -0,0 +1,26 @@ +--- ../naxsi-1.6/naxsi_src/config.orig 2023-09-27 09:43:18.644606000 -0400 ++++ ../naxsi-1.6/naxsi_src/config 2023-09-27 09:44:37.585970000 -0400 +@@ -24,11 +24,6 @@ + " + + # try to use libinjection as system library +-LIBINJECTION_CFLAGS="$(pkg-config --cflags libinjection)" +-LIBINJECTION_LIBS="$(pkg-config --libs libinjection)" +-LIBINJECTION_FOUND="$?" +- +-if [ "$LIBINJECTION_FOUND" != "0" ]; then + if [ ! -d "$ngx_addon_dir/libinjection/src/" ]; then + echo "Cannot find 'libinjection' submodule." + exit 1; +@@ -43,11 +38,6 @@ + naxsi_sources="$naxsi_sources $ngx_addon_dir/libinjection_ngxbuild/$src_file" + done; + CFLAGS="$CFLAGS -DLIBINJECTION_VERSION=0 -I$ngx_addon_dir/libinjection_ngxbuild/" +-else +- echo "Using system libinjection" +- CFLAGS="$CFLAGS $LIBINJECTION_CFLAGS" +- ngx_feature_libs="$LIBINJECTION_LIBS" +-fi + + # NGINX module condfiguration. + ngx_addon_name=naxsi diff --git a/www/nginx/files/extra-patch-nginx-thumbextractor-module-config b/www/nginx/files/extra-patch-nginx-thumbextractor-module-config new file mode 100644 index 000000000000..6324fd9fcc52 --- /dev/null +++ b/www/nginx/files/extra-patch-nginx-thumbextractor-module-config @@ -0,0 +1,25 @@ +--- ../nginx-video-thumbextractor-module-e81f850/config.orig 2024-02-22 08:49:55.972829000 -0500 ++++ ../nginx-video-thumbextractor-module-e81f850/config 2024-02-22 08:50:05.036983000 -0500 +@@ -1,9 +1,14 @@ + ngx_addon_name=ngx_http_video_thumbextractor_module +-ngx_feature_libs="-lavformat -lavcodec -lavutil -lavfilter -lswscale -lswresample -lpostproc -ljpeg" +-HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES $ngx_addon_name" +-CORE_INCS="$CORE_INCS \ +- $ngx_addon_dir/src \ +- $ngx_addon_dir/include" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ +- ${ngx_addon_dir}/src/ngx_http_video_thumbextractor_module.c" +-CORE_LIBS="$CORE_LIBS $ngx_feature_libs" ++ngx_module_libs="-lavformat -lavcodec -lavutil -lavfilter -lswscale -lswresample -lpostproc -ljpeg" ++ngx_module_incs="$ngx_addon_dir/include $ngx_addon_dir/src" ++ ++if test -n "$ngx_module_link"; then ++ ngx_module_type=HTTP ++ ngx_module_name=$ngx_addon_name ++ ngx_module_srcs="${ngx_addon_dir}/src/ngx_http_video_thumbextractor_module.c" ++ . auto/module ++else ++ HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" ++ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ ++ ${ngx_addon_dir}/src/ngx_http_video_thumbextractor_module.c" ++fi diff --git a/www/nginx/files/extra-patch-nginx_mod_h264_streaming-config b/www/nginx/files/extra-patch-nginx_mod_h264_streaming-config deleted file mode 100644 index 1acd8cd12405..000000000000 --- a/www/nginx/files/extra-patch-nginx_mod_h264_streaming-config +++ /dev/null @@ -1,41 +0,0 @@ ---- ../nginx_mod_h264_streaming-2.2.7/config.orig 2020-04-27 12:01:37.153986000 -0400 -+++ ../nginx_mod_h264_streaming-2.2.7/config 2020-04-27 12:16:52.832788000 -0400 -@@ -1,26 +1,19 @@ - ngx_addon_name=ngx_http_h264_streaming_module --HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_h264_streaming_module" --CFLAGS="$CFLAGS -D_LARGEFILE_SOURCE -DBUILDING_NGINX" -- --H264_STREAMING_MODULE_SOURCES="$ngx_addon_dir/src/ngx_http_h264_streaming_module.c \ -+ngx_module_name="$ngx_addon_name" -+ngx_module_type=HTTP_AUX_FILTER -+ngx_module_srcs="$ngx_addon_dir/src/ngx_http_h264_streaming_module.c \ - $ngx_addon_dir/src/moov.c \ - $ngx_addon_dir/src/mp4_io.c \ - $ngx_addon_dir/src/mp4_reader.c \ - $ngx_addon_dir/src/mp4_writer.c \ - $ngx_addon_dir/src/output_bucket.c \ - $ngx_addon_dir/src/output_mp4.c" -- --for streaming_module_source in $H264_STREAMING_MODULE_SOURCES --do -- already_included=NO -- for ngx_addon_src in $NGX_ADDON_SRCS -- do -- if [ "`basename $ngx_addon_src`" = "`basename $streaming_module_source`" ]; then -- already_included=YES -- break -- fi -- done -- if [ "$already_included" = "NO" ]; then -- NGX_ADDON_SRCS="$NGX_ADDON_SRCS $streaming_module_source" -- fi --done -+ngx_module_deps="$ngx_addon_dir/src/mod_streaming_export.h \ -+ $ngx_addon_dir/src/moov.h \ -+ $ngx_addon_dir/src/mp4_io.h \ -+ $ngx_addon_dir/src/mp4_process.h \ -+ $ngx_addon_dir/src/mp4_reader.h \ -+ $ngx_addon_dir/src/mp4_writer.h \ -+ $ngx_addon_dir/src/output_bucket.h \ -+ $ngx_addon_dir/src/output_mp4.h" -+. auto/module diff --git a/www/nginx/files/extra-patch-ngx_brotli_filter_config b/www/nginx/files/extra-patch-ngx_brotli_filter_config new file mode 100644 index 000000000000..dfac8cfe5696 --- /dev/null +++ b/www/nginx/files/extra-patch-ngx_brotli_filter_config @@ -0,0 +1,41 @@ +--- ../ngx_brotli-a71f931/filter/config.orig 2023-09-30 13:17:01.637746000 -0400 ++++ ../ngx_brotli-a71f931/filter/config 2023-09-30 13:23:51.839920000 -0400 +@@ -42,33 +42,16 @@ + ngx_module_type=HTTP_FILTER + ngx_module_name=ngx_http_brotli_filter_module + +-brotli="$ngx_addon_dir/deps/brotli/c" +-if [ ! -f "$brotli/include/brotli/encode.h" ]; then +-cat << END ++BROTLI_ENC_H="%%PREFIX%%/include/brotli/encode.h \ ++ %%PREFIX%%/include/brotli/port.h \ ++ %%PREFIX%%/include/brotli/types.h" + +-$0: error: \ +-Brotli library is missing from the $brotli directory. + +-Please make sure that the git submodule has been checked out: +- +- cd $ngx_addon_dir && git submodule update --init && cd $PWD +- +-END +- exit 1 +-fi +- +-BROTLI_OUTPUT_DIRECTORY="$brotli/../out" +-BROTLI_ENC_H="$brotli/include/brotli/encode.h \ +- $brotli/include/brotli/port.h \ +- $brotli/include/brotli/types.h" +- +- +-ngx_module_incs="$brotli/include" ++ngx_module_incs="$BROTLI_ENC_H" + ngx_module_deps="$BROTLI_ENC_H" + ngx_module_srcs="$BROTLI_MODULE_SRC_DIR/ngx_http_brotli_filter_module.c" +-ngx_module_libs="-L$BROTLI_OUTPUT_DIRECTORY -lbrotlienc -lbrotlicommon -lm" ++ngx_module_libs="-L%%PREFIX%%/lib -lbrotlienc -lbrotlicommon -lm" + ngx_module_order="$ngx_module_name \ +- ngx_pagespeed \ + ngx_http_postpone_filter_module \ + ngx_http_ssi_filter_module \ + ngx_http_charset_filter_module \ diff --git a/www/nginx/files/extra-patch-ngx_http_streaming_module.c b/www/nginx/files/extra-patch-ngx_http_streaming_module.c deleted file mode 100644 index 3641090a9b5d..000000000000 --- a/www/nginx/files/extra-patch-ngx_http_streaming_module.c +++ /dev/null @@ -1,13 +0,0 @@ ---- ../nginx_mod_h264_streaming-2.2.7/src/ngx_http_streaming_module.c.orig 2010-05-24 18:04:43.000000000 +0400 -+++ ../nginx_mod_h264_streaming-2.2.7/src/ngx_http_streaming_module.c 2010-05-24 18:05:02.000000000 +0400 -@@ -155,10 +155,6 @@ - } - - /* TODO: Win32 */ -- if (r->zero_in_uri) -- { -- return NGX_DECLINED; -- } - - rc = ngx_http_discard_request_body(r); - diff --git a/www/nginx/files/extra-patch-ngx_stream_ssl_ct_module.c b/www/nginx/files/extra-patch-ngx_stream_ssl_ct_module.c new file mode 100644 index 000000000000..b3df57b24218 --- /dev/null +++ b/www/nginx/files/extra-patch-ngx_stream_ssl_ct_module.c @@ -0,0 +1,14 @@ +--- ../nginx-ct-93e9884/ngx_stream_ssl_ct_module.c.orig 2024-04-18 12:32:44.086600000 -0400 ++++ ../nginx-ct-93e9884/ngx_stream_ssl_ct_module.c 2024-04-18 12:37:40.764525000 -0400 +@@ -70,7 +70,11 @@ + + static char *ngx_stream_ssl_ct_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child) { ++#if nginx_version >= 1025005 ++ ngx_stream_ssl_srv_conf_t *ssl_conf = ngx_stream_conf_get_module_srv_conf(cf, ++#else + ngx_stream_ssl_conf_t *ssl_conf = ngx_stream_conf_get_module_srv_conf(cf, ++#endif + ngx_stream_ssl_module); + + ngx_array_t *certificates; diff --git a/www/nginx/files/extra-patch-passenger-build-nginx.rb b/www/nginx/files/extra-patch-passenger-build-nginx.rb index 8225f4f10931..40db3ee74183 100644 --- a/www/nginx/files/extra-patch-passenger-build-nginx.rb +++ b/www/nginx/files/extra-patch-passenger-build-nginx.rb @@ -1,33 +1,33 @@ ---- ../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 +--- ../passenger-6.0.20/build/nginx.rb.orig 2013-10-26 18:00:00.000000000 -0400 ++++ ../passenger-6.0.20/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, - NATIVE_SUPPORT_TARGET + LIBBOOST_OXT, ].compact desc "Build Nginx support files, including objects suitable for dynamic linking against Nginx" task 'nginx:as_dynamic_module' => [ :nginx_dynamic_without_native_support, - NATIVE_SUPPORT_TARGET ].compact # Workaround for https://github.com/jimweirich/rake/issues/274 @@ -47,7 +46,6 @@ task :nginx_without_native_support => [ auto_generated_sources, - AGENT_TARGET, COMMON_LIBRARY.only(*NGINX_LIBS_SELECTOR).link_objects ].flatten @@ -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, - AGENT_TARGET, define_libboost_oxt_task("nginx", NGINX_DYNAMIC_OUTPUT_DIR + "libboost_oxt", "-fPIC"), COMMON_LIBRARY.only(*NGINX_LIBS_SELECTOR). set_namespace("nginx").set_output_dir(NGINX_DYNAMIC_OUTPUT_DIR + "module_libpassenger_common").define_tasks("-fPIC"). diff --git a/www/nginx/files/extra-patch-passenger-disable-telemetry b/www/nginx/files/extra-patch-passenger-disable-telemetry index adfafb141d70..5b01b146eb5d 100644 --- a/www/nginx/files/extra-patch-passenger-disable-telemetry +++ b/www/nginx/files/extra-patch-passenger-disable-telemetry @@ -1,11 +1,11 @@ ---- ../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 +--- ../passenger-6.0.20/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb.orig 2018-12-03 12:23:06.980728000 -0500 ++++ ../passenger-6.0.20/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, :type => :flag, - :default => false, + :default => true, :context => [:main], :struct => 'NGX_HTTP_MAIN_CONF_OFFSET' }, diff --git a/www/nginx/pkg-plist b/www/nginx/pkg-plist index 35d8f66bfad8..466e656d60e0 100644 --- a/www/nginx/pkg-plist +++ b/www/nginx/pkg-plist @@ -1,107 +1,104 @@ %%ETCDIR%%/koi-utf %%ETCDIR%%/koi-win %%ETCDIR%%/win-utf %%LINK%%include/ngx_link_func_module.h %%NAXSI%%%%ETCDIR%%/naxsi_core.rules @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%%%%COOKIE_FLAG%%libexec/nginx/ngx_http_cookie_flag_filter_module.so %%DSO%%%%CT%%libexec/nginx/ngx_http_ssl_ct_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_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 %%DSO%%%%HTTP_DAV_EXT%%libexec/nginx/ngx_http_dav_ext_module.so %%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_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 %%DSO%%%%HTTP_JSON_STATUS%%libexec/nginx/ngx_http_json_status_module.so %%DSO%%%%HTTP_MOGILEFS%%libexec/nginx/ngx_http_mogilefs_module.so -%%DSO%%%%HTTP_MP4_H264%%libexec/nginx/ngx_http_h264_streaming_module.so %%DSO%%%%HTTP_NOTICE%%libexec/nginx/ngx_http_notice_module.so %%DSO%%%%HTTP_PERL%%libexec/nginx/ngx_http_perl_module.so -%%DSO%%%%HTTP_PUSH%%libexec/nginx/ngx_nchan_module.so -%%DSO%%%%HTTP_PROXY_CONNECT%%libexec/nginx/ngx_http_proxy_connect_module.so %%DSO%%%%HTTP_PUSH_STREAM%%libexec/nginx/ngx_http_push_stream_module.so +%%DSO%%%%HTTP_PUSH%%libexec/nginx/ngx_nchan_module.so %%DSO%%%%HTTP_REDIS%%libexec/nginx/ngx_http_redis_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_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%%%%LUASTREAM%%libexec/nginx/ngx_stream_lua_module.so -%%DSO%%%%MAIL%%libexec/nginx/ngx_mail_module.so %%DSO%%%%MAIL%%%%CT%%libexec/nginx/ngx_mail_ssl_ct_module.so +%%DSO%%%%MAIL%%libexec/nginx/ngx_mail_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%%%%OTEL%%libexec/nginx/ngx_otel_module.so %%DSO%%%%PASSENGER%%libexec/nginx/ngx_http_passenger_module.so %%DSO%%%%POSTGRES%%libexec/nginx/ngx_postgres_module.so %%DSO%%%%RDS_CSV%%libexec/nginx/ngx_http_rds_csv_filter_module.so %%DSO%%%%RDS_JSON%%libexec/nginx/ngx_http_rds_json_filter_module.so %%DSO%%%%REDIS2%%libexec/nginx/ngx_http_redis2_module.so %%DSO%%%%RTMP%%libexec/nginx/ngx_rtmp_module.so %%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%%%%SRCACHE%%libexec/nginx/ngx_http_srcache_filter_module.so +%%DSO%%%%STREAM%%%%CT%%libexec/nginx/ngx_stream_ssl_ct_module.so +%%DSO%%%%STREAM%%%%HTTP_GEOIP2%%libexec/nginx/ngx_stream_geoip2_module.so %%DSO%%%%STREAM%%%%NJS%%libexec/nginx/ngx_stream_js_module.so %%DSO%%%%STREAM%%libexec/nginx/ngx_stream_module.so -%%DSO%%%%STREAM%%%%HTTP_GEOIP2%%libexec/nginx/ngx_stream_geoip2_module.so -%%DSO%%%%STREAM%%%%CT%%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 %%WWW%%www/nginx-dist/index.html %%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 @dir %%NGINX_TMPDIR%% @dir %%NGINX_LOGDIR%% share/man/man8/nginx.8.gz