diff --git a/www/nginx-devel/Makefile b/www/nginx-devel/Makefile index 9140dbf72c4f..226d737b15b1 100644 --- a/www/nginx-devel/Makefile +++ b/www/nginx-devel/Makefile @@ -1,377 +1,385 @@ # Created by: Sergey A. Osokin PORTNAME?= nginx PORTVERSION= 1.21.5 -PORTREVISION= 3 +PORTREVISION= 4 CATEGORIES= www MASTER_SITES= https://nginx.org/download/ \ LOCAL/osa PKGNAMESUFFIX?= -devel DISTFILES= ${DISTNAME}${EXTRACT_SUFX} MAINTAINER?= osa@FreeBSD.org COMMENT?= Robust and small WWW server LICENSE?= BSD2CLAUSE LICENSE_FILE?= ${WRKSRC}/LICENSE CONFLICTS_INSTALL= nginx PORTSCOUT= limit:^1\.21\.[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-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" \ --with-ld-opt="-L ${LOCALBASE}/lib" \ --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 + HTTP_STATUS HTTP_SUB HTTP_XSLT HTTPV2 HTTPV3 OPTIONS_GROUP_MAILGRP= MAIL MAIL_IMAP MAIL_POP3 MAIL_SMTP MAIL_SSL OPTIONS_GROUP_STREAMGRP= STREAM STREAM_REALIP STREAM_SSL \ STREAM_SSL_PREREAD OPTIONS_DEFINE= DEBUG DEBUGLOG DSO FILE_AIO IPV6 NJS THREADS WWW OPTIONS_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 PCRE_ONE STREAM \ STREAM_REALIP STREAM_SSL STREAM_SSL_PREREAD THREADS WWW OPTIONS_RADIO+= PCRE OPTIONS_RADIO_PCRE= PCRE_ONE PCRE_TWO PCRE_ONE_LIB_DEPENDS= libpcre.so:devel/pcre PCRE_ONE_CONFIGURE_ON= --without-pcre2 PCRE_TWO_LIB_DEPENDS= libpcre2-8.so:devel/pcre2 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 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_USES= gnome HTTP_XSLT_USE= GNOME=libxml2,libxslt HTTP_XSLT_VARS= DSO_BASEMODS+=http_xslt_module HTTPV2_IMPLIES= HTTP_SSL HTTPV2_CONFIGURE_ON= --with-http_v2_module +HTTPV3_BUILD_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl +HTTPV3_RUN_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl +HTTPV3_CONFIGURE_ON= --with-ld-opt="-L ${LOCALBASE}/lib -Wl,-rpath,${LOCALBASE}/lib" \ + --with-http_ssl_module \ + --build=nginx-quic \ + --with-stream_quic_module \ + --with-http_v3_module +HTTPV3_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-httpv3:-p1 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_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:MPASSENGER} && empty(PORT_OPTIONS:MDEBUG) CONFIGURE_ENV+= OPTIMIZE="yes" CFLAGS+= -DNDEBUG .endif .if empty(PORT_OPTIONS:MPCRE_ONE) && empty(PORT_OPTIONS:MPCRE_TWO) IGNORE= required at least PCRE_ONE or PCRE_TWO \ to be defined. Please do 'make config' again .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:MPCRE_ONE} NJS_CONFIGURE_ARGS= --no-pcre2 .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-GRIDFS-on: @${RMDIR} ${WRKSRC_gridfs}/mongo-c-driver/ @${MV} ${WRKSRC_mongo_c} ${WRKSRC_gridfs}/mongo-c-driver 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 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 # Respect CFLAGS by remove needless --std=c99 flag post-patch-GRIDFS-on: @${REINPLACE_CMD} 's!--std=c99!-DMONGO_HAVE_STDINT!' ${WRKSRC_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-PASSENGER-on: @${REINPLACE_CMD} \ '177,179s!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} \ && ${SETENV} ${MAKE_ENV} ${MAKE_CMD} njs \ && ${MV} build/njs ${WRKSRC_njs} ) .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_config/naxsi_core.rules \ ${STAGEDIR}${ETCDIR} do-install-NJS-on: ${INSTALL_PROGRAM} ${WRKSRC_njs}/njs ${STAGEDIR}${PREFIX}/sbin .endif .if !target(post-install) post-install: ${MKDIR} ${STAGEDIR}${PREFIX}/share/vim/vimfiles cd ${WRKSRC}/contrib/vim && ${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/share/vim/vimfiles ${INSTALL_MAN} ${WRKSRC}/objs/nginx.8 ${STAGEDIR}${MAN8PREFIX}/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-devel/Makefile.options.desc b/www/nginx-devel/Makefile.options.desc index 0424d95d8150..b39e871d0a9b 100644 --- a/www/nginx-devel/Makefile.options.desc +++ b/www/nginx-devel/Makefile.options.desc @@ -1,117 +1,118 @@ 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_HC_DESC= 3rd party dynamic_healthcheck module DYNAMIC_UPSTREAM_DESC= 3rd party dynamic_upstream module ECHO_DESC= 3rd party echo module ENCRYPTSESSION_DESC= 3rd party encrypted_session module FILE_AIO_DESC= Enable file aio 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 (BoringSSL req.) 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_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_RESPONSE_DESC= 3rd party http_response module HTTP_SECURE_LINK_DESC= Enable http_secure_link module HTTP_SLICE_DESC= Enable http_slice module HTTP_SLICE_AHEAD_DESC= 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 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 module OPENTRACING_DESC= 3rd party opentracing module PASSENGER_DESC= 3rd party passenger module PCRE_ONE_DESC= Enable PCRE1 support PCRE_TWO_DESC= Enable PCRE2 support POSTGRES_DESC= 3rd party postgres module RDS_CSV_DESC= 3rd party rds_csv module RDS_JSON_DESC= 3rd party rds_json module 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 SMALL_LIGHT_DESC= 3rd party small_light module SRCACHE_DESC= 3rd party srcache module STREAMGRP_DESC= Modules that require STREAM module STREAM_DESC= Enable stream module STREAM_REALIP_DESC= Enable stream_realip module STREAM_SSL_DESC= Enable stream_ssl module (SSL req.) STREAM_SSL_PREREAD_DESC= Enable stream_ssl_preread module (SSL req.) 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-devel/files/extra-patch-httpv3 b/www/nginx-devel/files/extra-patch-httpv3 new file mode 100644 index 000000000000..dac679832645 --- /dev/null +++ b/www/nginx-devel/files/extra-patch-httpv3 @@ -0,0 +1,25543 @@ +diff -r 67408b4a12c0 auto/lib/openssl/conf +--- a/auto/lib/openssl/conf Tue Dec 28 18:28:38 2021 +0300 ++++ b/auto/lib/openssl/conf Tue Jan 04 18:14:15 2022 -0500 +@@ -5,12 +5,16 @@ + + if [ $OPENSSL != NONE ]; then + ++ have=NGX_OPENSSL . auto/have ++ have=NGX_SSL . auto/have ++ ++ if [ $USE_OPENSSL_QUIC = YES ]; then ++ have=NGX_QUIC . auto/have ++ fi ++ + case "$CC" in + + cl | bcc32) +- have=NGX_OPENSSL . auto/have +- have=NGX_SSL . auto/have +- + CFLAGS="$CFLAGS -DNO_SYS_TYPES_H" + + CORE_INCS="$CORE_INCS $OPENSSL/openssl/include" +@@ -33,9 +37,6 @@ + ;; + + *) +- have=NGX_OPENSSL . auto/have +- have=NGX_SSL . auto/have +- + CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include" + CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h" + CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a" +@@ -139,4 +140,28 @@ + exit 1 + fi + ++ if [ $USE_OPENSSL_QUIC = YES ]; then ++ ++ ngx_feature="OpenSSL QUIC support" ++ ngx_feature_name="NGX_QUIC" ++ ngx_feature_run=no ++ ngx_feature_incs="#include " ++ ngx_feature_path= ++ ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" ++ ngx_feature_test="SSL_set_quic_method(NULL, NULL)" ++ . auto/feature ++ ++ if [ $ngx_found = no ]; then ++ ++cat << END ++ ++$0: error: certain modules require OpenSSL QUIC support. ++You can either do not enable the modules, or install the OpenSSL library with ++QUIC support into the system, or build the OpenSSL library with QUIC support ++statically from the source with nginx by using --with-openssl= option. ++ ++END ++ exit 1 ++ fi ++ fi + fi +diff -r 67408b4a12c0 auto/make +--- a/auto/make Tue Dec 28 18:28:38 2021 +0300 ++++ b/auto/make Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 auto/modules +--- a/auto/modules Tue Dec 28 18:28:38 2021 +0300 ++++ b/auto/modules Tue Jan 04 18:14:15 2022 -0500 +@@ -102,7 +102,7 @@ + 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 @@ + # 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 @@ + 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 @@ + . 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 @@ + . 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 @@ + + 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,60 @@ + fi + + ++if [ $USE_OPENSSL_QUIC = YES ]; then ++ ngx_module_type=CORE ++ ngx_module_name=ngx_quic_module ++ ngx_module_incs= ++ ngx_module_deps="src/event/quic/ngx_event_quic.h \ ++ src/event/quic/ngx_event_quic_transport.h \ ++ src/event/quic/ngx_event_quic_protection.h \ ++ src/event/quic/ngx_event_quic_connection.h \ ++ src/event/quic/ngx_event_quic_frames.h \ ++ src/event/quic/ngx_event_quic_connid.h \ ++ src/event/quic/ngx_event_quic_migration.h \ ++ src/event/quic/ngx_event_quic_streams.h \ ++ src/event/quic/ngx_event_quic_ssl.h \ ++ src/event/quic/ngx_event_quic_tokens.h \ ++ src/event/quic/ngx_event_quic_ack.h \ ++ src/event/quic/ngx_event_quic_output.h \ ++ src/event/quic/ngx_event_quic_socket.h" ++ ngx_module_srcs="src/event/quic/ngx_event_quic.c \ ++ src/event/quic/ngx_event_quic_transport.c \ ++ src/event/quic/ngx_event_quic_protection.c \ ++ src/event/quic/ngx_event_quic_frames.c \ ++ src/event/quic/ngx_event_quic_connid.c \ ++ src/event/quic/ngx_event_quic_migration.c \ ++ src/event/quic/ngx_event_quic_streams.c \ ++ src/event/quic/ngx_event_quic_ssl.c \ ++ src/event/quic/ngx_event_quic_tokens.c \ ++ src/event/quic/ngx_event_quic_ack.c \ ++ src/event/quic/ngx_event_quic_output.c \ ++ src/event/quic/ngx_event_quic_socket.c" ++ ++ ngx_module_libs= ++ ngx_module_link=YES ++ ngx_module_order= ++ ++ . auto/module ++ ++ if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then ++ ngx_module_type=CORE ++ ngx_module_name=ngx_quic_bpf_module ++ ngx_module_incs= ++ ngx_module_deps= ++ ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \ ++ src/event/quic/ngx_event_quic_bpf_code.c" ++ ngx_module_libs= ++ ngx_module_link=YES ++ ngx_module_order= ++ ++ . auto/module ++ ++ have=NGX_QUIC_BPF . auto/have ++ fi ++fi ++ ++ + if [ $USE_PCRE = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_regex_module +diff -r 67408b4a12c0 auto/options +--- a/auto/options Tue Dec 28 18:28:38 2021 +0300 ++++ b/auto/options Tue Jan 04 18:14:15 2022 -0500 +@@ -45,6 +45,8 @@ + + NGX_FILE_AIO=NO + ++QUIC_BPF=NO ++ + HTTP=YES + + NGX_HTTP_LOG_PATH= +@@ -59,6 +61,7 @@ + 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 @@ + + STREAM=NO + STREAM_SSL=NO ++STREAM_QUIC=NO + STREAM_REALIP=NO + STREAM_LIMIT_CONN=YES + STREAM_ACCESS=YES +@@ -149,6 +153,7 @@ + PCRE2=YES + + USE_OPENSSL=NO ++USE_OPENSSL_QUIC=NO + OPENSSL=NONE + + USE_ZLIB=NO +@@ -166,6 +171,8 @@ + NGX_GOOGLE_PERFTOOLS=NO + NGX_CPP_TEST=NO + ++SO_COOKIE_FOUND=NO ++ + NGX_LIBATOMIC=NO + + NGX_CPU_CACHE_LINE= +@@ -211,6 +218,8 @@ + + --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 @@ + + --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 @@ + --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 @@ + + --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 @@ + --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 67408b4a12c0 auto/os/linux +--- a/auto/os/linux Tue Dec 28 18:28:38 2021 +0300 ++++ b/auto/os/linux Tue Jan 04 18:14:15 2022 -0500 +@@ -233,3 +233,63 @@ + + + CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64" ++ ++ ++# 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 ++ #include " ++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" ++ngx_feature_name="NGX_HAVE_UDP_SEGMENT" ++ngx_feature_run=no ++ngx_feature_incs="#include ++ #include ++ #include " ++ngx_feature_path= ++ngx_feature_libs= ++ngx_feature_test="socklen_t optlen = sizeof(int); ++ int val; ++ getsockopt(0, SOL_UDP, UDP_SEGMENT, &val, &optlen)" ++. auto/feature +diff -r 67408b4a12c0 auto/sources +--- a/auto/sources Tue Dec 28 18:28:38 2021 +0300 ++++ b/auto/sources Tue Jan 04 18:14:15 2022 -0500 +@@ -83,13 +83,14 @@ + + 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 \ + src/event/ngx_event_posted.h \ + src/event/ngx_event_connect.h \ +- src/event/ngx_event_pipe.h" ++ src/event/ngx_event_pipe.h \ ++ src/event/ngx_event_udp.h" + + EVENT_SRCS="src/event/ngx_event.c \ + src/event/ngx_event_timer.c \ +diff -r 67408b4a12c0 src/core/nginx.c +--- a/src/core/nginx.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/core/nginx.c Tue Jan 04 18:14:15 2022 -0500 +@@ -680,6 +680,9 @@ + + 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 67408b4a12c0 src/core/ngx_bpf.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/core/ngx_bpf.c Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 src/core/ngx_bpf.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/core/ngx_bpf.h Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 src/core/ngx_connection.c +--- a/src/core/ngx_connection.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/core/ngx_connection.c Tue Jan 04 18:14:15 2022 -0500 +@@ -1037,6 +1037,12 @@ + 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 67408b4a12c0 src/core/ngx_connection.h +--- a/src/core/ngx_connection.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/core/ngx_connection.h Tue Jan 04 18:14:15 2022 -0500 +@@ -73,6 +73,7 @@ + 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 @@ + + 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 67408b4a12c0 src/core/ngx_core.h +--- a/src/core/ngx_core.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/core/ngx_core.h Tue Jan 04 18:14:15 2022 -0500 +@@ -27,6 +27,7 @@ + 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 @@ + #include + #if (NGX_OPENSSL) + #include ++#if (NGX_QUIC) ++#include ++#endif + #endif + #include + #include +@@ -91,6 +95,9 @@ + #include + #include + #include ++#if (NGX_HAVE_BPF) ++#include ++#endif + + + #define LF (u_char) '\n' +diff -r 67408b4a12c0 src/event/ngx_event.c +--- a/src/event/ngx_event.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/event/ngx_event.c Tue Jan 04 18:14:15 2022 -0500 +@@ -266,6 +266,18 @@ + ngx_int_t + ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) + { ++#if (NGX_QUIC) ++ ++ ngx_connection_t *c; ++ ++ c = rev->data; ++ ++ if (c->quic) { ++ return ngx_quic_handle_read_event(rev, flags); ++ } ++ ++#endif ++ + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { + + /* kqueue, epoll */ +@@ -336,9 +348,15 @@ + { + ngx_connection_t *c; + ++ c = wev->data; ++ ++#if (NGX_QUIC) ++ if (c->quic) { ++ return ngx_quic_handle_write_event(wev, lowat); ++ } ++#endif ++ + if (lowat) { +- c = wev->data; +- + if (ngx_send_lowat(c, lowat) == NGX_ERROR) { + return NGX_ERROR; + } +@@ -917,6 +935,12 @@ + { + int sndlowat; + ++#if (NGX_QUIC) ++ if (c->quic) { ++ return NGX_OK; ++ } ++#endif ++ + #if (NGX_HAVE_LOWAT_EVENT) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { +diff -r 67408b4a12c0 src/event/ngx_event.h +--- a/src/event/ngx_event.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/event/ngx_event.h Tue Jan 04 18:14:15 2022 -0500 +@@ -493,12 +493,6 @@ + + + void ngx_event_accept(ngx_event_t *ev); +-#if !(NGX_WIN32) +-void ngx_event_recvmsg(ngx_event_t *ev); +-void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, +- ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +-#endif +-void ngx_delete_udp_connection(void *data); + ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle); + ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle); + u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len); +@@ -528,6 +522,7 @@ + + #include + #include ++#include + + #if (NGX_WIN32) + #include +diff -r 67408b4a12c0 src/event/ngx_event_openssl.c +--- a/src/event/ngx_event_openssl.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/event/ngx_event_openssl.c Tue Jan 04 18:14:15 2022 -0500 +@@ -3146,6 +3146,13 @@ + 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 67408b4a12c0 src/event/ngx_event_openssl.h +--- a/src/event/ngx_event_openssl.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/event/ngx_event_openssl.h Tue Jan 04 18:14:15 2022 -0500 +@@ -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 +diff -r 67408b4a12c0 src/event/ngx_event_udp.c +--- a/src/event/ngx_event_udp.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/event/ngx_event_udp.c Tue Jan 04 18:14:15 2022 -0500 +@@ -12,52 +12,37 @@ + + #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); +-static ngx_int_t ngx_insert_udp_connection(ngx_connection_t *c); ++static ngx_int_t ngx_create_udp_connection(ngx_connection_t *c); + static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls, +- struct sockaddr *sockaddr, socklen_t socklen, +- struct sockaddr *local_sockaddr, socklen_t local_socklen); ++ ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); + + + void + ngx_event_recvmsg(ngx_event_t *ev) + { ++ size_t len; + ssize_t n; ++ ngx_str_t key; + ngx_buf_t buf; + ngx_log_t *log; + ngx_err_t err; +- socklen_t socklen, local_socklen; ++ socklen_t 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_udp_dgram_t dgram; ++ struct sockaddr *local_sockaddr; + ngx_listening_t *ls; + ngx_event_conf_t *ecf; + ngx_connection_t *c, *lc; + static u_char buffer[65535]; + +-#if (NGX_HAVE_MSGHDR_MSG_CONTROL) +- +-#if (NGX_HAVE_IP_RECVDSTADDR) +- u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))]; +-#elif (NGX_HAVE_IP_PKTINFO) +- u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))]; +-#endif +- +-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) +- u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; +-#endif +- ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; + #endif + + if (ev->timedout) { +@@ -92,25 +77,13 @@ + msg.msg_iov = iov; + msg.msg_iovlen = 1; + +-#if (NGX_HAVE_MSGHDR_MSG_CONTROL) +- ++#if (NGX_HAVE_ADDRINFO_CMSG) + if (ls->wildcard) { ++ msg.msg_control = &msg_control; ++ msg.msg_controllen = sizeof(msg_control); + +-#if (NGX_HAVE_IP_RECVDSTADDR || NGX_HAVE_IP_PKTINFO) +- if (ls->sockaddr->sa_family == AF_INET) { +- msg.msg_control = &msg_control; +- msg.msg_controllen = sizeof(msg_control); +- } +-#endif +- +-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) +- if (ls->sockaddr->sa_family == AF_INET6) { +- msg.msg_control = &msg_control6; +- msg.msg_controllen = sizeof(msg_control6); +- } +-#endif +- } +- ++ ngx_memzero(&msg_control, sizeof(msg_control)); ++ } + #endif + + n = recvmsg(lc->fd, &msg, 0); +@@ -129,7 +102,7 @@ + return; + } + +-#if (NGX_HAVE_MSGHDR_MSG_CONTROL) ++#if (NGX_HAVE_ADDRINFO_CMSG) + if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { + ngx_log_error(NGX_LOG_ALERT, ev->log, 0, + "recvmsg() truncated data"); +@@ -137,21 +110,21 @@ + } + #endif + +- sockaddr = msg.msg_name; +- socklen = msg.msg_namelen; ++ dgram.sockaddr = msg.msg_name; ++ dgram.socklen = msg.msg_namelen; + +- if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { +- socklen = sizeof(ngx_sockaddr_t); ++ if (dgram.socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { ++ dgram.socklen = sizeof(ngx_sockaddr_t); + } + +- if (socklen == 0) { ++ if (dgram.socklen == 0) { + + /* + * on Linux recvmsg() returns zero msg_namelen + * when receiving packets from unbound AF_UNIX sockets + */ + +- socklen = sizeof(struct sockaddr); ++ dgram.socklen = sizeof(struct sockaddr); + ngx_memzero(&sa, sizeof(struct sockaddr)); + sa.sockaddr.sa_family = ls->sockaddr->sa_family; + } +@@ -159,7 +132,7 @@ + local_sockaddr = ls->sockaddr; + local_socklen = ls->socklen; + +-#if (NGX_HAVE_MSGHDR_MSG_CONTROL) ++#if (NGX_HAVE_ADDRINFO_CMSG) + + if (ls->wildcard) { + struct cmsghdr *cmsg; +@@ -171,66 +144,43 @@ + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { +- +-#if (NGX_HAVE_IP_RECVDSTADDR) +- +- if (cmsg->cmsg_level == IPPROTO_IP +- && cmsg->cmsg_type == IP_RECVDSTADDR +- && local_sockaddr->sa_family == AF_INET) +- { +- struct in_addr *addr; +- struct sockaddr_in *sin; +- +- addr = (struct in_addr *) CMSG_DATA(cmsg); +- sin = (struct sockaddr_in *) local_sockaddr; +- sin->sin_addr = *addr; +- ++ if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { + break; + } +- +-#elif (NGX_HAVE_IP_PKTINFO) +- +- if (cmsg->cmsg_level == IPPROTO_IP +- && cmsg->cmsg_type == IP_PKTINFO +- && local_sockaddr->sa_family == AF_INET) +- { +- struct in_pktinfo *pkt; +- struct sockaddr_in *sin; +- +- pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); +- sin = (struct sockaddr_in *) local_sockaddr; +- sin->sin_addr = pkt->ipi_addr; +- +- break; +- } +- +-#endif +- +-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) +- +- if (cmsg->cmsg_level == IPPROTO_IPV6 +- && cmsg->cmsg_type == IPV6_PKTINFO +- && local_sockaddr->sa_family == AF_INET6) +- { +- struct in6_pktinfo *pkt6; +- struct sockaddr_in6 *sin6; +- +- pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); +- sin6 = (struct sockaddr_in6 *) local_sockaddr; +- sin6->sin6_addr = pkt6->ipi6_addr; +- +- break; +- } +- +-#endif +- + } + } + + #endif + +- c = ngx_lookup_udp_connection(ls, sockaddr, socklen, local_sockaddr, +- local_socklen); ++ key.data = (u_char *) dgram.sockaddr; ++ key.len = dgram.socklen; ++ ++#if (NGX_HAVE_UNIX_DOMAIN) ++ ++ if (dgram.sockaddr->sa_family == AF_UNIX) { ++ struct sockaddr_un *saun = (struct sockaddr_un *) dgram.sockaddr; ++ ++ if (dgram.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"); ++ key.len = 0; ++ } ++ } ++ ++#endif ++ ++#if (NGX_QUIC) ++ if (ls->quic) { ++ if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { ++ goto next; ++ } ++ } ++#endif ++ ++ c = ngx_lookup_udp_connection(ls, &key, local_sockaddr, local_socklen); + + if (c) { + +@@ -252,10 +202,14 @@ + + buf.pos = buffer; + buf.last = buffer + n; ++ buf.start = buf.pos; ++ buf.end = buffer + sizeof(buffer); + + rev = c->read; + +- c->udp->buffer = &buf; ++ dgram.buffer = &buf; ++ ++ c->udp->dgram = &dgram; + + rev->ready = 1; + rev->active = 0; +@@ -263,7 +217,7 @@ + rev->handler(rev); + + if (c->udp) { +- c->udp->buffer = NULL; ++ c->udp->dgram = NULL; + } + + rev->ready = 0; +@@ -286,7 +240,7 @@ + + c->shared = 1; + c->type = SOCK_DGRAM; +- c->socklen = socklen; ++ c->socklen = dgram.socklen; + + #if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +@@ -298,13 +252,21 @@ + return; + } + +- c->sockaddr = ngx_palloc(c->pool, socklen); ++ len = dgram.socklen; ++ ++#if (NGX_QUIC) ++ if (ls->quic) { ++ len = NGX_SOCKADDRLEN; ++ } ++#endif ++ ++ c->sockaddr = ngx_palloc(c->pool, len); + if (c->sockaddr == NULL) { + ngx_close_accepted_udp_connection(c); + return; + } + +- ngx_memcpy(c->sockaddr, sockaddr, socklen); ++ ngx_memcpy(c->sockaddr, dgram.sockaddr, dgram.socklen); + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { +@@ -405,7 +367,7 @@ + } + #endif + +- if (ngx_insert_udp_connection(c) != NGX_OK) { ++ if (ngx_create_udp_connection(c) != NGX_OK) { + ngx_close_accepted_udp_connection(c); + return; + } +@@ -448,17 +410,17 @@ + ssize_t n; + ngx_buf_t *b; + +- if (c->udp == NULL || c->udp->buffer == NULL) { ++ if (c->udp == NULL || c->udp->dgram == NULL) { + return NGX_AGAIN; + } + +- b = c->udp->buffer; ++ b = c->udp->dgram->buffer; + + n = ngx_min(b->last - b->pos, (ssize_t) size); + + ngx_memcpy(buf, b->pos, n); + +- c->udp->buffer = NULL; ++ c->udp->dgram = NULL; + + c->read->ready = 0; + c->read->active = 1; +@@ -494,8 +456,8 @@ + udpt = (ngx_udp_connection_t *) temp; + ct = udpt->connection; + +- rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, +- ct->sockaddr, ct->socklen, 1); ++ rc = ngx_memn2cmp(udp->key.data, udpt->key.data, ++ udp->key.len, udpt->key.len); + + if (rc == 0 && c->listening->wildcard) { + rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, +@@ -521,12 +483,18 @@ + + + static ngx_int_t +-ngx_insert_udp_connection(ngx_connection_t *c) ++ngx_create_udp_connection(ngx_connection_t *c) + { +- uint32_t hash; ++ ngx_str_t key; + ngx_pool_cleanup_t *cln; + ngx_udp_connection_t *udp; + ++#if (NGX_QUIC) ++ if (c->listening->quic) { ++ return NGX_OK; ++ } ++#endif ++ + if (c->udp) { + return NGX_OK; + } +@@ -536,19 +504,6 @@ + return NGX_ERROR; + } + +- udp->connection = c; +- +- ngx_crc32_init(hash); +- ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen); +- +- if (c->listening->wildcard) { +- ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen); +- } +- +- ngx_crc32_final(hash); +- +- udp->node.key = hash; +- + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + return NGX_ERROR; +@@ -557,7 +512,10 @@ + cln->data = c; + cln->handler = ngx_delete_udp_connection; + +- ngx_rbtree_insert(&c->listening->rbtree, &udp->node); ++ key.data = (u_char *) c->sockaddr; ++ key.len = c->socklen; ++ ++ ngx_insert_udp_connection(c, udp, &key); + + c->udp = udp; + +@@ -566,6 +524,30 @@ + + + void ++ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, ++ ngx_str_t *key) ++{ ++ uint32_t hash; ++ ++ ngx_crc32_init(hash); ++ ++ ngx_crc32_update(&hash, key->data, key->len); ++ ++ if (c->listening->wildcard) { ++ ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen); ++ } ++ ++ ngx_crc32_final(hash); ++ ++ udp->connection = c; ++ udp->key = *key; ++ udp->node.key = hash; ++ ++ ngx_rbtree_insert(&c->listening->rbtree, &udp->node); ++} ++ ++ ++void + ngx_delete_udp_connection(void *data) + { + ngx_connection_t *c = data; +@@ -581,8 +563,8 @@ + + + static ngx_connection_t * +-ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr, +- socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen) ++ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, ++ struct sockaddr *local_sockaddr, socklen_t local_socklen) + { + uint32_t hash; + ngx_int_t rc; +@@ -590,27 +572,15 @@ + ngx_rbtree_node_t *node, *sentinel; + ngx_udp_connection_t *udp; + +-#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"); +- return NULL; +- } ++ if (key->len == 0) { ++ return NULL; + } + +-#endif +- + node = ls->rbtree.root; + sentinel = ls->rbtree.sentinel; + + ngx_crc32_init(hash); +- ngx_crc32_update(&hash, (u_char *) sockaddr, socklen); ++ ngx_crc32_update(&hash, key->data, key->len); + + if (ls->wildcard) { + ngx_crc32_update(&hash, (u_char *) local_sockaddr, local_socklen); +@@ -636,8 +606,7 @@ + + c = udp->connection; + +- rc = ngx_cmp_sockaddr(sockaddr, socklen, +- c->sockaddr, c->socklen, 1); ++ rc = ngx_memn2cmp(key->data, udp->key.data, key->len, udp->key.len); + + if (rc == 0 && ls->wildcard) { + rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, +@@ -645,6 +614,13 @@ + } + + if (rc == 0) { ++ ++#if (NGX_QUIC) ++ if (ls->quic && c->udp != udp) { ++ c->udp = udp; ++ } ++#endif ++ + return c; + } + +diff -r 67408b4a12c0 src/event/ngx_event_udp.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/ngx_event_udp.h Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,76 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_UDP_H_INCLUDED_ ++#define _NGX_EVENT_UDP_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++#if !(NGX_WIN32) ++ ++#if ((NGX_HAVE_MSGHDR_MSG_CONTROL) \ ++ && (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR \ ++ || NGX_HAVE_IP_PKTINFO \ ++ || (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO))) ++#define NGX_HAVE_ADDRINFO_CMSG 1 ++#endif ++ ++ ++typedef struct { ++ ngx_buf_t *buffer; ++ struct sockaddr *sockaddr; ++ socklen_t socklen; ++} ngx_udp_dgram_t; ++ ++ ++struct ngx_udp_connection_s { ++ ngx_rbtree_node_t node; ++ ngx_connection_t *connection; ++ ngx_str_t key; ++ ngx_udp_dgram_t *dgram; ++}; ++ ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ ++typedef union { ++#if (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR) ++ struct in_addr addr; ++#endif ++ ++#if (NGX_HAVE_IP_PKTINFO) ++ struct in_pktinfo pkt; ++#endif ++ ++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) ++ struct in6_pktinfo pkt6; ++#endif ++} ngx_addrinfo_t; ++ ++size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, ++ struct sockaddr *local_sockaddr); ++ngx_int_t ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg, ++ struct sockaddr *local_sockaddr); ++ ++#endif ++ ++ ++void ngx_event_recvmsg(ngx_event_t *ev); ++ssize_t ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags); ++void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, ++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); ++void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, ++ ngx_str_t *key); ++ ++#endif ++ ++void ngx_delete_udp_connection(void *data); ++ ++ ++#endif /* _NGX_EVENT_UDP_H_INCLUDED_ */ +diff -r 67408b4a12c0 src/event/quic/bpf/bpfgen.sh +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/bpf/bpfgen.sh Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 src/event/quic/bpf/makefile +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/bpf/makefile Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,1489 @@ ++ ++/* ++ * 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 ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); ++static void ngx_quic_close_timer_handler(ngx_event_t *ev); ++ ++static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, ++ ngx_quic_conf_t *conf); ++static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, ++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c, ++ ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, ++ ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, ++ ngx_quic_header_t *pkt); ++ ++static void ngx_quic_push_handler(ngx_event_t *ev); ++ ++ ++static ngx_core_module_t ngx_quic_module_ctx = { ++ ngx_string("quic"), ++ NULL, ++ NULL ++}; ++ ++ ++ngx_module_t ngx_quic_module = { ++ NGX_MODULE_V1, ++ &ngx_quic_module_ctx, /* module context */ ++ NULL, /* module directives */ ++ NGX_CORE_MODULE, /* module type */ ++ NULL, /* init master */ ++ NULL, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ ++ ++#if (NGX_DEBUG) ++ ++void ++ngx_quic_connstate_dbg(ngx_connection_t *c) ++{ ++ u_char *p, *last; ++ ngx_quic_connection_t *qc; ++ u_char buf[NGX_MAX_ERROR_STR]; ++ ++ p = buf; ++ last = p + sizeof(buf); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ p = ngx_slprintf(p, last, "state:"); ++ ++ if (qc) { ++ ++ if (qc->error) { ++ p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); ++ p = ngx_slprintf(p, last, " error:%ui", qc->error); ++ ++ if (qc->error_reason) { ++ p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); ++ } ++ } ++ ++ p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : ""); ++ p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : ""); ++ p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); ++ p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); ++ ++ } else { ++ p = ngx_slprintf(p, last, " early"); ++ } ++ ++ if (c->read->timer_set) { ++ p = ngx_slprintf(p, last, ++ qc && qc->send_timer_set ? " send:%M" : " read:%M", ++ c->read->timer.key - ngx_current_msec); ++ } ++ ++ if (qc) { ++ ++ if (qc->push.timer_set) { ++ p = ngx_slprintf(p, last, " push:%M", ++ qc->push.timer.key - ngx_current_msec); ++ } ++ ++ if (qc->pto.timer_set) { ++ p = ngx_slprintf(p, last, " pto:%M", ++ qc->pto.timer.key - ngx_current_msec); ++ } ++ ++ if (qc->close.timer_set) { ++ p = ngx_slprintf(p, last, " close:%M", ++ qc->close.timer.key - ngx_current_msec); ++ } ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic %*s", p - buf, buf); ++} ++ ++#endif ++ ++ ++ngx_int_t ++ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) ++{ ++ ngx_str_t scid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ scid.data = qc->socket->cid->id; ++ scid.len = qc->socket->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 == NGX_DECLINED ? NGX_DONE : NGX_ERROR); ++ return; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc == NULL) { ++ ngx_quic_close_connection(c, NGX_DONE); ++ return; ++ } ++ ++ 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_quic_keys_new(c->pool); ++ if (qc->keys == NULL) { ++ return NULL; ++ } ++ ++ qc->version = pkt->version; ++ ++ ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ++ ngx_quic_rbtree_insert_stream); ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ngx_queue_init(&qc->send_ctx[i].frames); ++ ngx_queue_init(&qc->send_ctx[i].sending); ++ ngx_queue_init(&qc->send_ctx[i].sent); ++ qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; ++ qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; ++ qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; ++ qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ ++ qc->send_ctx[0].level = ssl_encryption_initial; ++ qc->send_ctx[1].level = ssl_encryption_handshake; ++ qc->send_ctx[2].level = ssl_encryption_application; ++ ++ ngx_queue_init(&qc->free_frames); ++ ++ qc->avg_rtt = NGX_QUIC_INITIAL_RTT; ++ qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; ++ qc->min_rtt = NGX_TIMER_INFINITE; ++ qc->first_rtt = NGX_TIMER_INFINITE; ++ ++ /* ++ * qc->latest_rtt = 0 ++ */ ++ ++ qc->pto.log = c->log; ++ qc->pto.data = c; ++ qc->pto.handler = ngx_quic_pto_handler; ++ qc->pto.cancelable = 1; ++ ++ qc->push.log = c->log; ++ qc->push.data = c; ++ qc->push.handler = ngx_quic_push_handler; ++ qc->push.cancelable = 1; ++ ++ qc->path_validation.log = c->log; ++ qc->path_validation.data = c; ++ qc->path_validation.handler = ngx_quic_path_validation_handler; ++ qc->path_validation.cancelable = 1; ++ ++ qc->conf = conf; ++ ++ if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { ++ return NULL; ++ } ++ ++ ctp = &qc->ctp; ++ ++ /* defaults to be used before actual client parameters are received */ ++ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); ++ ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ++ ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; ++ ctp->active_connection_id_limit = 2; ++ ++ ngx_queue_init(&qc->streams.uninitialized); ++ ++ 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(c->pool, qc->keys, &pkt->dcid, ++ qc->version) ++ != NGX_OK) ++ { ++ return NULL; ++ } ++ ++ qc->validated = pkt->validated; ++ ++ if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { ++ return NULL; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic connection created"); ++ ++ return qc; ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ u_char *tail, ch; ++ ngx_uint_t i; ++ ngx_queue_t *q; ++ ngx_quic_client_id_t *cid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* A stateless reset uses an entire UDP datagram */ ++ if (!pkt->first) { ++ return NGX_DECLINED; ++ } ++ ++ tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; ++ ++ for (q = ngx_queue_head(&qc->client_ids); ++ q != ngx_queue_sentinel(&qc->client_ids); ++ q = ngx_queue_next(q)) ++ { ++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); ++ ++ if (cid->seqnum == 0 || cid->refcnt == 0) { ++ /* ++ * No stateless reset token in initial connection id. ++ * Don't accept a token from an unused connection id. ++ */ ++ continue; ++ } ++ ++ /* constant time comparison */ ++ ++ for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { ++ ch |= tail[i] ^ cid->sr_token[i]; ++ } ++ ++ if (ch == 0) { ++ return NGX_OK; ++ } ++ } ++ ++ return NGX_DECLINED; ++} ++ ++ ++static void ++ngx_quic_input_handler(ngx_event_t *rev) ++{ ++ ngx_int_t rc; ++ ngx_buf_t *b; ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); ++ ++ c = rev->data; ++ qc = ngx_quic_get_connection(c); ++ ++ c->log->action = "handling quic input"; ++ ++ if (rev->timedout) { ++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, ++ "quic client timed out"); ++ ngx_quic_close_connection(c, NGX_DONE); ++ return; ++ } ++ ++ if (c->close) { ++ qc->error_reason = "graceful shutdown"; ++ ngx_quic_close_connection(c, NGX_OK); ++ return; ++ } ++ ++ if (!rev->ready) { ++ if (qc->closing) { ++ ngx_quic_close_connection(c, NGX_OK); ++ ++ } else if (qc->shutdown) { ++ ngx_quic_shutdown_quic(c); ++ } ++ ++ return; ++ } ++ ++ b = c->udp->dgram->buffer; ++ ++ rc = ngx_quic_handle_datagram(c, b, NULL); ++ ++ if (rc == NGX_ERROR) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ ++ if (rc == NGX_DECLINED) { ++ 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_pool_t *pool; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_close_connection rc:%i", rc); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc == NULL) { ++ if (rc == NGX_ERROR) { ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic close connection early error"); ++ } ++ ++ } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { ++ return; ++ } ++ ++ 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); ++} ++ ++ ++static ngx_int_t ++ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ++{ ++ ngx_uint_t i; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (!qc->closing) { ++ ++ /* drop packets from retransmit queues, no ack is expected */ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ngx_quic_free_frames(c, &qc->send_ctx[i].sent); ++ } ++ ++ if (rc == NGX_DONE) { ++ ++ /* ++ * RFC 9000, 10.1. Idle Timeout ++ * ++ * If a max_idle_timeout is specified by either endpoint in its ++ * transport parameters (Section 18.2), the connection is silently ++ * closed and its state is discarded when it remains idle ++ */ ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic closing %s connection", ++ qc->draining ? "drained" : "idle"); ++ ++ } else { ++ ++ /* ++ * RFC 9000, 10.2. Immediate Close ++ * ++ * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) ++ * to terminate the connection immediately. ++ */ ++ ++ qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) ++ : ssl_encryption_initial; ++ ++ if (rc == NGX_OK) { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic immediate close drain:%d", ++ qc->draining); ++ ++ qc->close.log = c->log; ++ qc->close.data = c; ++ qc->close.handler = ngx_quic_close_timer_handler; ++ qc->close.cancelable = 1; ++ ++ ctx = ngx_quic_get_send_ctx(qc, qc->error_level); ++ ++ ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); ++ ++ qc->error = NGX_QUIC_ERR_NO_ERROR; ++ ++ } else { ++ if (qc->error == 0 && !qc->error_app) { ++ qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; ++ } ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic immediate close due to %s error: %ui %s", ++ qc->error_app ? "app " : "", qc->error, ++ qc->error_reason ? qc->error_reason : ""); ++ } ++ ++ (void) ngx_quic_send_cc(c); ++ ++ if (qc->error_level == ssl_encryption_handshake) { ++ /* for clients that might not have handshake keys */ ++ qc->error_level = ssl_encryption_initial; ++ (void) ngx_quic_send_cc(c); ++ } ++ } ++ ++ qc->closing = 1; ++ } ++ ++ if (rc == NGX_ERROR && qc->close.timer_set) { ++ /* do not wait for timer in case of fatal error */ ++ ngx_del_timer(&qc->close); ++ } ++ ++ if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { ++ return NGX_AGAIN; ++ } ++ ++ if (qc->push.timer_set) { ++ ngx_del_timer(&qc->push); ++ } ++ ++ if (qc->pto.timer_set) { ++ ngx_del_timer(&qc->pto); ++ } ++ ++ if (qc->path_validation.timer_set) { ++ ngx_del_timer(&qc->path_validation); ++ } ++ ++ if (qc->push.posted) { ++ ngx_delete_posted_event(&qc->push); ++ } ++ ++ if (qc->close.timer_set) { ++ return NGX_AGAIN; ++ } ++ ++ ngx_quic_close_sockets(c); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic part of connection is terminated"); ++ ++ /* may be tested from SSL callback during SSL shutdown */ ++ c->udp = NULL; ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, ++ const char *reason) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ qc->error = err; ++ qc->error_reason = reason; ++ qc->error_app = 1; ++ qc->error_ftype = 0; ++ ++ ngx_quic_close_connection(c, NGX_ERROR); ++} ++ ++ ++void ++ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, ++ const char *reason) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ qc->shutdown = 1; ++ qc->shutdown_code = err; ++ qc->shutdown_reason = reason; ++ ++ ngx_quic_shutdown_quic(c); ++} ++ ++ ++static void ++ngx_quic_close_timer_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close timer"); ++ ++ c = ev->data; ++ ngx_quic_close_connection(c, NGX_DONE); ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, ++ ngx_quic_conf_t *conf) ++{ ++ size_t size; ++ u_char *p, *start; ++ ngx_int_t rc; ++ ngx_uint_t good; ++ ngx_quic_header_t pkt; ++ ngx_quic_connection_t *qc; ++ ++ good = 0; ++ ++ 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.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 %s done decr:%d pn:%L perr:%ui rc:%i", ++ ngx_quic_level_name(pkt.level), pkt.decrypted, ++ pkt.pn, pkt.error, rc); ++ } else { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet done parse failed rc:%i", rc); ++ } ++#endif ++ ++ if (rc == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_DONE) { ++ /* stop further processing */ ++ return NGX_DECLINED; ++ } ++ ++ if (rc == NGX_OK) { ++ good = 1; ++ } ++ ++ /* 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_DECLINED; ++ } ++ ++ 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_connection_t *qc; ++ ++ c->log->action = "parsing quic packet"; ++ ++ rc = ngx_quic_parse_packet(pkt); ++ ++ if (rc == NGX_DECLINED || rc == NGX_ERROR) { ++ return rc; ++ } ++ ++ pkt->parsed = 1; ++ ++ c->log->action = "processing 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) { ++ if (ngx_quic_find_path(c, c->udp->dgram->sockaddr, ++ c->udp->dgram->socklen) ++ == NULL) ++ { ++ /* packet comes from unknown path, possibly migration */ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic too early migration attempt"); ++ return NGX_DECLINED; ++ } ++ } ++ ++ 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) { ++ return NGX_ERROR; ++ } ++ ++ c->log->action = "processing initial packet"; ++ ++ if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { ++ /* RFC 9000, 7.2. Negotiating Connection IDs */ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic too short dcid in initial" ++ " packet: len:%i", pkt->dcid.len); ++ return NGX_ERROR; ++ } ++ ++ /* process retry and initialize connection IDs */ ++ ++ if (pkt->token.len) { ++ ++ rc = ngx_quic_validate_token(c, conf->av_token_key, pkt); ++ ++ if (rc == NGX_ERROR) { ++ /* internal error */ ++ return NGX_ERROR; ++ ++ } else if (rc == NGX_ABORT) { ++ /* token cannot be decrypted */ ++ return ngx_quic_send_early_cc(c, pkt, ++ NGX_QUIC_ERR_INVALID_TOKEN, ++ "cannot decrypt token"); ++ } else if (rc == NGX_DECLINED) { ++ /* token is invalid */ ++ ++ if (pkt->retried) { ++ /* invalid address validation token */ ++ return ngx_quic_send_early_cc(c, pkt, ++ NGX_QUIC_ERR_INVALID_TOKEN, ++ "invalid address validation token"); ++ } else if (conf->retry) { ++ /* invalid NEW_TOKEN */ ++ return ngx_quic_send_retry(c, conf, pkt); ++ } ++ } ++ ++ /* NGX_OK */ ++ ++ } else if (conf->retry) { ++ return ngx_quic_send_retry(c, conf, pkt); ++ ++ } else { ++ pkt->odcid = pkt->dcid; ++ } ++ ++ if (ngx_terminate || ngx_exiting) { ++ if (conf->retry) { ++ return ngx_quic_send_retry(c, conf, pkt); ++ } ++ ++ return NGX_ERROR; ++ } ++ ++ c->log->action = "creating quic connection"; ++ ++ qc = ngx_quic_new_connection(c, conf, pkt); ++ if (qc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ return ngx_quic_handle_payload(c, pkt); ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ ngx_int_t rc; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ qc->error = 0; ++ qc->error_reason = 0; ++ ++ c->log->action = "decrypting packet"; ++ ++ if (!ngx_quic_keys_available(qc->keys, pkt->level)) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic no %s keys, ignoring packet", ++ ngx_quic_level_name(pkt->level)); ++ return NGX_DECLINED; ++ } ++ ++#if !defined (OPENSSL_IS_BORINGSSL) ++ /* OpenSSL provides read keys for an application level before it's ready */ ++ ++ if (pkt->level == ssl_encryption_application ++ && SSL_quic_read_level(c->ssl->connection) ++ < ssl_encryption_application) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic no %s keys ready, ignoring packet", ++ ngx_quic_level_name(pkt->level)); ++ return NGX_DECLINED; ++ } ++#endif ++ ++ pkt->keys = qc->keys; ++ pkt->key_phase = qc->key_phase; ++ pkt->plaintext = buf; ++ ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ ++ rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); ++ if (rc != NGX_OK) { ++ qc->error = pkt->error; ++ qc->error_reason = "failed to decrypt packet"; ++ return rc; ++ } ++ ++ pkt->decrypted = 1; ++ ++ if (pkt->first) { ++ if (ngx_quic_update_paths(c, pkt) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ 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->socket->path->state != NGX_QUIC_PATH_VALIDATED) { ++ qc->socket->path->state = NGX_QUIC_PATH_VALIDATED; ++ qc->socket->path->limited = 0; ++ 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_chain(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_congestion_ack(c, f); ++ 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_socket_t *qsock; ++ 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, &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); ++ } ++ ++ qsock = ngx_quic_get_socket(c); ++ ++ if (qsock != qc->socket) { ++ ++ if (qsock->path != qc->socket->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; ++ } ++ } ++ /* ++ * else: packet arrived via non-default socket; ++ * no reason to change active path ++ */ ++ } ++ ++ 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 timer"); ++ ++ c = ev->data; ++ ++ if (ngx_quic_output(c) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ ++ ngx_quic_connstate_dbg(c); ++} ++ ++ ++void ++ngx_quic_shutdown_quic(ngx_connection_t *c) ++{ ++ ngx_rbtree_t *tree; ++ ngx_rbtree_node_t *node; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc->closing) { ++ return; ++ } ++ ++ tree = &qc->streams.tree; ++ ++ if (tree->root != tree->sentinel) { ++ for (node = ngx_rbtree_min(tree->root, tree->sentinel); ++ node; ++ node = ngx_rbtree_next(tree, node)) ++ { ++ qs = (ngx_quic_stream_t *) node; ++ ++ if (!qs->cancelable) { ++ return; ++ } ++ } ++ } ++ ++ ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); ++} ++ ++ ++uint32_t ++ngx_quic_version(ngx_connection_t *c) ++{ ++ uint32_t version; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ version = qc->version; ++ ++ return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,87 @@ ++ ++/* ++ * 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 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_int_t stream_close_code; ++ ngx_int_t stream_reject_code_uni; ++ ngx_int_t stream_reject_code_bidi; ++ ++ u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; ++ u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; ++} ngx_quic_conf_t; ++ ++ ++struct ngx_quic_stream_s { ++ ngx_rbtree_node_t node; ++ ngx_queue_t queue; ++ ngx_connection_t *parent; ++ ngx_connection_t *connection; ++ uint64_t id; ++ uint64_t acked; ++ uint64_t send_max_data; ++ uint64_t recv_max_data; ++ uint64_t recv_offset; ++ uint64_t recv_window; ++ uint64_t recv_last; ++ uint64_t final_size; ++ ngx_chain_t *in; ++ ngx_chain_t *out; ++ ngx_uint_t cancelable; /* unsigned cancelable:1; */ ++}; ++ ++ ++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); ++uint32_t ngx_quic_version(ngx_connection_t *c); ++ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags); ++ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat); ++ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, ++ ngx_str_t *dcid); ++ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, ++ ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); ++ ++#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,1190 @@ ++ ++/* ++ * 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 * (1 << qc->ctp.ack_delay_exponent) / 1000; ++ ++ if (c->ssl->handshaked) { ++ ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); ++ } ++ ++ adjusted_rtt = latest_rtt; ++ ++ if (qc->min_rtt + ack_delay < latest_rtt) { ++ adjusted_rtt -= ack_delay; ++ } ++ ++ qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3); ++ rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); ++ qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2); ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic rtt sample latest:%M min:%M avg:%M var:%M", ++ latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st) ++{ ++ ngx_uint_t found; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ st->max_pn = NGX_TIMER_INFINITE; ++ found = 0; ++ ++ q = ngx_queue_head(&ctx->sent); ++ ++ while (q != ngx_queue_sentinel(&ctx->sent)) { ++ ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ q = ngx_queue_next(q); ++ ++ if (f->pnum > max) { ++ break; ++ } ++ ++ if (f->pnum >= min) { ++ ngx_quic_congestion_ack(c, f); ++ ++ switch (f->type) { ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ ngx_quic_handle_stream_ack(c, f); ++ break; ++ } ++ ++ if (f->pnum == max) { ++ st->max_pn = f->last; ++ } ++ ++ /* save earliest and latest send times of frames ack'ed */ ++ if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) { ++ st->oldest = f->last; ++ } ++ ++ if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) { ++ st->newest = f->last; ++ } ++ ++ ngx_queue_remove(&f->queue); ++ ngx_quic_free_frame(c, f); ++ found = 1; ++ } ++ } ++ ++ if (!found) { ++ ++ if (max < ctx->pnum) { ++ /* duplicate ACK or ACK for non-ack-eliciting frame */ ++ return NGX_OK; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic ACK for the packet not sent"); ++ ++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ qc->error_ftype = NGX_QUIC_FT_ACK; ++ qc->error_reason = "unknown packet number"; ++ ++ return NGX_ERROR; ++ } ++ ++ if (!qc->push.timer_set) { ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++ ++ qc->pto_count = 0; ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ++{ ++ ngx_uint_t blocked; ++ ngx_msec_t timer; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ if (f->plen == 0) { ++ return; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ ++ blocked = (cg->in_flight >= cg->window) ? 1 : 0; ++ ++ cg->in_flight -= f->plen; ++ ++ timer = f->last - cg->recovery_start; ++ ++ if ((ngx_msec_int_t) timer <= 0) { ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion ack recovery win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++ goto done; ++ } ++ ++ if (cg->window < cg->ssthresh) { ++ cg->window += f->plen; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion slow start win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++ } else { ++ cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion avoidance win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ } ++ ++ /* prevent recovery_start from wrapping */ ++ ++ timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; ++ ++ if ((ngx_msec_int_t) timer < 0) { ++ cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; ++ } ++ ++done: ++ ++ if (blocked && cg->in_flight < cg->window) { ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++} ++ ++ ++static void ++ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t pn) ++{ ++ uint64_t base; ++ ngx_uint_t i, smallest, largest; ++ ngx_quic_ack_range_t *r; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" ++ " fr:%uL nranges:%ui", pn, ctx->largest_range, ++ ctx->first_range, ctx->nranges); ++ ++ base = ctx->largest_range; ++ ++ if (base == NGX_QUIC_UNSET_PN) { ++ return; ++ } ++ ++ if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { ++ ctx->pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ ++ largest = base; ++ smallest = largest - ctx->first_range; ++ ++ if (pn >= largest) { ++ ctx->largest_range = NGX_QUIC_UNSET_PN; ++ ctx->first_range = 0; ++ ctx->nranges = 0; ++ return; ++ } ++ ++ if (pn >= smallest) { ++ ctx->first_range = largest - pn - 1; ++ ctx->nranges = 0; ++ return; ++ } ++ ++ for (i = 0; i < ctx->nranges; i++) { ++ r = &ctx->ranges[i]; ++ ++ largest = smallest - r->gap - 2; ++ smallest = largest - r->range; ++ ++ if (pn >= largest) { ++ ctx->nranges = i; ++ return; ++ } ++ if (pn >= smallest) { ++ r->range = largest - pn - 1; ++ ctx->nranges = i + 1; ++ return; ++ } ++ } ++} ++ ++ ++static ngx_int_t ++ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st) ++{ ++ ngx_uint_t i, nlost; ++ ngx_msec_t now, wait, thr, oldest, newest; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *start; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ now = ngx_current_msec; ++ thr = ngx_quic_lost_threshold(qc); ++ ++ /* send time of lost packets across all send contexts */ ++ oldest = NGX_TIMER_INFINITE; ++ newest = NGX_TIMER_INFINITE; ++ ++ nlost = 0; ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ++ ctx = &qc->send_ctx[i]; ++ ++ if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { ++ continue; ++ } ++ ++ while (!ngx_queue_empty(&ctx->sent)) { ++ ++ q = ngx_queue_head(&ctx->sent); ++ start = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (start->pnum > ctx->largest_ack) { ++ break; ++ } ++ ++ wait = start->last + thr - now; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", ++ start->pnum, thr, (ngx_int_t) wait, start->level); ++ ++ if ((ngx_msec_int_t) wait > 0 ++ && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) ++ { ++ break; ++ } ++ ++ if (start->last > qc->first_rtt) { ++ ++ if (oldest == NGX_TIMER_INFINITE || start->last < oldest) { ++ oldest = start->last; ++ } ++ ++ if (newest == NGX_TIMER_INFINITE || start->last > newest) { ++ newest = start->last; ++ } ++ ++ nlost++; ++ } ++ ++ ngx_quic_resend_frames(c, ctx); ++ } ++ } ++ ++ ++ /* RFC 9002, 7.6.2. Establishing Persistent Congestion */ ++ ++ /* ++ * Once acknowledged, packets are no longer tracked. Thus no send time ++ * information is available for such packets. This limits persistent ++ * congestion algorithm to packets mentioned within ACK ranges of the ++ * latest ACK frame. ++ */ ++ ++ if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) { ++ ++ if (newest - oldest > ngx_quic_pcg_duration(c)) { ++ ngx_quic_persistent_congestion(c); ++ } ++ } ++ ++ ngx_quic_set_lost_timer(c); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_msec_t ++ngx_quic_pcg_duration(ngx_connection_t *c) ++{ ++ ngx_msec_t duration; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ duration = qc->avg_rtt; ++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); ++ duration += qc->ctp.max_ack_delay; ++ duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR; ++ ++ return duration; ++} ++ ++ ++static void ++ngx_quic_persistent_congestion(ngx_connection_t *c) ++{ ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ ++ cg->recovery_start = ngx_current_msec; ++ cg->window = qc->tp.max_udp_payload_size * 2; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic persistent congestion win:%uz", cg->window); ++} ++ ++ ++void ++ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f, *start; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ q = ngx_queue_head(&ctx->sent); ++ start = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic resend packet pnum:%uL", start->pnum); ++ ++ ngx_quic_congestion_lost(c, start); ++ ++ do { ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->pnum != start->pnum) { ++ break; ++ } ++ ++ q = ngx_queue_next(q); ++ ++ ngx_queue_remove(&f->queue); ++ ++ switch (f->type) { ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ if (ctx->level == ssl_encryption_application) { ++ /* force generation of most recent acknowledgment */ ++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; ++ } ++ ++ ngx_quic_free_frame(c, f); ++ break; ++ ++ case NGX_QUIC_FT_PING: ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ ngx_quic_free_frame(c, f); ++ break; ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ f->u.max_data.max_data = qc->streams.recv_max_data; ++ ngx_quic_queue_frame(qc, f); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ case NGX_QUIC_FT_MAX_STREAMS2: ++ f->u.max_streams.limit = f->u.max_streams.bidi ++ ? qc->streams.client_max_streams_bidi ++ : qc->streams.client_max_streams_uni; ++ ngx_quic_queue_frame(qc, f); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ qs = ngx_quic_find_stream(&qc->streams.tree, ++ f->u.max_stream_data.id); ++ if (qs == NULL) { ++ ngx_quic_free_frame(c, f); ++ break; ++ } ++ ++ f->u.max_stream_data.limit = qs->recv_max_data; ++ ngx_quic_queue_frame(qc, f); ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); ++ ++ if (qs && qs->connection->write->error) { ++ /* RESET_STREAM was sent */ ++ ngx_quic_free_frame(c, f); ++ break; ++ } ++ ++ /* fall through */ ++ ++ default: ++ ngx_queue_insert_tail(&ctx->frames, &f->queue); ++ } ++ ++ } while (q != ngx_queue_sentinel(&ctx->sent)); ++ ++ if (qc->closing) { ++ return; ++ } ++ ++ ngx_post_event(&qc->push, &ngx_posted_events); ++} ++ ++ ++static void ++ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) ++{ ++ ngx_uint_t blocked; ++ ngx_msec_t timer; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ if (f->plen == 0) { ++ return; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ ++ blocked = (cg->in_flight >= cg->window) ? 1 : 0; ++ ++ cg->in_flight -= f->plen; ++ f->plen = 0; ++ ++ timer = f->last - cg->recovery_start; ++ ++ if ((ngx_msec_int_t) timer <= 0) { ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion lost recovery win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++ goto done; ++ } ++ ++ cg->recovery_start = ngx_current_msec; ++ cg->window /= 2; ++ ++ if (cg->window < qc->tp.max_udp_payload_size * 2) { ++ cg->window = qc->tp.max_udp_payload_size * 2; ++ } ++ ++ cg->ssthresh = cg->window; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion lost win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++done: ++ ++ if (blocked && cg->in_flight < cg->window) { ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++} ++ ++ ++void ++ngx_quic_set_lost_timer(ngx_connection_t *c) ++{ ++ ngx_uint_t i; ++ ngx_msec_t now; ++ ngx_queue_t *q; ++ ngx_msec_int_t lost, pto, w; ++ ngx_quic_frame_t *f; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ now = ngx_current_msec; ++ ++ lost = -1; ++ pto = -1; ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ctx = &qc->send_ctx[i]; ++ ++ if (ngx_queue_empty(&ctx->sent)) { ++ continue; ++ } ++ ++ if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { ++ q = ngx_queue_head(&ctx->sent); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); ++ ++ if (f->pnum <= ctx->largest_ack) { ++ if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { ++ w = 0; ++ } ++ ++ if (lost == -1 || w < lost) { ++ lost = w; ++ } ++ } ++ } ++ ++ q = ngx_queue_last(&ctx->sent); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now); ++ ++ if (w < 0) { ++ w = 0; ++ } ++ ++ if (pto == -1 || w < pto) { ++ pto = w; ++ } ++ } ++ ++ if (qc->pto.timer_set) { ++ ngx_del_timer(&qc->pto); ++ } ++ ++ if (lost != -1) { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic lost timer lost:%M", lost); ++ ++ qc->pto.handler = ngx_quic_lost_handler; ++ ngx_add_timer(&qc->pto, lost); ++ return; ++ } ++ ++ if (pto != -1) { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic lost timer pto:%M", pto); ++ ++ qc->pto.handler = ngx_quic_pto_handler; ++ ngx_add_timer(&qc->pto, pto); ++ return; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); ++} ++ ++ ++ngx_msec_t ++ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ ngx_msec_t duration; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */ ++ duration = qc->avg_rtt; ++ ++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); ++ duration <<= qc->pto_count; ++ ++ if (qc->congestion.in_flight == 0) { /* no in-flight packets */ ++ return duration; ++ } ++ ++ if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { ++ duration += qc->ctp.max_ack_delay << qc->pto_count; ++ } ++ ++ return duration; ++} ++ ++ ++static ++void ngx_quic_lost_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); ++ ++ c = ev->data; ++ ++ if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ } ++ ++ ngx_quic_connstate_dbg(c); ++} ++ ++ ++void ++ngx_quic_pto_handler(ngx_event_t *ev) ++{ ++ ngx_uint_t i; ++ ngx_msec_t now; ++ ngx_queue_t *q, *next; ++ ngx_connection_t *c; ++ ngx_quic_frame_t *f; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); ++ ++ c = ev->data; ++ qc = ngx_quic_get_connection(c); ++ now = ngx_current_msec; ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ++ ctx = &qc->send_ctx[i]; ++ ++ if (ngx_queue_empty(&ctx->sent)) { ++ continue; ++ } ++ ++ q = ngx_queue_head(&ctx->sent); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->pnum <= ctx->largest_ack ++ && ctx->largest_ack != NGX_QUIC_UNSET_PN) ++ { ++ continue; ++ } ++ ++ if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { ++ continue; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic pto %s pto_count:%ui", ++ ngx_quic_level_name(ctx->level), qc->pto_count); ++ ++ for (q = ngx_queue_head(&ctx->frames); ++ q != ngx_queue_sentinel(&ctx->frames); ++ /* void */) ++ { ++ next = ngx_queue_next(q); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->type == NGX_QUIC_FT_PING) { ++ ngx_queue_remove(q); ++ ngx_quic_free_frame(c, f); ++ } ++ ++ q = next; ++ } ++ ++ for (q = ngx_queue_head(&ctx->sent); ++ q != ngx_queue_sentinel(&ctx->sent); ++ /* void */) ++ { ++ next = ngx_queue_next(q); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->type == NGX_QUIC_FT_PING) { ++ ngx_quic_congestion_lost(c, f); ++ ngx_queue_remove(q); ++ ngx_quic_free_frame(c, f); ++ } ++ ++ q = next; ++ } ++ ++ /* enforce 2 udp datagrams */ ++ ++ f = ngx_quic_alloc_frame(c); ++ if (f == NULL) { ++ break; ++ } ++ ++ f->level = ctx->level; ++ f->type = NGX_QUIC_FT_PING; ++ f->flush = 1; ++ ++ ngx_quic_queue_frame(qc, f); ++ ++ f = ngx_quic_alloc_frame(c); ++ if (f == NULL) { ++ break; ++ } ++ ++ f->level = ctx->level; ++ f->type = NGX_QUIC_FT_PING; ++ ++ ngx_quic_queue_frame(qc, f); ++ } ++ ++ qc->pto_count++; ++ ++ ngx_quic_connstate_dbg(c); ++} ++ ++ ++ngx_int_t ++ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ uint64_t base, largest, smallest, gs, ge, gap, range, pn; ++ uint64_t prev_pending; ++ ngx_uint_t i, nr; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_ack_range_t *r; ++ ngx_quic_connection_t *qc; ++ ++ c->log->action = "preparing ack"; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" ++ " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, ++ ctx->first_range, ctx->nranges); ++ ++ prev_pending = ctx->pending_ack; ++ ++ if (pkt->need_ack) { ++ ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ ++ if (ctx->send_ack == 0) { ++ ctx->ack_delay_start = ngx_current_msec; ++ } ++ ++ ctx->send_ack++; ++ ++ if (ctx->pending_ack == NGX_QUIC_UNSET_PN ++ || ctx->pending_ack < pkt->pn) ++ { ++ ctx->pending_ack = pkt->pn; ++ } ++ } ++ ++ base = ctx->largest_range; ++ pn = pkt->pn; ++ ++ if (base == NGX_QUIC_UNSET_PN) { ++ ctx->largest_range = pn; ++ ctx->largest_received = pkt->received; ++ return NGX_OK; ++ } ++ ++ if (base == pn) { ++ return NGX_OK; ++ } ++ ++ largest = base; ++ smallest = largest - ctx->first_range; ++ ++ if (pn > base) { ++ ++ if (pn - base == 1) { ++ ctx->first_range++; ++ ctx->largest_range = pn; ++ ctx->largest_received = pkt->received; ++ ++ return NGX_OK; ++ ++ } else { ++ /* new gap in front of current largest */ ++ ++ /* no place for new range, send current range as is */ ++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) { ++ ++ if (prev_pending != NGX_QUIC_UNSET_PN) { ++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) { ++ ctx->pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ } ++ ++ gap = pn - base - 2; ++ range = ctx->first_range; ++ ++ ctx->first_range = 0; ++ ctx->largest_range = pn; ++ ctx->largest_received = pkt->received; ++ ++ /* packet is out of order, force send */ ++ if (pkt->need_ack) { ++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; ++ } ++ ++ i = 0; ++ ++ goto insert; ++ } ++ } ++ ++ /* pn < base, perform lookup in existing ranges */ ++ ++ /* packet is out of order */ ++ if (pkt->need_ack) { ++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; ++ } ++ ++ if (pn >= smallest && pn <= largest) { ++ return NGX_OK; ++ } ++ ++#if (NGX_SUPPRESS_WARN) ++ r = NULL; ++#endif ++ ++ for (i = 0; i < ctx->nranges; i++) { ++ r = &ctx->ranges[i]; ++ ++ ge = smallest - 1; ++ gs = ge - r->gap; ++ ++ if (pn >= gs && pn <= ge) { ++ ++ if (gs == ge) { ++ /* gap size is exactly one packet, now filled */ ++ ++ /* data moves to previous range, current is removed */ ++ ++ if (i == 0) { ++ ctx->first_range += r->range + 2; ++ ++ } else { ++ ctx->ranges[i - 1].range += r->range + 2; ++ } ++ ++ nr = ctx->nranges - i - 1; ++ if (nr) { ++ ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], ++ sizeof(ngx_quic_ack_range_t) * nr); ++ } ++ ++ ctx->nranges--; ++ ++ } else if (pn == gs) { ++ /* current gap shrinks from tail (current range grows) */ ++ r->gap--; ++ r->range++; ++ ++ } else if (pn == ge) { ++ /* current gap shrinks from head (previous range grows) */ ++ r->gap--; ++ ++ if (i == 0) { ++ ctx->first_range++; ++ ++ } else { ++ ctx->ranges[i - 1].range++; ++ } ++ ++ } else { ++ /* current gap is split into two parts */ ++ ++ gap = ge - pn - 1; ++ range = 0; ++ ++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) { ++ if (prev_pending != NGX_QUIC_UNSET_PN) { ++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) { ++ ctx->pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ } ++ ++ r->gap = pn - gs - 1; ++ goto insert; ++ } ++ ++ return NGX_OK; ++ } ++ ++ largest = smallest - r->gap - 2; ++ smallest = largest - r->range; ++ ++ if (pn >= smallest && pn <= largest) { ++ /* this packet number is already known */ ++ return NGX_OK; ++ } ++ ++ } ++ ++ if (pn == smallest - 1) { ++ /* extend first or last range */ ++ ++ if (i == 0) { ++ ctx->first_range++; ++ ++ } else { ++ r->range++; ++ } ++ ++ return NGX_OK; ++ } ++ ++ /* nothing found, add new range at the tail */ ++ ++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) { ++ /* packet is too old to keep it */ ++ ++ if (pkt->need_ack) { ++ return ngx_quic_send_ack_range(c, ctx, pn, pn); ++ } ++ ++ return NGX_OK; ++ } ++ ++ gap = smallest - 2 - pn; ++ range = 0; ++ ++insert: ++ ++ if (ctx->nranges < NGX_QUIC_MAX_RANGES) { ++ ctx->nranges++; ++ } ++ ++ ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], ++ sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); ++ ++ ctx->ranges[i].gap = gap; ++ ctx->ranges[i].range = range; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ ngx_msec_t delay; ++ ngx_quic_connection_t *qc; ++ ++ if (!ctx->send_ack) { ++ return NGX_OK; ++ } ++ ++ if (ctx->level == ssl_encryption_application) { ++ ++ delay = ngx_current_msec - ctx->ack_delay_start; ++ qc = ngx_quic_get_connection(c); ++ ++ if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP ++ && delay < qc->tp.max_ack_delay) ++ { ++ if (!qc->push.timer_set && !qc->closing) { ++ ngx_add_timer(&qc->push, ++ qc->tp.max_ack_delay - delay); ++ } ++ ++ return NGX_OK; ++ } ++ } ++ ++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ctx->send_ack = 0; ++ ++ return NGX_OK; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,274 @@ ++/* ++ * 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; ++ ++#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 refcnt; ++}; ++ ++ ++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; ++ socklen_t socklen; ++ ngx_uint_t state; ++ ngx_uint_t limited; /* unsigned limited:1; */ ++ ngx_msec_t expires; ++ ngx_msec_t last_seen; ++ ngx_uint_t tries; ++ off_t sent; ++ off_t received; ++ u_char challenge1[8]; ++ u_char challenge2[8]; ++ ngx_uint_t refcnt; ++ uint64_t seqnum; ++ ngx_str_t addr_text; ++ u_char text[NGX_SOCKADDR_STRLEN]; ++}; ++ ++ ++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_quic_path_t *path; ++ ngx_quic_client_id_t *cid; ++}; ++ ++ ++typedef struct { ++ ngx_rbtree_t tree; ++ ngx_rbtree_node_t sentinel; ++ ngx_queue_t uninitialized; ++ ++ uint64_t sent; ++ uint64_t recv_offset; ++ uint64_t recv_window; ++ uint64_t recv_last; ++ uint64_t recv_max_data; ++ 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_chain_t *crypto; ++ uint64_t crypto_received; ++ 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_socket_t *socket; ++ ngx_quic_socket_t *backup; ++ ++ ngx_queue_t sockets; ++ ngx_queue_t paths; ++ ngx_queue_t client_ids; ++ ngx_queue_t free_sockets; ++ ngx_queue_t free_paths; ++ ngx_queue_t free_client_ids; ++ ++ ngx_uint_t nsockets; ++ ngx_uint_t nclient_ids; ++ uint64_t max_retired_seqnum; ++ uint64_t client_seqnum; ++ uint64_t server_seqnum; ++ uint64_t path_seqnum; ++ ++ ngx_quic_tp_t tp; ++ ngx_quic_tp_t ctp; ++ ++ ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; ++ ++ ngx_quic_keys_t *keys; ++ ++ ngx_quic_conf_t *conf; ++ ++ ngx_event_t push; ++ ngx_event_t pto; ++ ngx_event_t close; ++ ngx_event_t path_validation; ++ ngx_msec_t last_cc; ++ ++ ngx_msec_t first_rtt; ++ ngx_msec_t latest_rtt; ++ ngx_msec_t avg_rtt; ++ ngx_msec_t min_rtt; ++ ngx_msec_t rttvar; ++ ++ ngx_uint_t pto_count; ++ ++ ngx_queue_t free_frames; ++ ngx_buf_t *free_bufs; ++ ngx_buf_t *free_shadow_bufs; ++ ++ ngx_uint_t nframes; ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_uint_t nbufs; ++ ngx_uint_t nshadowbufs; ++#endif ++ ++ ngx_quic_streams_t streams; ++ ngx_quic_congestion_t congestion; ++ ++ off_t received; ++ ++ ngx_uint_t error; ++ enum ssl_encryption_level_t error_level; ++ ngx_uint_t error_ftype; ++ const char *error_reason; ++ ++ ngx_uint_t shutdown_code; ++ const char *shutdown_reason; ++ ++ unsigned error_app:1; ++ unsigned send_timer_set:1; ++ unsigned closing:1; ++ unsigned shutdown:1; ++ unsigned draining:1; ++ unsigned key_phase:1; ++ unsigned validated:1; ++ unsigned client_tp_done:1; ++}; ++ ++ ++ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ++ ngx_quic_tp_t *ctp); ++void ngx_quic_discard_ctx(ngx_connection_t *c, ++ enum ssl_encryption_level_t level); ++void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); ++void ngx_quic_shutdown_quic(ngx_connection_t *c); ++ ++#if (NGX_DEBUG) ++void ngx_quic_connstate_dbg(ngx_connection_t *c); ++#else ++#define ngx_quic_connstate_dbg(c) ++#endif ++ ++#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */ +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,613 @@ ++ ++/* ++ * 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_send_retire_connection_id(ngx_connection_t *c, ++ uint64_t seqnum); ++ ++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_replace_retired_client_id(ngx_connection_t *c, ++ ngx_quic_client_id_t *retired_cid); ++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) ++{ ++ uint64_t seq; ++ ngx_str_t id; ++ ngx_queue_t *q; ++ 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. ++ */ ++ ++ if (ngx_quic_send_retire_connection_id(c, f->seqnum) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ 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; ++ } ++ ++ /* this connection id must be retired */ ++ seq = cid->seqnum; ++ ++ if (cid->refcnt) { ++ /* we are going to retire client id which is in use */ ++ if (ngx_quic_replace_retired_client_id(c, cid) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ } else { ++ ngx_quic_unref_client_id(c, cid); ++ } ++ ++ if (ngx_quic_send_retire_connection_id(c, seq) != 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_send_retire_connection_id(ngx_connection_t *c, uint64_t seqnum) ++{ ++ 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 = seqnum; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ /* we are no longer going to use this client id */ ++ ++ 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 #%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->refcnt == 0) { ++ return cid; ++ } ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_quic_client_id_t * ++ngx_quic_used_client_id(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ ngx_queue_t *q; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* best guess: cid used by active path is good for us */ ++ if (qc->socket->path == path) { ++ return qc->socket->cid; ++ } ++ ++ 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->path && qsock->path == path) { ++ return qsock->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_path_t *path; ++ ngx_quic_socket_t *qsock, **tmp; ++ ngx_quic_client_id_t *cid; ++ 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 #%uL is retired", qsock->sid.seqnum); ++ ++ /* check if client is willing to retire sid we have in use */ ++ if (qsock->sid.seqnum == qc->socket->sid.seqnum) { ++ tmp = &qc->socket; ++ ++ } else if (qc->backup && qsock->sid.seqnum == qc->backup->sid.seqnum) { ++ tmp = &qc->backup; ++ ++ } else { ++ ++ 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; ++ } ++ ++ /* preserve path/cid from retired socket */ ++ path = qsock->path; ++ cid = qsock->cid; ++ ++ /* ensure that closing_socket will not drop path and cid */ ++ path->refcnt++; ++ cid->refcnt++; ++ ++ ngx_quic_close_socket(c, qsock); ++ ++ /* restore original values */ ++ path->refcnt--; ++ cid->refcnt--; ++ ++ /* restore socket count up to a limit after deletion */ ++ if (ngx_quic_create_sockets(c) != NGX_OK) { ++ goto failed; ++ } ++ ++ qsock = ngx_quic_get_unconnected_socket(c); ++ if (qsock == NULL) { ++ qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; ++ qc->error_reason = "not enough server IDs"; ++ goto failed; ++ } ++ ++ ngx_quic_connect(c, qsock, path, cid); ++ ++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic %s socket is now #%uL:%uL:%uL (%s)", ++ (*tmp) == qc->socket ? "active" : "backup", ++ qsock->sid.seqnum, qsock->cid->seqnum, ++ qsock->path->seqnum, ++ ngx_quic_path_state_str(qsock->path)); ++ ++ /* restore active/backup pointer in quic connection */ ++ *tmp = qsock; ++ ++ return NGX_OK; ++ ++failed: ++ ++ /* ++ * socket was closed, path and cid were preserved artifically ++ * to be reused, but it didn't happen, thus unref here ++ */ ++ ++ ngx_quic_unref_path(c, path); ++ ngx_quic_unref_client_id(c, cid); ++ ++ return NGX_ERROR; ++} ++ ++ ++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; ++} ++ ++ ++static ngx_int_t ++ngx_quic_replace_retired_client_id(ngx_connection_t *c, ++ ngx_quic_client_id_t *retired_cid) ++{ ++ ngx_queue_t *q; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_client_id_t *cid; ++ 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->cid == retired_cid) { ++ ++ cid = ngx_quic_next_client_id(c); ++ if (cid == NULL) { ++ return NGX_ERROR; ++ } ++ ++ qsock->cid = cid; ++ cid->refcnt++; ++ ++ ngx_quic_unref_client_id(c, retired_cid); ++ ++ if (retired_cid->refcnt == 0) { ++ return NGX_OK; ++ } ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) ++{ ++ ngx_quic_connection_t *qc; ++ ++ if (cid->refcnt) { ++ cid->refcnt--; ++ } /* else: unused client id */ ++ ++ if (cid->refcnt) { ++ return; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_queue_remove(&cid->queue); ++ ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); ++ ++ qc->nclient_ids--; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,30 @@ ++ ++/* ++ * 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_quic_client_id_t *ngx_quic_used_client_id(ngx_connection_t *c, ++ ngx_quic_path_t *path); ++void ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid); ++ ++#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,800 @@ ++ ++/* ++ * 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_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; ++} ++ ++ ++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_trim_chain(ngx_chain_t *in, size_t size) ++{ ++ size_t n; ++ ngx_buf_t *b; ++ ++ while (in && size > 0) { ++ b = in->buf; ++ n = ngx_min((size_t) (b->last - b->pos), size); ++ ++ b->pos += n; ++ size -= n; ++ ++ if (b->pos == b->last) { ++ in = in->next; ++ } ++ } ++} ++ ++ ++void ++ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) ++{ ++ ngx_chain_t *cl; ++ ++ while (in) { ++ cl = in; ++ in = in->next; ++ ++ ngx_quic_free_buf(c, cl->buf); ++ ngx_free_chain(c->pool, cl); ++ } ++} ++ ++ ++void ++ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) ++{ ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ++ do { ++ q = ngx_queue_head(frames); ++ ++ if (q == ngx_queue_sentinel(frames)) { ++ break; ++ } ++ ++ ngx_queue_remove(q); ++ ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ ngx_quic_free_frame(c, f); ++ } while (1); ++} ++ ++ ++void ++ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) ++{ ++ ngx_quic_send_ctx_t *ctx; ++ ++ ctx = ngx_quic_get_send_ctx(qc, frame->level); ++ ++ ngx_queue_insert_tail(&ctx->frames, &frame->queue); ++ ++ frame->len = ngx_quic_create_frame(NULL, frame); ++ /* always succeeds */ ++ ++ if (qc->closing) { ++ return; ++ } ++ ++ ngx_post_event(&qc->push, &ngx_posted_events); ++} ++ ++ ++ngx_int_t ++ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) ++{ ++ size_t shrink; ++ ngx_quic_frame_t *nf; ++ ngx_quic_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; ++ } ++ ++ 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); ++ ++ f->data = ngx_quic_read_chain(c, &nf->data, of->length); ++ if (f->data == NGX_CHAIN_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ ngx_queue_insert_after(&f->queue, &nf->queue); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_chain_t * ++ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) ++{ ++ off_t n; ++ ngx_buf_t *b; ++ ngx_chain_t *out, *cl, **ll; ++ ++ out = *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) { ++ goto split; ++ } ++ ++ limit -= n; ++ } ++ ++ *chain = *ll; ++ *ll = NULL; ++ ++ return out; ++ ++split: ++ ++ cl = ngx_alloc_chain_link(c->pool); ++ if (cl == NULL) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ cl->buf = ngx_quic_clone_buf(c, b); ++ if (cl->buf == NULL) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ cl->buf->pos += limit; ++ b->last = cl->buf->pos; ++ b->last_buf = 0; ++ ++ ll = &(*ll)->next; ++ cl->next = *ll; ++ *ll = NULL; ++ *chain = cl; ++ ++ return out; ++} ++ ++ ++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_copy_buf(ngx_connection_t *c, u_char *data, size_t len) ++{ ++ size_t n; ++ ngx_buf_t *b; ++ ngx_chain_t *cl, *out, **ll; ++ ++ out = NULL; ++ ll = &out; ++ ++ while (len) { ++ cl = ngx_quic_alloc_chain(c); ++ if (cl == NULL) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ b = cl->buf; ++ n = ngx_min((size_t) (b->end - b->last), len); ++ ++ b->last = ngx_cpymem(b->last, data, n); ++ ++ data += n; ++ len -= n; ++ ++ *ll = cl; ++ ll = &cl->next; ++ } ++ ++ *ll = NULL; ++ ++ return out; ++} ++ ++ ++ngx_chain_t * ++ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, ++ off_t limit, off_t offset) ++{ ++ off_t n; ++ u_char *p; ++ ngx_buf_t *b; ++ ngx_chain_t *cl, *sl; ++ ++ while (in && limit) { ++ 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 (n <= offset) { ++ offset -= n; ++ chain = &cl->next; ++ continue; ++ } ++ ++ if (b->sync && offset > 0) { ++ /* split hole at offset */ ++ ++ b->sync = 0; ++ ++ sl = ngx_quic_read_chain(c, &cl, offset); ++ if (cl == NGX_CHAIN_ERROR) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ sl->buf->sync = 1; ++ cl->buf->sync = 1; ++ ++ *chain = sl; ++ sl->next = cl; ++ continue; ++ } ++ ++ for (p = b->pos + offset; p != b->last && in && limit; /* void */ ) { ++ 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); ++ } ++ ++ p += n; ++ in->buf->pos += n; ++ offset += n; ++ limit -= n; ++ ++ if (in->buf->pos == in->buf->last) { ++ in = in->next; ++ } ++ } ++ ++ if (b->sync && p == b->last) { ++ b->sync = 0; ++ continue; ++ } ++ ++ if (b->sync && p != b->pos) { ++ /* split hole at p - b->pos */ ++ ++ b->sync = 0; ++ ++ sl = ngx_quic_read_chain(c, &cl, p - b->pos); ++ if (sl == NGX_CHAIN_ERROR) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ cl->buf->sync = 1; ++ ++ *chain = sl; ++ sl->next = cl; ++ } ++ } ++ ++ return in; ++} ++ ++ ++#if (NGX_DEBUG) ++ ++void ++ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) ++{ ++ u_char *p, *last, *pos, *end; ++ ssize_t n; ++ uint64_t gap, range, largest, smallest; ++ ngx_uint_t i; ++ u_char buf[NGX_MAX_ERROR_STR]; ++ ++ p = buf; ++ last = buf + sizeof(buf); ++ ++ switch (f->type) { ++ ++ case NGX_QUIC_FT_CRYPTO: ++ p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", ++ f->u.crypto.length, f->u.crypto.offset); ++ ++#ifdef NGX_QUIC_DEBUG_FRAMES ++ { ++ ngx_chain_t *cl; ++ ++ p = ngx_slprintf(p, last, " data:"); ++ ++ for (cl = f->data; cl; cl = cl->next) { ++ p = ngx_slprintf(p, last, "%*xs", ++ cl->buf->last - cl->buf->pos, cl->buf->pos); ++ } ++ } ++#endif ++ ++ break; ++ ++ case NGX_QUIC_FT_PADDING: ++ p = ngx_slprintf(p, last, "PADDING"); ++ break; ++ ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ ++ p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", ++ f->u.ack.range_count, f->u.ack.delay); ++ ++ if (f->data) { ++ pos = f->data->buf->pos; ++ end = f->data->buf->last; ++ ++ } else { ++ pos = NULL; ++ end = NULL; ++ } ++ ++ largest = f->u.ack.largest; ++ smallest = f->u.ack.largest - f->u.ack.first_range; ++ ++ if (largest == smallest) { ++ p = ngx_slprintf(p, last, "%uL", largest); ++ ++ } else { ++ p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); ++ } ++ ++ for (i = 0; i < f->u.ack.range_count; i++) { ++ n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); ++ if (n == NGX_ERROR) { ++ break; ++ } ++ ++ pos += n; ++ ++ largest = smallest - gap - 2; ++ smallest = largest - range; ++ ++ if (largest == smallest) { ++ p = ngx_slprintf(p, last, " %uL", largest); ++ ++ } else { ++ p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); ++ } ++ } ++ ++ if (f->type == NGX_QUIC_FT_ACK_ECN) { ++ p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", ++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); ++ } ++ break; ++ ++ case NGX_QUIC_FT_PING: ++ p = ngx_slprintf(p, last, "PING"); ++ break; ++ ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ p = ngx_slprintf(p, last, ++ "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", ++ f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); ++ break; ++ ++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID: ++ p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", ++ f->u.retire_cid.sequence_number); ++ break; ++ ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP: ++ p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", ++ f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", ++ f->u.close.error_code); ++ ++ if (f->u.close.reason.len) { ++ p = ngx_slprintf(p, last, " %V", &f->u.close.reason); ++ } ++ ++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { ++ p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); ++ ++ if (f->u.stream.off) { ++ p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); ++ } ++ ++ if (f->u.stream.len) { ++ p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); ++ } ++ ++ if (f->u.stream.fin) { ++ p = ngx_slprintf(p, last, " fin:1"); ++ } ++ ++#ifdef NGX_QUIC_DEBUG_FRAMES ++ { ++ ngx_chain_t *cl; ++ ++ p = ngx_slprintf(p, last, " data:"); ++ ++ for (cl = f->data; cl; cl = cl->next) { ++ p = ngx_slprintf(p, last, "%*xs", ++ cl->buf->last - cl->buf->pos, cl->buf->pos); ++ } ++ } ++#endif ++ ++ break; ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", ++ f->u.max_data.max_data); ++ break; ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ p = ngx_slprintf(p, last, "RESET_STREAM" ++ " id:0x%xL error_code:0x%xL final_size:0x%xL", ++ f->u.reset_stream.id, f->u.reset_stream.error_code, ++ f->u.reset_stream.final_size); ++ break; ++ ++ case NGX_QUIC_FT_STOP_SENDING: ++ p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", ++ f->u.stop_sending.id, f->u.stop_sending.error_code); ++ break; ++ ++ case NGX_QUIC_FT_STREAMS_BLOCKED: ++ case NGX_QUIC_FT_STREAMS_BLOCKED2: ++ p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", ++ f->u.streams_blocked.limit, f->u.streams_blocked.bidi); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ case NGX_QUIC_FT_MAX_STREAMS2: ++ p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", ++ f->u.max_streams.limit, f->u.max_streams.bidi); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", ++ f->u.max_stream_data.id, f->u.max_stream_data.limit); ++ break; ++ ++ ++ case NGX_QUIC_FT_DATA_BLOCKED: ++ p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", ++ f->u.data_blocked.limit); ++ break; ++ ++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED: ++ p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", ++ f->u.stream_data_blocked.id, ++ f->u.stream_data_blocked.limit); ++ break; ++ ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", ++ sizeof(f->u.path_challenge.data), ++ f->u.path_challenge.data); ++ break; ++ ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", ++ sizeof(f->u.path_challenge.data), ++ f->u.path_challenge.data); ++ break; ++ ++ case NGX_QUIC_FT_NEW_TOKEN: ++ p = ngx_slprintf(p, last, "NEW_TOKEN"); ++ break; ++ ++ case NGX_QUIC_FT_HANDSHAKE_DONE: ++ p = ngx_slprintf(p, last, "HANDSHAKE DONE"); ++ break; ++ ++ default: ++ p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); ++ break; ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", ++ tx ? "tx" : "rx", ngx_quic_level_name(f->level), ++ p - buf, buf); ++} ++ ++#endif +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,42 @@ ++ ++/* ++ * 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); ++ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, ++ size_t len); ++void ngx_quic_trim_chain(ngx_chain_t *in, size_t size); ++void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); ++ngx_chain_t *ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, ++ off_t limit); ++ngx_chain_t *ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ++ ngx_chain_t *in, off_t limit, off_t offset); ++ ++#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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,689 @@ ++ ++/* ++ * 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 ngx_int_t ngx_quic_path_restore(ngx_connection_t *c); ++static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c); ++ ++ ++ngx_int_t ++ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ++ ngx_quic_path_challenge_frame_t *f) ++{ ++ ngx_quic_path_t *path; ++ ngx_quic_frame_t frame, *fp; ++ ngx_quic_socket_t *qsock; ++ 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. ++ */ ++ qsock = ngx_quic_get_socket(c); ++ path = qsock->path; ++ ++ /* ++ * 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, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (qsock == qc->socket) { ++ /* ++ * 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->state != NGX_QUIC_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_error(NGX_LOG_INFO, 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; ++ ++ if (qc->backup) { ++ prev = qc->backup->path; ++ ++ 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 #%uL successfully validated", path->seqnum); ++ ++ path->state = NGX_QUIC_PATH_VALIDATED; ++ path->limited = 0; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_quic_path_t * ++ngx_quic_alloc_path(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ struct sockaddr *sa; ++ 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); ++ ++ sa = path->sockaddr; ++ ngx_memzero(path, sizeof(ngx_quic_path_t)); ++ path->sockaddr = sa; ++ ++ } else { ++ ++ path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); ++ if (path == NULL) { ++ return NULL; ++ } ++ ++ path->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); ++ if (path->sockaddr == NULL) { ++ return NULL; ++ } ++ } ++ ++ return path; ++} ++ ++ ++ngx_quic_path_t * ++ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, ++ socklen_t socklen) ++{ ++ ngx_quic_path_t *path; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ path = ngx_quic_alloc_path(c); ++ if (path == NULL) { ++ return NULL; ++ } ++ ++ path->state = NGX_QUIC_PATH_NEW; ++ path->limited = 1; ++ ++ path->seqnum = qc->path_seqnum++; ++ path->last_seen = ngx_current_msec; ++ ++ 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_queue_insert_tail(&qc->paths, &path->queue); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic path #%uL created src:%V", ++ path->seqnum, &path->addr_text); ++ ++ return path; ++} ++ ++ ++ngx_quic_path_t * ++ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr, ++ socklen_t socklen) ++{ ++ 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 (ngx_cmp_sockaddr(sockaddr, socklen, ++ path->sockaddr, path->socklen, 1) ++ == NGX_OK) ++ { ++ return path; ++ } ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_int_t ++ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ off_t len; ++ ngx_quic_path_t *path; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_client_id_t *cid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ qsock = ngx_quic_get_socket(c); ++ ++ if (c->udp->dgram == NULL) { ++ /* 1st ever packet in connection, path already exists */ ++ path = qsock->path; ++ goto update; ++ } ++ ++ path = ngx_quic_find_path(c, c->udp->dgram->sockaddr, ++ c->udp->dgram->socklen); ++ ++ if (path == NULL) { ++ path = ngx_quic_add_path(c, c->udp->dgram->sockaddr, ++ c->udp->dgram->socklen); ++ if (path == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (qsock->path) { ++ /* NAT rebinding case: packet to same CID, but from new address */ ++ ++ ngx_quic_unref_path(c, qsock->path); ++ ++ qsock->path = path; ++ path->refcnt++; ++ ++ goto update; ++ } ++ ++ } else if (qsock->path) { ++ goto update; ++ } ++ ++ /* prefer unused client IDs if available */ ++ cid = ngx_quic_next_client_id(c); ++ if (cid == NULL) { ++ ++ /* try to reuse connection ID used on the same path */ ++ cid = ngx_quic_used_client_id(c, path); ++ if (cid == NULL) { ++ ++ qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; ++ qc->error_reason = "no available client ids for new path"; ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "no available client ids for new path"); ++ ++ return NGX_ERROR; ++ } ++ } ++ ++ ngx_quic_connect(c, qsock, path, cid); ++ ++update: ++ ++ if (path->state != NGX_QUIC_PATH_NEW) { ++ /* force limits/revalidation for paths that were not seen recently */ ++ if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) { ++ path->state = NGX_QUIC_PATH_NEW; ++ path->limited = 1; ++ path->sent = 0; ++ path->received = 0; ++ } ++ } ++ ++ path->last_seen = ngx_current_msec; ++ ++ len = pkt->raw->last - pkt->raw->start; ++ ++ /* 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_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet via #%uL:%uL:%uL" ++ " size:%O path recvd:%O sent:%O limited:%ui", ++ qsock->sid.seqnum, qsock->cid->seqnum, path->seqnum, ++ len, path->received, path->sent, path->limited); ++ ++ 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 #%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; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ /* got non-probing packet via non-active socket with different path */ ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* current socket, different from active */ ++ qsock = ngx_quic_get_socket(c); ++ ++ next = qsock->path; /* going to migrate to this path... */ ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic migration from #%uL:%uL:%uL (%s)" ++ " to #%uL:%uL:%uL (%s)", ++ qc->socket->sid.seqnum, qc->socket->cid->seqnum, ++ qc->socket->path->seqnum, ++ ngx_quic_path_state_str(qc->socket->path), ++ qsock->sid.seqnum, qsock->cid->seqnum, next->seqnum, ++ ngx_quic_path_state_str(next)); ++ ++ if (next->state == NGX_QUIC_PATH_NEW) { ++ if (ngx_quic_validate_path(c, qsock->path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ 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; ++ } ++ ++ /* switching connection to new path */ ++ ++ ngx_quic_set_connection_path(c, next); ++ ++ /* ++ * RFC 9000, 9.5. Privacy Implications of Connection Migration ++ * ++ * An endpoint MUST NOT reuse a connection ID when sending to ++ * more than one destination address. ++ */ ++ ++ /* preserve valid path we are migrating from */ ++ if (qc->socket->path->state == NGX_QUIC_PATH_VALIDATED) { ++ ++ if (qc->backup) { ++ ngx_quic_close_socket(c, qc->backup); ++ } ++ ++ qc->backup = qc->socket; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic backup socket is now #%uL:%uL:%uL (%s)", ++ qc->backup->sid.seqnum, qc->backup->cid->seqnum, ++ qc->backup->path->seqnum, ++ ngx_quic_path_state_str(qc->backup->path)); ++ } ++ ++ qc->socket = qsock; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic active socket is now #%uL:%uL:%uL (%s)", ++ qsock->sid.seqnum, qsock->cid->seqnum, ++ qsock->path->seqnum, ngx_quic_path_state_str(qsock->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 new path #%uL", ++ path->seqnum); ++ ++ path->state = NGX_QUIC_PATH_VALIDATING; ++ ++ if (RAND_bytes(path->challenge1, 8) != 1) { ++ return NGX_ERROR; ++ } ++ ++ if (RAND_bytes(path->challenge2, 8) != 1) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_quic_send_path_challenge(c, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); ++ pto = ngx_quic_pto(c, ctx); ++ ++ path->expires = ngx_current_msec + pto; ++ path->tries = NGX_QUIC_PATH_RETRIES; ++ ++ if (!qc->path_validation.timer_set) { ++ ngx_add_timer(&qc->path_validation, pto); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ ngx_quic_frame_t frame; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic path #%uL send path challenge tries:%ui", ++ path->seqnum, path->tries); ++ ++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); ++ ++ frame.level = ssl_encryption_application; ++ frame.type = NGX_QUIC_FT_PATH_CHALLENGE; ++ ++ ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); ++ ++ /* ++ * RFC 9000, 8.2.1. Initiating Path Validation ++ * ++ * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame ++ * to at least the smallest allowed maximum datagram size of 1200 bytes, ++ * unless the anti-amplification limit for the path does not permit ++ * sending a datagram of this size. ++ */ ++ ++ /* same applies to PATH_RESPONSE frames */ ++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); ++ ++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_path_validation_handler(ngx_event_t *ev) ++{ ++ ngx_msec_t now; ++ ngx_queue_t *q; ++ ngx_msec_int_t left, next, pto; ++ ngx_quic_path_t *path; ++ ngx_connection_t *c; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ c = ev->data; ++ qc = ngx_quic_get_connection(c); ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); ++ pto = ngx_quic_pto(c, ctx); ++ ++ next = -1; ++ now = ngx_current_msec; ++ ++ 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->state != NGX_QUIC_PATH_VALIDATING) { ++ continue; ++ } ++ ++ left = path->expires - now; ++ ++ if (left > 0) { ++ ++ if (next == -1 || left < next) { ++ next = path->expires; ++ } ++ ++ continue; ++ } ++ ++ if (--path->tries) { ++ path->expires = ngx_current_msec + pto; ++ ++ if (next == -1 || pto < next) { ++ next = pto; ++ } ++ ++ /* retransmit */ ++ (void) ngx_quic_send_path_challenge(c, path); ++ ++ continue; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, ++ "quic path #%uL validation failed", path->seqnum); ++ ++ /* found expired path */ ++ ++ path->state = NGX_QUIC_PATH_NEW; ++ path->limited = 1; ++ ++ /* ++ * RFC 9000, 9.4. Loss Detection and Congestion Control ++ * ++ * If the timer fires before the PATH_RESPONSE is received, the ++ * endpoint might send a new PATH_CHALLENGE and restart the timer for ++ * a longer period of time. This timer SHOULD be set as described in ++ * Section 6.2.1 of [QUIC-RECOVERY] and MUST NOT be more aggressive. ++ */ ++ ++ if (qc->socket->path != path) { ++ /* the path was not actually used */ ++ continue; ++ } ++ ++ if (ngx_quic_path_restore(c) != NGX_OK) { ++ qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; ++ qc->error_reason = "no viable path"; ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ } ++ ++ if (next != -1) { ++ ngx_add_timer(&qc->path_validation, next); ++ } ++} ++ ++ ++static ngx_int_t ++ngx_quic_path_restore(ngx_connection_t *c) ++{ ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* ++ * RFC 9000, 9.1. Probing a New Path ++ * ++ * Failure to validate a path does not cause the connection to end ++ * ++ * 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->backup == NULL) { ++ return NGX_ERROR; ++ } ++ ++ qc->socket = qc->backup; ++ qc->backup = NULL; ++ ++ qsock = qc->socket; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic active socket is restored to #%uL:%uL:%uL" ++ " (%s), no backup", ++ qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum, ++ ngx_quic_path_state_str(qsock->path)); ++ ++ ngx_quic_set_connection_path(c, qsock->path); ++ ++ return NGX_OK; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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_NEW 0 ++#define NGX_QUIC_PATH_VALIDATING 1 ++#define NGX_QUIC_PATH_VALIDATED 2 ++ ++ ++#define ngx_quic_path_state_str(p) \ ++ ((p)->state == NGX_QUIC_PATH_NEW) ? "new" : \ ++ (((p)->state == NGX_QUIC_PATH_VALIDATED) ? "validated" : "validating") ++ ++ ++ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ++ 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_find_path(ngx_connection_t *c, ++ struct sockaddr *sockaddr, socklen_t socklen); ++ngx_quic_path_t *ngx_quic_add_path(ngx_connection_t *c, ++ struct sockaddr *sockaddr, socklen_t socklen); ++ ++ngx_int_t ngx_quic_update_paths(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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,1270 @@ ++ ++/* ++ * 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); ++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->send_timer_set && !qc->closing) { ++ 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->socket->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) { ++ continue; ++ } ++ ++ 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->socket->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->socket->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 defined(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 defined(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_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) { ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); ++ ++ if (ngx_queue_empty(&ctx->frames)) { ++ return 0; ++ } ++ ++ return 1; ++ } ++ } ++ ++ 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; ++ 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); ++ ++ ngx_quic_init_packet(c, ctx, &pkt); ++ ++ 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_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ qsock = qc->socket; ++ ++ 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 = qsock->cid->id; ++ pkt->dcid.len = qsock->cid->len; ++ ++ pkt->scid.data = qsock->sid.id; ++ pkt->scid.len = qsock->sid.len; ++ ++ 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 defined(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 defined(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_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ++ ngx_quic_header_t *pkt) ++{ ++ u_char *token; ++ size_t len, max; ++ uint16_t rndbytes; ++ u_char buf[NGX_QUIC_MAX_SR_PACKET]; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic handle stateless reset output"); ++ ++ if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { ++ return NGX_DECLINED; ++ } ++ ++ if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { ++ len = pkt->len - 1; ++ ++ } else { ++ max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); ++ ++ if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { ++ return NGX_ERROR; ++ } ++ ++ len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) ++ + NGX_QUIC_MIN_SR_PACKET; ++ } ++ ++ if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] &= ~NGX_QUIC_PKT_LONG; ++ buf[0] |= NGX_QUIC_PKT_FIXED_BIT; ++ ++ token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; ++ ++ if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); ++ ++ return NGX_DECLINED; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_cc(ngx_connection_t *c) ++{ ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc->draining) { ++ return NGX_OK; ++ } ++ ++ if (qc->closing ++ && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) ++ { ++ /* dot not send CC too often */ ++ return NGX_OK; ++ } ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = qc->error_level; ++ frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP ++ : NGX_QUIC_FT_CONNECTION_CLOSE; ++ frame->u.close.error_code = qc->error; ++ frame->u.close.frame_type = qc->error_ftype; ++ ++ if (qc->error_reason) { ++ frame->u.close.reason.len = ngx_strlen(qc->error_reason); ++ frame->u.close.reason.data = (u_char *) qc->error_reason; ++ } ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ qc->last_cc = ngx_current_msec; ++ ++ return ngx_quic_output(c); ++} ++ ++ ++ngx_int_t ++ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, ++ ngx_uint_t err, const char *reason) ++{ ++ ssize_t len; ++ ngx_str_t res; ++ ngx_quic_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; ++ } ++ ++ pkt.keys = ngx_quic_keys_new(c->pool); ++ if (pkt.keys == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid, ++ inpkt->version) ++ != 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_OK; ++} ++ ++ ++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]; ++ ++ expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; ++ ++ if (ngx_quic_new_token(c, c->sockaddr, c->socklen, conf->av_token_key, ++ &token, &inpkt->dcid, expires, 1) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); ++ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; ++ pkt.version = inpkt->version; ++ pkt.log = c->log; ++ ++ pkt.odcid = inpkt->dcid; ++ pkt.dcid = inpkt->scid; ++ ++ /* TODO: generate routable dcid */ ++ if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { ++ return NGX_ERROR; ++ } ++ ++ pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; ++ pkt.scid.data = dcid; ++ ++ pkt.token = token; ++ ++ res.data = buf; ++ ++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_PACKETS ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet to send len:%uz %xV", res.len, &res); ++#endif ++ ++ len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); ++ if (len < 0) { ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic retry packet sent to %xV", &pkt.dcid); ++ ++ /* ++ * RFC 9000, 17.2.5.1. Sending a Retry Packet ++ * ++ * A server MUST NOT send more than one Retry ++ * packet in response to a single UDP datagram. ++ * NGX_DONE will stop quic_input() from processing further ++ */ ++ return NGX_DONE; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ time_t expires; ++ ngx_str_t token; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; ++ ++ if (ngx_quic_new_token(c, path->sockaddr, path->socklen, ++ qc->conf->av_token_key, &token, NULL, expires, 0) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_NEW_TOKEN; ++ frame->u.token.length = token.len; ++ frame->u.token.data = token.data; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ size_t len, left; ++ uint64_t ack_delay; ++ ngx_buf_t *b; ++ ngx_uint_t i; ++ ngx_chain_t *cl, **ll; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ack_delay = ngx_current_msec - ctx->largest_received; ++ ack_delay *= 1000; ++ ack_delay >>= qc->tp.ack_delay_exponent; ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ll = &frame->data; ++ b = NULL; ++ ++ for (i = 0; i < ctx->nranges; i++) { ++ len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, ++ ctx->ranges[i].range); ++ ++ left = b ? b->end - b->last : 0; ++ ++ if (left < len) { ++ cl = ngx_quic_alloc_chain(c); ++ if (cl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ *ll = cl; ++ ll = &cl->next; ++ ++ b = cl->buf; ++ left = b->end - b->last; ++ ++ if (left < len) { ++ return NGX_ERROR; ++ } ++ } ++ ++ b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, ++ ctx->ranges[i].range); ++ ++ frame->u.ack.ranges_length += len; ++ } ++ ++ *ll = NULL; ++ ++ frame->level = ctx->level; ++ frame->type = NGX_QUIC_FT_ACK; ++ frame->u.ack.largest = ctx->largest_range; ++ frame->u.ack.delay = ack_delay; ++ frame->u.ack.range_count = ctx->nranges; ++ frame->u.ack.first_range = ctx->first_range; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t smallest, uint64_t largest) ++{ ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ctx->level; ++ frame->type = NGX_QUIC_FT_ACK; ++ frame->u.ack.largest = largest; ++ frame->u.ack.delay = 0; ++ frame->u.ack.range_count = 0; ++ frame->u.ack.first_range = largest - smallest; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ++ size_t min, ngx_quic_path_t *path) ++{ ++ size_t min_payload, pad; ++ ssize_t len, sent; ++ ngx_str_t res; ++ ngx_quic_header_t pkt; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ qc = ngx_quic_get_connection(c); ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); ++ ++ ngx_quic_init_packet(c, ctx, &pkt); ++ ++ 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,1186 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */ ++#define NGX_QUIC_IV_LEN 12 ++/* 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 ++ ++#define NGX_AES_128_GCM_SHA256 0x1301 ++#define NGX_AES_256_GCM_SHA384 0x1302 ++#define NGX_CHACHA20_POLY1305_SHA256 0x1303 ++ ++ ++#ifdef OPENSSL_IS_BORINGSSL ++#define ngx_quic_cipher_t EVP_AEAD ++#else ++#define ngx_quic_cipher_t EVP_CIPHER ++#endif ++ ++ ++typedef struct { ++ const ngx_quic_cipher_t *c; ++ const EVP_CIPHER *hp; ++ const EVP_MD *d; ++} ngx_quic_ciphers_t; ++ ++ ++typedef struct ngx_quic_secret_s { ++ ngx_str_t secret; ++ ngx_str_t key; ++ ngx_str_t iv; ++ ngx_str_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; ++}; ++ ++ ++static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, ++ const EVP_MD *digest, const u_char *prk, size_t prk_len, ++ const u_char *info, size_t info_len); ++static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, ++ const EVP_MD *digest, const u_char *secret, size_t secret_len, ++ const u_char *salt, size_t salt_len); ++ ++static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, ++ uint64_t *largest_pn); ++static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); ++static ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ++ ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); ++ ++static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ++ ngx_str_t *ad, ngx_log_t *log); ++static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ++ ngx_str_t *ad, ngx_log_t *log); ++static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ++ ngx_quic_secret_t *s, u_char *out, u_char *in); ++static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ++ ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); ++ ++static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ++ ngx_str_t *res); ++static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ++ ngx_str_t *res); ++ ++ ++static ngx_int_t ++ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, ++ enum ssl_encryption_level_t level) ++{ ++ ngx_int_t len; ++ ++ if (level == ssl_encryption_initial) { ++ id = NGX_AES_128_GCM_SHA256; ++ } ++ ++ switch (id) { ++ ++ case NGX_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 NGX_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 NGX_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_pool_t *pool, ngx_quic_keys_t *keys, ++ ngx_str_t *secret, uint32_t version) ++{ ++ size_t is_len; ++ uint8_t is[SHA256_DIGEST_LENGTH]; ++ ngx_uint_t i; ++ const EVP_MD *digest; ++ ngx_quic_secret_t *client, *server; ++ ++ static const uint8_t salt[20] = ++ "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" ++ "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; ++ static const uint8_t salt29[20] = ++ "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" ++ "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; ++ ++ 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, ++ (version & 0xff000000) ? salt29 : salt, sizeof(salt)) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ ngx_str_t iss = { ++ .data = is, ++ .len = is_len ++ }; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, ++ "quic ngx_quic_set_initial_secret"); ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, ++ "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->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; ++ ++ struct { ++ ngx_str_t label; ++ ngx_str_t *key; ++ ngx_str_t *prk; ++ } seq[] = { ++ /* labels per RFC 9001, 5.1. Packet Protection Keys */ ++ { ngx_string("tls13 client in"), &client->secret, &iss }, ++ { ngx_string("tls13 quic key"), &client->key, &client->secret }, ++ { ngx_string("tls13 quic iv"), &client->iv, &client->secret }, ++ { ngx_string("tls13 quic hp"), &client->hp, &client->secret }, ++ { ngx_string("tls13 server in"), &server->secret, &iss }, ++ { ngx_string("tls13 quic key"), &server->key, &server->secret }, ++ { ngx_string("tls13 quic iv"), &server->iv, &server->secret }, ++ { ngx_string("tls13 quic hp"), &server->hp, &server->secret }, ++ }; ++ ++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { ++ ++ if (ngx_quic_hkdf_expand(pool, digest, seq[i].key, &seq[i].label, ++ seq[i].prk->data, seq[i].prk->len) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, ++ ngx_str_t *label, const uint8_t *prk, size_t prk_len) ++{ ++ size_t info_len; ++ uint8_t *p; ++ uint8_t info[20]; ++ ++ if (out->data == NULL) { ++ out->data = ngx_pnalloc(pool, out->len); ++ if (out->data == NULL) { ++ return NGX_ERROR; ++ } ++ } ++ ++ info_len = 2 + 1 + label->len + 1; ++ ++ info[0] = 0; ++ info[1] = out->len; ++ info[2] = label->len; ++ p = ngx_cpymem(&info[3], label->data, label->len); ++ *p = '\0'; ++ ++ if (ngx_hkdf_expand(out->data, out->len, digest, ++ prk, prk_len, info, info_len) ++ != NGX_OK) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, ++ "ngx_hkdf_expand(%V) failed", label); ++ return NGX_ERROR; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, ++ "quic expand %V key len:%uz %xV", label, out->len, out); ++#endif ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, ++ const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) ++{ ++#ifdef OPENSSL_IS_BORINGSSL ++ ++ if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) ++ == 0) ++ { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++#else ++ ++ EVP_PKEY_CTX *pctx; ++ ++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); ++ if (pctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (EVP_PKEY_derive_init(pctx) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { ++ goto failed; ++ } ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_OK; ++ ++failed: ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_ERROR; ++ ++#endif ++} ++ ++ ++static ngx_int_t ++ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, ++ const u_char *secret, size_t secret_len, const u_char *salt, ++ size_t salt_len) ++{ ++#ifdef OPENSSL_IS_BORINGSSL ++ ++ if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, ++ salt_len) ++ == 0) ++ { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++#else ++ ++ EVP_PKEY_CTX *pctx; ++ ++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); ++ if (pctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (EVP_PKEY_derive_init(pctx) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { ++ goto failed; ++ } ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_OK; ++ ++failed: ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_ERROR; ++ ++#endif ++} ++ ++ ++static ngx_int_t ++ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ++ ngx_log_t *log) ++{ ++ ++#ifdef OPENSSL_IS_BORINGSSL ++ EVP_AEAD_CTX *ctx; ++ ++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, ++ EVP_AEAD_DEFAULT_TAG_LENGTH); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, ++ in->data, in->len, ad->data, ad->len) ++ != 1) ++ { ++ EVP_AEAD_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); ++ return NGX_ERROR; ++ } ++ ++ EVP_AEAD_CTX_free(ctx); ++#else ++ int len; ++ u_char *tag; ++ EVP_CIPHER_CTX *ctx; ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, ++ in->len - EVP_GCM_TLS_TAG_LEN) ++ != 1) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len = len; ++ tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len += len; ++ ++ EVP_CIPHER_CTX_free(ctx); ++#endif ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) ++{ ++ ++#ifdef OPENSSL_IS_BORINGSSL ++ EVP_AEAD_CTX *ctx; ++ ++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, ++ EVP_AEAD_DEFAULT_TAG_LENGTH); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, ++ in->data, in->len, ad->data, ad->len) ++ != 1) ++ { ++ EVP_AEAD_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); ++ return NGX_ERROR; ++ } ++ ++ EVP_AEAD_CTX_free(ctx); ++#else ++ int len; ++ EVP_CIPHER_CTX *ctx; ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len = len; ++ ++ if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len += len; ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, ++ out->data + in->len) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); ++ return NGX_ERROR; ++ } ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ out->len += EVP_GCM_TLS_TAG_LEN; ++#endif ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ++ ngx_quic_secret_t *s, u_char *out, u_char *in) ++{ ++ int outlen; ++ EVP_CIPHER_CTX *ctx; ++ u_char zero[NGX_QUIC_HP_LEN] = {0}; ++ ++#ifdef OPENSSL_IS_BORINGSSL ++ uint32_t cnt; ++ ++ ngx_memcpy(&cnt, in, sizeof(uint32_t)); ++ ++ if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { ++ CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); ++ return NGX_OK; ++ } ++#endif ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); ++ goto failed; ++ } ++ ++ if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); ++ goto failed; ++ } ++ ++ if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); ++ goto failed; ++ } ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ return NGX_OK; ++ ++failed: ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, 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_uint_t i; ++ ngx_quic_secret_t *peer_secret; ++ ngx_quic_ciphers_t ciphers; ++ ++ peer_secret = is_write ? &keys->secrets[level].server ++ : &keys->secrets[level].client; ++ ++ keys->cipher = SSL_CIPHER_get_protocol_id(cipher); ++ ++ key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); ++ ++ if (key_len == NGX_ERROR) { ++ ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher"); ++ return NGX_ERROR; ++ } ++ ++ peer_secret->secret.data = ngx_pnalloc(pool, secret_len); ++ if (peer_secret->secret.data == NULL) { ++ 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; ++ ++ struct { ++ ngx_str_t label; ++ ngx_str_t *key; ++ const uint8_t *secret; ++ } seq[] = { ++ { ngx_string("tls13 quic key"), &peer_secret->key, secret }, ++ { ngx_string("tls13 quic iv"), &peer_secret->iv, secret }, ++ { ngx_string("tls13 quic hp"), &peer_secret->hp, secret }, ++ }; ++ ++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { ++ ++ if (ngx_quic_hkdf_expand(pool, ciphers.d, seq[i].key, &seq[i].label, ++ seq[i].secret, secret_len) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_quic_keys_t * ++ngx_quic_keys_new(ngx_pool_t *pool) ++{ ++ return ngx_pcalloc(pool, sizeof(ngx_quic_keys_t)); ++} ++ ++ ++ngx_uint_t ++ngx_quic_keys_available(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level) ++{ ++ return keys->secrets[level].client.key.len != 0; ++} ++ ++ ++void ++ngx_quic_keys_discard(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level) ++{ ++ keys->secrets[level].client.key.len = 0; ++} ++ ++ ++void ++ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) ++{ ++ ngx_quic_secrets_t *current, *next, tmp; ++ ++ current = &keys->secrets[ssl_encryption_application]; ++ next = &keys->next_key; ++ ++ tmp = *current; ++ *current = *next; ++ *next = tmp; ++} ++ ++ ++ngx_int_t ++ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) ++{ ++ ngx_uint_t i; ++ ngx_quic_ciphers_t ciphers; ++ ngx_quic_secrets_t *current, *next; ++ ++ current = &keys->secrets[ssl_encryption_application]; ++ next = &keys->next_key; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); ++ ++ if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application) ++ == NGX_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ next->client.secret.len = current->client.secret.len; ++ next->client.key.len = current->client.key.len; ++ next->client.iv.len = NGX_QUIC_IV_LEN; ++ next->client.hp = current->client.hp; ++ ++ next->server.secret.len = current->server.secret.len; ++ next->server.key.len = current->server.key.len; ++ next->server.iv.len = NGX_QUIC_IV_LEN; ++ next->server.hp = current->server.hp; ++ ++ struct { ++ ngx_str_t label; ++ ngx_str_t *key; ++ ngx_str_t *secret; ++ } seq[] = { ++ { ++ ngx_string("tls13 quic ku"), ++ &next->client.secret, ++ ¤t->client.secret, ++ }, ++ { ++ ngx_string("tls13 quic key"), ++ &next->client.key, ++ &next->client.secret, ++ }, ++ { ++ ngx_string("tls13 quic iv"), ++ &next->client.iv, ++ &next->client.secret, ++ }, ++ { ++ ngx_string("tls13 quic ku"), ++ &next->server.secret, ++ ¤t->server.secret, ++ }, ++ { ++ ngx_string("tls13 quic key"), ++ &next->server.key, ++ &next->server.secret, ++ }, ++ { ++ ngx_string("tls13 quic iv"), ++ &next->server.iv, ++ &next->server.secret, ++ }, ++ }; ++ ++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { ++ ++ if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label, ++ seq[i].secret->data, seq[i].secret->len) ++ != 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 key29[16] = ++ "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; ++ static u_char nonce[NGX_QUIC_IV_LEN] = ++ "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; ++ static u_char nonce29[NGX_QUIC_IV_LEN] = ++ "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; ++ 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); ++ secret.key.data = (pkt->version & 0xff000000) ? key29 : key; ++ secret.iv.len = NGX_QUIC_IV_LEN; ++ ++ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, ++ (pkt->version & 0xff000000) ? nonce29 : nonce, ++ &in, &ad, pkt->log) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ res->len = itag.data + itag.len - start; ++ res->data = start; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, ++ ngx_str_t *salt, u_char *out, size_t len) ++{ ++ size_t is_len, info_len; ++ uint8_t *p; ++ const EVP_MD *digest; ++ ++ uint8_t is[SHA256_DIGEST_LENGTH]; ++ uint8_t info[20]; ++ ++ digest = EVP_sha256(); ++ is_len = SHA256_DIGEST_LENGTH; ++ ++ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, ++ salt->data, salt->len) ++ != NGX_OK) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "ngx_hkdf_extract(%s) failed", label); ++ return NGX_ERROR; ++ } ++ ++ info[0] = 0; ++ info[1] = len; ++ info[2] = ngx_strlen(label); ++ ++ info_len = 2 + 1 + info[2] + 1; ++ ++ if (info_len >= 20) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "ngx_quic_create_key label \"%s\" too long", label); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_cpymem(&info[3], label, info[2]); ++ *p = '\0'; ++ ++ if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "ngx_hkdf_expand(%s) failed", label); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static uint64_t ++ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, ++ uint64_t *largest_pn) ++{ ++ u_char *p; ++ uint64_t truncated_pn, expected_pn, candidate_pn; ++ uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; ++ ++ pn_nbits = ngx_min(len * 8, 62); ++ ++ p = *pos; ++ truncated_pn = *p++ ^ *mask++; ++ ++ while (--len) { ++ truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); ++ } ++ ++ *pos = p; ++ ++ expected_pn = *largest_pn + 1; ++ pn_win = 1ULL << pn_nbits; ++ pn_hwin = pn_win / 2; ++ pn_mask = pn_win - 1; ++ ++ candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; ++ ++ if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) ++ && candidate_pn < (1ULL << 62) - pn_win) ++ { ++ candidate_pn += pn_win; ++ ++ } else if (candidate_pn > expected_pn + pn_hwin ++ && candidate_pn >= pn_win) ++ { ++ candidate_pn -= pn_win; ++ } ++ ++ *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); ++ ++ return candidate_pn; ++} ++ ++ ++static void ++ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) ++{ ++ nonce[len - 4] ^= (pn & 0xff000000) >> 24; ++ nonce[len - 3] ^= (pn & 0x00ff0000) >> 16; ++ nonce[len - 2] ^= (pn & 0x0000ff00) >> 8; ++ nonce[len - 1] ^= (pn & 0x000000ff); ++} ++ ++ ++ngx_int_t ++ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) ++{ ++ if (ngx_quic_pkt_retry(pkt->flags)) { ++ return ngx_quic_create_retry_packet(pkt, res); ++ } ++ ++ return ngx_quic_create_packet(pkt, res); ++} ++ ++ ++ngx_int_t ++ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) ++{ ++ u_char *p, *sample; ++ size_t len; ++ uint64_t pn, lpn; ++ ngx_int_t pnl, rc, key_phase; ++ ngx_str_t in, ad; ++ ngx_quic_secret_t *secret; ++ ngx_quic_ciphers_t ciphers; ++ uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; ++ ++ if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ secret = &pkt->keys->secrets[pkt->level].client; ++ ++ p = pkt->raw->pos; ++ len = pkt->data + pkt->len - p; ++ ++ /* ++ * RFC 9001, 5.4.2. Header Protection Sample ++ * 5.4.3. AES-Based Header Protection ++ * 5.4.4. ChaCha20-Based Header Protection ++ * ++ * the Packet Number field is assumed to be 4 bytes long ++ * AES and ChaCha20 algorithms sample 16 bytes ++ */ ++ ++ if (len < EVP_GCM_TLS_TAG_LEN + 4) { ++ return NGX_DECLINED; ++ } ++ ++ sample = p + 4; ++ ++ /* header protection */ ++ ++ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) ++ != NGX_OK) ++ { ++ return NGX_DECLINED; ++ } ++ ++ pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); ++ ++ if (ngx_quic_short_pkt(pkt->flags)) { ++ key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; ++ ++ if (key_phase != pkt->key_phase) { ++ secret = &pkt->keys->next_key.client; ++ pkt->key_update = 1; ++ } ++ } ++ ++ lpn = *largest_pn; ++ ++ pnl = (pkt->flags & 0x03) + 1; ++ pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); ++ ++ pkt->pn = pn; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx clearflags:%xd", pkt->flags); ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx number:%uL len:%xi", pn, pnl); ++ ++ /* packet protection */ ++ ++ in.data = p; ++ in.len = len - pnl; ++ ++ ad.len = p - pkt->data; ++ ad.data = pkt->plaintext; ++ ++ ngx_memcpy(ad.data, pkt->data, ad.len); ++ ad.data[0] = pkt->flags; ++ ++ do { ++ ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; ++ } while (--pnl); ++ ++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ++ ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic ad len:%uz %xV", ad.len, &ad); ++#endif ++ ++ pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; ++ pkt->payload.data = pkt->plaintext + ad.len; ++ ++ rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, ++ nonce, &in, &ad, pkt->log); ++ if (rc != NGX_OK) { ++ return NGX_DECLINED; ++ } ++ ++ if (pkt->payload.len == 0) { ++ /* ++ * RFC 9000, 12.4. Frames and Frame Types ++ * ++ * An endpoint MUST treat receipt of a packet containing no ++ * frames as a connection error of type PROTOCOL_VIOLATION. ++ */ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); ++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ return NGX_ERROR; ++ } ++ ++ if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { ++ /* ++ * RFC 9000, Reserved Bits ++ * ++ * An endpoint MUST treat receipt of a packet that has ++ * a non-zero value for these bits, after removing both ++ * packet and header protection, as a connection error ++ * of type PROTOCOL_VIOLATION. ++ */ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic reserved bit set in packet"); ++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ return NGX_ERROR; ++ } ++ ++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet payload len:%uz %xV", ++ pkt->payload.len, &pkt->payload); ++#endif ++ ++ *largest_pn = lpn; ++ ++ return NGX_OK; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,37 @@ ++ ++/* ++ * 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) ++ ++ ++ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); ++ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ++ ngx_quic_keys_t *keys, ngx_str_t *secret, uint32_t version); ++ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ++ ngx_uint_t is_write, ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *secret, size_t secret_len); ++ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level); ++void ngx_quic_keys_discard(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level); ++void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); ++ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); ++ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); ++ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); ++ ++ ++#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,311 @@ ++ ++/* ++ * 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_path_t *path; ++ ngx_quic_socket_t *qsock, *tmp; ++ ngx_quic_client_id_t *cid; ++ ++ /* ++ * 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; ++ } ++ ++ 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; ++ } ++ ++ /* the client arrived from this path */ ++ path = ngx_quic_add_path(c, c->sockaddr, c->socklen); ++ if (path == NULL) { ++ goto failed; ++ } ++ ++ if (pkt->validated) { ++ path->state = NGX_QUIC_PATH_VALIDATED; ++ path->limited = 0; ++ } ++ ++ /* now bind socket to client and path */ ++ ngx_quic_connect(c, qsock, path, cid); ++ ++ 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; ++ } ++ ++ ngx_quic_connect(c, tmp, path, cid); ++ ++ /* use this socket as default destination */ ++ qc->socket = qsock; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic active socket is #%uL:%uL:%uL (%s)", ++ qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum, ++ ngx_quic_path_state_str(qsock->path)); ++ ++ 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--; ++ ++ if (qsock->path) { ++ ngx_quic_unref_path(c, qsock->path); ++ } ++ ++ if (qsock->cid) { ++ ngx_quic_unref_client_id(c, qsock->cid); ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic socket #%L closed nsock:%ui", ++ (int64_t) qsock->sid.seqnum, qc->nsockets); ++} ++ ++ ++void ++ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ ngx_quic_connection_t *qc; ++ ++ path->refcnt--; ++ ++ if (path->refcnt) { ++ return; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_queue_remove(&path->queue); ++ ngx_queue_insert_head(&qc->free_paths, &path->queue); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic path #%uL addr:%V removed", ++ path->seqnum, &path->addr_text); ++} ++ ++ ++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; ++ ++ ngx_insert_udp_connection(c, &qsock->udp, &id); ++ ++ 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 #%L listening at sid:%xV nsock:%ui", ++ (int64_t) sid->seqnum, &id, qc->nsockets); ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *sock, ++ ngx_quic_path_t *path, ngx_quic_client_id_t *cid) ++{ ++ sock->path = path; ++ path->refcnt++; ++ ++ sock->cid = cid; ++ cid->refcnt++; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic socket #%L connected to cid #%uL path:%uL", ++ (int64_t) sock->sid.seqnum, ++ sock->cid->seqnum, path->seqnum); ++} ++ ++ ++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; ++} ++ ++ ++ngx_quic_socket_t * ++ngx_quic_get_unconnected_socket(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ ngx_quic_socket_t *sock; ++ 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)) ++ { ++ sock = ngx_queue_data(q, ngx_quic_socket_t, queue); ++ ++ if (sock->cid == NULL) { ++ return sock; ++ } ++ } ++ ++ return NULL; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,33 @@ ++ ++/* ++ * 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); ++ ++void ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path); ++void ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *qsock, ++ ngx_quic_path_t *path, ngx_quic_client_id_t *cid); ++ ++ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); ++ngx_quic_socket_t *ngx_quic_get_unconnected_socket(ngx_connection_t *c); ++ ++ ++#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,614 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++/* ++ * RFC 9000, 7.5. Cryptographic Message Buffering ++ * ++ * Implementations MUST support buffering at least 4096 bytes of data ++ */ ++#define NGX_QUIC_MAX_BUFFERED 65535 ++ ++ ++#if BORINGSSL_API_VERSION >= 10 ++static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *secret, size_t secret_len); ++static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *secret, size_t secret_len); ++#else ++static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const uint8_t *read_secret, ++ const uint8_t *write_secret, size_t secret_len); ++#endif ++ ++static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const uint8_t *data, size_t len); ++static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); ++static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, uint8_t alert); ++static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); ++ ++ ++static SSL_QUIC_METHOD quic_method = { ++#if BORINGSSL_API_VERSION >= 10 ++ ngx_quic_set_read_secret, ++ ngx_quic_set_write_secret, ++#else ++ ngx_quic_set_encryption_secrets, ++#endif ++ ngx_quic_add_handshake_data, ++ ngx_quic_flush_flight, ++ ngx_quic_send_alert, ++}; ++ ++ ++#if BORINGSSL_API_VERSION >= 10 ++ ++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->pool, 0, qc->keys, level, ++ cipher, rsecret, secret_len) ++ != NGX_OK) ++ { ++ return 0; ++ } ++ ++ if (level == ssl_encryption_early_data) { ++ if (ngx_quic_init_streams(c) != NGX_OK) { ++ return 0; ++ } ++ } ++ ++ return 1; ++} ++ ++ ++static int ++ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *wsecret, size_t secret_len) ++{ ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_set_write_secret() level:%d", level); ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic write secret len:%uz %*xs", secret_len, ++ secret_len, wsecret); ++#endif ++ ++ if (ngx_quic_keys_set_encryption_secret(c->pool, 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->pool, 0, qc->keys, level, ++ cipher, rsecret, secret_len) ++ != NGX_OK) ++ { ++ return 0; ++ } ++ ++ if (level == ssl_encryption_early_data) { ++ if (ngx_quic_init_streams(c) != NGX_OK) { ++ return 0; ++ } ++ ++ return 1; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic write secret len:%uz %*xs", secret_len, ++ secret_len, wsecret); ++#endif ++ ++ if (ngx_quic_keys_set_encryption_secret(c->pool, 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; ++ 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 = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; ++ qc->error_reason = "unsupported protocol in ALPN extension"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic unsupported protocol in ALPN extension"); ++ return 0; ++ } ++ ++#endif ++ ++ SSL_get_peer_quic_transport_params(ssl_conn, &client_params, ++ &client_params_len); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic SSL_get_peer_quic_transport_params():" ++ " params_len:%ui", client_params_len); ++ ++ if (client_params_len == 0) { ++ /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ ++ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); ++ qc->error_reason = "missing transport parameters"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "missing transport parameters"); ++ return 0; ++ } ++ ++ p = (u_char *) client_params; ++ end = p + client_params_len; ++ ++ /* defaults for parameters not sent by client */ ++ ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t)); ++ ++ if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) ++ != NGX_OK) ++ { ++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ++ qc->error_reason = "failed to process transport parameters"; ++ ++ return 0; ++ } ++ ++ if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { ++ return 0; ++ } ++ ++ qc->client_tp_done = 1; ++ } ++ ++ ctx = ngx_quic_get_send_ctx(qc, level); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return 0; ++ } ++ ++ frame->data = ngx_quic_copy_buf(c, (u_char *) data, len); ++ if (frame->data == NGX_CHAIN_ERROR) { ++ return 0; ++ } ++ ++ frame->level = level; ++ frame->type = NGX_QUIC_FT_CRYPTO; ++ frame->u.crypto.offset = ctx->crypto_sent; ++ frame->u.crypto.length = len; ++ ++ ctx->crypto_sent += len; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return 1; ++} ++ ++ ++static int ++ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) ++{ ++#if (NGX_DEBUG) ++ ngx_connection_t *c; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_flush_flight()"); ++#endif ++ return 1; ++} ++ ++ ++static int ++ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, ++ uint8_t alert) ++{ ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_send_alert() level:%s alert:%d", ++ ngx_quic_level_name(level), (int) alert); ++ ++ /* already closed on regular shutdown */ ++ ++ qc = ngx_quic_get_connection(c); ++ if (qc == NULL) { ++ return 1; ++ } ++ ++ qc->error = NGX_QUIC_ERR_CRYPTO(alert); ++ ++ return 1; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ++ ngx_quic_frame_t *frame) ++{ ++ size_t len; ++ uint64_t last; ++ ngx_buf_t *b; ++ ngx_chain_t *cl, **ll; ++ 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_received + NGX_QUIC_MAX_BUFFERED) { ++ qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; ++ return NGX_ERROR; ++ } ++ ++ if (last <= ctx->crypto_received) { ++ 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_received) { ++ if (ngx_quic_write_chain(c, &ctx->crypto, frame->data, f->length, ++ f->offset - ctx->crypto_received) ++ == NGX_CHAIN_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ } ++ ++ ngx_quic_trim_chain(frame->data, ctx->crypto_received - f->offset); ++ ++ if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_trim_chain(ctx->crypto, last - ctx->crypto_received); ++ ctx->crypto_received = last; ++ ++ cl = ctx->crypto; ++ ll = &cl; ++ len = 0; ++ ++ while (*ll) { ++ b = (*ll)->buf; ++ ++ if (b->sync && b->pos != b->last) { ++ /* hole */ ++ break; ++ } ++ ++ len += b->last - b->pos; ++ ll = &(*ll)->next; ++ } ++ ++ ctx->crypto_received += len; ++ ctx->crypto = *ll; ++ *ll = NULL; ++ ++ if (cl) { ++ if (ngx_quic_crypto_input(c, cl) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_free_chain(c, cl); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) ++{ ++ int n, sslerr; ++ ngx_buf_t *b; ++ ngx_chain_t *cl; ++ ngx_ssl_conn_t *ssl_conn; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ssl_conn = c->ssl->connection; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", ++ (int) SSL_quic_read_level(ssl_conn), ++ (int) SSL_quic_write_level(ssl_conn)); ++ ++ for (cl = data; cl; cl = cl->next) { ++ b = cl->buf; ++ ++ if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), ++ b->pos, b->last - b->pos)) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, c->log, 0, ++ "SSL_provide_quic_data() failed"); ++ return NGX_ERROR; ++ } ++ } ++ ++ n = SSL_do_handshake(ssl_conn); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", ++ (int) SSL_quic_read_level(ssl_conn), ++ (int) SSL_quic_write_level(ssl_conn)); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); ++ ++ if (n <= 0) { ++ sslerr = SSL_get_error(ssl_conn, n); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", ++ sslerr); ++ ++ if (sslerr != SSL_ERROR_WANT_READ) { ++ ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); ++ qc->error_reason = "handshake failed"; ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ } ++ ++ if (SSL_in_init(ssl_conn)) { ++ return NGX_OK; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic handshake completed successfully"); ++ ++ c->ssl->handshaked = 1; ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; ++ ngx_quic_queue_frame(qc, frame); ++ ++ if (qc->conf->retry) { ++ if (ngx_quic_send_new_token(c, qc->socket->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_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ c->ssl->no_wait_shutdown = 1; ++ ++ ssl_conn = c->ssl->connection; ++ ++ if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic SSL_set_quic_method() failed"); ++ return NGX_ERROR; ++ } ++ ++#ifdef SSL_READ_EARLY_DATA_SUCCESS ++ if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { ++ SSL_set_quic_early_data_enabled(ssl_conn, 1); ++ } ++#endif ++ ++#if BORINGSSL_API_VERSION >= 13 ++ SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1); ++#endif ++ ++ dcid.data = qc->socket->sid.id; ++ dcid.len = qc->socket->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; ++ } ++ ++#if BORINGSSL_API_VERSION >= 11 ++ 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,1608 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_QUIC_STREAM_GONE (void *) -1 ++ ++ ++static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c); ++static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c); ++static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id); ++static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id); ++static void ngx_quic_init_stream_handler(ngx_event_t *ev); ++static void ngx_quic_init_streams_handler(ngx_connection_t *c); ++static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, ++ uint64_t id); ++static void ngx_quic_empty_handler(ngx_event_t *ev); ++static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, ++ size_t size); ++static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, ++ size_t size); ++static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ++ ngx_chain_t *in, off_t limit); ++static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); ++static void ngx_quic_stream_cleanup_handler(void *data); ++static ngx_int_t ngx_quic_control_flow(ngx_connection_t *c, uint64_t last); ++static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last); ++static ngx_int_t ngx_quic_update_max_stream_data(ngx_connection_t *c); ++static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); ++ ++ ++ngx_connection_t * ++ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) ++{ ++ uint64_t id; ++ ngx_quic_stream_t *qs, *nqs; ++ ngx_quic_connection_t *qc; ++ ++ qs = c->quic; ++ qc = ngx_quic_get_connection(qs->parent); ++ ++ if (bidi) { ++ if (qc->streams.server_streams_bidi ++ >= qc->streams.server_max_streams_bidi) ++ { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic too many server bidi streams:%uL", ++ qc->streams.server_streams_bidi); ++ return NULL; ++ } ++ ++ id = (qc->streams.server_streams_bidi << 2) ++ | NGX_QUIC_STREAM_SERVER_INITIATED; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic creating server bidi stream" ++ " streams:%uL max:%uL id:0x%xL", ++ qc->streams.server_streams_bidi, ++ qc->streams.server_max_streams_bidi, id); ++ ++ qc->streams.server_streams_bidi++; ++ ++ } else { ++ if (qc->streams.server_streams_uni ++ >= qc->streams.server_max_streams_uni) ++ { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic too many server uni streams:%uL", ++ qc->streams.server_streams_uni); ++ return NULL; ++ } ++ ++ id = (qc->streams.server_streams_uni << 2) ++ | NGX_QUIC_STREAM_SERVER_INITIATED ++ | NGX_QUIC_STREAM_UNIDIRECTIONAL; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic creating server uni stream" ++ " streams:%uL max:%uL id:0x%xL", ++ qc->streams.server_streams_uni, ++ qc->streams.server_max_streams_uni, id); ++ ++ qc->streams.server_streams_uni++; ++ } ++ ++ nqs = ngx_quic_create_stream(qs->parent, id); ++ if (nqs == NULL) { ++ return NULL; ++ } ++ ++ return nqs->connection; ++} ++ ++ ++void ++ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) ++{ ++ ngx_rbtree_node_t **p; ++ ngx_quic_stream_t *qn, *qnt; ++ ++ for ( ;; ) { ++ qn = (ngx_quic_stream_t *) node; ++ qnt = (ngx_quic_stream_t *) temp; ++ ++ p = (qn->id < qnt->id) ? &temp->left : &temp->right; ++ ++ if (*p == sentinel) { ++ break; ++ } ++ ++ temp = *p; ++ } ++ ++ *p = node; ++ node->parent = temp; ++ node->left = sentinel; ++ node->right = sentinel; ++ ngx_rbt_red(node); ++} ++ ++ ++ngx_quic_stream_t * ++ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) ++{ ++ ngx_rbtree_node_t *node, *sentinel; ++ ngx_quic_stream_t *qn; ++ ++ node = rbtree->root; ++ sentinel = rbtree->sentinel; ++ ++ while (node != sentinel) { ++ qn = (ngx_quic_stream_t *) node; ++ ++ if (id == qn->id) { ++ return qn; ++ } ++ ++ node = (id < qn->id) ? node->left : node->right; ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_int_t ++ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) ++{ ++ ngx_pool_t *pool; ++ ngx_queue_t *q; ++ ngx_event_t *rev, *wev; ++ ngx_rbtree_t *tree; ++ ngx_rbtree_node_t *node; ++ ngx_quic_stream_t *qs; ++ ++#if (NGX_DEBUG) ++ ngx_uint_t ns; ++#endif ++ ++ while (!ngx_queue_empty(&qc->streams.uninitialized)) { ++ q = ngx_queue_head(&qc->streams.uninitialized); ++ ngx_queue_remove(q); ++ ++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue); ++ pool = qs->connection->pool; ++ ++ ngx_close_connection(qs->connection); ++ ngx_destroy_pool(pool); ++ } ++ ++ tree = &qc->streams.tree; ++ ++ if (tree->root == tree->sentinel) { ++ return NGX_OK; ++ } ++ ++#if (NGX_DEBUG) ++ ns = 0; ++#endif ++ ++ for (node = ngx_rbtree_min(tree->root, tree->sentinel); ++ node; ++ node = ngx_rbtree_next(tree, node)) ++ { ++ qs = (ngx_quic_stream_t *) node; ++ ++ rev = qs->connection->read; ++ rev->error = 1; ++ rev->ready = 1; ++ ++ wev = qs->connection->write; ++ wev->error = 1; ++ wev->ready = 1; ++ ++ ngx_post_event(rev, &ngx_posted_events); ++ ++ if (rev->timer_set) { ++ ngx_del_timer(rev); ++ } ++ ++#if (NGX_DEBUG) ++ ns++; ++#endif ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic connection has %ui active streams", ns); ++ ++ return NGX_AGAIN; ++} ++ ++ ++ngx_int_t ++ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) ++{ ++ ngx_event_t *wev; ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ wev = c->write; ++ ++ if (wev->error) { ++ return NGX_OK; ++ } ++ ++ qs = c->quic; ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ 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 = c->sent; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ wev->error = 1; ++ wev->ready = 1; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_shutdown_stream(ngx_connection_t *c, int how) ++{ ++ ngx_quic_stream_t *qs; ++ ++ qs = c->quic; ++ ++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { ++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) ++ || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) ++ { ++ if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ } ++ ++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) { ++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 ++ || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) ++ { ++ 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_event_t *wev; ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ wev = c->write; ++ ++ if (wev->error) { ++ return NGX_OK; ++ } ++ ++ qs = c->quic; ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL send shutdown", qs->id); ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_STREAM; ++ frame->u.stream.off = 1; ++ frame->u.stream.len = 1; ++ frame->u.stream.fin = 1; ++ ++ frame->u.stream.stream_id = qs->id; ++ frame->u.stream.offset = c->sent; ++ frame->u.stream.length = 0; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ wev->error = 1; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_shutdown_stream_recv(ngx_connection_t *c) ++{ ++ ngx_event_t *rev; ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ rev = c->read; ++ ++ if (rev->pending_eof || rev->error) { ++ return NGX_OK; ++ } ++ ++ qs = c->quic; ++ 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, c->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); ++ ++ rev->error = 1; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_quic_stream_t * ++ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) ++{ ++ uint64_t min_id; ++ ngx_event_t *rev; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ qs = ngx_quic_find_stream(&qc->streams.tree, id); ++ ++ if (qs) { ++ return qs; ++ } ++ ++ if (qc->shutdown || qc->closing) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL is missing", id); ++ ++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ ++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { ++ if ((id >> 2) < qc->streams.server_streams_uni) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NULL; ++ } ++ ++ if ((id >> 2) < qc->streams.client_streams_uni) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ if ((id >> 2) >= qc->streams.client_max_streams_uni) { ++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; ++ return NULL; ++ } ++ ++ min_id = (qc->streams.client_streams_uni << 2) ++ | NGX_QUIC_STREAM_UNIDIRECTIONAL; ++ qc->streams.client_streams_uni = (id >> 2) + 1; ++ ++ } else { ++ ++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { ++ if ((id >> 2) < qc->streams.server_streams_bidi) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NULL; ++ } ++ ++ if ((id >> 2) < qc->streams.client_streams_bidi) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ if ((id >> 2) >= qc->streams.client_max_streams_bidi) { ++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; ++ return NULL; ++ } ++ ++ min_id = (qc->streams.client_streams_bidi << 2); ++ qc->streams.client_streams_bidi = (id >> 2) + 1; ++ } ++ ++ /* ++ * RFC 9000, 2.1. Stream Types and Identifiers ++ * ++ * successive streams of each type are created with numerically increasing ++ * stream IDs. A stream ID that is used out of order results in all ++ * streams of that type with lower-numbered stream IDs also being opened. ++ */ ++ ++#if (NGX_SUPPRESS_WARN) ++ qs = NULL; ++#endif ++ ++ for ( /* void */ ; min_id <= id; min_id += 0x04) { ++ ++ qs = ngx_quic_create_stream(c, min_id); ++ ++ if (qs == NULL) { ++ if (ngx_quic_reject_stream(c, min_id) != NGX_OK) { ++ return NULL; ++ } ++ ++ continue; ++ } ++ ++ ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); ++ ++ rev = qs->connection->read; ++ rev->handler = ngx_quic_init_stream_handler; ++ ++ if (qc->streams.initialized) { ++ ngx_post_event(rev, &ngx_posted_events); ++ } ++ } ++ ++ if (qs == NULL) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ return qs; ++} ++ ++ ++static ngx_int_t ++ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) ++{ ++ uint64_t code; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ++ ? qc->conf->stream_reject_code_uni ++ : qc->conf->stream_reject_code_bidi; ++ ++ if (code == 0) { ++ return NGX_DECLINED; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL reject err:0x%xL", id, code); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_RESET_STREAM; ++ frame->u.reset_stream.id = id; ++ frame->u.reset_stream.error_code = code; ++ frame->u.reset_stream.final_size = 0; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_STOP_SENDING; ++ frame->u.stop_sending.id = id; ++ frame->u.stop_sending.error_code = code; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_init_stream_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ngx_quic_stream_t *qs; ++ ++ c = ev->data; ++ qs = c->quic; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); ++ ++ ngx_queue_remove(&qs->queue); ++ ++ c->listening->handler(c); ++} ++ ++ ++ngx_int_t ++ngx_quic_init_streams(ngx_connection_t *c) ++{ ++ ngx_int_t rc; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc->streams.initialized) { ++ return NGX_OK; ++ } ++ ++ rc = ngx_ssl_ocsp_validate(c); ++ ++ if (rc == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_AGAIN) { ++ c->ssl->handler = ngx_quic_init_streams_handler; ++ return NGX_OK; ++ } ++ ++ ngx_quic_init_streams_handler(c); ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_init_streams_handler(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams"); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ for (q = ngx_queue_head(&qc->streams.uninitialized); ++ q != ngx_queue_sentinel(&qc->streams.uninitialized); ++ q = ngx_queue_next(q)) ++ { ++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue); ++ ngx_post_event(qs->connection->read, &ngx_posted_events); ++ } ++ ++ qc->streams.initialized = 1; ++} ++ ++ ++static ngx_quic_stream_t * ++ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) ++{ ++ ngx_log_t *log; ++ ngx_pool_t *pool; ++ ngx_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); ++ ++ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); ++ if (pool == NULL) { ++ return NULL; ++ } ++ ++ qs = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); ++ if (qs == NULL) { ++ ngx_destroy_pool(pool); ++ return NULL; ++ } ++ ++ qs->node.key = id; ++ qs->parent = c; ++ qs->id = id; ++ qs->final_size = (uint64_t) -1; ++ ++ log = ngx_palloc(pool, sizeof(ngx_log_t)); ++ if (log == NULL) { ++ ngx_destroy_pool(pool); ++ return NULL; ++ } ++ ++ *log = *c->log; ++ pool->log = log; ++ ++ sc = ngx_get_connection(c->fd, log); ++ if (sc == NULL) { ++ ngx_destroy_pool(pool); ++ return NULL; ++ } ++ ++ qs->connection = sc; ++ ++ sc->quic = qs; ++ sc->shared = 1; ++ sc->type = SOCK_STREAM; ++ sc->pool = pool; ++ sc->ssl = c->ssl; ++ sc->sockaddr = c->sockaddr; ++ sc->listening = c->listening; ++ sc->addr_text = c->addr_text; ++ sc->local_sockaddr = c->local_sockaddr; ++ sc->local_socklen = c->local_socklen; ++ sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); ++ sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; ++ ++ sc->recv = ngx_quic_stream_recv; ++ sc->send = ngx_quic_stream_send; ++ sc->send_chain = ngx_quic_stream_send_chain; ++ ++ sc->read->log = log; ++ sc->write->log = log; ++ ++ sc->read->handler = ngx_quic_empty_handler; ++ sc->write->handler = ngx_quic_empty_handler; ++ ++ log->connection = sc->number; ++ ++ if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 ++ || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) ++ { ++ sc->write->ready = 1; ++ } ++ ++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { ++ qs->send_max_data = qc->ctp.initial_max_stream_data_uni; ++ ++ } else { ++ qs->recv_max_data = qc->tp.initial_max_stream_data_uni; ++ } ++ ++ } 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_window = qs->recv_max_data; ++ ++ cln = ngx_pool_cleanup_add(pool, 0); ++ if (cln == NULL) { ++ ngx_close_connection(sc); ++ ngx_destroy_pool(pool); ++ return NULL; ++ } ++ ++ cln->handler = ngx_quic_stream_cleanup_handler; ++ cln->data = sc; ++ ++ ngx_rbtree_insert(&qc->streams.tree, &qs->node); ++ ++ return qs; ++} ++ ++ ++static void ++ngx_quic_empty_handler(ngx_event_t *ev) ++{ ++} ++ ++ ++static ssize_t ++ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ++{ ++ ssize_t len; ++ ngx_buf_t *b; ++ ngx_chain_t *cl, *in; ++ ngx_event_t *rev; ++ ngx_connection_t *pc; ++ ngx_quic_stream_t *qs; ++ ++ qs = c->quic; ++ pc = qs->parent; ++ rev = c->read; ++ ++ if (rev->error) { ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL recv eof:%d buf:%uz", ++ qs->id, rev->pending_eof, size); ++ ++ if (qs->in == NULL || qs->in->buf->sync) { ++ rev->ready = 0; ++ ++ if (qs->recv_offset == qs->final_size) { ++ 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; ++ } ++ ++ in = ngx_quic_read_chain(pc, &qs->in, 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 (qs->in == NULL) { ++ rev->ready = rev->pending_eof; ++ } ++ ++ 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(c, 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) ++{ ++ off_t n, flow; ++ ngx_event_t *wev; ++ ngx_chain_t *out, *cl; ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ 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 (wev->error) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ flow = ngx_quic_max_stream_flow(c); ++ if (flow == 0) { ++ wev->ready = 0; ++ return in; ++ } ++ ++ if (limit == 0 || limit > flow) { ++ limit = flow; ++ } ++ ++ n = 0; ++ ++ for (cl = in; cl; cl = cl->next) { ++ n += cl->buf->last - cl->buf->pos; ++ if (n >= limit) { ++ n = limit; ++ break; ++ } ++ } ++ ++ in = ngx_quic_write_chain(pc, &qs->out, in, n, 0); ++ if (in == NGX_CHAIN_ERROR) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ out = ngx_quic_read_chain(pc, &qs->out, n); ++ if (out == NGX_CHAIN_ERROR) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ return NGX_CHAIN_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 = 0; ++ ++ frame->u.stream.stream_id = qs->id; ++ frame->u.stream.offset = c->sent; ++ frame->u.stream.length = n; ++ ++ c->sent += n; ++ qc->streams.sent += n; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ if (in) { ++ wev->ready = 0; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic send_chain sent:%O", n); ++ ++ return in; ++} ++ ++ ++static size_t ++ngx_quic_max_stream_flow(ngx_connection_t *c) ++{ ++ size_t size; ++ uint64_t sent, unacked; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qs = c->quic; ++ qc = ngx_quic_get_connection(qs->parent); ++ ++ size = qc->conf->stream_buffer_size; ++ sent = c->sent; ++ unacked = sent - qs->acked; ++ ++ if (qc->streams.send_max_data == 0) { ++ qc->streams.send_max_data = qc->ctp.initial_max_data; ++ } ++ ++ if (unacked >= size) { ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic send flow hit buffer size"); ++ return 0; ++ } ++ ++ size -= unacked; ++ ++ if (qc->streams.sent >= qc->streams.send_max_data) { ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic send flow hit MAX_DATA"); ++ return 0; ++ } ++ ++ if (qc->streams.sent + size > qc->streams.send_max_data) { ++ size = qc->streams.send_max_data - qc->streams.sent; ++ } ++ ++ if (sent >= qs->send_max_data) { ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic send flow hit MAX_STREAM_DATA"); ++ return 0; ++ } ++ ++ if (sent + size > qs->send_max_data) { ++ size = qs->send_max_data - sent; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic send flow:%uz", size); ++ ++ return size; ++} ++ ++ ++static void ++ngx_quic_stream_cleanup_handler(void *data) ++{ ++ ngx_connection_t *c = data; ++ ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qs = c->quic; ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL cleanup", qs->id); ++ ++ ngx_rbtree_delete(&qc->streams.tree, &qs->node); ++ ngx_quic_free_chain(pc, qs->in); ++ ngx_quic_free_chain(pc, qs->out); ++ ++ if (qc->closing) { ++ /* schedule handler call to continue ngx_quic_close_connection() */ ++ ngx_post_event(pc->read, &ngx_posted_events); ++ return; ++ } ++ ++ if (qc->error) { ++ goto done; ++ } ++ ++ (void) ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN); ++ ++ (void) ngx_quic_update_flow(c, qs->recv_last); ++ ++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ goto done; ++ } ++ ++ 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); ++ } ++ ++done: ++ ++ (void) ngx_quic_output(pc); ++ ++ if (qc->shutdown) { ++ ngx_post_event(pc->read, &ngx_posted_events); ++ } ++} ++ ++ ++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_event_t *rev; ++ ngx_connection_t *sc; ++ 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; ++ } ++ ++ sc = qs->connection; ++ ++ rev = sc->read; ++ ++ if (rev->error) { ++ return NGX_OK; ++ } ++ ++ if (ngx_quic_control_flow(sc, last) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (qs->final_size != (uint64_t) -1 && last > qs->final_size) { ++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ if (last <= qs->recv_offset) { ++ return NGX_OK; ++ } ++ ++ if (f->offset < qs->recv_offset) { ++ ngx_quic_trim_chain(frame->data, qs->recv_offset - f->offset); ++ f->offset = qs->recv_offset; ++ } ++ ++ if (f->fin) { ++ if (qs->final_size != (uint64_t) -1 && qs->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; ++ } ++ ++ rev->pending_eof = 1; ++ qs->final_size = last; ++ } ++ ++ if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length, ++ f->offset - qs->recv_offset) ++ == NGX_CHAIN_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ if (f->offset == qs->recv_offset) { ++ rev->ready = 1; ++ ++ if (rev->active) { ++ ngx_post_event(rev, &ngx_posted_events); ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_max_data_frame(ngx_connection_t *c, ++ ngx_quic_max_data_frame_t *f) ++{ ++ ngx_event_t *wev; ++ 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.sent >= qc->streams.send_max_data) ++ { ++ ++ for (node = ngx_rbtree_min(tree->root, tree->sentinel); ++ node; ++ node = ngx_rbtree_next(tree, node)) ++ { ++ qs = (ngx_quic_stream_t *) node; ++ wev = qs->connection->write; ++ ++ if (wev->active) { ++ wev->ready = 1; ++ ngx_post_event(wev, &ngx_posted_events); ++ } ++ } ++ } ++ ++ qc->streams.send_max_data = f->max_data; ++ ++ 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->connection); ++} ++ ++ ++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) ++{ ++ uint64_t sent; ++ ngx_event_t *wev; ++ 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; ++ } ++ ++ sent = qs->connection->sent; ++ ++ if (sent >= qs->send_max_data) { ++ wev = qs->connection->write; ++ ++ if (wev->active) { ++ wev->ready = 1; ++ ngx_post_event(wev, &ngx_posted_events); ++ } ++ } ++ ++ qs->send_max_data = f->limit; ++ ++ return NGX_OK; ++} ++ ++ ++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_connection_t *sc; ++ 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; ++ } ++ ++ sc = qs->connection; ++ ++ rev = sc->read; ++ rev->error = 1; ++ rev->ready = 1; ++ ++ if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (qs->final_size != (uint64_t) -1 && qs->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->final_size = f->final_size; ++ ++ if (ngx_quic_update_flow(sc, qs->final_size) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (rev->active) { ++ ngx_post_event(rev, &ngx_posted_events); ++ } ++ ++ 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_event_t *wev; ++ 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_reset_stream(qs->connection, f->error_code) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ wev = qs->connection->write; ++ ++ if (wev->active) { ++ ngx_post_event(wev, &ngx_posted_events); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_max_streams_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (f->bidi) { ++ if (qc->streams.server_max_streams_bidi < f->limit) { ++ qc->streams.server_max_streams_bidi = f->limit; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic max_streams_bidi:%uL", f->limit); ++ } ++ ++ } else { ++ if (qc->streams.server_max_streams_uni < f->limit) { ++ qc->streams.server_max_streams_uni = f->limit; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic max_streams_uni:%uL", f->limit); ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ++{ ++ uint64_t sent, unacked; ++ ngx_event_t *wev; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); ++ if (qs == NULL) { ++ return; ++ } ++ ++ wev = qs->connection->write; ++ sent = qs->connection->sent; ++ unacked = sent - qs->acked; ++ ++ if (unacked >= qc->conf->stream_buffer_size && wev->active) { ++ wev->ready = 1; ++ ngx_post_event(wev, &ngx_posted_events); ++ } ++ ++ qs->acked += f->u.stream.length; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, qs->connection->log, 0, ++ "quic stream ack len:%uL acked:%uL unacked:%uL", ++ f->u.stream.length, qs->acked, sent - qs->acked); ++} ++ ++ ++static ngx_int_t ++ngx_quic_control_flow(ngx_connection_t *c, uint64_t last) ++{ ++ uint64_t len; ++ ngx_event_t *rev; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ rev = c->read; ++ qs = c->quic; ++ qc = ngx_quic_get_connection(qs->parent); ++ ++ if (last <= qs->recv_last) { ++ return NGX_OK; ++ } ++ ++ len = last - qs->recv_last; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic flow control msd:%uL/%uL md:%uL/%uL", ++ last, qs->recv_max_data, qc->streams.recv_last + len, ++ qc->streams.recv_max_data); ++ ++ qs->recv_last += len; ++ ++ if (!rev->error && 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_connection_t *c, uint64_t last) ++{ ++ uint64_t len; ++ ngx_event_t *rev; ++ ngx_connection_t *pc; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ rev = c->read; ++ qs = c->quic; ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (last <= qs->recv_offset) { ++ return NGX_OK; ++ } ++ ++ len = last - qs->recv_offset; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic flow update %uL", last); ++ ++ qs->recv_offset += len; ++ ++ if (!rev->pending_eof && !rev->error ++ && qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) ++ { ++ if (ngx_quic_update_max_stream_data(c) != 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_connection_t *c) ++{ ++ uint64_t recv_max_data; ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qs = c->quic; ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ 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_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic flow update msd:%uL", 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; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) ++{ ++ if (!rev->active && !rev->ready) { ++ rev->active = 1; ++ ++ } else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) { ++ rev->active = 0; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat) ++{ ++ if (!wev->active && !wev->ready) { ++ wev->active = 1; ++ ++ } else if (wev->active && wev->ready) { ++ wev->active = 0; ++ } ++ ++ return NGX_OK; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,295 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#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 ++ ++ ++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_connection_t *c, 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; ++ ++ token->len = iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE; ++ token->data = ngx_pnalloc(c->pool, token->len); ++ if (token->data == NULL) { ++ 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, c->log, 0, ++ "quic new token len:%uz %xV", token->len, token); ++#endif ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, ++ ngx_uint_t no_port, u_char buf[20]) ++{ ++ size_t len; ++ u_char *data; ++ ngx_sha1_t sha1; ++ struct sockaddr_in *sin; ++#if (NGX_HAVE_INET6) ++ struct sockaddr_in6 *sin6; ++#endif ++ ++ len = (size_t) socklen; ++ data = (u_char *) sockaddr; ++ ++ if (no_port) { ++ switch (sockaddr->sa_family) { ++ ++#if (NGX_HAVE_INET6) ++ case AF_INET6: ++ sin6 = (struct sockaddr_in6 *) sockaddr; ++ ++ len = sizeof(struct in6_addr); ++ data = sin6->sin6_addr.s6_addr; ++ ++ break; ++#endif ++ ++ case AF_INET: ++ sin = (struct sockaddr_in *) sockaddr; ++ ++ len = sizeof(in_addr_t); ++ data = (u_char *) &sin->sin_addr; ++ ++ break; ++ } ++ } ++ ++ ngx_sha1_init(&sha1); ++ ngx_sha1_update(&sha1, data, len); ++ ngx_sha1_final(buf, &sha1); ++} ++ ++ ++ngx_int_t ++ngx_quic_validate_token(ngx_connection_t *c, u_char *key, ++ ngx_quic_header_t *pkt) ++{ ++ int len, tlen, iv_len; ++ u_char *iv, *p; ++ time_t now, exp; ++ size_t total; ++ ngx_str_t odcid; ++ EVP_CIPHER_CTX *ctx; ++ const EVP_CIPHER *cipher; ++ ++ u_char addr_hash[20]; ++ u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; ++ ++ /* Retry token or NEW_TOKEN in a previous connection */ ++ ++ cipher = EVP_aes_256_cbc(); ++ iv = pkt->token.data; ++ iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; ++ ++ /* sanity checks */ ++ ++ if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) { ++ goto garbage; ++ } ++ ++ if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { ++ goto garbage; ++ } ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { ++ EVP_CIPHER_CTX_free(ctx); ++ return NGX_ERROR; ++ } ++ ++ p = pkt->token.data + iv_len; ++ len = pkt->token.len - iv_len; ++ ++ if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ goto garbage; ++ } ++ total = len; ++ ++ if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { ++ EVP_CIPHER_CTX_free(ctx); ++ goto garbage; ++ } ++ total += tlen; ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ if (total < (20 + sizeof(time_t) + 2)) { ++ goto garbage; ++ } ++ ++ p = tdec + 20; ++ ++ ngx_memcpy(&exp, p, sizeof(time_t)); ++ p += sizeof(time_t); ++ ++ pkt->retried = (*p++ == 1); ++ ++ ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash); ++ ++ if (ngx_memcmp(tdec, addr_hash, 20) != 0) { ++ goto bad_token; ++ } ++ ++ odcid.len = *p++; ++ if (odcid.len) { ++ if (odcid.len > NGX_QUIC_MAX_CID_LEN) { ++ goto bad_token; ++ } ++ ++ if ((size_t)(tdec + total - p) < odcid.len) { ++ goto bad_token; ++ } ++ ++ odcid.data = p; ++ } ++ ++ now = ngx_time(); ++ ++ if (now > exp) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); ++ return NGX_DECLINED; ++ } ++ ++ if (odcid.len) { ++ pkt->odcid.len = odcid.len; ++ pkt->odcid.data = ngx_pstrdup(c->pool, &odcid); ++ if (pkt->odcid.data == NULL) { ++ return NGX_ERROR; ++ } ++ ++ } 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,23 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++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_connection_t *c, 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,2170 @@ ++ ++/* ++ * 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)) ++ ++#define NGX_QUIC_VERSION(c) (0xff000000 + (c)) ++ ++ ++static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); ++static ngx_uint_t ngx_quic_varint_len(uint64_t value); ++static void ngx_quic_build_int(u_char **pos, uint64_t value); ++ ++static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); ++static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); ++static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, ++ u_char **out); ++static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, ++ u_char *dst); ++ ++static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ++ size_t dcid_len); ++static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_supported_version(uint32_t version); ++static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); ++ ++static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp); ++static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp); ++ ++static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ++ ngx_uint_t frame_type); ++static size_t ngx_quic_create_ping(u_char *p); ++static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ++ ngx_chain_t *ranges); ++static size_t ngx_quic_create_reset_stream(u_char *p, ++ ngx_quic_reset_stream_frame_t *rs); ++static size_t ngx_quic_create_stop_sending(u_char *p, ++ ngx_quic_stop_sending_frame_t *ss); ++static size_t ngx_quic_create_crypto(u_char *p, ++ ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); ++static size_t ngx_quic_create_hs_done(u_char *p); ++static size_t ngx_quic_create_new_token(u_char *p, ++ ngx_quic_new_token_frame_t *token); ++static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, ++ ngx_chain_t *data); ++static size_t ngx_quic_create_max_streams(u_char *p, ++ ngx_quic_max_streams_frame_t *ms); ++static size_t ngx_quic_create_max_stream_data(u_char *p, ++ ngx_quic_max_stream_data_frame_t *ms); ++static size_t ngx_quic_create_max_data(u_char *p, ++ ngx_quic_max_data_frame_t *md); ++static size_t ngx_quic_create_path_challenge(u_char *p, ++ ngx_quic_path_challenge_frame_t *pc); ++static size_t ngx_quic_create_path_response(u_char *p, ++ ngx_quic_path_challenge_frame_t *pc); ++static size_t ngx_quic_create_new_connection_id(u_char *p, ++ ngx_quic_new_conn_id_frame_t *rcid); ++static size_t ngx_quic_create_retire_connection_id(u_char *p, ++ ngx_quic_retire_cid_frame_t *rcid); ++static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f); ++ ++static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, ++ uint16_t id, ngx_quic_tp_t *dst); ++ ++ ++uint32_t ngx_quic_versions[] = { ++ /* QUICv1 */ ++ 0x00000001, ++ NGX_QUIC_VERSION(29), ++ NGX_QUIC_VERSION(30), ++ NGX_QUIC_VERSION(31), ++ NGX_QUIC_VERSION(32), ++}; ++ ++#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_DECLINED; ++ } ++ ++ return NGX_OK; ++ } ++ ++ if (ngx_quic_parse_long_header(pkt) != NGX_OK) { ++ return NGX_DECLINED; ++ } ++ ++ if (!ngx_quic_supported_version(pkt->version)) { ++ return NGX_ABORT; ++ } ++ ++ if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { ++ return NGX_DECLINED; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) ++{ ++ u_char *p, *end; ++ ++ p = pkt->raw->pos; ++ end = pkt->data + pkt->len; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx short flags:%xd", pkt->flags); ++ ++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); ++ return NGX_ERROR; ++ } ++ ++ pkt->dcid.len = dcid_len; ++ ++ p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read dcid"); ++ return NGX_ERROR; ++ } ++ ++ pkt->raw->pos = p; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_long_header(ngx_quic_header_t *pkt) ++{ ++ u_char *p, *end; ++ uint8_t idlen; ++ ++ p = pkt->raw->pos; ++ end = pkt->data + pkt->len; ++ ++ p = ngx_quic_read_uint32(p, end, &pkt->version); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read version"); ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx long flags:%xd version:%xD", ++ pkt->flags, pkt->version); ++ ++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_read_uint8(p, end, &idlen); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read dcid len"); ++ return NGX_ERROR; ++ } ++ ++ if (idlen > NGX_QUIC_CID_LEN_MAX) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet dcid is too long"); ++ return NGX_ERROR; ++ } ++ ++ pkt->dcid.len = idlen; ++ ++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read dcid"); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_read_uint8(p, end, &idlen); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read scid len"); ++ return NGX_ERROR; ++ } ++ ++ if (idlen > NGX_QUIC_CID_LEN_MAX) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet scid is too long"); ++ return NGX_ERROR; ++ } ++ ++ pkt->scid.len = idlen; ++ ++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read scid"); ++ return NGX_ERROR; ++ } ++ ++ pkt->raw->pos = p; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_supported_version(uint32_t version) ++{ ++ ngx_uint_t i; ++ ++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { ++ if (ngx_quic_versions[i] == version) { ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) ++{ ++ u_char *p, *end; ++ uint64_t varint; ++ ++ p = pkt->raw->pos; ++ end = pkt->raw->last; ++ ++ pkt->log->action = "parsing quic long header"; ++ ++ if (ngx_quic_pkt_in(pkt->flags)) { ++ ++ if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic UDP datagram is too small for initial packet"); ++ return NGX_DECLINED; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic failed to parse token length"); ++ return NGX_ERROR; ++ } ++ ++ pkt->token.len = varint; ++ ++ p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet too small to read token data"); ++ return NGX_ERROR; ++ } ++ ++ pkt->level = ssl_encryption_initial; ++ ++ } else if (ngx_quic_pkt_zrtt(pkt->flags)) { ++ pkt->level = ssl_encryption_early_data; ++ ++ } else if (ngx_quic_pkt_hs(pkt->flags)) { ++ pkt->level = ssl_encryption_handshake; ++ ++ } else { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic bad packet type"); ++ return NGX_DECLINED; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx %s len:%uL", ++ ngx_quic_level_name(pkt->level), varint); ++ ++ if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", ++ ngx_quic_level_name(pkt->level)); ++ return NGX_ERROR; ++ } ++ ++ pkt->raw->pos = p; ++ pkt->len = p + varint - pkt->data; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, ++ ngx_str_t *dcid) ++{ ++ size_t len, offset; ++ ++ if (n == 0) { ++ goto failed; ++ } ++ ++ if (ngx_quic_long_pkt(*data)) { ++ if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { ++ goto failed; ++ } ++ ++ len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; ++ offset = NGX_QUIC_LONG_DCID_OFFSET; ++ ++ } else { ++ len = NGX_QUIC_SERVER_CID_LEN; ++ offset = NGX_QUIC_SHORT_DCID_OFFSET; ++ } ++ ++ if (n < len + offset) { ++ goto failed; ++ } ++ ++ dcid->len = len; ++ dcid->data = &data[offset]; ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); ++ ++ return NGX_ERROR; ++} ++ ++ ++size_t ++ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) ++{ ++ u_char *p, *start; ++ ngx_uint_t i; ++ ++ p = start = out; ++ ++ *p++ = pkt->flags; ++ ++ /* ++ * The Version field of a Version Negotiation packet ++ * MUST be set to 0x00000000 ++ */ ++ p = ngx_quic_write_uint32(p, 0); ++ ++ *p++ = pkt->dcid.len; ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *p++ = pkt->scid.len; ++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); ++ ++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { ++ p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); ++ } ++ ++ return p - start; ++} ++ ++ ++/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */ ++size_t ++ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) ++{ ++ size_t len; ++ ++ if ngx_quic_short_pkt(pkt->flags) { ++ ++ len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN; ++ if (len > pkt_len) { ++ return 0; ++ } ++ ++ return pkt_len - len; ++ } ++ ++ /* flags, version, dcid and scid with lengths and zero-length token */ ++ len = 5 + 2 + pkt->dcid.len + pkt->scid.len ++ + (pkt->level == ssl_encryption_initial ? 1 : 0); ++ ++ if (len > pkt_len) { ++ return 0; ++ } ++ ++ /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */ ++ len += ngx_quic_varint_len(pkt_len - len) ++ + pkt->num_len + EVP_GCM_TLS_TAG_LEN; ++ ++ if (len > pkt_len) { ++ return 0; ++ } ++ ++ return pkt_len - len; ++} ++ ++ ++size_t ++ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) ++{ ++ return ngx_quic_short_pkt(pkt->flags) ++ ? ngx_quic_create_short_header(pkt, out, pnp) ++ : ngx_quic_create_long_header(pkt, out, pnp); ++} ++ ++ ++static size_t ++ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp) ++{ ++ size_t rem_len; ++ u_char *p, *start; ++ ++ rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN; ++ ++ if (out == NULL) { ++ return 5 + 2 + pkt->dcid.len + pkt->scid.len ++ + ngx_quic_varint_len(rem_len) + pkt->num_len ++ + (pkt->level == ssl_encryption_initial ? 1 : 0); ++ } ++ ++ p = start = out; ++ ++ *p++ = pkt->flags; ++ ++ p = ngx_quic_write_uint32(p, pkt->version); ++ ++ *p++ = pkt->dcid.len; ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *p++ = pkt->scid.len; ++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); ++ ++ if (pkt->level == ssl_encryption_initial) { ++ ngx_quic_build_int(&p, 0); ++ } ++ ++ ngx_quic_build_int(&p, rem_len); ++ ++ *pnp = p; ++ ++ switch (pkt->num_len) { ++ case 1: ++ *p++ = pkt->trunc; ++ break; ++ case 2: ++ p = ngx_quic_write_uint16(p, pkt->trunc); ++ break; ++ case 3: ++ p = ngx_quic_write_uint24(p, pkt->trunc); ++ break; ++ case 4: ++ p = ngx_quic_write_uint32(p, pkt->trunc); ++ break; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp) ++{ ++ u_char *p, *start; ++ ++ if (out == NULL) { ++ return 1 + pkt->dcid.len + pkt->num_len; ++ } ++ ++ p = start = out; ++ ++ *p++ = pkt->flags; ++ ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *pnp = p; ++ ++ switch (pkt->num_len) { ++ case 1: ++ *p++ = pkt->trunc; ++ break; ++ case 2: ++ p = ngx_quic_write_uint16(p, pkt->trunc); ++ break; ++ case 3: ++ p = ngx_quic_write_uint24(p, pkt->trunc); ++ break; ++ case 4: ++ p = ngx_quic_write_uint32(p, pkt->trunc); ++ break; ++ } ++ ++ return p - start; ++} ++ ++ ++size_t ++ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, ++ u_char **start) ++{ ++ u_char *p; ++ ++ p = out; ++ ++ *p++ = pkt->odcid.len; ++ p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); ++ ++ *start = p; ++ ++ *p++ = 0xff; ++ ++ p = ngx_quic_write_uint32(p, pkt->version); ++ ++ *p++ = pkt->dcid.len; ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *p++ = pkt->scid.len; ++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); ++ ++ p = ngx_cpymem(p, pkt->token.data, pkt->token.len); ++ ++ return p - out; ++} ++ ++ ++ssize_t ++ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ++ ngx_quic_frame_t *f) ++{ ++ u_char *p; ++ uint64_t varint; ++ ngx_buf_t *b; ++ ngx_uint_t i; ++ ++ b = f->data->buf; ++ ++ p = start; ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic failed to obtain quic frame type"); ++ return NGX_ERROR; ++ } ++ ++ if (varint > NGX_QUIC_FT_LAST) { ++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic unknown frame type 0x%xL", varint); ++ return NGX_ERROR; ++ } ++ ++ f->type = varint; ++ ++ if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { ++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ return NGX_ERROR; ++ } ++ ++ switch (f->type) { ++ ++ case NGX_QUIC_FT_CRYPTO: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.crypto.length); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ b->last = p; ++ ++ break; ++ ++ case NGX_QUIC_FT_PADDING: ++ ++ while (p < end && *p == NGX_QUIC_FT_PADDING) { ++ p++; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ ++ if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest)) ++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay)) ++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count)) ++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range)))) ++ { ++ goto error; ++ } ++ ++ b->pos = p; ++ ++ /* process all ranges to get bounds, values are ignored */ ++ for (i = 0; i < f->u.ack.range_count; i++) { ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p) { ++ p = ngx_quic_parse_int(p, end, &varint); ++ } ++ ++ if (p == NULL) { ++ goto error; ++ } ++ } ++ ++ b->last = p; ++ ++ f->u.ack.ranges_length = b->last - b->pos; ++ ++ if (f->type == NGX_QUIC_FT_ACK_ECN) { ++ ++ if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0)) ++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1)) ++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce)))) ++ { ++ goto error; ++ } ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", ++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PING: ++ break; ++ ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.ncid.retire > f->u.ncid.seqnum) { ++ goto error; ++ } ++ ++ p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { ++ goto error; ++ } ++ ++ p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.close.error_code); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { ++ p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); ++ if (p == NULL) { ++ goto error; ++ } ++ } ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ f->u.close.reason.len = varint; ++ ++ p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, ++ &f->u.close.reason.data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ case NGX_QUIC_FT_STREAM1: ++ case NGX_QUIC_FT_STREAM2: ++ case NGX_QUIC_FT_STREAM3: ++ case NGX_QUIC_FT_STREAM4: ++ case NGX_QUIC_FT_STREAM5: ++ case NGX_QUIC_FT_STREAM6: ++ case NGX_QUIC_FT_STREAM7: ++ ++ f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0; ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->type & NGX_QUIC_STREAM_FRAME_OFF) { ++ f->u.stream.off = 1; ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream.offset); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ } else { ++ f->u.stream.off = 0; ++ f->u.stream.offset = 0; ++ } ++ ++ if (f->type & NGX_QUIC_STREAM_FRAME_LEN) { ++ f->u.stream.len = 1; ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream.length); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ } else { ++ f->u.stream.len = 0; ++ f->u.stream.length = end - p; /* up to packet end */ ++ } ++ ++ p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ b->last = p; ++ ++ f->type = NGX_QUIC_FT_STREAM; ++ break; ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ ++ if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id)) ++ && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code)) ++ && (p = ngx_quic_parse_int(p, end, ++ &f->u.reset_stream.final_size)))) ++ { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STOP_SENDING: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAMS_BLOCKED: ++ case NGX_QUIC_FT_STREAMS_BLOCKED2: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.streams_blocked.limit > 0x1000000000000000) { ++ goto error; ++ } ++ ++ f->u.streams_blocked.bidi = ++ (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ case NGX_QUIC_FT_MAX_STREAMS2: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.max_streams.limit > 0x1000000000000000) { ++ goto error; ++ } ++ ++ f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; ++ ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_DATA_BLOCKED: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ ++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ ++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ default: ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic unknown frame type 0x%xi", f->type); ++ return NGX_ERROR; ++ } ++ ++ f->level = pkt->level; ++ ++ return p - start; ++ ++error: ++ ++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic failed to parse frame type:0x%xi", f->type); ++ ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) ++{ ++ uint8_t ptype; ++ ++ /* ++ * RFC 9000, 12.4. Frames and Frame Types: Table 3 ++ * ++ * Frame permissions per packet: 4 bits: IH01 ++ */ ++ static uint8_t ngx_quic_frame_masks[] = { ++ /* PADDING */ 0xF, ++ /* PING */ 0xF, ++ /* ACK */ 0xD, ++ /* ACK_ECN */ 0xD, ++ /* RESET_STREAM */ 0x3, ++ /* STOP_SENDING */ 0x3, ++ /* CRYPTO */ 0xD, ++ /* NEW_TOKEN */ 0x0, /* only sent by server */ ++ /* STREAM */ 0x3, ++ /* STREAM1 */ 0x3, ++ /* STREAM2 */ 0x3, ++ /* STREAM3 */ 0x3, ++ /* STREAM4 */ 0x3, ++ /* STREAM5 */ 0x3, ++ /* STREAM6 */ 0x3, ++ /* STREAM7 */ 0x3, ++ /* MAX_DATA */ 0x3, ++ /* MAX_STREAM_DATA */ 0x3, ++ /* MAX_STREAMS */ 0x3, ++ /* MAX_STREAMS2 */ 0x3, ++ /* DATA_BLOCKED */ 0x3, ++ /* STREAM_DATA_BLOCKED */ 0x3, ++ /* STREAMS_BLOCKED */ 0x3, ++ /* STREAMS_BLOCKED2 */ 0x3, ++ /* NEW_CONNECTION_ID */ 0x3, ++ /* RETIRE_CONNECTION_ID */ 0x3, ++ /* PATH_CHALLENGE */ 0x3, ++ /* PATH_RESPONSE */ 0x1, ++ /* CONNECTION_CLOSE */ 0xF, ++ /* CONNECTION_CLOSE2 */ 0x3, ++ /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ ++ }; ++ ++ if (ngx_quic_long_pkt(pkt->flags)) { ++ ++ if (ngx_quic_pkt_in(pkt->flags)) { ++ ptype = 8; /* initial */ ++ ++ } else if (ngx_quic_pkt_hs(pkt->flags)) { ++ ptype = 4; /* handshake */ ++ ++ } else { ++ ptype = 2; /* zero-rtt */ ++ } ++ ++ } else { ++ ptype = 1; /* application data */ ++ } ++ ++ if (ptype & ngx_quic_frame_masks[frame_type]) { ++ return NGX_OK; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic frame type 0x%xi is not " ++ "allowed in packet with flags 0x%xd", ++ frame_type, pkt->flags); ++ ++ return NGX_DECLINED; ++} ++ ++ ++ssize_t ++ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, ++ uint64_t *gap, uint64_t *range) ++{ ++ u_char *p; ++ ++ p = start; ++ ++ p = ngx_quic_parse_int(p, end, gap); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse ack frame gap"); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_parse_int(p, end, range); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse ack frame range"); ++ return NGX_ERROR; ++ } ++ ++ return p - start; ++} ++ ++ ++size_t ++ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(gap); ++ len += ngx_quic_varint_len(range); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, gap); ++ ngx_quic_build_int(&p, range); ++ ++ return p - start; ++} ++ ++ ++ssize_t ++ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) ++{ ++ /* ++ * RFC 9002, 2. Conventions and Definitions ++ * ++ * Ack-eliciting frames: All frames other than ACK, PADDING, and ++ * CONNECTION_CLOSE are considered ack-eliciting. ++ */ ++ f->need_ack = 1; ++ ++ switch (f->type) { ++ case NGX_QUIC_FT_PING: ++ return ngx_quic_create_ping(p); ++ ++ case NGX_QUIC_FT_ACK: ++ f->need_ack = 0; ++ return ngx_quic_create_ack(p, &f->u.ack, f->data); ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ return ngx_quic_create_reset_stream(p, &f->u.reset_stream); ++ ++ case NGX_QUIC_FT_STOP_SENDING: ++ return ngx_quic_create_stop_sending(p, &f->u.stop_sending); ++ ++ case NGX_QUIC_FT_CRYPTO: ++ return ngx_quic_create_crypto(p, &f->u.crypto, f->data); ++ ++ case NGX_QUIC_FT_HANDSHAKE_DONE: ++ return ngx_quic_create_hs_done(p); ++ ++ case NGX_QUIC_FT_NEW_TOKEN: ++ return ngx_quic_create_new_token(p, &f->u.token); ++ ++ case NGX_QUIC_FT_STREAM: ++ return ngx_quic_create_stream(p, &f->u.stream, f->data); ++ ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP: ++ f->need_ack = 0; ++ return ngx_quic_create_close(p, f); ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ return ngx_quic_create_max_streams(p, &f->u.max_streams); ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ return ngx_quic_create_max_data(p, &f->u.max_data); ++ ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ return ngx_quic_create_path_challenge(p, &f->u.path_challenge); ++ ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ return ngx_quic_create_path_response(p, &f->u.path_response); ++ ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ return ngx_quic_create_new_connection_id(p, &f->u.ncid); ++ ++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID: ++ return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); ++ ++ default: ++ /* BUG: unsupported frame type generated */ ++ return NGX_ERROR; ++ } ++} ++ ++ ++static size_t ++ngx_quic_create_ping(u_char *p) ++{ ++ u_char *start; ++ ++ if (p == NULL) { ++ return ngx_quic_varint_len(NGX_QUIC_FT_PING); ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_PING); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) ++{ ++ size_t len; ++ u_char *start; ++ ngx_buf_t *b; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); ++ len += ngx_quic_varint_len(ack->largest); ++ len += ngx_quic_varint_len(ack->delay); ++ len += ngx_quic_varint_len(ack->range_count); ++ len += ngx_quic_varint_len(ack->first_range); ++ len += ack->ranges_length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); ++ ngx_quic_build_int(&p, ack->largest); ++ ngx_quic_build_int(&p, ack->delay); ++ ngx_quic_build_int(&p, ack->range_count); ++ ngx_quic_build_int(&p, ack->first_range); ++ ++ while (ranges) { ++ b = ranges->buf; ++ p = ngx_cpymem(p, b->pos, b->last - b->pos); ++ ranges = ranges->next; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM); ++ len += ngx_quic_varint_len(rs->id); ++ len += ngx_quic_varint_len(rs->error_code); ++ len += ngx_quic_varint_len(rs->final_size); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM); ++ ngx_quic_build_int(&p, rs->id); ++ ngx_quic_build_int(&p, rs->error_code); ++ ngx_quic_build_int(&p, rs->final_size); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); ++ len += ngx_quic_varint_len(ss->id); ++ len += ngx_quic_varint_len(ss->error_code); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); ++ ngx_quic_build_int(&p, ss->id); ++ ngx_quic_build_int(&p, ss->error_code); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, ++ ngx_chain_t *data) ++{ ++ size_t len; ++ u_char *start; ++ ngx_buf_t *b; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); ++ len += ngx_quic_varint_len(crypto->offset); ++ len += ngx_quic_varint_len(crypto->length); ++ len += crypto->length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); ++ ngx_quic_build_int(&p, crypto->offset); ++ ngx_quic_build_int(&p, crypto->length); ++ ++ while (data) { ++ b = data->buf; ++ p = ngx_cpymem(p, b->pos, b->last - b->pos); ++ data = data->next; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_hs_done(u_char *p) ++{ ++ u_char *start; ++ ++ if (p == NULL) { ++ return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); ++ len += ngx_quic_varint_len(token->length); ++ len += token->length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); ++ ngx_quic_build_int(&p, token->length); ++ p = ngx_cpymem(p, token->data, token->length); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, ++ ngx_chain_t *data) ++{ ++ size_t len; ++ u_char *start, type; ++ ngx_buf_t *b; ++ ++ type = NGX_QUIC_FT_STREAM; ++ ++ if (sf->off) { ++ type |= NGX_QUIC_STREAM_FRAME_OFF; ++ } ++ ++ if (sf->len) { ++ type |= NGX_QUIC_STREAM_FRAME_LEN; ++ } ++ ++ if (sf->fin) { ++ type |= NGX_QUIC_STREAM_FRAME_FIN; ++ } ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(type); ++ len += ngx_quic_varint_len(sf->stream_id); ++ ++ if (sf->off) { ++ len += ngx_quic_varint_len(sf->offset); ++ } ++ ++ if (sf->len) { ++ len += ngx_quic_varint_len(sf->length); ++ } ++ ++ len += sf->length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, type); ++ ngx_quic_build_int(&p, sf->stream_id); ++ ++ if (sf->off) { ++ ngx_quic_build_int(&p, sf->offset); ++ } ++ ++ if (sf->len) { ++ ngx_quic_build_int(&p, sf->length); ++ } ++ ++ while (data) { ++ b = data->buf; ++ p = ngx_cpymem(p, b->pos, b->last - b->pos); ++ data = data->next; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) ++{ ++ size_t len; ++ u_char *start; ++ ngx_uint_t type; ++ ++ type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(type); ++ len += ngx_quic_varint_len(ms->limit); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, type); ++ ngx_quic_build_int(&p, ms->limit); ++ ++ return p - start; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, ++ ngx_quic_tp_t *dst) ++{ ++ uint64_t varint; ++ ngx_str_t str; ++ ++ varint = 0; ++ ngx_str_null(&str); ++ ++ switch (id) { ++ ++ case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: ++ /* zero-length option */ ++ if (end - p != 0) { ++ return NGX_ERROR; ++ } ++ dst->disable_active_migration = 1; ++ return NGX_OK; ++ ++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: ++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: ++ case NGX_QUIC_TP_INITIAL_MAX_DATA: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: ++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT: ++ case NGX_QUIC_TP_MAX_ACK_DELAY: ++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_SCID: ++ ++ str.len = end - p; ++ str.data = p; ++ break; ++ ++ default: ++ return NGX_DECLINED; ++ } ++ ++ switch (id) { ++ ++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: ++ dst->max_idle_timeout = varint; ++ break; ++ ++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: ++ dst->max_udp_payload_size = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_DATA: ++ dst->initial_max_data = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: ++ dst->initial_max_stream_data_bidi_local = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: ++ dst->initial_max_stream_data_bidi_remote = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: ++ dst->initial_max_stream_data_uni = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: ++ dst->initial_max_streams_bidi = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: ++ dst->initial_max_streams_uni = varint; ++ break; ++ ++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT: ++ dst->ack_delay_exponent = varint; ++ break; ++ ++ case NGX_QUIC_TP_MAX_ACK_DELAY: ++ dst->max_ack_delay = varint; ++ break; ++ ++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: ++ dst->active_connection_id_limit = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_SCID: ++ dst->initial_scid = str; ++ break; ++ ++ default: ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ++ ngx_log_t *log) ++{ ++ uint64_t id, len; ++ ngx_int_t rc; ++ ++ while (p < end) { ++ p = ngx_quic_parse_int(p, end, &id); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse transport param id"); ++ return NGX_ERROR; ++ } ++ ++ switch (id) { ++ case NGX_QUIC_TP_ORIGINAL_DCID: ++ case NGX_QUIC_TP_PREFERRED_ADDRESS: ++ case NGX_QUIC_TP_RETRY_SCID: ++ case NGX_QUIC_TP_SR_TOKEN: ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic client sent forbidden transport param" ++ " id:0x%xL", id); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &len); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse" ++ " transport param id:0x%xL length", id); ++ return NGX_ERROR; ++ } ++ ++ rc = ngx_quic_parse_transport_param(p, p + len, id, tp); ++ ++ if (rc == NGX_ERROR) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse" ++ " transport param id:0x%xL data", id); ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_DECLINED) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic %s transport param id:0x%xL, skipped", ++ (id % 31 == 27) ? "reserved" : "unknown", id); ++ } ++ ++ p += len; ++ } ++ ++ if (p != end) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic trailing garbage in" ++ " transport parameters: bytes:%ui", ++ end - p); ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic transport parameters parsed ok"); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp disable active migration: %ui", ++ tp->disable_active_migration); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", ++ tp->max_idle_timeout); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_udp_payload_size:%ui", ++ tp->max_udp_payload_size); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", ++ tp->initial_max_data); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_stream_data_bidi_local:%ui", ++ tp->initial_max_stream_data_bidi_local); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_stream_data_bidi_remote:%ui", ++ tp->initial_max_stream_data_bidi_remote); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_stream_data_uni:%ui", ++ tp->initial_max_stream_data_uni); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp initial_max_streams_bidi:%ui", ++ tp->initial_max_streams_bidi); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp initial_max_streams_uni:%ui", ++ tp->initial_max_streams_uni); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp ack_delay_exponent:%ui", ++ tp->ack_delay_exponent); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", ++ tp->max_ack_delay); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp active_connection_id_limit:%ui", ++ tp->active_connection_id_limit); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp initial source_connection_id len:%uz %xV", ++ tp->initial_scid.len, &tp->initial_scid); ++ ++ return NGX_OK; ++} ++ ++ ++static size_t ++ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); ++ len += ngx_quic_varint_len(ms->id); ++ len += ngx_quic_varint_len(ms->limit); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); ++ ngx_quic_build_int(&p, ms->id); ++ ngx_quic_build_int(&p, ms->limit); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); ++ len += ngx_quic_varint_len(md->max_data); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); ++ ngx_quic_build_int(&p, md->max_data); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE); ++ len += sizeof(pc->data); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE); ++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); ++ len += sizeof(pc->data); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); ++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); ++ len += ngx_quic_varint_len(ncid->seqnum); ++ len += ngx_quic_varint_len(ncid->retire); ++ len++; ++ len += ncid->len; ++ len += NGX_QUIC_SR_TOKEN_LEN; ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); ++ ngx_quic_build_int(&p, ncid->seqnum); ++ ngx_quic_build_int(&p, ncid->retire); ++ *p++ = ncid->len; ++ p = ngx_cpymem(p, ncid->cid, ncid->len); ++ p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_retire_connection_id(u_char *p, ++ ngx_quic_retire_cid_frame_t *rcid) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); ++ len += ngx_quic_varint_len(rcid->sequence_number); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); ++ ngx_quic_build_int(&p, rcid->sequence_number); ++ ++ return p - start; ++} ++ ++ ++ngx_int_t ++ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) ++{ ++ ngx_uint_t nstreams; ++ ++ ngx_memzero(tp, sizeof(ngx_quic_tp_t)); ++ ++ /* ++ * set by ngx_memzero(): ++ * ++ * tp->disable_active_migration = 0; ++ * tp->original_dcid = { 0, NULL }; ++ * tp->initial_scid = { 0, NULL }; ++ * tp->retry_scid = { 0, NULL }; ++ * tp->sr_token = { 0 } ++ * tp->sr_enabled = 0 ++ * tp->preferred_address = NULL ++ */ ++ ++ tp->max_idle_timeout = qcf->timeout; ++ ++ tp->max_udp_payload_size = qcf->mtu; ++ ++ nstreams = qcf->max_concurrent_streams_bidi ++ + qcf->max_concurrent_streams_uni; ++ ++ tp->initial_max_data = nstreams * qcf->stream_buffer_size; ++ tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size; ++ tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size; ++ tp->initial_max_stream_data_uni = qcf->stream_buffer_size; ++ ++ tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi; ++ tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni; ++ ++ tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; ++ tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ++ ++ tp->active_connection_id_limit = 2; ++ 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,395 @@ ++ ++/* ++ * 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; ++ u_char *data; ++} ngx_quic_new_token_frame_t; ++ ++/* ++ * common layout for CRYPTO and STREAM frames; ++ * conceptually, CRYPTO frame is also a stream ++ * frame lacking some properties ++ */ ++typedef struct { ++ uint64_t offset; ++ uint64_t length; ++} ngx_quic_ordered_frame_t; ++ ++typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; ++ ++ ++typedef struct { ++ /* initial fields same as in ngx_quic_ordered_frame_t */ ++ uint64_t offset; ++ uint64_t length; ++ ++ uint64_t stream_id; ++ unsigned off:1; ++ unsigned len:1; ++ unsigned fin:1; ++} ngx_quic_stream_frame_t; ++ ++ ++typedef struct { ++ uint64_t max_data; ++} ngx_quic_max_data_frame_t; ++ ++ ++typedef struct { ++ uint64_t error_code; ++ uint64_t frame_type; ++ ngx_str_t reason; ++} ngx_quic_close_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t error_code; ++ uint64_t final_size; ++} ngx_quic_reset_stream_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t error_code; ++} ngx_quic_stop_sending_frame_t; ++ ++ ++typedef struct { ++ uint64_t limit; ++ ngx_uint_t bidi; /* unsigned: bidi:1 */ ++} ngx_quic_streams_blocked_frame_t; ++ ++ ++typedef struct { ++ uint64_t limit; ++ ngx_uint_t bidi; /* unsigned: bidi:1 */ ++} ngx_quic_max_streams_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t limit; ++} ngx_quic_max_stream_data_frame_t; ++ ++ ++typedef struct { ++ uint64_t limit; ++} ngx_quic_data_blocked_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t limit; ++} ngx_quic_stream_data_blocked_frame_t; ++ ++ ++typedef struct { ++ uint64_t sequence_number; ++} ngx_quic_retire_cid_frame_t; ++ ++ ++typedef struct { ++ u_char data[8]; ++} ngx_quic_path_challenge_frame_t; ++ ++ ++typedef struct ngx_quic_frame_s ngx_quic_frame_t; ++ ++struct ngx_quic_frame_s { ++ ngx_uint_t type; ++ enum ssl_encryption_level_t level; ++ ngx_queue_t queue; ++ uint64_t pnum; ++ size_t plen; ++ ngx_msec_t first; ++ ngx_msec_t last; ++ ssize_t len; ++ unsigned need_ack:1; ++ unsigned pkt_need_ack:1; ++ unsigned flush:1; ++ ++ ngx_chain_t *data; ++ union { ++ ngx_quic_ack_frame_t ack; ++ ngx_quic_crypto_frame_t crypto; ++ ngx_quic_ordered_frame_t ord; ++ ngx_quic_new_conn_id_frame_t ncid; ++ ngx_quic_new_token_frame_t token; ++ ngx_quic_stream_frame_t stream; ++ ngx_quic_max_data_frame_t max_data; ++ ngx_quic_close_frame_t close; ++ ngx_quic_reset_stream_frame_t reset_stream; ++ ngx_quic_stop_sending_frame_t stop_sending; ++ ngx_quic_streams_blocked_frame_t streams_blocked; ++ ngx_quic_max_streams_frame_t max_streams; ++ ngx_quic_max_stream_data_frame_t max_stream_data; ++ ngx_quic_data_blocked_frame_t data_blocked; ++ ngx_quic_stream_data_blocked_frame_t stream_data_blocked; ++ ngx_quic_retire_cid_frame_t retire_cid; ++ ngx_quic_path_challenge_frame_t path_challenge; ++ ngx_quic_path_challenge_frame_t path_response; ++ } u; ++}; ++ ++ ++typedef struct { ++ ngx_log_t *log; ++ ++ ngx_quic_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 */ ++ 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; ++} 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 67408b4a12c0 src/http/modules/ngx_http_ssl_module.c +--- a/src/http/modules/ngx_http_ssl_module.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/modules/ngx_http_ssl_module.c Tue Jan 04 18:14:15 2022 -0500 +@@ -419,16 +419,22 @@ + unsigned char *outlen, const unsigned char *in, unsigned int inlen, + void *arg) + { +- unsigned int srvlen; +- unsigned char *srv; ++#if (NGX_HTTP_V3) ++ const char *fmt; ++#endif ++ unsigned int srvlen; ++ unsigned char *srv; + #if (NGX_DEBUG) +- unsigned int i; ++ unsigned int i; + #endif +-#if (NGX_HTTP_V2) +- ngx_http_connection_t *hc; ++#if (NGX_HTTP_V2 || NGX_HTTP_V3) ++ ngx_http_connection_t *hc; + #endif +-#if (NGX_HTTP_V2 || NGX_DEBUG) +- ngx_connection_t *c; ++#if (NGX_HTTP_V3 && NGX_HTTP_V3_HQ) ++ ngx_http_v3_srv_conf_t *h3scf; ++#endif ++#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) ++ ngx_connection_t *c; + + c = ngx_ssl_get_connection(ssl_conn); + #endif +@@ -441,14 +447,46 @@ + } + #endif + +-#if (NGX_HTTP_V2) ++#if (NGX_HTTP_V2 || NGX_HTTP_V3) + hc = c->data; ++#endif + ++#if (NGX_HTTP_V2) + if (hc->addr_conf->http2) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; + } else + #endif ++#if (NGX_HTTP_V3) ++ if (hc->addr_conf->http3) { ++ ++#if (NGX_HTTP_V3_HQ) ++ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); ++ ++ if (h3scf->hq) { ++ srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; ++ srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; ++ fmt = NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT; ++ } else ++#endif ++ { ++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; ++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; ++ fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT; ++ } ++ ++ /* QUIC draft */ ++ ++ if (ngx_quic_version(c) > 1) { ++ srv = ngx_pnalloc(c->pool, sizeof("\x05h3-xx") - 1); ++ if (srv == NULL) { ++ return SSL_TLSEXT_ERR_NOACK; ++ } ++ srvlen = ngx_sprintf(srv, fmt, ngx_quic_version(c)) - srv; ++ } ++ ++ } else ++#endif + { + srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; +@@ -1240,6 +1278,7 @@ + ngx_http_ssl_init(ngx_conf_t *cf) + { + ngx_uint_t a, p, s; ++ const char *name; + ngx_http_conf_addr_t *addr; + ngx_http_conf_port_t *port; + ngx_http_ssl_srv_conf_t *sscf; +@@ -1289,22 +1328,38 @@ + addr = port[p].addrs.elts; + for (a = 0; a < port[p].addrs.nelts; a++) { + +- if (!addr[a].opt.ssl) { ++ if (!addr[a].opt.ssl && !addr[a].opt.http3) { + continue; + } + ++ if (addr[a].opt.http3) { ++ name = "http3"; ++ ++ } else { ++ name = "ssl"; ++ } ++ + cscf = addr[a].default_server; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + + if (sscf->certificates) { ++ ++ if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "\"ssl_protocols\" must enable TLSv1.3 for " ++ "the \"listen ... %s\" directive in %s:%ui", ++ name, cscf->file_name, cscf->line); ++ return NGX_ERROR; ++ } ++ + continue; + } + + if (!sscf->reject_handshake) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " +- "the \"listen ... ssl\" directive in %s:%ui", +- cscf->file_name, cscf->line); ++ "the \"listen ... %s\" directive in %s:%ui", ++ name, cscf->file_name, cscf->line); + return NGX_ERROR; + } + +@@ -1325,8 +1380,8 @@ + + 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; + } + } +diff -r 67408b4a12c0 src/http/ngx_http.c +--- a/src/http/ngx_http.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http.c Tue Jan 04 18:14:15 2022 -0500 +@@ -1200,7 +1200,10 @@ + 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 @@ + } + + port->family = sa->sa_family; ++ port->type = lsopt->type; + port->port = p; + port->addrs.elts = NULL; + +@@ -1236,6 +1240,9 @@ + #if (NGX_HTTP_V2) + ngx_uint_t http2; + #endif ++#if (NGX_HTTP_V3) ++ ngx_uint_t http3; ++#endif + + /* + * we cannot compare whole sockaddr struct's as kernel +@@ -1271,6 +1278,9 @@ + #if (NGX_HTTP_V2) + http2 = lsopt->http2 || addr[i].opt.http2; + #endif ++#if (NGX_HTTP_V3) ++ http3 = lsopt->http3 || addr[i].opt.http3; ++#endif + + if (lsopt->set) { + +@@ -1307,6 +1317,9 @@ + #if (NGX_HTTP_V2) + addr[i].opt.http2 = http2; + #endif ++#if (NGX_HTTP_V3) ++ addr[i].opt.http3 = http3; ++#endif + + return NGX_OK; + } +@@ -1349,6 +1362,17 @@ + + #endif + ++#if (NGX_HTTP_V3 && !defined NGX_QUIC) ++ ++ if (lsopt->http3) { ++ ngx_conf_log_error(NGX_LOG_WARN, cf, 0, ++ "nginx was built with OpenSSL that lacks QUIC " ++ "support, HTTP/3 is not enabled for %V", ++ &lsopt->addr_text); ++ } ++ ++#endif ++ + addr = ngx_array_push(&port->addrs); + if (addr == NULL) { + return NGX_ERROR; +@@ -1770,6 +1794,7 @@ + } + #endif + ++ ls->type = addr->opt.type; + ls->backlog = addr->opt.backlog; + ls->rcvbuf = addr->opt.rcvbuf; + ls->sndbuf = addr->opt.sndbuf; +@@ -1805,6 +1830,12 @@ + ls->reuseport = addr->opt.reuseport; + #endif + ++ ls->wildcard = addr->opt.wildcard; ++ ++#if (NGX_HTTP_V3) ++ ls->quic = addr->opt.http3; ++#endif ++ + return ls; + } + +@@ -1837,6 +1868,9 @@ + #if (NGX_HTTP_V2) + addrs[i].conf.http2 = addr[i].opt.http2; + #endif ++#if (NGX_HTTP_V3) ++ addrs[i].conf.http3 = addr[i].opt.http3; ++#endif + addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL +@@ -1902,6 +1936,9 @@ + #if (NGX_HTTP_V2) + addrs6[i].conf.http2 = addr[i].opt.http2; + #endif ++#if (NGX_HTTP_V3) ++ addrs6[i].conf.http3 = addr[i].opt.http3; ++#endif + addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL +diff -r 67408b4a12c0 src/http/ngx_http.h +--- a/src/http/ngx_http.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http.h Tue Jan 04 18:14:15 2022 -0500 +@@ -20,6 +20,8 @@ + 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 @@ + #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_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 @@ + #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 67408b4a12c0 src/http/ngx_http_core_module.c +--- a/src/http/ngx_http_core_module.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http_core_module.c Tue Jan 04 18:14:15 2022 -0500 +@@ -3897,6 +3897,7 @@ + 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) +@@ -4095,6 +4096,19 @@ + #endif + } + ++ if (ngx_strcmp(value[n].data, "http3") == 0) { ++#if (NGX_HTTP_V3) ++ lsopt.http3 = 1; ++ lsopt.type = SOCK_DGRAM; ++ continue; ++#else ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "the \"http3\" parameter requires " ++ "ngx_http_v3_module"); ++ return NGX_CONF_ERROR; ++#endif ++ } ++ + if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { + + if (ngx_strcmp(&value[n].data[13], "on") == 0) { +@@ -4196,6 +4210,12 @@ + return NGX_CONF_ERROR; + } + ++#if (NGX_HTTP_SSL && NGX_HTTP_V3) ++ if (lsopt.ssl && lsopt.http3) { ++ return "\"ssl\" parameter is incompatible with \"http3\""; ++ } ++#endif ++ + for (n = 0; n < u.naddrs; n++) { + lsopt.sockaddr = u.addrs[n].sockaddr; + lsopt.socklen = u.addrs[n].socklen; +diff -r 67408b4a12c0 src/http/ngx_http_core_module.h +--- a/src/http/ngx_http_core_module.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http_core_module.h Tue Jan 04 18:14:15 2022 -0500 +@@ -75,6 +75,7 @@ + unsigned wildcard:1; + unsigned ssl:1; + unsigned http2:1; ++ unsigned http3:1; + #if (NGX_HAVE_INET6) + unsigned ipv6only:1; + #endif +@@ -86,6 +87,7 @@ + int backlog; + int rcvbuf; + int sndbuf; ++ int type; + #if (NGX_HAVE_SETFIB) + int setfib; + #endif +@@ -237,6 +239,7 @@ + + unsigned ssl:1; + unsigned http2:1; ++ unsigned http3:1; + unsigned proxy_protocol:1; + }; + +@@ -266,6 +269,7 @@ + + 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 67408b4a12c0 src/http/ngx_http_request.c +--- a/src/http/ngx_http_request.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http_request.c Tue Jan 04 18:14:15 2022 -0500 +@@ -31,10 +31,6 @@ + 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); +@@ -52,7 +48,6 @@ + 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); +@@ -331,6 +326,13 @@ + } + #endif + ++#if (NGX_HTTP_V3) ++ if (hc->addr_conf->http3) { ++ ngx_http_v3_init(c); ++ return; ++ } ++#endif ++ + #if (NGX_HTTP_SSL) + { + ngx_http_ssl_srv_conf_t *sscf; +@@ -952,6 +954,14 @@ + #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: +@@ -2121,7 +2131,7 @@ + } + + +-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; +@@ -2213,7 +2223,7 @@ + } + + +-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; +@@ -2736,6 +2746,13 @@ + } + #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) { +@@ -2950,6 +2967,19 @@ + + #endif + ++#if (NGX_HTTP_V3) ++ ++ if (c->quic) { ++ if (c->read->error) { ++ err = 0; ++ goto closed; ++ } ++ ++ return; ++ } ++ ++#endif ++ + #if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { +@@ -3614,7 +3644,7 @@ + } + + +-static void ++void + ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) + { + ngx_connection_t *c; +@@ -3701,7 +3731,12 @@ + + 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) { +@@ -3774,6 +3809,12 @@ + + #endif + ++#if (NGX_HTTP_V3) ++ if (c->quic) { ++ ngx_http_v3_reset_connection(c); ++ } ++#endif ++ + #if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); + #endif +diff -r 67408b4a12c0 src/http/ngx_http_request.h +--- a/src/http/ngx_http_request.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http_request.h Tue Jan 04 18:14:15 2022 -0500 +@@ -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 +@@ -321,6 +322,10 @@ + #endif + #endif + ++#if (NGX_HTTP_V3 || NGX_COMPAT) ++ ngx_http_v3_session_t *v3_session; ++#endif ++ + ngx_chain_t *busy; + ngx_int_t nbusy; + +@@ -449,6 +454,7 @@ + + 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; + +@@ -541,6 +547,7 @@ + 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 67408b4a12c0 src/http/ngx_http_request_body.c +--- a/src/http/ngx_http_request_body.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http_request_body.c Tue Jan 04 18:14:15 2022 -0500 +@@ -92,6 +92,13 @@ + } + #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 @@ + } + #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 @@ + } + #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; + } +@@ -917,11 +942,7 @@ + + if (r->expect_tested + || r->headers_in.expect == NULL +- || r->http_version < NGX_HTTP_VERSION_11 +-#if (NGX_HTTP_V2) +- || r->stream != NULL +-#endif +- ) ++ || r->http_version != NGX_HTTP_VERSION_11) + { + return NGX_OK; + } +diff -r 67408b4a12c0 src/http/ngx_http_upstream.c +--- a/src/http/ngx_http_upstream.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http_upstream.c Tue Jan 04 18:14:15 2022 -0500 +@@ -525,6 +525,13 @@ + } + #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); + } +@@ -1358,6 +1365,19 @@ + } + #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 67408b4a12c0 src/http/ngx_http_write_filter_module.c +--- a/src/http/ngx_http_write_filter_module.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/http/ngx_http_write_filter_module.c Tue Jan 04 18:14:15 2022 -0500 +@@ -239,6 +239,10 @@ + r->out = NULL; + c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + ++ if (last) { ++ r->response_sent = 1; ++ } ++ + return NGX_OK; + } + +@@ -345,6 +349,10 @@ + + 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,115 @@ ++ ++/* ++ * 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_connection_t *pc; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_connection_t *hc; ++ ngx_http_v3_session_t *h3c; ++ ++ pc = c->quic->parent; ++ hc = pc->data; ++ ++ if (hc->v3_session) { ++ return NGX_OK; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); ++ ++ h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t)); ++ if (h3c == NULL) { ++ goto failed; ++ } ++ ++ h3c->max_push_id = (uint64_t) -1; ++ h3c->goaway_push_id = (uint64_t) -1; ++ ++ ngx_queue_init(&h3c->blocked); ++ ngx_queue_init(&h3c->pushing); ++ ++ h3c->keepalive.log = pc->log; ++ h3c->keepalive.data = pc; ++ h3c->keepalive.handler = ngx_http_v3_keepalive_handler; ++ h3c->keepalive.cancelable = 1; ++ ++ cln = ngx_pool_cleanup_add(pc->pool, 0); ++ if (cln == NULL) { ++ goto failed; ++ } ++ ++ cln->handler = ngx_http_v3_cleanup_session; ++ cln->data = h3c; ++ ++ hc->v3_session = h3c; ++ ++ return ngx_http_v3_send_settings(c); ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, ++ "failed to create http3 session"); ++ return NGX_ERROR; ++} ++ ++ ++static void ++ngx_http_v3_keepalive_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ++ c = ev->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler"); ++ ++ ngx_quic_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); ++ } ++} ++ ++ ++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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,166 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_HTTP_V3_H_INCLUDED_ ++#define _NGX_HTTP_V3_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_HTTP_V3_ALPN_PROTO "\x02h3" ++#define NGX_HTTP_V3_ALPN_DRAFT_FMT "\x05h3-%02uD" ++ ++#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" ++#define NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT "\x05hq-%02uD" ++ ++#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_HEADER_LIST_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->parent->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->parent, code, reason) ++ ++#define ngx_http_v3_shutdown_connection(c, code, reason) \ ++ ngx_quic_shutdown_connection(c->quic->parent, code, reason) ++ ++ ++typedef struct { ++ size_t max_table_capacity; ++ ngx_uint_t max_blocked_streams; ++ ngx_uint_t max_concurrent_pushes; ++ ngx_uint_t max_concurrent_streams; ++#if (NGX_HTTP_V3_HQ) ++ ngx_flag_t hq; ++#endif ++ ngx_quic_conf_t quic; ++} ngx_http_v3_srv_conf_t; ++ ++ ++typedef struct { ++ ngx_flag_t push_preload; ++ ngx_flag_t push; ++ ngx_array_t *pushes; ++} ngx_http_v3_loc_conf_t; ++ ++ ++struct ngx_http_v3_parse_s { ++ size_t header_limit; ++ ngx_http_v3_parse_headers_t headers; ++ ngx_http_v3_parse_data_t body; ++ ngx_array_t *cookies; ++}; ++ ++ ++struct ngx_http_v3_session_s { ++ ngx_http_v3_dynamic_table_t table; ++ ++ ngx_event_t keepalive; ++ ngx_uint_t nrequests; ++ ++ ngx_queue_t blocked; ++ ngx_uint_t nblocked; ++ ++ ngx_queue_t pushing; ++ ngx_uint_t npushing; ++ uint64_t next_push_id; ++ uint64_t max_push_id; ++ uint64_t goaway_push_id; ++ ++ off_t total_bytes; ++ off_t payload_bytes; ++ ++ ngx_uint_t goaway; /* unsigned goaway:1; */ ++ ++ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; ++}; ++ ++ ++void ngx_http_v3_init(ngx_connection_t *c); ++void ngx_http_v3_reset_connection(ngx_connection_t *c); ++ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); ++ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); ++ ++ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); ++ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); ++ ++ ++extern ngx_module_t ngx_http_v3_module; ++ ++ ++#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,1538 @@ ++ ++/* ++ * 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; ++ } ++ ++ h = r->headers_out.link.elts; ++ ++ for (i = 0; i < r->headers_out.link.nelts; i++) { ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 parse link: \"%V\"", &h[i]->value); ++ ++ start = h[i]->value.data; ++ end = h[i]->value.data + h[i]->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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,539 @@ ++ ++/* ++ * 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_max_concurrent_pushes"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_num_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), ++ NULL }, ++ ++ { ngx_string("http3_max_concurrent_streams"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_num_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), ++ NULL }, ++ ++#if (NGX_HTTP_V3_HQ) ++ { ngx_string("http3_hq"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, hq), ++ NULL }, ++#endif ++ ++ { ngx_string("http3_push"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ++ ngx_http_v3_push, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ 0, ++ NULL }, ++ ++ { ngx_string("http3_push_preload"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ offsetof(ngx_http_v3_loc_conf_t, push_preload), ++ NULL }, ++ ++ { ngx_string("http3_stream_buffer_size"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_size_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size), ++ NULL }, ++ ++ { ngx_string("quic_retry"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.retry), ++ NULL }, ++ ++ { ngx_string("quic_gso"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), ++ NULL }, ++ ++ { ngx_string("quic_mtu"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_size_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.mtu), ++ &ngx_http_quic_mtu_post }, ++ ++ { ngx_string("quic_host_key"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_http_quic_host_key, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ 0, ++ NULL }, ++ ++ ngx_null_command ++}; ++ ++ ++static ngx_http_module_t ngx_http_v3_module_ctx = { ++ ngx_http_v3_add_variables, /* preconfiguration */ ++ NULL, /* postconfiguration */ ++ ++ NULL, /* create main configuration */ ++ NULL, /* init main configuration */ ++ ++ ngx_http_v3_create_srv_conf, /* create server configuration */ ++ ngx_http_v3_merge_srv_conf, /* merge server configuration */ ++ ++ ngx_http_v3_create_loc_conf, /* create location configuration */ ++ ngx_http_v3_merge_loc_conf /* merge location configuration */ ++}; ++ ++ ++ngx_module_t ngx_http_v3_module = { ++ NGX_MODULE_V1, ++ &ngx_http_v3_module_ctx, /* module context */ ++ ngx_http_v3_commands, /* module directives */ ++ NGX_HTTP_MODULE, /* module type */ ++ NULL, /* init master */ ++ NULL, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ ++ ++static ngx_http_variable_t ngx_http_v3_vars[] = { ++ ++ { ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 }, ++ ++ ngx_http_null_variable ++}; ++ ++static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); ++ ++ ++static ngx_int_t ++ngx_http_v3_variable(ngx_http_request_t *r, ++ ngx_http_variable_value_t *v, uintptr_t data) ++{ ++ if (r->connection->quic) { ++#if (NGX_HTTP_V3_HQ) ++ ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); ++ ++ if (h3scf->hq) { ++ v->len = sizeof("hq") - 1; ++ v->valid = 1; ++ v->no_cacheable = 0; ++ v->not_found = 0; ++ v->data = (u_char *) "hq"; ++ ++ return NGX_OK; ++ } ++ ++#endif ++ ++ v->len = sizeof("h3") - 1; ++ v->valid = 1; ++ v->no_cacheable = 0; ++ v->not_found = 0; ++ v->data = (u_char *) "h3"; ++ ++ return NGX_OK; ++ } ++ ++ *v = ngx_http_variable_null_value; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_add_variables(ngx_conf_t *cf) ++{ ++ ngx_http_variable_t *var, *v; ++ ++ for (v = ngx_http_v3_vars; v->name.len; v++) { ++ var = ngx_http_add_variable(cf, &v->name, v->flags); ++ if (var == NULL) { ++ return NGX_ERROR; ++ } ++ ++ var->get_handler = v->get_handler; ++ var->data = v->data; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void * ++ngx_http_v3_create_srv_conf(ngx_conf_t *cf) ++{ ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); ++ if (h3scf == NULL) { ++ return NULL; ++ } ++ ++ /* ++ * set by ngx_pcalloc(): ++ * ++ * h3scf->quic.host_key = { 0, NULL } ++ * h3scf->quic.stream_reject_code_uni = 0; ++ * h3scf->quic.disable_active_migration = 0; ++ * h3scf->quic.timeout = 0; ++ * h3scf->max_blocked_streams = 0; ++ */ ++ h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; ++ h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; ++ h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; ++#if (NGX_HTTP_V3_HQ) ++ h3scf->hq = NGX_CONF_UNSET; ++#endif ++ ++ h3scf->quic.mtu = NGX_CONF_UNSET_SIZE; ++ h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; ++ h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; ++ h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS; ++ h3scf->quic.retry = NGX_CONF_UNSET; ++ h3scf->quic.gso_enabled = NGX_CONF_UNSET; ++ h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; ++ h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; ++ ++ return h3scf; ++} ++ ++ ++static char * ++ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ++{ ++ ngx_http_v3_srv_conf_t *prev = parent; ++ ngx_http_v3_srv_conf_t *conf = child; ++ ++ ngx_http_ssl_srv_conf_t *sscf; ++ ++ ngx_conf_merge_uint_value(conf->max_concurrent_pushes, ++ prev->max_concurrent_pushes, 10); ++ ++ ngx_conf_merge_uint_value(conf->max_concurrent_streams, ++ prev->max_concurrent_streams, 128); ++ ++ conf->max_blocked_streams = conf->max_concurrent_streams; ++ ++#if (NGX_HTTP_V3_HQ) ++ ngx_conf_merge_value(conf->hq, prev->hq, 0); ++#endif ++ ++ ++ ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu, ++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); ++ ++ ngx_conf_merge_size_value(conf->quic.stream_buffer_size, ++ prev->quic.stream_buffer_size, ++ 65536); ++ ++ conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams; ++ ++ ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); ++ ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0); ++ ++ ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, ""); ++ ++ 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,2005 @@ ++ ++/* ++ * 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; ++ } ++ } ++ ++ 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) { ++ 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,1687 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++#if (NGX_HTTP_V3_HQ) ++static void ngx_http_v3_init_hq_stream(ngx_connection_t *c); ++#endif ++static void ngx_http_v3_init_request_stream(ngx_connection_t *c); ++static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); ++static void ngx_http_v3_cleanup_request(void *data); ++static void ngx_http_v3_process_request(ngx_event_t *rev); ++static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ++ ngx_str_t *name, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, ++ ngx_str_t *name, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ++ ngx_str_t *name, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); ++static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, ++ ngx_chain_t *in); ++ ++ ++static const struct { ++ ngx_str_t name; ++ ngx_uint_t method; ++} ngx_http_v3_methods[] = { ++ ++ { ngx_string("GET"), NGX_HTTP_GET }, ++ { ngx_string("POST"), NGX_HTTP_POST }, ++ { ngx_string("HEAD"), NGX_HTTP_HEAD }, ++ { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, ++ { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, ++ { ngx_string("PUT"), NGX_HTTP_PUT }, ++ { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, ++ { ngx_string("DELETE"), NGX_HTTP_DELETE }, ++ { ngx_string("COPY"), NGX_HTTP_COPY }, ++ { ngx_string("MOVE"), NGX_HTTP_MOVE }, ++ { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, ++ { ngx_string("LOCK"), NGX_HTTP_LOCK }, ++ { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, ++ { ngx_string("PATCH"), NGX_HTTP_PATCH }, ++ { ngx_string("TRACE"), NGX_HTTP_TRACE }, ++ { ngx_string("CONNECT"), NGX_HTTP_CONNECT } ++}; ++ ++ ++void ++ngx_http_v3_init(ngx_connection_t *c) ++{ ++ ngx_http_connection_t *hc, *phc; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ hc = c->data; ++ ++ hc->ssl = 1; ++ ++ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); ++ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); ++ ++ if (c->quic == NULL) { ++ h3scf->quic.timeout = clcf->keepalive_timeout; ++ ngx_quic_run(c, &h3scf->quic); ++ return; ++ } ++ ++ phc = ngx_http_quic_get_connection(c); ++ ++ if (phc->ssl_servername) { ++ hc->ssl_servername = phc->ssl_servername; ++ hc->conf_ctx = phc->conf_ctx; ++ ++ ngx_set_connection_log(c, clcf->error_log); ++ } ++ ++#if (NGX_HTTP_V3_HQ) ++ if (h3scf->hq) { ++ ngx_http_v3_init_hq_stream(c); ++ return; ++ } ++#endif ++ ++ if (ngx_http_v3_init_session(c) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ ngx_http_v3_init_uni_stream(c); ++ ++ } else { ++ ngx_http_v3_init_request_stream(c); ++ } ++} ++ ++ ++#if (NGX_HTTP_V3_HQ) ++ ++static void ++ngx_http_v3_init_hq_stream(ngx_connection_t *c) ++{ ++ uint64_t n; ++ ngx_event_t *rev; ++ ngx_http_connection_t *hc; ++ ngx_http_core_loc_conf_t *clcf; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init hq stream"); ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1); ++#endif ++ ++ hc = c->data; ++ ++ /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */ ++ ++ if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ ngx_quic_finalize_connection(c->quic->parent, ++ NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, ++ "unexpected uni stream"); ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ n = c->quic->id >> 2; ++ ++ if (n >= clcf->keepalive_requests) { ++ ngx_quic_finalize_connection(c->quic->parent, ++ NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, ++ "reached maximum number of requests"); ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ if (ngx_current_msec - c->quic->parent->start_time ++ > clcf->keepalive_time) ++ { ++ ngx_quic_finalize_connection(c->quic->parent, ++ NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, ++ "reached maximum time for requests"); ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ rev = c->read; ++ ++ if (rev->ready) { ++ rev->handler(rev); ++ return; ++ } ++ ++ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ ngx_reusable_connection(c, 1); ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++} ++ ++#endif ++ ++ ++static void ++ngx_http_v3_init_request_stream(ngx_connection_t *c) ++{ ++ uint64_t n; ++ ngx_event_t *rev; ++ ngx_http_connection_t *hc; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_core_loc_conf_t *clcf; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1); ++#endif ++ ++ hc = c->data; ++ ++ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ n = c->quic->id >> 2; ++ ++ if (n >= clcf->keepalive_requests * 2) { ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "too many requests per connection"); ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (h3c->goaway) { ++ c->close = 1; ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ if (n + 1 == clcf->keepalive_requests ++ || ngx_current_msec - c->quic->parent->start_time ++ > clcf->keepalive_time) ++ { ++ h3c->goaway = 1; ++ ++ if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, ++ "reached maximum number of requests"); ++ } ++ ++ rev = c->read; ++ rev->handler = ngx_http_v3_wait_request_handler; ++ c->write->handler = ngx_http_empty_handler; ++ ++ if (rev->ready) { ++ rev->handler(rev); ++ return; ++ } ++ ++ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ ngx_reusable_connection(c, 1); ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++} ++ ++ ++static void ++ngx_http_v3_wait_request_handler(ngx_event_t *rev) ++{ ++ size_t size; ++ ssize_t n; ++ ngx_buf_t *b; ++ ngx_connection_t *c; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_request_t *r; ++ ngx_http_connection_t *hc; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ c = rev->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler"); ++ ++ if (rev->timedout) { ++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ++ c->timedout = 1; ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ if (c->close) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ hc = c->data; ++ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ size = cscf->client_header_buffer_size; ++ ++ b = c->buffer; ++ ++ if (b == NULL) { ++ b = ngx_create_temp_buf(c->pool, size); ++ if (b == NULL) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ c->buffer = b; ++ ++ } else if (b->start == NULL) { ++ ++ b->start = ngx_palloc(c->pool, size); ++ if (b->start == NULL) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ b->pos = b->start; ++ b->last = b->start; ++ b->end = b->last + size; ++ } ++ ++ n = c->recv(c, b->last, size); ++ ++ if (n == NGX_AGAIN) { ++ ++ if (!rev->timer_set) { ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ ngx_reusable_connection(c, 1); ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ /* ++ * We are trying to not hold c->buffer's memory for an idle connection. ++ */ ++ ++ if (ngx_pfree(c->pool, b->start) == NGX_OK) { ++ b->start = NULL; ++ } ++ ++ return; ++ } ++ ++ if (n == NGX_ERROR) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ if (n == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client closed connection"); ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ b->last += n; ++ ++ c->log->action = "reading client request"; ++ ++ ngx_reusable_connection(c, 0); ++ ++ r = ngx_http_create_request(c); ++ if (r == NULL) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ r->http_version = NGX_HTTP_VERSION_30; ++ ++ r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); ++ if (r->v3_parse == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return; ++ } ++ ++ r->v3_parse->header_limit = cscf->large_client_header_buffers.size ++ * cscf->large_client_header_buffers.num; ++ ++ c->data = r; ++ c->requests = (c->quic->id >> 2) + 1; ++ ++ cln = ngx_pool_cleanup_add(r->pool, 0); ++ if (cln == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return; ++ } ++ ++ cln->handler = ngx_http_v3_cleanup_request; ++ cln->data = r; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->nrequests++; ++ ++ if (h3c->keepalive.timer_set) { ++ ngx_del_timer(&h3c->keepalive); ++ } ++ ++ rev->handler = ngx_http_v3_process_request; ++ ngx_http_v3_process_request(rev); ++} ++ ++ ++void ++ngx_http_v3_reset_connection(ngx_connection_t *c) ++{ ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++#if (NGX_HTTP_V3_HQ) ++ if (h3scf->hq) { ++ return; ++ } ++#endif ++ ++ if (h3scf->max_table_capacity > 0 && !c->read->eof ++ && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) ++ { ++ (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); ++ } ++ ++ if (c->timedout) { ++ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); ++ ++ } else if (c->close) { ++ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); ++ ++ } else if (c->requests == 0 || c->error) { ++ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR); ++ } ++} ++ ++ ++static void ++ngx_http_v3_cleanup_request(void *data) ++{ ++ ngx_http_request_t *r = data; ++ ++ ngx_connection_t *c; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ c = r->connection; ++ ++ if (!r->response_sent) { ++ c->error = 1; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (--h3c->nrequests == 0) { ++ clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); ++ ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); ++ } ++} ++ ++ ++static void ++ngx_http_v3_process_request(ngx_event_t *rev) ++{ ++ u_char *p; ++ ssize_t n; ++ ngx_buf_t *b; ++ ngx_int_t rc; ++ ngx_connection_t *c; ++ ngx_http_request_t *r; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_core_srv_conf_t *cscf; ++ ngx_http_v3_parse_headers_t *st; ++ ++ c = rev->data; ++ r = c->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request"); ++ ++ if (rev->timedout) { ++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ++ c->timedout = 1; ++ ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); ++ return; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ st = &r->v3_parse->headers; ++ ++ b = r->header_in; ++ ++ for ( ;; ) { ++ ++ if (b->pos == b->last) { ++ ++ if (rev->ready) { ++ n = c->recv(c, b->start, b->end - b->start); ++ ++ } else { ++ n = NGX_AGAIN; ++ } ++ ++ if (n == NGX_AGAIN) { ++ if (!rev->timer_set) { ++ cscf = ngx_http_get_module_srv_conf(r, ++ ngx_http_core_module); ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ } ++ ++ break; ++ } ++ ++ if (n == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client prematurely closed connection"); ++ } ++ ++ if (n == 0 || n == NGX_ERROR) { ++ c->error = 1; ++ c->log->action = "reading client request"; ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ break; ++ } ++ ++ b->pos = b->start; ++ b->last = b->start + n; ++ } ++ ++ p = b->pos; ++ ++ rc = ngx_http_v3_parse_headers(c, st, b); ++ ++ if (rc > 0) { ++ ngx_quic_reset_stream(c, rc); ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "client sent invalid header"); ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ break; ++ } ++ ++ if (rc == NGX_ERROR) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ break; ++ } ++ ++ r->request_length += b->pos - p; ++ h3c->total_bytes += b->pos - p; ++ ++ if (ngx_http_v3_check_flood(c) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_CLOSE); ++ break; ++ } ++ ++ if (rc == NGX_BUSY) { ++ if (rev->error) { ++ ngx_http_close_request(r, NGX_HTTP_CLOSE); ++ break; ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ } ++ ++ break; ++ } ++ ++ if (rc == NGX_AGAIN) { ++ continue; ++ } ++ ++ /* rc == NGX_OK || rc == NGX_DONE */ ++ ++ h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, ++ &st->field_rep.field.name, ++ &st->field_rep.field.value); ++ ++ if (ngx_http_v3_process_header(r, &st->field_rep.field.name, ++ &st->field_rep.field.value) ++ != NGX_OK) ++ { ++ break; ++ } ++ ++ if (rc == NGX_DONE) { ++ if (ngx_http_v3_process_request_header(r) != NGX_OK) { ++ break; ++ } ++ ++ ngx_http_process_request(r); ++ break; ++ } ++ } ++ ++ ngx_http_run_posted_requests(c); ++ ++ return; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ size_t len; ++ ngx_table_elt_t *h; ++ ngx_http_header_t *hh; ++ ngx_http_core_srv_conf_t *cscf; ++ ngx_http_core_main_conf_t *cmcf; ++ ++ static ngx_str_t cookie = ngx_string("cookie"); ++ ++ len = name->len + value->len; ++ ++ if (len > r->v3_parse->header_limit) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent too large header"); ++ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); ++ return NGX_ERROR; ++ } ++ ++ r->v3_parse->header_limit -= len; ++ ++ if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++ } ++ ++ if (r->invalid_header) { ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ if (cscf->ignore_invalid_headers) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid header: \"%V\"", name); ++ ++ return NGX_OK; ++ } ++ } ++ ++ if (name->len && name->data[0] == ':') { ++ return ngx_http_v3_process_pseudo_header(r, name, value); ++ } ++ ++ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (name->len == cookie.len ++ && ngx_memcmp(name->data, cookie.data, cookie.len) == 0) ++ { ++ if (ngx_http_v3_cookie(r, value) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ } else { ++ h = ngx_list_push(&r->headers_in.headers); ++ if (h == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ h->key = *name; ++ h->value = *value; ++ h->lowcase_key = h->key.data; ++ h->hash = ngx_hash_key(h->key.data, h->key.len); ++ ++ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ++ ++ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, ++ h->lowcase_key, h->key.len); ++ ++ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 header: \"%V: %V\"", name, value); ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ u_char ch; ++ ngx_uint_t i; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ r->invalid_header = 0; ++ ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ for (i = (name->data[0] == ':'); i != name->len; i++) { ++ ch = name->data[i]; ++ ++ if ((ch >= 'a' && ch <= 'z') ++ || (ch == '-') ++ || (ch >= '0' && ch <= '9') ++ || (ch == '_' && cscf->underscores_in_headers)) ++ { ++ continue; ++ } ++ ++ if (ch <= 0x20 || ch == 0x7f || ch == ':' ++ || (ch >= 'A' && ch <= 'Z')) ++ { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid header name: \"%V\"", name); ++ ++ return NGX_ERROR; ++ } ++ ++ r->invalid_header = 1; ++ } ++ ++ for (i = 0; i != value->len; i++) { ++ ch = value->data[i]; ++ ++ if (ch == '\0' || ch == LF || ch == CR) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent header \"%V\" with " ++ "invalid value: \"%V\"", name, value); ++ ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ u_char ch, c; ++ ngx_uint_t i; ++ ++ if (r->request_line.len) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent out of order pseudo-headers"); ++ goto failed; ++ } ++ ++ if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { ++ ++ if (r->method_name.len) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":method\" header"); ++ goto failed; ++ } ++ ++ if (value->len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent empty \":method\" header"); ++ goto failed; ++ } ++ ++ r->method_name = *value; ++ ++ for (i = 0; i < sizeof(ngx_http_v3_methods) ++ / sizeof(ngx_http_v3_methods[0]); i++) ++ { ++ if (value->len == ngx_http_v3_methods[i].name.len ++ && ngx_strncmp(value->data, ++ ngx_http_v3_methods[i].name.data, value->len) ++ == 0) ++ { ++ r->method = ngx_http_v3_methods[i].method; ++ break; ++ } ++ } ++ ++ for (i = 0; i < value->len; i++) { ++ ch = value->data[i]; ++ ++ if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid method: \"%V\"", value); ++ goto failed; ++ } ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 method \"%V\" %ui", value, r->method); ++ return NGX_OK; ++ } ++ ++ if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { ++ ++ if (r->uri_start) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":path\" header"); ++ goto failed; ++ } ++ ++ if (value->len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent empty \":path\" header"); ++ goto failed; ++ } ++ ++ r->uri_start = value->data; ++ r->uri_end = value->data + value->len; ++ ++ if (ngx_http_parse_uri(r) != NGX_OK) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid \":path\" header: \"%V\"", ++ value); ++ goto failed; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 path \"%V\"", value); ++ return NGX_OK; ++ } ++ ++ if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { ++ ++ if (r->schema.len) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":scheme\" header"); ++ goto failed; ++ } ++ ++ if (value->len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent empty \":scheme\" header"); ++ goto failed; ++ } ++ ++ for (i = 0; i < value->len; i++) { ++ ch = value->data[i]; ++ ++ c = (u_char) (ch | 0x20); ++ if (c >= 'a' && c <= 'z') { ++ continue; ++ } ++ ++ if (((ch >= '0' && ch <= '9') ++ || ch == '+' || ch == '-' || ch == '.') ++ && i > 0) ++ { ++ continue; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid \":scheme\" header: \"%V\"", ++ value); ++ goto failed; ++ } ++ ++ r->schema = *value; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 schema \"%V\"", value); ++ return NGX_OK; ++ } ++ ++ if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { ++ ++ if (r->host_start) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":authority\" header"); ++ goto failed; ++ } ++ ++ r->host_start = value->data; ++ r->host_end = value->data + value->len; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 authority \"%V\"", value); ++ return NGX_OK; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent unknown pseudo-header \"%V\"", name); ++ ++failed: ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) ++{ ++ size_t len; ++ u_char *p; ++ ngx_int_t rc; ++ ngx_str_t host; ++ ++ if (r->request_line.len) { ++ return NGX_OK; ++ } ++ ++ if (r->method_name.len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent no \":method\" header"); ++ goto failed; ++ } ++ ++ if (r->schema.len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent no \":scheme\" header"); ++ goto failed; ++ } ++ ++ if (r->uri_start == NULL) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent no \":path\" header"); ++ goto failed; ++ } ++ ++ len = r->method_name.len + 1 ++ + (r->uri_end - r->uri_start) + 1 ++ + sizeof("HTTP/3.0") - 1; ++ ++ p = ngx_pnalloc(r->pool, len); ++ if (p == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ r->request_line.data = p; ++ ++ p = ngx_cpymem(p, r->method_name.data, r->method_name.len); ++ *p++ = ' '; ++ p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); ++ *p++ = ' '; ++ p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); ++ ++ r->request_line.len = p - r->request_line.data; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 request line: \"%V\"", &r->request_line); ++ ++ ngx_str_set(&r->http_protocol, "HTTP/3.0"); ++ ++ if (ngx_http_process_request_uri(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (r->host_end) { ++ ++ host.len = r->host_end - r->host_start; ++ host.data = r->host_start; ++ ++ rc = ngx_http_validate_host(&host, r->pool, 0); ++ ++ if (rc == NGX_DECLINED) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid host in request line"); ++ goto failed; ++ } ++ ++ if (rc == NGX_ERROR) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ r->headers_in.server = host; ++ } ++ ++ if (ngx_list_init(&r->headers_in.headers, r->pool, 20, ++ sizeof(ngx_table_elt_t)) ++ != NGX_OK) ++ { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_process_request_header(ngx_http_request_t *r) ++{ ++ ssize_t n; ++ ngx_buf_t *b; ++ ngx_connection_t *c; ++ ++ c = r->connection; ++ ++ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (r->headers_in.server.len == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client sent neither \":authority\" nor \"Host\" header"); ++ goto failed; ++ } ++ ++ if (r->headers_in.host) { ++ if (r->headers_in.host->value.len != r->headers_in.server.len ++ || ngx_memcmp(r->headers_in.host->value.data, ++ r->headers_in.server.data, ++ r->headers_in.server.len) ++ != 0) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client sent \":authority\" and \"Host\" headers " ++ "with different values"); ++ goto failed; ++ } ++ } ++ ++ if (r->headers_in.content_length) { ++ r->headers_in.content_length_n = ++ ngx_atoof(r->headers_in.content_length->value.data, ++ r->headers_in.content_length->value.len); ++ ++ if (r->headers_in.content_length_n == NGX_ERROR) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client sent invalid \"Content-Length\" header"); ++ goto failed; ++ } ++ ++ } else { ++ b = r->header_in; ++ n = b->last - b->pos; ++ ++ if (n == 0) { ++ n = c->recv(c, b->start, b->end - b->start); ++ ++ if (n == NGX_ERROR) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ if (n > 0) { ++ b->pos = b->start; ++ b->last = b->start + n; ++ } ++ } ++ ++ if (n != 0) { ++ r->headers_in.chunked = 1; ++ } ++ } ++ ++ if (r->method == NGX_HTTP_CONNECT) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); ++ ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); ++ return NGX_ERROR; ++ } ++ ++ if (r->method == NGX_HTTP_TRACE) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); ++ ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value) ++{ ++ ngx_str_t *val; ++ ngx_array_t *cookies; ++ ++ cookies = r->v3_parse->cookies; ++ ++ if (cookies == NULL) { ++ cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); ++ if (cookies == NULL) { ++ return NGX_ERROR; ++ } ++ ++ r->v3_parse->cookies = cookies; ++ } ++ ++ val = ngx_array_push(cookies); ++ if (val == NULL) { ++ return NGX_ERROR; ++ } ++ ++ *val = *value; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) ++{ ++ u_char *buf, *p, *end; ++ size_t len; ++ ngx_str_t *vals; ++ ngx_uint_t i; ++ ngx_array_t *cookies; ++ ngx_table_elt_t *h; ++ ngx_http_header_t *hh; ++ ngx_http_core_main_conf_t *cmcf; ++ ++ static ngx_str_t cookie = ngx_string("cookie"); ++ ++ cookies = r->v3_parse->cookies; ++ ++ if (cookies == NULL) { ++ return NGX_OK; ++ } ++ ++ vals = cookies->elts; ++ ++ i = 0; ++ len = 0; ++ ++ do { ++ len += vals[i].len + 2; ++ } while (++i != cookies->nelts); ++ ++ len -= 2; ++ ++ buf = ngx_pnalloc(r->pool, len + 1); ++ if (buf == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ p = buf; ++ end = buf + len; ++ ++ for (i = 0; /* void */ ; i++) { ++ ++ p = ngx_cpymem(p, vals[i].data, vals[i].len); ++ ++ if (p == end) { ++ *p = '\0'; ++ break; ++ } ++ ++ *p++ = ';'; *p++ = ' '; ++ } ++ ++ h = ngx_list_push(&r->headers_in.headers); ++ if (h == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( ++ ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); ++ ++ h->key.len = cookie.len; ++ h->key.data = cookie.data; ++ ++ h->value.len = len; ++ h->value.data = buf; ++ ++ h->lowcase_key = cookie.data; ++ ++ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ++ ++ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, ++ h->lowcase_key, h->key.len); ++ ++ if (hh == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ if (hh->handler(r, h, hh->offset) != NGX_OK) { ++ /* ++ * request has been finalized already ++ * in ngx_http_process_multi_header_lines() ++ */ ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_read_request_body(ngx_http_request_t *r) ++{ ++ size_t preread; ++ ngx_int_t rc; ++ ngx_chain_t *cl, out; ++ ngx_http_request_body_t *rb; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ rb = r->request_body; ++ ++ preread = r->header_in->last - r->header_in->pos; ++ ++ if (preread) { ++ ++ /* there is the pre-read part of the request body */ ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 client request body preread %uz", preread); ++ ++ out.buf = r->header_in; ++ out.next = NULL; ++ cl = &out; ++ ++ } else { ++ cl = NULL; ++ } ++ ++ rc = ngx_http_v3_request_body_filter(r, cl); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ if (rb->rest == 0 && rb->last_saved) { ++ /* the whole request body was pre-read */ ++ r->request_body_no_buffering = 0; ++ rb->post_handler(r); ++ return NGX_OK; ++ } ++ ++ if (rb->rest < 0) { ++ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, ++ "negative request body rest"); ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ++ ++ rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size); ++ if (rb->buf == NULL) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ r->read_event_handler = ngx_http_v3_read_client_request_body_handler; ++ r->write_event_handler = ngx_http_request_empty_handler; ++ ++ return ngx_http_v3_do_read_client_request_body(r); ++} ++ ++ ++static void ++ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) ++{ ++ ngx_int_t rc; ++ ++ if (r->connection->read->timedout) { ++ r->connection->timedout = 1; ++ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); ++ return; ++ } ++ ++ rc = ngx_http_v3_do_read_client_request_body(r); ++ ++ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { ++ ngx_http_finalize_request(r, rc); ++ } ++} ++ ++ ++ngx_int_t ++ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) ++{ ++ ngx_int_t rc; ++ ++ if (r->connection->read->timedout) { ++ r->connection->timedout = 1; ++ return NGX_HTTP_REQUEST_TIME_OUT; ++ } ++ ++ rc = ngx_http_v3_do_read_client_request_body(r); ++ ++ if (rc == NGX_OK) { ++ r->reading_body = 0; ++ } ++ ++ return rc; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) ++{ ++ off_t rest; ++ size_t size; ++ ssize_t n; ++ ngx_int_t rc; ++ ngx_uint_t flush; ++ ngx_chain_t out; ++ ngx_connection_t *c; ++ ngx_http_request_body_t *rb; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ c = r->connection; ++ rb = r->request_body; ++ flush = 1; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 read client request body"); ++ ++ for ( ;; ) { ++ for ( ;; ) { ++ if (rb->rest == 0) { ++ break; ++ } ++ ++ if (rb->buf->last == rb->buf->end) { ++ ++ /* update chains */ ++ ++ rc = ngx_http_v3_request_body_filter(r, NULL); ++ ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ if (rb->busy != NULL) { ++ if (r->request_body_no_buffering) { ++ if (c->read->timer_set) { ++ ngx_del_timer(c->read); ++ } ++ ++ if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ return NGX_AGAIN; ++ } ++ ++ if (rb->filter_need_buffering) { ++ clcf = ngx_http_get_module_loc_conf(r, ++ ngx_http_core_module); ++ ngx_add_timer(c->read, clcf->client_body_timeout); ++ ++ if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ return NGX_AGAIN; ++ } ++ ++ ngx_log_error(NGX_LOG_ALERT, c->log, 0, ++ "busy buffers after request body flush"); ++ ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ flush = 0; ++ rb->buf->pos = rb->buf->start; ++ rb->buf->last = rb->buf->start; ++ } ++ ++ size = rb->buf->end - rb->buf->last; ++ rest = rb->rest - (rb->buf->last - rb->buf->pos); ++ ++ if ((off_t) size > rest) { ++ size = (size_t) rest; ++ } ++ ++ if (size == 0) { ++ break; ++ } ++ ++ n = c->recv(c, rb->buf->last, size); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 client request body recv %z", n); ++ ++ if (n == NGX_AGAIN) { ++ break; ++ } ++ ++ if (n == 0) { ++ rb->buf->last_buf = 1; ++ } ++ ++ if (n == NGX_ERROR) { ++ c->error = 1; ++ return NGX_HTTP_BAD_REQUEST; ++ } ++ ++ rb->buf->last += n; ++ ++ /* pass buffer to request body filter chain */ ++ ++ flush = 0; ++ out.buf = rb->buf; ++ out.next = NULL; ++ ++ rc = ngx_http_v3_request_body_filter(r, &out); ++ ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ if (rb->rest == 0) { ++ break; ++ } ++ ++ if (rb->buf->last < rb->buf->end) { ++ break; ++ } ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 client request body rest %O", rb->rest); ++ ++ if (flush) { ++ rc = ngx_http_v3_request_body_filter(r, NULL); ++ ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ } ++ ++ if (rb->rest == 0 && rb->last_saved) { ++ break; ++ } ++ ++ if (!c->read->ready || rb->rest == 0) { ++ ++ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ++ ngx_add_timer(c->read, clcf->client_body_timeout); ++ ++ if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ return NGX_AGAIN; ++ } ++ } ++ ++ if (c->read->timer_set) { ++ ngx_del_timer(c->read); ++ } ++ ++ if (!r->request_body_no_buffering) { ++ r->read_event_handler = ngx_http_block_reading; ++ rb->post_handler(r); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ++{ ++ off_t max; ++ size_t size; ++ u_char *p; ++ ngx_int_t rc; ++ ngx_buf_t *b; ++ ngx_uint_t last; ++ ngx_chain_t *cl, *out, *tl, **ll; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_request_body_t *rb; ++ ngx_http_core_loc_conf_t *clcf; ++ ngx_http_core_srv_conf_t *cscf; ++ ngx_http_v3_parse_data_t *st; ++ ++ rb = r->request_body; ++ st = &r->v3_parse->body; ++ ++ h3c = ngx_http_v3_get_session(r->connection); ++ ++ if (rb->rest == -1) { ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 request body filter"); ++ ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ rb->rest = cscf->large_client_header_buffers.size; ++ } ++ ++ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ++ ++ max = r->headers_in.content_length_n; ++ ++ if (max == -1 && clcf->client_max_body_size) { ++ max = clcf->client_max_body_size; ++ } ++ ++ out = NULL; ++ ll = &out; ++ last = 0; ++ ++ for (cl = in; cl; cl = cl->next) { ++ ++ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, ++ "http3 body buf " ++ "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O", ++ cl->buf->temporary, cl->buf->in_file, ++ cl->buf->start, cl->buf->pos, ++ cl->buf->last - cl->buf->pos, ++ cl->buf->file_pos, ++ cl->buf->file_last - cl->buf->file_pos); ++ ++ if (cl->buf->last_buf) { ++ last = 1; ++ } ++ ++ b = NULL; ++ ++ while (cl->buf->pos < cl->buf->last) { ++ ++ if (st->length == 0) { ++ p = cl->buf->pos; ++ ++ rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); ++ ++ r->request_length += cl->buf->pos - p; ++ h3c->total_bytes += cl->buf->pos - p; ++ ++ if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { ++ return NGX_HTTP_CLOSE; ++ } ++ ++ if (rc == NGX_AGAIN) { ++ continue; ++ } ++ ++ if (rc == NGX_DONE) { ++ last = 1; ++ goto done; ++ } ++ ++ if (rc > 0) { ++ ngx_quic_reset_stream(r->connection, rc); ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "client sent invalid body"); ++ return NGX_HTTP_BAD_REQUEST; ++ } ++ ++ if (rc == NGX_ERROR) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ /* rc == NGX_OK */ ++ } ++ ++ if (max != -1 && (uint64_t) (max - rb->received) < st->length) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "client intended to send too large " ++ "body: %O+%ui bytes", ++ rb->received, st->length); ++ ++ return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; ++ } ++ ++ 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,678 @@ ++ ++/* ++ * 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 need); ++static void ngx_http_v3_unblock(void *data); ++static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); ++ ++ ++typedef struct { ++ ngx_queue_t queue; ++ ngx_connection_t *connection; ++ ngx_uint_t *nblocked; ++} ngx_http_v3_block_t; ++ ++ ++static ngx_http_v3_field_t ngx_http_v3_static_table[] = { ++ ++ { ngx_string(":authority"), ngx_string("") }, ++ { ngx_string(":path"), ngx_string("/") }, ++ { ngx_string("age"), ngx_string("0") }, ++ { ngx_string("content-disposition"), ngx_string("") }, ++ { ngx_string("content-length"), ngx_string("0") }, ++ { ngx_string("cookie"), ngx_string("") }, ++ { ngx_string("date"), ngx_string("") }, ++ { ngx_string("etag"), ngx_string("") }, ++ { ngx_string("if-modified-since"), ngx_string("") }, ++ { ngx_string("if-none-match"), ngx_string("") }, ++ { ngx_string("last-modified"), ngx_string("") }, ++ { ngx_string("link"), ngx_string("") }, ++ { ngx_string("location"), ngx_string("") }, ++ { ngx_string("referer"), ngx_string("") }, ++ { ngx_string("set-cookie"), ngx_string("") }, ++ { ngx_string(":method"), ngx_string("CONNECT") }, ++ { ngx_string(":method"), ngx_string("DELETE") }, ++ { ngx_string(":method"), ngx_string("GET") }, ++ { ngx_string(":method"), ngx_string("HEAD") }, ++ { ngx_string(":method"), ngx_string("OPTIONS") }, ++ { ngx_string(":method"), ngx_string("POST") }, ++ { ngx_string(":method"), ngx_string("PUT") }, ++ { ngx_string(":scheme"), ngx_string("http") }, ++ { ngx_string(":scheme"), ngx_string("https") }, ++ { ngx_string(":status"), ngx_string("103") }, ++ { ngx_string(":status"), ngx_string("200") }, ++ { ngx_string(":status"), ngx_string("304") }, ++ { ngx_string(":status"), ngx_string("404") }, ++ { ngx_string(":status"), ngx_string("503") }, ++ { ngx_string("accept"), ngx_string("*/*") }, ++ { ngx_string("accept"), ++ ngx_string("application/dns-message") }, ++ { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, ++ { ngx_string("accept-ranges"), ngx_string("bytes") }, ++ { ngx_string("access-control-allow-headers"), ++ ngx_string("cache-control") }, ++ { ngx_string("access-control-allow-headers"), ++ ngx_string("content-type") }, ++ { ngx_string("access-control-allow-origin"), ++ ngx_string("*") }, ++ { ngx_string("cache-control"), ngx_string("max-age=0") }, ++ { ngx_string("cache-control"), ngx_string("max-age=2592000") }, ++ { ngx_string("cache-control"), ngx_string("max-age=604800") }, ++ { ngx_string("cache-control"), ngx_string("no-cache") }, ++ { ngx_string("cache-control"), ngx_string("no-store") }, ++ { ngx_string("cache-control"), ++ ngx_string("public, max-age=31536000") }, ++ { ngx_string("content-encoding"), ngx_string("br") }, ++ { ngx_string("content-encoding"), ngx_string("gzip") }, ++ { ngx_string("content-type"), ++ ngx_string("application/dns-message") }, ++ { ngx_string("content-type"), ++ ngx_string("application/javascript") }, ++ { ngx_string("content-type"), ngx_string("application/json") }, ++ { ngx_string("content-type"), ++ ngx_string("application/x-www-form-urlencoded") }, ++ { ngx_string("content-type"), ngx_string("image/gif") }, ++ { ngx_string("content-type"), ngx_string("image/jpeg") }, ++ { ngx_string("content-type"), ngx_string("image/png") }, ++ { ngx_string("content-type"), ngx_string("text/css") }, ++ { ngx_string("content-type"), ++ ngx_string("text/html;charset=utf-8") }, ++ { ngx_string("content-type"), ngx_string("text/plain") }, ++ { ngx_string("content-type"), ++ ngx_string("text/plain;charset=utf-8") }, ++ { ngx_string("range"), ngx_string("bytes=0-") }, ++ { ngx_string("strict-transport-security"), ++ ngx_string("max-age=31536000") }, ++ { ngx_string("strict-transport-security"), ++ ngx_string("max-age=31536000;includesubdomains") }, ++ { ngx_string("strict-transport-security"), ++ ngx_string("max-age=31536000;includesubdomains;preload") }, ++ { ngx_string("vary"), ngx_string("accept-encoding") }, ++ { ngx_string("vary"), ngx_string("origin") }, ++ { ngx_string("x-content-type-options"), ++ ngx_string("nosniff") }, ++ { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, ++ { ngx_string(":status"), ngx_string("100") }, ++ { ngx_string(":status"), ngx_string("204") }, ++ { ngx_string(":status"), ngx_string("206") }, ++ { ngx_string(":status"), ngx_string("302") }, ++ { ngx_string(":status"), ngx_string("400") }, ++ { ngx_string(":status"), ngx_string("403") }, ++ { ngx_string(":status"), ngx_string("421") }, ++ { ngx_string(":status"), ngx_string("425") }, ++ { ngx_string(":status"), ngx_string("500") }, ++ { ngx_string("accept-language"), ngx_string("") }, ++ { ngx_string("access-control-allow-credentials"), ++ ngx_string("FALSE") }, ++ { ngx_string("access-control-allow-credentials"), ++ ngx_string("TRUE") }, ++ { ngx_string("access-control-allow-headers"), ++ ngx_string("*") }, ++ { ngx_string("access-control-allow-methods"), ++ ngx_string("get") }, ++ { ngx_string("access-control-allow-methods"), ++ ngx_string("get, post, options") }, ++ { ngx_string("access-control-allow-methods"), ++ ngx_string("options") }, ++ { ngx_string("access-control-expose-headers"), ++ ngx_string("content-length") }, ++ { ngx_string("access-control-request-headers"), ++ ngx_string("content-type") }, ++ { ngx_string("access-control-request-method"), ++ ngx_string("get") }, ++ { ngx_string("access-control-request-method"), ++ ngx_string("post") }, ++ { ngx_string("alt-svc"), ngx_string("clear") }, ++ { ngx_string("authorization"), ngx_string("") }, ++ { ngx_string("content-security-policy"), ++ ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, ++ { ngx_string("early-data"), ngx_string("1") }, ++ { ngx_string("expect-ct"), ngx_string("") }, ++ { ngx_string("forwarded"), ngx_string("") }, ++ { ngx_string("if-range"), ngx_string("") }, ++ { ngx_string("origin"), ngx_string("") }, ++ { ngx_string("purpose"), ngx_string("prefetch") }, ++ { ngx_string("server"), ngx_string("") }, ++ { ngx_string("timing-allow-origin"), ngx_string("*") }, ++ { ngx_string("upgrade-insecure-requests"), ++ ngx_string("1") }, ++ { ngx_string("user-agent"), ngx_string("") }, ++ { ngx_string("x-forwarded-for"), ngx_string("") }, ++ { ngx_string("x-frame-options"), ngx_string("deny") }, ++ { ngx_string("x-frame-options"), ngx_string("sameorigin") } ++}; ++ ++ ++ngx_int_t ++ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ++ ngx_uint_t index, ngx_str_t *value) ++{ ++ ngx_str_t name; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ if (dynamic) { ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 ref insert dynamic[%ui] \"%V\"", index, value); ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (dt->base + dt->nelts <= index) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ index = dt->base + dt->nelts - 1 - index; ++ ++ if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ } else { ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 ref insert static[%ui] \"%V\"", index, value); ++ ++ if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ } ++ ++ return ngx_http_v3_insert(c, &name, value); ++} ++ ++ ++ngx_int_t ++ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) ++{ ++ u_char *p; ++ size_t size; ++ ngx_http_v3_field_t *field; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ size = ngx_http_v3_table_entry_size(name, value); ++ ++ if (ngx_http_v3_evict(c, size) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 insert [%ui] \"%V\":\"%V\", size:%uz", ++ dt->base + dt->nelts, name, value, size); ++ ++ p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, ++ c->log); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ ++ field = (ngx_http_v3_field_t *) p; ++ ++ field->name.data = p + sizeof(ngx_http_v3_field_t); ++ field->name.len = name->len; ++ field->value.data = ngx_cpymem(field->name.data, name->data, name->len); ++ field->value.len = value->len; ++ ngx_memcpy(field->value.data, value->data, value->len); ++ ++ dt->elts[dt->nelts++] = field; ++ dt->size += size; ++ ++ /* TODO increment can be sent less often */ ++ ++ if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_http_v3_new_entry(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ++{ ++ ngx_uint_t max, prev_max; ++ ngx_http_v3_field_t **elts; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 set capacity %ui", capacity); ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ if (capacity > h3scf->max_table_capacity) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client exceeded http3_max_table_capacity limit"); ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ dt = &h3c->table; ++ ++ if (dt->size > capacity) { ++ if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ } ++ ++ max = capacity / 32; ++ prev_max = dt->capacity / 32; ++ ++ if (max > prev_max) { ++ elts = ngx_alloc(max * sizeof(void *), c->log); ++ if (elts == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (dt->elts) { ++ ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); ++ ngx_free(dt->elts); ++ } ++ ++ dt->elts = elts; ++ } ++ ++ dt->capacity = capacity; ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) ++{ ++ ngx_uint_t n; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ dt = &h3c->table; ++ ++ if (dt->elts == NULL) { ++ return; ++ } ++ ++ for (n = 0; n < dt->nelts; n++) { ++ ngx_free(dt->elts[n]); ++ } ++ ++ ngx_free(dt->elts); ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_evict(ngx_connection_t *c, size_t need) ++{ ++ size_t size, target; ++ ngx_uint_t n; ++ ngx_http_v3_field_t *field; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (need > dt->capacity) { ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "not enough dynamic table capacity"); ++ return NGX_ERROR; ++ } ++ ++ target = dt->capacity - need; ++ n = 0; ++ ++ while (dt->size > target) { ++ field = dt->elts[n++]; ++ size = ngx_http_v3_table_entry_size(&field->name, &field->value); ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 evict [%ui] \"%V\":\"%V\" size:%uz", ++ dt->base, &field->name, &field->value, size); ++ ++ ngx_free(field); ++ dt->size -= size; ++ } ++ ++ if (n) { ++ dt->nelts -= n; ++ dt->base += n; ++ ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) ++{ ++ ngx_str_t name, value; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (dt->base + dt->nelts <= index) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ index = dt->base + dt->nelts - 1 - index; ++ ++ if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ return ngx_http_v3_insert(c, &name, &value); ++} ++ ++ ++ngx_int_t ++ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 ack section %ui", stream_id); ++ ++ /* we do not use dynamic tables */ ++ ++ return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ++{ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 increment insert count %ui", inc); ++ ++ /* we do not use dynamic tables */ ++ ++ return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, ++ ngx_str_t *name, ngx_str_t *value) ++{ ++ ngx_uint_t nelts; ++ ngx_http_v3_field_t *field; ++ ++ nelts = sizeof(ngx_http_v3_static_table) ++ / sizeof(ngx_http_v3_static_table[0]); ++ ++ if (index >= nelts) { ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 static[%ui] lookup out of bounds: %ui", ++ index, nelts); ++ return NGX_ERROR; ++ } ++ ++ field = &ngx_http_v3_static_table[index]; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 static[%ui] lookup \"%V\":\"%V\"", ++ index, &field->name, &field->value); ++ ++ if (name) { ++ *name = field->name; ++ } ++ ++ if (value) { ++ *value = field->value; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ ngx_http_v3_field_t *field; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (index < dt->base || index - dt->base >= dt->nelts) { ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", ++ index, dt->base, dt->base + dt->nelts); ++ return NGX_ERROR; ++ } ++ ++ field = dt->elts[index - dt->base]; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 dynamic[%ui] lookup \"%V\":\"%V\"", ++ index, &field->name, &field->value); ++ ++ if (name) { ++ *name = field->name; ++ } ++ ++ if (value) { ++ *value = field->value; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) ++{ ++ ngx_uint_t max_entries, full_range, max_value, ++ max_wrapped, req_insert_count; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ /* QPACK 4.5.1.1. Required Insert Count */ ++ ++ if (*insert_count == 0) { ++ return NGX_OK; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ max_entries = h3scf->max_table_capacity / 32; ++ full_range = 2 * max_entries; ++ ++ if (*insert_count > full_range) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ max_value = dt->base + dt->nelts + max_entries; ++ max_wrapped = (max_value / full_range) * full_range; ++ req_insert_count = max_wrapped + *insert_count - 1; ++ ++ if (req_insert_count > max_value) { ++ if (req_insert_count <= full_range) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ req_insert_count -= full_range; ++ } ++ ++ if (req_insert_count == 0) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 decode insert_count %ui -> %ui", ++ *insert_count, req_insert_count); ++ ++ *insert_count = req_insert_count; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) ++{ ++ size_t n; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_v3_block_t *block; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ n = dt->base + dt->nelts; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 check insert count req:%ui, have:%ui", ++ insert_count, n); ++ ++ if (n >= insert_count) { ++ return NGX_OK; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); ++ ++ block = NULL; ++ ++ for (cln = c->pool->cleanup; cln; cln = cln->next) { ++ if (cln->handler == ngx_http_v3_unblock) { ++ block = cln->data; ++ break; ++ } ++ } ++ ++ if (block == NULL) { ++ cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); ++ if (cln == NULL) { ++ return NGX_ERROR; ++ } ++ ++ cln->handler = ngx_http_v3_unblock; ++ ++ block = cln->data; ++ block->queue.prev = NULL; ++ block->connection = c; ++ block->nblocked = &h3c->nblocked; ++ } ++ ++ if (block->queue.prev == NULL) { ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ if (h3c->nblocked == h3scf->max_blocked_streams) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client exceeded http3_max_blocked_streams limit"); ++ ++ ngx_http_v3_finalize_connection(c, ++ NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, ++ "too many blocked streams"); ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ h3c->nblocked++; ++ ngx_queue_insert_tail(&h3c->blocked, &block->queue); ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 blocked:%ui", h3c->nblocked); ++ ++ return NGX_BUSY; ++} ++ ++ ++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_HEADER_LIST_SIZE: ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 param SETTINGS_MAX_HEADER_LIST_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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,53 @@ ++ ++/* ++ * 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; ++} ngx_http_v3_dynamic_table_t; ++ ++ ++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); ++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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,733 @@ ++ ++/* ++ * 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_dummy_write_handler(ngx_event_t *wev); ++static void ngx_http_v3_push_cleanup(void *data); ++static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ++ ngx_uint_t type); ++ ++ ++void ++ngx_http_v3_init_uni_stream(ngx_connection_t *c) ++{ ++ uint64_t n; ++ ngx_http_v3_uni_stream_t *us; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); ++ ++ n = c->quic->id >> 2; ++ ++ if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { ++ ngx_http_v3_finalize_connection(c, ++ NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, ++ "reached maximum number of uni streams"); ++ c->data = NULL; ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ c->quic->cancelable = 1; ++ ++ us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); ++ if (us == NULL) { ++ ngx_http_v3_finalize_connection(c, ++ NGX_HTTP_V3_ERR_INTERNAL_ERROR, ++ "memory allocation error"); ++ c->data = NULL; ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ us->index = -1; ++ ++ c->data = us; ++ ++ c->read->handler = ngx_http_v3_uni_read_handler; ++ c->write->handler = ngx_http_v3_dummy_write_handler; ++ ++ ngx_http_v3_uni_read_handler(c->read); ++} ++ ++ ++static void ++ngx_http_v3_close_uni_stream(ngx_connection_t *c) ++{ ++ ngx_pool_t *pool; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); ++ ++ us = c->data; ++ ++ if (us && us->index >= 0) { ++ h3c = ngx_http_v3_get_session(c); ++ h3c->known_streams[us->index] = NULL; ++ } ++ ++ c->destroyed = 1; ++ ++ pool = c->pool; ++ ++ ngx_close_connection(c); ++ ++ ngx_destroy_pool(pool); ++} ++ ++ ++ngx_int_t ++ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) ++{ ++ ngx_int_t index; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ switch (type) { ++ ++ case NGX_HTTP_V3_STREAM_ENCODER: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 encoder stream"); ++ index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; ++ break; ++ ++ case NGX_HTTP_V3_STREAM_DECODER: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 decoder stream"); ++ index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; ++ break; ++ ++ case NGX_HTTP_V3_STREAM_CONTROL: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 control stream"); ++ index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; ++ ++ break; ++ ++ default: ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 stream 0x%02xL", type); ++ ++ if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL ++ || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL ++ || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); ++ return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; ++ } ++ ++ index = -1; ++ } ++ ++ if (index >= 0) { ++ if (h3c->known_streams[index]) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); ++ return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; ++ } ++ ++ h3c->known_streams[index] = c; ++ ++ us = c->data; ++ us->index = index; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_http_v3_uni_read_handler(ngx_event_t *rev) ++{ ++ u_char buf[128]; ++ ssize_t n; ++ ngx_buf_t b; ++ ngx_int_t rc; ++ ngx_connection_t *c; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ c = rev->data; ++ us = c->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); ++ ++ ngx_memzero(&b, sizeof(ngx_buf_t)); ++ ++ while (rev->ready) { ++ ++ n = c->recv(c, buf, sizeof(buf)); ++ ++ if (n == NGX_ERROR) { ++ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; ++ goto failed; ++ } ++ ++ if (n == 0) { ++ if (us->index >= 0) { ++ rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; ++ goto failed; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ if (n == NGX_AGAIN) { ++ break; ++ } ++ ++ b.pos = buf; ++ b.last = buf + n; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (ngx_http_v3_check_flood(c) != NGX_OK) { ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ rc = ngx_http_v3_parse_uni(c, &us->parse, &b); ++ ++ if (rc == NGX_DONE) { ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 read done"); ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ if (rc > 0) { ++ goto failed; ++ } ++ ++ if (rc != NGX_AGAIN) { ++ rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; ++ goto failed; ++ } ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; ++ goto failed; ++ } ++ ++ return; ++ ++failed: ++ ++ ngx_http_v3_finalize_connection(c, rc, "stream error"); ++ ngx_http_v3_close_uni_stream(c); ++} ++ ++ ++static void ++ngx_http_v3_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); ++ } ++} ++ ++ ++/* XXX async & buffered stream writes */ ++ ++ngx_connection_t * ++ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) ++{ ++ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; ++ size_t n; ++ ngx_connection_t *sc; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_v3_push_t *push; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 create push stream id:%uL", push_id); ++ ++ sc = ngx_quic_open_stream(c, 0); ++ if (sc == NULL) { ++ goto failed; ++ } ++ ++ p = buf; ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); ++ n = p - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (sc->send(sc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); ++ if (cln == NULL) { ++ goto failed; ++ } ++ ++ h3c->npushing++; ++ ++ cln->handler = ngx_http_v3_push_cleanup; ++ ++ push = cln->data; ++ push->id = push_id; ++ push->connection = sc; ++ push->npushing = &h3c->npushing; ++ ++ ngx_queue_insert_tail(&h3c->pushing, &push->queue); ++ ++ return sc; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, ++ "failed to create push stream"); ++ if (sc) { ++ ngx_http_v3_close_uni_stream(sc); ++ } ++ ++ return NULL; ++} ++ ++ ++static void ++ngx_http_v3_push_cleanup(void *data) ++{ ++ ngx_http_v3_push_t *push = data; ++ ++ ngx_queue_remove(&push->queue); ++ (*push->npushing)--; ++} ++ ++ ++static ngx_connection_t * ++ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) ++{ ++ u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; ++ size_t n; ++ ngx_int_t index; ++ ngx_connection_t *sc; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ switch (type) { ++ case NGX_HTTP_V3_STREAM_ENCODER: ++ index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; ++ break; ++ case NGX_HTTP_V3_STREAM_DECODER: ++ index = NGX_HTTP_V3_STREAM_SERVER_DECODER; ++ break; ++ case NGX_HTTP_V3_STREAM_CONTROL: ++ index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; ++ break; ++ default: ++ index = -1; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (index >= 0) { ++ if (h3c->known_streams[index]) { ++ return h3c->known_streams[index]; ++ } ++ } ++ ++ sc = ngx_quic_open_stream(c, 0); ++ if (sc == NULL) { ++ goto failed; ++ } ++ ++ sc->quic->cancelable = 1; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 create uni stream, type:%ui", type); ++ ++ us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); ++ if (us == NULL) { ++ goto failed; ++ } ++ ++ us->index = index; ++ ++ sc->data = us; ++ ++ sc->read->handler = ngx_http_v3_uni_read_handler; ++ sc->write->handler = ngx_http_v3_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; ++ } ++ ++ return sc; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, ++ "failed to create server stream"); ++ if (sc) { ++ ngx_http_v3_close_uni_stream(sc); ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_settings(ngx_connection_t *c) ++{ ++ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; ++ size_t n; ++ ngx_connection_t *cc; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); ++ ++ cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); ++ if (cc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ n = ngx_http_v3_encode_varlen_int(NULL, ++ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); ++ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity); ++ n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); ++ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams); ++ ++ p = (u_char *) ngx_http_v3_encode_varlen_int(buf, ++ NGX_HTTP_V3_FRAME_SETTINGS); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, ++ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, ++ NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); ++ n = p - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (cc->send(cc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send settings"); ++ ngx_http_v3_close_uni_stream(cc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) ++{ ++ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; ++ size_t n; ++ ngx_connection_t *cc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); ++ ++ cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); ++ if (cc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ n = ngx_http_v3_encode_varlen_int(NULL, id); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); ++ n = p - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (cc->send(cc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send goaway"); ++ ngx_http_v3_close_uni_stream(cc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; ++ size_t n; ++ ngx_connection_t *dc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 send section acknowledgement %ui", stream_id); ++ ++ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); ++ if (dc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] = 0x80; ++ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (dc->send(dc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "failed to send section acknowledgement"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send section acknowledgement"); ++ ngx_http_v3_close_uni_stream(dc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; ++ size_t n; ++ ngx_connection_t *dc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 send stream cancellation %ui", stream_id); ++ ++ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); ++ if (dc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] = 0x40; ++ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (dc->send(dc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send stream cancellation"); ++ ngx_http_v3_close_uni_stream(dc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ++{ ++ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; ++ size_t n; ++ ngx_connection_t *dc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 send insert count increment %ui", inc); ++ ++ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); ++ if (dc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] = 0; ++ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (dc->send(dc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "failed to send insert count increment"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send insert count increment"); ++ ngx_http_v3_close_uni_stream(dc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) ++{ ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 MAX_PUSH_ID:%uL", max_push_id); ++ ++ if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { ++ return NGX_HTTP_V3_ERR_ID_ERROR; ++ } ++ ++ h3c->max_push_id = max_push_id; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) ++{ ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); ++ ++ h3c->goaway_push_id = push_id; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) ++{ ++ ngx_queue_t *q; ++ ngx_http_request_t *r; ++ ngx_http_v3_push_t *push; ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 CANCEL_PUSH:%uL", push_id); ++ ++ if (push_id >= h3c->next_push_id) { ++ return NGX_HTTP_V3_ERR_ID_ERROR; ++ } ++ ++ for (q = ngx_queue_head(&h3c->pushing); ++ q != ngx_queue_sentinel(&h3c->pushing); ++ q = ngx_queue_next(&h3c->pushing)) ++ { ++ push = (ngx_http_v3_push_t *) q; ++ ++ if (push->id != push_id) { ++ continue; ++ } ++ ++ r = push->connection->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 cancel push"); ++ ++ ngx_http_finalize_request(r, NGX_HTTP_CLOSE); ++ ++ break; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 cancel stream %ui", stream_id); ++ ++ /* we do not use dynamic tables */ ++ ++ return NGX_OK; ++} +diff -r 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 src/os/unix/ngx_linux_config.h +--- a/src/os/unix/ngx_linux_config.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/os/unix/ngx_linux_config.h Tue Jan 04 18:14:15 2022 -0500 +@@ -103,6 +103,10 @@ + #include + #endif + ++#if (NGX_HAVE_UDP_SEGMENT) ++#include ++#endif ++ + + #define NGX_LISTEN_BACKLOG 511 + +diff -r 67408b4a12c0 src/os/unix/ngx_socket.h +--- a/src/os/unix/ngx_socket.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/os/unix/ngx_socket.h Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 src/os/unix/ngx_udp_sendmsg_chain.c +--- a/src/os/unix/ngx_udp_sendmsg_chain.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/os/unix/ngx_udp_sendmsg_chain.c Tue Jan 04 18:14:15 2022 -0500 +@@ -12,7 +12,7 @@ + + static ngx_chain_t *ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec, + ngx_chain_t *in, ngx_log_t *log); +-static ssize_t ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec); ++static ssize_t ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec); + + + ngx_chain_t * +@@ -88,7 +88,7 @@ + + send += vec.size; + +- n = ngx_sendmsg(c, &vec); ++ n = ngx_sendmsg_vec(c, &vec); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; +@@ -204,24 +204,13 @@ + + + static ssize_t +-ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec) ++ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec) + { +- ssize_t n; +- ngx_err_t err; +- struct msghdr msg; +- +-#if (NGX_HAVE_MSGHDR_MSG_CONTROL) ++ struct msghdr msg; + +-#if (NGX_HAVE_IP_SENDSRCADDR) +- u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))]; +-#elif (NGX_HAVE_IP_PKTINFO) +- u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))]; +-#endif +- +-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) +- u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; +-#endif +- ++#if defined(NGX_HAVE_ADDRINFO_CMSG) ++ struct cmsghdr *cmsg; ++ u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; + #endif + + ngx_memzero(&msg, sizeof(struct msghdr)); +@@ -234,88 +223,180 @@ + msg.msg_iov = vec->iovs; + msg.msg_iovlen = vec->count; + +-#if (NGX_HAVE_MSGHDR_MSG_CONTROL) ++#if defined(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 ++ ++ return ngx_sendmsg(c, &msg, 0); ++} ++ ++ ++#if defined(NGX_HAVE_ADDRINFO_CMSG) + +- if (c->listening && c->listening->wildcard && c->local_sockaddr) { ++size_t ++ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) ++{ ++ size_t len; ++#if (NGX_HAVE_IP_SENDSRCADDR) ++ struct in_addr *addr; ++ struct sockaddr_in *sin; ++#elif (NGX_HAVE_IP_PKTINFO) ++ struct in_pktinfo *pkt; ++ struct sockaddr_in *sin; ++#endif ++ ++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) ++ struct in6_pktinfo *pkt6; ++ struct sockaddr_in6 *sin6; ++#endif ++ ++ ++#if (NGX_HAVE_IP_SENDSRCADDR) || (NGX_HAVE_IP_PKTINFO) ++ ++ if (local_sockaddr->sa_family == AF_INET) { ++ ++ cmsg->cmsg_level = IPPROTO_IP; + + #if (NGX_HAVE_IP_SENDSRCADDR) + +- if (c->local_sockaddr->sa_family == AF_INET) { +- struct cmsghdr *cmsg; +- struct in_addr *addr; +- struct sockaddr_in *sin; +- +- msg.msg_control = &msg_control; +- msg.msg_controllen = sizeof(msg_control); ++ cmsg->cmsg_type = IP_SENDSRCADDR; ++ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); ++ len = CMSG_SPACE(sizeof(struct in_addr)); + +- cmsg = CMSG_FIRSTHDR(&msg); +- cmsg->cmsg_level = IPPROTO_IP; +- cmsg->cmsg_type = IP_SENDSRCADDR; +- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); ++ sin = (struct sockaddr_in *) local_sockaddr; + +- sin = (struct sockaddr_in *) c->local_sockaddr; +- +- addr = (struct in_addr *) CMSG_DATA(cmsg); +- *addr = sin->sin_addr; +- } ++ addr = (struct in_addr *) CMSG_DATA(cmsg); ++ *addr = sin->sin_addr; + + #elif (NGX_HAVE_IP_PKTINFO) + +- if (c->local_sockaddr->sa_family == AF_INET) { +- struct cmsghdr *cmsg; +- struct in_pktinfo *pkt; +- struct sockaddr_in *sin; ++ cmsg->cmsg_type = IP_PKTINFO; ++ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); ++ len = CMSG_SPACE(sizeof(struct in_pktinfo)); ++ ++ sin = (struct sockaddr_in *) local_sockaddr; ++ ++ pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); ++ ngx_memzero(pkt, sizeof(struct in_pktinfo)); ++ pkt->ipi_spec_dst = sin->sin_addr; ++ ++#endif ++ return len; ++ } ++ ++#endif ++ ++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) ++ if (local_sockaddr->sa_family == AF_INET6) { + +- msg.msg_control = &msg_control; +- msg.msg_controllen = sizeof(msg_control); ++ cmsg->cmsg_level = IPPROTO_IPV6; ++ cmsg->cmsg_type = IPV6_PKTINFO; ++ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); ++ len = CMSG_SPACE(sizeof(struct in6_pktinfo)); ++ ++ sin6 = (struct sockaddr_in6 *) local_sockaddr; ++ ++ pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); ++ ngx_memzero(pkt6, sizeof(struct in6_pktinfo)); ++ pkt6->ipi6_addr = sin6->sin6_addr; ++ ++ return len; ++ } ++#endif ++ ++ return 0; ++} ++ + +- cmsg = CMSG_FIRSTHDR(&msg); +- cmsg->cmsg_level = IPPROTO_IP; +- cmsg->cmsg_type = IP_PKTINFO; +- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); ++ngx_int_t ++ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) ++{ ++ ++#if (NGX_HAVE_IP_RECVDSTADDR) ++ struct in_addr *addr; ++ struct sockaddr_in *sin; ++#elif (NGX_HAVE_IP_PKTINFO) ++ struct in_pktinfo *pkt; ++ struct sockaddr_in *sin; ++#endif ++ ++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) ++ struct in6_pktinfo *pkt6; ++ struct sockaddr_in6 *sin6; ++#endif ++ ++ ++ #if (NGX_HAVE_IP_RECVDSTADDR) + +- sin = (struct sockaddr_in *) c->local_sockaddr; ++ if (cmsg->cmsg_level == IPPROTO_IP ++ && cmsg->cmsg_type == IP_RECVDSTADDR ++ && local_sockaddr->sa_family == AF_INET) ++ { ++ addr = (struct in_addr *) CMSG_DATA(cmsg); ++ sin = (struct sockaddr_in *) local_sockaddr; ++ sin->sin_addr = *addr; ++ ++ return NGX_OK; ++ } + +- pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); +- ngx_memzero(pkt, sizeof(struct in_pktinfo)); +- pkt->ipi_spec_dst = sin->sin_addr; +- } ++#elif (NGX_HAVE_IP_PKTINFO) ++ ++ if (cmsg->cmsg_level == IPPROTO_IP ++ && cmsg->cmsg_type == IP_PKTINFO ++ && local_sockaddr->sa_family == AF_INET) ++ { ++ pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); ++ sin = (struct sockaddr_in *) local_sockaddr; ++ sin->sin_addr = pkt->ipi_addr; ++ ++ return NGX_OK; ++ } + + #endif + + #if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + +- if (c->local_sockaddr->sa_family == AF_INET6) { +- struct cmsghdr *cmsg; +- struct in6_pktinfo *pkt6; +- struct sockaddr_in6 *sin6; +- +- msg.msg_control = &msg_control6; +- msg.msg_controllen = sizeof(msg_control6); ++ if (cmsg->cmsg_level == IPPROTO_IPV6 ++ && cmsg->cmsg_type == IPV6_PKTINFO ++ && local_sockaddr->sa_family == AF_INET6) ++ { ++ pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); ++ sin6 = (struct sockaddr_in6 *) local_sockaddr; ++ sin6->sin6_addr = pkt6->ipi6_addr; + +- cmsg = CMSG_FIRSTHDR(&msg); +- cmsg->cmsg_level = IPPROTO_IPV6; +- cmsg->cmsg_type = IPV6_PKTINFO; +- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); +- +- sin6 = (struct sockaddr_in6 *) c->local_sockaddr; +- +- pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); +- ngx_memzero(pkt6, sizeof(struct in6_pktinfo)); +- pkt6->ipi6_addr = sin6->sin6_addr; +- } +- +-#endif ++ return NGX_OK; + } + + #endif + ++ return NGX_DECLINED; ++} ++ ++#endif ++ ++ ++ssize_t ++ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags) ++{ ++ ssize_t n; ++ ngx_err_t err; ++#if (NGX_DEBUG) ++ size_t size; ++ ngx_uint_t i; ++#endif ++ + eintr: + +- n = sendmsg(c->fd, &msg, 0); +- +- ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, +- "sendmsg: %z of %uz", n, vec->size); ++ n = sendmsg(c->fd, msg, flags); + + if (n == -1) { + err = ngx_errno; +@@ -338,5 +419,14 @@ + } + } + ++#if (NGX_DEBUG) ++ for (i = 0, size = 0; i < (size_t) msg->msg_iovlen; i++) { ++ size += msg->msg_iov[i].iov_len; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "sendmsg: %z of %uz", n, size); ++#endif ++ + return n; + } +diff -r 67408b4a12c0 src/stream/ngx_stream.c +--- a/src/stream/ngx_stream.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/stream/ngx_stream.c Tue Jan 04 18:14:15 2022 -0500 +@@ -518,6 +518,9 @@ + ls->reuseport = addr[i].opt.reuseport; + #endif + ++#if (NGX_STREAM_QUIC) ++ ls->quic = addr[i].opt.quic; ++#endif + stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); + if (stport == NULL) { + return NGX_CONF_ERROR; +@@ -576,6 +579,9 @@ + #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 +617,9 @@ + #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 67408b4a12c0 src/stream/ngx_stream.h +--- a/src/stream/ngx_stream.h Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/stream/ngx_stream.h Tue Jan 04 18:14:15 2022 -0500 +@@ -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 @@ + unsigned bind:1; + unsigned wildcard:1; + unsigned ssl:1; ++ unsigned quic:1; + #if (NGX_HAVE_INET6) + unsigned ipv6only:1; + #endif +@@ -76,6 +81,7 @@ + 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 67408b4a12c0 src/stream/ngx_stream_core_module.c +--- a/src/stream/ngx_stream_core_module.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/stream/ngx_stream_core_module.c Tue Jan 04 18:14:15 2022 -0500 +@@ -760,6 +760,29 @@ + #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 @@ + } + #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 67408b4a12c0 src/stream/ngx_stream_handler.c +--- a/src/stream/ngx_stream_handler.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/stream/ngx_stream_handler.c Tue Jan 04 18:14:15 2022 -0500 +@@ -129,6 +129,10 @@ + 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 @@ + 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 67408b4a12c0 src/stream/ngx_stream_proxy_module.c +--- a/src/stream/ngx_stream_proxy_module.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/stream/ngx_stream_proxy_module.c Tue Jan 04 18:14:15 2022 -0500 +@@ -1767,6 +1767,21 @@ + 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -0,0 +1,360 @@ ++ ++/* ++ * 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_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; ++ ++ 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_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, ""); ++ ++ 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 67408b4a12c0 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 Tue Jan 04 18:14:15 2022 -0500 +@@ -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 67408b4a12c0 src/stream/ngx_stream_ssl_module.c +--- a/src/stream/ngx_stream_ssl_module.c Tue Dec 28 18:28:38 2021 +0300 ++++ b/src/stream/ngx_stream_ssl_module.c Tue Jan 04 18:14:15 2022 -0500 +@@ -1194,7 +1194,10 @@ + static ngx_int_t + ngx_stream_ssl_init(ngx_conf_t *cf) + { ++ ngx_uint_t i; ++ ngx_stream_listen_t *listen; + ngx_stream_handler_pt *h; ++ ngx_stream_ssl_conf_t *scf; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); +@@ -1206,5 +1209,23 @@ + + *h = ngx_stream_ssl_handler; + ++ listen = cmcf->listen.elts; ++ ++ for (i = 0; i < cmcf->listen.nelts; i++) { ++ if (!listen[i].quic) { ++ continue; ++ } ++ ++ scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; ++ ++ if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "\"ssl_protocols\" must enable TLSv1.3 for " ++ "the \"listen ... quic\" directive in %s:%ui", ++ scf->file, scf->line); ++ return NGX_ERROR; ++ } ++ } ++ + return NGX_OK; + }