aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJochen Neumeister <joneum@FreeBSD.org>2024-02-25 09:24:06 +0000
committerJochen Neumeister <joneum@FreeBSD.org>2024-02-25 09:26:33 +0000
commit78842f5507e0259442311b8288fb438f584aed4d (patch)
tree750e6d97e9ca254f77051be9a6fcd3f289f61408
parent93afada380e907a0c523de6b0315e969da64d0d0 (diff)
downloadports-78842f5507e0259442311b8288fb438f584aed4d.tar.gz
ports-78842f5507e0259442311b8288fb438f584aed4d.zip
www/freenginx: add new port
Freenginx is a Robust ans small WWW server Sponsored by: Netzkommune GmbH
-rw-r--r--www/Makefile1
-rw-r--r--www/freenginx/Makefile403
-rw-r--r--www/freenginx/Makefile.extmod330
-rw-r--r--www/freenginx/Makefile.options.desc120
-rw-r--r--www/freenginx/distinfo145
-rw-r--r--www/freenginx/files/extra-patch-calio-iconv-nginx-module-config19
-rw-r--r--www/freenginx/files/extra-patch-httpv326867
-rw-r--r--www/freenginx/files/extra-patch-nginx-ct-LibreSSL20
-rw-r--r--www/freenginx/files/extra-patch-nginx-http-footer-filter-config12
-rw-r--r--www/freenginx/files/extra-patch-nginx-link-function-config42
-rw-r--r--www/freenginx/files/extra-patch-nginx-notice-config13
-rw-r--r--www/freenginx/files/extra-patch-nginx-opentracing-opentracing-config8
-rw-r--r--www/freenginx/files/extra-patch-nginx_mod_h264_streaming-config41
-rw-r--r--www/freenginx/files/extra-patch-nginx_mogilefs_module-config13
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_auth_ldap_module.c10
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_dav_ext_module.c15
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_json_status_module-config12
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_mogilefs_module.c12
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_notice_module.c11
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_redis_module.c34
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_sflow_config.c10
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_sflow_config.h11
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_sflow_module.c59
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_streaming_module.c13
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_tarantool-config24
-rw-r--r--www/freenginx/files/extra-patch-ngx_http_uploadprogress_module.c73
-rw-r--r--www/freenginx/files/extra-patch-ngx_link_func_module.c11
-rw-r--r--www/freenginx/files/extra-patch-ngx_postgres-config19
-rw-r--r--www/freenginx/files/extra-patch-openresty-drizzle-nginx-module-config42
-rw-r--r--www/freenginx/files/extra-patch-passenger-build-nginx.rb33
-rw-r--r--www/freenginx/files/extra-patch-passenger-disable-telemetry11
-rw-r--r--www/freenginx/files/extra-patch-rds-csv-nginx-module-config15
-rw-r--r--www/freenginx/files/extra-patch-rds-json-nginx-module-config15
-rw-r--r--www/freenginx/files/extra-patch-spnego-http-auth-nginx-module-config14
-rw-r--r--www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_hash_module.c44
-rw-r--r--www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_ip_hash_module.c28
-rw-r--r--www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_least_conn_module.c46
-rw-r--r--www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.c101
-rw-r--r--www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.h14
-rw-r--r--www/freenginx/files/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c456
-rw-r--r--www/freenginx/files/extra-patch-xss-nginx-module-config15
-rw-r--r--www/freenginx/files/nginx.in158
-rw-r--r--www/freenginx/files/patch-conf-nginx.conf47
-rw-r--r--www/freenginx/files/pkg-message.in25
-rw-r--r--www/freenginx/pkg-descr8
-rw-r--r--www/freenginx/pkg-plist107
46 files changed, 29527 insertions, 0 deletions
diff --git a/www/Makefile b/www/Makefile
index 51af194f4639..7be376a08b8d 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -190,6 +190,7 @@
SUBDIR += formication
SUBDIR += foswiki
SUBDIR += free-sa-devel
+ SUBDIR += freenginx
SUBDIR += fswiki
SUBDIR += fusionpbx
SUBDIR += g-cows
diff --git a/www/freenginx/Makefile b/www/freenginx/Makefile
new file mode 100644
index 000000000000..cf0a932189e3
--- /dev/null
+++ b/www/freenginx/Makefile
@@ -0,0 +1,403 @@
+PORTNAME= nginx
+PORTVERSION= 1.24.0
+PORTREVISION?= 0
+CATEGORIES= www
+MASTER_SITES= https://freenginx.org/download/ \
+ LOCAL/joneum
+PKGNAMEPREFIX= free
+DISTFILES= ${DISTNAME}${EXTRACT_SUFX}
+
+MAINTAINER?= joneum@FreeBSD.org
+COMMENT?= Robust and small WWW server
+WWW= https://freenginx.com/
+
+LICENSE= BSD2CLAUSE
+LICENSE_FILE= ${WRKSRC}/LICENSE
+
+CONFLICTS_INSTALL= nginx-devel nginx
+
+PORTSCOUT= limit:^1\.24\.[0-9]*
+
+USES= cpe
+
+CPE_VENDOR= freenginx
+CPE_PRODUCT= freenginx
+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-devel-1.* \
+ nginx-full-1.* \
+ nginx-lite-1.* \
+ nginx-naxsi-1.*
+USE_RC_SUBR?= nginx
+SUB_FILES?= pkg-message
+SUB_LIST+= WWWOWN=${WWWOWN} \
+ WWWGRP=${WWWGRP} \
+ NGINX_RUNDIR=${NGINX_RUNDIR} \
+ NGINX_TMPDIR=${NGINX_TMPDIR} \
+ PREFIX=${PREFIX}
+
+HAS_CONFIGURE= yes
+CONFIGURE_ARGS+=--prefix=${ETCDIR} \
+ --with-cc-opt="-I ${LOCALBASE}/include" \
+ --conf-path=${ETCDIR}/nginx.conf \
+ --sbin-path=${PREFIX}/sbin/nginx \
+ --pid-path=${NGINX_RUNDIR}/nginx.pid \
+ --error-log-path=${NGINX_ERRORLOG} \
+ --user=${WWWOWN} --group=${WWWGRP} \
+ --with-compat \
+ --with-pcre
+ALL_TARGET=
+
+PLIST_SUB+= NGINX_TMPDIR=${NGINX_TMPDIR} NGINX_LOGDIR=${NGINX_LOGDIR} WWWOWN=${WWWOWN} WWWGRP=${WWWGRP}
+
+USERS?= ${WWWOWN}
+GROUPS?=${WWWGRP}
+
+NO_OPTIONS_SORT= yes
+
+OPTIONS_GROUP= HTTPGRP MAILGRP STREAMGRP
+# Modules that are part of the base nginx distribution
+OPTIONS_GROUP_HTTPGRP= GOOGLE_PERFTOOLS HTTP HTTP_ADDITION HTTP_AUTH_REQ \
+ HTTP_CACHE HTTP_DAV HTTP_DEGRADATION HTTP_FLV HTTP_GUNZIP_FILTER \
+ HTTP_GZIP_STATIC HTTP_IMAGE_FILTER HTTP_MP4 HTTP_PERL \
+ HTTP_RANDOM_INDEX HTTP_REALIP HTTP_SECURE_LINK HTTP_SLICE HTTP_SSL \
+ HTTP_STATUS HTTP_SUB HTTP_XSLT HTTPV2 HTTPV3 HTTPV3_BORING HTTPV3_LSSL \
+ HTTPV3_QTLS
+
+OPTIONS_GROUP_MAILGRP= MAIL MAIL_IMAP MAIL_POP3 MAIL_SMTP MAIL_SSL
+
+OPTIONS_GROUP_STREAMGRP= STREAM STREAM_REALIP STREAM_SSL \
+ STREAM_SSL_PREREAD
+
+OPTIONS_DEFINE= DEBUG DEBUGLOG DSO FILE_AIO IPV6 NJS NJS_XML THREADS WWW
+OPTIONS_DEFAULT?= DSO FILE_AIO HTTP HTTP_ADDITION HTTP_AUTH_REQ HTTP_CACHE \
+ HTTP_DAV HTTP_FLV HTTP_GUNZIP_FILTER HTTP_GZIP_STATIC HTTP_MP4 \
+ HTTP_RANDOM_INDEX HTTP_REALIP HTTP_SECURE_LINK HTTP_SLICE HTTP_SSL \
+ HTTP_STATUS HTTP_SUB HTTPV2 MAIL MAIL_SSL STREAM \
+ STREAM_REALIP STREAM_SSL STREAM_SSL_PREREAD THREADS WWW
+
+LIB_DEPENDS+= libpcre2-8.so:devel/pcre2
+
+OPTIONS_RADIO+= GSSAPI
+OPTIONS_RADIO_GSSAPI= GSSAPI_HEIMDAL GSSAPI_MIT
+GSSAPI_HEIMDAL_USES= gssapi:heimdal,flags
+GSSAPI_MIT_USES= gssapi:mit
+
+OPTIONS_SUB= yes
+
+.include "Makefile.options.desc"
+
+.for opt in ${OPTIONS_GROUP_MAILGRP:NMAIL}
+${opt}_IMPLIES= MAIL
+.endfor
+
+.for opt in ${OPTIONS_GROUP_HTTPGRP:NHTTP} WWW
+${opt}_IMPLIES= HTTP
+.endfor
+
+.for opt in ${OPTIONS_GROUP_STREAMGRP:NSTREAM}
+${opt}_IMPLIES= STREAM
+.endfor
+
+GSSAPI_HEIMDAL_IMPLIES= HTTP_AUTH_KRB5
+GSSAPI_MIT_IMPLIES= HTTP_AUTH_KRB5
+
+# If the target is makesum, make sure that every distfile is fetched.
+.if ${.TARGETS:Mmakesum}
+OPTIONS_DEFAULT= ${OPTIONS_DEFINE} ${OPTIONS_GROUP_HTTPGRP} \
+ ${OPTIONS_GROUP_MAILGRP} ${OPTIONS_GROUP_STREAMGRP} \
+ ${OPTIONS_GROUP_THIRDPARTYGRP}
+.endif
+
+# Non-module options handling
+DEBUG_CFLAGS= -g
+DEBUG_VARS= STRIP=#do not strip if nginx with debug information
+DEBUGLOG_CONFIGURE_ON= --with-debug
+DSO_CONFIGURE_ON= --modules-path=${MODULESDIR}
+DSO_VARS= MODULESDIR=${PREFIX}/libexec/${PORTNAME}
+FILE_AIO_CONFIGURE_ON= --with-file-aio
+IPV6_CONFIGURE_OFF= --with-cc-opt="-DNGX_HAVE_INET6=0 -I ${LOCALBASE}/include"
+THREADS_CONFIGURE_ON= --with-threads
+
+# Bundled modules
+GOOGLE_PERFTOOLS_LIB_DEPENDS= libprofiler.so:devel/google-perftools
+GOOGLE_PERFTOOLS_CONFIGURE_ON= --with-google_perftools_module
+HTTP_CONFIGURE_ON= --http-client-body-temp-path=${NGINX_TMPDIR}/client_body_temp \
+ --http-fastcgi-temp-path=${NGINX_TMPDIR}/fastcgi_temp \
+ --http-proxy-temp-path=${NGINX_TMPDIR}/proxy_temp \
+ --http-scgi-temp-path=${NGINX_TMPDIR}/scgi_temp \
+ --http-uwsgi-temp-path=${NGINX_TMPDIR}/uwsgi_temp \
+ --http-log-path=${NGINX_ACCESSLOG}
+HTTP_CONFIGURE_OFF= --without-http
+HTTP_ADDITION_CONFIGURE_ON= --with-http_addition_module
+HTTP_AUTH_REQ_CONFIGURE_ON= --with-http_auth_request_module
+HTTP_CACHE_CONFIGURE_OFF= --without-http-cache
+HTTP_DAV_CONFIGURE_ON= --with-http_dav_module
+HTTP_DEGRADATION_CONFIGURE_ON= --with-http_degradation_module
+HTTP_FLV_CONFIGURE_ON= --with-http_flv_module
+HTTP_GZIP_STATIC_CONFIGURE_ON= --with-http_gzip_static_module
+HTTP_GUNZIP_FILTER_CONFIGURE_ON=--with-http_gunzip_module
+HTTP_IMAGE_FILTER_LIB_DEPENDS= libgd.so:graphics/gd
+HTTP_IMAGE_FILTER_VARS= DSO_BASEMODS+=http_image_filter_module
+HTTP_MP4_CONFIGURE_ON= --with-http_mp4_module
+HTTP_PERL_CATEGORIES= perl5
+HTTP_PERL_USES= perl5
+# Fix build failure on clang >= 12
+HTTP_PERL_CFLAGS= -Wno-compound-token-split-by-macro
+HTTP_PERL_VARS= DSO_BASEMODS+=http_perl_module
+HTTP_RANDOM_INDEX_CONFIGURE_ON= --with-http_random_index_module
+HTTP_REALIP_CONFIGURE_ON= --with-http_realip_module
+HTTP_SECURE_LINK_CONFIGURE_ON= --with-http_secure_link_module
+HTTP_SLICE_CONFIGURE_ON= --with-http_slice_module
+HTTP_SSL_CONFIGURE_ON= --with-http_ssl_module
+HTTP_SSL_USES= ssl
+HTTP_STATUS_CONFIGURE_ON= --with-http_stub_status_module
+HTTP_SUB_CONFIGURE_ON= --with-http_sub_module
+HTTP_XSLT_CONFIGURE_ON= --with-http_xslt_module
+HTTP_XSLT_LIB_DEPENDS= libxml2.so:textproc/libxml2 \
+ libxslt.so:textproc/libxslt
+HTTP_XSLT_VARS= DSO_BASEMODS+=http_xslt_module
+HTTPV2_IMPLIES= HTTP_SSL
+HTTPV2_CONFIGURE_ON= --with-http_v2_module
+HTTPV3_CONFIGURE_ON= --build=nginx-quic \
+ --with-stream_quic_module \
+ --with-http_v3_module
+HTTPV3_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-httpv3:-p1
+HTTPV3_BORING_BUILD_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl
+HTTPV3_BORING_RUN_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl
+HTTPV3_BORING_IMPLIES= HTTPV3
+HTTPV3_BORING_PREVENTS= HTTPV3_LSSL HTTPV3_QTLS
+HTTPV3_LSSL_BUILD_DEPENDS= ${LOCALBASE}/include/tls.h:security/libressl-devel
+HTTPV3_LSSL_BUILD_DEPENDS= ${LOCALBASE}/include/tls.h:security/libressl-devel
+HTTPV3_LSSL_IMPLIES= HTTPV3
+HTTPV3_LSSL_PREVENTS= HTTPV3_BORING HTTPV3_QTLS
+HTTPV3_QTLS_BUILD_DEPENDS= ${LOCALBASE}/include/openssl/quic.h:security/openssl-quictls
+HTTPV3_QTLS_RUN_DEPENDS= ${LOCALBASE}/include/openssl/quic.h:security/openssl-quictls
+HTTPV3_QTLS_IMPLIES= HTTPV3
+HTTPV3_QTLS_PREVENTS= HTTPV3_BORING HTTPV3_LSSL
+MAIL_VARS= DSO_BASEMODS+=mail
+MAIL_IMAP_CONFIGURE_OFF= --without-mail_imap_module
+MAIL_POP3_CONFIGURE_OFF= --without-mail_pop3_module
+MAIL_SMTP_CONFIGURE_OFF= --without-mail_smtp_module
+MAIL_SSL_USES= ssl
+MAIL_SSL_CONFIGURE_ON= --with-mail_ssl_module
+STREAM_VARS= DSO_BASEMODS+=stream
+STREAM_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 <bsd.port.pre.mk>
+
+.if ${PORT_OPTIONS:MDSO}
+_addbasemod= =dynamic
+_addextmod= add-dynamic-module
+.else
+_addextmod= add-module
+.endif
+
+.for mod in ${DSO_BASEMODS}
+CONFIGURE_ARGS+= --with-${mod}${_addbasemod}
+.endfor
+
+# Some modules depend on other being there before, for example, devel_kit needs
+# to be there before a few other.
+.for mod in ${FIRST_DSO_EXTMODS}
+CONFIGURE_ARGS+= --${_addextmod}=${WRKSRC_${mod}}${${mod:tu}_SUBDIR}
+.endfor
+
+.for mod in ${DSO_EXTMODS}
+CONFIGURE_ARGS+= --${_addextmod}=${WRKSRC_${mod}}${${mod:tu}_SUBDIR}
+.endfor
+# For non-GitHub hosted modules
+.for moddir in ${DSO_EXTDIRS}
+CONFIGURE_ARGS+= --${_addextmod}=${WRKDIR}/${moddir}
+.endfor
+
+.if empty(PORT_OPTIONS:MHTTP) && empty(PORT_OPTIONS:MMAIL)
+IGNORE= requires at least HTTP or MAIL to \
+ be defined. Please do 'make config' again
+.endif
+
+.if ${PORT_OPTIONS:MHTTP_AUTH_KRB5} && (empty(PORT_OPTIONS:MGSSAPI_HEIMDAL) && empty(PORT_OPTIONS:MGSSAPI_MIT))
+IGNORE= required at least GSSAPI_HEIMDAL or \
+ GSSAPI_MIT to be defined. Please do \
+ 'make config' again
+.endif
+
+.if ${PORT_OPTIONS:MPASSENGER} && empty(PORT_OPTIONS:MDEBUG)
+CONFIGURE_ENV+= OPTIMIZE="yes"
+CFLAGS+= -DNDEBUG
+.endif
+
+.if ${PORT_OPTIONS:MPASSENGER}
+CONFIGURE_ENV+= EXTRA_PRE_CXXFLAGS="-std=c++14"
+.endif
+
+.if empty(PORT_OPTIONS:MLUA) && empty(PORT_OPTIONS:MMODSECURITY3) && \
+ empty(PORT_OPTIONS:MPASSENGER)
+CONFIGURE_ARGS+= --with-ld-opt="-L ${LOCALBASE}/lib"
+.else
+CONFIGURE_ARGS+= --with-ld-opt="-L ${LOCALBASE}/lib -lpcre"
+LIB_DEPENDS+= libpcre.so:devel/pcre
+.endif
+
+.if ${PORT_OPTIONS:MNJS} && empty(PORT_OPTIONS:MNJS_XML)
+CONFIGURE_ENV+= NJS_LIBXSLT=NO
+NJS_CONFIGURE_ARGS= --no-libxml2
+.endif
+
+pre-everything::
+ @${ECHO_MSG}
+.if ${PORT_OPTIONS:MHTTP_UPSTREAM_FAIR}
+ @${ECHO_MSG} "Enable http_ssl module to build upstream_fair with SSL support"
+.endif
+.if ${PORT_OPTIONS:MPASSENGER}
+ @${ECHO_MSG} "This port install Passenger module only"
+.endif
+ @${ECHO_MSG}
+
+post-extract-NAXSI-on:
+ @${MKDIR} ${WRKDIR}/naxsi-${NAXSI_NGINX_VER}
+ @${MV} ${WRKDIR}/naxsi_rules ${WRKDIR}/naxsi_src \
+ ${WRKDIR}/naxsi-${NAXSI_NGINX_VER}
+
+pre-patch-HTTPV3-on:
+ @${MV} ${WRKSRC}/README ${WRKSRC}/README.1st
+
+post-patch:
+ @${REINPLACE_CMD} 's!%%HTTP_PORT%%!${HTTP_PORT}!; \
+ s!%%PREFIX%%!${PREFIX}!; \
+ s!%%NGINX_ERRORLOG%%!${NGINX_ERRORLOG}!' \
+ ${WRKSRC}/conf/nginx.conf
+
+post-patch-BROTLI-on:
+ @${REINPLACE_CMD} -E 's!^brotli=.*!brotli="${LOCALBASE}"!' ${WRKSRC_brotli}/config
+
+post-patch-DRIZZLE-on:
+ @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_drizzle}/config
+
+post-patch-FASTDFS-on:
+ @${REINPLACE_CMD} \
+ 's!%%PREFIX%%!${PREFIX}!g;s!%%LOCALBASE%%!${LOCALBASE}!g' \
+ ${WRKSRC_fastdfs}/src/config
+
+post-patch-GRIDFS-on:
+ @${REINPLACE_CMD} 's!\/usr!${LOCALBASE}!g' \
+ ${WRKSRC_gridfs}/nginx-gridfs/config
+
+post-patch-HTTP_AUTH_KRB5-on:
+ @${REINPLACE_CMD} 's!%%GSSAPILIBS%%!${GSSAPILIBS}!g; \
+ s!%%GSSAPIINCDIR%%!${GSSAPIINCDIR}!g; \
+ s!%%GSSAPILIBDIR%%!${GSSAPILIBDIR}!g' ${WRKSRC_auth_krb5}/config
+
+post-patch-HTTP_TARANTOOL-on:
+ @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_nginx_tarantool}/config
+
+# linker error acquire if --std=c99 defined, add "static" to inline function
+post-patch-HTTP_ZIP-on:
+ @${REINPLACE_CMD} \
+ 's!^inline!static inline!' \
+ ${WRKSRC_mod_zip}/ngx_http_zip_parsers.*
+
+post-patch-ICONV-on:
+ @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_iconv}/config
+
+post-patch-NAXSI-on:
+ @${REINPLACE_CMD} 's!MSIZE!TOK_MSIZE!g' \
+ ${WRKSRC_naxsi}/naxsi_src/libinjection/src/libinjection_sqli.c
+
+post-patch-PASSENGER-on:
+ @${REINPLACE_CMD} \
+ '177,179s!true!false!' \
+ ${WRKSRC_PASSENGER}/build/basics.rb
+ @${REINPLACE_CMD} \
+ 's!-I/usr/include/libev!!; \
+ s!-lev!!; \
+ s!-Iext/libev!!; \
+ s!-I/usr/include/libeio!!; \
+ s!-leio!!; \
+ s!-Iext/libeio!!' \
+ ${WRKSRC_PASSENGER}/build/common_library.rb
+
+post-patch-POSTGRES-on:
+ @${REINPLACE_CMD} 's!%%PREFIX%%!${LOCALBASE}!g' ${WRKSRC_postgres}/config
+
+post-patch-SFLOW-on:
+ @${REINPLACE_CMD} \
+ 's!%%PREFIX%%!${LOCALBASE}!g' \
+ ${WRKSRC_sflow}/ngx_http_sflow_config.h
+
+post-patch-VOD-on:
+ @${REINPLACE_CMD} \
+ 's!%%PREFIX%%!${LOCALBASE}!g' \
+ ${WRKSRC_vod}/config
+
+pre-configure-SMALL_LIGHT-on:
+ ( cd ${WRKSRC_small_light} && ./setup )
+
+do-configure-NJS-on:
+ ( cd ${WRKSRC_njs} && ${SETENV} ${CONFIGURE_ENV} ${CONFIGURE_CMD} ${NJS_CONFIGURE_ARGS} )
+
+.if !target(do-install)
+do-install:
+ ${MKDIR} ${STAGEDIR}${ETCDIR}
+ ${MKDIR} ${STAGEDIR}${NGINX_TMPDIR}
+ ${MKDIR} ${STAGEDIR}${NGINX_LOGDIR}
+ ${INSTALL_PROGRAM} ${WRKSRC}/objs/nginx ${STAGEDIR}${PREFIX}/sbin
+.for i in koi-utf koi-win win-utf
+ ${INSTALL_DATA} ${WRKSRC}/conf/${i} ${STAGEDIR}${ETCDIR}
+.endfor
+.for i in fastcgi_params mime.types scgi_params uwsgi_params
+ ${INSTALL_DATA} ${WRKSRC}/conf/${i} ${STAGEDIR}${ETCDIR}/${i}-dist
+.endfor
+
+do-install-HTTP_PERL-on:
+ ${MKDIR} ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/auto/nginx
+ ${INSTALL_PROGRAM} ${WRKSRC}/objs/src/http/modules/perl/blib/arch/auto/nginx/nginx.so \
+ ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/auto/nginx
+ ${INSTALL_DATA} ${WRKSRC}/objs/src/http/modules/perl/blib/lib/nginx.pm \
+ ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/
+
+# Install dynamic modules
+do-install-DSO-on:
+ ${MKDIR} ${STAGEDIR}${MODULESDIR}
+ (cd ${WRKSRC}/objs/ && ${FIND} . -name '*.so' -maxdepth 1 -type f \
+ -exec ${INSTALL_PROGRAM} {} ${STAGEDIR}${MODULESDIR} \;)
+
+do-install-LINK-on:
+ ${INSTALL_DATA} ${WRKSRC_link}/src/ngx_link_func_module.h ${STAGEDIR}${PREFIX}/include
+
+do-install-NAXSI-on:
+ ${INSTALL_DATA} \
+ ${WRKDIR}/naxsi-${NAXSI_NGINX_VER}/naxsi_rules/naxsi_core.rules \
+ ${STAGEDIR}${ETCDIR}
+.endif
+
+.if !target(post-install)
+post-install:
+ ${MKDIR} ${STAGEDIR}${PREFIX}/share/vim/vimfiles
+ cd ${WRKSRC}/contrib/vim && ${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/share/vim/vimfiles
+ ${INSTALL_MAN} ${WRKSRC}/objs/nginx.8 ${STAGEDIR}${PREFIX}/share/man/man8
+ ${CAT} ${WRKSRC}/conf/nginx.conf >> ${STAGEDIR}${ETCDIR}/nginx.conf-dist
+
+post-install-WWW-on:
+ ${MKDIR} ${STAGEDIR}${PREFIX}/www/nginx-dist
+ (cd ${WRKSRC}/html && ${COPYTREE_SHARE} . ${STAGEDIR}${PREFIX}/www/nginx-dist && \
+ ${TOUCH} ${STAGEDIR}${PREFIX}/www/nginx-dist/EXAMPLE_DIRECTORY-DONT_ADD_OR_TOUCH_ANYTHING)
+.endif
+
+.include <bsd.port.post.mk>
diff --git a/www/freenginx/Makefile.extmod b/www/freenginx/Makefile.extmod
new file mode 100644
index 000000000000..f3f6d0526210
--- /dev/null
+++ b/www/freenginx/Makefile.extmod
@@ -0,0 +1,330 @@
+### External modules
+
+OPTIONS_GROUP+= THIRDPARTYGRP
+# External modules (arrayvar MUST appear after devel_kit for build-dep)
+OPTIONS_GROUP_THIRDPARTYGRP= AJP AWS_AUTH BROTLI CACHE_PURGE CLOJURE COOKIE_FLAG CT \
+ DEVEL_KIT ARRAYVAR DRIZZLE DYNAMIC_UPSTREAM ECHO ENCRYPTSESSION \
+ FIPS_CHECK FORMINPUT GRIDFS HEADERS_MORE HTTP_ACCEPT_LANGUAGE HTTP_AUTH_DIGEST \
+ HTTP_AUTH_KRB5 HTTP_AUTH_LDAP HTTP_AUTH_PAM HTTP_DAV_EXT HTTP_EVAL \
+ HTTP_FANCYINDEX HTTP_FOOTER HTTP_GEOIP2 HTTP_IP2LOCATION HTTP_IP2PROXY \
+ HTTP_JSON_STATUS HTTP_MOGILEFS HTTP_MP4_H264 HTTP_NOTICE HTTP_PROXY_CONNECT HTTP_PUSH \
+ HTTP_PUSH_STREAM HTTP_REDIS HTTP_SLICE_AHEAD HTTP_SUBS_FILTER HTTP_TARANTOOL \
+ HTTP_UPLOAD HTTP_UPLOAD_PROGRESS HTTP_UPSTREAM_CHECK HTTP_UPSTREAM_FAIR \
+ HTTP_UPSTREAM_STICKY HTTP_VIDEO_THUMBEXTRACTOR HTTP_ZIP ICONV LET LINK LUA MEMC \
+ MODSECURITY3 NAXSI OPENTRACING PASSENGER POSTGRES RDS_CSV RDS_JSON \
+ REDIS2 RTMP SET_MISC SFLOW SHIBBOLETH SLOWFS_CACHE SRCACHE STS \
+ VOD VTS XSS WEBSOCKIFY
+
+AJP_GH_TUPLE= msva:nginx_ajp_module:fcbb2cc:ajp
+AJP_VARS= DSO_EXTMODS+=ajp
+
+ARRAYVAR_IMPLIES= DEVEL_KIT
+ARRAYVAR_GH_TUPLE= openresty:array-var-nginx-module:v0.05:arrayvar
+ARRAYVAR_VARS= DSO_EXTMODS+=arrayvar
+
+AWS_AUTH_GH_TUPLE= anomalizer:ngx_aws_auth:21931b2:aws_auth
+AWS_AUTH_VARS= DSO_EXTMODS+=aws_auth
+
+BROTLI_LIB_DEPENDS= libbrotlicommon.so:archivers/brotli
+BROTLI_GH_TUPLE= google:ngx_brotli:9aec15e:brotli
+BROTLI_VARS= DSO_EXTMODS+=brotli
+
+CACHE_PURGE_GH_TUPLE= nginx-modules:ngx_cache_purge:a84b0f3:cache_purge
+CACHE_PURGE_VARS= DSO_EXTMODS+=cache_purge
+
+CLOJURE_CATEGORIES+= java
+CLOJURE_USE= JAVA=yes JAVA_OS=native JAVA_VERSION=1.8 \
+ JAVA_VENDOR=openjdk JAVA_BUILD=yes JAVA_RUN=yes
+CLOJURE_GH_TUPLE= nginx-clojure:nginx-clojure:v0.6.0:clojure
+CLOJURE_CONFIGURE_ENV= "JNI_INCS=-I${LOCALBASE}/openjdk8/include -I${LOCALBASE}/openjdk8/include/freebsd"
+CLOJURE_VARS= DSO_EXTMODS+=clojure CLOJURE_SUBDIR=/src/c
+
+COOKIE_FLAG_GH_TUPLE= AirisX:nginx_cookie_flag_module:c4ff449:cookie_flag
+COOKIE_FLAG_VARS= DSO_EXTMODS+=cookie_flag
+
+CT_IMPLIES= HTTP_SSL
+CT_GH_TUPLE= grahamedgecombe:nginx-ct:93e9884:ct
+CT_VARS= DSO_EXTMODS+=ct
+CT_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-ct-LibreSSL
+
+ECHO_GH_TUPLE= openresty:echo-nginx-module:5a402aa:echo
+ECHO_VARS= DSO_EXTMODS+=echo
+
+DRIZZLE_LIB_DEPENDS= libdrizzle.so:databases/libdrizzle
+DRIZZLE_CONFIGURE_ENV= LIBDRIZZLE_INC=${LOCALBASE}/include \
+ LIBDRIZZLE_LIB=${LOCALBASE}/lib
+DRIZZLE_GH_TUPLE= openresty:drizzle-nginx-module:3504fc6:drizzle
+DRIZZLE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-openresty-drizzle-nginx-module-config
+DRIZZLE_VARS= DSO_EXTMODS+=drizzle
+
+DYNAMIC_UPSTREAM_IMPLIES= STREAM
+DYNAMIC_UPSTREAM_GH_TUPLE= ZigzagAK:ngx_dynamic_upstream:960eef2:dynamic_upstream
+DYNAMIC_UPSTREAM_VARS= DSO_EXTMODS+=dynamic_upstream
+
+DEVEL_KIT_GH_TUPLE= vision5:ngx_devel_kit:v0.3.2:devel_kit
+DEVEL_KIT_VARS= FIRST_DSO_EXTMODS+=devel_kit
+
+ENCRYPTSESSION_IMPLIES= DEVEL_KIT
+ENCRYPTSESSION_GH_TUPLE= openresty:encrypted-session-nginx-module:v0.09:encryptsession
+ENCRYPTSESSION_VARS= DSO_EXTMODS+=encryptsession
+
+FIPS_CHECK_GH_TUPLE= ogarrett:nginx-fips-check-module:6cb4270:fipscheck
+FIPS_CHECK_VARS= DSO_EXTMODS+=fipscheck
+
+FORMINPUT_IMPLIES= DEVEL_KIT
+FORMINPUT_GH_TUPLE= calio:form-input-nginx-module:v0.12:forminput
+FORMINPUT_VARS= DSO_EXTMODS+=forminput
+
+GRIDFS_GH_TUPLE= nieoding:nginx-gridfs:059bdc3:gridfs
+GRIDFS_LIB_DEPENDS= libbson-1.0.so:devel/libbson \
+ libmongoc-1.0.so:devel/mongo-c-driver
+GRIDFS_VARS= DSO_EXTMODS+=gridfs GRIDFS_SUBDIR=/nginx-gridfs
+
+HEADERS_MORE_GH_TUPLE= openresty:headers-more-nginx-module:33b646d:headers_more
+HEADERS_MORE_VARS= DSO_EXTMODS+=headers_more
+
+HTTP_ACCEPT_LANGUAGE_GH_TUPLE= dvershinin:nginx_accept_language_module:5683967:accept_language
+HTTP_ACCEPT_LANGUAGE_VARS= DSO_EXTMODS+=accept_language
+
+HTTP_AUTH_DIGEST_GH_TUPLE= atomx:nginx-http-auth-digest:274490c:auth_digest
+HTTP_AUTH_DIGEST_VARS= DSO_EXTMODS+=auth_digest
+
+HTTP_AUTH_KRB5_GH_TUPLE= stnoonan:spnego-http-auth-nginx-module:3575542:auth_krb5
+HTTP_AUTH_KRB5_VARS= DSO_EXTMODS+=auth_krb5
+HTTP_AUTH_KRB5_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-spnego-http-auth-nginx-module-config
+
+HTTP_AUTH_LDAP_GH_TUPLE= kvspb:nginx-auth-ldap:83c059b:http_auth_ldap
+HTTP_AUTH_LDAP_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_auth_ldap_module.c
+HTTP_AUTH_LDAP_VARS= DSO_EXTMODS+=http_auth_ldap
+HTTP_AUTH_LDAP_USES= ldap
+
+HTTP_AUTH_PAM_GH_TUPLE= sto:ngx_http_auth_pam_module:v1.5.3:auth_pam
+HTTP_AUTH_PAM_VARS= DSO_EXTMODS+=auth_pam
+
+HTTP_PROXY_CONNECT_GH_TUPLE= chobits:ngx_http_proxy_connect_module:75febef:mod_https_connect
+HTTP_PROXY_CONNECT_EXTRA_PATCHES= ${WRKSRC_mod_https_connect}/patch/proxy_connect_rewrite_102101.patch:-p1
+HTTP_PROXY_CONNECT_VARS= DSO_EXTMODS+=mod_https_connect
+
+HTTP_DAV_EXT_IMPLIES= HTTP_DAV
+HTTP_DAV_EXT_LIB_DEPENDS= libxml2.so:textproc/libxml2 \
+ libxslt.so:textproc/libxslt
+HTTP_DAV_EXT_GH_TUPLE= arut:nginx-dav-ext-module:v3.0.0:dav_ext
+HTTP_DAV_EXT_VARS= DSO_EXTMODS+=dav_ext
+HTTP_DAV_EXT_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_dav_ext_module.c
+
+HTTP_EVAL_GH_TUPLE= openresty:nginx-eval-module:582bd25:eval
+HTTP_EVAL_VARS= DSO_EXTMODS+=eval
+
+HTTP_FANCYINDEX_GH_TUPLE= aperezdc:ngx-fancyindex:v0.5.2:fancyindex
+HTTP_FANCYINDEX_VARS= DSO_EXTMODS+=fancyindex
+
+HTTP_FOOTER_GH_TUPLE= alibaba:nginx-http-footer-filter:1.2.2:footer
+HTTP_FOOTER_VARS= DSO_EXTMODS+=footer
+HTTP_FOOTER_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-http-footer-filter-config
+
+HTTP_GEOIP2_GH_TUPLE= leev:ngx_http_geoip2_module:3.4:geoip2
+HTTP_GEOIP2_CFLAGS= -I${LOCALBASE}/include
+HTTP_GEOIP2_VARS= DSO_EXTMODS+=geoip2
+HTTP_GEOIP2_LIB_DEPENDS= libmaxminddb.so:net/libmaxminddb
+
+HTTP_IP2LOCATION_GH_TUPLE= ip2location:ip2location-nginx:2df35fb:ip2location
+HTTP_IP2LOCATION_LIB_DEPENDS= libIP2Location.so:net/ip2location
+HTTP_IP2LOCATION_VARS= DSO_EXTMODS+=ip2location
+
+HTTP_IP2PROXY_GH_TUPLE= ip2location:ip2proxy-nginx:02ce447:ip2proxy
+HTTP_IP2PROXY_LIB_DEPENDS= libIP2Proxy.so:net/ip2proxy
+HTTP_IP2PROXY_VARS= DSO_EXTMODS+=ip2proxy
+
+HTTP_JSON_STATUS_GH_TUPLE= nginx-modules:ngx_http_json_status_module:1d2f303:json_status
+HTTP_JSON_STATUS_VARS= DSO_EXTMODS+=json_status
+HTTP_JSON_STATUS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_json_status_module-config
+
+HTTP_MOGILEFS_MASTER_SITES= http://www.grid.net.ru/nginx/download/:mogilefs
+HTTP_MOGILEFS_DISTFILES= nginx_mogilefs_module-1.0.4.tar.gz:mogilefs
+HTTP_MOGILEFS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_mogilefs_module.c \
+ ${PATCHDIR}/extra-patch-nginx_mogilefs_module-config
+HTTP_MOGILEFS_VARS= DSO_EXTDIRS+=nginx_mogilefs_module-1.0.4
+
+HTTP_MP4_H264_MASTER_SITES= http://h264.code-shop.com/download/:mp4streaming
+HTTP_MP4_H264_CONFIGURE_ON= --with-cc-opt="-DLARGEFILE_SOURCE -DBUILDING_NGINX"
+HTTP_MP4_H264_DISTFILES= nginx_mod_h264_streaming-2.2.7.tar.gz:mp4streaming
+HTTP_MP4_H264_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_streaming_module.c \
+ ${PATCHDIR}/extra-patch-nginx_mod_h264_streaming-config
+HTTP_MP4_H264_VARS= DSO_EXTDIRS+=nginx_mod_h264_streaming-2.2.7
+
+HTTP_NOTICE_GH_TUPLE= kr:nginx-notice:3c95966:notice
+HTTP_NOTICE_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_notice_module.c \
+ ${PATCHDIR}/extra-patch-nginx-notice-config
+HTTP_NOTICE_VARS= DSO_EXTMODS+=notice
+
+HTTP_PUSH_GH_TUPLE= slact:nchan:v1.3.6:push
+HTTP_PUSH_VARS= DSO_EXTMODS+=push
+
+HTTP_PUSH_STREAM_GH_TUPLE= wandenberg:nginx-push-stream-module:8c02220:pushstream
+HTTP_PUSH_STREAM_VARS= DSO_EXTMODS+=pushstream
+
+HTTP_REDIS_MASTER_SITES= LOCAL/osa:redis
+HTTP_REDIS_DISTFILES= ngx_http_redis-0.3.9.tar.gz:redis
+HTTP_REDIS_VARS= DSO_EXTDIRS+=ngx_http_redis-0.3.9
+HTTP_REDIS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_redis_module.c
+
+HTTP_SLICE_AHEAD_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c
+
+HTTP_SUBS_FILTER_GH_TUPLE= yaoweibin:ngx_http_substitutions_filter_module:c6f825f:subs_filter
+HTTP_SUBS_FILTER_VARS= DSO_EXTMODS+=subs_filter
+
+HTTP_TARANTOOL_LIB_DEPENDS= libmsgpuck.so:devel/msgpuck \
+ libyajl.so:devel/yajl
+HTTP_TARANTOOL_GH_TUPLE= tarantool:nginx_upstream_module:aeb8696:nginx_tarantool
+HTTP_TARANTOOL_VARS= DSO_EXTMODS+=nginx_tarantool
+HTTP_TARANTOOL_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_tarantool-config
+
+HTTP_UPLOAD_GH_TUPLE= fdintino:nginx-upload-module:643b4c1:upload
+HTTP_UPLOAD_VARS= DSO_EXTMODS+=upload
+
+HTTP_UPLOAD_PROGRESS_GH_TUPLE= masterzen:nginx-upload-progress-module:68b3ab3:uploadprogress
+HTTP_UPLOAD_PROGRESS_VARS= DSO_EXTMODS+=uploadprogress
+HTTP_UPLOAD_PROGRESS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_uploadprogress_module.c
+
+HTTP_UPSTREAM_CHECK_GH_TUPLE= yaoweibin:nginx_upstream_check_module:9aecf15:upstreamcheck
+HTTP_UPSTREAM_CHECK_CONFIGURE_ON= --add-module=${WRKSRC_upstreamcheck}
+HTTP_UPSTREAM_CHECK_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-src-http-modules-ngx_http_upstream_hash_module.c \
+ ${PATCHDIR}/extra-patch-src-http-modules-ngx_http_upstream_ip_hash_module.c \
+ ${PATCHDIR}/extra-patch-src-http-modules-ngx_http_upstream_least_conn_module.c \
+ ${PATCHDIR}/extra-patch-src-http-ngx_http_upstream_round_robin.c \
+ ${PATCHDIR}/extra-patch-src-http-ngx_http_upstream_round_robin.h
+
+HTTP_UPSTREAM_FAIR_GH_TUPLE= jaygooby:nginx-upstream-fair:10ecdcf:upstreamfair
+HTTP_UPSTREAM_FAIR_VARS= DSO_EXTMODS+=upstreamfair
+
+HTTP_UPSTREAM_STICKY_IMPLIES= HTTP_SSL
+HTTP_UPSTREAM_STICKY_GH_TUPLE= dvershinin:nginx-sticky-module-ng:2753211:upstreamsticky
+HTTP_UPSTREAM_STICKY_VARS= DSO_EXTMODS+=upstreamsticky
+
+HTTP_VIDEO_THUMBEXTRACTOR_LIB_DEPENDS= libavformat.so:multimedia/ffmpeg \
+ libavcodec.so:multimedia/ffmpeg \
+ libavutil.so:multimedia/ffmpeg \
+ libswscale.so:multimedia/ffmpeg
+HTTP_VIDEO_THUMBEXTRACTOR_USES= jpeg
+HTTP_VIDEO_THUMBEXTRACTOR_GH_TUPLE= Novetta:nginx-video-thumbextractor-module:28861f2:vte
+HTTP_VIDEO_THUMBEXTRACTOR_VARS= DSO_EXTMODS+=vte
+
+HTTP_ZIP_GH_TUPLE= evanmiller:mod_zip:39dc908:mod_zip
+HTTP_ZIP_VARS= DSO_EXTMODS+=mod_zip
+
+ICONV_IMPLIES= DEVEL_KIT
+ICONV_USES= iconv
+ICONV_GH_TUPLE= calio:iconv-nginx-module:v0.14:iconv
+ICONV_VARS= DSO_EXTMODS+=iconv
+ICONV_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-calio-iconv-nginx-module-config
+
+LET_GH_TUPLE= baysao:nginx-let-module:c1f23aa:let
+LET_VARS= DSO_EXTMODS+=let
+
+LUA_IMPLIES= DEVEL_KIT
+LUA_LIB_DEPENDS= libluajit-5.1.so:lang/luajit-openresty
+LUA_RUN_DEPENDS= lua-resty-core>0:www/lua-resty-core
+LUA_CONFIGURE_ENV= LUAJIT_INC=${LOCALBASE}/include/luajit-2.1 \
+ LUAJIT_LIB=${LOCALBASE}/lib
+LUA_GH_TUPLE= openresty:lua-nginx-module:v0.10.26:lua
+LUA_VARS= DSO_EXTMODS+=lua
+
+LINK_GH_TUPLE= Taymindis:nginx-link-function:3.2.4:link
+LINK_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-link-function-config \
+ ${PATCHDIR}/extra-patch-ngx_link_func_module.c
+LINK_VARS= DSO_EXTMODS+=link
+
+MEMC_GH_TUPLE= openresty:memc-nginx-module:v0.19:memc
+MEMC_VARS= DSO_EXTMODS+=memc
+
+MODSECURITY3_IMPLIES= HTTP_ADDITION HTTP_IMAGE_FILTER HTTP_GUNZIP_FILTER HTTP_XSLT
+MODSECURITY3_LIB_DEPENDS= libmodsecurity.so:security/modsecurity3
+MODSECURITY3_GH_TUPLE= SpiderLabs:ModSecurity-nginx:v1.0.3:modsecurity3
+MODSECURITY3_VARS= DSO_EXTMODS+=modsecurity3
+
+NAXSI_NGINX_VER= 1.6
+NAXSI_MASTER_SITES= https://www.github.com/wargio/naxsi/releases/download/${NAXSI_NGINX_VER}/:naxsi
+NAXSI_DISTFILES= naxsi-${NAXSI_NGINX_VER}-src-with-deps.tar.gz:naxsi
+NAXSI_VARS= DSO_EXTMODS+=naxsi NAXSI_SUBDIR=/naxsi_src
+WRKSRC_naxsi= ${WRKDIR}/naxsi-${NAXSI_NGINX_VER}
+
+NJS_GH_TUPLE= nginx:njs:0.8.0:njs
+NJS_VARS= DSO_EXTMODS+=njs NJS_SUBDIR=/nginx
+
+NJS_XML_IMPLIES= NJS
+NJS_XML_LIB_DEPENDS= libxml2.so:textproc/libxml2 \
+ libxslt.so:textproc/libxslt
+
+OPENTRACING_GH_TUPLE= opentracing-contrib:nginx-opentracing:v0.24.0:opentracing
+OPENTRACING_LIB_DEPENDS= libopentracing.so:devel/libopentracing
+OPENTRACING_VARS= DSO_EXTMODS+=opentracing OPENTRACING_SUBDIR=/opentracing
+OPENTRACING_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-nginx-opentracing-opentracing-config
+
+PASSENGER_NGINX_VER= 6.0.17
+PASSENGER_CATEGORIES= ruby
+PASSENGER_USES= ruby
+PASSENGER_BUILD_DEPENDS=${LOCALBASE}/bin/rake:devel/rubygem-rake
+PASSENGER_RAKE_BIN= ${LOCALBASE}/bin/rake
+PASSENGER_MASTER_SITES= https://s3.amazonaws.com/phusion-passenger/releases/:passenger
+PASSENGER_DISTFILES= passenger-${PASSENGER_NGINX_VER}.tar.gz:passenger
+PASSENGER_VARS= WRKSRC_passenger=${WRKDIR}/passenger-${PASSENGER_NGINX_VER} \
+ DSO_EXTDIRS+=passenger-${PASSENGER_NGINX_VER}/src/nginx_module
+PASSENGER_EXTRA_PATCHES=${PATCHDIR}/extra-patch-passenger-build-nginx.rb \
+ ${PATCHDIR}/extra-patch-passenger-disable-telemetry
+
+POSTGRES_USES= pgsql
+POSTGRES_GH_TUPLE= konstruxi:ngx_postgres:8aa7359:postgres
+POSTGRES_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_postgres-config
+POSTGRES_VARS= DSO_EXTMODS+=postgres
+
+RDS_CSV_GH_TUPLE= openresty:rds-csv-nginx-module:v0.09:rdscsv
+RDS_CSV_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-rds-csv-nginx-module-config
+RDS_CSV_VARS= DSO_EXTMODS+=rdscsv
+
+RDS_JSON_GH_TUPLE= openresty:rds-json-nginx-module:v0.15:rdsjson
+RDS_JSON_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-rds-json-nginx-module-config
+RDS_JSON_VARS= DSO_EXTMODS+=rdsjson
+
+REDIS2_GH_TUPLE= openresty:redis2-nginx-module:v0.15:redis2
+REDIS2_VARS= DSO_EXTMODS+=redis2
+
+RTMP_GH_TUPLE= arut:nginx-rtmp-module:v1.2.2:rtmp
+RTMP_VARS= DSO_EXTMODS+=rtmp
+
+SET_MISC_IMPLIES= DEVEL_KIT
+SET_MISC_GH_TUPLE= openresty:set-misc-nginx-module:3937e7b:setmisc
+SET_MISC_VARS= DSO_EXTMODS+=setmisc
+
+SFLOW_GH_TUPLE= sflow:nginx-sflow-module:543c72a:sflow
+SFLOW_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-ngx_http_sflow_config.c \
+ ${PATCHDIR}/extra-patch-ngx_http_sflow_config.h \
+ ${PATCHDIR}/extra-patch-ngx_http_sflow_module.c
+
+SHIBBOLETH_GH_TUPLE= nginx-shib:nginx-http-shibboleth:be12df5:shibboleth
+SHIBBOLETH_VARS= DSO_EXTMODS+=shibboleth
+
+SLOWFS_CACHE_GH_TUPLE= baysao:ngx_slowfs_cache:d011a18:slowfs_cache
+SLOWFS_CACHE_VARS= DSO_EXTMODS+=slowfs_cache
+
+SRCACHE_GH_TUPLE= openresty:srcache-nginx-module:be22ac0:srcache
+SRCACHE_VARS= DSO_EXTMODS+=srcache
+
+STS_IMPLIES= STREAM
+STS_GH_TUPLE= vozlt:nginx-module-sts:3c10d42:sts
+STS_VARS= DSO_EXTMODS+=sts
+
+VOD_GH_TUPLE= kaltura:nginx-vod-module:1.31:vod
+VOD_LIB_DEPENDS= libxml2.so:textproc/libxml2 \
+ libavutil.so:multimedia/ffmpeg
+VOD_USES= iconv
+VOD_VARS= DSO_EXTMODS+=vod
+
+VTS_GH_TUPLE= vozlt:nginx-module-vts:bf64dbf:vts
+VTS_VARS= DSO_EXTMODS+=vts
+
+XSS_GH_TUPLE= openresty:xss-nginx-module:v0.06:xss
+XSS_VARS= DSO_EXTMODS+=xss
+XSS_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-xss-nginx-module-config
+
+WEBSOCKIFY_GH_TUPLE= tg123:websockify-nginx-module:c11bc9a:websockify
+WEBSOCKIFY_VARS= DSO_EXTMODS+=websockify
diff --git a/www/freenginx/Makefile.options.desc b/www/freenginx/Makefile.options.desc
new file mode 100644
index 000000000000..dc7f5a7c47a1
--- /dev/null
+++ b/www/freenginx/Makefile.options.desc
@@ -0,0 +1,120 @@
+AJP_DESC= 3rd party ajp module
+ARRAYVAR_DESC= 3rd party array_var module
+AWS_AUTH_DESC= 3rd party aws auth module
+BROTLI_DESC= 3rd party brotli module
+CACHE_PURGE_DESC= 3rd party cache_purge module
+CLOJURE_DESC= 3rd party clojure module
+COOKIE_FLAG_DESC= 3rd party cookie_flag module
+CT_DESC= 3rd party cert_transparency module (SSL req.)
+DEBUGLOG_DESC= Enable debug log (--with-debug)
+DEVEL_KIT_DESC= 3rd party Nginx Development Kit module
+DRIZZLE_DESC= 3rd party drizzle module
+DSO_DESC= Enable dynamic modules support
+DYNAMIC_UPSTREAM_DESC= 3rd party dynamic_upstream module
+ECHO_DESC= 3rd party echo module
+ENCRYPTSESSION_DESC= 3rd party encrypted_session module
+FILE_AIO_DESC= Enable file aio
+FIPS_CHECK_DESC= 3rd party fips_check module
+FORMINPUT_DESC= 3rd party form_input module
+GOOGLE_PERFTOOLS_DESC= Enable google perftools module
+GRIDFS_DESC= 3rd party gridfs module
+GSSAPI_DESC= GSSAPI implementation (imply HTTP_AUTH_KRB5)
+HEADERS_MORE_DESC= 3rd party headers_more module
+HTTPGRP_DESC= Modules that require HTTP module
+HTTPV2_DESC= Enable HTTP/2 protocol support (SSL req.)
+HTTPV3_DESC= Enable HTTP/3 protocol support
+HTTPV3_BORING_DESC= Use security/boringssl
+HTTPV3_LSSL_DESC= Use security/libressl-devel
+HTTPV3_QTLS_DESC= Use security/openssl-quictls
+HTTP_ACCEPT_LANGUAGE_DESC= 3rd party accept_language module
+HTTP_ADDITION_DESC= Enable http_addition module
+HTTP_AUTH_DIGEST_DESC= 3rd party http_authdigest module
+HTTP_AUTH_KRB5_DESC= 3rd party http_auth_gss module
+HTTP_AUTH_LDAP_DESC= 3rd party http_auth_ldap module
+HTTP_AUTH_PAM_DESC= 3rd party http_auth_pam module
+HTTP_AUTH_REQ_DESC= Enable http_auth_request module
+HTTP_CACHE_DESC= Enable http_cache module
+HTTP_DAV_DESC= Enable http_webdav module
+HTTP_DAV_EXT_DESC= 3rd party webdav_ext module
+HTTP_DEGRADATION_DESC= Enable http_degradation module
+HTTP_DESC= Enable HTTP module
+HTTP_EVAL_DESC= 3rd party eval module
+HTTP_FANCYINDEX_DESC= 3rd party http_fancyindex module
+HTTP_FLV_DESC= Enable http_flv module
+HTTP_FOOTER_DESC= 3rd party http_footer module
+HTTP_GEOIP2_DESC= 3rd party geoip2 module
+HTTP_GUNZIP_FILTER_DESC= Enable http_gunzip_filter module
+HTTP_GZIP_STATIC_DESC= Enable http_gzip_static module
+HTTP_IMAGE_FILTER_DESC= Enable http_image_filter module
+HTTP_IP2LOCATION_DESC= 3rd party ip2location-nginx module
+HTTP_IP2PROXY_DESC= 3rd party ip2proxy-nginx module
+HTTP_JSON_STATUS_DESC= 3rd party http_json_status module
+HTTP_MOGILEFS_DESC= 3rd party mogilefs module
+HTTP_MP4_DESC= Enable http_mp4 module
+HTTP_MP4_H264_DESC= 3rd party mp4/h264 module
+HTTP_NOTICE_DESC= 3rd party notice module
+HTTP_PERL_DESC= Enable http_perl module
+HTTP_PROXY_CONNECT_DESC= 3rd party https proxy connect module
+HTTP_PUSH_DESC= 3rd party push module
+HTTP_PUSH_STREAM_DESC= 3rd party push stream module
+HTTP_RANDOM_INDEX_DESC= Enable http_random_index module
+HTTP_REALIP_DESC= Enable http_realip module
+HTTP_REDIS_DESC= 3rd party http_redis module
+HTTP_SECURE_LINK_DESC= Enable http_secure_link module
+HTTP_SLICE_DESC= Enable http_slice module
+HTTP_SLICE_AHEAD_DESC= 3rd party http_slice_ahead module
+HTTP_SSL_DESC= Enable http_ssl module
+HTTP_STATUS_DESC= Enable http_stub_status module
+HTTP_SUBS_FILTER_DESC= 3rd party subs filter module
+HTTP_SUB_DESC= Enable http_sub module
+HTTP_TARANTOOL_DESC= 3rd party tarantool upstream module
+HTTP_UPLOAD_DESC= 3rd party upload module
+HTTP_UPLOAD_PROGRESS_DESC= 3rd party uploadprogress module
+HTTP_UPSTREAM_CHECK_DESC= 3rd party upstream check module
+HTTP_UPSTREAM_FAIR_DESC= 3rd party upstream fair module
+HTTP_UPSTREAM_STICKY_DESC= 3rd party upstream sticky module
+HTTP_VIDEO_DESC= 3rd party video module support
+HTTP_VIDEO_THUMBEXTRACTOR_DESC= 3rd party video_thumbextractor module
+HTTP_XSLT_DESC= Enable http_xslt module
+HTTP_ZIP_DESC= 3rd party http_zip module
+ICONV_DESC= 3rd party iconv module
+IPV6_DESC= Enable IPv6 support
+LET_DESC= 3rd party let module
+LINK_DESC= 3rd party link function module
+LUA_DESC= 3rd party lua module
+MAILGRP_DESC= Modules that require MAIL module
+MAIL_DESC= Enable IMAP4/POP3/SMTP proxy module
+MAIL_IMAP_DESC= Enable IMAP4 proxy module
+MAIL_POP3_DESC= Enable POP3 proxy module
+MAIL_SMTP_DESC= Enable SMTP proxy module
+MAIL_SSL_DESC= Enable mail_ssl module
+MEMC_DESC= 3rd party memc (memcached) module
+MODSECURITY3_DESC= 3rd party modsecurity3 module
+NAXSI_DESC= 3rd party naxsi module
+NJS_DESC= Enable javascript (NJS) module
+NJS_XML_DESC= Enable XML functionality in NJS module
+OPENTRACING_DESC= 3rd party opentracing module
+PASSENGER_DESC= 3rd party passenger module
+POSTGRES_DESC= 3rd party postgres module
+RDS_CSV_DESC= 3rd party rds_csv module
+RDS_JSON_DESC= 3rd party rds_json module
+REDIS2_DESC= 3rd party redis2 module
+RTMP_DESC= 3rd party rtmp module
+SET_MISC_DESC= 3rd party set_misc module
+SFLOW_DESC= 3rd party sflow module
+SHIBBOLETH_DESC= 3rd party shibboleth module
+SLOWFS_CACHE_DESC= 3rd party slowfs_cache module
+SRCACHE_DESC= 3rd party srcache module
+STREAMGRP_DESC= Modules that require STREAM module
+STREAM_DESC= Enable stream module
+STREAM_REALIP_DESC= Enable stream_realip module
+STREAM_SSL_DESC= Enable stream_ssl module (SSL req.)
+STREAM_SSL_PREREAD_DESC= Enable stream_ssl_preread module (SSL req.)
+STS_DESC= 3rd party sts module
+THREADS_DESC= Enable threads support
+THIRDPARTYGRP_DESC= Third-party modules
+VOD_DESC= 3rd party vod module
+VTS_DESC= 3rd party vts module
+WWW_DESC= Enable html sample files
+XSS_DESC= 3rd party xss module
+WEBSOCKIFY_DESC= 3rd party websockify module
diff --git a/www/freenginx/distinfo b/www/freenginx/distinfo
new file mode 100644
index 000000000000..4a4c3c991169
--- /dev/null
+++ b/www/freenginx/distinfo
@@ -0,0 +1,145 @@
+TIMESTAMP = 1708852054
+SHA256 (nginx-1.24.0.tar.gz) = 77a2541637b92a621e3ee76776c8b7b40cf6d707e69ba53a940283e30ff2f55d
+SIZE (nginx-1.24.0.tar.gz) = 1112471
+SHA256 (nginx_mogilefs_module-1.0.4.tar.gz) = 7ac230d30907f013dff8d435a118619ea6168aa3714dba62c6962d350c6295ae
+SIZE (nginx_mogilefs_module-1.0.4.tar.gz) = 11208
+SHA256 (nginx_mod_h264_streaming-2.2.7.tar.gz) = 6d974ba630cef59de1f60996c66b401264a345d25988a76037c2856cec756c19
+SIZE (nginx_mod_h264_streaming-2.2.7.tar.gz) = 44012
+SHA256 (ngx_http_redis-0.3.9.tar.gz) = 21f87540f0a44b23ffa5df16fb3d788bc90803b255ef14f9c26e3847a6f26f46
+SIZE (ngx_http_redis-0.3.9.tar.gz) = 13051
+SHA256 (naxsi-1.6-src-with-deps.tar.gz) = 1add95e5e473fca58b18356fd896221f98a122450d5b6e91b4352ef726f98a06
+SIZE (naxsi-1.6-src-with-deps.tar.gz) = 3352718
+SHA256 (passenger-6.0.17.tar.gz) = 385559ed1d78eb83165222d568721dcc4222bb57c1939811ecd2c4ef33937ba7
+SIZE (passenger-6.0.17.tar.gz) = 8422867
+SHA256 (msva-nginx_ajp_module-fcbb2cc_GH0.tar.gz) = 522e94c59f5783f281d868ede2adf325bf2f8ffb9e62cf8451d4b9ac0516916c
+SIZE (msva-nginx_ajp_module-fcbb2cc_GH0.tar.gz) = 110807
+SHA256 (openresty-array-var-nginx-module-v0.05_GH0.tar.gz) = c949d4be6f3442c8e2937046448dc8d8def25c0e0fa6f4e805144cea45eabe80
+SIZE (openresty-array-var-nginx-module-v0.05_GH0.tar.gz) = 11280
+SHA256 (anomalizer-ngx_aws_auth-21931b2_GH0.tar.gz) = d8a2422da96a638e9a911e4edb592954d9c0fe1576456fec9809ef4e2a0a863d
+SIZE (anomalizer-ngx_aws_auth-21931b2_GH0.tar.gz) = 15580
+SHA256 (google-ngx_brotli-9aec15e_GH0.tar.gz) = 0177b1158ff7092b9996346de28a0b296dc33addb2af4e8904794d19b4a9a808
+SIZE (google-ngx_brotli-9aec15e_GH0.tar.gz) = 16194
+SHA256 (nginx-modules-ngx_cache_purge-a84b0f3_GH0.tar.gz) = ddfd4fdd99075d906b7b75c49f56ec96b76df7951dfa54502e0f83890447031f
+SIZE (nginx-modules-ngx_cache_purge-a84b0f3_GH0.tar.gz) = 17162
+SHA256 (nginx-clojure-nginx-clojure-v0.6.0_GH0.tar.gz) = e8215cdebc3eb13f852c10e9bbbf315f2e1b75bb4dec015ca60ec29efcb86509
+SIZE (nginx-clojure-nginx-clojure-v0.6.0_GH0.tar.gz) = 786029
+SHA256 (AirisX-nginx_cookie_flag_module-c4ff449_GH0.tar.gz) = 4b8c1c1e1ed59ed85751f4bd7d68026ad5051103c8b983e05ad17eb0cdab138e
+SIZE (AirisX-nginx_cookie_flag_module-c4ff449_GH0.tar.gz) = 4713
+SHA256 (grahamedgecombe-nginx-ct-93e9884_GH0.tar.gz) = 72fdd125b9207cdda135f368095f85b943a78a4ff004d1cd217972e12b1571b2
+SIZE (grahamedgecombe-nginx-ct-93e9884_GH0.tar.gz) = 7224
+SHA256 (vision5-ngx_devel_kit-v0.3.2_GH0.tar.gz) = aa961eafb8317e0eb8da37eb6e2c9ff42267edd18b56947384e719b85188f58b
+SIZE (vision5-ngx_devel_kit-v0.3.2_GH0.tar.gz) = 66551
+SHA256 (openresty-drizzle-nginx-module-3504fc6_GH0.tar.gz) = 86076735597f14db28cffabc0ab1f233cd51aab7cf112c56e267783e7814fc65
+SIZE (openresty-drizzle-nginx-module-3504fc6_GH0.tar.gz) = 51596
+SHA256 (ZigzagAK-ngx_dynamic_upstream-960eef2_GH0.tar.gz) = 86e7c6ed6dba2d4c5f5b87ecb91f25ccdb7a08b8a88236e632114f830b9e354b
+SIZE (ZigzagAK-ngx_dynamic_upstream-960eef2_GH0.tar.gz) = 23003
+SHA256 (openresty-echo-nginx-module-5a402aa_GH0.tar.gz) = bb2a4b1a0e5ffa0203c1be854e663fc92cee0d7b5e0f7a38c0e163ae9124a38f
+SIZE (openresty-echo-nginx-module-5a402aa_GH0.tar.gz) = 53336
+SHA256 (openresty-encrypted-session-nginx-module-v0.09_GH0.tar.gz) = fe9b95acf9726aefd71bf0aca6c11bee007f1da67e64be9b21a7131f0ed75ba6
+SIZE (openresty-encrypted-session-nginx-module-v0.09_GH0.tar.gz) = 11847
+SHA256 (ogarrett-nginx-fips-check-module-6cb4270_GH0.tar.gz) = d52fbb0f2819cd91b710ad85e6c8b452fdca6a5d81b0694d6637adba3fc2382c
+SIZE (ogarrett-nginx-fips-check-module-6cb4270_GH0.tar.gz) = 6494
+SHA256 (calio-form-input-nginx-module-v0.12_GH0.tar.gz) = 5c1869d55897075adb3fdf840b21060dc54669a1f840a36d1539acc7e59dd106
+SIZE (calio-form-input-nginx-module-v0.12_GH0.tar.gz) = 11090
+SHA256 (nieoding-nginx-gridfs-059bdc3_GH0.tar.gz) = 9b059b5ae7b602d12d32d5ebe2700827ea625f22c0fb3b9956242e11de63845b
+SIZE (nieoding-nginx-gridfs-059bdc3_GH0.tar.gz) = 4674
+SHA256 (openresty-headers-more-nginx-module-33b646d_GH0.tar.gz) = 4e68ef77ce8bc3c248c04ddc112bb2230adf2de84c77430cedc8a4458ffb7369
+SIZE (openresty-headers-more-nginx-module-33b646d_GH0.tar.gz) = 28812
+SHA256 (dvershinin-nginx_accept_language_module-5683967_GH0.tar.gz) = a58feb576f2231498b8a3863d3c6fba45c7d48bc48735fa714e07a7bfbedb6e3
+SIZE (dvershinin-nginx_accept_language_module-5683967_GH0.tar.gz) = 3425
+SHA256 (atomx-nginx-http-auth-digest-274490c_GH0.tar.gz) = 0839c33c2f8d519f92daae274f62cf87eb68415d562c6500ee3e3721ce80557c
+SIZE (atomx-nginx-http-auth-digest-274490c_GH0.tar.gz) = 17815
+SHA256 (stnoonan-spnego-http-auth-nginx-module-3575542_GH0.tar.gz) = 6d710f97bef58b2d5dc54445c0e48103786425f6d4ab18cf30a2168904d0ba62
+SIZE (stnoonan-spnego-http-auth-nginx-module-3575542_GH0.tar.gz) = 24680
+SHA256 (kvspb-nginx-auth-ldap-83c059b_GH0.tar.gz) = e76e9e117ad51af578a68fa7a30c256178796bb271fa77f01c93281a92b09921
+SIZE (kvspb-nginx-auth-ldap-83c059b_GH0.tar.gz) = 18547
+SHA256 (sto-ngx_http_auth_pam_module-v1.5.3_GH0.tar.gz) = 882018fea8d6955ab3fe294aafa8ebb1fdff4eac313c29583fef02c6de76fae7
+SIZE (sto-ngx_http_auth_pam_module-v1.5.3_GH0.tar.gz) = 7084
+SHA256 (arut-nginx-dav-ext-module-v3.0.0_GH0.tar.gz) = d2499d94d82d4e4eac8425d799e52883131ae86a956524040ff2fd230ef9f859
+SIZE (arut-nginx-dav-ext-module-v3.0.0_GH0.tar.gz) = 14558
+SHA256 (openresty-nginx-eval-module-582bd25_GH0.tar.gz) = 014bedb2b334ba8e8e23b4c660590357f8055dbed7b9b017e4cc2937876a8822
+SIZE (openresty-nginx-eval-module-582bd25_GH0.tar.gz) = 14849
+SHA256 (aperezdc-ngx-fancyindex-v0.5.2_GH0.tar.gz) = c3dd84d8ba0b8daeace3041ef5987e3fb96e9c7c17df30c9ffe2fe3aa2a0ca31
+SIZE (aperezdc-ngx-fancyindex-v0.5.2_GH0.tar.gz) = 29052
+SHA256 (alibaba-nginx-http-footer-filter-1.2.2_GH0.tar.gz) = 3493b54460c59370f9f60c6e662862752f1920fc6e684f7a66bb2b3260692813
+SIZE (alibaba-nginx-http-footer-filter-1.2.2_GH0.tar.gz) = 3934
+SHA256 (leev-ngx_http_geoip2_module-3.4_GH0.tar.gz) = ad72fc23348d715a330994984531fab9b3606e160483236737f9a4a6957d9452
+SIZE (leev-ngx_http_geoip2_module-3.4_GH0.tar.gz) = 8877
+SHA256 (ip2location-ip2location-nginx-2df35fb_GH0.tar.gz) = 86d6d6d6b4437ecc621c5aac7bd5475dffd33afb70a51c5ea3c7f341ded46efb
+SIZE (ip2location-ip2location-nginx-2df35fb_GH0.tar.gz) = 5462
+SHA256 (ip2location-ip2proxy-nginx-02ce447_GH0.tar.gz) = edbafe23087f019364f9d1c1c615fdbc5116ec727c49bf442e3e4b39441fc4cc
+SIZE (ip2location-ip2proxy-nginx-02ce447_GH0.tar.gz) = 5177
+SHA256 (nginx-modules-ngx_http_json_status_module-1d2f303_GH0.tar.gz) = fdc34e0e712d28f4452ce3858ba05a38cc00703f14502095189c4a1063a36997
+SIZE (nginx-modules-ngx_http_json_status_module-1d2f303_GH0.tar.gz) = 6736
+SHA256 (kr-nginx-notice-3c95966_GH0.tar.gz) = e829fc94178cc8c91fef15a1fc44ee7ac162c13eddc0bba4c9427aaa23386885
+SIZE (kr-nginx-notice-3c95966_GH0.tar.gz) = 3343
+SHA256 (chobits-ngx_http_proxy_connect_module-75febef_GH0.tar.gz) = 6169361f31607af0ec8c78b356e62c2aeb128649161d688d7ea92f4d2c1c39f9
+SIZE (chobits-ngx_http_proxy_connect_module-75febef_GH0.tar.gz) = 32645
+SHA256 (slact-nchan-v1.3.6_GH0.tar.gz) = ba0b7cc6b710a20ce1ed2554caf56154035291aaf115e407d7a6bb699fde42df
+SIZE (slact-nchan-v1.3.6_GH0.tar.gz) = 761436
+SHA256 (wandenberg-nginx-push-stream-module-8c02220_GH0.tar.gz) = ab4fbe236e8bc500f0c5e13403d6a0e2e4e4ec17b81e0fcedaf669b4339626a6
+SIZE (wandenberg-nginx-push-stream-module-8c02220_GH0.tar.gz) = 196720
+SHA256 (yaoweibin-ngx_http_substitutions_filter_module-c6f825f_GH0.tar.gz) = 4ab034f2e056148469b440394e1664c46405712ef27bc4f3197e42bf7df8460e
+SIZE (yaoweibin-ngx_http_substitutions_filter_module-c6f825f_GH0.tar.gz) = 94062
+SHA256 (tarantool-nginx_upstream_module-aeb8696_GH0.tar.gz) = 514aa57155c73c2e3f7bdfe00c580183df343f2fa4b34e77f040cf6557caffae
+SIZE (tarantool-nginx_upstream_module-aeb8696_GH0.tar.gz) = 75708
+SHA256 (fdintino-nginx-upload-module-643b4c1_GH0.tar.gz) = a5bb48589b5c242683da33a9f1acc7847acc3ce4f2c4213ea524858aa789a6e9
+SIZE (fdintino-nginx-upload-module-643b4c1_GH0.tar.gz) = 42571
+SHA256 (masterzen-nginx-upload-progress-module-68b3ab3_GH0.tar.gz) = 35b506e57e19e780e01ecc7c3c31a64473c35e4a022f5a3f98092a60cd1c1602
+SIZE (masterzen-nginx-upload-progress-module-68b3ab3_GH0.tar.gz) = 17322
+SHA256 (yaoweibin-nginx_upstream_check_module-9aecf15_GH0.tar.gz) = 4404c64e845e19feeb07a37976347987892a8e8680a961f793ff0d3ef96c07f4
+SIZE (yaoweibin-nginx_upstream_check_module-9aecf15_GH0.tar.gz) = 130039
+SHA256 (jaygooby-nginx-upstream-fair-10ecdcf_GH0.tar.gz) = 93f71b7cf0db9c6dbf97e3ee11cf8efbc149946c0949d7abd19c74c7620eea50
+SIZE (jaygooby-nginx-upstream-fair-10ecdcf_GH0.tar.gz) = 10433
+SHA256 (dvershinin-nginx-sticky-module-ng-2753211_GH0.tar.gz) = e4a533dfa214ea28122301aeebbb1a38e1d1972edb7ee9bc72271c14f2693005
+SIZE (dvershinin-nginx-sticky-module-ng-2753211_GH0.tar.gz) = 120676
+SHA256 (Novetta-nginx-video-thumbextractor-module-28861f2_GH0.tar.gz) = 04656da527d9e64cbdf1bf475a93193fa60324ffea160d05d4cc53c864943bc1
+SIZE (Novetta-nginx-video-thumbextractor-module-28861f2_GH0.tar.gz) = 34447
+SHA256 (evanmiller-mod_zip-39dc908_GH0.tar.gz) = bc5c3d725268abbe1c5c38de5b18a4ad9dbe5821c4afeaccabd3eec38b272be4
+SIZE (evanmiller-mod_zip-39dc908_GH0.tar.gz) = 30275
+SHA256 (calio-iconv-nginx-module-v0.14_GH0.tar.gz) = b8b9f355c05c0790226512f6732348a2404d48531688a1fc04ce6768163bf462
+SIZE (calio-iconv-nginx-module-v0.14_GH0.tar.gz) = 13133
+SHA256 (baysao-nginx-let-module-c1f23aa_GH0.tar.gz) = 7393809d5d8877812da1bd5b5fbd1d8b00bc85e71f2f387c344f007773e49050
+SIZE (baysao-nginx-let-module-c1f23aa_GH0.tar.gz) = 20617
+SHA256 (Taymindis-nginx-link-function-3.2.4_GH0.tar.gz) = 20c3679199ba7efe1598f03b2fa0b13591226363c8dd7930d7f02702cd5abada
+SIZE (Taymindis-nginx-link-function-3.2.4_GH0.tar.gz) = 139656
+SHA256 (openresty-lua-nginx-module-v0.10.26_GH0.tar.gz) = a75983287a2bdc5e964ace56a51b215dc2ec996639d4916cd393d6ebba94b565
+SIZE (openresty-lua-nginx-module-v0.10.26_GH0.tar.gz) = 745785
+SHA256 (openresty-memc-nginx-module-v0.19_GH0.tar.gz) = 8c2bdbe875e4f5225d0778bfb09a2668f9281d7de6218c7b462a7ba2cee06fe8
+SIZE (openresty-memc-nginx-module-v0.19_GH0.tar.gz) = 34654
+SHA256 (SpiderLabs-ModSecurity-nginx-v1.0.3_GH0.tar.gz) = 32a42256616cc674dca24c8654397390adff15b888b77eb74e0687f023c8751b
+SIZE (SpiderLabs-ModSecurity-nginx-v1.0.3_GH0.tar.gz) = 34063
+SHA256 (nginx-njs-0.8.0_GH0.tar.gz) = b98033fff6aadcbb8e108b96e80c0d94c6e2103bcbe75846b5ae0b560696084b
+SIZE (nginx-njs-0.8.0_GH0.tar.gz) = 715391
+SHA256 (opentracing-contrib-nginx-opentracing-v0.24.0_GH0.tar.gz) = 5328c5f37e0615b5252aed51b9cd40f3d14989d995ad54134076aeda4ab9b280
+SIZE (opentracing-contrib-nginx-opentracing-v0.24.0_GH0.tar.gz) = 679417
+SHA256 (konstruxi-ngx_postgres-8aa7359_GH0.tar.gz) = c69ad4495de7c7883ebc23e1e6c4cc83a4ac6a7fddd4d5c12e49d33b65f7c50b
+SIZE (konstruxi-ngx_postgres-8aa7359_GH0.tar.gz) = 48544
+SHA256 (openresty-rds-csv-nginx-module-v0.09_GH0.tar.gz) = 896be99c0cad50218417800a159e43ec088d6b58c099472ed3b3d7f179d6c0ea
+SIZE (openresty-rds-csv-nginx-module-v0.09_GH0.tar.gz) = 20531
+SHA256 (openresty-rds-json-nginx-module-v0.15_GH0.tar.gz) = eaf18f60e981ea2442a7902689a26eba6cf6f36ebee712feeb1f4429eb654bdc
+SIZE (openresty-rds-json-nginx-module-v0.15_GH0.tar.gz) = 34744
+SHA256 (openresty-redis2-nginx-module-v0.15_GH0.tar.gz) = d255571bcfb9939b78099df39cb4d42f174d789aec8c8e5e47b93942b0299438
+SIZE (openresty-redis2-nginx-module-v0.15_GH0.tar.gz) = 25471
+SHA256 (arut-nginx-rtmp-module-v1.2.2_GH0.tar.gz) = 07f19b7bffec5e357bb8820c63e5281debd45f5a2e6d46b1636d9202c3e09d78
+SIZE (arut-nginx-rtmp-module-v1.2.2_GH0.tar.gz) = 519934
+SHA256 (openresty-set-misc-nginx-module-3937e7b_GH0.tar.gz) = cb3a4675ab6b8741e5847cf5bc41ee3f6ec5cbceec53188f9ae96e48feea17c5
+SIZE (openresty-set-misc-nginx-module-3937e7b_GH0.tar.gz) = 29335
+SHA256 (sflow-nginx-sflow-module-543c72a_GH0.tar.gz) = 95efdb1f6cfd6c32c577707f693eb6795c6f21ae062842bf84fe762d8b842955
+SIZE (sflow-nginx-sflow-module-543c72a_GH0.tar.gz) = 29504
+SHA256 (nginx-shib-nginx-http-shibboleth-be12df5_GH0.tar.gz) = aff9830b5de78dd9ce32cd2c55c5cf9173c99fe1a1d2190407c96668e7517bab
+SIZE (nginx-shib-nginx-http-shibboleth-be12df5_GH0.tar.gz) = 23872
+SHA256 (baysao-ngx_slowfs_cache-d011a18_GH0.tar.gz) = 6ae8abb01a2aff788e75ec68621cb0159148a6f73730a84b30b0bdbc6cdc1758
+SIZE (baysao-ngx_slowfs_cache-d011a18_GH0.tar.gz) = 11186
+SHA256 (openresty-srcache-nginx-module-be22ac0_GH0.tar.gz) = 5753d1ffe87b5d6f5b7a0696667bb5ff1388738136fdee26ba55bc33f5796061
+SIZE (openresty-srcache-nginx-module-be22ac0_GH0.tar.gz) = 51029
+SHA256 (vozlt-nginx-module-sts-3c10d42_GH0.tar.gz) = 748b67ceb82b3b843ae915bf7863fd08b7c2427c045e5ec540242d050f7b30d0
+SIZE (vozlt-nginx-module-sts-3c10d42_GH0.tar.gz) = 352431
+SHA256 (kaltura-nginx-vod-module-1.31_GH0.tar.gz) = ace04201cf2d2b1a3e5e732a22b92225b8ce61a494df9cc7f79d97efface8952
+SIZE (kaltura-nginx-vod-module-1.31_GH0.tar.gz) = 470904
+SHA256 (vozlt-nginx-module-vts-bf64dbf_GH0.tar.gz) = d2782c75e39cb2ecf68453922b43ab2295adb6a35fa6a0f9c14173f70d22d7b1
+SIZE (vozlt-nginx-module-vts-bf64dbf_GH0.tar.gz) = 180394
+SHA256 (tg123-websockify-nginx-module-c11bc9a_GH0.tar.gz) = aca454bffcee2476dc92682ebfb8c0378a271fda178be7e945d648419d220758
+SIZE (tg123-websockify-nginx-module-c11bc9a_GH0.tar.gz) = 14646
+SHA256 (openresty-xss-nginx-module-v0.06_GH0.tar.gz) = 0b12bbc53a41f3e3d6df419c173b8c87434be3e6cd255a8193aa91345a2de6cf
+SIZE (openresty-xss-nginx-module-v0.06_GH0.tar.gz) = 12448
diff --git a/www/freenginx/files/extra-patch-calio-iconv-nginx-module-config b/www/freenginx/files/extra-patch-calio-iconv-nginx-module-config
new file mode 100644
index 000000000000..fe9b12dc7747
--- /dev/null
+++ b/www/freenginx/files/extra-patch-calio-iconv-nginx-module-config
@@ -0,0 +1,19 @@
+--- ../iconv-nginx-module-0.14/config.orig 2013-04-16 17:57:17.000000000 -0700
++++ ../iconv-nginx-module-0.14/config 2013-05-01 17:16:28.134624745 -0700
+@@ -39,12 +39,12 @@
+ fi
+
+ if [ $ngx_found = no ]; then
+- ngx_feature="libiconv in /usr/local/"
+- ngx_feature_path="/usr/local/include"
++ ngx_feature="libiconv in %%PREFIX%%/"
++ ngx_feature_path="%%PREFIX%%/include"
+ if [ $NGX_RPATH = YES ]; then
+- ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -liconv"
++ ngx_feature_libs="-R%%PREFIX%%/lib -L%%PREFIX%%/lib -liconv"
+ else
+- ngx_feature_libs="-L/usr/local/lib -liconv"
++ ngx_feature_libs="-L%%PREFIX%%/lib -liconv"
+ fi
+ . auto/feature
+ fi
diff --git a/www/freenginx/files/extra-patch-httpv3 b/www/freenginx/files/extra-patch-httpv3
new file mode 100644
index 000000000000..c49f591c25d5
--- /dev/null
+++ b/www/freenginx/files/extra-patch-httpv3
@@ -0,0 +1,26867 @@
+diff -r ac779115ed6e README
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/README Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,386 @@
++Experimental QUIC support for nginx
++-----------------------------------
++
++1. Introduction
++2. Building from sources
++3. Configuration
++4. Directives
++5. Clients
++6. Troubleshooting
++7. Contributing
++8. Links
++
++1. Introduction
++
++ This is an experimental QUIC [1] / HTTP/3 [2] support for nginx.
++
++ The code is developed in a separate "quic" branch available
++ at https://hg.nginx.org/nginx-quic. Currently it is based
++ on nginx mainline 1.23.x. We merge new nginx releases into
++ this branch regularly.
++
++ The project code base is under the same BSD license as nginx.
++
++ The code is currently at a beta level of quality, however
++ there are several production deployments with it.
++
++ NGINX Development Team is working on improving HTTP/3 support to
++ integrate it into the main NGINX codebase. Thus, expect further
++ updates of this code, including features, changes in behaviour,
++ bug fixes, and refactoring. NGINX Development team will be
++ grateful for any feedback and code submissions.
++
++ Please contact NGINX Development Team via nginx-devel mailing list [3].
++
++ What works now:
++
++ IETF QUIC version 1 is supported. Internet drafts are no longer supported.
++
++ nginx should be able to respond to HTTP/3 requests over QUIC and
++ it should be possible to upload and download big files without errors.
++
++ + The handshake completes successfully
++ + One endpoint can update keys and its peer responds correctly
++ + 0-RTT data is being received and acted on
++ + Connection is established using TLS Resume Ticket
++ + A handshake that includes a Retry packet completes successfully
++ + Stream data is being exchanged and ACK'ed
++ + An H3 transaction succeeded
++ + One or both endpoints insert entries into dynamic table and
++ subsequently reference them from header blocks
++ + Version Negotiation packet is sent to client with unknown version
++ + Lost packets are detected and retransmitted properly
++ + Clients may migrate to new address
++
++2. Building from sources
++
++ The build is configured using the configure command.
++ Refer to http://nginx.org/en/docs/configure.html for details.
++
++ When configuring nginx, it's possible to enable QUIC and HTTP/3
++ using the following new configuration options:
++
++ --with-http_v3_module - enable QUIC and HTTP/3
++ --with-stream_quic_module - enable QUIC in Stream
++
++ A library that provides QUIC support is recommended to build nginx, there
++ are several of those available on the market:
++ + BoringSSL [4]
++ + LibreSSL [5]
++ + QuicTLS [6]
++
++ Alternatively, nginx can be configured with OpenSSL compatibility
++ layer, which emulates BoringSSL QUIC API for OpenSSL. This mode is
++ enabled by default if native QUIC support is not detected.
++ 0-RTT is not supported in OpenSSL compatibility mode.
++
++ Clone the NGINX QUIC repository
++
++ $ hg clone -b quic https://hg.nginx.org/nginx-quic
++ $ cd nginx-quic
++
++ Use the following command to configure nginx with BoringSSL [4]
++
++ $ ./auto/configure --with-debug --with-http_v3_module \
++ --with-cc-opt="-I../boringssl/include" \
++ --with-ld-opt="-L../boringssl/build/ssl \
++ -L../boringssl/build/crypto"
++ $ make
++
++ Alternatively, nginx can be configured with QuicTLS [6]
++
++ $ ./auto/configure --with-debug --with-http_v3_module \
++ --with-cc-opt="-I../quictls/build/include" \
++ --with-ld-opt="-L../quictls/build/lib"
++
++ Alternatively, nginx can be configured with a modern version
++ of LibreSSL [7]
++
++ $ ./auto/configure --with-debug --with-http_v3_module \
++ --with-cc-opt="-I../libressl/build/include" \
++ --with-ld-opt="-L../libressl/build/lib"
++
++3. Configuration
++
++ The HTTP "listen" directive got a new option "quic" which enables
++ QUIC as client transport protocol instead of TCP.
++
++ The Stream "listen" directive got a new option "quic" which enables
++ QUIC as client transport protocol instead of TCP or plain UDP.
++
++ Along with "quic", it's also possible to specify "reuseport"
++ option [8] to make it work properly with multiple workers.
++
++ To enable address validation:
++
++ quic_retry on;
++
++ To enable 0-RTT:
++
++ ssl_early_data on;
++
++ To enable GSO (Generic Segmentation Offloading):
++
++ quic_gso on;
++
++ To limit maximum UDP payload size on receive path:
++
++ quic_mtu <size>;
++
++ To set host key for various tokens:
++
++ quic_host_key <filename>;
++
++ QUIC requires TLSv1.3 protocol, which is enabled by the default
++ by "ssl_protocols" directive.
++
++ By default, GSO Linux-specific optimization [10] is disabled.
++ Enable it in case a corresponding network interface is configured to
++ support GSO.
++
++ A number of directives were added that configure HTTP/3:
++
++ http3
++ http3_hq
++ http3_stream_buffer_size
++ http3_max_concurrent_pushes
++ http3_max_concurrent_streams
++ http3_push
++ http3_push_preload
++
++ In http, an additional variable is available: $http3.
++ The value of $http3 is "h3" for HTTP/3 connections,
++ "hq" for hq connections, or an empty string otherwise.
++
++ In stream, an additional variable is available: $quic.
++ The value of $quic is "quic" if QUIC connection is used,
++ or an empty string otherwise.
++
++Example configuration:
++
++ http {
++ log_format quic '$remote_addr - $remote_user [$time_local] '
++ '"$request" $status $body_bytes_sent '
++ '"$http_referer" "$http_user_agent" "$http3"';
++
++ access_log logs/access.log quic;
++
++ server {
++ # for better compatibility it's recommended
++ # to use the same port for quic and https
++ listen 8443 quic reuseport;
++ listen 8443 ssl;
++
++ ssl_certificate certs/example.com.crt;
++ ssl_certificate_key certs/example.com.key;
++
++ location / {
++ # required for browsers to direct them into quic port
++ add_header Alt-Svc 'h3=":8443"; ma=86400';
++ }
++ }
++ }
++
++4. Directives
++
++ Syntax: quic_bpf on | off;
++ Default: quic_bpf off;
++ Context: main
++
++ Enables routing of QUIC packets using eBPF.
++ When enabled, this allows to support QUIC connection migration.
++ The directive is only supported on Linux 5.7+.
++
++
++ Syntax: quic_retry on | off;
++ Default: quic_retry off;
++ Context: http | stream, server
++
++ Enables the QUIC Address Validation feature. This includes:
++ - sending a new token in a Retry packet or a NEW_TOKEN frame
++ - validating a token received in the Initial packet
++
++
++ Syntax: quic_gso on | off;
++ Default: quic_gso off;
++ Context: http | stream, server
++
++ Enables sending in optimized batch mode using segmentation offloading.
++ Optimized sending is only supported on Linux featuring UDP_SEGMENT.
++
++
++ Syntax: quic_mtu size;
++ Default: quic_mtu 65527;
++ Context: http | stream, server
++
++ Sets the QUIC max_udp_payload_size transport parameter value.
++ This is the maximum UDP payload that we are willing to receive.
++
++
++ Syntax: quic_host_key file;
++ Default: -
++ Context: http | stream, server
++
++ Specifies a file with the secret key used to encrypt stateless reset and
++ address validation tokens. By default, a randomly generated key is used.
++
++
++ Syntax: quic_active_connection_id_limit number;
++ Default: quic_active_connection_id_limit 2;
++ Context: http | stream, server
++
++ Sets the QUIC active_connection_id_limit transport parameter value.
++ This is the maximum number of connection IDs we are willing to store.
++
++
++ Syntax: quic_timeout time;
++ Default: quic_timeout 60s;
++ Context: stream, server
++
++ Defines a timeout used to negotiate the QUIC idle timeout.
++ In the http module, it is taken from the keepalive_timeout directive.
++
++
++ Syntax: quic_stream_buffer_size size;
++ Default: quic_stream_buffer_size 64k;
++ Context: stream, server
++
++ Syntax: http3_stream_buffer_size size;
++ Default: http3_stream_buffer_size 64k;
++ Context: http, server
++
++ Sets buffer size for reading and writing of the QUIC STREAM payload.
++ The buffer size is used to calculate initial flow control limits
++ in the following QUIC transport parameters:
++ - initial_max_data
++ - initial_max_stream_data_bidi_local
++ - initial_max_stream_data_bidi_remote
++ - initial_max_stream_data_uni
++
++
++ Syntax: http3_max_concurrent_pushes number;
++ Default: http3_max_concurrent_pushes 10;
++ Context: http, server
++
++ Limits the maximum number of concurrent push requests in a connection.
++
++
++ Syntax: http3_max_concurrent_streams number;
++ Default: http3_max_concurrent_streams 128;
++ Context: http, server
++
++ Sets the maximum number of concurrent HTTP/3 streams in a connection.
++
++
++ Syntax: http3_push uri | off;
++ Default: http3_push off;
++ Context: http, server, location
++
++ Pre-emptively sends (pushes) a request to the specified uri along with
++ the response to the original request. Only relative URIs with absolute
++ path will be processed, for example:
++
++ http3_push /static/css/main.css;
++
++ The uri value can contain variables.
++
++ Several http3_push directives can be specified on the same configuration
++ level. The off parameter cancels the effect of the http3_push directives
++ inherited from the previous configuration level.
++
++
++ Syntax: http3_push_preload on | off;
++ Default: http3_push_preload off;
++ Context: http, server, location
++
++ Enables automatic conversion of preload links specified in the “Link”
++ response header fields into push requests.
++
++
++ Syntax: http3 on | off;
++ Default: http3 on;
++ Context: http, server
++
++ Enables HTTP/3 protocol negotiation.
++
++
++ Syntax: http3_hq on | off;
++ Default: http3_hq off;
++ Context: http, server
++
++ Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests.
++
++5. Clients
++
++ * Browsers
++
++ Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1)
++
++ Beware of strange issues: sometimes browser may decide to ignore QUIC
++ Cache clearing/restart might help. Always check access.log and
++ error.log to make sure the browser is using HTTP/3 and not TCP https.
++
++ * Console clients
++
++ Known to work: ngtcp2, firefox's neqo and chromium's console clients:
++
++ $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html
++
++ $ ./neqo-client https://127.0.0.1:8443/
++
++ $ chromium-build/out/my_build/quic_client http://example.com:8443
++
++
++ In case everyhing is right, the access log should show something like:
++
++ 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-"
++ "nghttp3/ngtcp2 client" "quic"
++
++
++6. Troubleshooting
++
++ Here are some tips that may help to identify problems:
++
++ + Ensure nginx is built with proper SSL library that supports QUIC
++
++ + Ensure nginx is using the proper SSL library in runtime
++ (`nginx -V` shows what it's using)
++
++ + Ensure a client is actually sending requests over QUIC
++ (see "Clients" section about browsers and cache)
++
++ We recommend to start with simple console client like ngtcp2
++ to ensure the server is configured properly before trying
++ with real browsers that may be very picky with certificates,
++ for example.
++
++ + Build nginx with debug support [9] and check the debug log.
++ It should contain all details about connection and why it
++ failed. All related messages contain "quic " prefix and can
++ be easily filtered out.
++
++ + For a deeper investigation, please enable additional debugging
++ in src/event/quic/ngx_event_quic_connection.h:
++
++ #define NGX_QUIC_DEBUG_PACKETS
++ #define NGX_QUIC_DEBUG_FRAMES
++ #define NGX_QUIC_DEBUG_ALLOC
++ #define NGX_QUIC_DEBUG_CRYPTO
++
++7. Contributing
++
++ Please refer to
++ http://nginx.org/en/docs/contributing_changes.html
++
++8. Links
++
++ [1] https://datatracker.ietf.org/doc/html/rfc9000
++ [2] https://datatracker.ietf.org/doc/html/rfc9114
++ [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel
++ [4] https://boringssl.googlesource.com/boringssl/
++ [5] https://www.libressl.org/
++ [6] https://github.com/quictls/openssl
++ [7] https://github.com/libressl-portable/portable/releases/tag/v3.6.0
++ [8] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen
++ [9] https://nginx.org/en/docs/debugging_log.html
++ [10] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf
+diff -r ac779115ed6e auto/lib/openssl/conf
+--- a/auto/lib/openssl/conf Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/lib/openssl/conf Thu May 11 11:48:37 2023 -0400
+@@ -5,12 +5,17 @@
+
+ if [ $OPENSSL != NONE ]; then
+
++ have=NGX_OPENSSL . auto/have
++ have=NGX_SSL . auto/have
++
++ if [ $USE_OPENSSL_QUIC = YES ]; then
++ have=NGX_QUIC . auto/have
++ have=NGX_QUIC_OPENSSL_COMPAT . auto/have
++ fi
++
+ case "$CC" in
+
+ cl | bcc32)
+- have=NGX_OPENSSL . auto/have
+- have=NGX_SSL . auto/have
+-
+ CFLAGS="$CFLAGS -DNO_SYS_TYPES_H"
+
+ CORE_INCS="$CORE_INCS $OPENSSL/openssl/include"
+@@ -33,9 +38,6 @@ if [ $OPENSSL != NONE ]; then
+ ;;
+
+ *)
+- have=NGX_OPENSSL . auto/have
+- have=NGX_SSL . auto/have
+-
+ CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include"
+ CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h"
+ CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a"
+@@ -123,6 +125,35 @@ else
+ CORE_INCS="$CORE_INCS $ngx_feature_path"
+ CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+ OPENSSL=YES
++
++ if [ $USE_OPENSSL_QUIC = YES ]; then
++
++ ngx_feature="OpenSSL QUIC support"
++ ngx_feature_name="NGX_QUIC"
++ ngx_feature_test="SSL_set_quic_method(NULL, NULL)"
++ . auto/feature
++
++ if [ $ngx_found = no ]; then
++ have=NGX_QUIC_OPENSSL_COMPAT . auto/have
++
++ ngx_feature="OpenSSL QUIC compatibility"
++ ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0,
++ NULL, NULL, NULL, NULL, NULL)"
++ . auto/feature
++ fi
++
++ if [ $ngx_found = no ]; then
++cat << END
++
++$0: error: certain modules require OpenSSL QUIC support.
++You can either do not enable the modules, or install the OpenSSL library with
++QUIC support into the system, or build the OpenSSL library with QUIC support
++statically from the source with nginx by using --with-openssl=<path> option.
++
++END
++ exit 1
++ fi
++ fi
+ fi
+ fi
+
+diff -r ac779115ed6e auto/make
+--- a/auto/make Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/make Thu May 11 11:48:37 2023 -0400
+@@ -6,9 +6,10 @@
+ echo "creating $NGX_MAKEFILE"
+
+ mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \
++ $NGX_OBJS/src/event/quic \
+ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \
+- $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \
+- $NGX_OBJS/src/http/modules/perl \
++ $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \
++ $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \
+ $NGX_OBJS/src/mail \
+ $NGX_OBJS/src/stream \
+ $NGX_OBJS/src/misc
+diff -r ac779115ed6e auto/modules
+--- a/auto/modules Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/modules Thu May 11 11:48:37 2023 -0400
+@@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then
+ fi
+
+
+- if [ $HTTP_V2 = YES ]; then
++ if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then
+ HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS"
+ fi
+
+@@ -124,6 +124,7 @@ if [ $HTTP = YES ]; then
+ # ngx_http_header_filter
+ # ngx_http_chunked_filter
+ # ngx_http_v2_filter
++ # ngx_http_v3_filter
+ # ngx_http_range_header_filter
+ # ngx_http_gzip_filter
+ # ngx_http_postpone_filter
+@@ -156,6 +157,7 @@ if [ $HTTP = YES ]; then
+ ngx_http_header_filter_module \
+ ngx_http_chunked_filter_module \
+ ngx_http_v2_filter_module \
++ ngx_http_v3_filter_module \
+ ngx_http_range_header_filter_module \
+ ngx_http_gzip_filter_module \
+ ngx_http_postpone_filter_module \
+@@ -217,6 +219,17 @@ if [ $HTTP = YES ]; then
+ . auto/module
+ fi
+
++ if [ $HTTP_V3 = YES ]; then
++ ngx_module_name=ngx_http_v3_filter_module
++ ngx_module_incs=
++ ngx_module_deps=
++ ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c
++ ngx_module_libs=
++ ngx_module_link=$HTTP_V3
++
++ . auto/module
++ fi
++
+ if :; then
+ ngx_module_name=ngx_http_range_header_filter_module
+ ngx_module_incs=
+@@ -426,6 +439,33 @@ if [ $HTTP = YES ]; then
+ . auto/module
+ fi
+
++ if [ $HTTP_V3 = YES ]; then
++ USE_OPENSSL_QUIC=YES
++ HTTP_SSL=YES
++
++ have=NGX_HTTP_V3 . auto/have
++ have=NGX_HTTP_HEADERS . auto/have
++
++ ngx_module_name=ngx_http_v3_module
++ ngx_module_incs=src/http/v3
++ ngx_module_deps="src/http/v3/ngx_http_v3.h \
++ src/http/v3/ngx_http_v3_encode.h \
++ src/http/v3/ngx_http_v3_parse.h \
++ src/http/v3/ngx_http_v3_table.h \
++ src/http/v3/ngx_http_v3_uni.h"
++ ngx_module_srcs="src/http/v3/ngx_http_v3.c \
++ src/http/v3/ngx_http_v3_encode.c \
++ src/http/v3/ngx_http_v3_parse.c \
++ src/http/v3/ngx_http_v3_table.c \
++ src/http/v3/ngx_http_v3_uni.c \
++ src/http/v3/ngx_http_v3_request.c \
++ src/http/v3/ngx_http_v3_module.c"
++ ngx_module_libs=
++ ngx_module_link=$HTTP_V3
++
++ . auto/module
++ fi
++
+ if :; then
+ ngx_module_name=ngx_http_static_module
+ ngx_module_incs=
+@@ -1035,6 +1075,20 @@ if [ $STREAM != NO ]; then
+
+ ngx_module_incs=
+
++ if [ $STREAM_QUIC = YES ]; then
++ USE_OPENSSL_QUIC=YES
++ have=NGX_STREAM_QUIC . auto/have
++ STREAM_SSL=YES
++
++ ngx_module_name=ngx_stream_quic_module
++ ngx_module_deps=src/stream/ngx_stream_quic_module.h
++ ngx_module_srcs=src/stream/ngx_stream_quic_module.c
++ ngx_module_libs=
++ ngx_module_link=$STREAM_QUIC
++
++ . auto/module
++ fi
++
+ if [ $STREAM_SSL = YES ]; then
+ USE_OPENSSL=YES
+ have=NGX_STREAM_SSL . auto/have
+@@ -1272,6 +1326,63 @@ if [ $USE_OPENSSL = YES ]; then
+ fi
+
+
++if [ $USE_OPENSSL_QUIC = YES ]; then
++ ngx_module_type=CORE
++ ngx_module_name=ngx_quic_module
++ ngx_module_incs=
++ ngx_module_deps="src/event/quic/ngx_event_quic.h \
++ src/event/quic/ngx_event_quic_transport.h \
++ src/event/quic/ngx_event_quic_protection.h \
++ src/event/quic/ngx_event_quic_connection.h \
++ src/event/quic/ngx_event_quic_frames.h \
++ src/event/quic/ngx_event_quic_connid.h \
++ src/event/quic/ngx_event_quic_migration.h \
++ src/event/quic/ngx_event_quic_streams.h \
++ src/event/quic/ngx_event_quic_ssl.h \
++ src/event/quic/ngx_event_quic_tokens.h \
++ src/event/quic/ngx_event_quic_ack.h \
++ src/event/quic/ngx_event_quic_output.h \
++ src/event/quic/ngx_event_quic_socket.h \
++ src/event/quic/ngx_event_quic_openssl_compat.h"
++ ngx_module_srcs="src/event/quic/ngx_event_quic.c \
++ src/event/quic/ngx_event_quic_udp.c \
++ src/event/quic/ngx_event_quic_transport.c \
++ src/event/quic/ngx_event_quic_protection.c \
++ src/event/quic/ngx_event_quic_frames.c \
++ src/event/quic/ngx_event_quic_connid.c \
++ src/event/quic/ngx_event_quic_migration.c \
++ src/event/quic/ngx_event_quic_streams.c \
++ src/event/quic/ngx_event_quic_ssl.c \
++ src/event/quic/ngx_event_quic_tokens.c \
++ src/event/quic/ngx_event_quic_ack.c \
++ src/event/quic/ngx_event_quic_output.c \
++ src/event/quic/ngx_event_quic_socket.c \
++ src/event/quic/ngx_event_quic_openssl_compat.c"
++
++ ngx_module_libs=
++ ngx_module_link=YES
++ ngx_module_order=
++
++ . auto/module
++
++ if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then
++ ngx_module_type=CORE
++ ngx_module_name=ngx_quic_bpf_module
++ ngx_module_incs=
++ ngx_module_deps=
++ ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \
++ src/event/quic/ngx_event_quic_bpf_code.c"
++ ngx_module_libs=
++ ngx_module_link=YES
++ ngx_module_order=
++
++ . auto/module
++
++ have=NGX_QUIC_BPF . auto/have
++ fi
++fi
++
++
+ if [ $USE_PCRE = YES ]; then
+ ngx_module_type=CORE
+ ngx_module_name=ngx_regex_module
+diff -r ac779115ed6e auto/options
+--- a/auto/options Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/options Thu May 11 11:48:37 2023 -0400
+@@ -45,6 +45,8 @@ USE_THREADS=NO
+
+ NGX_FILE_AIO=NO
+
++QUIC_BPF=NO
++
+ HTTP=YES
+
+ NGX_HTTP_LOG_PATH=
+@@ -59,6 +61,7 @@ HTTP_CHARSET=YES
+ HTTP_GZIP=YES
+ HTTP_SSL=NO
+ HTTP_V2=NO
++HTTP_V3=NO
+ HTTP_SSI=YES
+ HTTP_REALIP=NO
+ HTTP_XSLT=NO
+@@ -116,6 +119,7 @@ MAIL_SMTP=YES
+
+ STREAM=NO
+ STREAM_SSL=NO
++STREAM_QUIC=NO
+ STREAM_REALIP=NO
+ STREAM_LIMIT_CONN=YES
+ STREAM_ACCESS=YES
+@@ -149,6 +153,7 @@ PCRE_JIT=NO
+ PCRE2=YES
+
+ USE_OPENSSL=NO
++USE_OPENSSL_QUIC=NO
+ OPENSSL=NONE
+
+ USE_ZLIB=NO
+@@ -166,6 +171,8 @@ USE_GEOIP=NO
+ NGX_GOOGLE_PERFTOOLS=NO
+ NGX_CPP_TEST=NO
+
++SO_COOKIE_FOUND=NO
++
+ NGX_LIBATOMIC=NO
+
+ NGX_CPU_CACHE_LINE=
+@@ -211,6 +218,8 @@ do
+
+ --with-file-aio) NGX_FILE_AIO=YES ;;
+
++ --without-quic_bpf_module) QUIC_BPF=NONE ;;
++
+ --with-ipv6)
+ NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+ $0: warning: the \"--with-ipv6\" option is deprecated"
+@@ -228,6 +237,7 @@ do
+
+ --with-http_ssl_module) HTTP_SSL=YES ;;
+ --with-http_v2_module) HTTP_V2=YES ;;
++ --with-http_v3_module) HTTP_V3=YES ;;
+ --with-http_realip_module) HTTP_REALIP=YES ;;
+ --with-http_addition_module) HTTP_ADDITION=YES ;;
+ --with-http_xslt_module) HTTP_XSLT=YES ;;
+@@ -314,6 +324,7 @@ use the \"--with-mail_ssl_module\" optio
+ --with-stream) STREAM=YES ;;
+ --with-stream=dynamic) STREAM=DYNAMIC ;;
+ --with-stream_ssl_module) STREAM_SSL=YES ;;
++ --with-stream_quic_module) STREAM_QUIC=YES ;;
+ --with-stream_realip_module) STREAM_REALIP=YES ;;
+ --with-stream_geoip_module) STREAM_GEOIP=YES ;;
+ --with-stream_geoip_module=dynamic)
+@@ -443,8 +454,11 @@ cat << END
+
+ --with-file-aio enable file AIO support
+
++ --without-quic_bpf_module disable ngx_quic_bpf_module
++
+ --with-http_ssl_module enable ngx_http_ssl_module
+ --with-http_v2_module enable ngx_http_v2_module
++ --with-http_v3_module enable ngx_http_v3_module
+ --with-http_realip_module enable ngx_http_realip_module
+ --with-http_addition_module enable ngx_http_addition_module
+ --with-http_xslt_module enable ngx_http_xslt_module
+@@ -533,6 +547,7 @@ cat << END
+ --with-stream enable TCP/UDP proxy module
+ --with-stream=dynamic enable dynamic TCP/UDP proxy module
+ --with-stream_ssl_module enable ngx_stream_ssl_module
++ --with-stream_quic_module enable ngx_stream_quic_module
+ --with-stream_realip_module enable ngx_stream_realip_module
+ --with-stream_geoip_module enable ngx_stream_geoip_module
+ --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module
+diff -r ac779115ed6e auto/os/linux
+--- a/auto/os/linux Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/os/linux Thu May 11 11:48:37 2023 -0400
+@@ -232,6 +232,50 @@ ngx_feature_test="struct crypt_data cd;
+ ngx_include="sys/vfs.h"; . auto/include
+
+
++# BPF sockhash
++
++ngx_feature="BPF sockhash"
++ngx_feature_name="NGX_HAVE_BPF"
++ngx_feature_run=no
++ngx_feature_incs="#include <linux/bpf.h>
++ #include <sys/syscall.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="union bpf_attr attr = { 0 };
++
++ attr.map_flags = 0;
++ attr.map_type = BPF_MAP_TYPE_SOCKHASH;
++
++ syscall(__NR_bpf, 0, &attr, 0);"
++. auto/feature
++
++if [ $ngx_found = yes ]; then
++ CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c"
++ CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h"
++
++ if [ $QUIC_BPF != NONE ]; then
++ QUIC_BPF=YES
++ fi
++fi
++
++
++ngx_feature="SO_COOKIE"
++ngx_feature_name="NGX_HAVE_SO_COOKIE"
++ngx_feature_run=no
++ngx_feature_incs="#include <sys/socket.h>
++ $NGX_INCLUDE_INTTYPES_H"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="socklen_t optlen = sizeof(uint64_t);
++ uint64_t cookie;
++ getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)"
++. auto/feature
++
++if [ $ngx_found = yes ]; then
++ SO_COOKIE_FOUND=YES
++fi
++
++
+ # UDP segmentation offloading
+
+ ngx_feature="UDP_SEGMENT"
+diff -r ac779115ed6e auto/sources
+--- a/auto/sources Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/sources Thu May 11 11:48:37 2023 -0400
+@@ -83,7 +83,7 @@ CORE_SRCS="src/core/nginx.c \
+
+ EVENT_MODULES="ngx_events_module ngx_event_core_module"
+
+-EVENT_INCS="src/event src/event/modules"
++EVENT_INCS="src/event src/event/modules src/event/quic"
+
+ EVENT_DEPS="src/event/ngx_event.h \
+ src/event/ngx_event_timer.h \
+diff -r ac779115ed6e auto/unix
+--- a/auto/unix Tue Mar 28 18:01:53 2023 +0300
++++ b/auto/unix Thu May 11 11:48:37 2023 -0400
+@@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_
+ . auto/feature
+
+
++# IP packet fragmentation
++
++ngx_feature="IP_MTU_DISCOVER"
++ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER"
++ngx_feature_run=no
++ngx_feature_incs="#include <sys/socket.h>
++ #include <netinet/in.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="(void) IP_PMTUDISC_DO;
++ setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)"
++. auto/feature
++
++
++ngx_feature="IPV6_MTU_DISCOVER"
++ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER"
++ngx_feature_run=no
++ngx_feature_incs="#include <sys/socket.h>
++ #include <netinet/in.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="(void) IPV6_PMTUDISC_DO;
++ setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)"
++. auto/feature
++
++
++ngx_feature="IP_DONTFRAG"
++ngx_feature_name="NGX_HAVE_IP_DONTFRAG"
++ngx_feature_run=no
++ngx_feature_incs="#include <sys/socket.h>
++ #include <netinet/in.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)"
++. auto/feature
++
++
++ngx_feature="IPV6_DONTFRAG"
++ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG"
++ngx_feature_run=no
++ngx_feature_incs="#include <sys/socket.h>
++ #include <netinet/in.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)"
++. auto/feature
++
++
+ ngx_feature="TCP_DEFER_ACCEPT"
+ ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT"
+ ngx_feature_run=no
+diff -r ac779115ed6e src/core/nginx.c
+--- a/src/core/nginx.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/nginx.c Thu May 11 11:48:37 2023 -0400
+@@ -680,6 +680,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle,
+
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
++ if (ls[i].ignore) {
++ continue;
++ }
+ p = ngx_sprintf(p, "%ud;", ls[i].fd);
+ }
+
+diff -r ac779115ed6e src/core/ngx_bpf.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/core/ngx_bpf.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,143 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#define NGX_BPF_LOGBUF_SIZE (16 * 1024)
++
++
++static ngx_inline int
++ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
++{
++ return syscall(__NR_bpf, cmd, attr, size);
++}
++
++
++void
++ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd)
++{
++ ngx_uint_t i;
++ ngx_bpf_reloc_t *rl;
++
++ rl = program->relocs;
++
++ for (i = 0; i < program->nrelocs; i++) {
++ if (ngx_strcmp(rl[i].name, symbol) == 0) {
++ program->ins[rl[i].offset].src_reg = 1;
++ program->ins[rl[i].offset].imm = fd;
++ }
++ }
++}
++
++
++int
++ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program)
++{
++ int fd;
++ union bpf_attr attr;
++#if (NGX_DEBUG)
++ char buf[NGX_BPF_LOGBUF_SIZE];
++#endif
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.license = (uintptr_t) program->license;
++ attr.prog_type = program->type;
++ attr.insns = (uintptr_t) program->ins;
++ attr.insn_cnt = program->nins;
++
++#if (NGX_DEBUG)
++ /* for verifier errors */
++ attr.log_buf = (uintptr_t) buf;
++ attr.log_size = NGX_BPF_LOGBUF_SIZE;
++ attr.log_level = 1;
++#endif
++
++ fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
++ if (fd < 0) {
++ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
++ "failed to load BPF program");
++
++ ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
++ "bpf verifier: %s", buf);
++
++ return -1;
++ }
++
++ return fd;
++}
++
++
++int
++ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
++ int value_size, int max_entries, uint32_t map_flags)
++{
++ int fd;
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_type = type;
++ attr.key_size = key_size;
++ attr.value_size = value_size;
++ attr.max_entries = max_entries;
++ attr.map_flags = map_flags;
++
++ fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
++ if (fd < 0) {
++ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
++ "failed to create BPF map");
++ return NGX_ERROR;
++ }
++
++ return fd;
++}
++
++
++int
++ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags)
++{
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_fd = fd;
++ attr.key = (uintptr_t) key;
++ attr.value = (uintptr_t) value;
++ attr.flags = flags;
++
++ return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
++}
++
++
++int
++ngx_bpf_map_delete(int fd, const void *key)
++{
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_fd = fd;
++ attr.key = (uintptr_t) key;
++
++ return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
++}
++
++
++int
++ngx_bpf_map_lookup(int fd, const void *key, void *value)
++{
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_fd = fd;
++ attr.key = (uintptr_t) key;
++ attr.value = (uintptr_t) value;
++
++ return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
++}
+diff -r ac779115ed6e src/core/ngx_bpf.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/core/ngx_bpf.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,43 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_BPF_H_INCLUDED_
++#define _NGX_BPF_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#include <linux/bpf.h>
++
++
++typedef struct {
++ char *name;
++ int offset;
++} ngx_bpf_reloc_t;
++
++typedef struct {
++ char *license;
++ enum bpf_prog_type type;
++ struct bpf_insn *ins;
++ size_t nins;
++ ngx_bpf_reloc_t *relocs;
++ size_t nrelocs;
++} ngx_bpf_program_t;
++
++
++void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol,
++ int fd);
++int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program);
++
++int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
++ int value_size, int max_entries, uint32_t map_flags);
++int ngx_bpf_map_update(int fd, const void *key, const void *value,
++ uint64_t flags);
++int ngx_bpf_map_delete(int fd, const void *key);
++int ngx_bpf_map_lookup(int fd, const void *key, void *value);
++
++#endif /* _NGX_BPF_H_INCLUDED_ */
+diff -r ac779115ed6e src/core/ngx_connection.c
+--- a/src/core/ngx_connection.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/ngx_connection.c Thu May 11 11:48:37 2023 -0400
+@@ -72,10 +72,6 @@ ngx_create_listening(ngx_conf_t *cf, str
+
+ ngx_memcpy(ls->addr_text.data, text, len);
+
+-#if !(NGX_WIN32)
+- ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ngx_udp_rbtree_insert_value);
+-#endif
+-
+ ls->fd = (ngx_socket_t) -1;
+ ls->type = SOCK_STREAM;
+
+@@ -1014,6 +1010,78 @@ ngx_configure_listening_sockets(ngx_cycl
+ }
+
+ #endif
++
++#if (NGX_HAVE_IP_MTU_DISCOVER)
++
++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) {
++ value = IP_PMTUDISC_DO;
++
++ if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER,
++ (const void *) &value, sizeof(int))
++ == -1)
++ {
++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
++ "setsockopt(IP_MTU_DISCOVER) "
++ "for %V failed, ignored",
++ &ls[i].addr_text);
++ }
++ }
++
++#elif (NGX_HAVE_IP_DONTFRAG)
++
++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) {
++ value = 1;
++
++ if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG,
++ (const void *) &value, sizeof(int))
++ == -1)
++ {
++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
++ "setsockopt(IP_DONTFRAG) "
++ "for %V failed, ignored",
++ &ls[i].addr_text);
++ }
++ }
++
++#endif
++
++#if (NGX_HAVE_INET6)
++
++#if (NGX_HAVE_IPV6_MTU_DISCOVER)
++
++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) {
++ value = IPV6_PMTUDISC_DO;
++
++ if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
++ (const void *) &value, sizeof(int))
++ == -1)
++ {
++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
++ "setsockopt(IPV6_MTU_DISCOVER) "
++ "for %V failed, ignored",
++ &ls[i].addr_text);
++ }
++ }
++
++#elif (NGX_HAVE_IP_DONTFRAG)
++
++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) {
++ value = 1;
++
++ if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG,
++ (const void *) &value, sizeof(int))
++ == -1)
++ {
++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
++ "setsockopt(IPV6_DONTFRAG) "
++ "for %V failed, ignored",
++ &ls[i].addr_text);
++ }
++ }
++
++#endif
++
++#endif
+ }
+
+ return;
+@@ -1037,6 +1105,12 @@ ngx_close_listening_sockets(ngx_cycle_t
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
+
++#if (NGX_QUIC)
++ if (ls[i].quic) {
++ continue;
++ }
++#endif
++
+ c = ls[i].connection;
+
+ if (c) {
+diff -r ac779115ed6e src/core/ngx_connection.h
+--- a/src/core/ngx_connection.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/ngx_connection.h Thu May 11 11:48:37 2023 -0400
+@@ -73,6 +73,7 @@ struct ngx_listening_s {
+ unsigned reuseport:1;
+ unsigned add_reuseport:1;
+ unsigned keepalive:2;
++ unsigned quic:1;
+
+ unsigned deferred_accept:1;
+ unsigned delete_deferred:1;
+@@ -147,6 +148,10 @@ struct ngx_connection_s {
+
+ ngx_proxy_protocol_t *proxy_protocol;
+
++#if (NGX_QUIC || NGX_COMPAT)
++ ngx_quic_stream_t *quic;
++#endif
++
+ #if (NGX_SSL || NGX_COMPAT)
+ ngx_ssl_connection_t *ssl;
+ #endif
+diff -r ac779115ed6e src/core/ngx_core.h
+--- a/src/core/ngx_core.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/core/ngx_core.h Thu May 11 11:48:37 2023 -0400
+@@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx
+ typedef struct ngx_thread_task_s ngx_thread_task_t;
+ typedef struct ngx_ssl_s ngx_ssl_t;
+ typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;
++typedef struct ngx_quic_stream_s ngx_quic_stream_t;
+ typedef struct ngx_ssl_connection_s ngx_ssl_connection_t;
+ typedef struct ngx_udp_connection_s ngx_udp_connection_t;
+
+@@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt
+ #include <ngx_resolver.h>
+ #if (NGX_OPENSSL)
+ #include <ngx_event_openssl.h>
++#if (NGX_QUIC)
++#include <ngx_event_quic.h>
++#endif
+ #endif
+ #include <ngx_process_cycle.h>
+ #include <ngx_conf_file.h>
+@@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt
+ #include <ngx_connection.h>
+ #include <ngx_syslog.h>
+ #include <ngx_proxy_protocol.h>
++#if (NGX_HAVE_BPF)
++#include <ngx_bpf.h>
++#endif
+
+
+ #define LF (u_char) '\n'
+diff -r ac779115ed6e src/event/ngx_event.c
+--- a/src/event/ngx_event.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event.c Thu May 11 11:48:37 2023 -0400
+@@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_
+ ngx_int_t
+ ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
+ {
++#if (NGX_QUIC)
++
++ ngx_connection_t *c;
++
++ c = rev->data;
++
++ if (c->quic) {
++ return NGX_OK;
++ }
++
++#endif
++
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ /* kqueue, epoll */
+@@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev,
+ {
+ ngx_connection_t *c;
+
++ c = wev->data;
++
++#if (NGX_QUIC)
++ if (c->quic) {
++ return NGX_OK;
++ }
++#endif
++
+ if (lowat) {
+- c = wev->data;
+-
+ if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+@@ -873,8 +891,16 @@ ngx_event_process_init(ngx_cycle_t *cycl
+
+ #else
+
+- rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
+- : ngx_event_recvmsg;
++ if (c->type == SOCK_STREAM) {
++ rev->handler = ngx_event_accept;
++
++#if (NGX_QUIC)
++ } else if (ls[i].quic) {
++ rev->handler = ngx_quic_recvmsg;
++#endif
++ } else {
++ rev->handler = ngx_event_recvmsg;
++ }
+
+ #if (NGX_HAVE_REUSEPORT)
+
+diff -r ac779115ed6e src/event/ngx_event_openssl.c
+--- a/src/event/ngx_event_openssl.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_openssl.c Thu May 11 11:48:37 2023 -0400
+@@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ng
+ #ifdef SSL_READ_EARLY_DATA_SUCCESS
+ static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c);
+ #endif
+-#if (NGX_DEBUG)
+-static void ngx_ssl_handshake_log(ngx_connection_t *c);
+-#endif
+ static void ngx_ssl_handshake_handler(ngx_event_t *ev);
+ #ifdef SSL_READ_EARLY_DATA_SUCCESS
+ static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf,
+@@ -2052,7 +2049,7 @@ ngx_ssl_try_early_data(ngx_connection_t
+
+ #if (NGX_DEBUG)
+
+-static void
++void
+ ngx_ssl_handshake_log(ngx_connection_t *c)
+ {
+ char buf[129], *s, *d;
+@@ -3202,6 +3199,13 @@ ngx_ssl_shutdown(ngx_connection_t *c)
+ ngx_err_t err;
+ ngx_uint_t tries;
+
++#if (NGX_QUIC)
++ if (c->quic) {
++ /* QUIC streams inherit SSL object */
++ return NGX_OK;
++ }
++#endif
++
+ rc = NGX_OK;
+
+ ngx_ssl_ocsp_cleanup(c);
+diff -r ac779115ed6e src/event/ngx_event_openssl.h
+--- a/src/event/ngx_event_openssl.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_openssl.h Thu May 11 11:48:37 2023 -0400
+@@ -24,6 +24,14 @@
+ #include <openssl/engine.h>
+ #endif
+ #include <openssl/evp.h>
++#if (NGX_QUIC)
++#ifdef OPENSSL_IS_BORINGSSL
++#include <openssl/hkdf.h>
++#include <openssl/chacha.h>
++#else
++#include <openssl/kdf.h>
++#endif
++#endif
+ #include <openssl/hmac.h>
+ #ifndef OPENSSL_NO_OCSP
+ #include <openssl/ocsp.h>
+@@ -302,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ng
+
+
+ ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
++#if (NGX_DEBUG)
++void ngx_ssl_handshake_log(ngx_connection_t *c);
++#endif
+ ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
+ ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit);
+diff -r ac779115ed6e src/event/ngx_event_udp.c
+--- a/src/event/ngx_event_udp.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_udp.c Thu May 11 11:48:37 2023 -0400
+@@ -12,13 +12,6 @@
+
+ #if !(NGX_WIN32)
+
+-struct ngx_udp_connection_s {
+- ngx_rbtree_node_t node;
+- ngx_connection_t *connection;
+- ngx_buf_t *buffer;
+-};
+-
+-
+ static void ngx_close_accepted_udp_connection(ngx_connection_t *c);
+ static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf,
+ size_t size);
+diff -r ac779115ed6e src/event/ngx_event_udp.h
+--- a/src/event/ngx_event_udp.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/event/ngx_event_udp.h Thu May 11 11:48:37 2023 -0400
+@@ -23,6 +23,13 @@
+ #endif
+
+
++struct ngx_udp_connection_s {
++ ngx_rbtree_node_t node;
++ ngx_connection_t *connection;
++ ngx_buf_t *buffer;
++};
++
++
+ #if (NGX_HAVE_ADDRINFO_CMSG)
+
+ typedef union {
+diff -r ac779115ed6e src/event/quic/bpf/bpfgen.sh
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/bpfgen.sh Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,113 @@
++#!/bin/bash
++
++export LANG=C
++
++set -e
++
++if [ $# -lt 1 ]; then
++ echo "Usage: PROGNAME=foo LICENSE=bar $0 <bpf object file>"
++ exit 1
++fi
++
++
++self=$0
++filename=$1
++funcname=$PROGNAME
++
++generate_head()
++{
++ cat << END
++/* AUTO-GENERATED, DO NOT EDIT. */
++
++#include <stddef.h>
++#include <stdint.h>
++
++#include "ngx_bpf.h"
++
++
++END
++}
++
++generate_tail()
++{
++ cat << END
++
++ngx_bpf_program_t $PROGNAME = {
++ .relocs = bpf_reloc_prog_$funcname,
++ .nrelocs = sizeof(bpf_reloc_prog_$funcname)
++ / sizeof(bpf_reloc_prog_$funcname[0]),
++ .ins = bpf_insn_prog_$funcname,
++ .nins = sizeof(bpf_insn_prog_$funcname)
++ / sizeof(bpf_insn_prog_$funcname[0]),
++ .license = "$LICENSE",
++ .type = BPF_PROG_TYPE_SK_REUSEPORT,
++};
++
++END
++}
++
++process_relocations()
++{
++ echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {"
++
++ objdump -r $filename | awk '{
++
++ if (enabled && $NF > 0) {
++ off = strtonum(sprintf("0x%s", $1));
++ name = $3;
++
++ printf(" { \"%s\", %d },\n", name, off/8);
++ }
++
++ if ($1 == "OFFSET") {
++ enabled=1;
++ }
++}'
++ echo "};"
++ echo
++}
++
++process_section()
++{
++ echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {"
++ echo " /* opcode dst src offset imm */"
++
++ section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname")
++
++ # dd doesn't know hex
++ length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3))
++ offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6))
++
++ for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8)
++ do
++ opcode=0x${ins:0:2}
++ srcdst=0x${ins:2:2}
++
++ # bytes are dumped in LE order
++ offset=0x${ins:6:2}${ins:4:2} # short
++ immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int
++
++ dst="$(($srcdst & 0xF))"
++ src="$(($srcdst & 0xF0))"
++ src="$(($src >> 4))"
++
++ opcode=$(printf "0x%x" $opcode)
++ dst=$(printf "BPF_REG_%d" $dst)
++ src=$(printf "BPF_REG_%d" $src)
++ offset=$(printf "%d" $offset)
++ immedi=$(printf "0x%x" $immedi)
++
++ printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi
++ done
++
++cat << END
++};
++
++END
++}
++
++generate_head
++process_relocations
++process_section
++generate_tail
++
+diff -r ac779115ed6e src/event/quic/bpf/makefile
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/makefile Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,30 @@
++CFLAGS=-O2 -Wall
++
++LICENSE=BSD
++
++PROGNAME=ngx_quic_reuseport_helper
++RESULT=ngx_event_quic_bpf_code
++DEST=../$(RESULT).c
++
++all: $(RESULT)
++
++$(RESULT): $(PROGNAME).o
++ LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@
++
++DEFS=-DPROGNAME=\"$(PROGNAME)\" \
++ -DLICENSE_$(LICENSE) \
++ -DLICENSE=\"$(LICENSE)\" \
++
++$(PROGNAME).o: $(PROGNAME).c
++ clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@
++
++install: $(RESULT)
++ cp $(RESULT) $(DEST)
++
++clean:
++ @rm -f $(RESULT) *.o
++
++debug: $(PROGNAME).o
++ llvm-objdump -S -no-show-raw-insn $<
++
++.DELETE_ON_ERROR:
+diff -r ac779115ed6e src/event/quic/bpf/ngx_quic_reuseport_helper.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,140 @@
++#include <errno.h>
++#include <linux/string.h>
++#include <linux/udp.h>
++#include <linux/bpf.h>
++/*
++ * the bpf_helpers.h is not included into linux-headers, only available
++ * with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf.
++ */
++#include <bpf/bpf_helpers.h>
++
++
++#if !defined(SEC)
++#define SEC(NAME) __attribute__((section(NAME), used))
++#endif
++
++
++#if defined(LICENSE_GPL)
++
++/*
++ * To see debug:
++ *
++ * echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable
++ * cat /sys/kernel/debug/tracing/trace_pipe
++ * echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable
++ */
++
++#define debugmsg(fmt, ...) \
++do { \
++ char __buf[] = fmt; \
++ bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \
++} while (0)
++
++#else
++
++#define debugmsg(fmt, ...)
++
++#endif
++
++char _license[] SEC("license") = LICENSE;
++
++/*****************************************************************************/
++
++#define NGX_QUIC_PKT_LONG 0x80 /* header form */
++#define NGX_QUIC_SERVER_CID_LEN 20
++
++
++#define advance_data(nbytes) \
++ offset += nbytes; \
++ if (start + offset > end) { \
++ debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \
++ goto failed; \
++ } \
++ data = start + offset - 1;
++
++
++#define ngx_quic_parse_uint64(p) \
++ (((__u64)(p)[0] << 56) | \
++ ((__u64)(p)[1] << 48) | \
++ ((__u64)(p)[2] << 40) | \
++ ((__u64)(p)[3] << 32) | \
++ ((__u64)(p)[4] << 24) | \
++ ((__u64)(p)[5] << 16) | \
++ ((__u64)(p)[6] << 8) | \
++ ((__u64)(p)[7]))
++
++/*
++ * actual map object is created by the "bpf" system call,
++ * all pointers to this variable are replaced by the bpf loader
++ */
++struct bpf_map_def SEC("maps") ngx_quic_sockmap;
++
++
++SEC(PROGNAME)
++int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx)
++{
++ int rc;
++ __u64 key;
++ size_t len, offset;
++ unsigned char *start, *end, *data, *dcid;
++
++ start = ctx->data;
++ end = (unsigned char *) ctx->data_end;
++ offset = 0;
++
++ advance_data(sizeof(struct udphdr)); /* data at UDP header */
++ advance_data(1); /* data at QUIC flags */
++
++ if (data[0] & NGX_QUIC_PKT_LONG) {
++
++ advance_data(4); /* data at QUIC version */
++ advance_data(1); /* data at DCID len */
++
++ len = data[0]; /* read DCID length */
++
++ if (len < 8) {
++ /* it's useless to search for key in such short DCID */
++ return SK_PASS;
++ }
++
++ } else {
++ len = NGX_QUIC_SERVER_CID_LEN;
++ }
++
++ dcid = &data[1];
++ advance_data(len); /* we expect the packet to have full DCID */
++
++ /* make verifier happy */
++ if (dcid + sizeof(__u64) > end) {
++ goto failed;
++ }
++
++ key = ngx_quic_parse_uint64(dcid);
++
++ rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0);
++
++ switch (rc) {
++ case 0:
++ debugmsg("nginx quic socket selected by key 0x%llx", key);
++ return SK_PASS;
++
++ /* kernel returns positive error numbers, errno.h defines positive */
++ case -ENOENT:
++ debugmsg("nginx quic default route for key 0x%llx", key);
++ /* let the default reuseport logic decide which socket to choose */
++ return SK_PASS;
++
++ default:
++ debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx",
++ rc, key);
++ goto failed;
++ }
++
++failed:
++ /*
++ * SK_DROP will generate ICMP, but we may want to process "invalid" packet
++ * in userspace quic to investigate further and finally react properly
++ * (maybe ignore, maybe send something in response or close connection)
++ */
++ return SK_PASS;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,1445 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++static void ngx_quic_input_handler(ngx_event_t *rev);
++static void ngx_quic_close_handler(ngx_event_t *ev);
++
++static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b,
++ ngx_quic_conf_t *conf);
++static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc,
++ ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++
++static void ngx_quic_push_handler(ngx_event_t *ev);
++
++
++static ngx_core_module_t ngx_quic_module_ctx = {
++ ngx_string("quic"),
++ NULL,
++ NULL
++};
++
++
++ngx_module_t ngx_quic_module = {
++ NGX_MODULE_V1,
++ &ngx_quic_module_ctx, /* module context */
++ NULL, /* module directives */
++ NGX_CORE_MODULE, /* module type */
++ NULL, /* init master */
++ NULL, /* init module */
++ NULL, /* init process */
++ NULL, /* init thread */
++ NULL, /* exit thread */
++ NULL, /* exit process */
++ NULL, /* exit master */
++ NGX_MODULE_V1_PADDING
++};
++
++
++#if (NGX_DEBUG)
++
++void
++ngx_quic_connstate_dbg(ngx_connection_t *c)
++{
++ u_char *p, *last;
++ ngx_quic_connection_t *qc;
++ u_char buf[NGX_MAX_ERROR_STR];
++
++ p = buf;
++ last = p + sizeof(buf);
++
++ qc = ngx_quic_get_connection(c);
++
++ p = ngx_slprintf(p, last, "state:");
++
++ if (qc) {
++
++ if (qc->error != (ngx_uint_t) -1) {
++ p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : "");
++ p = ngx_slprintf(p, last, " error:%ui", qc->error);
++
++ if (qc->error_reason) {
++ p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason);
++ }
++ }
++
++ p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : "");
++ p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : "");
++ p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : "");
++ p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : "");
++
++ } else {
++ p = ngx_slprintf(p, last, " early");
++ }
++
++ if (c->read->timer_set) {
++ p = ngx_slprintf(p, last,
++ qc && qc->send_timer_set ? " send:%M" : " read:%M",
++ c->read->timer.key - ngx_current_msec);
++ }
++
++ if (qc) {
++
++ if (qc->push.timer_set) {
++ p = ngx_slprintf(p, last, " push:%M",
++ qc->push.timer.key - ngx_current_msec);
++ }
++
++ if (qc->pto.timer_set) {
++ p = ngx_slprintf(p, last, " pto:%M",
++ qc->pto.timer.key - ngx_current_msec);
++ }
++
++ if (qc->close.timer_set) {
++ p = ngx_slprintf(p, last, " close:%M",
++ qc->close.timer.key - ngx_current_msec);
++ }
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic %*s", p - buf, buf);
++}
++
++#endif
++
++
++ngx_int_t
++ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp)
++{
++ ngx_str_t scid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ scid.data = qc->path->cid->id;
++ scid.len = qc->path->cid->len;
++
++ if (scid.len != ctp->initial_scid.len
++ || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0)
++ {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic client initial_source_connection_id mismatch");
++ return NGX_ERROR;
++ }
++
++ if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE
++ || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
++ {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid maximum packet size";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic maximum packet size is invalid");
++ return NGX_ERROR;
++
++ } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) {
++ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic client maximum packet size truncated");
++ }
++
++ if (ctp->active_connection_id_limit < 2) {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid active_connection_id_limit";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic active_connection_id_limit is invalid");
++ return NGX_ERROR;
++ }
++
++ if (ctp->ack_delay_exponent > 20) {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid ack_delay_exponent";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic ack_delay_exponent is invalid");
++ return NGX_ERROR;
++ }
++
++ if (ctp->max_ack_delay >= 16384) {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid max_ack_delay";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic max_ack_delay is invalid");
++ return NGX_ERROR;
++ }
++
++ if (ctp->max_idle_timeout > 0
++ && ctp->max_idle_timeout < qc->tp.max_idle_timeout)
++ {
++ qc->tp.max_idle_timeout = ctp->max_idle_timeout;
++ }
++
++ qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi;
++ qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni;
++
++ ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t));
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf)
++{
++ ngx_int_t rc;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run");
++
++ rc = ngx_quic_handle_datagram(c, c->buffer, conf);
++ if (rc != NGX_OK) {
++ ngx_quic_close_connection(c, rc);
++ return;
++ }
++
++ /* quic connection is now created */
++ qc = ngx_quic_get_connection(c);
++
++ ngx_add_timer(c->read, qc->tp.max_idle_timeout);
++ ngx_quic_connstate_dbg(c);
++
++ c->read->handler = ngx_quic_input_handler;
++
++ return;
++}
++
++
++static ngx_quic_connection_t *
++ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *pkt)
++{
++ ngx_uint_t i;
++ ngx_quic_tp_t *ctp;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
++ if (qc == NULL) {
++ return NULL;
++ }
++
++ qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t));
++ if (qc->keys == NULL) {
++ return NULL;
++ }
++
++ qc->version = pkt->version;
++
++ ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
++ ngx_quic_rbtree_insert_stream);
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_queue_init(&qc->send_ctx[i].frames);
++ ngx_queue_init(&qc->send_ctx[i].sending);
++ ngx_queue_init(&qc->send_ctx[i].sent);
++ qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN;
++ qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN;
++ qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN;
++ qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN;
++ }
++
++ qc->send_ctx[0].level = ssl_encryption_initial;
++ qc->send_ctx[1].level = ssl_encryption_handshake;
++ qc->send_ctx[2].level = ssl_encryption_application;
++
++ ngx_queue_init(&qc->free_frames);
++
++ qc->avg_rtt = NGX_QUIC_INITIAL_RTT;
++ qc->rttvar = NGX_QUIC_INITIAL_RTT / 2;
++ qc->min_rtt = NGX_TIMER_INFINITE;
++ qc->first_rtt = NGX_TIMER_INFINITE;
++
++ /*
++ * qc->latest_rtt = 0
++ */
++
++ qc->pto.log = c->log;
++ qc->pto.data = c;
++ qc->pto.handler = ngx_quic_pto_handler;
++
++ qc->push.log = c->log;
++ qc->push.data = c;
++ qc->push.handler = ngx_quic_push_handler;
++
++ qc->close.log = c->log;
++ qc->close.data = c;
++ qc->close.handler = ngx_quic_close_handler;
++
++ qc->path_validation.log = c->log;
++ qc->path_validation.data = c;
++ qc->path_validation.handler = ngx_quic_path_validation_handler;
++
++ qc->conf = conf;
++
++ if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) {
++ return NULL;
++ }
++
++ ctp = &qc->ctp;
++
++ /* defaults to be used before actual client parameters are received */
++ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
++ ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
++ ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
++ ctp->active_connection_id_limit = 2;
++
++ ngx_queue_init(&qc->streams.uninitialized);
++ ngx_queue_init(&qc->streams.free);
++
++ qc->streams.recv_max_data = qc->tp.initial_max_data;
++ qc->streams.recv_window = qc->streams.recv_max_data;
++
++ qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni;
++ qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi;
++
++ qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
++ ngx_max(2 * qc->tp.max_udp_payload_size,
++ 14720));
++ qc->congestion.ssthresh = (size_t) -1;
++ qc->congestion.recovery_start = ngx_current_msec;
++
++ if (pkt->validated && pkt->retried) {
++ qc->tp.retry_scid.len = pkt->dcid.len;
++ qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid);
++ if (qc->tp.retry_scid.data == NULL) {
++ return NULL;
++ }
++ }
++
++ if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log)
++ != NGX_OK)
++ {
++ return NULL;
++ }
++
++ qc->validated = pkt->validated;
++
++ if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) {
++ return NULL;
++ }
++
++ c->idle = 1;
++ ngx_reusable_connection(c, 1);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic connection created");
++
++ return qc;
++}
++
++
++static ngx_int_t
++ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ u_char *tail, ch;
++ ngx_uint_t i;
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /* A stateless reset uses an entire UDP datagram */
++ if (!pkt->first) {
++ return NGX_DECLINED;
++ }
++
++ tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN;
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (cid->seqnum == 0 || !cid->used) {
++ /*
++ * No stateless reset token in initial connection id.
++ * Don't accept a token from an unused connection id.
++ */
++ continue;
++ }
++
++ /* constant time comparison */
++
++ for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) {
++ ch |= tail[i] ^ cid->sr_token[i];
++ }
++
++ if (ch == 0) {
++ return NGX_OK;
++ }
++ }
++
++ return NGX_DECLINED;
++}
++
++
++static void
++ngx_quic_input_handler(ngx_event_t *rev)
++{
++ ngx_int_t rc;
++ ngx_buf_t *b;
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler");
++
++ c = rev->data;
++ qc = ngx_quic_get_connection(c);
++
++ c->log->action = "handling quic input";
++
++ if (rev->timedout) {
++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
++ "quic client timed out");
++ ngx_quic_close_connection(c, NGX_DONE);
++ return;
++ }
++
++ if (c->close) {
++ c->close = 0;
++
++ if (!ngx_exiting) {
++ qc->error = NGX_QUIC_ERR_NO_ERROR;
++ qc->error_reason = "graceful shutdown";
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ if (!qc->closing && qc->conf->shutdown) {
++ qc->conf->shutdown(c);
++ }
++
++ return;
++ }
++
++ b = c->udp->buffer;
++ if (b == NULL) {
++ return;
++ }
++
++ rc = ngx_quic_handle_datagram(c, b, NULL);
++
++ if (rc == NGX_ERROR) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ if (rc == NGX_DONE) {
++ return;
++ }
++
++ /* rc == NGX_OK */
++
++ qc->send_timer_set = 0;
++ ngx_add_timer(rev, qc->tp.max_idle_timeout);
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++void
++ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc)
++{
++ ngx_uint_t i;
++ ngx_pool_t *pool;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc == NULL) {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet rejected rc:%i, cleanup connection", rc);
++ goto quic_done;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic close %s rc:%i",
++ qc->closing ? "resumed": "initiated", rc);
++
++ if (!qc->closing) {
++
++ /* drop packets from retransmit queues, no ack is expected */
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_quic_free_frames(c, &qc->send_ctx[i].frames);
++ ngx_quic_free_frames(c, &qc->send_ctx[i].sent);
++ }
++
++ if (rc == NGX_DONE) {
++
++ /*
++ * RFC 9000, 10.1. Idle Timeout
++ *
++ * If a max_idle_timeout is specified by either endpoint in its
++ * transport parameters (Section 18.2), the connection is silently
++ * closed and its state is discarded when it remains idle
++ */
++
++ /* this case also handles some errors from ngx_quic_run() */
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic close silent drain:%d timedout:%d",
++ qc->draining, c->read->timedout);
++ } else {
++
++ /*
++ * RFC 9000, 10.2. Immediate Close
++ *
++ * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19)
++ * to terminate the connection immediately.
++ */
++
++ qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection)
++ : ssl_encryption_initial;
++
++ if (qc->error == (ngx_uint_t) -1) {
++ qc->error = NGX_QUIC_ERR_INTERNAL_ERROR;
++ qc->error_app = 0;
++ }
++
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic close immediate term:%d drain:%d "
++ "%serror:%ui \"%s\"",
++ rc == NGX_ERROR ? 1 : 0, qc->draining,
++ qc->error_app ? "app " : "", qc->error,
++ qc->error_reason ? qc->error_reason : "");
++
++ if (rc == NGX_OK) {
++ ctx = ngx_quic_get_send_ctx(qc, qc->error_level);
++ ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx));
++ }
++
++ (void) ngx_quic_send_cc(c);
++
++ if (qc->error_level == ssl_encryption_handshake) {
++ /* for clients that might not have handshake keys */
++ qc->error_level = ssl_encryption_initial;
++ (void) ngx_quic_send_cc(c);
++ }
++ }
++
++ qc->closing = 1;
++ }
++
++ if (rc == NGX_ERROR && qc->close.timer_set) {
++ /* do not wait for timer in case of fatal error */
++ ngx_del_timer(&qc->close);
++ }
++
++ if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) {
++ return;
++ }
++
++ if (qc->push.timer_set) {
++ ngx_del_timer(&qc->push);
++ }
++
++ if (qc->pto.timer_set) {
++ ngx_del_timer(&qc->pto);
++ }
++
++ if (qc->path_validation.timer_set) {
++ ngx_del_timer(&qc->path_validation);
++ }
++
++ if (qc->push.posted) {
++ ngx_delete_posted_event(&qc->push);
++ }
++
++ if (qc->close.timer_set) {
++ return;
++ }
++
++ if (qc->close.posted) {
++ ngx_delete_posted_event(&qc->close);
++ }
++
++ ngx_quic_close_sockets(c);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed");
++
++ /* may be tested from SSL callback during SSL shutdown */
++ c->udp = NULL;
++
++quic_done:
++
++ if (c->ssl) {
++ (void) ngx_ssl_shutdown(c);
++ }
++
++ if (c->read->timer_set) {
++ ngx_del_timer(c->read);
++ }
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
++#endif
++
++ c->destroyed = 1;
++
++ pool = c->pool;
++
++ ngx_close_connection(c);
++
++ ngx_destroy_pool(pool);
++}
++
++
++void
++ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc->closing) {
++ return;
++ }
++
++ qc->error = err;
++ qc->error_reason = reason;
++ qc->error_app = 1;
++ qc->error_ftype = 0;
++
++ ngx_post_event(&qc->close, &ngx_posted_events);
++}
++
++
++void
++ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ qc->shutdown = 1;
++ qc->shutdown_code = err;
++ qc->shutdown_reason = reason;
++
++ ngx_quic_shutdown_quic(c);
++}
++
++
++static void
++ngx_quic_close_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler");
++
++ c = ev->data;
++
++ ngx_quic_close_connection(c, NGX_OK);
++}
++
++
++static ngx_int_t
++ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b,
++ ngx_quic_conf_t *conf)
++{
++ size_t size;
++ u_char *p, *start;
++ ngx_int_t rc;
++ ngx_uint_t good;
++ ngx_quic_path_t *path;
++ ngx_quic_header_t pkt;
++ ngx_quic_connection_t *qc;
++
++ good = 0;
++ path = NULL;
++
++ size = b->last - b->pos;
++
++ p = start = b->pos;
++
++ while (p < b->last) {
++
++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
++ pkt.raw = b;
++ pkt.data = p;
++ pkt.len = b->last - p;
++ pkt.log = c->log;
++ pkt.first = (p == start) ? 1 : 0;
++ pkt.path = path;
++ pkt.flags = p[0];
++ pkt.raw->pos++;
++
++ rc = ngx_quic_handle_packet(c, conf, &pkt);
++
++#if (NGX_DEBUG)
++ if (pkt.parsed) {
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet done rc:%i level:%s"
++ " decr:%d pn:%L perr:%ui",
++ rc, ngx_quic_level_name(pkt.level),
++ pkt.decrypted, pkt.pn, pkt.error);
++ } else {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet done rc:%i parse failed", rc);
++ }
++#endif
++
++ if (rc == NGX_ERROR || rc == NGX_DONE) {
++ return rc;
++ }
++
++ if (rc == NGX_OK) {
++ good = 1;
++ }
++
++ path = pkt.path; /* preserve packet path from 1st packet */
++
++ /* NGX_OK || NGX_DECLINED */
++
++ /*
++ * we get NGX_DECLINED when there are no keys [yet] available
++ * to decrypt packet.
++ * Instead of queueing it, we ignore it and rely on the sender's
++ * retransmission:
++ *
++ * RFC 9000, 12.2. Coalescing Packets
++ *
++ * For example, if decryption fails (because the keys are
++ * not available or for any other reason), the receiver MAY either
++ * discard or buffer the packet for later processing and MUST
++ * attempt to process the remaining packets.
++ *
++ * We also skip packets that don't match connection state
++ * or cannot be parsed properly.
++ */
++
++ /* b->pos is at header end, adjust by actual packet length */
++ b->pos = pkt.data + pkt.len;
++
++ p = b->pos;
++ }
++
++ if (!good) {
++ return NGX_DONE;
++ }
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc) {
++ qc->received += size;
++
++ if ((uint64_t) (c->sent + qc->received) / 8 >
++ (qc->streams.sent + qc->streams.recv_last) + 1048576)
++ {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected");
++
++ qc->error = NGX_QUIC_ERR_NO_ERROR;
++ qc->error_reason = "QUIC flood detected";
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *pkt)
++{
++ ngx_int_t rc;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ c->log->action = "parsing quic packet";
++
++ rc = ngx_quic_parse_packet(pkt);
++
++ if (rc == NGX_ERROR) {
++ return NGX_DECLINED;
++ }
++
++ pkt->parsed = 1;
++
++ c->log->action = "handling quic packet";
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet rx dcid len:%uz %xV",
++ pkt->dcid.len, &pkt->dcid);
++
++#if (NGX_DEBUG)
++ if (pkt->level != ssl_encryption_application) {
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet rx scid len:%uz %xV",
++ pkt->scid.len, &pkt->scid);
++ }
++
++ if (pkt->level == ssl_encryption_initial) {
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic address validation token len:%uz %xV",
++ pkt->token.len, &pkt->token);
++ }
++#endif
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc) {
++
++ if (rc == NGX_ABORT) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic unsupported version: 0x%xD", pkt->version);
++ return NGX_DECLINED;
++ }
++
++ if (pkt->level != ssl_encryption_application) {
++
++ if (pkt->version != qc->version) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic version mismatch: 0x%xD", pkt->version);
++ return NGX_DECLINED;
++ }
++
++ if (pkt->first) {
++ qsock = ngx_quic_get_socket(c);
++
++ if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen,
++ qc->path->sockaddr, qc->path->socklen, 1)
++ != NGX_OK)
++ {
++ /* packet comes from unknown path, possibly migration */
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic too early migration attempt");
++ return NGX_DONE;
++ }
++ }
++
++ if (ngx_quic_check_csid(qc, pkt) != NGX_OK) {
++ return NGX_DECLINED;
++ }
++
++ }
++
++ rc = ngx_quic_handle_payload(c, pkt);
++
++ if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) {
++ if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic stateless reset packet detected");
++
++ qc->draining = 1;
++ ngx_quic_close_connection(c, NGX_OK);
++
++ return NGX_OK;
++ }
++ }
++
++ return rc;
++ }
++
++ /* packet does not belong to a connection */
++
++ if (rc == NGX_ABORT) {
++ return ngx_quic_negotiate_version(c, pkt);
++ }
++
++ if (pkt->level == ssl_encryption_application) {
++ return ngx_quic_send_stateless_reset(c, conf, pkt);
++ }
++
++ if (pkt->level != ssl_encryption_initial) {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic expected initial, got handshake");
++ return NGX_ERROR;
++ }
++
++ c->log->action = "handling initial packet";
++
++ if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) {
++ /* RFC 9000, 7.2. Negotiating Connection IDs */
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic too short dcid in initial"
++ " packet: len:%i", pkt->dcid.len);
++ return NGX_ERROR;
++ }
++
++ /* process retry and initialize connection IDs */
++
++ if (pkt->token.len) {
++
++ rc = ngx_quic_validate_token(c, conf->av_token_key, pkt);
++
++ if (rc == NGX_ERROR) {
++ /* internal error */
++ return NGX_ERROR;
++
++ } else if (rc == NGX_ABORT) {
++ /* token cannot be decrypted */
++ return ngx_quic_send_early_cc(c, pkt,
++ NGX_QUIC_ERR_INVALID_TOKEN,
++ "cannot decrypt token");
++ } else if (rc == NGX_DECLINED) {
++ /* token is invalid */
++
++ if (pkt->retried) {
++ /* invalid address validation token */
++ return ngx_quic_send_early_cc(c, pkt,
++ NGX_QUIC_ERR_INVALID_TOKEN,
++ "invalid address validation token");
++ } else if (conf->retry) {
++ /* invalid NEW_TOKEN */
++ return ngx_quic_send_retry(c, conf, pkt);
++ }
++ }
++
++ /* NGX_OK */
++
++ } else if (conf->retry) {
++ return ngx_quic_send_retry(c, conf, pkt);
++
++ } else {
++ pkt->odcid = pkt->dcid;
++ }
++
++ if (ngx_terminate || ngx_exiting) {
++ if (conf->retry) {
++ return ngx_quic_send_retry(c, conf, pkt);
++ }
++
++ return NGX_ERROR;
++ }
++
++ c->log->action = "creating quic connection";
++
++ qc = ngx_quic_new_connection(c, conf, pkt);
++ if (qc == NULL) {
++ return NGX_ERROR;
++ }
++
++ return ngx_quic_handle_payload(c, pkt);
++}
++
++
++static ngx_int_t
++ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ ngx_int_t rc;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ qc = ngx_quic_get_connection(c);
++
++ qc->error = (ngx_uint_t) -1;
++ qc->error_reason = 0;
++
++ c->log->action = "decrypting packet";
++
++ if (!ngx_quic_keys_available(qc->keys, pkt->level)) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic no %s keys, ignoring packet",
++ ngx_quic_level_name(pkt->level));
++ return NGX_DECLINED;
++ }
++
++#if !defined (OPENSSL_IS_BORINGSSL)
++ /* OpenSSL provides read keys for an application level before it's ready */
++
++ if (pkt->level == ssl_encryption_application
++ && SSL_quic_read_level(c->ssl->connection)
++ < ssl_encryption_application)
++ {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic no %s keys ready, ignoring packet",
++ ngx_quic_level_name(pkt->level));
++ return NGX_DECLINED;
++ }
++#endif
++
++ pkt->keys = qc->keys;
++ pkt->key_phase = qc->key_phase;
++ pkt->plaintext = buf;
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ rc = ngx_quic_decrypt(pkt, &ctx->largest_pn);
++ if (rc != NGX_OK) {
++ qc->error = pkt->error;
++ qc->error_reason = "failed to decrypt packet";
++ return rc;
++ }
++
++ pkt->decrypted = 1;
++
++ c->log->action = "handling decrypted packet";
++
++ if (pkt->path == NULL) {
++ rc = ngx_quic_set_path(c, pkt);
++ if (rc != NGX_OK) {
++ return rc;
++ }
++ }
++
++ if (c->ssl == NULL) {
++ if (ngx_quic_init_connection(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (pkt->level == ssl_encryption_handshake) {
++ /*
++ * RFC 9001, 4.9.1. Discarding Initial Keys
++ *
++ * The successful use of Handshake packets indicates
++ * that no more Initial packets need to be exchanged
++ */
++ ngx_quic_discard_ctx(c, ssl_encryption_initial);
++
++ if (!qc->path->validated) {
++ qc->path->validated = 1;
++ qc->path->limited = 0;
++ ngx_quic_path_dbg(c, "in handshake", qc->path);
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++ }
++
++ if (qc->closing) {
++ /*
++ * RFC 9000, 10.2. Immediate Close
++ *
++ * ... delayed or reordered packets are properly discarded.
++ *
++ * In the closing state, an endpoint retains only enough information
++ * to generate a packet containing a CONNECTION_CLOSE frame and to
++ * identify packets as belonging to the connection.
++ */
++
++ qc->error_level = pkt->level;
++ qc->error = NGX_QUIC_ERR_NO_ERROR;
++ qc->error_reason = "connection is closing, packet discarded";
++ qc->error_ftype = 0;
++ qc->error_app = 0;
++
++ return ngx_quic_send_cc(c);
++ }
++
++ pkt->received = ngx_current_msec;
++
++ c->log->action = "handling payload";
++
++ if (pkt->level != ssl_encryption_application) {
++ return ngx_quic_handle_frames(c, pkt);
++ }
++
++ if (!pkt->key_update) {
++ return ngx_quic_handle_frames(c, pkt);
++ }
++
++ /* switch keys and generate next on Key Phase change */
++
++ qc->key_phase ^= 1;
++ ngx_quic_keys_switch(c, qc->keys);
++
++ rc = ngx_quic_handle_frames(c, pkt);
++ if (rc != NGX_OK) {
++ return rc;
++ }
++
++ return ngx_quic_keys_update(c, qc->keys);
++}
++
++
++void
++ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!ngx_quic_keys_available(qc->keys, level)) {
++ return;
++ }
++
++ ngx_quic_keys_discard(qc->keys, level);
++
++ qc->pto_count = 0;
++
++ ctx = ngx_quic_get_send_ctx(qc, level);
++
++ ngx_quic_free_buffer(c, &ctx->crypto);
++
++ while (!ngx_queue_empty(&ctx->sent)) {
++ q = ngx_queue_head(&ctx->sent);
++ ngx_queue_remove(q);
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ ngx_quic_congestion_ack(c, f);
++ ngx_quic_free_frame(c, f);
++ }
++
++ while (!ngx_queue_empty(&ctx->frames)) {
++ q = ngx_queue_head(&ctx->frames);
++ ngx_queue_remove(q);
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ ngx_quic_free_frame(c, f);
++ }
++
++ if (level == ssl_encryption_initial) {
++ /* close temporary listener with odcid */
++ qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN);
++ if (qsock) {
++ ngx_quic_close_socket(c, qsock);
++ }
++ }
++
++ ctx->send_ack = 0;
++
++ ngx_quic_set_lost_timer(c);
++}
++
++
++static ngx_int_t
++ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt)
++{
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (pkt->scid.len == cid->len
++ && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0)
++ {
++ return NGX_OK;
++ }
++ }
++
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid");
++ return NGX_ERROR;
++}
++
++
++static ngx_int_t
++ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ u_char *end, *p;
++ ssize_t len;
++ ngx_buf_t buf;
++ ngx_uint_t do_close, nonprobing;
++ ngx_chain_t chain;
++ ngx_quic_frame_t frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ p = pkt->payload.data;
++ end = p + pkt->payload.len;
++
++ do_close = 0;
++ nonprobing = 0;
++
++ while (p < end) {
++
++ c->log->action = "parsing frames";
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++ ngx_memzero(&buf, sizeof(ngx_buf_t));
++ buf.temporary = 1;
++
++ chain.buf = &buf;
++ chain.next = NULL;
++ frame.data = &chain;
++
++ len = ngx_quic_parse_frame(pkt, p, end, &frame);
++
++ if (len < 0) {
++ qc->error = pkt->error;
++ return NGX_ERROR;
++ }
++
++ ngx_quic_log_frame(c->log, &frame, 0);
++
++ c->log->action = "handling frames";
++
++ p += len;
++
++ switch (frame.type) {
++ /* probing frames */
++ case NGX_QUIC_FT_PADDING:
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++ break;
++
++ /* non-probing frames */
++ default:
++ nonprobing = 1;
++ break;
++ }
++
++ switch (frame.type) {
++
++ case NGX_QUIC_FT_ACK:
++ if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ continue;
++
++ case NGX_QUIC_FT_PADDING:
++ /* no action required */
++ continue;
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++ do_close = 1;
++ continue;
++ }
++
++ /* got there with ack-eliciting packet */
++ pkt->need_ack = 1;
++
++ switch (frame.type) {
++
++ case NGX_QUIC_FT_CRYPTO:
++
++ if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PING:
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++
++ if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++
++ if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAMS_BLOCKED:
++ case NGX_QUIC_FT_STREAMS_BLOCKED2:
++
++ if (ngx_quic_handle_streams_blocked_frame(c, pkt,
++ &frame.u.streams_blocked)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_DATA_BLOCKED:
++
++ if (ngx_quic_handle_data_blocked_frame(c, pkt,
++ &frame.u.data_blocked)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
++
++ if (ngx_quic_handle_stream_data_blocked_frame(c, pkt,
++ &frame.u.stream_data_blocked)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++
++ if (ngx_quic_handle_max_stream_data_frame(c, pkt,
++ &frame.u.max_stream_data)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RESET_STREAM:
++
++ if (ngx_quic_handle_reset_stream_frame(c, pkt,
++ &frame.u.reset_stream)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STOP_SENDING:
++
++ if (ngx_quic_handle_stop_sending_frame(c, pkt,
++ &frame.u.stop_sending)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++
++ if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++
++ if (ngx_quic_handle_path_challenge_frame(c, pkt,
++ &frame.u.path_challenge)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++
++ if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++
++ if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++
++ if (ngx_quic_handle_retire_connection_id_frame(c,
++ &frame.u.retire_cid)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ default:
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic missing frame handler");
++ return NGX_ERROR;
++ }
++ }
++
++ if (p != end) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic trailing garbage in payload:%ui bytes", end - p);
++
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (do_close) {
++ qc->draining = 1;
++ ngx_quic_close_connection(c, NGX_OK);
++ }
++
++ if (pkt->path != qc->path && nonprobing) {
++
++ /*
++ * RFC 9000, 9.2. Initiating Connection Migration
++ *
++ * An endpoint can migrate a connection to a new local
++ * address by sending packets containing non-probing frames
++ * from that address.
++ */
++ if (ngx_quic_handle_migration(c, pkt) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (ngx_quic_ack_packet(c, pkt) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_push_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler");
++
++ c = ev->data;
++
++ if (ngx_quic_output(c) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++void
++ngx_quic_shutdown_quic(ngx_connection_t *c)
++{
++ ngx_quic_connection_t *qc;
++
++ if (c->reusable) {
++ qc = ngx_quic_get_connection(c);
++ ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason);
++ }
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,131 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
++#define _NGX_EVENT_QUIC_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527
++
++#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
++#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
++#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32
++#define NGX_QUIC_SR_KEY_LEN 32
++#define NGX_QUIC_AV_KEY_LEN 32
++
++#define NGX_QUIC_SR_TOKEN_LEN 16
++
++#define NGX_QUIC_MIN_INITIAL_SIZE 1200
++
++#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01
++#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02
++
++
++typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c);
++typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c);
++
++
++typedef enum {
++ NGX_QUIC_STREAM_SEND_READY = 0,
++ NGX_QUIC_STREAM_SEND_SEND,
++ NGX_QUIC_STREAM_SEND_DATA_SENT,
++ NGX_QUIC_STREAM_SEND_DATA_RECVD,
++ NGX_QUIC_STREAM_SEND_RESET_SENT,
++ NGX_QUIC_STREAM_SEND_RESET_RECVD
++} ngx_quic_stream_send_state_e;
++
++
++typedef enum {
++ NGX_QUIC_STREAM_RECV_RECV = 0,
++ NGX_QUIC_STREAM_RECV_SIZE_KNOWN,
++ NGX_QUIC_STREAM_RECV_DATA_RECVD,
++ NGX_QUIC_STREAM_RECV_DATA_READ,
++ NGX_QUIC_STREAM_RECV_RESET_RECVD,
++ NGX_QUIC_STREAM_RECV_RESET_READ
++} ngx_quic_stream_recv_state_e;
++
++
++typedef struct {
++ uint64_t size;
++ uint64_t offset;
++ uint64_t last_offset;
++ ngx_chain_t *chain;
++ ngx_chain_t *last_chain;
++} ngx_quic_buffer_t;
++
++
++typedef struct {
++ ngx_ssl_t *ssl;
++
++ ngx_flag_t retry;
++ ngx_flag_t gso_enabled;
++ ngx_flag_t disable_active_migration;
++ ngx_msec_t timeout;
++ ngx_str_t host_key;
++ size_t mtu;
++ size_t stream_buffer_size;
++ ngx_uint_t max_concurrent_streams_bidi;
++ ngx_uint_t max_concurrent_streams_uni;
++ ngx_uint_t active_connection_id_limit;
++ ngx_int_t stream_close_code;
++ ngx_int_t stream_reject_code_uni;
++ ngx_int_t stream_reject_code_bidi;
++
++ ngx_quic_init_pt init;
++ ngx_quic_shutdown_pt shutdown;
++
++ u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
++ u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
++} ngx_quic_conf_t;
++
++
++struct ngx_quic_stream_s {
++ ngx_rbtree_node_t node;
++ ngx_queue_t queue;
++ ngx_connection_t *parent;
++ ngx_connection_t *connection;
++ uint64_t id;
++ uint64_t sent;
++ uint64_t acked;
++ uint64_t send_max_data;
++ uint64_t send_offset;
++ uint64_t send_final_size;
++ uint64_t recv_max_data;
++ uint64_t recv_offset;
++ uint64_t recv_window;
++ uint64_t recv_last;
++ uint64_t recv_final_size;
++ ngx_quic_buffer_t send;
++ ngx_quic_buffer_t recv;
++ ngx_quic_stream_send_state_e send_state;
++ ngx_quic_stream_recv_state_e recv_state;
++ unsigned cancelable:1;
++ unsigned fin_acked:1;
++};
++
++
++void ngx_quic_recvmsg(ngx_event_t *ev);
++void ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp,
++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
++void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf);
++ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi);
++void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason);
++void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason);
++ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err);
++ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how);
++void ngx_quic_cancelable_stream(ngx_connection_t *c);
++ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len,
++ ngx_str_t *dcid);
++ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label,
++ ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len);
++
++#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ack.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ack.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,1192 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_MAX_ACK_GAP 2
++
++/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */
++#define NGX_QUIC_PKT_THR 3 /* packets */
++/* RFC 9002, 6.1.2. Time Threshold: kGranularity */
++#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */
++
++/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */
++#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3
++
++
++/* send time of ACK'ed packets */
++typedef struct {
++ ngx_msec_t max_pn;
++ ngx_msec_t oldest;
++ ngx_msec_t newest;
++} ngx_quic_ack_stat_t;
++
++
++static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc);
++static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
++ enum ssl_encryption_level_t level, ngx_msec_t send_time);
++static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max,
++ ngx_quic_ack_stat_t *st);
++static void ngx_quic_drop_ack_ranges(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, uint64_t pn);
++static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c,
++ ngx_quic_ack_stat_t *st);
++static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c);
++static void ngx_quic_persistent_congestion(ngx_connection_t *c);
++static void ngx_quic_congestion_lost(ngx_connection_t *c,
++ ngx_quic_frame_t *frame);
++static void ngx_quic_lost_handler(ngx_event_t *ev);
++
++
++/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */
++static ngx_inline ngx_msec_t
++ngx_quic_lost_threshold(ngx_quic_connection_t *qc)
++{
++ ngx_msec_t thr;
++
++ thr = ngx_max(qc->latest_rtt, qc->avg_rtt);
++ thr += thr >> 3;
++
++ return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY);
++}
++
++
++ngx_int_t
++ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
++ ngx_quic_frame_t *f)
++{
++ ssize_t n;
++ u_char *pos, *end;
++ uint64_t min, max, gap, range;
++ ngx_uint_t i;
++ ngx_quic_ack_stat_t send_time;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_ack_frame_t *ack;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_handle_ack_frame level:%d", pkt->level);
++
++ ack = &f->u.ack;
++
++ /*
++ * RFC 9000, 19.3.1. ACK Ranges
++ *
++ * If any computed packet number is negative, an endpoint MUST
++ * generate a connection error of type FRAME_ENCODING_ERROR.
++ */
++
++ if (ack->first_range > ack->largest) {
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic invalid first range in ack frame");
++ return NGX_ERROR;
++ }
++
++ min = ack->largest - ack->first_range;
++ max = ack->largest;
++
++ send_time.oldest = NGX_TIMER_INFINITE;
++ send_time.newest = NGX_TIMER_INFINITE;
++
++ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ /* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */
++ if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) {
++ ctx->largest_ack = max;
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic updated largest received ack:%uL", max);
++
++ /*
++ * RFC 9002, 5.1. Generating RTT Samples
++ *
++ * An endpoint generates an RTT sample on receiving an
++ * ACK frame that meets the following two conditions:
++ *
++ * - the largest acknowledged packet number is newly acknowledged
++ * - at least one of the newly acknowledged packets was ack-eliciting.
++ */
++
++ if (send_time.max_pn != NGX_TIMER_INFINITE) {
++ ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn);
++ }
++ }
++
++ if (f->data) {
++ pos = f->data->buf->pos;
++ end = f->data->buf->last;
++
++ } else {
++ pos = NULL;
++ end = NULL;
++ }
++
++ for (i = 0; i < ack->range_count; i++) {
++
++ n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++ pos += n;
++
++ if (gap + 2 > min) {
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic invalid range:%ui in ack frame", i);
++ return NGX_ERROR;
++ }
++
++ max = min - gap - 2;
++
++ if (range > max) {
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic invalid range:%ui in ack frame", i);
++ return NGX_ERROR;
++ }
++
++ min = max - range;
++
++ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ return ngx_quic_detect_lost(c, &send_time);
++}
++
++
++static void
++ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
++ enum ssl_encryption_level_t level, ngx_msec_t send_time)
++{
++ ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ latest_rtt = ngx_current_msec - send_time;
++ qc->latest_rtt = latest_rtt;
++
++ if (qc->min_rtt == NGX_TIMER_INFINITE) {
++ qc->min_rtt = latest_rtt;
++ qc->avg_rtt = latest_rtt;
++ qc->rttvar = latest_rtt / 2;
++ qc->first_rtt = ngx_current_msec;
++
++ } else {
++ qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt);
++
++ ack_delay = (ack->delay << qc->ctp.ack_delay_exponent) / 1000;
++
++ if (c->ssl->handshaked) {
++ ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay);
++ }
++
++ adjusted_rtt = latest_rtt;
++
++ if (qc->min_rtt + ack_delay < latest_rtt) {
++ adjusted_rtt -= ack_delay;
++ }
++
++ qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3);
++ rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt));
++ qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2);
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic rtt sample latest:%M min:%M avg:%M var:%M",
++ latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar);
++}
++
++
++static ngx_int_t
++ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st)
++{
++ ngx_uint_t found;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ st->max_pn = NGX_TIMER_INFINITE;
++ found = 0;
++
++ q = ngx_queue_head(&ctx->sent);
++
++ while (q != ngx_queue_sentinel(&ctx->sent)) {
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ q = ngx_queue_next(q);
++
++ if (f->pnum > max) {
++ break;
++ }
++
++ if (f->pnum >= min) {
++ ngx_quic_congestion_ack(c, f);
++
++ switch (f->type) {
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++ ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest);
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ case NGX_QUIC_FT_RESET_STREAM:
++ ngx_quic_handle_stream_ack(c, f);
++ break;
++ }
++
++ if (f->pnum == max) {
++ st->max_pn = f->last;
++ }
++
++ /* save earliest and latest send times of frames ack'ed */
++ if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) {
++ st->oldest = f->last;
++ }
++
++ if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) {
++ st->newest = f->last;
++ }
++
++ ngx_queue_remove(&f->queue);
++ ngx_quic_free_frame(c, f);
++ found = 1;
++ }
++ }
++
++ if (!found) {
++
++ if (max < ctx->pnum) {
++ /* duplicate ACK or ACK for non-ack-eliciting frame */
++ return NGX_OK;
++ }
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic ACK for the packet not sent");
++
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_ftype = NGX_QUIC_FT_ACK;
++ qc->error_reason = "unknown packet number";
++
++ return NGX_ERROR;
++ }
++
++ if (!qc->push.timer_set) {
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++
++ qc->pto_count = 0;
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
++{
++ ngx_uint_t blocked;
++ ngx_msec_t timer;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ if (f->plen == 0) {
++ return;
++ }
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ blocked = (cg->in_flight >= cg->window) ? 1 : 0;
++
++ cg->in_flight -= f->plen;
++
++ timer = f->last - cg->recovery_start;
++
++ if ((ngx_msec_int_t) timer <= 0) {
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion ack recovery win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++ goto done;
++ }
++
++ if (cg->window < cg->ssthresh) {
++ cg->window += f->plen;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion slow start win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++ } else {
++ cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion avoidance win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++ }
++
++ /* prevent recovery_start from wrapping */
++
++ timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2;
++
++ if ((ngx_msec_int_t) timer < 0) {
++ cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2;
++ }
++
++done:
++
++ if (blocked && cg->in_flight < cg->window) {
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++}
++
++
++static void
++ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t pn)
++{
++ uint64_t base;
++ ngx_uint_t i, smallest, largest;
++ ngx_quic_ack_range_t *r;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL"
++ " fr:%uL nranges:%ui", pn, ctx->largest_range,
++ ctx->first_range, ctx->nranges);
++
++ base = ctx->largest_range;
++
++ if (base == NGX_QUIC_UNSET_PN) {
++ return;
++ }
++
++ if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) {
++ ctx->pending_ack = NGX_QUIC_UNSET_PN;
++ }
++
++ largest = base;
++ smallest = largest - ctx->first_range;
++
++ if (pn >= largest) {
++ ctx->largest_range = NGX_QUIC_UNSET_PN;
++ ctx->first_range = 0;
++ ctx->nranges = 0;
++ return;
++ }
++
++ if (pn >= smallest) {
++ ctx->first_range = largest - pn - 1;
++ ctx->nranges = 0;
++ return;
++ }
++
++ for (i = 0; i < ctx->nranges; i++) {
++ r = &ctx->ranges[i];
++
++ largest = smallest - r->gap - 2;
++ smallest = largest - r->range;
++
++ if (pn >= largest) {
++ ctx->nranges = i;
++ return;
++ }
++ if (pn >= smallest) {
++ r->range = largest - pn - 1;
++ ctx->nranges = i + 1;
++ return;
++ }
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st)
++{
++ ngx_uint_t i, nlost;
++ ngx_msec_t now, wait, thr, oldest, newest;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *start;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ now = ngx_current_msec;
++ thr = ngx_quic_lost_threshold(qc);
++
++ /* send time of lost packets across all send contexts */
++ oldest = NGX_TIMER_INFINITE;
++ newest = NGX_TIMER_INFINITE;
++
++ nlost = 0;
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++
++ ctx = &qc->send_ctx[i];
++
++ if (ctx->largest_ack == NGX_QUIC_UNSET_PN) {
++ continue;
++ }
++
++ while (!ngx_queue_empty(&ctx->sent)) {
++
++ q = ngx_queue_head(&ctx->sent);
++ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (start->pnum > ctx->largest_ack) {
++ break;
++ }
++
++ wait = start->last + thr - now;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic detect_lost pnum:%uL thr:%M wait:%i level:%d",
++ start->pnum, thr, (ngx_int_t) wait, start->level);
++
++ if ((ngx_msec_int_t) wait > 0
++ && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR)
++ {
++ break;
++ }
++
++ if (start->last > qc->first_rtt) {
++
++ if (oldest == NGX_TIMER_INFINITE || start->last < oldest) {
++ oldest = start->last;
++ }
++
++ if (newest == NGX_TIMER_INFINITE || start->last > newest) {
++ newest = start->last;
++ }
++
++ nlost++;
++ }
++
++ ngx_quic_resend_frames(c, ctx);
++ }
++ }
++
++
++ /* RFC 9002, 7.6.2. Establishing Persistent Congestion */
++
++ /*
++ * Once acknowledged, packets are no longer tracked. Thus no send time
++ * information is available for such packets. This limits persistent
++ * congestion algorithm to packets mentioned within ACK ranges of the
++ * latest ACK frame.
++ */
++
++ if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) {
++
++ if (newest - oldest > ngx_quic_pcg_duration(c)) {
++ ngx_quic_persistent_congestion(c);
++ }
++ }
++
++ ngx_quic_set_lost_timer(c);
++
++ return NGX_OK;
++}
++
++
++static ngx_msec_t
++ngx_quic_pcg_duration(ngx_connection_t *c)
++{
++ ngx_msec_t duration;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ duration = qc->avg_rtt;
++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
++ duration += qc->ctp.max_ack_delay;
++ duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR;
++
++ return duration;
++}
++
++
++static void
++ngx_quic_persistent_congestion(ngx_connection_t *c)
++{
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ cg->recovery_start = ngx_current_msec;
++ cg->window = qc->tp.max_udp_payload_size * 2;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic persistent congestion win:%uz", cg->window);
++}
++
++
++void
++ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f, *start;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ q = ngx_queue_head(&ctx->sent);
++ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic resend packet pnum:%uL", start->pnum);
++
++ ngx_quic_congestion_lost(c, start);
++
++ do {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->pnum != start->pnum) {
++ break;
++ }
++
++ q = ngx_queue_next(q);
++
++ ngx_queue_remove(&f->queue);
++
++ switch (f->type) {
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++ if (ctx->level == ssl_encryption_application) {
++ /* force generation of most recent acknowledgment */
++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
++ }
++
++ ngx_quic_free_frame(c, f);
++ break;
++
++ case NGX_QUIC_FT_PING:
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ ngx_quic_free_frame(c, f);
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++ f->u.max_data.max_data = qc->streams.recv_max_data;
++ ngx_quic_queue_frame(qc, f);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++ f->u.max_streams.limit = f->u.max_streams.bidi
++ ? qc->streams.client_max_streams_bidi
++ : qc->streams.client_max_streams_uni;
++ ngx_quic_queue_frame(qc, f);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++ qs = ngx_quic_find_stream(&qc->streams.tree,
++ f->u.max_stream_data.id);
++ if (qs == NULL) {
++ ngx_quic_free_frame(c, f);
++ break;
++ }
++
++ f->u.max_stream_data.limit = qs->recv_max_data;
++ ngx_quic_queue_frame(qc, f);
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
++
++ if (qs) {
++ if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT
++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD)
++ {
++ ngx_quic_free_frame(c, f);
++ break;
++ }
++ }
++
++ /* fall through */
++
++ default:
++ ngx_queue_insert_tail(&ctx->frames, &f->queue);
++ }
++
++ } while (q != ngx_queue_sentinel(&ctx->sent));
++
++ if (qc->closing) {
++ return;
++ }
++
++ ngx_post_event(&qc->push, &ngx_posted_events);
++}
++
++
++static void
++ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f)
++{
++ ngx_uint_t blocked;
++ ngx_msec_t timer;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ if (f->plen == 0) {
++ return;
++ }
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ blocked = (cg->in_flight >= cg->window) ? 1 : 0;
++
++ cg->in_flight -= f->plen;
++ f->plen = 0;
++
++ timer = f->last - cg->recovery_start;
++
++ if ((ngx_msec_int_t) timer <= 0) {
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion lost recovery win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++ goto done;
++ }
++
++ cg->recovery_start = ngx_current_msec;
++ cg->window /= 2;
++
++ if (cg->window < qc->tp.max_udp_payload_size * 2) {
++ cg->window = qc->tp.max_udp_payload_size * 2;
++ }
++
++ cg->ssthresh = cg->window;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion lost win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++done:
++
++ if (blocked && cg->in_flight < cg->window) {
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++}
++
++
++void
++ngx_quic_set_lost_timer(ngx_connection_t *c)
++{
++ ngx_uint_t i;
++ ngx_msec_t now;
++ ngx_queue_t *q;
++ ngx_msec_int_t lost, pto, w;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ now = ngx_current_msec;
++
++ lost = -1;
++ pto = -1;
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ctx = &qc->send_ctx[i];
++
++ if (ngx_queue_empty(&ctx->sent)) {
++ continue;
++ }
++
++ if (ctx->largest_ack != NGX_QUIC_UNSET_PN) {
++ q = ngx_queue_head(&ctx->sent);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now);
++
++ if (f->pnum <= ctx->largest_ack) {
++ if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) {
++ w = 0;
++ }
++
++ if (lost == -1 || w < lost) {
++ lost = w;
++ }
++ }
++ }
++
++ q = ngx_queue_last(&ctx->sent);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count)
++ - now);
++
++ if (w < 0) {
++ w = 0;
++ }
++
++ if (pto == -1 || w < pto) {
++ pto = w;
++ }
++ }
++
++ if (qc->pto.timer_set) {
++ ngx_del_timer(&qc->pto);
++ }
++
++ if (lost != -1) {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic lost timer lost:%M", lost);
++
++ qc->pto.handler = ngx_quic_lost_handler;
++ ngx_add_timer(&qc->pto, lost);
++ return;
++ }
++
++ if (pto != -1) {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic lost timer pto:%M", pto);
++
++ qc->pto.handler = ngx_quic_pto_handler;
++ ngx_add_timer(&qc->pto, pto);
++ return;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset");
++}
++
++
++ngx_msec_t
++ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_msec_t duration;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */
++
++ duration = qc->avg_rtt;
++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
++
++ if (ctx->level == ssl_encryption_application && c->ssl->handshaked) {
++ duration += qc->ctp.max_ack_delay;
++ }
++
++ return duration;
++}
++
++
++static
++void ngx_quic_lost_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer");
++
++ c = ev->data;
++
++ if (ngx_quic_detect_lost(c, NULL) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ }
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++void
++ngx_quic_pto_handler(ngx_event_t *ev)
++{
++ ngx_uint_t i;
++ ngx_msec_t now;
++ ngx_queue_t *q, *next;
++ ngx_connection_t *c;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer");
++
++ c = ev->data;
++ qc = ngx_quic_get_connection(c);
++ now = ngx_current_msec;
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++
++ ctx = &qc->send_ctx[i];
++
++ if (ngx_queue_empty(&ctx->sent)) {
++ continue;
++ }
++
++ q = ngx_queue_head(&ctx->sent);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->pnum <= ctx->largest_ack
++ && ctx->largest_ack != NGX_QUIC_UNSET_PN)
++ {
++ continue;
++ }
++
++ if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count)
++ - now) > 0)
++ {
++ continue;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic pto %s pto_count:%ui",
++ ngx_quic_level_name(ctx->level), qc->pto_count);
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ /* void */)
++ {
++ next = ngx_queue_next(q);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->type == NGX_QUIC_FT_PING) {
++ ngx_queue_remove(q);
++ ngx_quic_free_frame(c, f);
++ }
++
++ q = next;
++ }
++
++ for (q = ngx_queue_head(&ctx->sent);
++ q != ngx_queue_sentinel(&ctx->sent);
++ /* void */)
++ {
++ next = ngx_queue_next(q);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->type == NGX_QUIC_FT_PING) {
++ ngx_quic_congestion_lost(c, f);
++ ngx_queue_remove(q);
++ ngx_quic_free_frame(c, f);
++ }
++
++ q = next;
++ }
++
++ /* enforce 2 udp datagrams */
++
++ f = ngx_quic_alloc_frame(c);
++ if (f == NULL) {
++ break;
++ }
++
++ f->level = ctx->level;
++ f->type = NGX_QUIC_FT_PING;
++ f->flush = 1;
++
++ ngx_quic_queue_frame(qc, f);
++
++ f = ngx_quic_alloc_frame(c);
++ if (f == NULL) {
++ break;
++ }
++
++ f->level = ctx->level;
++ f->type = NGX_QUIC_FT_PING;
++
++ ngx_quic_queue_frame(qc, f);
++ }
++
++ qc->pto_count++;
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++ngx_int_t
++ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ uint64_t base, largest, smallest, gs, ge, gap, range, pn;
++ uint64_t prev_pending;
++ ngx_uint_t i, nr;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_ack_range_t *r;
++ ngx_quic_connection_t *qc;
++
++ c->log->action = "preparing ack";
++
++ qc = ngx_quic_get_connection(c);
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL"
++ " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range,
++ ctx->first_range, ctx->nranges);
++
++ prev_pending = ctx->pending_ack;
++
++ if (pkt->need_ack) {
++
++ ngx_post_event(&qc->push, &ngx_posted_events);
++
++ if (ctx->send_ack == 0) {
++ ctx->ack_delay_start = ngx_current_msec;
++ }
++
++ ctx->send_ack++;
++
++ if (ctx->pending_ack == NGX_QUIC_UNSET_PN
++ || ctx->pending_ack < pkt->pn)
++ {
++ ctx->pending_ack = pkt->pn;
++ }
++ }
++
++ base = ctx->largest_range;
++ pn = pkt->pn;
++
++ if (base == NGX_QUIC_UNSET_PN) {
++ ctx->largest_range = pn;
++ ctx->largest_received = pkt->received;
++ return NGX_OK;
++ }
++
++ if (base == pn) {
++ return NGX_OK;
++ }
++
++ largest = base;
++ smallest = largest - ctx->first_range;
++
++ if (pn > base) {
++
++ if (pn - base == 1) {
++ ctx->first_range++;
++ ctx->largest_range = pn;
++ ctx->largest_received = pkt->received;
++
++ return NGX_OK;
++
++ } else {
++ /* new gap in front of current largest */
++
++ /* no place for new range, send current range as is */
++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
++
++ if (prev_pending != NGX_QUIC_UNSET_PN) {
++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) {
++ ctx->pending_ack = NGX_QUIC_UNSET_PN;
++ }
++ }
++
++ gap = pn - base - 2;
++ range = ctx->first_range;
++
++ ctx->first_range = 0;
++ ctx->largest_range = pn;
++ ctx->largest_received = pkt->received;
++
++ /* packet is out of order, force send */
++ if (pkt->need_ack) {
++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
++ }
++
++ i = 0;
++
++ goto insert;
++ }
++ }
++
++ /* pn < base, perform lookup in existing ranges */
++
++ /* packet is out of order */
++ if (pkt->need_ack) {
++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
++ }
++
++ if (pn >= smallest && pn <= largest) {
++ return NGX_OK;
++ }
++
++#if (NGX_SUPPRESS_WARN)
++ r = NULL;
++#endif
++
++ for (i = 0; i < ctx->nranges; i++) {
++ r = &ctx->ranges[i];
++
++ ge = smallest - 1;
++ gs = ge - r->gap;
++
++ if (pn >= gs && pn <= ge) {
++
++ if (gs == ge) {
++ /* gap size is exactly one packet, now filled */
++
++ /* data moves to previous range, current is removed */
++
++ if (i == 0) {
++ ctx->first_range += r->range + 2;
++
++ } else {
++ ctx->ranges[i - 1].range += r->range + 2;
++ }
++
++ nr = ctx->nranges - i - 1;
++ if (nr) {
++ ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1],
++ sizeof(ngx_quic_ack_range_t) * nr);
++ }
++
++ ctx->nranges--;
++
++ } else if (pn == gs) {
++ /* current gap shrinks from tail (current range grows) */
++ r->gap--;
++ r->range++;
++
++ } else if (pn == ge) {
++ /* current gap shrinks from head (previous range grows) */
++ r->gap--;
++
++ if (i == 0) {
++ ctx->first_range++;
++
++ } else {
++ ctx->ranges[i - 1].range++;
++ }
++
++ } else {
++ /* current gap is split into two parts */
++
++ gap = ge - pn - 1;
++ range = 0;
++
++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
++ if (prev_pending != NGX_QUIC_UNSET_PN) {
++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) {
++ ctx->pending_ack = NGX_QUIC_UNSET_PN;
++ }
++ }
++
++ r->gap = pn - gs - 1;
++ goto insert;
++ }
++
++ return NGX_OK;
++ }
++
++ largest = smallest - r->gap - 2;
++ smallest = largest - r->range;
++
++ if (pn >= smallest && pn <= largest) {
++ /* this packet number is already known */
++ return NGX_OK;
++ }
++
++ }
++
++ if (pn == smallest - 1) {
++ /* extend first or last range */
++
++ if (i == 0) {
++ ctx->first_range++;
++
++ } else {
++ r->range++;
++ }
++
++ return NGX_OK;
++ }
++
++ /* nothing found, add new range at the tail */
++
++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
++ /* packet is too old to keep it */
++
++ if (pkt->need_ack) {
++ return ngx_quic_send_ack_range(c, ctx, pn, pn);
++ }
++
++ return NGX_OK;
++ }
++
++ gap = smallest - 2 - pn;
++ range = 0;
++
++insert:
++
++ if (ctx->nranges < NGX_QUIC_MAX_RANGES) {
++ ctx->nranges++;
++ }
++
++ ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i],
++ sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1));
++
++ ctx->ranges[i].gap = gap;
++ ctx->ranges[i].range = range;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_msec_t delay;
++ ngx_quic_connection_t *qc;
++
++ if (!ctx->send_ack) {
++ return NGX_OK;
++ }
++
++ if (ctx->level == ssl_encryption_application) {
++
++ delay = ngx_current_msec - ctx->ack_delay_start;
++ qc = ngx_quic_get_connection(c);
++
++ if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP
++ && delay < qc->tp.max_ack_delay)
++ {
++ if (!qc->push.timer_set && !qc->closing) {
++ ngx_add_timer(&qc->push,
++ qc->tp.max_ack_delay - delay);
++ }
++
++ return NGX_OK;
++ }
++ }
++
++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx->send_ack = 0;
++
++ return NGX_OK;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ack.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ack.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,30 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_
++#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_frame_t *f);
++
++void ngx_quic_congestion_ack(ngx_connection_t *c,
++ ngx_quic_frame_t *frame);
++void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
++void ngx_quic_set_lost_timer(ngx_connection_t *c);
++void ngx_quic_pto_handler(ngx_event_t *ev);
++ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
++
++ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx);
++
++#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_bpf.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_bpf.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,657 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS"
++#define NGX_QUIC_BPF_VARSEP ';'
++#define NGX_QUIC_BPF_ADDRSEP '#'
++
++
++#define ngx_quic_bpf_get_conf(cycle) \
++ (ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module)
++
++#define ngx_quic_bpf_get_old_conf(cycle) \
++ cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \
++ : NULL
++
++#define ngx_core_get_conf(cycle) \
++ (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module)
++
++
++typedef struct {
++ ngx_queue_t queue;
++ int map_fd;
++
++ struct sockaddr *sockaddr;
++ socklen_t socklen;
++ ngx_uint_t unused; /* unsigned unused:1; */
++} ngx_quic_sock_group_t;
++
++
++typedef struct {
++ ngx_flag_t enabled;
++ ngx_uint_t map_size;
++ ngx_queue_t groups; /* of ngx_quic_sock_group_t */
++} ngx_quic_bpf_conf_t;
++
++
++static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle);
++static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle);
++
++static void ngx_quic_bpf_cleanup(void *data);
++static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd,
++ const char *name);
++
++static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf,
++ ngx_listening_t *ls);
++static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle,
++ struct sockaddr *sa, socklen_t socklen);
++static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle,
++ ngx_listening_t *ls);
++static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle,
++ ngx_listening_t *ls);
++static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle,
++ ngx_listening_t *ls);
++static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log);
++
++static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle);
++static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle);
++
++extern ngx_bpf_program_t ngx_quic_reuseport_helper;
++
++
++static ngx_command_t ngx_quic_bpf_commands[] = {
++
++ { ngx_string("quic_bpf"),
++ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
++ ngx_conf_set_flag_slot,
++ 0,
++ offsetof(ngx_quic_bpf_conf_t, enabled),
++ NULL },
++
++ ngx_null_command
++};
++
++
++static ngx_core_module_t ngx_quic_bpf_module_ctx = {
++ ngx_string("quic_bpf"),
++ ngx_quic_bpf_create_conf,
++ NULL
++};
++
++
++ngx_module_t ngx_quic_bpf_module = {
++ NGX_MODULE_V1,
++ &ngx_quic_bpf_module_ctx, /* module context */
++ ngx_quic_bpf_commands, /* module directives */
++ NGX_CORE_MODULE, /* module type */
++ NULL, /* init master */
++ ngx_quic_bpf_module_init, /* init module */
++ NULL, /* init process */
++ NULL, /* init thread */
++ NULL, /* exit thread */
++ NULL, /* exit process */
++ NULL, /* exit master */
++ NGX_MODULE_V1_PADDING
++};
++
++
++static void *
++ngx_quic_bpf_create_conf(ngx_cycle_t *cycle)
++{
++ ngx_quic_bpf_conf_t *bcf;
++
++ bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t));
++ if (bcf == NULL) {
++ return NULL;
++ }
++
++ bcf->enabled = NGX_CONF_UNSET;
++ bcf->map_size = NGX_CONF_UNSET_UINT;
++
++ ngx_queue_init(&bcf->groups);
++
++ return bcf;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_module_init(ngx_cycle_t *cycle)
++{
++ ngx_uint_t i;
++ ngx_listening_t *ls;
++ ngx_core_conf_t *ccf;
++ ngx_pool_cleanup_t *cln;
++ ngx_quic_bpf_conf_t *bcf;
++
++ if (ngx_test_config) {
++ /*
++ * during config test, SO_REUSEPORT socket option is
++ * not set, thus making further processing meaningless
++ */
++ return NGX_OK;
++ }
++
++ ccf = ngx_core_get_conf(cycle);
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ ngx_conf_init_value(bcf->enabled, 0);
++
++ bcf->map_size = ccf->worker_processes * 4;
++
++ cln = ngx_pool_cleanup_add(cycle->pool, 0);
++ if (cln == NULL) {
++ goto failed;
++ }
++
++ cln->data = bcf;
++ cln->handler = ngx_quic_bpf_cleanup;
++
++ if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) {
++ if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) {
++ goto failed;
++ }
++ }
++
++ ls = cycle->listening.elts;
++
++ for (i = 0; i < cycle->listening.nelts; i++) {
++ if (ls[i].quic && ls[i].reuseport) {
++ if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) {
++ goto failed;
++ }
++ }
++ }
++
++ if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) {
++ goto failed;
++ }
++
++ return NGX_OK;
++
++failed:
++
++ if (ngx_is_init_cycle(cycle->old_cycle)) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "ngx_quic_bpf_module failed to initialize, check limits");
++
++ /* refuse to start */
++ return NGX_ERROR;
++ }
++
++ /*
++ * returning error now will lead to master process exiting immediately
++ * leaving worker processes orphaned, what is really unexpected.
++ * Instead, just issue a not about failed initialization and try
++ * to cleanup a bit. Still program can be already loaded to kernel
++ * for some reuseport groups, and there is no way to revert, so
++ * behaviour may be inconsistent.
++ */
++
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "ngx_quic_bpf_module failed to initialize properly, ignored."
++ "please check limits and note that nginx state now "
++ "can be inconsistent and restart may be required");
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_bpf_cleanup(void *data)
++{
++ ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data;
++
++ ngx_queue_t *q;
++ ngx_quic_sock_group_t *grp;
++
++ for (q = ngx_queue_head(&bcf->groups);
++ q != ngx_queue_sentinel(&bcf->groups);
++ q = ngx_queue_next(q))
++ {
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map");
++ }
++}
++
++
++static ngx_inline void
++ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name)
++{
++ if (close(fd) != -1) {
++ return;
++ }
++
++ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
++ "quic bpf close %s fd:%d failed", name, fd);
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls)
++{
++ ngx_queue_t *q;
++ ngx_quic_sock_group_t *grp;
++
++ for (q = ngx_queue_head(&bcf->groups);
++ q != ngx_queue_sentinel(&bcf->groups);
++ q = ngx_queue_next(q))
++ {
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen,
++ grp->sockaddr, grp->socklen, 1)
++ == NGX_OK)
++ {
++ return grp;
++ }
++ }
++
++ return NULL;
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa,
++ socklen_t socklen)
++{
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t));
++ if (grp == NULL) {
++ return NULL;
++ }
++
++ grp->socklen = socklen;
++ grp->sockaddr = ngx_palloc(cycle->pool, socklen);
++ if (grp->sockaddr == NULL) {
++ return NULL;
++ }
++ ngx_memcpy(grp->sockaddr, sa, socklen);
++
++ ngx_queue_insert_tail(&bcf->groups, &grp->queue);
++
++ return grp;
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
++{
++ int progfd, failed, flags, rc;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ if (!bcf->enabled) {
++ return NULL;
++ }
++
++ grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
++ if (grp == NULL) {
++ return NULL;
++ }
++
++ grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH,
++ sizeof(uint64_t), sizeof(uint64_t),
++ bcf->map_size, 0);
++ if (grp->map_fd == -1) {
++ goto failed;
++ }
++
++ flags = fcntl(grp->map_fd, F_GETFD);
++ if (flags == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
++ "quic bpf getfd failed");
++ goto failed;
++ }
++
++ /* need to inherit map during binary upgrade after exec */
++ flags &= ~FD_CLOEXEC;
++
++ rc = fcntl(grp->map_fd, F_SETFD, flags);
++ if (rc == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
++ "quic bpf setfd failed");
++ goto failed;
++ }
++
++ ngx_bpf_program_link(&ngx_quic_reuseport_helper,
++ "ngx_quic_sockmap", grp->map_fd);
++
++ progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper);
++ if (progfd < 0) {
++ goto failed;
++ }
++
++ failed = 0;
++
++ if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF,
++ &progfd, sizeof(int))
++ == -1)
++ {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
++ "quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed");
++ failed = 1;
++ }
++
++ ngx_quic_bpf_close(cycle->log, progfd, "program");
++
++ if (failed) {
++ goto failed;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap created fd:%d", grp->map_fd);
++ return grp;
++
++failed:
++
++ if (grp->map_fd != -1) {
++ ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
++ }
++
++ ngx_queue_remove(&grp->queue);
++
++ return NULL;
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
++{
++ ngx_quic_bpf_conf_t *bcf, *old_bcf;
++ ngx_quic_sock_group_t *grp, *ogrp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ grp = ngx_quic_bpf_find_group(bcf, ls);
++ if (grp) {
++ return grp;
++ }
++
++ old_bcf = ngx_quic_bpf_get_old_conf(cycle);
++
++ if (old_bcf == NULL) {
++ return ngx_quic_bpf_create_group(cycle, ls);
++ }
++
++ ogrp = ngx_quic_bpf_find_group(old_bcf, ls);
++ if (ogrp == NULL) {
++ return ngx_quic_bpf_create_group(cycle, ls);
++ }
++
++ grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
++ if (grp == NULL) {
++ return NULL;
++ }
++
++ grp->map_fd = dup(ogrp->map_fd);
++ if (grp->map_fd == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
++ "quic bpf failed to duplicate bpf map descriptor");
++
++ ngx_queue_remove(&grp->queue);
++
++ return NULL;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap fd duplicated old:%d new:%d",
++ ogrp->map_fd, grp->map_fd);
++
++ return grp;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls)
++{
++ uint64_t cookie;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ grp = ngx_quic_bpf_get_group(cycle, ls);
++
++ if (grp == NULL) {
++ if (!bcf->enabled) {
++ return NGX_OK;
++ }
++
++ return NGX_ERROR;
++ }
++
++ grp->unused = 0;
++
++ cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log);
++ if (cookie == (uint64_t) NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ /* map[cookie] = socket; for use in kernel helper */
++ if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
++ "quic bpf failed to update socket map key=%xL", cookie);
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui",
++ grp->map_fd, ls->fd, cookie, ls->worker);
++
++ /* do not inherit this socket */
++ ls->ignore = 1;
++
++ return NGX_OK;
++}
++
++
++static uint64_t
++ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log)
++{
++ uint64_t cookie;
++ socklen_t optlen;
++
++ optlen = sizeof(cookie);
++
++ if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
++ ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
++ "quic bpf getsockopt(SO_COOKIE) failed");
++
++ return (ngx_uint_t) NGX_ERROR;
++ }
++
++ return cookie;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_export_maps(ngx_cycle_t *cycle)
++{
++ u_char *p, *buf;
++ size_t len;
++ ngx_str_t *var;
++ ngx_queue_t *q;
++ ngx_core_conf_t *ccf;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ ccf = ngx_core_get_conf(cycle);
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ len = sizeof(NGX_QUIC_BPF_VARNAME) + 1;
++
++ q = ngx_queue_head(&bcf->groups);
++
++ while (q != ngx_queue_sentinel(&bcf->groups)) {
++
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ q = ngx_queue_next(q);
++
++ if (grp->unused) {
++ /*
++ * map was inherited, but it is not used in this configuration;
++ * do not pass such map further and drop the group to prevent
++ * interference with changes during reload
++ */
++
++ ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
++ ngx_queue_remove(&grp->queue);
++
++ continue;
++ }
++
++ len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1;
++ }
++
++ len++;
++
++ buf = ngx_palloc(cycle->pool, len);
++ if (buf == NULL) {
++ return NGX_ERROR;
++ }
++
++ p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=",
++ sizeof(NGX_QUIC_BPF_VARNAME));
++
++ for (q = ngx_queue_head(&bcf->groups);
++ q != ngx_queue_sentinel(&bcf->groups);
++ q = ngx_queue_next(q))
++ {
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ p = ngx_sprintf(p, "%ud", grp->map_fd);
++
++ *p++ = NGX_QUIC_BPF_ADDRSEP;
++
++ p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p,
++ NGX_SOCKADDR_STRLEN, 1);
++
++ *p++ = NGX_QUIC_BPF_VARSEP;
++ }
++
++ *p = '\0';
++
++ var = ngx_array_push(&ccf->env);
++ if (var == NULL) {
++ return NGX_ERROR;
++ }
++
++ var->data = buf;
++ var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_import_maps(ngx_cycle_t *cycle)
++{
++ int s;
++ u_char *inherited, *p, *v;
++ ngx_uint_t in_fd;
++ ngx_addr_t tmp;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME);
++
++ if (inherited == NULL) {
++ return NGX_OK;
++ }
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++#if (NGX_SUPPRESS_WARN)
++ s = -1;
++#endif
++
++ in_fd = 1;
++
++ for (p = inherited, v = p; *p; p++) {
++
++ switch (*p) {
++
++ case NGX_QUIC_BPF_ADDRSEP:
++
++ if (!in_fd) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited env");
++ return NGX_ERROR;
++ }
++ in_fd = 0;
++
++ s = ngx_atoi(v, p - v);
++ if (s == NGX_ERROR) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited map fd");
++ return NGX_ERROR;
++ }
++
++ v = p + 1;
++ break;
++
++ case NGX_QUIC_BPF_VARSEP:
++
++ if (in_fd) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited env");
++ return NGX_ERROR;
++ }
++ in_fd = 1;
++
++ grp = ngx_pcalloc(cycle->pool,
++ sizeof(ngx_quic_sock_group_t));
++ if (grp == NULL) {
++ return NGX_ERROR;
++ }
++
++ grp->map_fd = s;
++
++ if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v)
++ != NGX_OK)
++ {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited"
++ " address '%*s'", p - v , v);
++
++ ngx_quic_bpf_close(cycle->log, s, "inherited map");
++
++ return NGX_ERROR;
++ }
++
++ grp->sockaddr = tmp.sockaddr;
++ grp->socklen = tmp.socklen;
++
++ grp->unused = 1;
++
++ ngx_queue_insert_tail(&bcf->groups, &grp->queue);
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap inherited with "
++ "fd:%d address:%*s",
++ grp->map_fd, p - v, v);
++ v = p + 1;
++ break;
++
++ default:
++ break;
++ }
++ }
++
++ return NGX_OK;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_bpf_code.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_bpf_code.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,88 @@
++/* AUTO-GENERATED, DO NOT EDIT. */
++
++#include <stddef.h>
++#include <stdint.h>
++
++#include "ngx_bpf.h"
++
++
++static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = {
++ { "ngx_quic_sockmap", 55 },
++};
++
++static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = {
++ /* opcode dst src offset imm */
++ { 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 },
++ { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 },
++ { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 },
++ { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 },
++ { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 },
++ { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 },
++ { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 },
++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 },
++ { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
++ { 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
++ { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff },
++ { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd },
++ { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 },
++ { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe },
++ { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 },
++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe },
++ { 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 },
++ { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 },
++ { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 },
++ { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 },
++ { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 },
++ { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 },
++ { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 },
++ { 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 },
++ { 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 },
++ { 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 },
++ { 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 },
++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 },
++ { 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
++};
++
++
++ngx_bpf_program_t ngx_quic_reuseport_helper = {
++ .relocs = bpf_reloc_prog_ngx_quic_reuseport_helper,
++ .nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper)
++ / sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]),
++ .ins = bpf_insn_prog_ngx_quic_reuseport_helper,
++ .nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper)
++ / sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]),
++ .license = "BSD",
++ .type = BPF_PROG_TYPE_SK_REUSEPORT,
++};
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_connection.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connection.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,283 @@
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
++#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++
++
++/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */
++/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */
++/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */
++/* #define NGX_QUIC_DEBUG_CRYPTO */
++
++typedef struct ngx_quic_connection_s ngx_quic_connection_t;
++typedef struct ngx_quic_server_id_s ngx_quic_server_id_t;
++typedef struct ngx_quic_client_id_s ngx_quic_client_id_t;
++typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t;
++typedef struct ngx_quic_socket_s ngx_quic_socket_t;
++typedef struct ngx_quic_path_s ngx_quic_path_t;
++typedef struct ngx_quic_keys_s ngx_quic_keys_t;
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++#include <ngx_event_quic_openssl_compat.h>
++#endif
++#include <ngx_event_quic_transport.h>
++#include <ngx_event_quic_protection.h>
++#include <ngx_event_quic_frames.h>
++#include <ngx_event_quic_migration.h>
++#include <ngx_event_quic_connid.h>
++#include <ngx_event_quic_streams.h>
++#include <ngx_event_quic_ssl.h>
++#include <ngx_event_quic_tokens.h>
++#include <ngx_event_quic_ack.h>
++#include <ngx_event_quic_output.h>
++#include <ngx_event_quic_socket.h>
++
++
++/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */
++#define NGX_QUIC_INITIAL_RTT 333 /* ms */
++
++#define NGX_QUIC_UNSET_PN (uint64_t) -1
++
++#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1)
++
++/* 0-RTT and 1-RTT data exist in the same packet number space,
++ * so we have 3 packet number spaces:
++ *
++ * 0 - Initial
++ * 1 - Handshake
++ * 2 - 0-RTT and 1-RTT
++ */
++#define ngx_quic_get_send_ctx(qc, level) \
++ ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \
++ : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \
++ : &((qc)->send_ctx[2]))
++
++#define ngx_quic_get_connection(c) \
++ (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL)
++
++#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp))
++
++
++struct ngx_quic_client_id_s {
++ ngx_queue_t queue;
++ uint64_t seqnum;
++ size_t len;
++ u_char id[NGX_QUIC_CID_LEN_MAX];
++ u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
++ ngx_uint_t used; /* unsigned used:1; */
++};
++
++
++struct ngx_quic_server_id_s {
++ uint64_t seqnum;
++ size_t len;
++ u_char id[NGX_QUIC_CID_LEN_MAX];
++};
++
++
++struct ngx_quic_path_s {
++ ngx_queue_t queue;
++ struct sockaddr *sockaddr;
++ ngx_sockaddr_t sa;
++ socklen_t socklen;
++ ngx_quic_client_id_t *cid;
++ ngx_msec_t expires;
++ ngx_uint_t tries;
++ ngx_uint_t tag;
++ off_t sent;
++ off_t received;
++ u_char challenge1[8];
++ u_char challenge2[8];
++ uint64_t seqnum;
++ ngx_str_t addr_text;
++ u_char text[NGX_SOCKADDR_STRLEN];
++ unsigned validated:1;
++ unsigned validating:1;
++ unsigned limited:1;
++};
++
++
++struct ngx_quic_socket_s {
++ ngx_udp_connection_t udp;
++ ngx_quic_connection_t *quic;
++ ngx_queue_t queue;
++ ngx_quic_server_id_t sid;
++ ngx_sockaddr_t sockaddr;
++ socklen_t socklen;
++ ngx_uint_t used; /* unsigned used:1; */
++};
++
++
++typedef struct {
++ ngx_rbtree_t tree;
++ ngx_rbtree_node_t sentinel;
++
++ ngx_queue_t uninitialized;
++ ngx_queue_t free;
++
++ uint64_t sent;
++ uint64_t recv_offset;
++ uint64_t recv_window;
++ uint64_t recv_last;
++ uint64_t recv_max_data;
++ uint64_t send_offset;
++ uint64_t send_max_data;
++
++ uint64_t server_max_streams_uni;
++ uint64_t server_max_streams_bidi;
++ uint64_t server_streams_uni;
++ uint64_t server_streams_bidi;
++
++ uint64_t client_max_streams_uni;
++ uint64_t client_max_streams_bidi;
++ uint64_t client_streams_uni;
++ uint64_t client_streams_bidi;
++
++ ngx_uint_t initialized;
++ /* unsigned initialized:1; */
++} ngx_quic_streams_t;
++
++
++typedef struct {
++ size_t in_flight;
++ size_t window;
++ size_t ssthresh;
++ ngx_msec_t recovery_start;
++} ngx_quic_congestion_t;
++
++
++/*
++ * RFC 9000, 12.3. Packet Numbers
++ *
++ * Conceptually, a packet number space is the context in which a packet
++ * can be processed and acknowledged. Initial packets can only be sent
++ * with Initial packet protection keys and acknowledged in packets that
++ * are also Initial packets.
++ */
++struct ngx_quic_send_ctx_s {
++ enum ssl_encryption_level_t level;
++
++ ngx_quic_buffer_t crypto;
++ uint64_t crypto_sent;
++
++ uint64_t pnum; /* to be sent */
++ uint64_t largest_ack; /* received from peer */
++ uint64_t largest_pn; /* received from peer */
++
++ ngx_queue_t frames; /* generated frames */
++ ngx_queue_t sending; /* frames assigned to pkt */
++ ngx_queue_t sent; /* frames waiting ACK */
++
++ uint64_t pending_ack; /* non sent ack-eliciting */
++ uint64_t largest_range;
++ uint64_t first_range;
++ ngx_msec_t largest_received;
++ ngx_msec_t ack_delay_start;
++ ngx_uint_t nranges;
++ ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES];
++ ngx_uint_t send_ack;
++};
++
++
++struct ngx_quic_connection_s {
++ uint32_t version;
++
++ ngx_quic_path_t *path;
++
++ ngx_queue_t sockets;
++ ngx_queue_t paths;
++ ngx_queue_t client_ids;
++ ngx_queue_t free_sockets;
++ ngx_queue_t free_paths;
++ ngx_queue_t free_client_ids;
++
++ ngx_uint_t nsockets;
++ ngx_uint_t nclient_ids;
++ uint64_t max_retired_seqnum;
++ uint64_t client_seqnum;
++ uint64_t server_seqnum;
++ uint64_t path_seqnum;
++
++ ngx_quic_tp_t tp;
++ ngx_quic_tp_t ctp;
++
++ ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST];
++
++ ngx_quic_keys_t *keys;
++
++ ngx_quic_conf_t *conf;
++
++ ngx_event_t push;
++ ngx_event_t pto;
++ ngx_event_t close;
++ ngx_event_t path_validation;
++ ngx_msec_t last_cc;
++
++ ngx_msec_t first_rtt;
++ ngx_msec_t latest_rtt;
++ ngx_msec_t avg_rtt;
++ ngx_msec_t min_rtt;
++ ngx_msec_t rttvar;
++
++ ngx_uint_t pto_count;
++
++ ngx_queue_t free_frames;
++ ngx_buf_t *free_bufs;
++ ngx_buf_t *free_shadow_bufs;
++
++ ngx_uint_t nframes;
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_uint_t nbufs;
++ ngx_uint_t nshadowbufs;
++#endif
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++ ngx_quic_compat_t *compat;
++#endif
++
++ ngx_quic_streams_t streams;
++ ngx_quic_congestion_t congestion;
++
++ off_t received;
++
++ ngx_uint_t error;
++ enum ssl_encryption_level_t error_level;
++ ngx_uint_t error_ftype;
++ const char *error_reason;
++
++ ngx_uint_t shutdown_code;
++ const char *shutdown_reason;
++
++ unsigned error_app:1;
++ unsigned send_timer_set:1;
++ unsigned closing:1;
++ unsigned shutdown:1;
++ unsigned draining:1;
++ unsigned key_phase:1;
++ unsigned validated:1;
++ unsigned client_tp_done:1;
++};
++
++
++ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c,
++ ngx_quic_tp_t *ctp);
++void ngx_quic_discard_ctx(ngx_connection_t *c,
++ enum ssl_encryption_level_t level);
++void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc);
++void ngx_quic_shutdown_quic(ngx_connection_t *c);
++
++#if (NGX_DEBUG)
++void ngx_quic_connstate_dbg(ngx_connection_t *c);
++#else
++#define ngx_quic_connstate_dbg(c)
++#endif
++
++#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_connid.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connid.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,502 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++#define NGX_QUIC_MAX_SERVER_IDS 8
++
++
++#if (NGX_QUIC_BPF)
++static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id);
++#endif
++static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c,
++ ngx_quic_client_id_t *cid);
++static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c,
++ ngx_quic_connection_t *qc);
++static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c,
++ ngx_quic_server_id_t *sid);
++
++
++ngx_int_t
++ngx_quic_create_server_id(ngx_connection_t *c, u_char *id)
++{
++ if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) {
++ return NGX_ERROR;
++ }
++
++#if (NGX_QUIC_BPF)
++ if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) {
++ ngx_log_error(NGX_LOG_ERR, c->log, 0,
++ "quic bpf failed to generate socket key");
++ /* ignore error, things still may work */
++ }
++#endif
++
++ return NGX_OK;
++}
++
++
++#if (NGX_QUIC_BPF)
++
++static ngx_int_t
++ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id)
++{
++ int fd;
++ uint64_t cookie;
++ socklen_t optlen;
++
++ fd = c->listening->fd;
++
++ optlen = sizeof(cookie);
++
++ if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
++ ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno,
++ "quic getsockopt(SO_COOKIE) failed");
++
++ return NGX_ERROR;
++ }
++
++ ngx_quic_dcid_encode_key(id, cookie);
++
++ return NGX_OK;
++}
++
++#endif
++
++
++ngx_int_t
++ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_new_conn_id_frame_t *f)
++{
++ ngx_str_t id;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *frame;
++ ngx_quic_client_id_t *cid, *item;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (f->seqnum < qc->max_retired_seqnum) {
++ /*
++ * RFC 9000, 19.15. NEW_CONNECTION_ID Frame
++ *
++ * An endpoint that receives a NEW_CONNECTION_ID frame with
++ * a sequence number smaller than the Retire Prior To field
++ * of a previously received NEW_CONNECTION_ID frame MUST send
++ * a corresponding RETIRE_CONNECTION_ID frame that retires
++ * the newly received connection ID, unless it has already
++ * done so for that sequence number.
++ */
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
++ frame->u.retire_cid.sequence_number = f->seqnum;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ goto retire;
++ }
++
++ cid = NULL;
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ item = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (item->seqnum == f->seqnum) {
++ cid = item;
++ break;
++ }
++ }
++
++ if (cid) {
++ /*
++ * Transmission errors, timeouts, and retransmissions might cause the
++ * same NEW_CONNECTION_ID frame to be received multiple times.
++ */
++
++ if (cid->len != f->len
++ || ngx_strncmp(cid->id, f->cid, f->len) != 0
++ || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0)
++ {
++ /*
++ * ..if a sequence number is used for different connection IDs,
++ * the endpoint MAY treat that receipt as a connection error
++ * of type PROTOCOL_VIOLATION.
++ */
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_reason = "seqnum refers to different connection id/token";
++ return NGX_ERROR;
++ }
++
++ } else {
++
++ id.data = f->cid;
++ id.len = f->len;
++
++ if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) {
++ return NGX_ERROR;
++ }
++ }
++
++retire:
++
++ if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) {
++ /*
++ * Once a sender indicates a Retire Prior To value, smaller values sent
++ * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver
++ * MUST ignore any Retire Prior To fields that do not increase the
++ * largest received Retire Prior To value.
++ */
++ goto done;
++ }
++
++ qc->max_retired_seqnum = f->retire;
++
++ q = ngx_queue_head(&qc->client_ids);
++
++ while (q != ngx_queue_sentinel(&qc->client_ids)) {
++
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++ q = ngx_queue_next(q);
++
++ if (cid->seqnum >= f->retire) {
++ continue;
++ }
++
++ if (ngx_quic_retire_client_id(c, cid) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++done:
++
++ if (qc->nclient_ids > qc->tp.active_connection_id_limit) {
++ /*
++ * RFC 9000, 5.1.1. Issuing Connection IDs
++ *
++ * After processing a NEW_CONNECTION_ID frame and
++ * adding and retiring active connection IDs, if the number of active
++ * connection IDs exceeds the value advertised in its
++ * active_connection_id_limit transport parameter, an endpoint MUST
++ * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR.
++ */
++ qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
++ qc->error_reason = "too many connection ids received";
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
++{
++ ngx_queue_t *q;
++ ngx_quic_path_t *path;
++ ngx_quic_client_id_t *new_cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!cid->used) {
++ return ngx_quic_free_client_id(c, cid);
++ }
++
++ /* we are going to retire client id which is in use */
++
++ q = ngx_queue_head(&qc->paths);
++
++ while (q != ngx_queue_sentinel(&qc->paths)) {
++
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++ q = ngx_queue_next(q);
++
++ if (path->cid != cid) {
++ continue;
++ }
++
++ if (path == qc->path) {
++ /* this is the active path: update it with new CID */
++ new_cid = ngx_quic_next_client_id(c);
++ if (new_cid == NULL) {
++ return NGX_ERROR;
++ }
++
++ qc->path->cid = new_cid;
++ new_cid->used = 1;
++
++ return ngx_quic_free_client_id(c, cid);
++ }
++
++ return ngx_quic_free_path(c, path);
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_quic_client_id_t *
++ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc)
++{
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++
++ if (!ngx_queue_empty(&qc->free_client_ids)) {
++
++ q = ngx_queue_head(&qc->free_client_ids);
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ ngx_queue_remove(&cid->queue);
++
++ ngx_memzero(cid, sizeof(ngx_quic_client_id_t));
++
++ } else {
++
++ cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t));
++ if (cid == NULL) {
++ return NULL;
++ }
++ }
++
++ return cid;
++}
++
++
++ngx_quic_client_id_t *
++ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id,
++ uint64_t seqnum, u_char *token)
++{
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ cid = ngx_quic_alloc_client_id(c, qc);
++ if (cid == NULL) {
++ return NULL;
++ }
++
++ cid->seqnum = seqnum;
++
++ cid->len = id->len;
++ ngx_memcpy(cid->id, id->data, id->len);
++
++ if (token) {
++ ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN);
++ }
++
++ ngx_queue_insert_tail(&qc->client_ids, &cid->queue);
++ qc->nclient_ids++;
++
++ if (seqnum > qc->client_seqnum) {
++ qc->client_seqnum = seqnum;
++ }
++
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic cid seq:%uL received id:%uz:%xV:%*xs",
++ cid->seqnum, id->len, id,
++ (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token);
++
++ return cid;
++}
++
++
++ngx_quic_client_id_t *
++ngx_quic_next_client_id(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (!cid->used) {
++ return cid;
++ }
++ }
++
++ return NULL;
++}
++
++
++ngx_int_t
++ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_retire_cid_frame_t *f)
++{
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (f->sequence_number >= qc->server_seqnum) {
++ /*
++ * RFC 9000, 19.16.
++ *
++ * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence
++ * number greater than any previously sent to the peer MUST be
++ * treated as a connection error of type PROTOCOL_VIOLATION.
++ */
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_reason = "sequence number of id to retire was never issued";
++
++ return NGX_ERROR;
++ }
++
++ qsock = ngx_quic_get_socket(c);
++
++ if (qsock->sid.seqnum == f->sequence_number) {
++
++ /*
++ * RFC 9000, 19.16.
++ *
++ * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST
++ * NOT refer to the Destination Connection ID field of the packet in
++ * which the frame is contained. The peer MAY treat this as a
++ * connection error of type PROTOCOL_VIOLATION.
++ */
++
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_reason = "sequence number of id to retire refers DCID";
++
++ return NGX_ERROR;
++ }
++
++ qsock = ngx_quic_find_socket(c, f->sequence_number);
++ if (qsock == NULL) {
++ return NGX_OK;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic socket seq:%uL is retired", qsock->sid.seqnum);
++
++ ngx_quic_close_socket(c, qsock);
++
++ /* restore socket count up to a limit after deletion */
++ if (ngx_quic_create_sockets(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_create_sockets(ngx_connection_t *c)
++{
++ ngx_uint_t n;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic create sockets has:%ui max:%ui", qc->nsockets, n);
++
++ while (qc->nsockets < n) {
++
++ qsock = ngx_quic_create_socket(c, qc);
++ if (qsock == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid)
++{
++ ngx_str_t dcid;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ dcid.len = sid->len;
++ dcid.data = sid->id;
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID;
++ frame->u.ncid.seqnum = sid->seqnum;
++ frame->u.ncid.retire = 0;
++ frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN;
++ ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN);
++
++ if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key,
++ frame->u.ncid.srt)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
++{
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
++ frame->u.retire_cid.sequence_number = cid->seqnum;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ /* we are no longer going to use this client id */
++
++ ngx_queue_remove(&cid->queue);
++ ngx_queue_insert_head(&qc->free_client_ids, &cid->queue);
++
++ qc->nclient_ids--;
++
++ return NGX_OK;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_connid.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connid.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,29 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
++#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_retire_cid_frame_t *f);
++ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_new_conn_id_frame_t *f);
++
++ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c);
++ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id);
++
++ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c,
++ ngx_str_t *id, uint64_t seqnum, u_char *token);
++ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c);
++ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c,
++ ngx_quic_client_id_t *cid);
++
++#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_frames.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_frames.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,894 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_BUFFER_SIZE 4096
++
++#define ngx_quic_buf_refs(b) (b)->shadow->num
++#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++
++#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)--
++#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v
++
++
++static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c);
++static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b);
++static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b);
++static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl,
++ off_t offset);
++
++
++static ngx_buf_t *
++ngx_quic_alloc_buf(ngx_connection_t *c)
++{
++ u_char *p;
++ ngx_buf_t *b;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ b = qc->free_bufs;
++
++ if (b) {
++ qc->free_bufs = b->shadow;
++ p = b->start;
++
++ } else {
++ b = qc->free_shadow_bufs;
++
++ if (b) {
++ qc->free_shadow_bufs = b->shadow;
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic use shadow buffer n:%ui %ui",
++ ++qc->nbufs, --qc->nshadowbufs);
++#endif
++
++ } else {
++ b = ngx_palloc(c->pool, sizeof(ngx_buf_t));
++ if (b == NULL) {
++ return NULL;
++ }
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic new buffer n:%ui", ++qc->nbufs);
++#endif
++ }
++
++ p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE);
++ if (p == NULL) {
++ return NULL;
++ }
++ }
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b);
++#endif
++
++ ngx_memzero(b, sizeof(ngx_buf_t));
++
++ b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf;
++ b->temporary = 1;
++ b->shadow = b;
++
++ b->start = p;
++ b->pos = p;
++ b->last = p;
++ b->end = p + NGX_QUIC_BUFFER_SIZE;
++
++ ngx_quic_buf_set_refs(b, 1);
++
++ return b;
++}
++
++
++static void
++ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b)
++{
++ ngx_buf_t *shadow;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_quic_buf_dec_refs(b);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic free buffer %p r:%ui",
++ b, (ngx_uint_t) ngx_quic_buf_refs(b));
++#endif
++
++ shadow = b->shadow;
++
++ if (ngx_quic_buf_refs(b) == 0) {
++ shadow->shadow = qc->free_bufs;
++ qc->free_bufs = shadow;
++ }
++
++ if (b != shadow) {
++ b->shadow = qc->free_shadow_bufs;
++ qc->free_shadow_bufs = b;
++ }
++
++}
++
++
++static ngx_buf_t *
++ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b)
++{
++ ngx_buf_t *nb;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ nb = qc->free_shadow_bufs;
++
++ if (nb) {
++ qc->free_shadow_bufs = nb->shadow;
++
++ } else {
++ nb = ngx_palloc(c->pool, sizeof(ngx_buf_t));
++ if (nb == NULL) {
++ return NULL;
++ }
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic new shadow buffer n:%ui", ++qc->nshadowbufs);
++#endif
++ }
++
++ *nb = *b;
++
++ ngx_quic_buf_inc_refs(b);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic clone buffer %p %p r:%ui",
++ b, nb, (ngx_uint_t) ngx_quic_buf_refs(b));
++#endif
++
++ return nb;
++}
++
++
++static ngx_int_t
++ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset)
++{
++ ngx_buf_t *b, *tb;
++ ngx_chain_t *tail;
++
++ b = cl->buf;
++
++ tail = ngx_alloc_chain_link(c->pool);
++ if (tail == NULL) {
++ return NGX_ERROR;
++ }
++
++ tb = ngx_quic_clone_buf(c, b);
++ if (tb == NULL) {
++ return NGX_ERROR;
++ }
++
++ tail->buf = tb;
++
++ tb->pos += offset;
++
++ b->last = tb->pos;
++ b->last_buf = 0;
++
++ tail->next = cl->next;
++ cl->next = tail;
++
++ return NGX_OK;
++}
++
++
++ngx_quic_frame_t *
++ngx_quic_alloc_frame(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!ngx_queue_empty(&qc->free_frames)) {
++
++ q = ngx_queue_head(&qc->free_frames);
++ frame = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_queue_remove(&frame->queue);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic reuse frame n:%ui", qc->nframes);
++#endif
++
++ } else if (qc->nframes < 10000) {
++ frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t));
++ if (frame == NULL) {
++ return NULL;
++ }
++
++ ++qc->nframes;
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic alloc frame n:%ui", qc->nframes);
++#endif
++
++ } else {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected");
++ return NULL;
++ }
++
++ ngx_memzero(frame, sizeof(ngx_quic_frame_t));
++
++ return frame;
++}
++
++
++void
++ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (frame->data) {
++ ngx_quic_free_chain(c, frame->data);
++ }
++
++ ngx_queue_insert_head(&qc->free_frames, &frame->queue);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic free frame n:%ui", qc->nframes);
++#endif
++}
++
++
++void
++ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in)
++{
++ ngx_chain_t *cl;
++
++ while (in) {
++ cl = in;
++ in = in->next;
++
++ ngx_quic_free_buf(c, cl->buf);
++ ngx_free_chain(c->pool, cl);
++ }
++}
++
++
++void
++ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++
++ do {
++ q = ngx_queue_head(frames);
++
++ if (q == ngx_queue_sentinel(frames)) {
++ break;
++ }
++
++ ngx_queue_remove(q);
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_quic_free_frame(c, f);
++ } while (1);
++}
++
++
++void
++ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame)
++{
++ ngx_quic_send_ctx_t *ctx;
++
++ ctx = ngx_quic_get_send_ctx(qc, frame->level);
++
++ ngx_queue_insert_tail(&ctx->frames, &frame->queue);
++
++ frame->len = ngx_quic_create_frame(NULL, frame);
++ /* always succeeds */
++
++ if (qc->closing) {
++ return;
++ }
++
++ ngx_post_event(&qc->push, &ngx_posted_events);
++}
++
++
++ngx_int_t
++ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len)
++{
++ size_t shrink;
++ ngx_chain_t *out;
++ ngx_quic_frame_t *nf;
++ ngx_quic_buffer_t qb;
++ ngx_quic_ordered_frame_t *of, *onf;
++
++ switch (f->type) {
++ case NGX_QUIC_FT_CRYPTO:
++ case NGX_QUIC_FT_STREAM:
++ break;
++
++ default:
++ return NGX_DECLINED;
++ }
++
++ if ((size_t) f->len <= len) {
++ return NGX_OK;
++ }
++
++ shrink = f->len - len;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic split frame now:%uz need:%uz shrink:%uz",
++ f->len, len, shrink);
++
++ of = &f->u.ord;
++
++ if (of->length <= shrink) {
++ return NGX_DECLINED;
++ }
++
++ of->length -= shrink;
++ f->len = ngx_quic_create_frame(NULL, f);
++
++ if ((size_t) f->len > len) {
++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame");
++ return NGX_ERROR;
++ }
++
++ ngx_memzero(&qb, sizeof(ngx_quic_buffer_t));
++ qb.chain = f->data;
++
++ out = ngx_quic_read_buffer(c, &qb, of->length);
++ if (out == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ f->data = out;
++
++ nf = ngx_quic_alloc_frame(c);
++ if (nf == NULL) {
++ return NGX_ERROR;
++ }
++
++ *nf = *f;
++ onf = &nf->u.ord;
++ onf->offset += of->length;
++ onf->length = shrink;
++ nf->len = ngx_quic_create_frame(NULL, nf);
++ nf->data = qb.chain;
++
++ if (f->type == NGX_QUIC_FT_STREAM) {
++ f->u.stream.fin = 0;
++ }
++
++ ngx_queue_insert_after(&f->queue, &nf->queue);
++
++ return NGX_OK;
++}
++
++
++ngx_chain_t *
++ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, size_t len)
++{
++ ngx_buf_t buf;
++ ngx_chain_t cl, *out;
++ ngx_quic_buffer_t qb;
++
++ ngx_memzero(&buf, sizeof(ngx_buf_t));
++
++ buf.pos = data;
++ buf.last = buf.pos + len;
++ buf.temporary = 1;
++
++ cl.buf = &buf;
++ cl.next = NULL;
++
++ ngx_memzero(&qb, sizeof(ngx_quic_buffer_t));
++
++ if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ out = ngx_quic_read_buffer(c, &qb, len);
++ if (out == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ ngx_quic_free_buffer(c, &qb);
++
++ return out;
++}
++
++
++ngx_chain_t *
++ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit)
++{
++ uint64_t n;
++ ngx_buf_t *b;
++ ngx_chain_t *out, **ll;
++
++ out = qb->chain;
++
++ for (ll = &out; *ll; ll = &(*ll)->next) {
++ b = (*ll)->buf;
++
++ if (b->sync) {
++ /* hole */
++ break;
++ }
++
++ if (limit == 0) {
++ break;
++ }
++
++ n = b->last - b->pos;
++
++ if (n > limit) {
++ if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ n = limit;
++ }
++
++ limit -= n;
++ qb->offset += n;
++ }
++
++ if (qb->offset >= qb->last_offset) {
++ qb->last_chain = NULL;
++ }
++
++ qb->chain = *ll;
++ *ll = NULL;
++
++ return out;
++}
++
++
++void
++ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
++ uint64_t offset)
++{
++ size_t n;
++ ngx_buf_t *b;
++ ngx_chain_t *cl;
++
++ while (qb->chain) {
++ if (qb->offset >= offset) {
++ break;
++ }
++
++ cl = qb->chain;
++ b = cl->buf;
++ n = b->last - b->pos;
++
++ if (qb->offset + n > offset) {
++ n = offset - qb->offset;
++ b->pos += n;
++ qb->offset += n;
++ break;
++ }
++
++ qb->offset += n;
++ qb->chain = cl->next;
++
++ cl->next = NULL;
++ ngx_quic_free_chain(c, cl);
++ }
++
++ if (qb->chain == NULL) {
++ qb->offset = offset;
++ }
++
++ if (qb->offset >= qb->last_offset) {
++ qb->last_chain = NULL;
++ }
++}
++
++
++ngx_chain_t *
++ngx_quic_alloc_chain(ngx_connection_t *c)
++{
++ ngx_chain_t *cl;
++
++ cl = ngx_alloc_chain_link(c->pool);
++ if (cl == NULL) {
++ return NULL;
++ }
++
++ cl->buf = ngx_quic_alloc_buf(c);
++ if (cl->buf == NULL) {
++ return NULL;
++ }
++
++ return cl;
++}
++
++
++ngx_chain_t *
++ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
++ ngx_chain_t *in, uint64_t limit, uint64_t offset)
++{
++ u_char *p;
++ uint64_t n, base;
++ ngx_buf_t *b;
++ ngx_chain_t *cl, **chain;
++
++ if (qb->last_chain && offset >= qb->last_offset) {
++ base = qb->last_offset;
++ chain = &qb->last_chain;
++
++ } else {
++ base = qb->offset;
++ chain = &qb->chain;
++ }
++
++ while (in && limit) {
++
++ if (offset < base) {
++ n = ngx_min((uint64_t) (in->buf->last - in->buf->pos),
++ ngx_min(base - offset, limit));
++
++ in->buf->pos += n;
++ offset += n;
++ limit -= n;
++
++ if (in->buf->pos == in->buf->last) {
++ in = in->next;
++ }
++
++ continue;
++ }
++
++ cl = *chain;
++
++ if (cl == NULL) {
++ cl = ngx_quic_alloc_chain(c);
++ if (cl == NULL) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ cl->buf->last = cl->buf->end;
++ cl->buf->sync = 1; /* hole */
++ cl->next = NULL;
++ *chain = cl;
++ }
++
++ b = cl->buf;
++ n = b->last - b->pos;
++
++ if (base + n <= offset) {
++ base += n;
++ chain = &cl->next;
++ continue;
++ }
++
++ if (b->sync && offset > base) {
++ if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ continue;
++ }
++
++ p = b->pos + (offset - base);
++
++ while (in) {
++
++ if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) {
++ in = in->next;
++ continue;
++ }
++
++ if (p == b->last || limit == 0) {
++ break;
++ }
++
++ n = ngx_min(b->last - p, in->buf->last - in->buf->pos);
++ n = ngx_min(n, limit);
++
++ if (b->sync) {
++ ngx_memcpy(p, in->buf->pos, n);
++ qb->size += n;
++ }
++
++ p += n;
++ in->buf->pos += n;
++ offset += n;
++ limit -= n;
++ }
++
++ if (b->sync && p == b->last) {
++ b->sync = 0;
++ continue;
++ }
++
++ if (b->sync && p != b->pos) {
++ if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ b->sync = 0;
++ }
++ }
++
++ qb->last_offset = base;
++ qb->last_chain = *chain;
++
++ return in;
++}
++
++
++void
++ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb)
++{
++ ngx_quic_free_chain(c, qb->chain);
++
++ qb->chain = NULL;
++}
++
++
++#if (NGX_DEBUG)
++
++void
++ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx)
++{
++ u_char *p, *last, *pos, *end;
++ ssize_t n;
++ uint64_t gap, range, largest, smallest;
++ ngx_uint_t i;
++ u_char buf[NGX_MAX_ERROR_STR];
++
++ p = buf;
++ last = buf + sizeof(buf);
++
++ switch (f->type) {
++
++ case NGX_QUIC_FT_CRYPTO:
++ p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL",
++ f->u.crypto.length, f->u.crypto.offset);
++
++#ifdef NGX_QUIC_DEBUG_FRAMES
++ {
++ ngx_chain_t *cl;
++
++ p = ngx_slprintf(p, last, " data:");
++
++ for (cl = f->data; cl; cl = cl->next) {
++ p = ngx_slprintf(p, last, "%*xs",
++ cl->buf->last - cl->buf->pos, cl->buf->pos);
++ }
++ }
++#endif
++
++ break;
++
++ case NGX_QUIC_FT_PADDING:
++ p = ngx_slprintf(p, last, "PADDING");
++ break;
++
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++
++ p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ",
++ f->u.ack.range_count, f->u.ack.delay);
++
++ if (f->data) {
++ pos = f->data->buf->pos;
++ end = f->data->buf->last;
++
++ } else {
++ pos = NULL;
++ end = NULL;
++ }
++
++ largest = f->u.ack.largest;
++ smallest = f->u.ack.largest - f->u.ack.first_range;
++
++ if (largest == smallest) {
++ p = ngx_slprintf(p, last, "%uL", largest);
++
++ } else {
++ p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest);
++ }
++
++ for (i = 0; i < f->u.ack.range_count; i++) {
++ n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range);
++ if (n == NGX_ERROR) {
++ break;
++ }
++
++ pos += n;
++
++ largest = smallest - gap - 2;
++ smallest = largest - range;
++
++ if (largest == smallest) {
++ p = ngx_slprintf(p, last, " %uL", largest);
++
++ } else {
++ p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest);
++ }
++ }
++
++ if (f->type == NGX_QUIC_FT_ACK_ECN) {
++ p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL",
++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
++ }
++ break;
++
++ case NGX_QUIC_FT_PING:
++ p = ngx_slprintf(p, last, "PING");
++ break;
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++ p = ngx_slprintf(p, last,
++ "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud",
++ f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len);
++ break;
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++ p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL",
++ f->u.retire_cid.sequence_number);
++ break;
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++ p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui",
++ f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP",
++ f->u.close.error_code);
++
++ if (f->u.close.reason.len) {
++ p = ngx_slprintf(p, last, " %V", &f->u.close.reason);
++ }
++
++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
++ p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type);
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id);
++
++ if (f->u.stream.off) {
++ p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset);
++ }
++
++ if (f->u.stream.len) {
++ p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length);
++ }
++
++ if (f->u.stream.fin) {
++ p = ngx_slprintf(p, last, " fin:1");
++ }
++
++#ifdef NGX_QUIC_DEBUG_FRAMES
++ {
++ ngx_chain_t *cl;
++
++ p = ngx_slprintf(p, last, " data:");
++
++ for (cl = f->data; cl; cl = cl->next) {
++ p = ngx_slprintf(p, last, "%*xs",
++ cl->buf->last - cl->buf->pos, cl->buf->pos);
++ }
++ }
++#endif
++
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++ p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv",
++ f->u.max_data.max_data);
++ break;
++
++ case NGX_QUIC_FT_RESET_STREAM:
++ p = ngx_slprintf(p, last, "RESET_STREAM"
++ " id:0x%xL error_code:0x%xL final_size:0x%xL",
++ f->u.reset_stream.id, f->u.reset_stream.error_code,
++ f->u.reset_stream.final_size);
++ break;
++
++ case NGX_QUIC_FT_STOP_SENDING:
++ p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL",
++ f->u.stop_sending.id, f->u.stop_sending.error_code);
++ break;
++
++ case NGX_QUIC_FT_STREAMS_BLOCKED:
++ case NGX_QUIC_FT_STREAMS_BLOCKED2:
++ p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui",
++ f->u.streams_blocked.limit, f->u.streams_blocked.bidi);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++ p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui",
++ f->u.max_streams.limit, f->u.max_streams.bidi);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++ p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL",
++ f->u.max_stream_data.id, f->u.max_stream_data.limit);
++ break;
++
++
++ case NGX_QUIC_FT_DATA_BLOCKED:
++ p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL",
++ f->u.data_blocked.limit);
++ break;
++
++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
++ p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL",
++ f->u.stream_data_blocked.id,
++ f->u.stream_data_blocked.limit);
++ break;
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++ p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs",
++ sizeof(f->u.path_challenge.data),
++ f->u.path_challenge.data);
++ break;
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs",
++ sizeof(f->u.path_challenge.data),
++ f->u.path_challenge.data);
++ break;
++
++ case NGX_QUIC_FT_NEW_TOKEN:
++ p = ngx_slprintf(p, last, "NEW_TOKEN");
++
++#ifdef NGX_QUIC_DEBUG_FRAMES
++ {
++ ngx_chain_t *cl;
++
++ p = ngx_slprintf(p, last, " token:");
++
++ for (cl = f->data; cl; cl = cl->next) {
++ p = ngx_slprintf(p, last, "%*xs",
++ cl->buf->last - cl->buf->pos, cl->buf->pos);
++ }
++ }
++#endif
++
++ break;
++
++ case NGX_QUIC_FT_HANDSHAKE_DONE:
++ p = ngx_slprintf(p, last, "HANDSHAKE DONE");
++ break;
++
++ default:
++ p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type);
++ break;
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s",
++ tx ? "tx" : "rx", ngx_quic_level_name(f->level),
++ p - buf, buf);
++}
++
++#endif
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_frames.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_frames.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,45 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
++#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c,
++ ngx_quic_frame_t *frame, void *data);
++
++
++ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c);
++void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame);
++void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames);
++void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame);
++ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f,
++ size_t len);
++
++ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c);
++void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in);
++
++ngx_chain_t *ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data,
++ size_t len);
++ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
++ uint64_t limit);
++ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
++ ngx_chain_t *in, uint64_t limit, uint64_t offset);
++void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb,
++ uint64_t offset);
++void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb);
++
++#if (NGX_DEBUG)
++void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx);
++#else
++#define ngx_quic_log_frame(log, f, tx)
++#endif
++
++#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_migration.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_migration.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,714 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++static void ngx_quic_set_connection_path(ngx_connection_t *c,
++ ngx_quic_path_t *path);
++static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
++ ngx_quic_path_t *path);
++static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
++ ngx_quic_path_t *path);
++static void ngx_quic_set_path_timer(ngx_connection_t *c);
++static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
++
++
++ngx_int_t
++ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
++{
++ ngx_quic_frame_t frame, *fp;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++
++ frame.level = ssl_encryption_application;
++ frame.type = NGX_QUIC_FT_PATH_RESPONSE;
++ frame.u.path_response = *f;
++
++ /*
++ * RFC 9000, 8.2.2. Path Validation Responses
++ *
++ * A PATH_RESPONSE frame MUST be sent on the network path where the
++ * PATH_CHALLENGE frame was received.
++ */
++
++ /*
++ * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
++ * to at least the smallest allowed maximum datagram size of 1200 bytes.
++ */
++ if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (pkt->path == qc->path) {
++ /*
++ * RFC 9000, 9.3.3. Off-Path Packet Forwarding
++ *
++ * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD
++ * send a non-probing packet in response.
++ */
++
++ fp = ngx_quic_alloc_frame(c);
++ if (fp == NULL) {
++ return NGX_ERROR;
++ }
++
++ fp->level = ssl_encryption_application;
++ fp->type = NGX_QUIC_FT_PING;
++
++ ngx_quic_queue_frame(qc, fp);
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_path_response_frame(ngx_connection_t *c,
++ ngx_quic_path_challenge_frame_t *f)
++{
++ ngx_uint_t rst;
++ ngx_queue_t *q;
++ ngx_quic_path_t *path, *prev;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /*
++ * RFC 9000, 8.2.3. Successful Path Validation
++ *
++ * A PATH_RESPONSE frame received on any network path validates the path
++ * on which the PATH_CHALLENGE was sent.
++ */
++
++ for (q = ngx_queue_head(&qc->paths);
++ q != ngx_queue_sentinel(&qc->paths);
++ q = ngx_queue_next(q))
++ {
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ if (!path->validating) {
++ continue;
++ }
++
++ if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0
++ || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0)
++ {
++ goto valid;
++ }
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stale PATH_RESPONSE ignored");
++
++ return NGX_OK;
++
++valid:
++
++ /*
++ * RFC 9000, 9.4. Loss Detection and Congestion Control
++ *
++ * On confirming a peer's ownership of its new address,
++ * an endpoint MUST immediately reset the congestion controller
++ * and round-trip time estimator for the new path to initial values
++ * unless the only change in the peer's address is its port number.
++ */
++
++ rst = 1;
++
++ prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
++
++ if (prev != NULL) {
++
++ if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen,
++ path->sockaddr, path->socklen, 0)
++ == NGX_OK)
++ {
++ /* address did not change */
++ rst = 0;
++ }
++ }
++
++ if (rst) {
++ ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t));
++
++ qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
++ ngx_max(2 * qc->tp.max_udp_payload_size,
++ 14720));
++ qc->congestion.ssthresh = (size_t) -1;
++ qc->congestion.recovery_start = ngx_current_msec;
++ }
++
++ /*
++ * RFC 9000, 9.3. Responding to Connection Migration
++ *
++ * After verifying a new client address, the server SHOULD
++ * send new address validation tokens (Section 8) to the client.
++ */
++
++ if (ngx_quic_send_new_token(c, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic path seq:%uL addr:%V successfully validated",
++ path->seqnum, &path->addr_text);
++
++ ngx_quic_path_dbg(c, "is validated", path);
++
++ path->validated = 1;
++ path->validating = 0;
++ path->limited = 0;
++
++ ngx_quic_set_path_timer(c);
++
++ return NGX_OK;
++}
++
++
++ngx_quic_path_t *
++ngx_quic_new_path(ngx_connection_t *c,
++ struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid)
++{
++ ngx_queue_t *q;
++ ngx_quic_path_t *path;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!ngx_queue_empty(&qc->free_paths)) {
++
++ q = ngx_queue_head(&qc->free_paths);
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ ngx_queue_remove(&path->queue);
++
++ ngx_memzero(path, sizeof(ngx_quic_path_t));
++
++ } else {
++
++ path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t));
++ if (path == NULL) {
++ return NULL;
++ }
++ }
++
++ ngx_queue_insert_tail(&qc->paths, &path->queue);
++
++ path->cid = cid;
++ cid->used = 1;
++
++ path->limited = 1;
++
++ path->seqnum = qc->path_seqnum++;
++
++ path->sockaddr = &path->sa.sockaddr;
++ path->socklen = socklen;
++ ngx_memcpy(path->sockaddr, sockaddr, socklen);
++
++ path->addr_text.data = path->text;
++ path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
++ NGX_SOCKADDR_STRLEN, 1);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path seq:%uL created addr:%V",
++ path->seqnum, &path->addr_text);
++ return path;
++}
++
++
++static ngx_quic_path_t *
++ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag)
++{
++ ngx_queue_t *q;
++ ngx_quic_path_t *path;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->paths);
++ q != ngx_queue_sentinel(&qc->paths);
++ q = ngx_queue_next(q))
++ {
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ if (path->tag == tag) {
++ return path;
++ }
++ }
++
++ return NULL;
++}
++
++
++ngx_int_t
++ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ off_t len;
++ ngx_queue_t *q;
++ ngx_quic_path_t *path, *probe;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ qsock = ngx_quic_get_socket(c);
++
++ len = pkt->raw->last - pkt->raw->start;
++
++ if (c->udp->buffer == NULL) {
++ /* first ever packet in connection, path already exists */
++ path = qc->path;
++ goto update;
++ }
++
++ probe = NULL;
++
++ for (q = ngx_queue_head(&qc->paths);
++ q != ngx_queue_sentinel(&qc->paths);
++ q = ngx_queue_next(q))
++ {
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen,
++ path->sockaddr, path->socklen, 1)
++ == NGX_OK)
++ {
++ goto update;
++ }
++
++ if (path->tag == NGX_QUIC_PATH_PROBE) {
++ probe = path;
++ }
++ }
++
++ /* packet from new path, drop current probe, if any */
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ /*
++ * only accept highest-numbered packets to prevent connection id
++ * exhaustion by excessive probing packets from unknown paths
++ */
++ if (pkt->pn != ctx->largest_pn) {
++ return NGX_DONE;
++ }
++
++ if (probe && ngx_quic_free_path(c, probe) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ /* new path requires new client id */
++ cid = ngx_quic_next_client_id(c);
++ if (cid == NULL) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic no available client ids for new path");
++ /* stop processing of this datagram */
++ return NGX_DONE;
++ }
++
++ path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid);
++ if (path == NULL) {
++ return NGX_ERROR;
++ }
++
++ path->tag = NGX_QUIC_PATH_PROBE;
++
++ /*
++ * client arrived using new path and previously seen DCID,
++ * this indicates NAT rebinding (or bad client)
++ */
++ if (qsock->used) {
++ pkt->rebound = 1;
++ }
++
++update:
++
++ qsock->used = 1;
++ pkt->path = path;
++
++ /* TODO: this may be too late in some cases;
++ * for example, if error happens during decrypt(), we cannot
++ * send CC, if error happens in 1st packet, due to amplification
++ * limit, because path->received = 0
++ *
++ * should we account garbage as received or only decrypting packets?
++ */
++ path->received += len;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet len:%O via sock seq:%L path seq:%uL",
++ len, (int64_t) qsock->sid.seqnum, path->seqnum);
++ ngx_quic_path_dbg(c, "status", path);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_queue_remove(&path->queue);
++ ngx_queue_insert_head(&qc->free_paths, &path->queue);
++
++ /*
++ * invalidate CID that is no longer usable for any other path;
++ * this also requests new CIDs from client
++ */
++ if (path->cid) {
++ if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path seq:%uL addr:%V retired",
++ path->seqnum, &path->addr_text);
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ size_t len;
++
++ ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen);
++ c->socklen = path->socklen;
++
++ if (c->addr_text.data) {
++ len = ngx_min(c->addr_text.len, path->addr_text.len);
++
++ ngx_memcpy(c->addr_text.data, path->addr_text.data, len);
++ c->addr_text.len = len;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send path set to seq:%uL addr:%V",
++ path->seqnum, &path->addr_text);
++}
++
++
++ngx_int_t
++ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ ngx_quic_path_t *next, *bkp;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ /* got non-probing packet via non-active path */
++
++ qc = ngx_quic_get_connection(c);
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ /*
++ * RFC 9000, 9.3. Responding to Connection Migration
++ *
++ * An endpoint only changes the address to which it sends packets in
++ * response to the highest-numbered non-probing packet.
++ */
++ if (pkt->pn != ctx->largest_pn) {
++ return NGX_OK;
++ }
++
++ next = pkt->path;
++
++ /*
++ * RFC 9000, 9.3.3:
++ *
++ * In response to an apparent migration, endpoints MUST validate the
++ * previously active path using a PATH_CHALLENGE frame.
++ */
++ if (pkt->rebound) {
++
++ /* NAT rebinding: client uses new path with old SID */
++ if (ngx_quic_validate_path(c, qc->path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (qc->path->validated) {
++
++ if (next->tag != NGX_QUIC_PATH_BACKUP) {
++ /* can delete backup path, if any */
++ bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
++
++ if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ qc->path->tag = NGX_QUIC_PATH_BACKUP;
++ ngx_quic_path_dbg(c, "is now backup", qc->path);
++
++ } else {
++ if (ngx_quic_free_path(c, qc->path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ /* switch active path to migrated */
++ qc->path = next;
++ qc->path->tag = NGX_QUIC_PATH_ACTIVE;
++
++ ngx_quic_set_connection_path(c, next);
++
++ if (!next->validated && !next->validating) {
++ if (ngx_quic_validate_path(c, next) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic migrated to path seq:%uL addr:%V",
++ qc->path->seqnum, &qc->path->addr_text);
++
++ ngx_quic_path_dbg(c, "is now active", qc->path);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ ngx_msec_t pto;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic initiated validation of path seq:%uL", path->seqnum);
++
++ path->validating = 1;
++ path->tries = 0;
++
++ if (RAND_bytes(path->challenge1, 8) != 1) {
++ return NGX_ERROR;
++ }
++
++ if (RAND_bytes(path->challenge2, 8) != 1) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_send_path_challenge(c, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++ pto = ngx_max(ngx_quic_pto(c, ctx), 1000);
++
++ path->expires = ngx_current_msec + pto;
++
++ ngx_quic_set_path_timer(c);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ ngx_quic_frame_t frame;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path seq:%uL send path_challenge tries:%ui",
++ path->seqnum, path->tries);
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++
++ frame.level = ssl_encryption_application;
++ frame.type = NGX_QUIC_FT_PATH_CHALLENGE;
++
++ ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8);
++
++ /*
++ * RFC 9000, 8.2.1. Initiating Path Validation
++ *
++ * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame
++ * to at least the smallest allowed maximum datagram size of 1200 bytes,
++ * unless the anti-amplification limit for the path does not permit
++ * sending a datagram of this size.
++ */
++
++ /* same applies to PATH_RESPONSE frames */
++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8);
++
++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_set_path_timer(ngx_connection_t *c)
++{
++ ngx_msec_t now;
++ ngx_queue_t *q;
++ ngx_msec_int_t left, next;
++ ngx_quic_path_t *path;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ now = ngx_current_msec;
++ next = -1;
++
++ for (q = ngx_queue_head(&qc->paths);
++ q != ngx_queue_sentinel(&qc->paths);
++ q = ngx_queue_next(q))
++ {
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ if (!path->validating) {
++ continue;
++ }
++
++ left = path->expires - now;
++ left = ngx_max(left, 1);
++
++ if (next == -1 || left < next) {
++ next = left;
++ }
++ }
++
++ if (next != -1) {
++ ngx_add_timer(&qc->path_validation, next);
++
++ } else if (qc->path_validation.timer_set) {
++ ngx_del_timer(&qc->path_validation);
++ }
++}
++
++
++void
++ngx_quic_path_validation_handler(ngx_event_t *ev)
++{
++ ngx_msec_t now;
++ ngx_queue_t *q;
++ ngx_msec_int_t left, next, pto;
++ ngx_quic_path_t *path, *bkp;
++ ngx_connection_t *c;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ c = ev->data;
++ qc = ngx_quic_get_connection(c);
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++
++ next = -1;
++ now = ngx_current_msec;
++
++ q = ngx_queue_head(&qc->paths);
++
++ while (q != ngx_queue_sentinel(&qc->paths)) {
++
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++ q = ngx_queue_next(q);
++
++ if (!path->validating) {
++ continue;
++ }
++
++ left = path->expires - now;
++
++ if (left > 0) {
++
++ if (next == -1 || left < next) {
++ next = left;
++ }
++
++ continue;
++ }
++
++ if (++path->tries < NGX_QUIC_PATH_RETRIES) {
++ pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries;
++
++ path->expires = ngx_current_msec + pto;
++
++ if (next == -1 || pto < next) {
++ next = pto;
++ }
++
++ /* retransmit */
++ (void) ngx_quic_send_path_challenge(c, path);
++
++ continue;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
++ "quic path seq:%uL validation failed", path->seqnum);
++
++ /* found expired path */
++
++ path->validated = 0;
++ path->validating = 0;
++ path->limited = 1;
++
++
++ /* RFC 9000, 9.3.2. On-Path Address Spoofing
++ *
++ * To protect the connection from failing due to such a spurious
++ * migration, an endpoint MUST revert to using the last validated
++ * peer address when validation of a new peer address fails.
++ */
++
++ if (qc->path == path) {
++ /* active path validation failed */
++
++ bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
++
++ if (bkp == NULL) {
++ qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
++ qc->error_reason = "no viable path";
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ qc->path = bkp;
++ qc->path->tag = NGX_QUIC_PATH_ACTIVE;
++
++ ngx_quic_set_connection_path(c, qc->path);
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic path seq:%uL addr:%V is restored from backup",
++ qc->path->seqnum, &qc->path->addr_text);
++
++ ngx_quic_path_dbg(c, "is active", qc->path);
++ }
++
++ if (ngx_quic_free_path(c, path) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++ }
++
++ if (next != -1) {
++ ngx_add_timer(&qc->path_validation, next);
++ }
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_migration.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_migration.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,42 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
++#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#define NGX_QUIC_PATH_RETRIES 3
++
++#define NGX_QUIC_PATH_PROBE 0
++#define NGX_QUIC_PATH_ACTIVE 1
++#define NGX_QUIC_PATH_BACKUP 2
++
++#define ngx_quic_path_dbg(c, msg, path) \
++ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \
++ "quic path seq:%uL %s sent:%O recvd:%O state:%s%s%s", \
++ path->seqnum, msg, path->sent, path->received, \
++ path->limited ? "L" : "", path->validated ? "V": "N", \
++ path->validating ? "R": "");
++
++ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f);
++ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c,
++ ngx_quic_path_challenge_frame_t *f);
++
++ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c,
++ struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid);
++ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path);
++
++ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++
++void ngx_quic_path_validation_handler(ngx_event_t *ev);
++
++#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_openssl_compat.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_openssl_compat.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,646 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++
++#define NGX_QUIC_COMPAT_RECORD_SIZE 1024
++
++#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39
++
++#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET"
++#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET"
++#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0"
++#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0"
++
++
++typedef struct {
++ ngx_quic_secret_t secret;
++ ngx_uint_t cipher;
++} ngx_quic_compat_keys_t;
++
++
++typedef struct {
++ ngx_log_t *log;
++
++ u_char type;
++ ngx_str_t payload;
++ uint64_t number;
++ ngx_quic_compat_keys_t *keys;
++
++ enum ssl_encryption_level_t level;
++} ngx_quic_compat_record_t;
++
++
++struct ngx_quic_compat_s {
++ const SSL_QUIC_METHOD *method;
++
++ enum ssl_encryption_level_t write_level;
++ enum ssl_encryption_level_t read_level;
++
++ uint64_t read_record;
++ ngx_quic_compat_keys_t keys;
++
++ ngx_str_t tp;
++ ngx_str_t ctp;
++};
++
++
++static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line);
++static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
++ ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len);
++static int ngx_quic_compat_add_transport_params_callback(SSL *ssl,
++ unsigned int ext_type, unsigned int context, const unsigned char **out,
++ size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg);
++static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl,
++ unsigned int ext_type, unsigned int context, const unsigned char *in,
++ size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg);
++static void ngx_quic_compat_message_callback(int write_p, int version,
++ int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
++static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec,
++ u_char *out, ngx_uint_t plain);
++static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec,
++ ngx_str_t *res);
++
++
++ngx_int_t
++ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx)
++{
++ SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback);
++
++ if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) {
++ return NGX_OK;
++ }
++
++ if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT,
++ SSL_EXT_CLIENT_HELLO
++ |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
++ ngx_quic_compat_add_transport_params_callback,
++ NULL,
++ NULL,
++ ngx_quic_compat_parse_transport_params_callback,
++ NULL)
++ == 0)
++ {
++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
++ "SSL_CTX_add_custom_ext() failed");
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line)
++{
++ u_char ch, *p, *start, value;
++ size_t n;
++ ngx_uint_t write;
++ const SSL_CIPHER *cipher;
++ ngx_quic_compat_t *com;
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++ enum ssl_encryption_level_t level;
++ u_char secret[EVP_MAX_MD_SIZE];
++
++ c = ngx_ssl_get_connection(ssl);
++ if (c->type != SOCK_DGRAM) {
++ return;
++ }
++
++ p = (u_char *) line;
++
++ for (start = p; *p && *p != ' '; p++);
++
++ n = p - start;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat secret %*s", n, start);
++
++ if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0)
++ {
++ level = ssl_encryption_handshake;
++ write = 0;
++
++ } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0)
++ {
++ level = ssl_encryption_handshake;
++ write = 1;
++
++ } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n)
++ == 0)
++ {
++ level = ssl_encryption_application;
++ write = 0;
++
++ } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1
++ && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n)
++ == 0)
++ {
++ level = ssl_encryption_application;
++ write = 1;
++
++ } else {
++ return;
++ }
++
++ if (*p++ == '\0') {
++ return;
++ }
++
++ for ( /* void */ ; *p && *p != ' '; p++);
++
++ if (*p++ == '\0') {
++ return;
++ }
++
++ for (n = 0, start = p; *p; p++) {
++ ch = *p;
++
++ if (ch >= '0' && ch <= '9') {
++ value = ch - '0';
++ goto next;
++ }
++
++ ch = (u_char) (ch | 0x20);
++
++ if (ch >= 'a' && ch <= 'f') {
++ value = ch - 'a' + 10;
++ goto next;
++ }
++
++ ngx_log_error(NGX_LOG_EMERG, c->log, 0,
++ "invalid OpenSSL QUIC secret format");
++
++ return;
++
++ next:
++
++ if ((p - start) % 2) {
++ secret[n++] += value;
++
++ } else {
++ if (n >= EVP_MAX_MD_SIZE) {
++ ngx_log_error(NGX_LOG_EMERG, c->log, 0,
++ "too big OpenSSL QUIC secret");
++ return;
++ }
++
++ secret[n] = (value << 4);
++ }
++ }
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++ cipher = SSL_get_current_cipher(ssl);
++
++ if (write) {
++ com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n);
++ com->write_level = level;
++
++ } else {
++ com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n);
++ com->read_level = level;
++ com->read_record = 0;
++
++ (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level,
++ cipher, secret, n);
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
++ ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
++{
++ ngx_int_t key_len;
++ ngx_str_t secret_str;
++ ngx_uint_t i;
++ ngx_quic_hkdf_t seq[2];
++ ngx_quic_secret_t *peer_secret;
++ ngx_quic_ciphers_t ciphers;
++
++ peer_secret = &keys->secret;
++
++ keys->cipher = SSL_CIPHER_get_id(cipher);
++
++ key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
++
++ if (key_len == NGX_ERROR) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher");
++ return NGX_ERROR;
++ }
++
++ if (sizeof(peer_secret->secret.data) < secret_len) {
++ ngx_log_error(NGX_LOG_ALERT, log, 0,
++ "unexpected secret len: %uz", secret_len);
++ return NGX_ERROR;
++ }
++
++ peer_secret->secret.len = secret_len;
++ ngx_memcpy(peer_secret->secret.data, secret, secret_len);
++
++ peer_secret->key.len = key_len;
++ peer_secret->iv.len = NGX_QUIC_IV_LEN;
++
++ secret_str.len = secret_len;
++ secret_str.data = (u_char *) secret;
++
++ ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str);
++ ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str);
++
++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
++ if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static int
++ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type,
++ unsigned int context, const unsigned char **out, size_t *outlen, X509 *x,
++ size_t chainidx, int *al, void *add_arg)
++{
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ if (c->type != SOCK_DGRAM) {
++ return 0;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat add transport params");
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ *out = com->tp.data;
++ *outlen = com->tp.len;
++
++ return 1;
++}
++
++
++static int
++ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type,
++ unsigned int context, const unsigned char *in, size_t inlen, X509 *x,
++ size_t chainidx, int *al, void *parse_arg)
++{
++ u_char *p;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ if (c->type != SOCK_DGRAM) {
++ return 0;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat parse transport params");
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ p = ngx_pnalloc(c->pool, inlen);
++ if (p == NULL) {
++ return 0;
++ }
++
++ ngx_memcpy(p, in, inlen);
++
++ com->ctp.data = p;
++ com->ctp.len = inlen;
++
++ return 1;
++}
++
++
++int
++SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method)
++{
++ BIO *rbio, *wbio;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method");
++
++ qc = ngx_quic_get_connection(c);
++
++ qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t));
++ if (qc->compat == NULL) {
++ return 0;
++ }
++
++ com = qc->compat;
++ com->method = quic_method;
++
++ rbio = BIO_new(BIO_s_mem());
++ if (rbio == NULL) {
++ return 0;
++ }
++
++ wbio = BIO_new(BIO_s_null());
++ if (wbio == NULL) {
++ return 0;
++ }
++
++ SSL_set_bio(ssl, rbio, wbio);
++
++ SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback);
++
++ /* early data is not supported */
++ SSL_set_max_early_data(ssl, 0);
++
++ return 1;
++}
++
++
++static void
++ngx_quic_compat_message_callback(int write_p, int version, int content_type,
++ const void *buf, size_t len, SSL *ssl, void *arg)
++{
++ ngx_uint_t alert;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++ enum ssl_encryption_level_t level;
++
++ if (!write_p) {
++ return;
++ }
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++
++ if (qc == NULL) {
++ /* closing */
++ return;
++ }
++
++ com = qc->compat;
++ level = com->write_level;
++
++ switch (content_type) {
++
++ case SSL3_RT_HANDSHAKE:
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat tx %s len:%uz ",
++ ngx_quic_level_name(level), len);
++
++ (void) com->method->add_handshake_data(ssl, level, buf, len);
++
++ break;
++
++ case SSL3_RT_ALERT:
++ if (len >= 2) {
++ alert = ((u_char *) buf)[1];
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat %s alert:%ui len:%uz ",
++ ngx_quic_level_name(level), alert, len);
++
++ (void) com->method->send_alert(ssl, level, alert);
++ }
++
++ break;
++ }
++}
++
++
++int
++SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
++ const uint8_t *data, size_t len)
++{
++ BIO *rbio;
++ size_t n;
++ u_char *p;
++ ngx_str_t res;
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++ ngx_quic_compat_record_t rec;
++ u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1];
++ u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1
++ + SSL3_RT_HEADER_LENGTH
++ + EVP_GCM_TLS_TAG_LEN];
++
++ c = ngx_ssl_get_connection(ssl);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz",
++ ngx_quic_level_name(level), len);
++
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++ rbio = SSL_get_rbio(ssl);
++
++ while (len) {
++ ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t));
++
++ rec.type = SSL3_RT_HANDSHAKE;
++ rec.log = c->log;
++ rec.number = com->read_record++;
++ rec.keys = &com->keys;
++
++ if (level == ssl_encryption_initial) {
++ n = ngx_min(len, 65535);
++
++ rec.payload.len = n;
++ rec.payload.data = (u_char *) data;
++
++ ngx_quic_compat_create_header(&rec, out, 1);
++
++ BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH);
++ BIO_write(rbio, data, n);
++
++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat record len:%uz %*xs%*xs",
++ n + SSL3_RT_HEADER_LENGTH,
++ (size_t) SSL3_RT_HEADER_LENGTH, out, n, data);
++#endif
++
++ } else {
++ n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE);
++
++ p = ngx_cpymem(in, data, n);
++ *p++ = SSL3_RT_HANDSHAKE;
++
++ rec.payload.len = p - in;
++ rec.payload.data = in;
++
++ res.data = out;
++
++ if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) {
++ return 0;
++ }
++
++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic compat record len:%uz %xV", res.len, &res);
++#endif
++
++ BIO_write(rbio, res.data, res.len);
++ }
++
++ data += n;
++ len -= n;
++ }
++
++ return 1;
++}
++
++
++static size_t
++ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out,
++ ngx_uint_t plain)
++{
++ u_char type;
++ size_t len;
++
++ len = rec->payload.len;
++
++ if (plain) {
++ type = rec->type;
++
++ } else {
++ type = SSL3_RT_APPLICATION_DATA;
++ len += EVP_GCM_TLS_TAG_LEN;
++ }
++
++ out[0] = type;
++ out[1] = 0x03;
++ out[2] = 0x03;
++ out[3] = (len >> 8);
++ out[4] = len;
++
++ return 5;
++}
++
++
++static ngx_int_t
++ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res)
++{
++ ngx_str_t ad, out;
++ ngx_quic_secret_t *secret;
++ ngx_quic_ciphers_t ciphers;
++ u_char nonce[NGX_QUIC_IV_LEN];
++
++ ad.data = res->data;
++ ad.len = ngx_quic_compat_create_header(rec, ad.data, 0);
++
++ out.len = rec->payload.len + EVP_GCM_TLS_TAG_LEN;
++ out.data = res->data + ad.len;
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0,
++ "quic compat ad len:%uz %xV", ad.len, &ad);
++#endif
++
++ if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ secret = &rec->keys->secret;
++
++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
++ ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number);
++
++ if (ngx_quic_tls_seal(ciphers.c, secret, &out,
++ nonce, &rec->payload, &ad, rec->log)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ res->len = ad.len + out.len;
++
++ return NGX_OK;
++}
++
++
++enum ssl_encryption_level_t
++SSL_quic_read_level(const SSL *ssl)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++
++ return qc->compat->read_level;
++}
++
++
++enum ssl_encryption_level_t
++SSL_quic_write_level(const SSL *ssl)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++
++ return qc->compat->write_level;
++}
++
++
++int
++SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
++ size_t params_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ com->tp.len = params_len;
++ com->tp.data = (u_char *) params;
++
++ return 1;
++}
++
++
++void
++SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params,
++ size_t *out_params_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_compat_t *com;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection(ssl);
++ qc = ngx_quic_get_connection(c);
++ com = qc->compat;
++
++ *out_params = com->ctp.data;
++ *out_params_len = com->ctp.len;
++}
++
++#endif /* NGX_QUIC_OPENSSL_COMPAT */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_openssl_compat.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_openssl_compat.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,60 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_
++#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_
++
++#ifdef TLSEXT_TYPE_quic_transport_parameters
++#undef NGX_QUIC_OPENSSL_COMPAT
++#else
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++typedef struct ngx_quic_compat_s ngx_quic_compat_t;
++
++
++enum ssl_encryption_level_t {
++ ssl_encryption_initial = 0,
++ ssl_encryption_early_data,
++ ssl_encryption_handshake,
++ ssl_encryption_application
++};
++
++
++typedef struct ssl_quic_method_st {
++ int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher,
++ const uint8_t *rsecret, size_t secret_len);
++ int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher,
++ const uint8_t *wsecret, size_t secret_len);
++ int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level,
++ const uint8_t *data, size_t len);
++ int (*flush_flight)(SSL *ssl);
++ int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level,
++ uint8_t alert);
++} SSL_QUIC_METHOD;
++
++
++ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx);
++
++int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method);
++int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
++ const uint8_t *data, size_t len);
++enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl);
++enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl);
++int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
++ size_t params_len);
++void SSL_get_peer_quic_transport_params(const SSL *ssl,
++ const uint8_t **out_params, size_t *out_params_len);
++
++
++#endif /* TLSEXT_TYPE_quic_transport_parameters */
++
++#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_output.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_output.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,1293 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252
++#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232
++
++#define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */
++#define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */
++
++#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */
++#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */
++#define NGX_QUIC_RETRY_BUFFER_SIZE 256
++ /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */
++
++/*
++ * RFC 9000, 10.3. Stateless Reset
++ *
++ * Endpoints MUST discard packets that are too small to be valid QUIC
++ * packets. With the set of AEAD functions defined in [QUIC-TLS],
++ * short header packets that are smaller than 21 bytes are never valid.
++ */
++#define NGX_QUIC_MIN_PKT_LEN 21
++
++#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */
++#define NGX_QUIC_MAX_SR_PACKET 1200
++
++#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */
++
++#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */
++
++
++static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c);
++static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
++static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t pnum);
++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
++static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c);
++static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c);
++static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf,
++ size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment);
++#endif
++static ssize_t ngx_quic_output_packet(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min);
++static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ ngx_quic_header_t *pkt, ngx_quic_path_t *path);
++static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c);
++static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
++ struct sockaddr *sockaddr, socklen_t socklen);
++static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt,
++ ngx_quic_send_ctx_t *ctx);
++static size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path,
++ size_t size);
++
++
++size_t
++ngx_quic_max_udp_payload(ngx_connection_t *c)
++{
++ /* TODO: path MTU discovery */
++
++#if (NGX_HAVE_INET6)
++ if (c->sockaddr->sa_family == AF_INET6) {
++ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6;
++ }
++#endif
++
++ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT;
++}
++
++
++ngx_int_t
++ngx_quic_output(ngx_connection_t *c)
++{
++ size_t in_flight;
++ ngx_int_t rc;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ c->log->action = "sending frames";
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ in_flight = cg->in_flight;
++
++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
++ if (ngx_quic_allow_segmentation(c)) {
++ rc = ngx_quic_create_segments(c);
++ } else
++#endif
++ {
++ rc = ngx_quic_create_datagrams(c);
++ }
++
++ if (rc != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (in_flight == cg->in_flight || qc->closing) {
++ /* no ack-eliciting data was sent or we are done */
++ return NGX_OK;
++ }
++
++ if (!qc->send_timer_set) {
++ qc->send_timer_set = 1;
++ ngx_add_timer(c->read, qc->tp.max_idle_timeout);
++ }
++
++ ngx_quic_set_lost_timer(c);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_create_datagrams(ngx_connection_t *c)
++{
++ size_t len, min;
++ ssize_t n;
++ u_char *p;
++ uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST];
++ ngx_uint_t i, pad;
++ ngx_quic_path_t *path;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++ path = qc->path;
++
++ while (cg->in_flight < cg->window) {
++
++ p = dst;
++
++ len = ngx_min(qc->ctp.max_udp_payload_size,
++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
++
++ len = ngx_quic_path_limit(c, path, len);
++
++ pad = ngx_quic_get_padding_level(c);
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++
++ ctx = &qc->send_ctx[i];
++
++ preserved_pnum[i] = ctx->pnum;
++
++ if (ngx_quic_generate_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE)
++ ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0;
++
++ if (min > len) {
++ /* padding can't be applied - avoid sending the packet */
++
++ while (i-- > 0) {
++ ctx = &qc->send_ctx[i];
++ ngx_quic_revert_send(c, ctx, preserved_pnum[i]);
++ }
++
++ return NGX_OK;
++ }
++
++ n = ngx_quic_output_packet(c, ctx, p, len, min);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ p += n;
++ len -= n;
++ }
++
++ len = p - dst;
++ if (len == 0) {
++ break;
++ }
++
++ n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen);
++
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (n == NGX_AGAIN) {
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]);
++ }
++
++ ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY);
++ break;
++ }
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_quic_commit_send(c, &qc->send_ctx[i]);
++ }
++
++ path->sent += len;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ cg = &qc->congestion;
++
++ while (!ngx_queue_empty(&ctx->sending)) {
++
++ q = ngx_queue_head(&ctx->sending);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_queue_remove(q);
++
++ if (f->pkt_need_ack && !qc->closing) {
++ ngx_queue_insert_tail(&ctx->sent, q);
++
++ cg->in_flight += f->plen;
++
++ } else {
++ ngx_quic_free_frame(c, f);
++ }
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion send if:%uz", cg->in_flight);
++}
++
++
++static void
++ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t pnum)
++{
++ ngx_queue_t *q;
++
++ while (!ngx_queue_empty(&ctx->sending)) {
++
++ q = ngx_queue_last(&ctx->sending);
++ ngx_queue_remove(q);
++ ngx_queue_insert_head(&ctx->frames, q);
++ }
++
++ ctx->pnum = pnum;
++}
++
++
++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
++
++static ngx_uint_t
++ngx_quic_allow_segmentation(ngx_connection_t *c)
++{
++ size_t bytes, len;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!qc->conf->gso_enabled) {
++ return 0;
++ }
++
++ if (qc->path->limited) {
++ /* don't even try to be faster on non-validated paths */
++ return 0;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial);
++ if (!ngx_queue_empty(&ctx->frames)) {
++ return 0;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
++ if (!ngx_queue_empty(&ctx->frames)) {
++ return 0;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++
++ bytes = 0;
++
++ len = ngx_min(qc->ctp.max_udp_payload_size,
++ NGX_QUIC_MAX_UDP_SEGMENT_BUF);
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ q = ngx_queue_next(q))
++ {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ bytes += f->len;
++
++ if (bytes > len * 3) {
++ /* require at least ~3 full packets to batch */
++ return 1;
++ }
++ }
++
++ return 0;
++}
++
++
++static ngx_int_t
++ngx_quic_create_segments(ngx_connection_t *c)
++{
++ size_t len, segsize;
++ ssize_t n;
++ u_char *p, *end;
++ uint64_t preserved_pnum;
++ ngx_uint_t nseg;
++ ngx_quic_path_t *path;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++ static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF];
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++ path = qc->path;
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++
++ if (ngx_quic_generate_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ segsize = ngx_min(qc->ctp.max_udp_payload_size,
++ NGX_QUIC_MAX_UDP_SEGMENT_BUF);
++ p = dst;
++ end = dst + sizeof(dst);
++
++ nseg = 0;
++
++ preserved_pnum = ctx->pnum;
++
++ for ( ;; ) {
++
++ len = ngx_min(segsize, (size_t) (end - p));
++
++ if (len && cg->in_flight < cg->window) {
++
++ n = ngx_quic_output_packet(c, ctx, p, len, len);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (n) {
++ p += n;
++ nseg++;
++ }
++
++ } else {
++ n = 0;
++ }
++
++ if (p == dst) {
++ break;
++ }
++
++ if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) {
++ n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr,
++ path->socklen, segsize);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (n == NGX_AGAIN) {
++ ngx_quic_revert_send(c, ctx, preserved_pnum);
++
++ ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY);
++ break;
++ }
++
++ ngx_quic_commit_send(c, ctx);
++
++ path->sent += n;
++
++ p = dst;
++ nseg = 0;
++ preserved_pnum = ctx->pnum;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ssize_t
++ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len,
++ struct sockaddr *sockaddr, socklen_t socklen, size_t segment)
++{
++ size_t clen;
++ ssize_t n;
++ uint16_t *valp;
++ struct iovec iov;
++ struct msghdr msg;
++ struct cmsghdr *cmsg;
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ char msg_control[CMSG_SPACE(sizeof(uint16_t))
++ + CMSG_SPACE(sizeof(ngx_addrinfo_t))];
++#else
++ char msg_control[CMSG_SPACE(sizeof(uint16_t))];
++#endif
++
++ ngx_memzero(&msg, sizeof(struct msghdr));
++ ngx_memzero(msg_control, sizeof(msg_control));
++
++ iov.iov_len = len;
++ iov.iov_base = buf;
++
++ msg.msg_iov = &iov;
++ msg.msg_iovlen = 1;
++
++ msg.msg_name = sockaddr;
++ msg.msg_namelen = socklen;
++
++ msg.msg_control = msg_control;
++ msg.msg_controllen = sizeof(msg_control);
++
++ cmsg = CMSG_FIRSTHDR(&msg);
++
++ cmsg->cmsg_level = SOL_UDP;
++ cmsg->cmsg_type = UDP_SEGMENT;
++ cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t));
++
++ clen = CMSG_SPACE(sizeof(uint16_t));
++
++ valp = (void *) CMSG_DATA(cmsg);
++ *valp = segment;
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
++ cmsg = CMSG_NXTHDR(&msg, cmsg);
++ clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
++ }
++#endif
++
++ msg.msg_controllen = clen;
++
++ n = ngx_sendmsg(c, &msg, 0);
++ if (n < 0) {
++ return n;
++ }
++
++ c->sent += n;
++
++ return n;
++}
++
++#endif
++
++
++
++static ngx_uint_t
++ngx_quic_get_padding_level(ngx_connection_t *c)
++{
++ ngx_uint_t i;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ /*
++ * RFC 9000, 14.1. Initial Datagram Size
++ *
++ * Similarly, a server MUST expand the payload of all UDP datagrams
++ * carrying ack-eliciting Initial packets to at least the smallest
++ * allowed maximum datagram size of 1200 bytes.
++ */
++
++ qc = ngx_quic_get_connection(c);
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial);
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ q = ngx_queue_next(q))
++ {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->need_ack) {
++ for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ctx = &qc->send_ctx[i + 1];
++
++ if (ngx_queue_empty(&ctx->frames)) {
++ break;
++ }
++ }
++
++ return i;
++ }
++ }
++
++ return NGX_QUIC_SEND_CTX_LAST;
++}
++
++
++static ssize_t
++ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ u_char *data, size_t max, size_t min)
++{
++ size_t len, pad, min_payload, max_payload;
++ u_char *p;
++ ssize_t flen;
++ ngx_str_t res;
++ ngx_int_t rc;
++ ngx_uint_t nframes, expand;
++ ngx_msec_t now;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_header_t pkt;
++ ngx_quic_connection_t *qc;
++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ if (ngx_queue_empty(&ctx->frames)) {
++ return 0;
++ }
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic output %s packet max:%uz min:%uz",
++ ngx_quic_level_name(ctx->level), max, min);
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_quic_init_packet(c, ctx, &pkt, qc->path);
++
++ min_payload = ngx_quic_payload_size(&pkt, min);
++ max_payload = ngx_quic_payload_size(&pkt, max);
++
++ /* RFC 9001, 5.4.2. Header Protection Sample */
++ pad = 4 - pkt.num_len;
++ min_payload = ngx_max(min_payload, pad);
++
++ if (min_payload > max_payload) {
++ return 0;
++ }
++
++ now = ngx_current_msec;
++ nframes = 0;
++ p = src;
++ len = 0;
++ expand = 0;
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ q = ngx_queue_next(q))
++ {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (!expand && (f->type == NGX_QUIC_FT_PATH_RESPONSE
++ || f->type == NGX_QUIC_FT_PATH_CHALLENGE))
++ {
++ /*
++ * RFC 9000, 8.2.1. Initiating Path Validation
++ *
++ * An endpoint MUST expand datagrams that contain a
++ * PATH_CHALLENGE frame to at least the smallest allowed
++ * maximum datagram size of 1200 bytes...
++ *
++ * (same applies to PATH_RESPONSE frames)
++ */
++
++ if (max < 1200) {
++ /* expanded packet will not fit */
++ break;
++ }
++
++ if (min < 1200) {
++ min = 1200;
++
++ min_payload = ngx_quic_payload_size(&pkt, min);
++ }
++
++ expand = 1;
++ }
++
++ if (len >= max_payload) {
++ break;
++ }
++
++ if (len + f->len > max_payload) {
++ rc = ngx_quic_split_frame(c, f, max_payload - len);
++
++ if (rc == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_DECLINED) {
++ break;
++ }
++ }
++
++ if (f->need_ack) {
++ pkt.need_ack = 1;
++ }
++
++ ngx_quic_log_frame(c->log, f, 1);
++
++ flen = ngx_quic_create_frame(p, f);
++ if (flen == -1) {
++ return NGX_ERROR;
++ }
++
++ len += flen;
++ p += flen;
++
++ f->pnum = ctx->pnum;
++ f->first = now;
++ f->last = now;
++ f->plen = 0;
++
++ nframes++;
++
++ if (f->flush) {
++ break;
++ }
++ }
++
++ if (nframes == 0) {
++ return 0;
++ }
++
++ if (len < min_payload) {
++ ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len);
++ len = min_payload;
++ }
++
++ pkt.payload.data = src;
++ pkt.payload.len = len;
++
++ res.data = data;
++
++ ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet tx %s bytes:%ui"
++ " need_ack:%d number:%L encoded nl:%d trunc:0x%xD",
++ ngx_quic_level_name(ctx->level), pkt.payload.len,
++ pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc);
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx->pnum++;
++
++ if (pkt.need_ack) {
++ q = ngx_queue_head(&ctx->frames);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ f->plen = res.len;
++ }
++
++ while (nframes--) {
++ q = ngx_queue_head(&ctx->frames);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ f->pkt_need_ack = pkt.need_ack;
++
++ ngx_queue_remove(q);
++ ngx_queue_insert_tail(&ctx->sending, q);
++ }
++
++ return res.len;
++}
++
++
++static void
++ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ ngx_quic_header_t *pkt, ngx_quic_path_t *path)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_memzero(pkt, sizeof(ngx_quic_header_t));
++
++ pkt->flags = NGX_QUIC_PKT_FIXED_BIT;
++
++ if (ctx->level == ssl_encryption_initial) {
++ pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL;
++
++ } else if (ctx->level == ssl_encryption_handshake) {
++ pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE;
++
++ } else {
++ if (qc->key_phase) {
++ pkt->flags |= NGX_QUIC_PKT_KPHASE;
++ }
++ }
++
++ pkt->dcid.data = path->cid->id;
++ pkt->dcid.len = path->cid->len;
++
++ pkt->scid = qc->tp.initial_scid;
++
++ pkt->version = qc->version;
++ pkt->log = c->log;
++ pkt->level = ctx->level;
++
++ pkt->keys = qc->keys;
++
++ ngx_quic_set_packet_number(pkt, ctx);
++}
++
++
++static ssize_t
++ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
++ struct sockaddr *sockaddr, socklen_t socklen)
++{
++ ssize_t n;
++ struct iovec iov;
++ struct msghdr msg;
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ struct cmsghdr *cmsg;
++ char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
++#endif
++
++ ngx_memzero(&msg, sizeof(struct msghdr));
++
++ iov.iov_len = len;
++ iov.iov_base = buf;
++
++ msg.msg_iov = &iov;
++ msg.msg_iovlen = 1;
++
++ msg.msg_name = sockaddr;
++ msg.msg_namelen = socklen;
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
++
++ msg.msg_control = msg_control;
++ msg.msg_controllen = sizeof(msg_control);
++ ngx_memzero(msg_control, sizeof(msg_control));
++
++ cmsg = CMSG_FIRSTHDR(&msg);
++
++ msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
++ }
++#endif
++
++ n = ngx_sendmsg(c, &msg, 0);
++ if (n < 0) {
++ return n;
++ }
++
++ c->sent += n;
++
++ return n;
++}
++
++
++static void
++ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx)
++{
++ uint64_t delta;
++
++ delta = ctx->pnum - ctx->largest_ack;
++ pkt->number = ctx->pnum;
++
++ if (delta <= 0x7F) {
++ pkt->num_len = 1;
++ pkt->trunc = ctx->pnum & 0xff;
++
++ } else if (delta <= 0x7FFF) {
++ pkt->num_len = 2;
++ pkt->flags |= 0x1;
++ pkt->trunc = ctx->pnum & 0xffff;
++
++ } else if (delta <= 0x7FFFFF) {
++ pkt->num_len = 3;
++ pkt->flags |= 0x2;
++ pkt->trunc = ctx->pnum & 0xffffff;
++
++ } else {
++ pkt->num_len = 4;
++ pkt->flags |= 0x3;
++ pkt->trunc = ctx->pnum & 0xffffffff;
++ }
++}
++
++
++ngx_int_t
++ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt)
++{
++ size_t len;
++ ngx_quic_header_t pkt;
++ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "sending version negotiation packet");
++
++ pkt.log = c->log;
++ pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT;
++ pkt.dcid = inpkt->scid;
++ pkt.scid = inpkt->dcid;
++
++ len = ngx_quic_create_version_negotiation(&pkt, buf);
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic vnego packet to send len:%uz %*xs", len, len, buf);
++#endif
++
++ (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
++
++ return NGX_DONE;
++}
++
++
++ngx_int_t
++ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *pkt)
++{
++ u_char *token;
++ size_t len, max;
++ uint16_t rndbytes;
++ u_char buf[NGX_QUIC_MAX_SR_PACKET];
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic handle stateless reset output");
++
++ if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) {
++ return NGX_DECLINED;
++ }
++
++ if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) {
++ len = pkt->len - 1;
++
++ } else {
++ max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3);
++
++ if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) {
++ return NGX_ERROR;
++ }
++
++ len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1))
++ + NGX_QUIC_MIN_SR_PACKET;
++ }
++
++ if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) {
++ return NGX_ERROR;
++ }
++
++ buf[0] &= ~NGX_QUIC_PKT_LONG;
++ buf[0] |= NGX_QUIC_PKT_FIXED_BIT;
++
++ token = &buf[len - NGX_QUIC_SR_TOKEN_LEN];
++
++ if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
++
++ return NGX_DECLINED;
++}
++
++
++ngx_int_t
++ngx_quic_send_cc(ngx_connection_t *c)
++{
++ ngx_quic_frame_t frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc->draining) {
++ return NGX_OK;
++ }
++
++ if (qc->closing
++ && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL)
++ {
++ /* dot not send CC too often */
++ return NGX_OK;
++ }
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++
++ frame.level = qc->error_level;
++ frame.type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP
++ : NGX_QUIC_FT_CONNECTION_CLOSE;
++ frame.u.close.error_code = qc->error;
++ frame.u.close.frame_type = qc->error_ftype;
++
++ if (qc->error_reason) {
++ frame.u.close.reason.len = ngx_strlen(qc->error_reason);
++ frame.u.close.reason.data = (u_char *) qc->error_reason;
++ }
++
++ qc->last_cc = ngx_current_msec;
++
++ return ngx_quic_frame_sendto(c, &frame, 0, qc->path);
++}
++
++
++ngx_int_t
++ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt,
++ ngx_uint_t err, const char *reason)
++{
++ ssize_t len;
++ ngx_str_t res;
++ ngx_quic_keys_t keys;
++ ngx_quic_frame_t frame;
++ ngx_quic_header_t pkt;
++
++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
++
++ frame.level = inpkt->level;
++ frame.type = NGX_QUIC_FT_CONNECTION_CLOSE;
++ frame.u.close.error_code = err;
++
++ frame.u.close.reason.data = (u_char *) reason;
++ frame.u.close.reason.len = ngx_strlen(reason);
++
++ len = ngx_quic_create_frame(NULL, &frame);
++ if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_log_frame(c->log, &frame, 1);
++
++ len = ngx_quic_create_frame(src, &frame);
++ if (len == -1) {
++ return NGX_ERROR;
++ }
++
++ ngx_memzero(&keys, sizeof(ngx_quic_keys_t));
++
++ pkt.keys = &keys;
++
++ if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG
++ | NGX_QUIC_PKT_INITIAL;
++
++ pkt.num_len = 1;
++ /*
++ * pkt.num = 0;
++ * pkt.trunc = 0;
++ */
++
++ pkt.version = inpkt->version;
++ pkt.log = c->log;
++ pkt.level = inpkt->level;
++ pkt.dcid = inpkt->scid;
++ pkt.scid = inpkt->dcid;
++ pkt.payload.data = src;
++ pkt.payload.len = len;
++
++ res.data = dst;
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) {
++ return NGX_ERROR;
++ }
++
++ return NGX_DONE;
++}
++
++
++ngx_int_t
++ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *inpkt)
++{
++ time_t expires;
++ ssize_t len;
++ ngx_str_t res, token;
++ ngx_quic_header_t pkt;
++
++ u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
++ u_char dcid[NGX_QUIC_SERVER_CID_LEN];
++ u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE];
++
++ expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME;
++
++ token.data = tbuf;
++ token.len = NGX_QUIC_TOKEN_BUF_SIZE;
++
++ if (ngx_quic_new_token(c->log, c->sockaddr, c->socklen, conf->av_token_key,
++ &token, &inpkt->dcid, expires, 1)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
++ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY;
++ pkt.version = inpkt->version;
++ pkt.log = c->log;
++
++ pkt.odcid = inpkt->dcid;
++ pkt.dcid = inpkt->scid;
++
++ /* TODO: generate routable dcid */
++ if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) {
++ return NGX_ERROR;
++ }
++
++ pkt.scid.len = NGX_QUIC_SERVER_CID_LEN;
++ pkt.scid.data = dcid;
++
++ pkt.token = token;
++
++ res.data = buf;
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet to send len:%uz %xV", res.len, &res);
++#endif
++
++ len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen);
++ if (len < 0) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic retry packet sent to %xV", &pkt.dcid);
++
++ /*
++ * RFC 9000, 17.2.5.1. Sending a Retry Packet
++ *
++ * A server MUST NOT send more than one Retry
++ * packet in response to a single UDP datagram.
++ * NGX_DONE will stop quic_input() from processing further
++ */
++ return NGX_DONE;
++}
++
++
++ngx_int_t
++ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ time_t expires;
++ ngx_str_t token;
++ ngx_chain_t *out;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE];
++
++ qc = ngx_quic_get_connection(c);
++
++ expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME;
++
++ token.data = tbuf;
++ token.len = NGX_QUIC_TOKEN_BUF_SIZE;
++
++ if (ngx_quic_new_token(c->log, path->sockaddr, path->socklen,
++ qc->conf->av_token_key, &token, NULL, expires, 0)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ out = ngx_quic_copy_buffer(c, token.data, token.len);
++ if (out == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_NEW_TOKEN;
++ frame->data = out;
++ frame->u.token.length = token.len;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ size_t len, left;
++ uint64_t ack_delay;
++ ngx_buf_t *b;
++ ngx_uint_t i;
++ ngx_chain_t *cl, **ll;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ack_delay = ngx_current_msec - ctx->largest_received;
++ ack_delay *= 1000;
++ ack_delay >>= qc->tp.ack_delay_exponent;
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ ll = &frame->data;
++ b = NULL;
++
++ for (i = 0; i < ctx->nranges; i++) {
++ len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap,
++ ctx->ranges[i].range);
++
++ left = b ? b->end - b->last : 0;
++
++ if (left < len) {
++ cl = ngx_quic_alloc_chain(c);
++ if (cl == NULL) {
++ return NGX_ERROR;
++ }
++
++ *ll = cl;
++ ll = &cl->next;
++
++ b = cl->buf;
++ left = b->end - b->last;
++
++ if (left < len) {
++ return NGX_ERROR;
++ }
++ }
++
++ b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap,
++ ctx->ranges[i].range);
++
++ frame->u.ack.ranges_length += len;
++ }
++
++ *ll = NULL;
++
++ frame->level = ctx->level;
++ frame->type = NGX_QUIC_FT_ACK;
++ frame->u.ack.largest = ctx->largest_range;
++ frame->u.ack.delay = ack_delay;
++ frame->u.ack.range_count = ctx->nranges;
++ frame->u.ack.first_range = ctx->first_range;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t smallest, uint64_t largest)
++{
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ctx->level;
++ frame->type = NGX_QUIC_FT_ACK;
++ frame->u.ack.largest = largest;
++ frame->u.ack.delay = 0;
++ frame->u.ack.range_count = 0;
++ frame->u.ack.first_range = largest - smallest;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
++ size_t min, ngx_quic_path_t *path)
++{
++ size_t min_payload, pad;
++ ssize_t len, sent;
++ ngx_str_t res;
++ ngx_quic_header_t pkt;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ qc = ngx_quic_get_connection(c);
++ ctx = ngx_quic_get_send_ctx(qc, frame->level);
++
++ ngx_quic_init_packet(c, ctx, &pkt, path);
++
++ min = ngx_quic_path_limit(c, path, min);
++
++ min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0;
++
++ pad = 4 - pkt.num_len;
++ min_payload = ngx_max(min_payload, pad);
++
++ len = ngx_quic_create_frame(NULL, frame);
++ if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_log_frame(c->log, frame, 1);
++
++ len = ngx_quic_create_frame(src, frame);
++ if (len == -1) {
++ return NGX_ERROR;
++ }
++
++ if (len < (ssize_t) min_payload) {
++ ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len);
++ len = min_payload;
++ }
++
++ pkt.payload.data = src;
++ pkt.payload.len = len;
++
++ res.data = dst;
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx->pnum++;
++
++ sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen);
++ if (sent < 0) {
++ return NGX_ERROR;
++ }
++
++ path->sent += sent;
++
++ return NGX_OK;
++}
++
++
++static size_t
++ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size)
++{
++ off_t max;
++
++ if (path->limited) {
++ max = path->received * 3;
++ max = (path->sent >= max) ? 0 : max - path->sent;
++
++ if ((off_t) size > max) {
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path limit %uz - %O", size, max);
++ return max;
++ }
++ }
++
++ return size;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_output.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_output.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,40 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
++#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
++
++ngx_int_t ngx_quic_output(ngx_connection_t *c);
++
++ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
++ ngx_quic_header_t *inpkt);
++
++ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_send_cc(ngx_connection_t *c);
++ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c,
++ ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason);
++
++ngx_int_t ngx_quic_send_retry(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path);
++
++ngx_int_t ngx_quic_send_ack(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx);
++ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest);
++
++ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
++ size_t min, ngx_quic_path_t *path);
++
++#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_protection.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_protection.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,1087 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */
++#define NGX_QUIC_HP_LEN 5
++
++#define NGX_QUIC_AES_128_KEY_LEN 16
++
++#ifndef TLS1_3_CK_AES_128_GCM_SHA256
++#define TLS1_3_CK_AES_128_GCM_SHA256 0x03001301
++#define TLS1_3_CK_AES_256_GCM_SHA384 0x03001302
++#define TLS1_3_CK_CHACHA20_POLY1305_SHA256 \
++ 0x03001303
++#endif
++
++
++static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len,
++ const EVP_MD *digest, const u_char *prk, size_t prk_len,
++ const u_char *info, size_t info_len);
++static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len,
++ const EVP_MD *digest, const u_char *secret, size_t secret_len,
++ const u_char *salt, size_t salt_len);
++
++static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
++ uint64_t *largest_pn);
++
++static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher,
++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
++ ngx_str_t *ad, ngx_log_t *log);
++static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
++ ngx_quic_secret_t *s, u_char *out, u_char *in);
++
++static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt,
++ ngx_str_t *res);
++static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt,
++ ngx_str_t *res);
++
++
++ngx_int_t
++ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers,
++ enum ssl_encryption_level_t level)
++{
++ ngx_int_t len;
++
++ if (level == ssl_encryption_initial) {
++ id = TLS1_3_CK_AES_128_GCM_SHA256;
++ }
++
++ switch (id) {
++
++ case TLS1_3_CK_AES_128_GCM_SHA256:
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->c = EVP_aead_aes_128_gcm();
++#else
++ ciphers->c = EVP_aes_128_gcm();
++#endif
++ ciphers->hp = EVP_aes_128_ctr();
++ ciphers->d = EVP_sha256();
++ len = 16;
++ break;
++
++ case TLS1_3_CK_AES_256_GCM_SHA384:
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->c = EVP_aead_aes_256_gcm();
++#else
++ ciphers->c = EVP_aes_256_gcm();
++#endif
++ ciphers->hp = EVP_aes_256_ctr();
++ ciphers->d = EVP_sha384();
++ len = 32;
++ break;
++
++ case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->c = EVP_aead_chacha20_poly1305();
++#else
++ ciphers->c = EVP_chacha20_poly1305();
++#endif
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305();
++#else
++ ciphers->hp = EVP_chacha20();
++#endif
++ ciphers->d = EVP_sha256();
++ len = 32;
++ break;
++
++ default:
++ return NGX_ERROR;
++ }
++
++ return len;
++}
++
++
++ngx_int_t
++ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret,
++ ngx_log_t *log)
++{
++ size_t is_len;
++ uint8_t is[SHA256_DIGEST_LENGTH];
++ ngx_str_t iss;
++ ngx_uint_t i;
++ const EVP_MD *digest;
++ ngx_quic_hkdf_t seq[8];
++ ngx_quic_secret_t *client, *server;
++
++ static const uint8_t salt[20] =
++ "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17"
++ "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a";
++
++ client = &keys->secrets[ssl_encryption_initial].client;
++ server = &keys->secrets[ssl_encryption_initial].server;
++
++ /*
++ * RFC 9001, section 5. Packet Protection
++ *
++ * Initial packets use AEAD_AES_128_GCM. The hash function
++ * for HKDF when deriving initial secrets and keys is SHA-256.
++ */
++
++ digest = EVP_sha256();
++ is_len = SHA256_DIGEST_LENGTH;
++
++ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
++ salt, sizeof(salt))
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ iss.len = is_len;
++ iss.data = is;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic ngx_quic_set_initial_secret");
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt);
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic initial secret len:%uz %*xs", is_len, is_len, is);
++#endif
++
++ client->secret.len = SHA256_DIGEST_LENGTH;
++ server->secret.len = SHA256_DIGEST_LENGTH;
++
++ client->key.len = NGX_QUIC_AES_128_KEY_LEN;
++ server->key.len = NGX_QUIC_AES_128_KEY_LEN;
++
++ client->hp.len = NGX_QUIC_AES_128_KEY_LEN;
++ server->hp.len = NGX_QUIC_AES_128_KEY_LEN;
++
++ client->iv.len = NGX_QUIC_IV_LEN;
++ server->iv.len = NGX_QUIC_IV_LEN;
++
++ /* labels per RFC 9001, 5.1. Packet Protection Keys */
++ ngx_quic_hkdf_set(&seq[0], "tls13 client in", &client->secret, &iss);
++ ngx_quic_hkdf_set(&seq[1], "tls13 quic key", &client->key, &client->secret);
++ ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", &client->iv, &client->secret);
++ ngx_quic_hkdf_set(&seq[3], "tls13 quic hp", &client->hp, &client->secret);
++ ngx_quic_hkdf_set(&seq[4], "tls13 server in", &server->secret, &iss);
++ ngx_quic_hkdf_set(&seq[5], "tls13 quic key", &server->key, &server->secret);
++ ngx_quic_hkdf_set(&seq[6], "tls13 quic iv", &server->iv, &server->secret);
++ ngx_quic_hkdf_set(&seq[7], "tls13 quic hp", &server->hp, &server->secret);
++
++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
++ if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log)
++{
++ size_t info_len;
++ uint8_t *p;
++ uint8_t info[20];
++
++ info_len = 2 + 1 + h->label_len + 1;
++
++ info[0] = 0;
++ info[1] = h->out_len;
++ info[2] = h->label_len;
++
++ p = ngx_cpymem(&info[3], h->label, h->label_len);
++ *p = '\0';
++
++ if (ngx_hkdf_expand(h->out, h->out_len, digest,
++ h->prk, h->prk_len, info, info_len)
++ != NGX_OK)
++ {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "ngx_hkdf_expand(%*s) failed", h->label_len, h->label);
++ return NGX_ERROR;
++ }
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic expand \"%*s\" len:%uz %*xs",
++ h->label_len, h->label, h->out_len, h->out_len, h->out);
++#endif
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest,
++ const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len)
++{
++#ifdef OPENSSL_IS_BORINGSSL
++
++ if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len)
++ == 0)
++ {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++
++#else
++
++ EVP_PKEY_CTX *pctx;
++
++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
++ if (pctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (EVP_PKEY_derive_init(pctx) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) {
++ goto failed;
++ }
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_OK;
++
++failed:
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_ERROR;
++
++#endif
++}
++
++
++static ngx_int_t
++ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest,
++ const u_char *secret, size_t secret_len, const u_char *salt,
++ size_t salt_len)
++{
++#ifdef OPENSSL_IS_BORINGSSL
++
++ if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt,
++ salt_len)
++ == 0)
++ {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++
++#else
++
++ EVP_PKEY_CTX *pctx;
++
++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
++ if (pctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (EVP_PKEY_derive_init(pctx) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) {
++ goto failed;
++ }
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_OK;
++
++failed:
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_ERROR;
++
++#endif
++}
++
++
++static ngx_int_t
++ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad,
++ ngx_log_t *log)
++{
++
++#ifdef OPENSSL_IS_BORINGSSL
++ EVP_AEAD_CTX *ctx;
++
++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
++ EVP_AEAD_DEFAULT_TAG_LENGTH);
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
++ in->data, in->len, ad->data, ad->len)
++ != 1)
++ {
++ EVP_AEAD_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed");
++ return NGX_ERROR;
++ }
++
++ EVP_AEAD_CTX_free(ctx);
++#else
++ int len;
++ u_char *tag;
++ EVP_CIPHER_CTX *ctx;
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptUpdate(ctx, out->data, &len, in->data,
++ in->len - EVP_GCM_TLS_TAG_LEN)
++ != 1)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ out->len = len;
++ tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN;
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed");
++ return NGX_ERROR;
++ }
++
++ out->len += len;
++
++ EVP_CIPHER_CTX_free(ctx);
++#endif
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log)
++{
++
++#ifdef OPENSSL_IS_BORINGSSL
++ EVP_AEAD_CTX *ctx;
++
++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
++ EVP_AEAD_DEFAULT_TAG_LENGTH);
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
++ in->data, in->len, ad->data, ad->len)
++ != 1)
++ {
++ EVP_AEAD_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed");
++ return NGX_ERROR;
++ }
++
++ EVP_AEAD_CTX_free(ctx);
++#else
++ int len;
++ EVP_CIPHER_CTX *ctx;
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ out->len = len;
++
++ if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed");
++ return NGX_ERROR;
++ }
++
++ out->len += len;
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN,
++ out->data + in->len)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed");
++ return NGX_ERROR;
++ }
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ out->len += EVP_GCM_TLS_TAG_LEN;
++#endif
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
++ ngx_quic_secret_t *s, u_char *out, u_char *in)
++{
++ int outlen;
++ EVP_CIPHER_CTX *ctx;
++ u_char zero[NGX_QUIC_HP_LEN] = {0};
++
++#ifdef OPENSSL_IS_BORINGSSL
++ uint32_t cnt;
++
++ ngx_memcpy(&cnt, in, sizeof(uint32_t));
++
++ if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) {
++ CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt);
++ return NGX_OK;
++ }
++#endif
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
++ goto failed;
++ }
++
++ if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
++ goto failed;
++ }
++
++ if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed");
++ goto failed;
++ }
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ return NGX_OK;
++
++failed:
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ return NGX_ERROR;
++}
++
++
++ngx_int_t
++ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write,
++ ngx_quic_keys_t *keys, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
++{
++ ngx_int_t key_len;
++ ngx_str_t secret_str;
++ ngx_uint_t i;
++ ngx_quic_hkdf_t seq[3];
++ ngx_quic_secret_t *peer_secret;
++ ngx_quic_ciphers_t ciphers;
++
++ peer_secret = is_write ? &keys->secrets[level].server
++ : &keys->secrets[level].client;
++
++ keys->cipher = SSL_CIPHER_get_id(cipher);
++
++ key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
++
++ if (key_len == NGX_ERROR) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher");
++ return NGX_ERROR;
++ }
++
++ if (sizeof(peer_secret->secret.data) < secret_len) {
++ ngx_log_error(NGX_LOG_ALERT, log, 0,
++ "unexpected secret len: %uz", secret_len);
++ return NGX_ERROR;
++ }
++
++ peer_secret->secret.len = secret_len;
++ ngx_memcpy(peer_secret->secret.data, secret, secret_len);
++
++ peer_secret->key.len = key_len;
++ peer_secret->iv.len = NGX_QUIC_IV_LEN;
++ peer_secret->hp.len = key_len;
++
++ secret_str.len = secret_len;
++ secret_str.data = (u_char *) secret;
++
++ ngx_quic_hkdf_set(&seq[0], "tls13 quic key",
++ &peer_secret->key, &secret_str);
++ ngx_quic_hkdf_set(&seq[1], "tls13 quic iv", &peer_secret->iv, &secret_str);
++ ngx_quic_hkdf_set(&seq[2], "tls13 quic hp", &peer_secret->hp, &secret_str);
++
++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
++ if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_uint_t
++ngx_quic_keys_available(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level)
++{
++ return keys->secrets[level].client.key.len != 0;
++}
++
++
++void
++ngx_quic_keys_discard(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level)
++{
++ keys->secrets[level].client.key.len = 0;
++}
++
++
++void
++ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys)
++{
++ ngx_quic_secrets_t *current, *next, tmp;
++
++ current = &keys->secrets[ssl_encryption_application];
++ next = &keys->next_key;
++
++ tmp = *current;
++ *current = *next;
++ *next = tmp;
++}
++
++
++ngx_int_t
++ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys)
++{
++ ngx_uint_t i;
++ ngx_quic_hkdf_t seq[6];
++ ngx_quic_ciphers_t ciphers;
++ ngx_quic_secrets_t *current, *next;
++
++ current = &keys->secrets[ssl_encryption_application];
++ next = &keys->next_key;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update");
++
++ if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application)
++ == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ next->client.secret.len = current->client.secret.len;
++ next->client.key.len = current->client.key.len;
++ next->client.iv.len = NGX_QUIC_IV_LEN;
++ next->client.hp = current->client.hp;
++
++ next->server.secret.len = current->server.secret.len;
++ next->server.key.len = current->server.key.len;
++ next->server.iv.len = NGX_QUIC_IV_LEN;
++ next->server.hp = current->server.hp;
++
++ ngx_quic_hkdf_set(&seq[0], "tls13 quic ku",
++ &next->client.secret, &current->client.secret);
++ ngx_quic_hkdf_set(&seq[1], "tls13 quic key",
++ &next->client.key, &next->client.secret);
++ ngx_quic_hkdf_set(&seq[2], "tls13 quic iv",
++ &next->client.iv, &next->client.secret);
++ ngx_quic_hkdf_set(&seq[3], "tls13 quic ku",
++ &next->server.secret, &current->server.secret);
++ ngx_quic_hkdf_set(&seq[4], "tls13 quic key",
++ &next->server.key, &next->server.secret);
++ ngx_quic_hkdf_set(&seq[5], "tls13 quic iv",
++ &next->server.iv, &next->server.secret);
++
++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
++ if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
++{
++ u_char *pnp, *sample;
++ ngx_str_t ad, out;
++ ngx_uint_t i;
++ ngx_quic_secret_t *secret;
++ ngx_quic_ciphers_t ciphers;
++ u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN];
++
++ ad.data = res->data;
++ ad.len = ngx_quic_create_header(pkt, ad.data, &pnp);
++
++ out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
++ out.data = res->data + ad.len;
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic ad len:%uz %xV", ad.len, &ad);
++#endif
++
++ if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ secret = &pkt->keys->secrets[pkt->level].server;
++
++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
++ ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number);
++
++ if (ngx_quic_tls_seal(ciphers.c, secret, &out,
++ nonce, &pkt->payload, &ad, pkt->log)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ sample = &out.data[4 - pkt->num_len];
++ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ /* RFC 9001, 5.4.1. Header Protection Application */
++ ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags);
++
++ for (i = 0; i < pkt->num_len; i++) {
++ pnp[i] ^= mask[i + 1];
++ }
++
++ res->len = ad.len + out.len;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
++{
++ u_char *start;
++ ngx_str_t ad, itag;
++ ngx_quic_secret_t secret;
++ ngx_quic_ciphers_t ciphers;
++
++ /* 5.8. Retry Packet Integrity */
++ static u_char key[16] =
++ "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e";
++ static u_char nonce[NGX_QUIC_IV_LEN] =
++ "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb";
++ static ngx_str_t in = ngx_string("");
++
++ ad.data = res->data;
++ ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start);
++
++ itag.data = ad.data + ad.len;
++ itag.len = EVP_GCM_TLS_TAG_LEN;
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic retry itag len:%uz %xV", ad.len, &ad);
++#endif
++
++ if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ secret.key.len = sizeof(key);
++ ngx_memcpy(secret.key.data, key, sizeof(key));
++ secret.iv.len = NGX_QUIC_IV_LEN;
++
++ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ res->len = itag.data + itag.len - start;
++ res->data = start;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret,
++ ngx_str_t *salt, u_char *out, size_t len)
++{
++ size_t is_len, info_len;
++ uint8_t *p;
++ const EVP_MD *digest;
++
++ uint8_t is[SHA256_DIGEST_LENGTH];
++ uint8_t info[20];
++
++ digest = EVP_sha256();
++ is_len = SHA256_DIGEST_LENGTH;
++
++ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
++ salt->data, salt->len)
++ != NGX_OK)
++ {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "ngx_hkdf_extract(%s) failed", label);
++ return NGX_ERROR;
++ }
++
++ info[0] = 0;
++ info[1] = len;
++ info[2] = ngx_strlen(label);
++
++ info_len = 2 + 1 + info[2] + 1;
++
++ if (info_len >= 20) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "ngx_quic_create_key label \"%s\" too long", label);
++ return NGX_ERROR;
++ }
++
++ p = ngx_cpymem(&info[3], label, info[2]);
++ *p = '\0';
++
++ if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK)
++ {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "ngx_hkdf_expand(%s) failed", label);
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static uint64_t
++ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
++ uint64_t *largest_pn)
++{
++ u_char *p;
++ uint64_t truncated_pn, expected_pn, candidate_pn;
++ uint64_t pn_nbits, pn_win, pn_hwin, pn_mask;
++
++ pn_nbits = ngx_min(len * 8, 62);
++
++ p = *pos;
++ truncated_pn = *p++ ^ *mask++;
++
++ while (--len) {
++ truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++);
++ }
++
++ *pos = p;
++
++ expected_pn = *largest_pn + 1;
++ pn_win = 1ULL << pn_nbits;
++ pn_hwin = pn_win / 2;
++ pn_mask = pn_win - 1;
++
++ candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
++
++ if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin)
++ && candidate_pn < (1ULL << 62) - pn_win)
++ {
++ candidate_pn += pn_win;
++
++ } else if (candidate_pn > expected_pn + pn_hwin
++ && candidate_pn >= pn_win)
++ {
++ candidate_pn -= pn_win;
++ }
++
++ *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn);
++
++ return candidate_pn;
++}
++
++
++void
++ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn)
++{
++ nonce[len - 8] ^= (pn >> 56) & 0x3f;
++ nonce[len - 7] ^= (pn >> 48) & 0xff;
++ nonce[len - 6] ^= (pn >> 40) & 0xff;
++ nonce[len - 5] ^= (pn >> 32) & 0xff;
++ nonce[len - 4] ^= (pn >> 24) & 0xff;
++ nonce[len - 3] ^= (pn >> 16) & 0xff;
++ nonce[len - 2] ^= (pn >> 8) & 0xff;
++ nonce[len - 1] ^= pn & 0xff;
++}
++
++
++ngx_int_t
++ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res)
++{
++ if (ngx_quic_pkt_retry(pkt->flags)) {
++ return ngx_quic_create_retry_packet(pkt, res);
++ }
++
++ return ngx_quic_create_packet(pkt, res);
++}
++
++
++ngx_int_t
++ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn)
++{
++ u_char *p, *sample;
++ size_t len;
++ uint64_t pn, lpn;
++ ngx_int_t pnl, rc;
++ ngx_str_t in, ad;
++ ngx_uint_t key_phase;
++ ngx_quic_secret_t *secret;
++ ngx_quic_ciphers_t ciphers;
++ uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN];
++
++ if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ secret = &pkt->keys->secrets[pkt->level].client;
++
++ p = pkt->raw->pos;
++ len = pkt->data + pkt->len - p;
++
++ /*
++ * RFC 9001, 5.4.2. Header Protection Sample
++ * 5.4.3. AES-Based Header Protection
++ * 5.4.4. ChaCha20-Based Header Protection
++ *
++ * the Packet Number field is assumed to be 4 bytes long
++ * AES and ChaCha20 algorithms sample 16 bytes
++ */
++
++ if (len < EVP_GCM_TLS_TAG_LEN + 4) {
++ return NGX_DECLINED;
++ }
++
++ sample = p + 4;
++
++ /* header protection */
++
++ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
++ != NGX_OK)
++ {
++ return NGX_DECLINED;
++ }
++
++ pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags);
++
++ if (ngx_quic_short_pkt(pkt->flags)) {
++ key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0;
++
++ if (key_phase != pkt->key_phase) {
++ secret = &pkt->keys->next_key.client;
++ pkt->key_update = 1;
++ }
++ }
++
++ lpn = *largest_pn;
++
++ pnl = (pkt->flags & 0x03) + 1;
++ pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn);
++
++ pkt->pn = pn;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx clearflags:%xd", pkt->flags);
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx number:%uL len:%xi", pn, pnl);
++
++ /* packet protection */
++
++ in.data = p;
++ in.len = len - pnl;
++
++ ad.len = p - pkt->data;
++ ad.data = pkt->plaintext;
++
++ ngx_memcpy(ad.data, pkt->data, ad.len);
++ ad.data[0] = pkt->flags;
++
++ do {
++ ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256;
++ } while (--pnl);
++
++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
++ ngx_quic_compute_nonce(nonce, sizeof(nonce), pn);
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic ad len:%uz %xV", ad.len, &ad);
++#endif
++
++ pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN;
++ pkt->payload.data = pkt->plaintext + ad.len;
++
++ rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload,
++ nonce, &in, &ad, pkt->log);
++ if (rc != NGX_OK) {
++ return NGX_DECLINED;
++ }
++
++ if (pkt->payload.len == 0) {
++ /*
++ * RFC 9000, 12.4. Frames and Frame Types
++ *
++ * An endpoint MUST treat receipt of a packet containing no
++ * frames as a connection error of type PROTOCOL_VIOLATION.
++ */
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet");
++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ return NGX_ERROR;
++ }
++
++ if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) {
++ /*
++ * RFC 9000, Reserved Bits
++ *
++ * An endpoint MUST treat receipt of a packet that has
++ * a non-zero value for these bits, after removing both
++ * packet and header protection, as a connection error
++ * of type PROTOCOL_VIOLATION.
++ */
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic reserved bit set in packet");
++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ return NGX_ERROR;
++ }
++
++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet payload len:%uz %xV",
++ pkt->payload.len, &pkt->payload);
++#endif
++
++ *largest_pn = lpn;
++
++ return NGX_OK;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_protection.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_protection.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,114 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
++#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#include <ngx_event_quic_transport.h>
++
++
++#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1)
++
++/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */
++#define NGX_QUIC_IV_LEN 12
++
++/* largest hash used in TLS is SHA-384 */
++#define NGX_QUIC_MAX_MD_SIZE 48
++
++
++#ifdef OPENSSL_IS_BORINGSSL
++#define ngx_quic_cipher_t EVP_AEAD
++#else
++#define ngx_quic_cipher_t EVP_CIPHER
++#endif
++
++
++typedef struct {
++ size_t len;
++ u_char data[NGX_QUIC_MAX_MD_SIZE];
++} ngx_quic_md_t;
++
++
++typedef struct {
++ size_t len;
++ u_char data[NGX_QUIC_IV_LEN];
++} ngx_quic_iv_t;
++
++
++typedef struct {
++ ngx_quic_md_t secret;
++ ngx_quic_md_t key;
++ ngx_quic_iv_t iv;
++ ngx_quic_md_t hp;
++} ngx_quic_secret_t;
++
++
++typedef struct {
++ ngx_quic_secret_t client;
++ ngx_quic_secret_t server;
++} ngx_quic_secrets_t;
++
++
++struct ngx_quic_keys_s {
++ ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST];
++ ngx_quic_secrets_t next_key;
++ ngx_uint_t cipher;
++};
++
++
++typedef struct {
++ const ngx_quic_cipher_t *c;
++ const EVP_CIPHER *hp;
++ const EVP_MD *d;
++} ngx_quic_ciphers_t;
++
++
++typedef struct {
++ size_t out_len;
++ u_char *out;
++
++ size_t prk_len;
++ const uint8_t *prk;
++
++ size_t label_len;
++ const u_char *label;
++} ngx_quic_hkdf_t;
++
++#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \
++ (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \
++ (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \
++ (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label);
++
++
++ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys,
++ ngx_str_t *secret, ngx_log_t *log);
++ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log,
++ ngx_uint_t is_write, ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *secret, size_t secret_len);
++ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level);
++void ngx_quic_keys_discard(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level);
++void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys);
++ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys);
++ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res);
++ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn);
++void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
++ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers,
++ enum ssl_encryption_level_t level);
++ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher,
++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
++ ngx_str_t *ad, ngx_log_t *log);
++ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest,
++ ngx_log_t *log);
++
++
++#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_socket.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_socket.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,237 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++ngx_int_t
++ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc,
++ ngx_quic_header_t *pkt)
++{
++ ngx_quic_socket_t *qsock, *tmp;
++ ngx_quic_client_id_t *cid;
++
++ /*
++ * qc->path = NULL
++ *
++ * qc->nclient_ids = 0
++ * qc->nsockets = 0
++ * qc->max_retired_seqnum = 0
++ * qc->client_seqnum = 0
++ */
++
++ ngx_queue_init(&qc->sockets);
++ ngx_queue_init(&qc->free_sockets);
++
++ ngx_queue_init(&qc->paths);
++ ngx_queue_init(&qc->free_paths);
++
++ ngx_queue_init(&qc->client_ids);
++ ngx_queue_init(&qc->free_client_ids);
++
++ qc->tp.original_dcid.len = pkt->odcid.len;
++ qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid);
++ if (qc->tp.original_dcid.data == NULL) {
++ return NGX_ERROR;
++ }
++
++ /* socket to use for further processing (id auto-generated) */
++ qsock = ngx_quic_create_socket(c, qc);
++ if (qsock == NULL) {
++ return NGX_ERROR;
++ }
++
++ /* socket is listening at new server id */
++ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ qsock->used = 1;
++
++ qc->tp.initial_scid.len = qsock->sid.len;
++ qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len);
++ if (qc->tp.initial_scid.data == NULL) {
++ goto failed;
++ }
++ ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len);
++
++ /* for all packets except first, this is set at udp layer */
++ c->udp = &qsock->udp;
++
++ /* ngx_quic_get_connection(c) macro is now usable */
++
++ /* we have a client identified by scid */
++ cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL);
++ if (cid == NULL) {
++ goto failed;
++ }
++
++ /* path of the first packet is our initial active path */
++ qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid);
++ if (qc->path == NULL) {
++ goto failed;
++ }
++
++ qc->path->tag = NGX_QUIC_PATH_ACTIVE;
++
++ if (pkt->validated) {
++ qc->path->validated = 1;
++ qc->path->limited = 0;
++ }
++
++ ngx_quic_path_dbg(c, "set active", qc->path);
++
++ tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
++ if (tmp == NULL) {
++ goto failed;
++ }
++
++ tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */
++
++ ngx_memcpy(tmp->sid.id, pkt->odcid.data, pkt->odcid.len);
++ tmp->sid.len = pkt->odcid.len;
++
++ if (ngx_quic_listen(c, qc, tmp) != NGX_OK) {
++ goto failed;
++ }
++
++ return NGX_OK;
++
++failed:
++
++ ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
++ c->udp = NULL;
++
++ return NGX_ERROR;
++}
++
++
++ngx_quic_socket_t *
++ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *sock;
++
++ if (!ngx_queue_empty(&qc->free_sockets)) {
++
++ q = ngx_queue_head(&qc->free_sockets);
++ sock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ ngx_queue_remove(&sock->queue);
++
++ ngx_memzero(sock, sizeof(ngx_quic_socket_t));
++
++ } else {
++
++ sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
++ if (sock == NULL) {
++ return NULL;
++ }
++ }
++
++ sock->sid.len = NGX_QUIC_SERVER_CID_LEN;
++ if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) {
++ return NULL;
++ }
++
++ sock->sid.seqnum = qc->server_seqnum++;
++
++ return sock;
++}
++
++
++void
++ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_queue_remove(&qsock->queue);
++ ngx_queue_insert_head(&qc->free_sockets, &qsock->queue);
++
++ ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
++ qc->nsockets--;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic socket seq:%L closed nsock:%ui",
++ (int64_t) qsock->sid.seqnum, qc->nsockets);
++}
++
++
++ngx_int_t
++ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
++ ngx_quic_socket_t *qsock)
++{
++ ngx_str_t id;
++ ngx_quic_server_id_t *sid;
++
++ sid = &qsock->sid;
++
++ id.data = sid->id;
++ id.len = sid->len;
++
++ qsock->udp.connection = c;
++ qsock->udp.node.key = ngx_crc32_long(id.data, id.len);
++
++ ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node);
++
++ ngx_queue_insert_tail(&qc->sockets, &qsock->queue);
++
++ qc->nsockets++;
++ qsock->quic = qc;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic socket seq:%L listening at sid:%xV nsock:%ui",
++ (int64_t) sid->seqnum, &id, qc->nsockets);
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_close_sockets(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ while (!ngx_queue_empty(&qc->sockets)) {
++ q = ngx_queue_head(&qc->sockets);
++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ ngx_quic_close_socket(c, qsock);
++ }
++}
++
++
++ngx_quic_socket_t *
++ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->sockets);
++ q != ngx_queue_sentinel(&qc->sockets);
++ q = ngx_queue_next(q))
++ {
++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ if (qsock->sid.seqnum == seqnum) {
++ return qsock;
++ }
++ }
++
++ return NULL;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_socket.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_socket.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,28 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
++#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c,
++ ngx_quic_connection_t *qc, ngx_quic_header_t *pkt);
++void ngx_quic_close_sockets(ngx_connection_t *c);
++
++ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c,
++ ngx_quic_connection_t *qc);
++ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
++ ngx_quic_socket_t *qsock);
++void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock);
++
++ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum);
++
++
++#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ssl.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ssl.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,600 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#if defined OPENSSL_IS_BORINGSSL \
++ || defined LIBRESSL_VERSION_NUMBER \
++ || NGX_QUIC_OPENSSL_COMPAT
++#define NGX_QUIC_BORINGSSL_API 1
++#endif
++
++
++/*
++ * RFC 9000, 7.5. Cryptographic Message Buffering
++ *
++ * Implementations MUST support buffering at least 4096 bytes of data
++ */
++#define NGX_QUIC_MAX_BUFFERED 65535
++
++
++#if (NGX_QUIC_BORINGSSL_API)
++static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *secret, size_t secret_len);
++static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *secret, size_t secret_len);
++#else
++static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *read_secret,
++ const uint8_t *write_secret, size_t secret_len);
++#endif
++
++static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *data, size_t len);
++static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
++static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, uint8_t alert);
++static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data);
++
++
++#if (NGX_QUIC_BORINGSSL_API)
++
++static int
++ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *rsecret, size_t secret_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_set_read_secret() level:%d", level);
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic read secret len:%uz %*xs", secret_len,
++ secret_len, rsecret);
++#endif
++
++ if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level,
++ cipher, rsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ return 1;
++}
++
++
++static int
++ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *wsecret, size_t secret_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_set_write_secret() level:%d", level);
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic write secret len:%uz %*xs", secret_len,
++ secret_len, wsecret);
++#endif
++
++ if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
++ cipher, wsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ return 1;
++}
++
++#else
++
++static int
++ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *rsecret,
++ const uint8_t *wsecret, size_t secret_len)
++{
++ ngx_connection_t *c;
++ const SSL_CIPHER *cipher;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_set_encryption_secrets() level:%d", level);
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic read secret len:%uz %*xs", secret_len,
++ secret_len, rsecret);
++#endif
++
++ cipher = SSL_get_current_cipher(ssl_conn);
++
++ if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level,
++ cipher, rsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ if (level == ssl_encryption_early_data) {
++ return 1;
++ }
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic write secret len:%uz %*xs", secret_len,
++ secret_len, wsecret);
++#endif
++
++ if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
++ cipher, wsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ return 1;
++}
++
++#endif
++
++
++static int
++ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *data, size_t len)
++{
++ u_char *p, *end;
++ size_t client_params_len;
++ ngx_chain_t *out;
++ const uint8_t *client_params;
++ ngx_quic_tp_t ctp;
++ ngx_quic_frame_t *frame;
++ ngx_connection_t *c;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
++ unsigned int alpn_len;
++ const unsigned char *alpn_data;
++#endif
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_add_handshake_data");
++
++ if (!qc->client_tp_done) {
++ /*
++ * things to do once during handshake: check ALPN and transport
++ * parameters; we want to break handshake if something is wrong
++ * here;
++ */
++
++#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
++
++ SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len);
++
++ if (alpn_len == 0) {
++ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL);
++ qc->error_reason = "unsupported protocol in ALPN extension";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic unsupported protocol in ALPN extension");
++ return 0;
++ }
++
++#endif
++
++ SSL_get_peer_quic_transport_params(ssl_conn, &client_params,
++ &client_params_len);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic SSL_get_peer_quic_transport_params():"
++ " params_len:%ui", client_params_len);
++
++ if (client_params_len == 0) {
++ /* RFC 9001, 8.2. QUIC Transport Parameters Extension */
++ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION);
++ qc->error_reason = "missing transport parameters";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "missing transport parameters");
++ return 0;
++ }
++
++ p = (u_char *) client_params;
++ end = p + client_params_len;
++
++ /* defaults for parameters not sent by client */
++ ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t));
++
++ if (ngx_quic_parse_transport_params(p, end, &ctp, c->log)
++ != NGX_OK)
++ {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "failed to process transport parameters";
++
++ return 0;
++ }
++
++ if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) {
++ return 0;
++ }
++
++ qc->client_tp_done = 1;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, level);
++
++ out = ngx_quic_copy_buffer(c, (u_char *) data, len);
++ if (out == NGX_CHAIN_ERROR) {
++ return 0;
++ }
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return 0;
++ }
++
++ frame->data = out;
++ frame->level = level;
++ frame->type = NGX_QUIC_FT_CRYPTO;
++ frame->u.crypto.offset = ctx->crypto_sent;
++ frame->u.crypto.length = len;
++
++ ctx->crypto_sent += len;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return 1;
++}
++
++
++static int
++ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
++{
++#if (NGX_DEBUG)
++ ngx_connection_t *c;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_flush_flight()");
++#endif
++ return 1;
++}
++
++
++static int
++ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
++ uint8_t alert)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_send_alert() level:%s alert:%d",
++ ngx_quic_level_name(level), (int) alert);
++
++ /* already closed on regular shutdown */
++
++ qc = ngx_quic_get_connection(c);
++ if (qc == NULL) {
++ return 1;
++ }
++
++ qc->error = NGX_QUIC_ERR_CRYPTO(alert);
++ qc->error_reason = "handshake failed";
++
++ return 1;
++}
++
++
++ngx_int_t
++ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
++ ngx_quic_frame_t *frame)
++{
++ uint64_t last;
++ ngx_chain_t *cl;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++ ngx_quic_crypto_frame_t *f;
++
++ qc = ngx_quic_get_connection(c);
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++ f = &frame->u.crypto;
++
++ /* no overflow since both values are 62-bit */
++ last = f->offset + f->length;
++
++ if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) {
++ qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED;
++ return NGX_ERROR;
++ }
++
++ if (last <= ctx->crypto.offset) {
++ if (pkt->level == ssl_encryption_initial) {
++ /* speeding up handshake completion */
++
++ if (!ngx_queue_empty(&ctx->sent)) {
++ ngx_quic_resend_frames(c, ctx);
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
++ while (!ngx_queue_empty(&ctx->sent)) {
++ ngx_quic_resend_frames(c, ctx);
++ }
++ }
++ }
++
++ return NGX_OK;
++ }
++
++ if (f->offset == ctx->crypto.offset) {
++ if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_skip_buffer(c, &ctx->crypto, last);
++
++ } else {
++ if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length,
++ f->offset)
++ == NGX_CHAIN_ERROR)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1);
++
++ if (cl) {
++ if (ngx_quic_crypto_input(c, cl) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_free_chain(c, cl);
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data)
++{
++ int n, sslerr;
++ ngx_buf_t *b;
++ ngx_chain_t *cl;
++ ngx_ssl_conn_t *ssl_conn;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ssl_conn = c->ssl->connection;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
++ (int) SSL_quic_read_level(ssl_conn),
++ (int) SSL_quic_write_level(ssl_conn));
++
++ for (cl = data; cl; cl = cl->next) {
++ b = cl->buf;
++
++ if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn),
++ b->pos, b->last - b->pos))
++ {
++ ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
++ "SSL_provide_quic_data() failed");
++ return NGX_ERROR;
++ }
++ }
++
++ n = SSL_do_handshake(ssl_conn);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
++ (int) SSL_quic_read_level(ssl_conn),
++ (int) SSL_quic_write_level(ssl_conn));
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
++
++ if (n <= 0) {
++ sslerr = SSL_get_error(ssl_conn, n);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
++ sslerr);
++
++ if (sslerr != SSL_ERROR_WANT_READ) {
++
++ if (c->ssl->handshake_rejected) {
++ ngx_connection_error(c, 0, "handshake rejected");
++ ERR_clear_error();
++
++ return NGX_ERROR;
++ }
++
++ ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
++ return NGX_ERROR;
++ }
++ }
++
++ if (n <= 0 || SSL_in_init(ssl_conn)) {
++ if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data)
++ && qc->client_tp_done)
++ {
++ if (ngx_quic_init_streams(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++ }
++
++#if (NGX_DEBUG)
++ ngx_ssl_handshake_log(c);
++#endif
++
++ c->ssl->handshaked = 1;
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_HANDSHAKE_DONE;
++ ngx_quic_queue_frame(qc, frame);
++
++ if (qc->conf->retry) {
++ if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ /*
++ * RFC 9001, 9.5. Header Protection Timing Side Channels
++ *
++ * Generating next keys before a key update is received.
++ */
++
++ if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ /*
++ * RFC 9001, 4.9.2. Discarding Handshake Keys
++ *
++ * An endpoint MUST discard its Handshake keys
++ * when the TLS handshake is confirmed.
++ */
++ ngx_quic_discard_ctx(c, ssl_encryption_handshake);
++
++ /* start accepting clients on negotiated number of server ids */
++ if (ngx_quic_create_sockets(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_init_streams(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_init_connection(ngx_connection_t *c)
++{
++ u_char *p;
++ size_t clen;
++ ssize_t len;
++ ngx_str_t dcid;
++ ngx_ssl_conn_t *ssl_conn;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++ static SSL_QUIC_METHOD quic_method;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ c->ssl->no_wait_shutdown = 1;
++
++ ssl_conn = c->ssl->connection;
++
++ if (!quic_method.send_alert) {
++#if (NGX_QUIC_BORINGSSL_API)
++ quic_method.set_read_secret = ngx_quic_set_read_secret;
++ quic_method.set_write_secret = ngx_quic_set_write_secret;
++#else
++ quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets;
++#endif
++ quic_method.add_handshake_data = ngx_quic_add_handshake_data;
++ quic_method.flush_flight = ngx_quic_flush_flight;
++ quic_method.send_alert = ngx_quic_send_alert;
++ }
++
++ if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic SSL_set_quic_method() failed");
++ return NGX_ERROR;
++ }
++
++#ifdef OPENSSL_INFO_QUIC
++ if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) {
++ SSL_set_quic_early_data_enabled(ssl_conn, 1);
++ }
++#endif
++
++ qsock = ngx_quic_get_socket(c);
++
++ dcid.data = qsock->sid.id;
++ dcid.len = qsock->sid.len;
++
++ if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen);
++ /* always succeeds */
++
++ p = ngx_pnalloc(c->pool, len);
++ if (p == NULL) {
++ return NGX_ERROR;
++ }
++
++ len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL);
++ if (len < 0) {
++ return NGX_ERROR;
++ }
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic transport parameters len:%uz %*xs", len, len, p);
++#endif
++
++ if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic SSL_set_quic_transport_params() failed");
++ return NGX_ERROR;
++ }
++
++#ifdef OPENSSL_IS_BORINGSSL
++ if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic SSL_set_quic_early_data_context() failed");
++ return NGX_ERROR;
++ }
++#endif
++
++ return NGX_OK;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_ssl.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ssl.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,19 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_
++#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
++
++ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
++
++#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_streams.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_streams.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,1779 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_STREAM_GONE (void *) -1
++
++
++static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs,
++ ngx_uint_t err);
++static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c);
++static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c);
++static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id);
++static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id);
++static void ngx_quic_init_stream_handler(ngx_event_t *ev);
++static void ngx_quic_init_streams_handler(ngx_connection_t *c);
++static ngx_int_t ngx_quic_do_init_streams(ngx_connection_t *c);
++static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c,
++ uint64_t id);
++static void ngx_quic_empty_handler(ngx_event_t *ev);
++static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf,
++ size_t size);
++static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf,
++ size_t size);
++static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c,
++ ngx_chain_t *in, off_t limit);
++static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs);
++static void ngx_quic_stream_cleanup_handler(void *data);
++static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs);
++static ngx_int_t ngx_quic_can_shutdown(ngx_connection_t *c);
++static ngx_int_t ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last);
++static ngx_int_t ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last);
++static ngx_int_t ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs);
++static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c);
++static void ngx_quic_set_event(ngx_event_t *ev);
++
++
++ngx_connection_t *
++ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi)
++{
++ uint64_t id;
++ ngx_connection_t *pc, *sc;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ pc = c->quic ? c->quic->parent : c;
++ qc = ngx_quic_get_connection(pc);
++
++ if (qc->closing) {
++ return NULL;
++ }
++
++ if (bidi) {
++ if (qc->streams.server_streams_bidi
++ >= qc->streams.server_max_streams_bidi)
++ {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic too many server bidi streams:%uL",
++ qc->streams.server_streams_bidi);
++ return NULL;
++ }
++
++ id = (qc->streams.server_streams_bidi << 2)
++ | NGX_QUIC_STREAM_SERVER_INITIATED;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic creating server bidi stream"
++ " streams:%uL max:%uL id:0x%xL",
++ qc->streams.server_streams_bidi,
++ qc->streams.server_max_streams_bidi, id);
++
++ qc->streams.server_streams_bidi++;
++
++ } else {
++ if (qc->streams.server_streams_uni
++ >= qc->streams.server_max_streams_uni)
++ {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic too many server uni streams:%uL",
++ qc->streams.server_streams_uni);
++ return NULL;
++ }
++
++ id = (qc->streams.server_streams_uni << 2)
++ | NGX_QUIC_STREAM_SERVER_INITIATED
++ | NGX_QUIC_STREAM_UNIDIRECTIONAL;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic creating server uni stream"
++ " streams:%uL max:%uL id:0x%xL",
++ qc->streams.server_streams_uni,
++ qc->streams.server_max_streams_uni, id);
++
++ qc->streams.server_streams_uni++;
++ }
++
++ qs = ngx_quic_create_stream(pc, id);
++ if (qs == NULL) {
++ return NULL;
++ }
++
++ sc = qs->connection;
++
++ sc->write->active = 1;
++ sc->write->ready = 1;
++
++ if (bidi) {
++ sc->read->active = 1;
++ }
++
++ return sc;
++}
++
++
++void
++ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
++{
++ ngx_rbtree_node_t **p;
++ ngx_quic_stream_t *qn, *qnt;
++
++ for ( ;; ) {
++ qn = (ngx_quic_stream_t *) node;
++ qnt = (ngx_quic_stream_t *) temp;
++
++ p = (qn->id < qnt->id) ? &temp->left : &temp->right;
++
++ if (*p == sentinel) {
++ break;
++ }
++
++ temp = *p;
++ }
++
++ *p = node;
++ node->parent = temp;
++ node->left = sentinel;
++ node->right = sentinel;
++ ngx_rbt_red(node);
++}
++
++
++ngx_quic_stream_t *
++ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id)
++{
++ ngx_rbtree_node_t *node, *sentinel;
++ ngx_quic_stream_t *qn;
++
++ node = rbtree->root;
++ sentinel = rbtree->sentinel;
++
++ while (node != sentinel) {
++ qn = (ngx_quic_stream_t *) node;
++
++ if (id == qn->id) {
++ return qn;
++ }
++
++ node = (id < qn->id) ? node->left : node->right;
++ }
++
++ return NULL;
++}
++
++
++ngx_int_t
++ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc)
++{
++ ngx_pool_t *pool;
++ ngx_queue_t *q;
++ ngx_rbtree_t *tree;
++ ngx_connection_t *sc;
++ ngx_rbtree_node_t *node;
++ ngx_quic_stream_t *qs;
++
++ while (!ngx_queue_empty(&qc->streams.uninitialized)) {
++ q = ngx_queue_head(&qc->streams.uninitialized);
++ ngx_queue_remove(q);
++
++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
++ pool = qs->connection->pool;
++
++ ngx_close_connection(qs->connection);
++ ngx_destroy_pool(pool);
++ }
++
++ tree = &qc->streams.tree;
++
++ if (tree->root == tree->sentinel) {
++ return NGX_OK;
++ }
++
++ node = ngx_rbtree_min(tree->root, tree->sentinel);
++
++ while (node) {
++ qs = (ngx_quic_stream_t *) node;
++ node = ngx_rbtree_next(tree, node);
++ sc = qs->connection;
++
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;
++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT;
++
++ if (sc == NULL) {
++ ngx_quic_close_stream(qs);
++ continue;
++ }
++
++ sc->read->error = 1;
++ sc->write->error = 1;
++
++ ngx_quic_set_event(sc->read);
++ ngx_quic_set_event(sc->write);
++
++ sc->close = 1;
++ sc->read->handler(sc->read);
++ }
++
++ if (tree->root == tree->sentinel) {
++ return NGX_OK;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic connection has active streams");
++
++ return NGX_AGAIN;
++}
++
++
++ngx_int_t
++ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err)
++{
++ return ngx_quic_do_reset_stream(c->quic, err);
++}
++
++
++static ngx_int_t
++ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err)
++{
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD
++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT
++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD)
++ {
++ return NGX_OK;
++ }
++
++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT;
++ qs->send_final_size = qs->send_offset;
++
++ if (qs->connection) {
++ qs->connection->write->error = 1;
++ }
++
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL reset", qs->id);
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_RESET_STREAM;
++ frame->u.reset_stream.id = qs->id;
++ frame->u.reset_stream.error_code = err;
++ frame->u.reset_stream.final_size = qs->send_offset;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ ngx_quic_free_buffer(pc, &qs->send);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_shutdown_stream(ngx_connection_t *c, int how)
++{
++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) {
++ if (ngx_quic_shutdown_stream_send(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) {
++ if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_shutdown_stream_send(ngx_connection_t *c)
++{
++ ngx_quic_stream_t *qs;
++
++ qs = c->quic;
++
++ if (qs->send_state != NGX_QUIC_STREAM_SEND_READY
++ && qs->send_state != NGX_QUIC_STREAM_SEND_SEND)
++ {
++ return NGX_OK;
++ }
++
++ qs->send_state = NGX_QUIC_STREAM_SEND_SEND;
++ qs->send_final_size = c->sent;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0,
++ "quic stream id:0x%xL send shutdown", qs->id);
++
++ return ngx_quic_stream_flush(qs);
++}
++
++
++static ngx_int_t
++ngx_quic_shutdown_stream_recv(ngx_connection_t *c)
++{
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++
++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV
++ && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN)
++ {
++ return NGX_OK;
++ }
++
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (qc->conf->stream_close_code == 0) {
++ return NGX_OK;
++ }
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL recv shutdown", qs->id);
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_STOP_SENDING;
++ frame->u.stop_sending.id = qs->id;
++ frame->u.stop_sending.error_code = qc->conf->stream_close_code;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++static ngx_quic_stream_t *
++ngx_quic_get_stream(ngx_connection_t *c, uint64_t id)
++{
++ uint64_t min_id;
++ ngx_event_t *rev;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ qs = ngx_quic_find_stream(&qc->streams.tree, id);
++
++ if (qs) {
++ return qs;
++ }
++
++ if (qc->shutdown || qc->closing) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL is missing", id);
++
++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ if ((id >> 2) < qc->streams.server_streams_uni) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NULL;
++ }
++
++ if ((id >> 2) < qc->streams.client_streams_uni) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ if ((id >> 2) >= qc->streams.client_max_streams_uni) {
++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
++ return NULL;
++ }
++
++ min_id = (qc->streams.client_streams_uni << 2)
++ | NGX_QUIC_STREAM_UNIDIRECTIONAL;
++ qc->streams.client_streams_uni = (id >> 2) + 1;
++
++ } else {
++
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ if ((id >> 2) < qc->streams.server_streams_bidi) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NULL;
++ }
++
++ if ((id >> 2) < qc->streams.client_streams_bidi) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ if ((id >> 2) >= qc->streams.client_max_streams_bidi) {
++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
++ return NULL;
++ }
++
++ min_id = (qc->streams.client_streams_bidi << 2);
++ qc->streams.client_streams_bidi = (id >> 2) + 1;
++ }
++
++ /*
++ * RFC 9000, 2.1. Stream Types and Identifiers
++ *
++ * successive streams of each type are created with numerically increasing
++ * stream IDs. A stream ID that is used out of order results in all
++ * streams of that type with lower-numbered stream IDs also being opened.
++ */
++
++#if (NGX_SUPPRESS_WARN)
++ qs = NULL;
++#endif
++
++ for ( /* void */ ; min_id <= id; min_id += 0x04) {
++
++ qs = ngx_quic_create_stream(c, min_id);
++
++ if (qs == NULL) {
++ if (ngx_quic_reject_stream(c, min_id) != NGX_OK) {
++ return NULL;
++ }
++
++ continue;
++ }
++
++ ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue);
++
++ rev = qs->connection->read;
++ rev->handler = ngx_quic_init_stream_handler;
++
++ if (qc->streams.initialized) {
++ ngx_post_event(rev, &ngx_posted_events);
++
++ if (qc->push.posted) {
++ /*
++ * The posted stream can produce output immediately.
++ * By postponing the push event, we coalesce the stream
++ * output with queued frames in one UDP datagram.
++ */
++
++ ngx_delete_posted_event(&qc->push);
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++ }
++ }
++
++ if (qs == NULL) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ return qs;
++}
++
++
++static ngx_int_t
++ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id)
++{
++ uint64_t code;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ ? qc->conf->stream_reject_code_uni
++ : qc->conf->stream_reject_code_bidi;
++
++ if (code == 0) {
++ return NGX_DECLINED;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL reject err:0x%xL", id, code);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_RESET_STREAM;
++ frame->u.reset_stream.id = id;
++ frame->u.reset_stream.error_code = code;
++ frame->u.reset_stream.final_size = 0;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_STOP_SENDING;
++ frame->u.stop_sending.id = id;
++ frame->u.stop_sending.error_code = code;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_init_stream_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++ ngx_quic_stream_t *qs;
++
++ c = ev->data;
++ qs = c->quic;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream");
++
++ if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) {
++ c->write->active = 1;
++ c->write->ready = 1;
++ }
++
++ c->read->active = 1;
++
++ ngx_queue_remove(&qs->queue);
++
++ c->listening->handler(c);
++}
++
++
++ngx_int_t
++ngx_quic_init_streams(ngx_connection_t *c)
++{
++ ngx_int_t rc;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc->streams.initialized) {
++ return NGX_OK;
++ }
++
++ rc = ngx_ssl_ocsp_validate(c);
++
++ if (rc == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_AGAIN) {
++ c->ssl->handler = ngx_quic_init_streams_handler;
++ return NGX_OK;
++ }
++
++ return ngx_quic_do_init_streams(c);
++}
++
++
++static void
++ngx_quic_init_streams_handler(ngx_connection_t *c)
++{
++ if (ngx_quic_do_init_streams(c) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_do_init_streams(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams");
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc->conf->init) {
++ if (qc->conf->init(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ for (q = ngx_queue_head(&qc->streams.uninitialized);
++ q != ngx_queue_sentinel(&qc->streams.uninitialized);
++ q = ngx_queue_next(q))
++ {
++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
++ ngx_post_event(qs->connection->read, &ngx_posted_events);
++ }
++
++ qc->streams.initialized = 1;
++
++ return NGX_OK;
++}
++
++
++static ngx_quic_stream_t *
++ngx_quic_create_stream(ngx_connection_t *c, uint64_t id)
++{
++ ngx_log_t *log;
++ ngx_pool_t *pool;
++ ngx_uint_t reusable;
++ ngx_queue_t *q;
++ ngx_connection_t *sc;
++ ngx_quic_stream_t *qs;
++ ngx_pool_cleanup_t *cln;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL create", id);
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!ngx_queue_empty(&qc->streams.free)) {
++ q = ngx_queue_head(&qc->streams.free);
++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
++ ngx_queue_remove(&qs->queue);
++
++ } else {
++ /*
++ * the number of streams is limited by transport
++ * parameters and application requirements
++ */
++
++ qs = ngx_palloc(c->pool, sizeof(ngx_quic_stream_t));
++ if (qs == NULL) {
++ return NULL;
++ }
++ }
++
++ ngx_memzero(qs, sizeof(ngx_quic_stream_t));
++
++ qs->node.key = id;
++ qs->parent = c;
++ qs->id = id;
++ qs->send_final_size = (uint64_t) -1;
++ qs->recv_final_size = (uint64_t) -1;
++
++ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log);
++ if (pool == NULL) {
++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
++ return NULL;
++ }
++
++ log = ngx_palloc(pool, sizeof(ngx_log_t));
++ if (log == NULL) {
++ ngx_destroy_pool(pool);
++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
++ return NULL;
++ }
++
++ *log = *c->log;
++ pool->log = log;
++
++ reusable = c->reusable;
++ ngx_reusable_connection(c, 0);
++
++ sc = ngx_get_connection(c->fd, log);
++ if (sc == NULL) {
++ ngx_destroy_pool(pool);
++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
++ ngx_reusable_connection(c, reusable);
++ return NULL;
++ }
++
++ qs->connection = sc;
++
++ sc->quic = qs;
++ sc->shared = 1;
++ sc->type = SOCK_STREAM;
++ sc->pool = pool;
++ sc->ssl = c->ssl;
++ sc->sockaddr = c->sockaddr;
++ sc->listening = c->listening;
++ sc->addr_text = c->addr_text;
++ sc->local_sockaddr = c->local_sockaddr;
++ sc->local_socklen = c->local_socklen;
++ sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
++ sc->start_time = c->start_time;
++ sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
++
++ sc->recv = ngx_quic_stream_recv;
++ sc->send = ngx_quic_stream_send;
++ sc->send_chain = ngx_quic_stream_send_chain;
++
++ sc->read->log = log;
++ sc->write->log = log;
++
++ sc->read->handler = ngx_quic_empty_handler;
++ sc->write->handler = ngx_quic_empty_handler;
++
++ log->connection = sc->number;
++
++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ qs->send_max_data = qc->ctp.initial_max_stream_data_uni;
++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ;
++ qs->send_state = NGX_QUIC_STREAM_SEND_READY;
++
++ } else {
++ qs->recv_max_data = qc->tp.initial_max_stream_data_uni;
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RECV;
++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD;
++ }
++
++ } else {
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote;
++ qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local;
++
++ } else {
++ qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local;
++ qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote;
++ }
++
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RECV;
++ qs->send_state = NGX_QUIC_STREAM_SEND_READY;
++ }
++
++ qs->recv_window = qs->recv_max_data;
++
++ cln = ngx_pool_cleanup_add(pool, 0);
++ if (cln == NULL) {
++ ngx_close_connection(sc);
++ ngx_destroy_pool(pool);
++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
++ ngx_reusable_connection(c, reusable);
++ return NULL;
++ }
++
++ cln->handler = ngx_quic_stream_cleanup_handler;
++ cln->data = sc;
++
++ ngx_rbtree_insert(&qc->streams.tree, &qs->node);
++
++ return qs;
++}
++
++
++void
++ngx_quic_cancelable_stream(ngx_connection_t *c)
++{
++ ngx_connection_t *pc;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (!qs->cancelable) {
++ qs->cancelable = 1;
++
++ if (ngx_quic_can_shutdown(pc) == NGX_OK) {
++ ngx_reusable_connection(pc, 1);
++
++ if (qc->shutdown) {
++ ngx_quic_shutdown_quic(pc);
++ }
++ }
++ }
++}
++
++
++static void
++ngx_quic_empty_handler(ngx_event_t *ev)
++{
++}
++
++
++static ssize_t
++ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size)
++{
++ ssize_t len;
++ ngx_buf_t *b;
++ ngx_chain_t *cl, *in;
++ ngx_event_t *rev;
++ ngx_connection_t *pc;
++ ngx_quic_stream_t *qs;
++
++ qs = c->quic;
++ pc = qs->parent;
++ rev = c->read;
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
++ || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
++ {
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ;
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL recv buf:%uz", qs->id, size);
++
++ if (size == 0) {
++ return 0;
++ }
++
++ in = ngx_quic_read_buffer(pc, &qs->recv, size);
++ if (in == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ len = 0;
++
++ for (cl = in; cl; cl = cl->next) {
++ b = cl->buf;
++ len += b->last - b->pos;
++ buf = ngx_cpymem(buf, b->pos, b->last - b->pos);
++ }
++
++ ngx_quic_free_chain(pc, in);
++
++ if (len == 0) {
++ rev->ready = 0;
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD
++ && qs->recv_offset == qs->recv_final_size)
++ {
++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ;
++ }
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) {
++ rev->eof = 1;
++ return 0;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv() not ready", qs->id);
++ return NGX_AGAIN;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv len:%z", qs->id, len);
++
++ if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return len;
++}
++
++
++static ssize_t
++ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size)
++{
++ ngx_buf_t b;
++ ngx_chain_t cl;
++
++ ngx_memzero(&b, sizeof(ngx_buf_t));
++
++ b.memory = 1;
++ b.pos = buf;
++ b.last = buf + size;
++
++ cl.buf = &b;
++ cl.next = NULL;
++
++ if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (b.pos == buf) {
++ return NGX_AGAIN;
++ }
++
++ return b.pos - buf;
++}
++
++
++static ngx_chain_t *
++ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
++{
++ uint64_t n, flow;
++ ngx_event_t *wev;
++ ngx_connection_t *pc;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++ wev = c->write;
++
++ if (qs->send_state != NGX_QUIC_STREAM_SEND_READY
++ && qs->send_state != NGX_QUIC_STREAM_SEND_SEND)
++ {
++ wev->error = 1;
++ return NGX_CHAIN_ERROR;
++ }
++
++ qs->send_state = NGX_QUIC_STREAM_SEND_SEND;
++
++ flow = qs->acked + qc->conf->stream_buffer_size - qs->sent;
++
++ if (flow == 0) {
++ wev->ready = 0;
++ return in;
++ }
++
++ if (limit == 0 || limit > (off_t) flow) {
++ limit = flow;
++ }
++
++ n = qs->send.size;
++
++ in = ngx_quic_write_buffer(pc, &qs->send, in, limit, qs->sent);
++ if (in == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ n = qs->send.size - n;
++ c->sent += n;
++ qs->sent += n;
++ qc->streams.sent += n;
++
++ if (flow == n) {
++ wev->ready = 0;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send_chain sent:%uL", n);
++
++ if (ngx_quic_stream_flush(qs) != NGX_OK) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ return in;
++}
++
++
++static ngx_int_t
++ngx_quic_stream_flush(ngx_quic_stream_t *qs)
++{
++ off_t limit, len;
++ ngx_uint_t last;
++ ngx_chain_t *out;
++ ngx_quic_frame_t *frame;
++ ngx_connection_t *pc;
++ ngx_quic_connection_t *qc;
++
++ if (qs->send_state != NGX_QUIC_STREAM_SEND_SEND) {
++ return NGX_OK;
++ }
++
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (qc->streams.send_max_data == 0) {
++ qc->streams.send_max_data = qc->ctp.initial_max_data;
++ }
++
++ limit = ngx_min(qc->streams.send_max_data - qc->streams.send_offset,
++ qs->send_max_data - qs->send_offset);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL flush limit:%O", qs->id, limit);
++
++ len = qs->send.offset;
++
++ out = ngx_quic_read_buffer(pc, &qs->send, limit);
++ if (out == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ len = qs->send.offset - len;
++ last = 0;
++
++ if (qs->send_final_size != (uint64_t) -1
++ && qs->send_final_size == qs->send.offset)
++ {
++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT;
++ last = 1;
++ }
++
++ if (len == 0 && !last) {
++ return NGX_OK;
++ }
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_STREAM;
++ frame->data = out;
++
++ frame->u.stream.off = 1;
++ frame->u.stream.len = 1;
++ frame->u.stream.fin = last;
++
++ frame->u.stream.stream_id = qs->id;
++ frame->u.stream.offset = qs->send_offset;
++ frame->u.stream.length = len;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ qs->send_offset += len;
++ qc->streams.send_offset += len;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL flush len:%O last:%ui",
++ qs->id, len, last);
++
++ if (qs->connection == NULL) {
++ return ngx_quic_close_stream(qs);
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_stream_cleanup_handler(void *data)
++{
++ ngx_connection_t *c = data;
++
++ ngx_quic_stream_t *qs;
++
++ qs = c->quic;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0,
++ "quic stream id:0x%xL cleanup", qs->id);
++
++ if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ qs->connection = NULL;
++
++ if (ngx_quic_close_stream(qs) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_close_stream(ngx_quic_stream_t *qs)
++{
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (!qc->closing) {
++ /* make sure everything is sent and final size is received */
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV) {
++ return NGX_OK;
++ }
++
++ if (qs->send_state != NGX_QUIC_STREAM_SEND_DATA_RECVD
++ && qs->send_state != NGX_QUIC_STREAM_SEND_RESET_RECVD)
++ {
++ return NGX_OK;
++ }
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL close", qs->id);
++
++ ngx_quic_free_buffer(pc, &qs->send);
++ ngx_quic_free_buffer(pc, &qs->recv);
++
++ ngx_rbtree_delete(&qc->streams.tree, &qs->node);
++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue);
++
++ if (qc->closing) {
++ /* schedule handler call to continue ngx_quic_close_connection() */
++ ngx_post_event(&qc->close, &ngx_posted_events);
++ return NGX_OK;
++ }
++
++ if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) {
++ ngx_reusable_connection(pc, 1);
++ }
++
++ if (qc->shutdown) {
++ ngx_quic_shutdown_quic(pc);
++ return NGX_OK;
++ }
++
++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) {
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_MAX_STREAMS;
++
++ if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++ frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni;
++ frame->u.max_streams.bidi = 0;
++
++ } else {
++ frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi;
++ frame->u.max_streams.bidi = 1;
++ }
++
++ ngx_quic_queue_frame(qc, frame);
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_can_shutdown(ngx_connection_t *c)
++{
++ ngx_rbtree_t *tree;
++ ngx_rbtree_node_t *node;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ tree = &qc->streams.tree;
++
++ if (tree->root != tree->sentinel) {
++ for (node = ngx_rbtree_min(tree->root, tree->sentinel);
++ node;
++ node = ngx_rbtree_next(tree, node))
++ {
++ qs = (ngx_quic_stream_t *) node;
++
++ if (!qs->cancelable) {
++ return NGX_DECLINED;
++ }
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
++ ngx_quic_frame_t *frame)
++{
++ uint64_t last;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++ ngx_quic_stream_frame_t *f;
++
++ qc = ngx_quic_get_connection(c);
++ f = &frame->u.stream;
++
++ if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED))
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ /* no overflow since both values are 62-bit */
++ last = f->offset + f->length;
++
++ qs = ngx_quic_get_stream(c, f->stream_id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV
++ && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN)
++ {
++ return NGX_OK;
++ }
++
++ if (ngx_quic_control_flow(qs, last) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (last < qs->recv_offset) {
++ return NGX_OK;
++ }
++
++ if (f->fin) {
++ if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last)
++ {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (qs->recv_last > last) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs->recv_final_size = last;
++ qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN;
++ }
++
++ if (ngx_quic_write_buffer(c, &qs->recv, frame->data, f->length, f->offset)
++ == NGX_CHAIN_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN
++ && qs->recv.size == qs->recv_final_size)
++ {
++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD;
++ }
++
++ if (qs->connection == NULL) {
++ return ngx_quic_close_stream(qs);
++ }
++
++ if (f->offset <= qs->recv_offset) {
++ ngx_quic_set_event(qs->connection->read);
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_max_data_frame(ngx_connection_t *c,
++ ngx_quic_max_data_frame_t *f)
++{
++ ngx_rbtree_t *tree;
++ ngx_rbtree_node_t *node;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ tree = &qc->streams.tree;
++
++ if (f->max_data <= qc->streams.send_max_data) {
++ return NGX_OK;
++ }
++
++ if (tree->root == tree->sentinel
++ || qc->streams.send_offset < qc->streams.send_max_data)
++ {
++ /* not blocked on MAX_DATA */
++ qc->streams.send_max_data = f->max_data;
++ return NGX_OK;
++ }
++
++ qc->streams.send_max_data = f->max_data;
++ node = ngx_rbtree_min(tree->root, tree->sentinel);
++
++ while (node && qc->streams.send_offset < qc->streams.send_max_data) {
++
++ qs = (ngx_quic_stream_t *) node;
++ node = ngx_rbtree_next(tree, node);
++
++ if (ngx_quic_stream_flush(qs) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f)
++{
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f)
++{
++ return ngx_quic_update_max_data(c);
++}
++
++
++ngx_int_t
++ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f)
++{
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ return ngx_quic_update_max_stream_data(qs);
++}
++
++
++ngx_int_t
++ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f)
++{
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ if (f->limit <= qs->send_max_data) {
++ return NGX_OK;
++ }
++
++ if (qs->send_offset < qs->send_max_data) {
++ /* not blocked on MAX_STREAM_DATA */
++ qs->send_max_data = f->limit;
++ return NGX_OK;
++ }
++
++ qs->send_max_data = f->limit;
++
++ return ngx_quic_stream_flush(qs);
++}
++
++
++ngx_int_t
++ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f)
++{
++ ngx_event_t *rev;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD
++ || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ)
++ {
++ return NGX_OK;
++ }
++
++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD;
++
++ if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (qs->recv_final_size != (uint64_t) -1
++ && qs->recv_final_size != f->final_size)
++ {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (qs->recv_last > f->final_size) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs->recv_final_size = f->final_size;
++
++ if (ngx_quic_update_flow(qs, qs->recv_final_size) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (qs->connection == NULL) {
++ return ngx_quic_close_stream(qs);
++ }
++
++ rev = qs->connection->read;
++ rev->error = 1;
++
++ ngx_quic_set_event(rev);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f)
++{
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ if (ngx_quic_do_reset_stream(qs, f->error_code) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (qs->connection == NULL) {
++ return ngx_quic_close_stream(qs);
++ }
++
++ ngx_quic_set_event(qs->connection->write);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (f->bidi) {
++ if (qc->streams.server_max_streams_bidi < f->limit) {
++ qc->streams.server_max_streams_bidi = f->limit;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic max_streams_bidi:%uL", f->limit);
++ }
++
++ } else {
++ if (qc->streams.server_max_streams_uni < f->limit) {
++ qc->streams.server_max_streams_uni = f->limit;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic max_streams_uni:%uL", f->limit);
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
++{
++ uint64_t acked;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ switch (f->type) {
++
++ case NGX_QUIC_FT_RESET_STREAM:
++
++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.reset_stream.id);
++ if (qs == NULL) {
++ return;
++ }
++
++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_RECVD;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL ack reset final_size:%uL",
++ qs->id, f->u.reset_stream.final_size);
++
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++
++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
++ if (qs == NULL) {
++ return;
++ }
++
++ acked = qs->acked;
++ qs->acked += f->u.stream.length;
++
++ if (f->u.stream.fin) {
++ qs->fin_acked = 1;
++ }
++
++ if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_SENT
++ && qs->acked == qs->sent && qs->fin_acked)
++ {
++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD;
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL ack len:%uL fin:%d unacked:%uL",
++ qs->id, f->u.stream.length, f->u.stream.fin,
++ qs->sent - qs->acked);
++
++ if (qs->connection
++ && qs->sent - acked == qc->conf->stream_buffer_size
++ && f->u.stream.length > 0)
++ {
++ ngx_quic_set_event(qs->connection->write);
++ }
++
++ break;
++
++ default:
++ return;
++ }
++
++ if (qs->connection == NULL) {
++ ngx_quic_close_stream(qs);
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last)
++{
++ uint64_t len;
++ ngx_connection_t *pc;
++ ngx_quic_connection_t *qc;
++
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (last <= qs->recv_last) {
++ return NGX_OK;
++ }
++
++ len = last - qs->recv_last;
++
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL flow control msd:%uL/%uL md:%uL/%uL",
++ qs->id, last, qs->recv_max_data, qc->streams.recv_last + len,
++ qc->streams.recv_max_data);
++
++ qs->recv_last += len;
++
++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV
++ && qs->recv_last > qs->recv_max_data)
++ {
++ qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR;
++ return NGX_ERROR;
++ }
++
++ qc->streams.recv_last += len;
++
++ if (qc->streams.recv_last > qc->streams.recv_max_data) {
++ qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR;
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last)
++{
++ uint64_t len;
++ ngx_connection_t *pc;
++ ngx_quic_connection_t *qc;
++
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (last <= qs->recv_offset) {
++ return NGX_OK;
++ }
++
++ len = last - qs->recv_offset;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL flow update %uL", qs->id, last);
++
++ qs->recv_offset += len;
++
++ if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) {
++ if (ngx_quic_update_max_stream_data(qs) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ qc->streams.recv_offset += len;
++
++ if (qc->streams.recv_max_data
++ <= qc->streams.recv_offset + qc->streams.recv_window / 2)
++ {
++ if (ngx_quic_update_max_data(pc) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs)
++{
++ uint64_t recv_max_data;
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) {
++ return NGX_OK;
++ }
++
++ recv_max_data = qs->recv_offset + qs->recv_window;
++
++ if (qs->recv_max_data == recv_max_data) {
++ return NGX_OK;
++ }
++
++ qs->recv_max_data = recv_max_data;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
++ "quic stream id:0x%xL flow update msd:%uL",
++ qs->id, qs->recv_max_data);
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_MAX_STREAM_DATA;
++ frame->u.max_stream_data.id = qs->id;
++ frame->u.max_stream_data.limit = qs->recv_max_data;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_update_max_data(ngx_connection_t *c)
++{
++ uint64_t recv_max_data;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ recv_max_data = qc->streams.recv_offset + qc->streams.recv_window;
++
++ if (qc->streams.recv_max_data == recv_max_data) {
++ return NGX_OK;
++ }
++
++ qc->streams.recv_max_data = recv_max_data;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic flow update md:%uL", qc->streams.recv_max_data);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_MAX_DATA;
++ frame->u.max_data.max_data = qc->streams.recv_max_data;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_set_event(ngx_event_t *ev)
++{
++ ev->ready = 1;
++
++ if (ev->active) {
++ ngx_post_event(ev, &ngx_posted_events);
++ }
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_streams.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_streams.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,44 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
++#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
++void ngx_quic_handle_stream_ack(ngx_connection_t *c,
++ ngx_quic_frame_t *f);
++ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c,
++ ngx_quic_max_data_frame_t *f);
++ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f);
++ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f);
++ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f);
++ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f);
++ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f);
++ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f);
++ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f);
++
++ngx_int_t ngx_quic_init_streams(ngx_connection_t *c);
++void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
++ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree,
++ uint64_t id);
++ngx_int_t ngx_quic_close_streams(ngx_connection_t *c,
++ ngx_quic_connection_t *qc);
++
++#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_tokens.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_tokens.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,289 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_sha1.h>
++#include <ngx_event_quic_connection.h>
++
++
++static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
++ ngx_uint_t no_port, u_char buf[20]);
++
++
++ngx_int_t
++ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret,
++ u_char *token)
++{
++ ngx_str_t tmp;
++
++ tmp.data = secret;
++ tmp.len = NGX_QUIC_SR_KEY_LEN;
++
++ if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token,
++ NGX_QUIC_SR_TOKEN_LEN)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stateless reset token %*xs",
++ (size_t) NGX_QUIC_SR_TOKEN_LEN, token);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr,
++ socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
++ time_t exp, ngx_uint_t is_retry)
++{
++ int len, iv_len;
++ u_char *p, *iv;
++ EVP_CIPHER_CTX *ctx;
++ const EVP_CIPHER *cipher;
++
++ u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
++
++ ngx_quic_address_hash(sockaddr, socklen, !is_retry, in);
++
++ p = in + 20;
++
++ p = ngx_cpymem(p, &exp, sizeof(time_t));
++
++ *p++ = is_retry ? 1 : 0;
++
++ if (odcid) {
++ *p++ = odcid->len;
++ p = ngx_cpymem(p, odcid->data, odcid->len);
++
++ } else {
++ *p++ = 0;
++ }
++
++ len = p - in;
++
++ cipher = EVP_aes_256_cbc();
++ iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
++
++ if ((size_t) (iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) > token->len)
++ {
++ ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small");
++ return NGX_ERROR;
++ }
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ iv = token->data;
++
++ if (RAND_bytes(iv, iv_len) <= 0
++ || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv))
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ token->len = iv_len;
++
++ if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ token->len += len;
++
++ if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ token->len += len;
++
++ EVP_CIPHER_CTX_free(ctx);
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic new token len:%uz %xV", token->len, token);
++#endif
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
++ ngx_uint_t no_port, u_char buf[20])
++{
++ size_t len;
++ u_char *data;
++ ngx_sha1_t sha1;
++ struct sockaddr_in *sin;
++#if (NGX_HAVE_INET6)
++ struct sockaddr_in6 *sin6;
++#endif
++
++ len = (size_t) socklen;
++ data = (u_char *) sockaddr;
++
++ if (no_port) {
++ switch (sockaddr->sa_family) {
++
++#if (NGX_HAVE_INET6)
++ case AF_INET6:
++ sin6 = (struct sockaddr_in6 *) sockaddr;
++
++ len = sizeof(struct in6_addr);
++ data = sin6->sin6_addr.s6_addr;
++
++ break;
++#endif
++
++ case AF_INET:
++ sin = (struct sockaddr_in *) sockaddr;
++
++ len = sizeof(in_addr_t);
++ data = (u_char *) &sin->sin_addr;
++
++ break;
++ }
++ }
++
++ ngx_sha1_init(&sha1);
++ ngx_sha1_update(&sha1, data, len);
++ ngx_sha1_final(buf, &sha1);
++}
++
++
++ngx_int_t
++ngx_quic_validate_token(ngx_connection_t *c, u_char *key,
++ ngx_quic_header_t *pkt)
++{
++ int len, tlen, iv_len;
++ u_char *iv, *p;
++ time_t now, exp;
++ size_t total;
++ ngx_str_t odcid;
++ EVP_CIPHER_CTX *ctx;
++ const EVP_CIPHER *cipher;
++
++ u_char addr_hash[20];
++ u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
++
++#if NGX_SUPPRESS_WARN
++ ngx_str_null(&odcid);
++#endif
++
++ /* Retry token or NEW_TOKEN in a previous connection */
++
++ cipher = EVP_aes_256_cbc();
++ iv = pkt->token.data;
++ iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
++
++ /* sanity checks */
++
++ if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) {
++ goto garbage;
++ }
++
++ if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
++ goto garbage;
++ }
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ p = pkt->token.data + iv_len;
++ len = pkt->token.len - iv_len;
++
++ if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ goto garbage;
++ }
++ total = len;
++
++ if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ goto garbage;
++ }
++ total += tlen;
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ if (total < (20 + sizeof(time_t) + 2)) {
++ goto garbage;
++ }
++
++ p = tdec + 20;
++
++ ngx_memcpy(&exp, p, sizeof(time_t));
++ p += sizeof(time_t);
++
++ pkt->retried = (*p++ == 1);
++
++ ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash);
++
++ if (ngx_memcmp(tdec, addr_hash, 20) != 0) {
++ goto bad_token;
++ }
++
++ odcid.len = *p++;
++ if (odcid.len) {
++ if (odcid.len > NGX_QUIC_MAX_CID_LEN) {
++ goto bad_token;
++ }
++
++ if ((size_t)(tdec + total - p) < odcid.len) {
++ goto bad_token;
++ }
++
++ odcid.data = p;
++ }
++
++ now = ngx_time();
++
++ if (now > exp) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token");
++ return NGX_DECLINED;
++ }
++
++ if (odcid.len) {
++ pkt->odcid.len = odcid.len;
++ pkt->odcid.data = pkt->odcid_buf;
++ ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len);
++
++ } else {
++ pkt->odcid = pkt->dcid;
++ }
++
++ pkt->validated = 1;
++
++ return NGX_OK;
++
++garbage:
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token");
++
++ return NGX_ABORT;
++
++bad_token:
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
++
++ return NGX_DECLINED;
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_tokens.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_tokens.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,35 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
++#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++#define NGX_QUIC_MAX_TOKEN_SIZE 64
++ /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */
++
++/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */
++#define NGX_QUIC_AES_256_CBC_IV_LEN 16
++#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16
++
++#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_CBC_IV_LEN \
++ + NGX_QUIC_MAX_TOKEN_SIZE \
++ + NGX_QUIC_AES_256_CBC_BLOCK_SIZE)
++
++
++ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid,
++ u_char *secret, u_char *token);
++ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr,
++ socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
++ time_t expires, ngx_uint_t is_retry);
++ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
++ u_char *key, ngx_quic_header_t *pkt);
++
++#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_transport.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_transport.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,2199 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5
++#define NGX_QUIC_LONG_DCID_OFFSET 6
++#define NGX_QUIC_SHORT_DCID_OFFSET 1
++
++#define NGX_QUIC_STREAM_FRAME_FIN 0x01
++#define NGX_QUIC_STREAM_FRAME_LEN 0x02
++#define NGX_QUIC_STREAM_FRAME_OFF 0x04
++
++
++#if (NGX_HAVE_NONALIGNED)
++
++#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p))
++#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p))
++
++#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned
++#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned
++
++#else
++
++#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1])
++#define ngx_quic_parse_uint32(p) \
++ ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
++
++#define ngx_quic_write_uint16(p, s) \
++ ((p)[0] = (u_char) ((s) >> 8), \
++ (p)[1] = (u_char) (s), \
++ (p) + sizeof(uint16_t))
++
++#define ngx_quic_write_uint32(p, s) \
++ ((p)[0] = (u_char) ((s) >> 24), \
++ (p)[1] = (u_char) ((s) >> 16), \
++ (p)[2] = (u_char) ((s) >> 8), \
++ (p)[3] = (u_char) (s), \
++ (p) + sizeof(uint32_t))
++
++#endif
++
++#define ngx_quic_write_uint64(p, s) \
++ ((p)[0] = (u_char) ((s) >> 56), \
++ (p)[1] = (u_char) ((s) >> 48), \
++ (p)[2] = (u_char) ((s) >> 40), \
++ (p)[3] = (u_char) ((s) >> 32), \
++ (p)[4] = (u_char) ((s) >> 24), \
++ (p)[5] = (u_char) ((s) >> 16), \
++ (p)[6] = (u_char) ((s) >> 8), \
++ (p)[7] = (u_char) (s), \
++ (p) + sizeof(uint64_t))
++
++#define ngx_quic_write_uint24(p, s) \
++ ((p)[0] = (u_char) ((s) >> 16), \
++ (p)[1] = (u_char) ((s) >> 8), \
++ (p)[2] = (u_char) (s), \
++ (p) + 3)
++
++#define ngx_quic_write_uint16_aligned(p, s) \
++ (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
++
++#define ngx_quic_write_uint32_aligned(p, s) \
++ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
++
++#define ngx_quic_build_int_set(p, value, len, bits) \
++ (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6))
++
++
++static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out);
++static ngx_uint_t ngx_quic_varint_len(uint64_t value);
++static void ngx_quic_build_int(u_char **pos, uint64_t value);
++
++static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value);
++static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value);
++static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len,
++ u_char **out);
++static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len,
++ u_char *dst);
++
++static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt,
++ size_t dcid_len);
++static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_supported_version(uint32_t version);
++static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt);
++
++static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp);
++static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp);
++
++static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt,
++ ngx_uint_t frame_type);
++static size_t ngx_quic_create_ping(u_char *p);
++static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack,
++ ngx_chain_t *ranges);
++static size_t ngx_quic_create_reset_stream(u_char *p,
++ ngx_quic_reset_stream_frame_t *rs);
++static size_t ngx_quic_create_stop_sending(u_char *p,
++ ngx_quic_stop_sending_frame_t *ss);
++static size_t ngx_quic_create_crypto(u_char *p,
++ ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data);
++static size_t ngx_quic_create_hs_done(u_char *p);
++static size_t ngx_quic_create_new_token(u_char *p,
++ ngx_quic_new_token_frame_t *token, ngx_chain_t *data);
++static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf,
++ ngx_chain_t *data);
++static size_t ngx_quic_create_max_streams(u_char *p,
++ ngx_quic_max_streams_frame_t *ms);
++static size_t ngx_quic_create_max_stream_data(u_char *p,
++ ngx_quic_max_stream_data_frame_t *ms);
++static size_t ngx_quic_create_max_data(u_char *p,
++ ngx_quic_max_data_frame_t *md);
++static size_t ngx_quic_create_path_challenge(u_char *p,
++ ngx_quic_path_challenge_frame_t *pc);
++static size_t ngx_quic_create_path_response(u_char *p,
++ ngx_quic_path_challenge_frame_t *pc);
++static size_t ngx_quic_create_new_connection_id(u_char *p,
++ ngx_quic_new_conn_id_frame_t *rcid);
++static size_t ngx_quic_create_retire_connection_id(u_char *p,
++ ngx_quic_retire_cid_frame_t *rcid);
++static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f);
++
++static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end,
++ uint16_t id, ngx_quic_tp_t *dst);
++
++
++uint32_t ngx_quic_versions[] = {
++ /* QUICv1 */
++ 0x00000001,
++};
++
++#define NGX_QUIC_NVERSIONS \
++ (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0]))
++
++
++static ngx_inline u_char *
++ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out)
++{
++ u_char *p;
++ uint64_t value;
++ ngx_uint_t len;
++
++ if (pos >= end) {
++ return NULL;
++ }
++
++ p = pos;
++ len = 1 << (*p >> 6);
++
++ value = *p++ & 0x3f;
++
++ if ((size_t)(end - p) < (len - 1)) {
++ return NULL;
++ }
++
++ while (--len) {
++ value = (value << 8) + *p++;
++ }
++
++ *out = value;
++
++ return p;
++}
++
++
++static ngx_inline u_char *
++ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value)
++{
++ if ((size_t)(end - pos) < 1) {
++ return NULL;
++ }
++
++ *value = *pos;
++
++ return pos + 1;
++}
++
++
++static ngx_inline u_char *
++ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value)
++{
++ if ((size_t)(end - pos) < sizeof(uint32_t)) {
++ return NULL;
++ }
++
++ *value = ngx_quic_parse_uint32(pos);
++
++ return pos + sizeof(uint32_t);
++}
++
++
++static ngx_inline u_char *
++ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out)
++{
++ if ((size_t)(end - pos) < len) {
++ return NULL;
++ }
++
++ *out = pos;
++
++ return pos + len;
++}
++
++
++static u_char *
++ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst)
++{
++ if ((size_t)(end - pos) < len) {
++ return NULL;
++ }
++
++ ngx_memcpy(dst, pos, len);
++
++ return pos + len;
++}
++
++
++static ngx_inline ngx_uint_t
++ngx_quic_varint_len(uint64_t value)
++{
++ if (value < (1 << 6)) {
++ return 1;
++ }
++
++ if (value < (1 << 14)) {
++ return 2;
++ }
++
++ if (value < (1 << 30)) {
++ return 4;
++ }
++
++ return 8;
++}
++
++
++static ngx_inline void
++ngx_quic_build_int(u_char **pos, uint64_t value)
++{
++ u_char *p;
++
++ p = *pos;
++
++ if (value < (1 << 6)) {
++ ngx_quic_build_int_set(p, value, 0, 0);
++
++ } else if (value < (1 << 14)) {
++ ngx_quic_build_int_set(p, value, 1, 1);
++ ngx_quic_build_int_set(p, value, 0, 0);
++
++ } else if (value < (1 << 30)) {
++ ngx_quic_build_int_set(p, value, 3, 2);
++ ngx_quic_build_int_set(p, value, 2, 0);
++ ngx_quic_build_int_set(p, value, 1, 0);
++ ngx_quic_build_int_set(p, value, 0, 0);
++
++ } else {
++ ngx_quic_build_int_set(p, value, 7, 3);
++ ngx_quic_build_int_set(p, value, 6, 0);
++ ngx_quic_build_int_set(p, value, 5, 0);
++ ngx_quic_build_int_set(p, value, 4, 0);
++ ngx_quic_build_int_set(p, value, 3, 0);
++ ngx_quic_build_int_set(p, value, 2, 0);
++ ngx_quic_build_int_set(p, value, 1, 0);
++ ngx_quic_build_int_set(p, value, 0, 0);
++ }
++
++ *pos = p;
++}
++
++
++ngx_int_t
++ngx_quic_parse_packet(ngx_quic_header_t *pkt)
++{
++ if (!ngx_quic_long_pkt(pkt->flags)) {
++ pkt->level = ssl_encryption_application;
++
++ if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++ }
++
++ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (!ngx_quic_supported_version(pkt->version)) {
++ return NGX_ABORT;
++ }
++
++ if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len)
++{
++ u_char *p, *end;
++
++ p = pkt->raw->pos;
++ end = pkt->data + pkt->len;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx short flags:%xd", pkt->flags);
++
++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
++ return NGX_ERROR;
++ }
++
++ pkt->dcid.len = dcid_len;
++
++ p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read dcid");
++ return NGX_ERROR;
++ }
++
++ pkt->raw->pos = p;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
++{
++ u_char *p, *end;
++ uint8_t idlen;
++
++ p = pkt->raw->pos;
++ end = pkt->data + pkt->len;
++
++ p = ngx_quic_read_uint32(p, end, &pkt->version);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read version");
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx long flags:%xd version:%xD",
++ pkt->flags, pkt->version);
++
++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_read_uint8(p, end, &idlen);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read dcid len");
++ return NGX_ERROR;
++ }
++
++ if (idlen > NGX_QUIC_CID_LEN_MAX) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet dcid is too long");
++ return NGX_ERROR;
++ }
++
++ pkt->dcid.len = idlen;
++
++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read dcid");
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_read_uint8(p, end, &idlen);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read scid len");
++ return NGX_ERROR;
++ }
++
++ if (idlen > NGX_QUIC_CID_LEN_MAX) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet scid is too long");
++ return NGX_ERROR;
++ }
++
++ pkt->scid.len = idlen;
++
++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read scid");
++ return NGX_ERROR;
++ }
++
++ pkt->raw->pos = p;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_supported_version(uint32_t version)
++{
++ ngx_uint_t i;
++
++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) {
++ if (ngx_quic_versions[i] == version) {
++ return 1;
++ }
++ }
++
++ return 0;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt)
++{
++ u_char *p, *end;
++ uint64_t varint;
++
++ p = pkt->raw->pos;
++ end = pkt->raw->last;
++
++ pkt->log->action = "parsing quic long header";
++
++ if (ngx_quic_pkt_in(pkt->flags)) {
++
++ if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic UDP datagram is too small for initial packet");
++ return NGX_DECLINED;
++ }
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic failed to parse token length");
++ return NGX_ERROR;
++ }
++
++ pkt->token.len = varint;
++
++ p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet too small to read token data");
++ return NGX_ERROR;
++ }
++
++ pkt->level = ssl_encryption_initial;
++
++ } else if (ngx_quic_pkt_zrtt(pkt->flags)) {
++ pkt->level = ssl_encryption_early_data;
++
++ } else if (ngx_quic_pkt_hs(pkt->flags)) {
++ pkt->level = ssl_encryption_handshake;
++
++ } else {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic bad packet type");
++ return NGX_DECLINED;
++ }
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length");
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx %s len:%uL",
++ ngx_quic_level_name(pkt->level), varint);
++
++ if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet",
++ ngx_quic_level_name(pkt->level));
++ return NGX_ERROR;
++ }
++
++ pkt->raw->pos = p;
++ pkt->len = p + varint - pkt->data;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n,
++ ngx_str_t *dcid)
++{
++ size_t len, offset;
++
++ if (n == 0) {
++ goto failed;
++ }
++
++ if (ngx_quic_long_pkt(*data)) {
++ if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) {
++ goto failed;
++ }
++
++ len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET];
++ offset = NGX_QUIC_LONG_DCID_OFFSET;
++
++ } else {
++ len = NGX_QUIC_SERVER_CID_LEN;
++ offset = NGX_QUIC_SHORT_DCID_OFFSET;
++ }
++
++ if (n < len + offset) {
++ goto failed;
++ }
++
++ dcid->len = len;
++ dcid->data = &data[offset];
++
++ return NGX_OK;
++
++failed:
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet");
++
++ return NGX_ERROR;
++}
++
++
++size_t
++ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out)
++{
++ u_char *p, *start;
++ ngx_uint_t i;
++
++ p = start = out;
++
++ *p++ = pkt->flags;
++
++ /*
++ * The Version field of a Version Negotiation packet
++ * MUST be set to 0x00000000
++ */
++ p = ngx_quic_write_uint32(p, 0);
++
++ *p++ = pkt->dcid.len;
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *p++ = pkt->scid.len;
++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
++
++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) {
++ p = ngx_quic_write_uint32(p, ngx_quic_versions[i]);
++ }
++
++ return p - start;
++}
++
++
++/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */
++size_t
++ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len)
++{
++ size_t len;
++
++ if (ngx_quic_short_pkt(pkt->flags)) {
++
++ len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN;
++ if (len > pkt_len) {
++ return 0;
++ }
++
++ return pkt_len - len;
++ }
++
++ /* flags, version, dcid and scid with lengths and zero-length token */
++ len = 5 + 2 + pkt->dcid.len + pkt->scid.len
++ + (pkt->level == ssl_encryption_initial ? 1 : 0);
++
++ if (len > pkt_len) {
++ return 0;
++ }
++
++ /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */
++ len += ngx_quic_varint_len(pkt_len - len)
++ + pkt->num_len + EVP_GCM_TLS_TAG_LEN;
++
++ if (len > pkt_len) {
++ return 0;
++ }
++
++ return pkt_len - len;
++}
++
++
++size_t
++ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp)
++{
++ return ngx_quic_short_pkt(pkt->flags)
++ ? ngx_quic_create_short_header(pkt, out, pnp)
++ : ngx_quic_create_long_header(pkt, out, pnp);
++}
++
++
++static size_t
++ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp)
++{
++ size_t rem_len;
++ u_char *p, *start;
++
++ rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
++
++ if (out == NULL) {
++ return 5 + 2 + pkt->dcid.len + pkt->scid.len
++ + ngx_quic_varint_len(rem_len) + pkt->num_len
++ + (pkt->level == ssl_encryption_initial ? 1 : 0);
++ }
++
++ p = start = out;
++
++ *p++ = pkt->flags;
++
++ p = ngx_quic_write_uint32(p, pkt->version);
++
++ *p++ = pkt->dcid.len;
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *p++ = pkt->scid.len;
++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
++
++ if (pkt->level == ssl_encryption_initial) {
++ ngx_quic_build_int(&p, 0);
++ }
++
++ ngx_quic_build_int(&p, rem_len);
++
++ *pnp = p;
++
++ switch (pkt->num_len) {
++ case 1:
++ *p++ = pkt->trunc;
++ break;
++ case 2:
++ p = ngx_quic_write_uint16(p, pkt->trunc);
++ break;
++ case 3:
++ p = ngx_quic_write_uint24(p, pkt->trunc);
++ break;
++ case 4:
++ p = ngx_quic_write_uint32(p, pkt->trunc);
++ break;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp)
++{
++ u_char *p, *start;
++
++ if (out == NULL) {
++ return 1 + pkt->dcid.len + pkt->num_len;
++ }
++
++ p = start = out;
++
++ *p++ = pkt->flags;
++
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *pnp = p;
++
++ switch (pkt->num_len) {
++ case 1:
++ *p++ = pkt->trunc;
++ break;
++ case 2:
++ p = ngx_quic_write_uint16(p, pkt->trunc);
++ break;
++ case 3:
++ p = ngx_quic_write_uint24(p, pkt->trunc);
++ break;
++ case 4:
++ p = ngx_quic_write_uint32(p, pkt->trunc);
++ break;
++ }
++
++ return p - start;
++}
++
++
++size_t
++ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
++ u_char **start)
++{
++ u_char *p;
++
++ p = out;
++
++ *p++ = pkt->odcid.len;
++ p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len);
++
++ *start = p;
++
++ *p++ = 0xff;
++
++ p = ngx_quic_write_uint32(p, pkt->version);
++
++ *p++ = pkt->dcid.len;
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *p++ = pkt->scid.len;
++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
++
++ p = ngx_cpymem(p, pkt->token.data, pkt->token.len);
++
++ return p - out;
++}
++
++
++ssize_t
++ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
++ ngx_quic_frame_t *f)
++{
++ u_char *p;
++ uint64_t varint;
++ ngx_buf_t *b;
++ ngx_uint_t i;
++
++ b = f->data->buf;
++
++ p = start;
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic failed to obtain quic frame type");
++ return NGX_ERROR;
++ }
++
++ if (varint > NGX_QUIC_FT_LAST) {
++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic unknown frame type 0x%xL", varint);
++ return NGX_ERROR;
++ }
++
++ f->type = varint;
++
++ if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) {
++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ return NGX_ERROR;
++ }
++
++ switch (f->type) {
++
++ case NGX_QUIC_FT_CRYPTO:
++
++ p = ngx_quic_parse_int(p, end, &f->u.crypto.offset);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.crypto.length);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos);
++ if (p == NULL) {
++ goto error;
++ }
++
++ b->last = p;
++
++ break;
++
++ case NGX_QUIC_FT_PADDING:
++
++ while (p < end && *p == NGX_QUIC_FT_PADDING) {
++ p++;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.largest);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.delay);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.range_count);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.first_range);
++ if (p == NULL) {
++ goto error;
++ }
++
++ b->pos = p;
++
++ /* process all ranges to get bounds, values are ignored */
++ for (i = 0; i < f->u.ack.range_count; i++) {
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ goto error;
++ }
++ }
++
++ b->last = p;
++
++ f->u.ack.ranges_length = b->last - b->pos;
++
++ if (f->type == NGX_QUIC_FT_ACK_ECN) {
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.ect0);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.ect1);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ack.ce);
++ if (p == NULL) {
++ goto error;
++ }
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL",
++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PING:
++ break;
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++
++ p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ncid.retire);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.ncid.retire > f->u.ncid.seqnum) {
++ goto error;
++ }
++
++ p = ngx_quic_read_uint8(p, end, &f->u.ncid.len);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) {
++ goto error;
++ }
++
++ p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++
++ p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++
++ p = ngx_quic_parse_int(p, end, &f->u.close.error_code);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
++ p = ngx_quic_parse_int(p, end, &f->u.close.frame_type);
++ if (p == NULL) {
++ goto error;
++ }
++ }
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ goto error;
++ }
++
++ f->u.close.reason.len = varint;
++
++ p = ngx_quic_read_bytes(p, end, f->u.close.reason.len,
++ &f->u.close.reason.data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ case NGX_QUIC_FT_STREAM1:
++ case NGX_QUIC_FT_STREAM2:
++ case NGX_QUIC_FT_STREAM3:
++ case NGX_QUIC_FT_STREAM4:
++ case NGX_QUIC_FT_STREAM5:
++ case NGX_QUIC_FT_STREAM6:
++ case NGX_QUIC_FT_STREAM7:
++
++ f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0;
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->type & NGX_QUIC_STREAM_FRAME_OFF) {
++ f->u.stream.off = 1;
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream.offset);
++ if (p == NULL) {
++ goto error;
++ }
++
++ } else {
++ f->u.stream.off = 0;
++ f->u.stream.offset = 0;
++ }
++
++ if (f->type & NGX_QUIC_STREAM_FRAME_LEN) {
++ f->u.stream.len = 1;
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream.length);
++ if (p == NULL) {
++ goto error;
++ }
++
++ } else {
++ f->u.stream.len = 0;
++ f->u.stream.length = end - p; /* up to packet end */
++ }
++
++ p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos);
++ if (p == NULL) {
++ goto error;
++ }
++
++ b->last = p;
++
++ f->type = NGX_QUIC_FT_STREAM;
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RESET_STREAM:
++
++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STOP_SENDING:
++
++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAMS_BLOCKED:
++ case NGX_QUIC_FT_STREAMS_BLOCKED2:
++
++ p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.streams_blocked.limit > 0x1000000000000000) {
++ goto error;
++ }
++
++ f->u.streams_blocked.bidi =
++ (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0;
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.max_streams.limit > 0x1000000000000000) {
++ goto error;
++ }
++
++ f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0;
++
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_DATA_BLOCKED:
++
++ p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++
++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++
++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ default:
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic unknown frame type 0x%xi", f->type);
++ return NGX_ERROR;
++ }
++
++ f->level = pkt->level;
++
++ return p - start;
++
++error:
++
++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic failed to parse frame type:0x%xi", f->type);
++
++ return NGX_ERROR;
++}
++
++
++static ngx_int_t
++ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type)
++{
++ uint8_t ptype;
++
++ /*
++ * RFC 9000, 12.4. Frames and Frame Types: Table 3
++ *
++ * Frame permissions per packet: 4 bits: IH01
++ */
++ static uint8_t ngx_quic_frame_masks[] = {
++ /* PADDING */ 0xF,
++ /* PING */ 0xF,
++ /* ACK */ 0xD,
++ /* ACK_ECN */ 0xD,
++ /* RESET_STREAM */ 0x3,
++ /* STOP_SENDING */ 0x3,
++ /* CRYPTO */ 0xD,
++ /* NEW_TOKEN */ 0x0, /* only sent by server */
++ /* STREAM */ 0x3,
++ /* STREAM1 */ 0x3,
++ /* STREAM2 */ 0x3,
++ /* STREAM3 */ 0x3,
++ /* STREAM4 */ 0x3,
++ /* STREAM5 */ 0x3,
++ /* STREAM6 */ 0x3,
++ /* STREAM7 */ 0x3,
++ /* MAX_DATA */ 0x3,
++ /* MAX_STREAM_DATA */ 0x3,
++ /* MAX_STREAMS */ 0x3,
++ /* MAX_STREAMS2 */ 0x3,
++ /* DATA_BLOCKED */ 0x3,
++ /* STREAM_DATA_BLOCKED */ 0x3,
++ /* STREAMS_BLOCKED */ 0x3,
++ /* STREAMS_BLOCKED2 */ 0x3,
++ /* NEW_CONNECTION_ID */ 0x3,
++ /* RETIRE_CONNECTION_ID */ 0x3,
++ /* PATH_CHALLENGE */ 0x3,
++ /* PATH_RESPONSE */ 0x1,
++ /* CONNECTION_CLOSE */ 0xF,
++ /* CONNECTION_CLOSE2 */ 0x3,
++ /* HANDSHAKE_DONE */ 0x0, /* only sent by server */
++ };
++
++ if (ngx_quic_long_pkt(pkt->flags)) {
++
++ if (ngx_quic_pkt_in(pkt->flags)) {
++ ptype = 8; /* initial */
++
++ } else if (ngx_quic_pkt_hs(pkt->flags)) {
++ ptype = 4; /* handshake */
++
++ } else {
++ ptype = 2; /* zero-rtt */
++ }
++
++ } else {
++ ptype = 1; /* application data */
++ }
++
++ if (ptype & ngx_quic_frame_masks[frame_type]) {
++ return NGX_OK;
++ }
++
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic frame type 0x%xi is not "
++ "allowed in packet with flags 0x%xd",
++ frame_type, pkt->flags);
++
++ return NGX_DECLINED;
++}
++
++
++ssize_t
++ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end,
++ uint64_t *gap, uint64_t *range)
++{
++ u_char *p;
++
++ p = start;
++
++ p = ngx_quic_parse_int(p, end, gap);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse ack frame gap");
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_parse_int(p, end, range);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse ack frame range");
++ return NGX_ERROR;
++ }
++
++ return p - start;
++}
++
++
++size_t
++ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(gap);
++ len += ngx_quic_varint_len(range);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, gap);
++ ngx_quic_build_int(&p, range);
++
++ return p - start;
++}
++
++
++ssize_t
++ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f)
++{
++ /*
++ * RFC 9002, 2. Conventions and Definitions
++ *
++ * Ack-eliciting frames: All frames other than ACK, PADDING, and
++ * CONNECTION_CLOSE are considered ack-eliciting.
++ */
++ f->need_ack = 1;
++
++ switch (f->type) {
++ case NGX_QUIC_FT_PING:
++ return ngx_quic_create_ping(p);
++
++ case NGX_QUIC_FT_ACK:
++ f->need_ack = 0;
++ return ngx_quic_create_ack(p, &f->u.ack, f->data);
++
++ case NGX_QUIC_FT_RESET_STREAM:
++ return ngx_quic_create_reset_stream(p, &f->u.reset_stream);
++
++ case NGX_QUIC_FT_STOP_SENDING:
++ return ngx_quic_create_stop_sending(p, &f->u.stop_sending);
++
++ case NGX_QUIC_FT_CRYPTO:
++ return ngx_quic_create_crypto(p, &f->u.crypto, f->data);
++
++ case NGX_QUIC_FT_HANDSHAKE_DONE:
++ return ngx_quic_create_hs_done(p);
++
++ case NGX_QUIC_FT_NEW_TOKEN:
++ return ngx_quic_create_new_token(p, &f->u.token, f->data);
++
++ case NGX_QUIC_FT_STREAM:
++ return ngx_quic_create_stream(p, &f->u.stream, f->data);
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++ f->need_ack = 0;
++ return ngx_quic_create_close(p, f);
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ return ngx_quic_create_max_streams(p, &f->u.max_streams);
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++ return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data);
++
++ case NGX_QUIC_FT_MAX_DATA:
++ return ngx_quic_create_max_data(p, &f->u.max_data);
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++ return ngx_quic_create_path_challenge(p, &f->u.path_challenge);
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ return ngx_quic_create_path_response(p, &f->u.path_response);
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++ return ngx_quic_create_new_connection_id(p, &f->u.ncid);
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++ return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid);
++
++ default:
++ /* BUG: unsupported frame type generated */
++ return NGX_ERROR;
++ }
++}
++
++
++static size_t
++ngx_quic_create_ping(u_char *p)
++{
++ u_char *start;
++
++ if (p == NULL) {
++ return ngx_quic_varint_len(NGX_QUIC_FT_PING);
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_PING);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges)
++{
++ size_t len;
++ u_char *start;
++ ngx_buf_t *b;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_ACK);
++ len += ngx_quic_varint_len(ack->largest);
++ len += ngx_quic_varint_len(ack->delay);
++ len += ngx_quic_varint_len(ack->range_count);
++ len += ngx_quic_varint_len(ack->first_range);
++ len += ack->ranges_length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_ACK);
++ ngx_quic_build_int(&p, ack->largest);
++ ngx_quic_build_int(&p, ack->delay);
++ ngx_quic_build_int(&p, ack->range_count);
++ ngx_quic_build_int(&p, ack->first_range);
++
++ while (ranges) {
++ b = ranges->buf;
++ p = ngx_cpymem(p, b->pos, b->last - b->pos);
++ ranges = ranges->next;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM);
++ len += ngx_quic_varint_len(rs->id);
++ len += ngx_quic_varint_len(rs->error_code);
++ len += ngx_quic_varint_len(rs->final_size);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM);
++ ngx_quic_build_int(&p, rs->id);
++ ngx_quic_build_int(&p, rs->error_code);
++ ngx_quic_build_int(&p, rs->final_size);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING);
++ len += ngx_quic_varint_len(ss->id);
++ len += ngx_quic_varint_len(ss->error_code);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING);
++ ngx_quic_build_int(&p, ss->id);
++ ngx_quic_build_int(&p, ss->error_code);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto,
++ ngx_chain_t *data)
++{
++ size_t len;
++ u_char *start;
++ ngx_buf_t *b;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO);
++ len += ngx_quic_varint_len(crypto->offset);
++ len += ngx_quic_varint_len(crypto->length);
++ len += crypto->length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO);
++ ngx_quic_build_int(&p, crypto->offset);
++ ngx_quic_build_int(&p, crypto->length);
++
++ while (data) {
++ b = data->buf;
++ p = ngx_cpymem(p, b->pos, b->last - b->pos);
++ data = data->next;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_hs_done(u_char *p)
++{
++ u_char *start;
++
++ if (p == NULL) {
++ return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE);
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token,
++ ngx_chain_t *data)
++{
++ size_t len;
++ u_char *start;
++ ngx_buf_t *b;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN);
++ len += ngx_quic_varint_len(token->length);
++ len += token->length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN);
++ ngx_quic_build_int(&p, token->length);
++
++ while (data) {
++ b = data->buf;
++ p = ngx_cpymem(p, b->pos, b->last - b->pos);
++ data = data->next;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf,
++ ngx_chain_t *data)
++{
++ size_t len;
++ u_char *start, type;
++ ngx_buf_t *b;
++
++ type = NGX_QUIC_FT_STREAM;
++
++ if (sf->off) {
++ type |= NGX_QUIC_STREAM_FRAME_OFF;
++ }
++
++ if (sf->len) {
++ type |= NGX_QUIC_STREAM_FRAME_LEN;
++ }
++
++ if (sf->fin) {
++ type |= NGX_QUIC_STREAM_FRAME_FIN;
++ }
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(type);
++ len += ngx_quic_varint_len(sf->stream_id);
++
++ if (sf->off) {
++ len += ngx_quic_varint_len(sf->offset);
++ }
++
++ if (sf->len) {
++ len += ngx_quic_varint_len(sf->length);
++ }
++
++ len += sf->length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, type);
++ ngx_quic_build_int(&p, sf->stream_id);
++
++ if (sf->off) {
++ ngx_quic_build_int(&p, sf->offset);
++ }
++
++ if (sf->len) {
++ ngx_quic_build_int(&p, sf->length);
++ }
++
++ while (data) {
++ b = data->buf;
++ p = ngx_cpymem(p, b->pos, b->last - b->pos);
++ data = data->next;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms)
++{
++ size_t len;
++ u_char *start;
++ ngx_uint_t type;
++
++ type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(type);
++ len += ngx_quic_varint_len(ms->limit);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, type);
++ ngx_quic_build_int(&p, ms->limit);
++
++ return p - start;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id,
++ ngx_quic_tp_t *dst)
++{
++ uint64_t varint;
++ ngx_str_t str;
++
++ varint = 0;
++ ngx_str_null(&str);
++
++ switch (id) {
++
++ case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION:
++ /* zero-length option */
++ if (end - p != 0) {
++ return NGX_ERROR;
++ }
++ dst->disable_active_migration = 1;
++ return NGX_OK;
++
++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
++ case NGX_QUIC_TP_INITIAL_MAX_DATA:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
++ case NGX_QUIC_TP_MAX_ACK_DELAY:
++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ return NGX_ERROR;
++ }
++ break;
++
++ case NGX_QUIC_TP_INITIAL_SCID:
++
++ str.len = end - p;
++ str.data = p;
++ break;
++
++ default:
++ return NGX_DECLINED;
++ }
++
++ switch (id) {
++
++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
++ dst->max_idle_timeout = varint;
++ break;
++
++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
++ dst->max_udp_payload_size = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_DATA:
++ dst->initial_max_data = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
++ dst->initial_max_stream_data_bidi_local = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
++ dst->initial_max_stream_data_bidi_remote = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
++ dst->initial_max_stream_data_uni = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
++ dst->initial_max_streams_bidi = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
++ dst->initial_max_streams_uni = varint;
++ break;
++
++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
++ dst->ack_delay_exponent = varint;
++ break;
++
++ case NGX_QUIC_TP_MAX_ACK_DELAY:
++ dst->max_ack_delay = varint;
++ break;
++
++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
++ dst->active_connection_id_limit = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_SCID:
++ dst->initial_scid = str;
++ break;
++
++ default:
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp,
++ ngx_log_t *log)
++{
++ uint64_t id, len;
++ ngx_int_t rc;
++
++ while (p < end) {
++ p = ngx_quic_parse_int(p, end, &id);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse transport param id");
++ return NGX_ERROR;
++ }
++
++ switch (id) {
++ case NGX_QUIC_TP_ORIGINAL_DCID:
++ case NGX_QUIC_TP_PREFERRED_ADDRESS:
++ case NGX_QUIC_TP_RETRY_SCID:
++ case NGX_QUIC_TP_SR_TOKEN:
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic client sent forbidden transport param"
++ " id:0x%xL", id);
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_parse_int(p, end, &len);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse"
++ " transport param id:0x%xL length", id);
++ return NGX_ERROR;
++ }
++
++ rc = ngx_quic_parse_transport_param(p, p + len, id, tp);
++
++ if (rc == NGX_ERROR) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse"
++ " transport param id:0x%xL data", id);
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_DECLINED) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic %s transport param id:0x%xL, skipped",
++ (id % 31 == 27) ? "reserved" : "unknown", id);
++ }
++
++ p += len;
++ }
++
++ if (p != end) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic trailing garbage in"
++ " transport parameters: bytes:%ui",
++ end - p);
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic transport parameters parsed ok");
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp disable active migration: %ui",
++ tp->disable_active_migration);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui",
++ tp->max_idle_timeout);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_udp_payload_size:%ui",
++ tp->max_udp_payload_size);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui",
++ tp->initial_max_data);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_stream_data_bidi_local:%ui",
++ tp->initial_max_stream_data_bidi_local);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_stream_data_bidi_remote:%ui",
++ tp->initial_max_stream_data_bidi_remote);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_stream_data_uni:%ui",
++ tp->initial_max_stream_data_uni);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp initial_max_streams_bidi:%ui",
++ tp->initial_max_streams_bidi);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp initial_max_streams_uni:%ui",
++ tp->initial_max_streams_uni);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp ack_delay_exponent:%ui",
++ tp->ack_delay_exponent);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui",
++ tp->max_ack_delay);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp active_connection_id_limit:%ui",
++ tp->active_connection_id_limit);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp initial source_connection_id len:%uz %xV",
++ tp->initial_scid.len, &tp->initial_scid);
++
++ return NGX_OK;
++}
++
++
++static size_t
++ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA);
++ len += ngx_quic_varint_len(ms->id);
++ len += ngx_quic_varint_len(ms->limit);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA);
++ ngx_quic_build_int(&p, ms->id);
++ ngx_quic_build_int(&p, ms->limit);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA);
++ len += ngx_quic_varint_len(md->max_data);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA);
++ ngx_quic_build_int(&p, md->max_data);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE);
++ len += sizeof(pc->data);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE);
++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data));
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE);
++ len += sizeof(pc->data);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE);
++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data));
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID);
++ len += ngx_quic_varint_len(ncid->seqnum);
++ len += ngx_quic_varint_len(ncid->retire);
++ len++;
++ len += ncid->len;
++ len += NGX_QUIC_SR_TOKEN_LEN;
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID);
++ ngx_quic_build_int(&p, ncid->seqnum);
++ ngx_quic_build_int(&p, ncid->retire);
++ *p++ = ncid->len;
++ p = ngx_cpymem(p, ncid->cid, ncid->len);
++ p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_retire_connection_id(u_char *p,
++ ngx_quic_retire_cid_frame_t *rcid)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID);
++ len += ngx_quic_varint_len(rcid->sequence_number);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID);
++ ngx_quic_build_int(&p, rcid->sequence_number);
++
++ return p - start;
++}
++
++
++ngx_int_t
++ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf)
++{
++ ngx_uint_t nstreams;
++
++ ngx_memzero(tp, sizeof(ngx_quic_tp_t));
++
++ /*
++ * set by ngx_memzero():
++ *
++ * tp->disable_active_migration = 0;
++ * tp->original_dcid = { 0, NULL };
++ * tp->initial_scid = { 0, NULL };
++ * tp->retry_scid = { 0, NULL };
++ * tp->sr_token = { 0 }
++ * tp->sr_enabled = 0
++ * tp->preferred_address = NULL
++ */
++
++ tp->max_idle_timeout = qcf->timeout;
++
++ tp->max_udp_payload_size = qcf->mtu;
++
++ nstreams = qcf->max_concurrent_streams_bidi
++ + qcf->max_concurrent_streams_uni;
++
++ tp->initial_max_data = nstreams * qcf->stream_buffer_size;
++ tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size;
++ tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size;
++ tp->initial_max_stream_data_uni = qcf->stream_buffer_size;
++
++ tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi;
++ tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni;
++
++ tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
++ tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
++
++ tp->active_connection_id_limit = qcf->active_connection_id_limit;
++ tp->disable_active_migration = qcf->disable_active_migration;
++
++ return NGX_OK;
++}
++
++
++ssize_t
++ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp,
++ size_t *clen)
++{
++ u_char *p;
++ size_t len;
++
++#define ngx_quic_tp_len(id, value) \
++ ngx_quic_varint_len(id) \
++ + ngx_quic_varint_len(value) \
++ + ngx_quic_varint_len(ngx_quic_varint_len(value))
++
++#define ngx_quic_tp_vint(id, value) \
++ do { \
++ ngx_quic_build_int(&p, id); \
++ ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \
++ ngx_quic_build_int(&p, value); \
++ } while (0)
++
++#define ngx_quic_tp_strlen(id, value) \
++ ngx_quic_varint_len(id) \
++ + ngx_quic_varint_len(value.len) \
++ + value.len
++
++#define ngx_quic_tp_str(id, value) \
++ do { \
++ ngx_quic_build_int(&p, id); \
++ ngx_quic_build_int(&p, value.len); \
++ p = ngx_cpymem(p, value.data, value.len); \
++ } while (0)
++
++ len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
++ tp->initial_max_streams_uni);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
++ tp->initial_max_streams_bidi);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
++ tp->initial_max_stream_data_bidi_local);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
++ tp->initial_max_stream_data_bidi_remote);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
++ tp->initial_max_stream_data_uni);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
++ tp->max_idle_timeout);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE,
++ tp->max_udp_payload_size);
++
++ if (tp->disable_active_migration) {
++ len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
++ len += ngx_quic_varint_len(0);
++ }
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
++ tp->active_connection_id_limit);
++
++ /* transport parameters listed above will be saved in 0-RTT context */
++ if (clen) {
++ *clen = len;
++ }
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY,
++ tp->max_ack_delay);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT,
++ tp->ack_delay_exponent);
++
++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
++
++ if (tp->retry_scid.len) {
++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
++ }
++
++ len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN);
++ len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN);
++ len += NGX_QUIC_SR_TOKEN_LEN;
++
++ if (pos == NULL) {
++ return len;
++ }
++
++ p = pos;
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA,
++ tp->initial_max_data);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
++ tp->initial_max_streams_uni);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
++ tp->initial_max_streams_bidi);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
++ tp->initial_max_stream_data_bidi_local);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
++ tp->initial_max_stream_data_bidi_remote);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
++ tp->initial_max_stream_data_uni);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
++ tp->max_idle_timeout);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE,
++ tp->max_udp_payload_size);
++
++ if (tp->disable_active_migration) {
++ ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
++ ngx_quic_build_int(&p, 0);
++ }
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
++ tp->active_connection_id_limit);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY,
++ tp->max_ack_delay);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT,
++ tp->ack_delay_exponent);
++
++ ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
++ ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
++
++ if (tp->retry_scid.len) {
++ ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
++ }
++
++ ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN);
++ ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN);
++ p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN);
++
++ return p - pos;
++}
++
++
++static size_t
++ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f)
++{
++ size_t len;
++ u_char *start;
++ ngx_quic_close_frame_t *cl;
++
++ cl = &f->u.close;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(f->type);
++ len += ngx_quic_varint_len(cl->error_code);
++
++ if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) {
++ len += ngx_quic_varint_len(cl->frame_type);
++ }
++
++ len += ngx_quic_varint_len(cl->reason.len);
++ len += cl->reason.len;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, f->type);
++ ngx_quic_build_int(&p, cl->error_code);
++
++ if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) {
++ ngx_quic_build_int(&p, cl->frame_type);
++ }
++
++ ngx_quic_build_int(&p, cl->reason.len);
++ p = ngx_cpymem(p, cl->reason.data, cl->reason.len);
++
++ return p - start;
++}
++
++
++void
++ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key)
++{
++ (void) ngx_quic_write_uint64(dcid, key);
++}
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_transport.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_transport.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,397 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
++#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++/*
++ * RFC 9000, 17.2. Long Header Packets
++ * 17.3. Short Header Packets
++ *
++ * QUIC flags in first byte
++ */
++#define NGX_QUIC_PKT_LONG 0x80 /* header form */
++#define NGX_QUIC_PKT_FIXED_BIT 0x40
++#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */
++#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */
++
++#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG)
++#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0)
++
++/* Long packet types */
++#define NGX_QUIC_PKT_INITIAL 0x00
++#define NGX_QUIC_PKT_ZRTT 0x10
++#define NGX_QUIC_PKT_HANDSHAKE 0x20
++#define NGX_QUIC_PKT_RETRY 0x30
++
++#define ngx_quic_pkt_in(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL)
++#define ngx_quic_pkt_zrtt(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT)
++#define ngx_quic_pkt_hs(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE)
++#define ngx_quic_pkt_retry(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY)
++
++#define ngx_quic_pkt_rb_mask(flags) \
++ (ngx_quic_long_pkt(flags) ? 0x0C : 0x18)
++#define ngx_quic_pkt_hp_mask(flags) \
++ (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F)
++
++#define ngx_quic_level_name(lvl) \
++ (lvl == ssl_encryption_application) ? "app" \
++ : (lvl == ssl_encryption_initial) ? "init" \
++ : (lvl == ssl_encryption_handshake) ? "hs" : "early"
++
++#define NGX_QUIC_MAX_CID_LEN 20
++#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN
++
++/* 12.4. Frames and Frame Types */
++#define NGX_QUIC_FT_PADDING 0x00
++#define NGX_QUIC_FT_PING 0x01
++#define NGX_QUIC_FT_ACK 0x02
++#define NGX_QUIC_FT_ACK_ECN 0x03
++#define NGX_QUIC_FT_RESET_STREAM 0x04
++#define NGX_QUIC_FT_STOP_SENDING 0x05
++#define NGX_QUIC_FT_CRYPTO 0x06
++#define NGX_QUIC_FT_NEW_TOKEN 0x07
++#define NGX_QUIC_FT_STREAM 0x08
++#define NGX_QUIC_FT_STREAM1 0x09
++#define NGX_QUIC_FT_STREAM2 0x0A
++#define NGX_QUIC_FT_STREAM3 0x0B
++#define NGX_QUIC_FT_STREAM4 0x0C
++#define NGX_QUIC_FT_STREAM5 0x0D
++#define NGX_QUIC_FT_STREAM6 0x0E
++#define NGX_QUIC_FT_STREAM7 0x0F
++#define NGX_QUIC_FT_MAX_DATA 0x10
++#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11
++#define NGX_QUIC_FT_MAX_STREAMS 0x12
++#define NGX_QUIC_FT_MAX_STREAMS2 0x13
++#define NGX_QUIC_FT_DATA_BLOCKED 0x14
++#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15
++#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16
++#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17
++#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18
++#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19
++#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A
++#define NGX_QUIC_FT_PATH_RESPONSE 0x1B
++#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C
++#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D
++#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E
++
++#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE
++
++/* 22.5. QUIC Transport Error Codes Registry */
++#define NGX_QUIC_ERR_NO_ERROR 0x00
++#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01
++#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02
++#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03
++#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04
++#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05
++#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06
++#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07
++#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08
++#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09
++#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A
++#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B
++#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C
++#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D
++#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E
++#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F
++#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10
++
++#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100
++
++#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e))
++
++
++/* 22.3. QUIC Transport Parameters Registry */
++#define NGX_QUIC_TP_ORIGINAL_DCID 0x00
++#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01
++#define NGX_QUIC_TP_SR_TOKEN 0x02
++#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03
++#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04
++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05
++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06
++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07
++#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08
++#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09
++#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A
++#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B
++#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C
++#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D
++#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E
++#define NGX_QUIC_TP_INITIAL_SCID 0x0F
++#define NGX_QUIC_TP_RETRY_SCID 0x10
++
++#define NGX_QUIC_CID_LEN_MIN 8
++#define NGX_QUIC_CID_LEN_MAX 20
++
++#define NGX_QUIC_MAX_RANGES 10
++
++
++typedef struct {
++ uint64_t gap;
++ uint64_t range;
++} ngx_quic_ack_range_t;
++
++
++typedef struct {
++ uint64_t largest;
++ uint64_t delay;
++ uint64_t range_count;
++ uint64_t first_range;
++ uint64_t ect0;
++ uint64_t ect1;
++ uint64_t ce;
++ uint64_t ranges_length;
++} ngx_quic_ack_frame_t;
++
++
++typedef struct {
++ uint64_t seqnum;
++ uint64_t retire;
++ uint8_t len;
++ u_char cid[NGX_QUIC_CID_LEN_MAX];
++ u_char srt[NGX_QUIC_SR_TOKEN_LEN];
++} ngx_quic_new_conn_id_frame_t;
++
++
++typedef struct {
++ uint64_t length;
++} ngx_quic_new_token_frame_t;
++
++/*
++ * common layout for CRYPTO and STREAM frames;
++ * conceptually, CRYPTO frame is also a stream
++ * frame lacking some properties
++ */
++typedef struct {
++ uint64_t offset;
++ uint64_t length;
++} ngx_quic_ordered_frame_t;
++
++typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t;
++
++
++typedef struct {
++ /* initial fields same as in ngx_quic_ordered_frame_t */
++ uint64_t offset;
++ uint64_t length;
++
++ uint64_t stream_id;
++ unsigned off:1;
++ unsigned len:1;
++ unsigned fin:1;
++} ngx_quic_stream_frame_t;
++
++
++typedef struct {
++ uint64_t max_data;
++} ngx_quic_max_data_frame_t;
++
++
++typedef struct {
++ uint64_t error_code;
++ uint64_t frame_type;
++ ngx_str_t reason;
++} ngx_quic_close_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t error_code;
++ uint64_t final_size;
++} ngx_quic_reset_stream_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t error_code;
++} ngx_quic_stop_sending_frame_t;
++
++
++typedef struct {
++ uint64_t limit;
++ ngx_uint_t bidi; /* unsigned: bidi:1 */
++} ngx_quic_streams_blocked_frame_t;
++
++
++typedef struct {
++ uint64_t limit;
++ ngx_uint_t bidi; /* unsigned: bidi:1 */
++} ngx_quic_max_streams_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t limit;
++} ngx_quic_max_stream_data_frame_t;
++
++
++typedef struct {
++ uint64_t limit;
++} ngx_quic_data_blocked_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t limit;
++} ngx_quic_stream_data_blocked_frame_t;
++
++
++typedef struct {
++ uint64_t sequence_number;
++} ngx_quic_retire_cid_frame_t;
++
++
++typedef struct {
++ u_char data[8];
++} ngx_quic_path_challenge_frame_t;
++
++
++typedef struct ngx_quic_frame_s ngx_quic_frame_t;
++
++struct ngx_quic_frame_s {
++ ngx_uint_t type;
++ enum ssl_encryption_level_t level;
++ ngx_queue_t queue;
++ uint64_t pnum;
++ size_t plen;
++ ngx_msec_t first;
++ ngx_msec_t last;
++ ssize_t len;
++ unsigned need_ack:1;
++ unsigned pkt_need_ack:1;
++ unsigned flush:1;
++
++ ngx_chain_t *data;
++ union {
++ ngx_quic_ack_frame_t ack;
++ ngx_quic_crypto_frame_t crypto;
++ ngx_quic_ordered_frame_t ord;
++ ngx_quic_new_conn_id_frame_t ncid;
++ ngx_quic_new_token_frame_t token;
++ ngx_quic_stream_frame_t stream;
++ ngx_quic_max_data_frame_t max_data;
++ ngx_quic_close_frame_t close;
++ ngx_quic_reset_stream_frame_t reset_stream;
++ ngx_quic_stop_sending_frame_t stop_sending;
++ ngx_quic_streams_blocked_frame_t streams_blocked;
++ ngx_quic_max_streams_frame_t max_streams;
++ ngx_quic_max_stream_data_frame_t max_stream_data;
++ ngx_quic_data_blocked_frame_t data_blocked;
++ ngx_quic_stream_data_blocked_frame_t stream_data_blocked;
++ ngx_quic_retire_cid_frame_t retire_cid;
++ ngx_quic_path_challenge_frame_t path_challenge;
++ ngx_quic_path_challenge_frame_t path_response;
++ } u;
++};
++
++
++typedef struct {
++ ngx_log_t *log;
++ ngx_quic_path_t *path;
++
++ ngx_quic_keys_t *keys;
++
++ ngx_msec_t received;
++ uint64_t number;
++ uint8_t num_len;
++ uint32_t trunc;
++ uint8_t flags;
++ uint32_t version;
++ ngx_str_t token;
++ enum ssl_encryption_level_t level;
++ ngx_uint_t error;
++
++ /* filled in by parser */
++ ngx_buf_t *raw; /* udp datagram */
++
++ u_char *data; /* quic packet */
++ size_t len;
++
++ /* cleartext fields */
++ ngx_str_t odcid; /* retry packet tag */
++ u_char odcid_buf[NGX_QUIC_MAX_CID_LEN];
++ ngx_str_t dcid;
++ ngx_str_t scid;
++ uint64_t pn;
++ u_char *plaintext;
++ ngx_str_t payload; /* decrypted data */
++
++ unsigned need_ack:1;
++ unsigned key_phase:1;
++ unsigned key_update:1;
++ unsigned parsed:1;
++ unsigned decrypted:1;
++ unsigned validated:1;
++ unsigned retried:1;
++ unsigned first:1;
++ unsigned rebound:1;
++} ngx_quic_header_t;
++
++
++typedef struct {
++ ngx_msec_t max_idle_timeout;
++ ngx_msec_t max_ack_delay;
++
++ size_t max_udp_payload_size;
++ size_t initial_max_data;
++ size_t initial_max_stream_data_bidi_local;
++ size_t initial_max_stream_data_bidi_remote;
++ size_t initial_max_stream_data_uni;
++ ngx_uint_t initial_max_streams_bidi;
++ ngx_uint_t initial_max_streams_uni;
++ ngx_uint_t ack_delay_exponent;
++ ngx_uint_t active_connection_id_limit;
++ ngx_flag_t disable_active_migration;
++
++ ngx_str_t original_dcid;
++ ngx_str_t initial_scid;
++ ngx_str_t retry_scid;
++ u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
++
++ /* TODO */
++ void *preferred_address;
++} ngx_quic_tp_t;
++
++
++ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt);
++
++size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out);
++
++size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len);
++
++size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp);
++
++size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
++ u_char **start);
++
++ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
++ ngx_quic_frame_t *frame);
++ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f);
++
++ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start,
++ u_char *end, uint64_t *gap, uint64_t *range);
++size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range);
++
++ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp,
++ ngx_quic_conf_t *qcf);
++ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end,
++ ngx_quic_tp_t *tp, ngx_log_t *log);
++ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end,
++ ngx_quic_tp_t *tp, size_t *clen);
++
++void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key);
++
++#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */
+diff -r ac779115ed6e src/event/quic/ngx_event_quic_udp.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_udp.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,473 @@
++
++/*
++ * Copyright (C) Roman Arutyunyan
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++static void ngx_quic_close_accepted_connection(ngx_connection_t *c);
++static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls,
++ ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen);
++
++
++void
++ngx_quic_recvmsg(ngx_event_t *ev)
++{
++ ssize_t n;
++ ngx_str_t key;
++ ngx_buf_t buf;
++ ngx_log_t *log;
++ ngx_err_t err;
++ socklen_t socklen, local_socklen;
++ ngx_event_t *rev, *wev;
++ struct iovec iov[1];
++ struct msghdr msg;
++ ngx_sockaddr_t sa, lsa;
++ struct sockaddr *sockaddr, *local_sockaddr;
++ ngx_listening_t *ls;
++ ngx_event_conf_t *ecf;
++ ngx_connection_t *c, *lc;
++ ngx_quic_socket_t *qsock;
++ static u_char buffer[65535];
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
++#endif
++
++ if (ev->timedout) {
++ if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
++ return;
++ }
++
++ ev->timedout = 0;
++ }
++
++ ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
++
++ if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
++ ev->available = ecf->multi_accept;
++ }
++
++ lc = ev->data;
++ ls = lc->listening;
++ ev->ready = 0;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
++ "quic recvmsg on %V, ready: %d",
++ &ls->addr_text, ev->available);
++
++ do {
++ ngx_memzero(&msg, sizeof(struct msghdr));
++
++ iov[0].iov_base = (void *) buffer;
++ iov[0].iov_len = sizeof(buffer);
++
++ msg.msg_name = &sa;
++ msg.msg_namelen = sizeof(ngx_sockaddr_t);
++ msg.msg_iov = iov;
++ msg.msg_iovlen = 1;
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ if (ls->wildcard) {
++ msg.msg_control = &msg_control;
++ msg.msg_controllen = sizeof(msg_control);
++
++ ngx_memzero(&msg_control, sizeof(msg_control));
++ }
++#endif
++
++ n = recvmsg(lc->fd, &msg, 0);
++
++ if (n == -1) {
++ err = ngx_socket_errno;
++
++ if (err == NGX_EAGAIN) {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
++ "quic recvmsg() not ready");
++ return;
++ }
++
++ ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed");
++
++ return;
++ }
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
++ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
++ "quic recvmsg() truncated data");
++ continue;
++ }
++#endif
++
++ sockaddr = msg.msg_name;
++ socklen = msg.msg_namelen;
++
++ if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
++ socklen = sizeof(ngx_sockaddr_t);
++ }
++
++#if (NGX_HAVE_UNIX_DOMAIN)
++
++ if (sockaddr->sa_family == AF_UNIX) {
++ struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr;
++
++ if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path)
++ || saun->sun_path[0] == '\0')
++ {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
++ "unbound unix socket");
++ goto next;
++ }
++ }
++
++#endif
++
++ local_sockaddr = ls->sockaddr;
++ local_socklen = ls->socklen;
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++
++ if (ls->wildcard) {
++ struct cmsghdr *cmsg;
++
++ ngx_memcpy(&lsa, local_sockaddr, local_socklen);
++ local_sockaddr = &lsa.sockaddr;
++
++ for (cmsg = CMSG_FIRSTHDR(&msg);
++ cmsg != NULL;
++ cmsg = CMSG_NXTHDR(&msg, cmsg))
++ {
++ if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) {
++ break;
++ }
++ }
++ }
++
++#endif
++
++ if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) {
++ goto next;
++ }
++
++ c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen);
++
++ if (c) {
++
++#if (NGX_DEBUG)
++ if (c->log->log_level & NGX_LOG_DEBUG_EVENT) {
++ ngx_log_handler_pt handler;
++
++ handler = c->log->handler;
++ c->log->handler = NULL;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic recvmsg: fd:%d n:%z", c->fd, n);
++
++ c->log->handler = handler;
++ }
++#endif
++
++ ngx_memzero(&buf, sizeof(ngx_buf_t));
++
++ buf.pos = buffer;
++ buf.last = buffer + n;
++ buf.start = buf.pos;
++ buf.end = buffer + sizeof(buffer);
++
++ qsock = ngx_quic_get_socket(c);
++
++ ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen);
++ qsock->socklen = socklen;
++
++ c->udp->buffer = &buf;
++
++ rev = c->read;
++ rev->ready = 1;
++ rev->active = 0;
++
++ rev->handler(rev);
++
++ if (c->udp) {
++ c->udp->buffer = NULL;
++ }
++
++ rev->ready = 0;
++ rev->active = 1;
++
++ goto next;
++ }
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
++#endif
++
++ ngx_accept_disabled = ngx_cycle->connection_n / 8
++ - ngx_cycle->free_connection_n;
++
++ c = ngx_get_connection(lc->fd, ev->log);
++ if (c == NULL) {
++ return;
++ }
++
++ c->shared = 1;
++ c->type = SOCK_DGRAM;
++ c->socklen = socklen;
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
++#endif
++
++ c->pool = ngx_create_pool(ls->pool_size, ev->log);
++ if (c->pool == NULL) {
++ ngx_quic_close_accepted_connection(c);
++ return;
++ }
++
++ c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN);
++ if (c->sockaddr == NULL) {
++ ngx_quic_close_accepted_connection(c);
++ return;
++ }
++
++ ngx_memcpy(c->sockaddr, sockaddr, socklen);
++
++ log = ngx_palloc(c->pool, sizeof(ngx_log_t));
++ if (log == NULL) {
++ ngx_quic_close_accepted_connection(c);
++ return;
++ }
++
++ *log = ls->log;
++
++ c->log = log;
++ c->pool->log = log;
++ c->listening = ls;
++
++ if (local_sockaddr == &lsa.sockaddr) {
++ local_sockaddr = ngx_palloc(c->pool, local_socklen);
++ if (local_sockaddr == NULL) {
++ ngx_quic_close_accepted_connection(c);
++ return;
++ }
++
++ ngx_memcpy(local_sockaddr, &lsa, local_socklen);
++ }
++
++ c->local_sockaddr = local_sockaddr;
++ c->local_socklen = local_socklen;
++
++ c->buffer = ngx_create_temp_buf(c->pool, n);
++ if (c->buffer == NULL) {
++ ngx_quic_close_accepted_connection(c);
++ return;
++ }
++
++ c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n);
++
++ rev = c->read;
++ wev = c->write;
++
++ rev->active = 1;
++ wev->ready = 1;
++
++ rev->log = log;
++ wev->log = log;
++
++ /*
++ * TODO: MT: - ngx_atomic_fetch_add()
++ * or protection by critical section or light mutex
++ *
++ * TODO: MP: - allocated in a shared memory
++ * - ngx_atomic_fetch_add()
++ * or protection by critical section or light mutex
++ */
++
++ c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
++
++ c->start_time = ngx_current_msec;
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
++#endif
++
++ if (ls->addr_ntop) {
++ c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
++ if (c->addr_text.data == NULL) {
++ ngx_quic_close_accepted_connection(c);
++ return;
++ }
++
++ c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
++ c->addr_text.data,
++ ls->addr_text_max_len, 0);
++ if (c->addr_text.len == 0) {
++ ngx_quic_close_accepted_connection(c);
++ return;
++ }
++ }
++
++#if (NGX_DEBUG)
++ {
++ ngx_str_t addr;
++ u_char text[NGX_SOCKADDR_STRLEN];
++
++ ngx_debug_accepted_connection(ecf, c);
++
++ if (log->log_level & NGX_LOG_DEBUG_EVENT) {
++ addr.data = text;
++ addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
++ NGX_SOCKADDR_STRLEN, 1);
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0,
++ "*%uA quic recvmsg: %V fd:%d n:%z",
++ c->number, &addr, c->fd, n);
++ }
++
++ }
++#endif
++
++ log->data = NULL;
++ log->handler = NULL;
++
++ ls->handler(c);
++
++ next:
++
++ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
++ ev->available -= n;
++ }
++
++ } while (ev->available);
++}
++
++
++static void
++ngx_quic_close_accepted_connection(ngx_connection_t *c)
++{
++ ngx_free_connection(c);
++
++ c->fd = (ngx_socket_t) -1;
++
++ if (c->pool) {
++ ngx_destroy_pool(c->pool);
++ }
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
++#endif
++}
++
++
++void
++ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp,
++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
++{
++ ngx_int_t rc;
++ ngx_connection_t *c, *ct;
++ ngx_rbtree_node_t **p;
++ ngx_quic_socket_t *qsock, *qsockt;
++
++ for ( ;; ) {
++
++ if (node->key < temp->key) {
++
++ p = &temp->left;
++
++ } else if (node->key > temp->key) {
++
++ p = &temp->right;
++
++ } else { /* node->key == temp->key */
++
++ qsock = (ngx_quic_socket_t *) node;
++ c = qsock->udp.connection;
++
++ qsockt = (ngx_quic_socket_t *) temp;
++ ct = qsockt->udp.connection;
++
++ rc = ngx_memn2cmp(qsock->sid.id, qsockt->sid.id,
++ qsock->sid.len, qsockt->sid.len);
++
++ if (rc == 0 && c->listening->wildcard) {
++ rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen,
++ ct->local_sockaddr, ct->local_socklen, 1);
++ }
++
++ p = (rc < 0) ? &temp->left : &temp->right;
++ }
++
++ if (*p == sentinel) {
++ break;
++ }
++
++ temp = *p;
++ }
++
++ *p = node;
++ node->parent = temp;
++ node->left = sentinel;
++ node->right = sentinel;
++ ngx_rbt_red(node);
++}
++
++
++static ngx_connection_t *
++ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key,
++ struct sockaddr *local_sockaddr, socklen_t local_socklen)
++{
++ uint32_t hash;
++ ngx_int_t rc;
++ ngx_connection_t *c;
++ ngx_rbtree_node_t *node, *sentinel;
++ ngx_quic_socket_t *qsock;
++
++ if (key->len == 0) {
++ return NULL;
++ }
++
++ node = ls->rbtree.root;
++ sentinel = ls->rbtree.sentinel;
++ hash = ngx_crc32_long(key->data, key->len);
++
++ while (node != sentinel) {
++
++ if (hash < node->key) {
++ node = node->left;
++ continue;
++ }
++
++ if (hash > node->key) {
++ node = node->right;
++ continue;
++ }
++
++ /* hash == node->key */
++
++ qsock = (ngx_quic_socket_t *) node;
++
++ rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len);
++
++ c = qsock->udp.connection;
++
++ if (rc == 0 && ls->wildcard) {
++ rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen,
++ c->local_sockaddr, c->local_socklen, 1);
++ }
++
++ if (rc == 0) {
++ c->udp = &qsock->udp;
++ return c;
++ }
++
++ node = (rc < 0) ? node->left : node->right;
++ }
++
++ return NULL;
++}
+diff -r ac779115ed6e src/http/modules/ngx_http_ssl_module.c
+--- a/src/http/modules/ngx_http_ssl_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/modules/ngx_http_ssl_module.c Thu May 11 11:48:37 2023 -0400
+@@ -9,6 +9,10 @@
+ #include <ngx_core.h>
+ #include <ngx_http.h>
+
++#if (NGX_QUIC_OPENSSL_COMPAT)
++#include <ngx_event_quic_openssl_compat.h>
++#endif
++
+
+ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
+ ngx_pool_t *pool, ngx_str_t *s);
+@@ -52,6 +56,10 @@ static char *ngx_http_ssl_conf_command_c
+ void *data);
+
+ static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
++#if (NGX_QUIC_OPENSSL_COMPAT)
++static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf,
++ ngx_http_conf_addr_t *addr);
++#endif
+
+
+ static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = {
+@@ -419,16 +427,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
+ unsigned char *outlen, const unsigned char *in, unsigned int inlen,
+ void *arg)
+ {
+- unsigned int srvlen;
+- unsigned char *srv;
++ unsigned int srvlen;
++ unsigned char *srv;
+ #if (NGX_DEBUG)
+- unsigned int i;
++ unsigned int i;
+ #endif
+-#if (NGX_HTTP_V2)
+- ngx_http_connection_t *hc;
++#if (NGX_HTTP_V2 || NGX_HTTP_V3)
++ ngx_http_connection_t *hc;
+ #endif
+-#if (NGX_HTTP_V2 || NGX_DEBUG)
+- ngx_connection_t *c;
++#if (NGX_HTTP_V3)
++ ngx_http_v3_srv_conf_t *h3scf;
++#endif
++#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG)
++ ngx_connection_t *c;
+
+ c = ngx_ssl_get_connection(ssl_conn);
+ #endif
+@@ -441,14 +452,41 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t
+ }
+ #endif
+
+-#if (NGX_HTTP_V2)
++#if (NGX_HTTP_V2 || NGX_HTTP_V3)
+ hc = c->data;
++#endif
+
++#if (NGX_HTTP_V2)
+ if (hc->addr_conf->http2) {
+ srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS;
+ srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1;
+ } else
+ #endif
++#if (NGX_HTTP_V3)
++ if (hc->addr_conf->quic) {
++
++ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
++
++ if (h3scf->enable && h3scf->enable_hq) {
++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO
++ NGX_HTTP_V3_HQ_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO)
++ - 1;
++
++ } else if (h3scf->enable_hq) {
++ srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1;
++
++ } else if (h3scf->enable || hc->addr_conf->http3) {
++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1;
++
++ } else {
++ return SSL_TLSEXT_ERR_ALERT_FATAL;
++ }
++
++ } else
++#endif
+ {
+ srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS;
+ srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1;
+@@ -1241,6 +1279,7 @@ static ngx_int_t
+ ngx_http_ssl_init(ngx_conf_t *cf)
+ {
+ ngx_uint_t a, p, s;
++ const char *name;
+ ngx_http_conf_addr_t *addr;
+ ngx_http_conf_port_t *port;
+ ngx_http_ssl_srv_conf_t *sscf;
+@@ -1290,22 +1329,44 @@ ngx_http_ssl_init(ngx_conf_t *cf)
+ addr = port[p].addrs.elts;
+ for (a = 0; a < port[p].addrs.nelts; a++) {
+
+- if (!addr[a].opt.ssl) {
++ if (!addr[a].opt.ssl && !addr[a].opt.quic) {
+ continue;
+ }
+
++ if (addr[a].opt.quic) {
++ name = "quic";
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++ if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) {
++ return NGX_ERROR;
++ }
++#endif
++
++ } else {
++ name = "ssl";
++ }
++
+ cscf = addr[a].default_server;
+ sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+ if (sscf->certificates) {
++
++ if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
++ "\"ssl_protocols\" must enable TLSv1.3 for "
++ "the \"listen ... %s\" directive in %s:%ui",
++ name, cscf->file_name, cscf->line);
++ return NGX_ERROR;
++ }
++
+ continue;
+ }
+
+ if (!sscf->reject_handshake) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+- "the \"listen ... ssl\" directive in %s:%ui",
+- cscf->file_name, cscf->line);
++ "the \"listen ... %s\" directive in %s:%ui",
++ name, cscf->file_name, cscf->line);
+ return NGX_ERROR;
+ }
+
+@@ -1326,8 +1387,8 @@ ngx_http_ssl_init(ngx_conf_t *cf)
+
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+- "the \"listen ... ssl\" directive in %s:%ui",
+- cscf->file_name, cscf->line);
++ "the \"listen ... %s\" directive in %s:%ui",
++ name, cscf->file_name, cscf->line);
+ return NGX_ERROR;
+ }
+ }
+@@ -1335,3 +1396,31 @@ ngx_http_ssl_init(ngx_conf_t *cf)
+
+ return NGX_OK;
+ }
++
++
++#if (NGX_QUIC_OPENSSL_COMPAT)
++
++static ngx_int_t
++ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
++{
++ ngx_uint_t s;
++ ngx_http_ssl_srv_conf_t *sscf;
++ ngx_http_core_srv_conf_t **cscfp, *cscf;
++
++ cscfp = addr->servers.elts;
++ for (s = 0; s < addr->servers.nelts; s++) {
++
++ cscf = cscfp[s];
++ sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
++
++ if (sscf->certificates || sscf->reject_handshake) {
++ if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++ }
++
++ return NGX_OK;
++}
++
++#endif
+diff -r ac779115ed6e src/http/ngx_http.c
+--- a/src/http/ngx_http.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http.c Thu May 11 11:48:37 2023 -0400
+@@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_
+ port = cmcf->ports->elts;
+ for (i = 0; i < cmcf->ports->nelts; i++) {
+
+- if (p != port[i].port || sa->sa_family != port[i].family) {
++ if (p != port[i].port
++ || lsopt->type != port[i].type
++ || sa->sa_family != port[i].family)
++ {
+ continue;
+ }
+
+@@ -1217,6 +1220,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_
+ }
+
+ port->family = sa->sa_family;
++ port->type = lsopt->type;
+ port->port = p;
+ port->addrs.elts = NULL;
+
+@@ -1237,6 +1241,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
+ #if (NGX_HTTP_V2)
+ ngx_uint_t http2;
+ #endif
++#if (NGX_HTTP_V3)
++ ngx_uint_t http3;
++ ngx_uint_t quic;
++#endif
+
+ /*
+ * we cannot compare whole sockaddr struct's as kernel
+@@ -1278,6 +1286,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
+ protocols |= lsopt->http2 << 2;
+ protocols_prev |= addr[i].opt.http2 << 2;
+ #endif
++#if (NGX_HTTP_V3)
++ http3 = lsopt->http3 || addr[i].opt.http3;
++ quic = lsopt->quic || addr[i].opt.quic;
++#endif
+
+ if (lsopt->set) {
+
+@@ -1365,6 +1377,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n
+ #if (NGX_HTTP_V2)
+ addr[i].opt.http2 = http2;
+ #endif
++#if (NGX_HTTP_V3)
++ addr[i].opt.http3 = http3;
++ addr[i].opt.quic = quic;
++#endif
+
+ return NGX_OK;
+ }
+@@ -1831,6 +1847,7 @@ ngx_http_add_listening(ngx_conf_t *cf, n
+ }
+ #endif
+
++ ls->type = addr->opt.type;
+ ls->backlog = addr->opt.backlog;
+ ls->rcvbuf = addr->opt.rcvbuf;
+ ls->sndbuf = addr->opt.sndbuf;
+@@ -1866,6 +1883,19 @@ ngx_http_add_listening(ngx_conf_t *cf, n
+ ls->reuseport = addr->opt.reuseport;
+ #endif
+
++ ls->wildcard = addr->opt.wildcard;
++
++#if (NGX_HTTP_V3)
++
++ ls->quic = addr->opt.quic;
++
++ if (ls->quic) {
++ ngx_rbtree_init(&ls->rbtree, &ls->sentinel,
++ ngx_quic_rbtree_insert_value);
++ }
++
++#endif
++
+ return ls;
+ }
+
+@@ -1898,6 +1928,10 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h
+ #if (NGX_HTTP_V2)
+ addrs[i].conf.http2 = addr[i].opt.http2;
+ #endif
++#if (NGX_HTTP_V3)
++ addrs[i].conf.http3 = addr[i].opt.http3;
++ addrs[i].conf.quic = addr[i].opt.quic;
++#endif
+ addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+ if (addr[i].hash.buckets == NULL
+@@ -1963,6 +1997,10 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_
+ #if (NGX_HTTP_V2)
+ addrs6[i].conf.http2 = addr[i].opt.http2;
+ #endif
++#if (NGX_HTTP_V3)
++ addrs6[i].conf.http3 = addr[i].opt.http3;
++ addrs6[i].conf.quic = addr[i].opt.quic;
++#endif
+ addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+ if (addr[i].hash.buckets == NULL
+diff -r ac779115ed6e src/http/ngx_http.h
+--- a/src/http/ngx_http.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http.h Thu May 11 11:48:37 2023 -0400
+@@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ng
+ typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t;
+ typedef struct ngx_http_chunked_s ngx_http_chunked_t;
+ typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t;
++typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t;
++typedef struct ngx_http_v3_session_s ngx_http_v3_session_t;
+
+ typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+@@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_p
+ #if (NGX_HTTP_V2)
+ #include <ngx_http_v2.h>
+ #endif
++#if (NGX_HTTP_V3)
++#include <ngx_http_v3.h>
++#endif
+ #if (NGX_HTTP_CACHE)
+ #include <ngx_http_cache.h>
+ #endif
+@@ -124,6 +129,11 @@ void ngx_http_handler(ngx_http_request_t
+ void ngx_http_run_posted_requests(ngx_connection_t *c);
+ ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
+ ngx_http_posted_request_t *pr);
++ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
++ ngx_str_t *host);
++ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
++ ngx_uint_t alloc);
++void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc);
+ void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
+ void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc);
+
+@@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_r
+ #endif
+
+
+-#if (NGX_HTTP_V2)
++#if (NGX_HTTP_V2 || NGX_HTTP_V3)
+ ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len,
+ u_char **dst, ngx_uint_t last, ngx_log_t *log);
+ size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,
+diff -r ac779115ed6e src/http/ngx_http_core_module.c
+--- a/src/http/ngx_http_core_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_core_module.c Thu May 11 11:48:37 2023 -0400
+@@ -3005,6 +3005,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx
+ lsopt.socklen = sizeof(struct sockaddr_in);
+
+ lsopt.backlog = NGX_LISTEN_BACKLOG;
++ lsopt.type = SOCK_STREAM;
+ lsopt.rcvbuf = -1;
+ lsopt.sndbuf = -1;
+ #if (NGX_HAVE_SETFIB)
+@@ -3986,6 +3987,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
+ ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
+
+ lsopt.backlog = NGX_LISTEN_BACKLOG;
++ lsopt.type = SOCK_STREAM;
+ lsopt.rcvbuf = -1;
+ lsopt.sndbuf = -1;
+ #if (NGX_HAVE_SETFIB)
+@@ -4184,6 +4186,36 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
+ #endif
+ }
+
++ if (ngx_strcmp(value[n].data, "http3") == 0) {
++#if (NGX_HTTP_V3)
++ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
++ "the \"http3\" parameter is deprecated, "
++ "use \"quic\" parameter instead");
++ lsopt.quic = 1;
++ lsopt.http3 = 1;
++ lsopt.type = SOCK_DGRAM;
++ continue;
++#else
++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
++ "the \"http3\" parameter requires "
++ "ngx_http_v3_module");
++ return NGX_CONF_ERROR;
++#endif
++ }
++
++ if (ngx_strcmp(value[n].data, "quic") == 0) {
++#if (NGX_HTTP_V3)
++ lsopt.quic = 1;
++ lsopt.type = SOCK_DGRAM;
++ continue;
++#else
++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
++ "the \"quic\" parameter requires "
++ "ngx_http_v3_module");
++ return NGX_CONF_ERROR;
++#endif
++ }
++
+ if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) {
+
+ if (ngx_strcmp(&value[n].data[13], "on") == 0) {
+@@ -4285,6 +4317,28 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
+ return NGX_CONF_ERROR;
+ }
+
++#if (NGX_HTTP_V3)
++
++ if (lsopt.quic) {
++#if (NGX_HTTP_SSL)
++ if (lsopt.ssl) {
++ return "\"ssl\" parameter is incompatible with \"quic\"";
++ }
++#endif
++
++#if (NGX_HTTP_V2)
++ if (lsopt.http2) {
++ return "\"http2\" parameter is incompatible with \"quic\"";
++ }
++#endif
++
++ if (lsopt.proxy_protocol) {
++ return "\"proxy_protocol\" parameter is incompatible with \"quic\"";
++ }
++ }
++
++#endif
++
+ for (n = 0; n < u.naddrs; n++) {
+
+ for (i = 0; i < n; i++) {
+diff -r ac779115ed6e src/http/ngx_http_core_module.h
+--- a/src/http/ngx_http_core_module.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_core_module.h Thu May 11 11:48:37 2023 -0400
+@@ -75,6 +75,8 @@ typedef struct {
+ unsigned wildcard:1;
+ unsigned ssl:1;
+ unsigned http2:1;
++ unsigned http3:1;
++ unsigned quic:1;
+ #if (NGX_HAVE_INET6)
+ unsigned ipv6only:1;
+ #endif
+@@ -86,6 +88,7 @@ typedef struct {
+ int backlog;
+ int rcvbuf;
+ int sndbuf;
++ int type;
+ #if (NGX_HAVE_SETFIB)
+ int setfib;
+ #endif
+@@ -237,6 +240,8 @@ struct ngx_http_addr_conf_s {
+
+ unsigned ssl:1;
+ unsigned http2:1;
++ unsigned http3:1;
++ unsigned quic:1;
+ unsigned proxy_protocol:1;
+ };
+
+@@ -266,6 +271,7 @@ typedef struct {
+
+ typedef struct {
+ ngx_int_t family;
++ ngx_int_t type;
+ in_port_t port;
+ ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
+ } ngx_http_conf_port_t;
+diff -r ac779115ed6e src/http/ngx_http_request.c
+--- a/src/http/ngx_http_request.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_request.c Thu May 11 11:48:37 2023 -0400
+@@ -29,10 +29,6 @@ static ngx_int_t ngx_http_process_connec
+ static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+
+-static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
+- ngx_uint_t alloc);
+-static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
+- ngx_str_t *host);
+ static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
+ ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
+ ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
+@@ -50,7 +46,6 @@ static void ngx_http_keepalive_handler(n
+ static void ngx_http_set_lingering_close(ngx_connection_t *c);
+ static void ngx_http_lingering_close_handler(ngx_event_t *ev);
+ static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
+-static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
+ static void ngx_http_log_request(ngx_http_request_t *r);
+
+ static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
+@@ -329,6 +324,13 @@ ngx_http_init_connection(ngx_connection_
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (hc->addr_conf->quic) {
++ ngx_http_v3_init_stream(c);
++ return;
++ }
++#endif
++
+ #if (NGX_HTTP_SSL)
+ {
+ ngx_http_ssl_srv_conf_t *sscf;
+@@ -950,6 +952,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *
+ #ifdef SSL_OP_NO_RENEGOTIATION
+ SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION);
+ #endif
++
++#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
++#if (NGX_HTTP_V3)
++ if (c->listening->quic) {
++ SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
++ }
++#endif
++#endif
+ }
+
+ done:
+@@ -2095,7 +2105,7 @@ ngx_http_process_request(ngx_http_reques
+ }
+
+
+-static ngx_int_t
++ngx_int_t
+ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
+ {
+ u_char *h, ch;
+@@ -2187,7 +2197,7 @@ ngx_http_validate_host(ngx_str_t *host,
+ }
+
+
+-static ngx_int_t
++ngx_int_t
+ ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
+ {
+ ngx_int_t rc;
+@@ -2710,6 +2720,13 @@ ngx_http_finalize_connection(ngx_http_re
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->connection->quic) {
++ ngx_http_close_request(r, 0);
++ return;
++ }
++#endif
++
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->main->count != 1) {
+@@ -2925,6 +2942,20 @@ ngx_http_test_reading(ngx_http_request_t
+
+ #endif
+
++#if (NGX_HTTP_V3)
++
++ if (c->quic) {
++ if (rev->error) {
++ c->error = 1;
++ err = 0;
++ goto closed;
++ }
++
++ return;
++ }
++
++#endif
++
+ #if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+@@ -3590,7 +3621,7 @@ ngx_http_post_action(ngx_http_request_t
+ }
+
+
+-static void
++void
+ ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
+ {
+ ngx_connection_t *c;
+@@ -3677,7 +3708,12 @@ ngx_http_free_request(ngx_http_request_t
+
+ log->action = "closing request";
+
+- if (r->connection->timedout) {
++ if (r->connection->timedout
++#if (NGX_HTTP_V3)
++ && r->connection->quic == NULL
++#endif
++ )
++ {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->reset_timedout_connection) {
+@@ -3750,6 +3786,12 @@ ngx_http_close_connection(ngx_connection
+
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (c->quic) {
++ ngx_http_v3_reset_stream(c);
++ }
++#endif
++
+ #if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+ #endif
+diff -r ac779115ed6e src/http/ngx_http_request.h
+--- a/src/http/ngx_http_request.h Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_request.h Thu May 11 11:48:37 2023 -0400
+@@ -24,6 +24,7 @@
+ #define NGX_HTTP_VERSION_10 1000
+ #define NGX_HTTP_VERSION_11 1001
+ #define NGX_HTTP_VERSION_20 2000
++#define NGX_HTTP_VERSION_30 3000
+
+ #define NGX_HTTP_UNKNOWN 0x00000001
+ #define NGX_HTTP_GET 0x00000002
+@@ -323,6 +324,10 @@ typedef struct {
+ #endif
+ #endif
+
++#if (NGX_HTTP_V3 || NGX_COMPAT)
++ ngx_http_v3_session_t *v3_session;
++#endif
++
+ ngx_chain_t *busy;
+ ngx_int_t nbusy;
+
+@@ -451,6 +456,7 @@ struct ngx_http_request_s {
+
+ ngx_http_connection_t *http_connection;
+ ngx_http_v2_stream_t *stream;
++ ngx_http_v3_parse_t *v3_parse;
+
+ ngx_http_log_handler_pt log_handler;
+
+@@ -543,6 +549,7 @@ struct ngx_http_request_s {
+ unsigned request_complete:1;
+ unsigned request_output:1;
+ unsigned header_sent:1;
++ unsigned response_sent:1;
+ unsigned expect_tested:1;
+ unsigned root_tested:1;
+ unsigned done:1;
+diff -r ac779115ed6e src/http/ngx_http_request_body.c
+--- a/src/http/ngx_http_request_body.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_request_body.c Thu May 11 11:48:37 2023 -0400
+@@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_ht
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->http_version == NGX_HTTP_VERSION_30) {
++ rc = ngx_http_v3_read_request_body(r);
++ goto done;
++ }
++#endif
++
+ preread = r->header_in->last - r->header_in->pos;
+
+ if (preread) {
+@@ -238,6 +245,18 @@ ngx_http_read_unbuffered_request_body(ng
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->http_version == NGX_HTTP_VERSION_30) {
++ rc = ngx_http_v3_read_unbuffered_request_body(r);
++
++ if (rc == NGX_OK) {
++ r->reading_body = 0;
++ }
++
++ return rc;
++ }
++#endif
++
+ if (r->connection->read->timedout) {
+ r->connection->timedout = 1;
+ return NGX_HTTP_REQUEST_TIME_OUT;
+@@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_r
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->http_version == NGX_HTTP_VERSION_30) {
++ return NGX_OK;
++ }
++#endif
++
+ if (ngx_http_test_expect(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+@@ -921,6 +946,9 @@ ngx_http_test_expect(ngx_http_request_t
+ #if (NGX_HTTP_V2)
+ || r->stream != NULL
+ #endif
++#if (NGX_HTTP_V3)
++ || r->connection->quic != NULL
++#endif
+ )
+ {
+ return NGX_OK;
+diff -r ac779115ed6e src/http/ngx_http_upstream.c
+--- a/src/http/ngx_http_upstream.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_upstream.c Thu May 11 11:48:37 2023 -0400
+@@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (c->quic) {
++ ngx_http_upstream_init_request(r);
++ return;
++ }
++#endif
++
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+@@ -1354,6 +1361,19 @@ ngx_http_upstream_check_broken_connectio
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++
++ if (c->quic) {
++ if (c->write->error) {
++ ngx_http_upstream_finalize_request(r, u,
++ NGX_HTTP_CLIENT_CLOSED_REQUEST);
++ }
++
++ return;
++ }
++
++#endif
++
+ #if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+diff -r ac779115ed6e src/http/ngx_http_write_filter_module.c
+--- a/src/http/ngx_http_write_filter_module.c Tue Mar 28 18:01:53 2023 +0300
++++ b/src/http/ngx_http_write_filter_module.c Thu May 11 11:48:37 2023 -0400
+@@ -240,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t
+ r->out = NULL;
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
++ if (last) {
++ r->response_sent = 1;
++ }
++
+ return NGX_OK;
+ }
+
+@@ -346,6 +350,10 @@ ngx_http_write_filter(ngx_http_request_t
+
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
++ if (last) {
++ r->response_sent = 1;
++ }
++
+ if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
+ return NGX_AGAIN;
+ }
+diff -r ac779115ed6e src/http/v3/ngx_http_v3.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3.c Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,113 @@
++
++/*
++ * Copyright (C) Roman Arutyunyan
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_http.h>
++
++
++static void ngx_http_v3_keepalive_handler(ngx_event_t *ev);
++static void ngx_http_v3_cleanup_session(void *data);
++
++
++ngx_int_t
++ngx_http_v3_init_session(ngx_connection_t *c)
++{
++ ngx_pool_cleanup_t *cln;
++ ngx_http_connection_t *hc;
++ ngx_http_v3_session_t *h3c;
++
++ hc = c->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
++
++ h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t));
++ if (h3c == NULL) {
++ goto failed;
++ }
++
++ h3c->max_push_id = (uint64_t) -1;
++ h3c->goaway_push_id = (uint64_t) -1;
++
++ ngx_queue_init(&h3c->blocked);
++ ngx_queue_init(&h3c->pushing);
++
++ h3c->keepalive.log = c->log;
++ h3c->keepalive.data = c;
++ h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
++
++ h3c->table.send_insert_count.log = c->log;
++ h3c->table.send_insert_count.data = c;
++ h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler;
++
++ cln = ngx_pool_cleanup_add(c->pool, 0);
++ if (cln == NULL) {
++ goto failed;
++ }
++
++ cln->handler = ngx_http_v3_cleanup_session;
++ cln->data = h3c;
++
++ hc->v3_session = h3c;
++
++ return NGX_OK;
++
++failed:
++
++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session");
++ return NGX_ERROR;
++}
++
++
++static void
++ngx_http_v3_keepalive_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ c = ev->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler");
++
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
++ "keepalive timeout");
++}
++
++
++static void
++ngx_http_v3_cleanup_session(void *data)
++{
++ ngx_http_v3_session_t *h3c = data;
++
++ ngx_http_v3_cleanup_table(h3c);
++
++ if (h3c->keepalive.timer_set) {
++ ngx_del_timer(&h3c->keepalive);
++ }
++
++ if (h3c->table.send_insert_count.posted) {
++ ngx_delete_posted_event(&h3c->table.send_insert_count);
++ }
++}
++
++
++ngx_int_t
++ngx_http_v3_check_flood(ngx_connection_t *c)
++{
++ ngx_http_v3_session_t *h3c;
++
++ h3c = ngx_http_v3_get_session(c);
++
++ if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected");
++
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
++ "HTTP/3 flood detected");
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
+diff -r ac779115ed6e src/http/v3/ngx_http_v3.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3.h Thu May 11 11:48:37 2023 -0400
+@@ -0,0 +1,170 @@
++
++/*
++ * Copyright (C) Roman Arutyunyan
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_HTTP_V3_H_INCLUDED_
++#define _NGX_HTTP_V3_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_http.h>
++
++#include <ngx_http_v3_parse.h>
++#include <ngx_http_v3_encode.h>
++#include <ngx_http_v3_uni.h>
++#include <ngx_http_v3_table.h>
++
++
++#define NGX_HTTP_V3_ALPN_PROTO "\x02h3"
++#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop"
++#define NGX_HTTP_V3_HQ_PROTO "hq-interop"
++
++#define NGX_HTTP_V3_VARLEN_INT_LEN 4
++#define NGX_HTTP_V3_PREFIX_INT_LEN 11
++
++#define NGX_HTTP_V3_STREAM_CONTROL 0x00
++#define NGX_HTTP_V3_STREAM_PUSH 0x01
++#define NGX_HTTP_V3_STREAM_ENCODER 0x02
++#define NGX_HTTP_V3_STREAM_DECODER 0x03
++
++#define NGX_HTTP_V3_FRAME_DATA 0x00
++#define NGX_HTTP_V3_FRAME_HEADERS 0x01
++#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03
++#define NGX_HTTP_V3_FRAME_SETTINGS 0x04
++#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05
++#define NGX_HTTP_V3_FRAME_GOAWAY 0x07
++#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d
++
++#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01
++#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06
++#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07
++
++#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096
++
++#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0
++#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1
++#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2
++#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3
++#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4
++#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5
++#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6
++#define NGX_HTTP_V3_MAX_UNI_STREAMS 3
++
++/* HTTP/3 errors */
++#define NGX_HTTP_V3_ERR_NO_ERROR 0x100
++#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101
++#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102
++#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103
++#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104
++#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105
++#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106
++#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107
++#define NGX_HTTP_V3_ERR_ID_ERROR 0x108
++#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109
++#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a
++#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b
++#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c
++#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d
++#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f
++#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110
++
++/* QPACK errors */
++#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200
++#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201
++#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202
++
++
++#define ngx_http_quic_get_connection(c) \
++ ((ngx_http_connection_t *) ((c)->quic ? (c)->quic->parent->data \
++ : (c)->data))
++
++#define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session
++
++#define ngx_http_v3_get_module_loc_conf(c, module) \
++ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \
++ module)
++
++#define ngx_http_v3_get_module_srv_conf(c, module)