diff --git a/www/Makefile b/www/Makefile index 51af194f4639..7be376a08b8d 100644 --- a/www/Makefile +++ b/www/Makefile @@ -1,2543 +1,2544 @@ COMMENT = Ports related to the World Wide Web SUBDIR += R-cran-RgoogleMaps SUBDIR += R-cran-Rook SUBDIR += R-cran-bslib SUBDIR += R-cran-crosstalk SUBDIR += R-cran-downloader SUBDIR += R-cran-gh SUBDIR += R-cran-htmlwidgets SUBDIR += R-cran-httpuv SUBDIR += R-cran-httr SUBDIR += R-cran-jquerylib SUBDIR += R-cran-rvest SUBDIR += R-cran-scrapeR SUBDIR += R-cran-selectr SUBDIR += R-cran-servr SUBDIR += R-cran-shiny SUBDIR += R-cran-shinyjs SUBDIR += R-cran-webshot SUBDIR += Stikked SUBDIR += UniversalFeedCreator SUBDIR += adguardhome SUBDIR += adjuster SUBDIR += adzap SUBDIR += alef-webfont SUBDIR += amfora SUBDIR += amphetadesk SUBDIR += analog SUBDIR += angie SUBDIR += angie-module-auth-jwt SUBDIR += angie-module-auth-spnego SUBDIR += angie-module-brotli SUBDIR += angie-module-cache-purge SUBDIR += angie-module-dav-ext SUBDIR += angie-module-echo SUBDIR += angie-module-enhanced-memcached SUBDIR += angie-module-eval SUBDIR += angie-module-geoip2 SUBDIR += angie-module-headers-more SUBDIR += angie-module-image-filter SUBDIR += angie-module-jwt SUBDIR += angie-module-keyval SUBDIR += angie-module-lua SUBDIR += angie-module-ndk SUBDIR += angie-module-njs SUBDIR += angie-module-perl SUBDIR += angie-module-postgres SUBDIR += angie-module-redis2 SUBDIR += angie-module-rtmp SUBDIR += angie-module-set-misc SUBDIR += angie-module-subs SUBDIR += angie-module-testcookie SUBDIR += angie-module-upload SUBDIR += angie-module-vod SUBDIR += angie-module-xslt SUBDIR += anyremote2html SUBDIR += apache-mode.el SUBDIR += apache24 SUBDIR += apt-cacher-ng SUBDIR += aquatone SUBDIR += archiva SUBDIR += aria2 SUBDIR += asql SUBDIR += authelia SUBDIR += awstats SUBDIR += axis2 SUBDIR += bacula-web SUBDIR += baculum-api SUBDIR += baculum-common SUBDIR += baculum-web SUBDIR += badwolf SUBDIR += baikal SUBDIR += bareos-webui SUBDIR += bareos20-webui SUBDIR += bareos21-webui SUBDIR += bareos22-webui SUBDIR += beehive SUBDIR += bkmrkconv SUBDIR += bluefish SUBDIR += bolt SUBDIR += bozohttpd SUBDIR += browsh SUBDIR += bugzilla2atom SUBDIR += buku SUBDIR += butterfly SUBDIR += c-icap SUBDIR += c-icap-modules SUBDIR += cadaver SUBDIR += caddy SUBDIR += caddy-custom SUBDIR += calamaris SUBDIR += calamaris-devel SUBDIR += caldavzap SUBDIR += carbonapi SUBDIR += castget SUBDIR += castor SUBDIR += cgi-lib SUBDIR += cgi-lib.pl SUBDIR += cgicc SUBDIR += cgichk SUBDIR += cgiwrap SUBDIR += checkbot SUBDIR += chems SUBDIR += chisel SUBDIR += choqok SUBDIR += chpasswd SUBDIR += chrome-gnome-shell SUBDIR += chromium SUBDIR += cinny SUBDIR += civetweb SUBDIR += ckeditor SUBDIR += cl-lml SUBDIR += cl-lml-sbcl SUBDIR += clearsilver SUBDIR += closure-compiler SUBDIR += cntlm SUBDIR += cobalt SUBDIR += codeigniter SUBDIR += colly SUBDIR += coppermine SUBDIR += cplanet SUBDIR += cpp-httplib SUBDIR += cpr SUBDIR += crawl SUBDIR += css-mode.el SUBDIR += cssed SUBDIR += csso SUBDIR += csstidy SUBDIR += ctemplate SUBDIR += cutelyst SUBDIR += darkhttpd SUBDIR += dasherr SUBDIR += davical SUBDIR += davix SUBDIR += dddbl SUBDIR += ddgr SUBDIR += deforaos-surfer SUBDIR += deno SUBDIR += devd SUBDIR += dezoomify-rs SUBDIR += dillo2 SUBDIR += dmarcts-report-viewer SUBDIR += dojo SUBDIR += dokuwiki SUBDIR += dolibarr SUBDIR += dolibarr16 SUBDIR += dolibarr17 SUBDIR += dolibarr18 SUBDIR += domoticz SUBDIR += dooble SUBDIR += dot-http SUBDIR += dpp SUBDIR += drill SUBDIR += drupal10 SUBDIR += drupal7 SUBDIR += drupal7-wysiwyg SUBDIR += drush SUBDIR += dtse SUBDIR += dufs SUBDIR += e107 SUBDIR += e2guardian SUBDIR += edbrowse SUBDIR += eden SUBDIR += element-web SUBDIR += elgg SUBDIR += elinks SUBDIR += emacs-w3m SUBDIR += encode-explorer SUBDIR += envoy SUBDIR += eolie SUBDIR += ephemera SUBDIR += epiphany SUBDIR += fabio SUBDIR += falkon SUBDIR += fancybox SUBDIR += faup SUBDIR += fcgi SUBDIR += fcgiwrap SUBDIR += ffproxy SUBDIR += ffsend SUBDIR += fgallery SUBDIR += filtron SUBDIR += firedm SUBDIR += firefox SUBDIR += firefox-esr SUBDIR += flat-frog SUBDIR += flexget SUBDIR += flickcurl SUBDIR += foreign-cdm SUBDIR += formication SUBDIR += foswiki SUBDIR += free-sa-devel + SUBDIR += freenginx SUBDIR += fswiki SUBDIR += fusionpbx SUBDIR += g-cows SUBDIR += g-gcl SUBDIR += galene SUBDIR += gallery-dl SUBDIR += gallery2 SUBDIR += garage SUBDIR += gatling SUBDIR += gauche-makiki SUBDIR += geckodriver SUBDIR += geneweb SUBDIR += geolizer SUBDIR += get_flash_videos SUBDIR += ghostunnel SUBDIR += gist SUBDIR += gitea SUBDIR += gitlab-ce SUBDIR += gitlab-pages SUBDIR += gitlab-workhorse SUBDIR += glassfish SUBDIR += glassfish4 SUBDIR += glewlwyd SUBDIR += glpi SUBDIR += gnome-user-share SUBDIR += go-www SUBDIR += gobuffalo SUBDIR += gohugo SUBDIR += googlebook_dl SUBDIR += googler SUBDIR += goose SUBDIR += gophernicus SUBDIR += gopherus SUBDIR += gotty SUBDIR += gpx2map SUBDIR += grafana SUBDIR += grafana9 SUBDIR += grails SUBDIR += gregarius SUBDIR += groupoffice SUBDIR += grr SUBDIR += gstreamer1-plugins-neon SUBDIR += gstreamer1-plugins-srt SUBDIR += gtkhtml4 SUBDIR += guacamole-client SUBDIR += gurl SUBDIR += gwsocket SUBDIR += h2c SUBDIR += h2o SUBDIR += h2o-devel SUBDIR += habari SUBDIR += hiawatha SUBDIR += hiawatha-monitor SUBDIR += homer-web SUBDIR += hotcrp SUBDIR += hs-DAV SUBDIR += hs-gitit SUBDIR += hs-hjsmin SUBDIR += hs-postgrest SUBDIR += hs-wai-app-static SUBDIR += hs-yesod-bin SUBDIR += htdigest SUBDIR += htdump SUBDIR += html2hdml SUBDIR += html2wml SUBDIR += htmlcompressor SUBDIR += htmlcxx SUBDIR += httest SUBDIR += http-parser SUBDIR += http_get SUBDIR += http_load SUBDIR += http_post SUBDIR += httpasyncclient SUBDIR += httpclient SUBDIR += httpcore SUBDIR += httptunnel SUBDIR += httrack SUBDIR += hurl SUBDIR += hypermail SUBDIR += icapeg SUBDIR += igal2 SUBDIR += ikiwiki SUBDIR += ilias SUBDIR += interchange SUBDIR += iridium SUBDIR += itop SUBDIR += janus SUBDIR += jericho-html SUBDIR += jesred SUBDIR += jetty10 SUBDIR += jira-cli SUBDIR += jitsi-meet SUBDIR += jmeter SUBDIR += joomla4 SUBDIR += joomla5 SUBDIR += jwt-cli SUBDIR += kanboard SUBDIR += kannel SUBDIR += kannel-sqlbox SUBDIR += kcgi SUBDIR += kdsoap SUBDIR += kf5-khtml SUBDIR += kf5-kjs SUBDIR += kf5-kjsembed SUBDIR += kineto SUBDIR += kiwix-tools SUBDIR += kohana SUBDIR += ladybird SUBDIR += lagrange SUBDIR += larbin SUBDIR += libapreq2 SUBDIR += libdatachannel SUBDIR += libdom SUBDIR += libecap SUBDIR += libepc SUBDIR += libevhtp SUBDIR += libhsts SUBDIR += libhubbub SUBDIR += libjwt SUBDIR += libmicrohttpd SUBDIR += libnghttp2 SUBDIR += libnghttp3 SUBDIR += libresonic-standalone SUBDIR += librespeed SUBDIR += librewolf SUBDIR += librtcdcpp SUBDIR += libwpe SUBDIR += libwww SUBDIR += lightsquid SUBDIR += lighttpd SUBDIR += limesurvey SUBDIR += linklint SUBDIR += links SUBDIR += links1 SUBDIR += linux-c7-qtwebkit SUBDIR += linux-widevine-cdm SUBDIR += litmus SUBDIR += ljdeps SUBDIR += llhttp SUBDIR += logswan SUBDIR += logtools SUBDIR += ls-qpack SUBDIR += lua-resty-core SUBDIR += lua-resty-http SUBDIR += lua-resty-lrucache SUBDIR += lua-resty-session SUBDIR += luakit SUBDIR += luakit-devel SUBDIR += lux SUBDIR += lychee SUBDIR += lynx SUBDIR += lynx-current SUBDIR += lzr SUBDIR += man2web SUBDIR += manpageblog SUBDIR += mathjax SUBDIR += mathjax3 SUBDIR += mathopd SUBDIR += matomo SUBDIR += mattermost-server SUBDIR += mattermost-webapp SUBDIR += mediawiki135 SUBDIR += mediawiki139 SUBDIR += mediawiki140 SUBDIR += mediawiki141 SUBDIR += mergelog SUBDIR += mhonarc SUBDIR += micro_httpd SUBDIR += microbin SUBDIR += middleman SUBDIR += midori SUBDIR += mimetex SUBDIR += mini_httpd SUBDIR += miniflux SUBDIR += minio SUBDIR += minio-client SUBDIR += miniserve SUBDIR += mirrorselect SUBDIR += mitmproxy SUBDIR += mknmz-wwwoffle SUBDIR += mnogosearch SUBDIR += mod_amazon_proxy SUBDIR += mod_asn SUBDIR += mod_auth_cas SUBDIR += mod_auth_cookie_mysql2 SUBDIR += mod_auth_gssapi SUBDIR += mod_auth_kerb2 SUBDIR += mod_auth_mellon SUBDIR += mod_auth_mysql2 SUBDIR += mod_auth_mysql_another SUBDIR += mod_auth_openid SUBDIR += mod_auth_openidc SUBDIR += mod_auth_pam2 SUBDIR += mod_auth_pgsql2 SUBDIR += mod_auth_pubtkt SUBDIR += mod_auth_tkt SUBDIR += mod_auth_xradius SUBDIR += mod_authn_dovecot SUBDIR += mod_authnz_external24 SUBDIR += mod_authz_unixgroup24 SUBDIR += mod_cfg_ldap SUBDIR += mod_cloudflare SUBDIR += mod_dav_svn SUBDIR += mod_defensible SUBDIR += mod_dnssd SUBDIR += mod_evasive SUBDIR += mod_fastcgi SUBDIR += mod_fcgid SUBDIR += mod_fileiri SUBDIR += mod_gnutls SUBDIR += mod_h264_streaming SUBDIR += mod_http2 SUBDIR += mod_jk SUBDIR += mod_limitipconn2 SUBDIR += mod_log_sql SUBDIR += mod_maxminddb SUBDIR += mod_memcache SUBDIR += mod_memcache_block SUBDIR += mod_mono SUBDIR += mod_mpm_itk SUBDIR += mod_perl2 SUBDIR += mod_php81 SUBDIR += mod_php82 SUBDIR += mod_php83 SUBDIR += mod_proctitle SUBDIR += mod_qos SUBDIR += mod_realdoc SUBDIR += mod_reproxy SUBDIR += mod_rivet SUBDIR += mod_rpaf2 SUBDIR += mod_scgi SUBDIR += mod_security SUBDIR += mod_setenvifplus SUBDIR += mod_tidy SUBDIR += mod_umask SUBDIR += mod_webauth SUBDIR += mod_webkit SUBDIR += mod_wsgi4 SUBDIR += mod_xsendfile SUBDIR += mohawk SUBDIR += moinmoin SUBDIR += moinmoincli SUBDIR += mongoose SUBDIR += monolith SUBDIR += moodle41 SUBDIR += moodle42 SUBDIR += moodle43 SUBDIR += morty SUBDIR += multisort SUBDIR += multiwatch SUBDIR += mybb SUBDIR += mysqlphp2postgres SUBDIR += mythplugin-mythweb SUBDIR += nanoblogger SUBDIR += nanoblogger-extra SUBDIR += ncgopher SUBDIR += neon SUBDIR += netrik SUBDIR += netstiff SUBDIR += netsurf SUBDIR += newsboat SUBDIR += nextcloud SUBDIR += nextcloud-appointments SUBDIR += nextcloud-calendar SUBDIR += nextcloud-contacts SUBDIR += nextcloud-deck SUBDIR += nextcloud-forms SUBDIR += nextcloud-groupfolders SUBDIR += nextcloud-notes SUBDIR += nextcloud-tasks SUBDIR += nghttp2 SUBDIR += nginx SUBDIR += nginx-devel SUBDIR += nginx-full SUBDIR += nginx-lite SUBDIR += nginx-naxsi SUBDIR += nginx-prometheus-exporter SUBDIR += nginx-ultimate-bad-bot-blocker SUBDIR += nginx-vts-exporter SUBDIR += nibbleblog SUBDIR += nift SUBDIR += node SUBDIR += node16 SUBDIR += node18 SUBDIR += node20 SUBDIR += node21 SUBDIR += nostromo SUBDIR += novnc SUBDIR += novnc-websockify SUBDIR += npapi-xine SUBDIR += npc SUBDIR += npm SUBDIR += npm-node16 SUBDIR += npm-node18 SUBDIR += npm-node20 SUBDIR += npm-node21 SUBDIR += oauth2-proxy SUBDIR += obhttpd SUBDIR += offpunk SUBDIR += oneshot SUBDIR += onionbalance SUBDIR += onionshare SUBDIR += onionshare-cli SUBDIR += onlyoffice-documentserver SUBDIR += opencart SUBDIR += openresty SUBDIR += orangehrm SUBDIR += osrm-backend SUBDIR += osticket SUBDIR += ot-recorder SUBDIR += otrs SUBDIR += otter-browser SUBDIR += owncast SUBDIR += owncloud SUBDIR += p5-AMF-Perl SUBDIR += p5-Acme-Monta SUBDIR += p5-Amon2 SUBDIR += p5-Amon2-Lite SUBDIR += p5-Amon2-Plugin-LogDispatch SUBDIR += p5-Amon2-Plugin-Web-CSRFDefender SUBDIR += p5-Amon2-Plugin-Web-MobileAgent SUBDIR += p5-Any-Template SUBDIR += p5-Any-URI-Escape SUBDIR += p5-AnyEvent-HTTP SUBDIR += p5-AnyEvent-HTTP-LWP-UserAgent SUBDIR += p5-AnyEvent-HTTPD SUBDIR += p5-AnyEvent-ReverseHTTP SUBDIR += p5-AnyEvent-SCGI SUBDIR += p5-AnyEvent-WebSocket-Client SUBDIR += p5-Apache-ASP SUBDIR += p5-Apache-Admin-Config SUBDIR += p5-Apache-AuthCookie SUBDIR += p5-Apache-AuthTicket SUBDIR += p5-Apache-Clean2 SUBDIR += p5-Apache-Config-Preproc SUBDIR += p5-Apache-ConfigFile SUBDIR += p5-Apache-ConfigParser SUBDIR += p5-Apache-DB SUBDIR += p5-Apache-DBI SUBDIR += p5-Apache-Defaults SUBDIR += p5-Apache-Htgroup SUBDIR += p5-Apache-LogFormat-Compiler SUBDIR += p5-Apache-MP3 SUBDIR += p5-Apache-ParseFormData SUBDIR += p5-Apache-Profiler SUBDIR += p5-Apache-Session SUBDIR += p5-Apache-Session-PHP SUBDIR += p5-Apache-Session-SQLite3 SUBDIR += p5-Apache-Session-SharedMem SUBDIR += p5-Apache-Session-Wrapper SUBDIR += p5-Apache-SessionX SUBDIR += p5-Apache-Singleton SUBDIR += p5-Apache2-SiteControl SUBDIR += p5-ApacheBench SUBDIR += p5-App-Nopaste SUBDIR += p5-App-gist SUBDIR += p5-Ark SUBDIR += p5-Bigtop SUBDIR += p5-Blog-Spam SUBDIR += p5-Browser-Open SUBDIR += p5-Business-PayPal SUBDIR += p5-CGI SUBDIR += p5-CGI-Ajax SUBDIR += p5-CGI-Application SUBDIR += p5-CGI-Application-Dispatch SUBDIR += p5-CGI-Application-Dispatch-Server SUBDIR += p5-CGI-Application-PSGI SUBDIR += p5-CGI-Application-Plugin-AnyTemplate SUBDIR += p5-CGI-Application-Plugin-Apache SUBDIR += p5-CGI-Application-Plugin-Authentication SUBDIR += p5-CGI-Application-Plugin-Authorization SUBDIR += p5-CGI-Application-Plugin-AutoRunmode SUBDIR += p5-CGI-Application-Plugin-Config-YAML SUBDIR += p5-CGI-Application-Plugin-ConfigAuto SUBDIR += p5-CGI-Application-Plugin-DBH SUBDIR += p5-CGI-Application-Plugin-DebugScreen SUBDIR += p5-CGI-Application-Plugin-DevPopup SUBDIR += p5-CGI-Application-Plugin-Forward SUBDIR += p5-CGI-Application-Plugin-HTDot SUBDIR += p5-CGI-Application-Plugin-HTMLPrototype SUBDIR += p5-CGI-Application-Plugin-HtmlTidy SUBDIR += p5-CGI-Application-Plugin-JSON SUBDIR += p5-CGI-Application-Plugin-LinkIntegrity SUBDIR += p5-CGI-Application-Plugin-LogDispatch SUBDIR += p5-CGI-Application-Plugin-MessageStack SUBDIR += p5-CGI-Application-Plugin-Redirect SUBDIR += p5-CGI-Application-Plugin-Session SUBDIR += p5-CGI-Application-Plugin-Stream SUBDIR += p5-CGI-Application-Plugin-TT SUBDIR += p5-CGI-Application-Plugin-ValidateRM SUBDIR += p5-CGI-Application-Plugin-ViewCode SUBDIR += p5-CGI-Application-Server SUBDIR += p5-CGI-ArgChecker SUBDIR += p5-CGI-Builder SUBDIR += p5-CGI-Builder-TT2 SUBDIR += p5-CGI-Cache SUBDIR += p5-CGI-Compile SUBDIR += p5-CGI-Compress-Gzip SUBDIR += p5-CGI-Cookie-Splitter SUBDIR += p5-CGI-Cookie-XS SUBDIR += p5-CGI-Deurl-XS SUBDIR += p5-CGI-Emulate-PSGI SUBDIR += p5-CGI-EncryptForm SUBDIR += p5-CGI-Enurl SUBDIR += p5-CGI-Ex SUBDIR += p5-CGI-Expand SUBDIR += p5-CGI-ExtDirect SUBDIR += p5-CGI-FCKeditor SUBDIR += p5-CGI-Fast SUBDIR += p5-CGI-FastTemplate SUBDIR += p5-CGI-FormBuilder SUBDIR += p5-CGI-Framework SUBDIR += p5-CGI-Kwiki SUBDIR += p5-CGI-Lite SUBDIR += p5-CGI-Minimal SUBDIR += p5-CGI-PSGI SUBDIR += p5-CGI-Pager SUBDIR += p5-CGI-Prototype SUBDIR += p5-CGI-Response SUBDIR += p5-CGI-SSI SUBDIR += p5-CGI-Session SUBDIR += p5-CGI-Session-ExpireSessions SUBDIR += p5-CGI-Simple SUBDIR += p5-CGI-SpeedyCGI SUBDIR += p5-CGI-Struct SUBDIR += p5-CGI-Thin SUBDIR += p5-CGI-Tiny SUBDIR += p5-CGI-Untaint SUBDIR += p5-CGI-Untaint-date SUBDIR += p5-CGI-Untaint-email SUBDIR += p5-CGI-Upload SUBDIR += p5-CGI-Utils SUBDIR += p5-CGI-XMLApplication SUBDIR += p5-CIF-Client SUBDIR += p5-CSS-DOM SUBDIR += p5-CSS-Inliner SUBDIR += p5-Catalyst-Action-REST SUBDIR += p5-Catalyst-Action-RenderView SUBDIR += p5-Catalyst-Action-Serialize-XML-Hash-LX SUBDIR += p5-Catalyst-ActionRole-ACL SUBDIR += p5-Catalyst-Authentication-Credential-HTTP SUBDIR += p5-Catalyst-Authentication-Credential-OpenID SUBDIR += p5-Catalyst-Authentication-Store-DBIx-Class SUBDIR += p5-Catalyst-Authentication-Store-LDAP SUBDIR += p5-Catalyst-Component-ACCEPT_CONTEXT SUBDIR += p5-Catalyst-Component-InstancePerContext SUBDIR += p5-Catalyst-Controller-ActionRole SUBDIR += p5-Catalyst-Controller-BindLex SUBDIR += p5-Catalyst-Controller-FormBuilder SUBDIR += p5-Catalyst-Controller-HTML-FormFu SUBDIR += p5-Catalyst-Controller-RateLimit SUBDIR += p5-Catalyst-Controller-RequestToken SUBDIR += p5-Catalyst-Controller-SOAP SUBDIR += p5-Catalyst-Devel SUBDIR += p5-Catalyst-DispatchType-Regex SUBDIR += p5-Catalyst-Engine-Apache SUBDIR += p5-Catalyst-Engine-HTTP-Prefork SUBDIR += p5-Catalyst-Engine-PSGI SUBDIR += p5-Catalyst-Enzyme SUBDIR += p5-Catalyst-Helper-Controller-Scaffold SUBDIR += p5-Catalyst-Manual SUBDIR += p5-Catalyst-Model-Adaptor SUBDIR += p5-Catalyst-Model-CDBI SUBDIR += p5-Catalyst-Model-CDBI-Plain SUBDIR += p5-Catalyst-Model-CDBI-Sweet SUBDIR += p5-Catalyst-Model-DBIC-Plain SUBDIR += p5-Catalyst-Model-DBIC-Schema SUBDIR += p5-Catalyst-Model-DynamicAdaptor SUBDIR += p5-Catalyst-Model-LDAP SUBDIR += p5-Catalyst-Model-Memcached SUBDIR += p5-Catalyst-Model-Oryx SUBDIR += p5-Catalyst-Model-Tarantool SUBDIR += p5-Catalyst-Model-XML-Feed SUBDIR += p5-Catalyst-Model-Xapian SUBDIR += p5-Catalyst-Plugin-AtomServer SUBDIR += p5-Catalyst-Plugin-Authentication SUBDIR += p5-Catalyst-Plugin-Authentication-CDBI SUBDIR += p5-Catalyst-Plugin-Authentication-OpenID SUBDIR += p5-Catalyst-Plugin-Authentication-Store-Htpasswd SUBDIR += p5-Catalyst-Plugin-Authorization-ACL SUBDIR += p5-Catalyst-Plugin-Authorization-Roles SUBDIR += p5-Catalyst-Plugin-AutoCRUD SUBDIR += p5-Catalyst-Plugin-Browser SUBDIR += p5-Catalyst-Plugin-C3 SUBDIR += p5-Catalyst-Plugin-Cache SUBDIR += p5-Catalyst-Plugin-Cache-FastMmap SUBDIR += p5-Catalyst-Plugin-Cache-Memcached SUBDIR += p5-Catalyst-Plugin-Cache-Memcached-Fast SUBDIR += p5-Catalyst-Plugin-Captcha SUBDIR += p5-Catalyst-Plugin-ConfigLoader SUBDIR += p5-Catalyst-Plugin-ConfigLoader-Environment SUBDIR += p5-Catalyst-Plugin-CookiedSession SUBDIR += p5-Catalyst-Plugin-DateTime SUBDIR += p5-Catalyst-Plugin-DefaultEnd SUBDIR += p5-Catalyst-Plugin-Email SUBDIR += p5-Catalyst-Plugin-ErrorCatcher SUBDIR += p5-Catalyst-Plugin-FillInForm SUBDIR += p5-Catalyst-Plugin-FormBuilder SUBDIR += p5-Catalyst-Plugin-FormValidator SUBDIR += p5-Catalyst-Plugin-I18N SUBDIR += p5-Catalyst-Plugin-Log-Dispatch SUBDIR += p5-Catalyst-Plugin-Log-Handler SUBDIR += p5-Catalyst-Plugin-LogWarnings SUBDIR += p5-Catalyst-Plugin-PageCache SUBDIR += p5-Catalyst-Plugin-Params-Nested SUBDIR += p5-Catalyst-Plugin-Pluggable SUBDIR += p5-Catalyst-Plugin-Prototype SUBDIR += p5-Catalyst-Plugin-RunAfterRequest SUBDIR += p5-Catalyst-Plugin-Scheduler SUBDIR += p5-Catalyst-Plugin-Server SUBDIR += p5-Catalyst-Plugin-Session SUBDIR += p5-Catalyst-Plugin-Session-FastMmap SUBDIR += p5-Catalyst-Plugin-Session-PerUser SUBDIR += p5-Catalyst-Plugin-Session-State-Cookie SUBDIR += p5-Catalyst-Plugin-Session-State-URI SUBDIR += p5-Catalyst-Plugin-Session-Store-Cache SUBDIR += p5-Catalyst-Plugin-Session-Store-DBI SUBDIR += p5-Catalyst-Plugin-Session-Store-DBIC SUBDIR += p5-Catalyst-Plugin-Session-Store-Delegate SUBDIR += p5-Catalyst-Plugin-Session-Store-FastMmap SUBDIR += p5-Catalyst-Plugin-Session-Store-File SUBDIR += p5-Catalyst-Plugin-Session-Store-Memcached SUBDIR += p5-Catalyst-Plugin-Session-Store-Memcached-Fast SUBDIR += p5-Catalyst-Plugin-Setenv SUBDIR += p5-Catalyst-Plugin-SmartURI SUBDIR += p5-Catalyst-Plugin-StackTrace SUBDIR += p5-Catalyst-Plugin-Static SUBDIR += p5-Catalyst-Plugin-Static-Simple SUBDIR += p5-Catalyst-Plugin-StatusMessage SUBDIR += p5-Catalyst-Plugin-SubRequest SUBDIR += p5-Catalyst-Plugin-Textile SUBDIR += p5-Catalyst-Plugin-Unicode SUBDIR += p5-Catalyst-Plugin-XMLRPC SUBDIR += p5-Catalyst-Runtime SUBDIR += p5-Catalyst-TraitFor-Controller-DBIC-DoesPaging SUBDIR += p5-Catalyst-TraitFor-Request-BrowserDetect SUBDIR += p5-Catalyst-View-ClearSilver SUBDIR += p5-Catalyst-View-Email SUBDIR += p5-Catalyst-View-GraphViz SUBDIR += p5-Catalyst-View-HTML-Template SUBDIR += p5-Catalyst-View-HTML-Template-Compiled SUBDIR += p5-Catalyst-View-JSON SUBDIR += p5-Catalyst-View-Jemplate SUBDIR += p5-Catalyst-View-Mason SUBDIR += p5-Catalyst-View-REST-XML SUBDIR += p5-Catalyst-View-RRDGraph SUBDIR += p5-Catalyst-View-TT SUBDIR += p5-Catalyst-View-TT-Alloy SUBDIR += p5-Catalyst-View-TT-ControllerLocal SUBDIR += p5-Catalyst-View-Template-Declare SUBDIR += p5-Catalyst-View-Templated SUBDIR += p5-Catalyst-View-XML-Feed SUBDIR += p5-Catalyst-View-XML-Simple SUBDIR += p5-Catalyst-View-XSLT SUBDIR += p5-Catalyst-View-vCard SUBDIR += p5-CatalystX-AppBuilder SUBDIR += p5-CatalystX-Component-Traits SUBDIR += p5-CatalystX-InjectComponent SUBDIR += p5-CatalystX-LeakChecker SUBDIR += p5-CatalystX-Profile SUBDIR += p5-CatalystX-REPL SUBDIR += p5-CatalystX-RoleApplicator SUBDIR += p5-CatalystX-SimpleLogin SUBDIR += p5-CatalystX-VirtualComponents SUBDIR += p5-Class-DBI-FromForm SUBDIR += p5-ClearSilver SUBDIR += p5-Compress-LeadingBlankSpaces SUBDIR += p5-Continuity SUBDIR += p5-Cookie-Baker SUBDIR += p5-Corona SUBDIR += p5-Dancer SUBDIR += p5-Dancer-Logger-Log4perl SUBDIR += p5-Dancer-Plugin-CORS SUBDIR += p5-Dancer-Plugin-ExtDirect SUBDIR += p5-Dancer-Plugin-Feed SUBDIR += p5-Dancer-Plugin-FlashMessage SUBDIR += p5-Dancer-Plugin-Lexicon SUBDIR += p5-Dancer-Plugin-Memcached SUBDIR += p5-Dancer-Plugin-REST SUBDIR += p5-Dancer-Plugin-RPC SUBDIR += p5-Dancer-Plugin-SiteMap SUBDIR += p5-Dancer-Plugin-Swagger SUBDIR += p5-Dancer-Plugin-ValidationClass SUBDIR += p5-Dancer-Session-Cookie SUBDIR += p5-Dancer-Template-Xslate SUBDIR += p5-Dancer2 SUBDIR += p5-Dancer2-Plugin-Ajax SUBDIR += p5-Dancer2-Plugin-Deferred SUBDIR += p5-Dancer2-Plugin-FormValidator SUBDIR += p5-Dancer2-Plugin-Interchange6 SUBDIR += p5-Dancer2-Plugin-Path-Class SUBDIR += p5-Data-TreeDumper-Renderer-DHTML SUBDIR += p5-Data-Validate-URI SUBDIR += p5-Emplacken SUBDIR += p5-FAQ-OMatic SUBDIR += p5-FCGI SUBDIR += p5-FCGI-Async SUBDIR += p5-FCGI-Client SUBDIR += p5-FCGI-Engine SUBDIR += p5-FCGI-ProcManager SUBDIR += p5-FCGI-Spawn SUBDIR += p5-FEAR-API SUBDIR += p5-Facebook-Graph SUBDIR += p5-Feed-Find SUBDIR += p5-Feersum SUBDIR += p5-File-Mork SUBDIR += p5-Firefox-Marionette SUBDIR += p5-Flea SUBDIR += p5-Flickr-API SUBDIR += p5-Flickr-Upload SUBDIR += p5-Fliggy SUBDIR += p5-Furl SUBDIR += p5-FurlX-Coro SUBDIR += p5-Gantry SUBDIR += p5-Gazelle SUBDIR += p5-Geo-Caching SUBDIR += p5-Google-Search SUBDIR += p5-Gungho SUBDIR += p5-GunghoX-FollowLinks SUBDIR += p5-HTML-Adsense SUBDIR += p5-HTML-Breadcrumbs SUBDIR += p5-HTML-CalendarMonthSimple SUBDIR += p5-HTML-Chunks SUBDIR += p5-HTML-Clean SUBDIR += p5-HTML-ContentExtractor SUBDIR += p5-HTML-DOM SUBDIR += p5-HTML-Declare SUBDIR += p5-HTML-Defang SUBDIR += p5-HTML-Defaultify SUBDIR += p5-HTML-Diff SUBDIR += p5-HTML-Display SUBDIR += p5-HTML-Element-Extended SUBDIR += p5-HTML-Element-Library SUBDIR += p5-HTML-Element-Replacer SUBDIR += p5-HTML-Encoding SUBDIR += p5-HTML-Escape SUBDIR += p5-HTML-ExtractContent SUBDIR += p5-HTML-ExtractMain SUBDIR += p5-HTML-Field SUBDIR += p5-HTML-FillInForm SUBDIR += p5-HTML-FillInForm-ForceUTF8 SUBDIR += p5-HTML-FillInForm-Lite SUBDIR += p5-HTML-Form SUBDIR += p5-HTML-FormFu SUBDIR += p5-HTML-FormFu-Imager SUBDIR += p5-HTML-FormFu-Model-DBIC SUBDIR += p5-HTML-FormFu-MultiForm SUBDIR += p5-HTML-FormHandler SUBDIR += p5-HTML-FromANSI SUBDIR += p5-HTML-FromText SUBDIR += p5-HTML-GenToc SUBDIR += p5-HTML-GenerateUtil SUBDIR += p5-HTML-GoogleMaps SUBDIR += p5-HTML-Gumbo SUBDIR += p5-HTML-Highlight SUBDIR += p5-HTML-LinkExtractor SUBDIR += p5-HTML-LinkList SUBDIR += p5-HTML-Lint SUBDIR += p5-HTML-Location SUBDIR += p5-HTML-Macro SUBDIR += p5-HTML-Mason SUBDIR += p5-HTML-Mason-PSGIHandler SUBDIR += p5-HTML-MobileConverter SUBDIR += p5-HTML-Pager SUBDIR += p5-HTML-Parser SUBDIR += p5-HTML-Parser-Simple SUBDIR += p5-HTML-Perlinfo SUBDIR += p5-HTML-PrettyPrinter SUBDIR += p5-HTML-Prototype SUBDIR += p5-HTML-Query SUBDIR += p5-HTML-QuickCheck SUBDIR += p5-HTML-RSSAutodiscovery SUBDIR += p5-HTML-ResolveLink SUBDIR += p5-HTML-Restrict SUBDIR += p5-HTML-RobotsMETA SUBDIR += p5-HTML-Scrubber SUBDIR += p5-HTML-Seamstress SUBDIR += p5-HTML-Selector-XPath SUBDIR += p5-HTML-Shakan SUBDIR += p5-HTML-SimpleLinkExtor SUBDIR += p5-HTML-SimpleParse SUBDIR += p5-HTML-StickyQuery SUBDIR += p5-HTML-StickyQuery-DoCoMoGUID SUBDIR += p5-HTML-Stream SUBDIR += p5-HTML-Strip SUBDIR += p5-HTML-StripScripts SUBDIR += p5-HTML-StripScripts-Parser SUBDIR += p5-HTML-Summary SUBDIR += p5-HTML-Table SUBDIR += p5-HTML-TableContentParser SUBDIR += p5-HTML-TableExtract SUBDIR += p5-HTML-TableLayout SUBDIR += p5-HTML-TableParser SUBDIR += p5-HTML-TableTiler SUBDIR += p5-HTML-TagCloud SUBDIR += p5-HTML-TagCloud-Extended SUBDIR += p5-HTML-TagParser SUBDIR += p5-HTML-Tagset SUBDIR += p5-HTML-Template SUBDIR += p5-HTML-Template-Associate SUBDIR += p5-HTML-Template-Compiled SUBDIR += p5-HTML-Template-Expr SUBDIR += p5-HTML-Template-HashWrapper SUBDIR += p5-HTML-Template-JIT SUBDIR += p5-HTML-Template-Pluggable SUBDIR += p5-HTML-Template-Pro SUBDIR += p5-HTML-Toc SUBDIR += p5-HTML-TokeParser-Simple SUBDIR += p5-HTML-Tree SUBDIR += p5-HTML-TreeBuilder-LibXML SUBDIR += p5-HTML-TreeBuilder-XPath SUBDIR += p5-HTML-Widgets-SelectLayers SUBDIR += p5-HTML-WikiConverter SUBDIR += p5-HTML-WikiConverter-DokuWiki SUBDIR += p5-HTML-WikiConverter-GoogleCode SUBDIR += p5-HTML-WikiConverter-Kwiki SUBDIR += p5-HTML-WikiConverter-Markdown SUBDIR += p5-HTML-WikiConverter-MediaWiki SUBDIR += p5-HTML-WikiConverter-MoinMoin SUBDIR += p5-HTML-WikiConverter-Oddmuse SUBDIR += p5-HTML-WikiConverter-PbWiki SUBDIR += p5-HTML-WikiConverter-PhpWiki SUBDIR += p5-HTML-WikiConverter-PmWiki SUBDIR += p5-HTML-WikiConverter-SnipSnap SUBDIR += p5-HTML-WikiConverter-Socialtext SUBDIR += p5-HTML-WikiConverter-TikiWiki SUBDIR += p5-HTML-WikiConverter-UseMod SUBDIR += p5-HTML-WikiConverter-WakkaWiki SUBDIR += p5-HTML-WikiConverter-WikkaWiki SUBDIR += p5-HTML5-DOM SUBDIR += p5-HTTP-AnyUA SUBDIR += p5-HTTP-Async SUBDIR += p5-HTTP-Body SUBDIR += p5-HTTP-BrowserDetect SUBDIR += p5-HTTP-Cache-Transparent SUBDIR += p5-HTTP-CookieJar SUBDIR += p5-HTTP-Cookies SUBDIR += p5-HTTP-Cookies-Mozilla SUBDIR += p5-HTTP-Cookies-iCab SUBDIR += p5-HTTP-Cookies-w3m SUBDIR += p5-HTTP-DAV SUBDIR += p5-HTTP-Daemon SUBDIR += p5-HTTP-Daemon-SSL SUBDIR += p5-HTTP-Date SUBDIR += p5-HTTP-Engine SUBDIR += p5-HTTP-Engine-Middleware SUBDIR += p5-HTTP-Entity-Parser SUBDIR += p5-HTTP-Exception SUBDIR += p5-HTTP-HeaderParser-XS SUBDIR += p5-HTTP-Headers-ActionPack SUBDIR += p5-HTTP-Headers-Fast SUBDIR += p5-HTTP-Link-Parser SUBDIR += p5-HTTP-Lite SUBDIR += p5-HTTP-MHTTP SUBDIR += p5-HTTP-Message SUBDIR += p5-HTTP-MobileAgent SUBDIR += p5-HTTP-MobileAgent-Plugin-Charset SUBDIR += p5-HTTP-MobileAgent-Plugin-Locator SUBDIR += p5-HTTP-MultiPartParser SUBDIR += p5-HTTP-Negotiate SUBDIR += p5-HTTP-OAI SUBDIR += p5-HTTP-Parser SUBDIR += p5-HTTP-Parser-XS SUBDIR += p5-HTTP-Proxy SUBDIR += p5-HTTP-ProxyPAC SUBDIR += p5-HTTP-Recorder SUBDIR += p5-HTTP-Request-AsCGI SUBDIR += p5-HTTP-Request-Params SUBDIR += p5-HTTP-Response-Encoding SUBDIR += p5-HTTP-Router SUBDIR += p5-HTTP-Server-Simple SUBDIR += p5-HTTP-Server-Simple-Authen SUBDIR += p5-HTTP-Server-Simple-Mason SUBDIR += p5-HTTP-Server-Simple-PSGI SUBDIR += p5-HTTP-Server-Simple-Recorder SUBDIR += p5-HTTP-Server-Simple-Static SUBDIR += p5-HTTP-Session SUBDIR += p5-HTTP-Session-State-MobileAgentID SUBDIR += p5-HTTP-Session-Store-DBI SUBDIR += p5-HTTP-Session2 SUBDIR += p5-HTTP-Simple SUBDIR += p5-HTTP-SimpleLinkChecker SUBDIR += p5-HTTP-Size SUBDIR += p5-HTTP-Thin SUBDIR += p5-HTTP-Throwable SUBDIR += p5-HTTP-Tiny SUBDIR += p5-HTTP-Tiny-Multipart SUBDIR += p5-HTTP-Tiny-SPDY SUBDIR += p5-HTTP-Tiny-UA SUBDIR += p5-HTTP-Tiny-UNIX SUBDIR += p5-HTTP-WebTest SUBDIR += p5-HTTP-XSCookies SUBDIR += p5-HTTPD-Log-Filter SUBDIR += p5-HTTPD-User-Manage SUBDIR += p5-Hijk SUBDIR += p5-I18N-AcceptLanguage SUBDIR += p5-IMDB-Film SUBDIR += p5-Image-Delivery SUBDIR += p5-Interchange6 SUBDIR += p5-JE SUBDIR += p5-JSON-API SUBDIR += p5-JSON-WebToken SUBDIR += p5-Jemplate SUBDIR += p5-Jifty SUBDIR += p5-LWP-Authen-Negotiate SUBDIR += p5-LWP-Authen-OAuth SUBDIR += p5-LWP-Authen-OAuth2 SUBDIR += p5-LWP-Authen-Wsse SUBDIR += p5-LWP-ConnCache-MaxKeepAliveRequests SUBDIR += p5-LWP-MediaTypes SUBDIR += p5-LWP-Online SUBDIR += p5-LWP-Protocol-PSGI SUBDIR += p5-LWP-Protocol-connect SUBDIR += p5-LWP-Protocol-http10 SUBDIR += p5-LWP-Protocol-https SUBDIR += p5-LWP-Protocol-socks SUBDIR += p5-LWP-UserAgent-Cached SUBDIR += p5-LWP-UserAgent-Determined SUBDIR += p5-LWP-UserAgent-POE SUBDIR += p5-LWP-UserAgent-WithCache SUBDIR += p5-LWPx-ParanoidAgent SUBDIR += p5-LWPx-TimedHTTP SUBDIR += p5-Markup-Perl SUBDIR += p5-Mason SUBDIR += p5-MasonX-Interp-WithCallbacks SUBDIR += p5-MasonX-Profiler SUBDIR += p5-MasonX-Request-WithApacheSession SUBDIR += p5-MasonX-WebApp SUBDIR += p5-Maypole SUBDIR += p5-Maypole-Authentication-UserSessionCookie SUBDIR += p5-Maypole-Component SUBDIR += p5-McBain SUBDIR += p5-McBain-WithPSGI SUBDIR += p5-MediaWiki SUBDIR += p5-MediaWiki-API SUBDIR += p5-Mobile-UserAgent SUBDIR += p5-ModPerl-VersionUtil SUBDIR += p5-Mojo-DOM58 SUBDIR += p5-Mojo-IOLoop-Delay SUBDIR += p5-Mojo-IOLoop-ForkCall SUBDIR += p5-Mojo-Server-FastCGI SUBDIR += p5-Mojo-Weixin SUBDIR += p5-MojoMojo SUBDIR += p5-MojoX-Log-Dispatch-Simple SUBDIR += p5-MojoX-Renderer-Xslate SUBDIR += p5-Mojolicious SUBDIR += p5-Mojolicious-Plugin-Authentication SUBDIR += p5-Mojolicious-Plugin-Database SUBDIR += p5-Mojolicious-Plugin-HamlRenderer SUBDIR += p5-Mojolicious-Plugin-Mongodb SUBDIR += p5-Mojolicious-Plugin-OpenAPI SUBDIR += p5-Mojolicious-Plugin-SetUserGroup SUBDIR += p5-Mojolicious-Plugin-TtRenderer SUBDIR += p5-Mojolicious-Plugin-YamlConfig SUBDIR += p5-Monoceros SUBDIR += p5-Mozilla-CA SUBDIR += p5-Net-Akismet SUBDIR += p5-Net-Amazon-AWIS SUBDIR += p5-Net-Async-FastCGI SUBDIR += p5-Net-Async-HTTP SUBDIR += p5-Net-Curl SUBDIR += p5-Net-FastCGI SUBDIR += p5-Net-FireEagle SUBDIR += p5-Net-Flickr-API SUBDIR += p5-Net-Flickr-Backup SUBDIR += p5-Net-Flickr-RDF SUBDIR += p5-Net-FreshBooks-API SUBDIR += p5-Net-GeoPlanet SUBDIR += p5-Net-Plurk SUBDIR += p5-Net-STF-Client SUBDIR += p5-Net-Trac SUBDIR += p5-Net-UPS SUBDIR += p5-Net-YAP SUBDIR += p5-Net-eBay SUBDIR += p5-Newsletter SUBDIR += p5-Nginx-ReadBody SUBDIR += p5-Nginx-Simple SUBDIR += p5-OpenAPI-Client SUBDIR += p5-PHP-Session SUBDIR += p5-POE-Component-Client-HTTP SUBDIR += p5-POE-Component-Client-UserAgent SUBDIR += p5-POE-Component-Server-HTTP SUBDIR += p5-POE-Component-Server-HTTPServer SUBDIR += p5-POE-Component-Server-PSGI SUBDIR += p5-POE-Component-Server-SOAP SUBDIR += p5-POE-Component-Server-SimpleHTTP SUBDIR += p5-POE-Filter-HTTP-Parser SUBDIR += p5-POEx-Role-PSGIServer SUBDIR += p5-PSGI SUBDIR += p5-ParallelUserAgent SUBDIR += p5-Parse-HTTP-UserAgent SUBDIR += p5-Path-Class-URI SUBDIR += p5-Perlanet SUBDIR += p5-Perlbal-Plugin-PSGI SUBDIR += p5-Plack SUBDIR += p5-Plack-App-Proxy SUBDIR += p5-Plack-Builder-Conditionals SUBDIR += p5-Plack-Handler-AnyEvent-HTTPD SUBDIR += p5-Plack-Handler-AnyEvent-ReverseHTTP SUBDIR += p5-Plack-Handler-AnyEvent-SCGI SUBDIR += p5-Plack-Handler-CLI SUBDIR += p5-Plack-Handler-SCGI SUBDIR += p5-Plack-Middleware-AMF SUBDIR += p5-Plack-Middleware-AddDefaultCharset SUBDIR += p5-Plack-Middleware-Auth-Digest SUBDIR += p5-Plack-Middleware-AutoRefresh SUBDIR += p5-Plack-Middleware-ConsoleLogger SUBDIR += p5-Plack-Middleware-CrossOrigin SUBDIR += p5-Plack-Middleware-DBIx-DisconnectAll SUBDIR += p5-Plack-Middleware-Debug SUBDIR += p5-Plack-Middleware-Deflater SUBDIR += p5-Plack-Middleware-Expires SUBDIR += p5-Plack-Middleware-ExtDirect SUBDIR += p5-Plack-Middleware-File-Sass SUBDIR += p5-Plack-Middleware-FixMissingBodyInRedirect SUBDIR += p5-Plack-Middleware-ForceEnv SUBDIR += p5-Plack-Middleware-Header SUBDIR += p5-Plack-Middleware-Headers SUBDIR += p5-Plack-Middleware-IEnosniff SUBDIR += p5-Plack-Middleware-InteractiveDebugger SUBDIR += p5-Plack-Middleware-JSConcat SUBDIR += p5-Plack-Middleware-MemoryUsage SUBDIR += p5-Plack-Middleware-MethodOverride SUBDIR += p5-Plack-Middleware-NoMultipleSlashes SUBDIR += p5-Plack-Middleware-Precompressed SUBDIR += p5-Plack-Middleware-RemoveRedundantBody SUBDIR += p5-Plack-Middleware-Reproxy SUBDIR += p5-Plack-Middleware-ReverseProxy SUBDIR += p5-Plack-Middleware-Rewrite SUBDIR += p5-Plack-Middleware-ServerStatus-Lite SUBDIR += p5-Plack-Middleware-Session SUBDIR += p5-Plack-Middleware-SocketIO SUBDIR += p5-Plack-Middleware-Status SUBDIR += p5-Plack-Middleware-Test-StashWarnings SUBDIR += p5-Plack-Middleware-Throttle SUBDIR += p5-Plack-Middleware-XForwardedFor SUBDIR += p5-Plack-Server-Coro SUBDIR += p5-Plack-Server-POE SUBDIR += p5-Plack-Server-ReverseHTTP SUBDIR += p5-Plack-Test-ExternalServer SUBDIR += p5-PocketIO SUBDIR += p5-Pod-Site SUBDIR += p5-PodToHTML SUBDIR += p5-Protocol-HTTP2 SUBDIR += p5-Protocol-SocketIO SUBDIR += p5-Protocol-WebSocket SUBDIR += p5-Protocol-XMLRPC SUBDIR += p5-REST-Client SUBDIR += p5-REST-Google-Apps-Provisioning SUBDIR += p5-RPC-ExtDirect SUBDIR += p5-RT-Client-REST SUBDIR += p5-RT-Extension-CommandByMail SUBDIR += p5-RT-Extension-Gravatar SUBDIR += p5-RT-Extension-LDAPImport SUBDIR += p5-RT-Extension-MandatoryOnTransition SUBDIR += p5-RT-Extension-RepeatTicket SUBDIR += p5-RT-Extension-RepeatTicket2 SUBDIR += p5-RTx-Calendar SUBDIR += p5-Reaction SUBDIR += p5-Reddit SUBDIR += p5-Reddit-Client SUBDIR += p5-Role-REST-Client SUBDIR += p5-Rose-HTML-Objects SUBDIR += p5-Router-Boom SUBDIR += p5-Router-Simple SUBDIR += p5-Router-Simple-Sinatraish SUBDIR += p5-SCGI SUBDIR += p5-SOAP-Transport-HTTP-Plack SUBDIR += p5-SRU SUBDIR += p5-STF-Dispatcher-PSGI SUBDIR += p5-SWF-Chart SUBDIR += p5-Scrappy SUBDIR += p5-Selenium-Remote-Driver SUBDIR += p5-Session-Storage-Secure SUBDIR += p5-Squatting SUBDIR += p5-Squatting-On-PSGI SUBDIR += p5-Starlet SUBDIR += p5-Starman SUBDIR += p5-Syntax-Highlight-HTML SUBDIR += p5-Syntax-Highlight-Shell SUBDIR += p5-Task-Catalyst SUBDIR += p5-Task-Plack SUBDIR += p5-Tatsumaki SUBDIR += p5-Template-Alloy SUBDIR += p5-Template-GD SUBDIR += p5-Template-Iterator-AlzaboWrapperCursor SUBDIR += p5-Template-Multilingual SUBDIR += p5-Template-Mustache SUBDIR += p5-Template-Plugin-Class SUBDIR += p5-Template-Plugin-Clickable SUBDIR += p5-Template-Plugin-Clickable-Email SUBDIR += p5-Template-Plugin-Comma SUBDIR += p5-Template-Plugin-FillInForm SUBDIR += p5-Template-Plugin-Gettext SUBDIR += p5-Template-Plugin-JSON SUBDIR += p5-Template-Plugin-JavaScript SUBDIR += p5-Template-Plugin-MP3 SUBDIR += p5-Template-Plugin-Markdown SUBDIR += p5-Template-Plugin-Monta SUBDIR += p5-Template-Plugin-Number-Format SUBDIR += p5-Template-Plugin-StripScripts SUBDIR += p5-Template-Plugin-Subst SUBDIR += p5-Template-Plugin-VMethods SUBDIR += p5-Template-Provider-Encoding SUBDIR += p5-Template-Provider-FromDATA SUBDIR += p5-Template-Simple SUBDIR += p5-Template-Stash-AutoEscape SUBDIR += p5-Template-Timer SUBDIR += p5-Template-Toolkit SUBDIR += p5-Template-Toolkit-Simple SUBDIR += p5-Tenjin SUBDIR += p5-Test-HTTP SUBDIR += p5-Test-HTTP-LocalServer SUBDIR += p5-Test-HTTP-Server-Simple SUBDIR += p5-Test-LWP-UserAgent SUBDIR += p5-Test-Nginx SUBDIR += p5-TestGen4Web-Runner SUBDIR += p5-Text-MultiMarkdown-ApacheHandler SUBDIR += p5-Tie-TinyURL SUBDIR += p5-Toader SUBDIR += p5-Toadfarm SUBDIR += p5-Twiggy SUBDIR += p5-Twiggy-TLS SUBDIR += p5-URI-Encode SUBDIR += p5-URI-Escape-JavaScript SUBDIR += p5-URI-Escape-XS SUBDIR += p5-URI-Fetch SUBDIR += p5-URI-Normalize SUBDIR += p5-URI-ParseSearchString SUBDIR += p5-URI-Sequin SUBDIR += p5-URI-Title SUBDIR += p5-URI-ToDisk SUBDIR += p5-URL-Encode SUBDIR += p5-URL-Encode-XS SUBDIR += p5-VUser-Google-ProvisioningAPI SUBDIR += p5-W3C-LinkChecker SUBDIR += p5-W3C-LogValidator SUBDIR += p5-WWW-AtMovies-TV SUBDIR += p5-WWW-Babelfish SUBDIR += p5-WWW-Baseball-NPB SUBDIR += p5-WWW-Comic SUBDIR += p5-WWW-Contact SUBDIR += p5-WWW-Curl SUBDIR += p5-WWW-DHL SUBDIR += p5-WWW-Dilbert SUBDIR += p5-WWW-Facebook-API SUBDIR += p5-WWW-Form-UrlEncoded SUBDIR += p5-WWW-FreeProxy SUBDIR += p5-WWW-GitHub-Gist SUBDIR += p5-WWW-Google-Calculator SUBDIR += p5-WWW-Google-News SUBDIR += p5-WWW-Google-News-TW SUBDIR += p5-WWW-Google-PageRank SUBDIR += p5-WWW-HatenaDiary SUBDIR += p5-WWW-HatenaLogin SUBDIR += p5-WWW-HatenaStar SUBDIR += p5-WWW-IMDb SUBDIR += p5-WWW-Instapaper-Client SUBDIR += p5-WWW-LongURL SUBDIR += p5-WWW-Mechanize SUBDIR += p5-WWW-Mechanize-CGI SUBDIR += p5-WWW-Mechanize-DecodedContent SUBDIR += p5-WWW-Mechanize-FormFiller SUBDIR += p5-WWW-Mechanize-GZip SUBDIR += p5-WWW-Mechanize-Meta SUBDIR += p5-WWW-Mechanize-Pluggable SUBDIR += p5-WWW-Mechanize-Plugin-phpBB SUBDIR += p5-WWW-Mechanize-Shell SUBDIR += p5-WWW-Mechanize-SpamCop SUBDIR += p5-WWW-Mechanize-TreeBuilder SUBDIR += p5-WWW-Mediawiki-Client SUBDIR += p5-WWW-Mixi SUBDIR += p5-WWW-Mixi-Scraper SUBDIR += p5-WWW-Myspace SUBDIR += p5-WWW-NicoVideo-Download SUBDIR += p5-WWW-NioTV SUBDIR += p5-WWW-OAuth SUBDIR += p5-WWW-OpenSVN SUBDIR += p5-WWW-OpenSearch SUBDIR += p5-WWW-Pastebin-PastebinCom-Create SUBDIR += p5-WWW-Plurk SUBDIR += p5-WWW-Robot SUBDIR += p5-WWW-RobotRules SUBDIR += p5-WWW-RobotRules-Parser SUBDIR += p5-WWW-Salesforce SUBDIR += p5-WWW-Scraper-ISBN SUBDIR += p5-WWW-Scraper-ISBN-Amazon_Driver SUBDIR += p5-WWW-Scraper-ISBN-ORA_Driver SUBDIR += p5-WWW-Scripter SUBDIR += p5-WWW-Scripter-Plugin-Ajax SUBDIR += p5-WWW-Scripter-Plugin-JavaScript SUBDIR += p5-WWW-Search SUBDIR += p5-WWW-Search-AltaVista SUBDIR += p5-WWW-Search-Google SUBDIR += p5-WWW-Search-MSN SUBDIR += p5-WWW-Shorten SUBDIR += p5-WWW-Shorten-0rz SUBDIR += p5-WWW-Shorten-Bitly SUBDIR += p5-WWW-Shorten-Googl SUBDIR += p5-WWW-Shorten-Yourls SUBDIR += p5-WWW-SourceForge SUBDIR += p5-WWW-Spinn3r SUBDIR += p5-WWW-TV SUBDIR += p5-WWW-TWSMS SUBDIR += p5-WWW-Telegram-BotAPI SUBDIR += p5-WWW-TinySong SUBDIR += p5-WWW-Tumblr SUBDIR += p5-WWW-VenusEnvy SUBDIR += p5-WWW-WebArchive SUBDIR += p5-WWW-Wikipedia SUBDIR += p5-WWW-Yandex-TIC SUBDIR += p5-WWW-iTunesConnect SUBDIR += p5-Web-Machine SUBDIR += p5-Web-Query SUBDIR += p5-Web-Scraper SUBDIR += p5-Web-Scraper-Config SUBDIR += p5-Web-Simple SUBDIR += p5-Web-oEmbed SUBDIR += p5-WebDAO SUBDIR += p5-WebDriver-Tiny SUBDIR += p5-WebService-Basecamp SUBDIR += p5-WebService-Bloglines SUBDIR += p5-WebService-BuzzurlAPI SUBDIR += p5-WebService-CIA SUBDIR += p5-WebService-GData SUBDIR += p5-WebService-Google-Reader SUBDIR += p5-WebService-Google-Sets SUBDIR += p5-WebService-IMDB SUBDIR += p5-WebService-ISBNDB SUBDIR += p5-WebService-Linode SUBDIR += p5-WebService-MoviePosterDB SUBDIR += p5-WebService-MusicBrainz SUBDIR += p5-WebService-NoPaste SUBDIR += p5-WebService-Pushover SUBDIR += p5-WebService-Rakuten SUBDIR += p5-WebService-Redmine SUBDIR += p5-WebService-Simple SUBDIR += p5-WebService-Technorati SUBDIR += p5-WebService-YouTube SUBDIR += p5-Woothee SUBDIR += p5-WordPress-XMLRPC SUBDIR += p5-Yahoo-Search SUBDIR += p5-chklinks SUBDIR += p5-jQuery-File-Upload SUBDIR += p5-libapreq2 SUBDIR += p5-libservlet SUBDIR += p5-libwww SUBDIR += p5-pQuery SUBDIR += p5-webservice-validator-css-w3c SUBDIR += p5-webservice-validator-html-w3c SUBDIR += pacparser SUBDIR += payara SUBDIR += pear-HTML_AJAX SUBDIR += pear-HTML_TagCloud SUBDIR += pear-HTTP SUBDIR += pear-HTTP_Client SUBDIR += pear-HTTP_Download SUBDIR += pear-HTTP_FloodControl SUBDIR += pear-HTTP_Header SUBDIR += pear-HTTP_Request SUBDIR += pear-HTTP_Request2 SUBDIR += pear-HTTP_Server SUBDIR += pear-HTTP_Session2 SUBDIR += pear-HTTP_Upload SUBDIR += pear-HTTP_WebDAV_Client SUBDIR += pear-HTTP_WebDAV_Server SUBDIR += pear-Horde_Browser SUBDIR += pear-Horde_Css_Parser SUBDIR += pear-Horde_Dav SUBDIR += pear-Horde_Editor SUBDIR += pear-Horde_Feed SUBDIR += pear-Horde_Form SUBDIR += pear-Horde_Http SUBDIR += pear-Horde_Routes SUBDIR += pear-Horde_Service_Facebook SUBDIR += pear-Horde_Service_Gravatar SUBDIR += pear-Horde_Service_Twitter SUBDIR += pear-Horde_Service_UrlShortener SUBDIR += pear-Horde_Service_Weather SUBDIR += pear-Horde_SessionHandler SUBDIR += pear-Horde_Template SUBDIR += pear-Services_Amazon SUBDIR += pear-Services_Amazon_S3 SUBDIR += pear-Services_Blogging SUBDIR += pear-Services_Compete SUBDIR += pear-Services_Delicious SUBDIR += pear-Services_Digg SUBDIR += pear-Services_Facebook SUBDIR += pear-Services_GeoNames SUBDIR += pear-Services_Google SUBDIR += pear-Services_OpenSearch SUBDIR += pear-Services_ShortURL SUBDIR += pear-Services_TinyURL SUBDIR += pear-Services_TwitPic SUBDIR += pear-Services_W3C_CSSValidator SUBDIR += pear-Services_W3C_HTMLValidator SUBDIR += pear-Services_Yadis SUBDIR += pear-Services_Yahoo SUBDIR += pear-Services_urlTea SUBDIR += pear-Structures_DataGrid_Renderer_Flexy SUBDIR += pear-Structures_DataGrid_Renderer_Pager SUBDIR += pear-Structures_DataGrid_Renderer_Smarty SUBDIR += pear-Text_Wiki SUBDIR += pear-UDDI SUBDIR += pear-XML_GRDDL SUBDIR += pear-twig SUBDIR += pecl-http SUBDIR += pecl-solr SUBDIR += pecl-yaf SUBDIR += pecl-yar SUBDIR += perlbal SUBDIR += persepolis SUBDIR += pglogd SUBDIR += phalcon SUBDIR += php-google-api-php-client SUBDIR += php81-opcache SUBDIR += php81-session SUBDIR += php81-tidy SUBDIR += php82-opcache SUBDIR += php82-session SUBDIR += php82-tidy SUBDIR += php83-opcache SUBDIR += php83-session SUBDIR += php83-tidy SUBDIR += phpbb SUBDIR += phpbb3 SUBDIR += phpfpmtop SUBDIR += phpgroupware SUBDIR += phpmustache SUBDIR += phpmyfaq SUBDIR += phprecipebook SUBDIR += phpsysinfo SUBDIR += phpvirtualbox SUBDIR += phpvirtualbox-legacy SUBDIR += piwigo SUBDIR += plasma5-plasma-browser-integration SUBDIR += plasma6-plasma-browser-integration SUBDIR += pmwiki SUBDIR += pnews SUBDIR += podcastamatic SUBDIR += pomerium SUBDIR += pound SUBDIR += privatebin SUBDIR += privoxy SUBDIR += protovis SUBDIR += proxygen SUBDIR += publicfile SUBDIR += punbb SUBDIR += py-Pituophis SUBDIR += py-Tenjin SUBDIR += py-WebError SUBDIR += py-WebFlash SUBDIR += py-adblock SUBDIR += py-advocate SUBDIR += py-aioh2 SUBDIR += py-aiohttp SUBDIR += py-aiohttp-jinja2 SUBDIR += py-aiohttp-middlewares SUBDIR += py-aiohttp-oauthlib SUBDIR += py-aiohttp-session SUBDIR += py-aiohttp-wsgi SUBDIR += py-aiohttp_cors SUBDIR += py-aioquic SUBDIR += py-aiostream SUBDIR += py-arxiv SUBDIR += py-asgi-csrf SUBDIR += py-asgiref SUBDIR += py-autobahn SUBDIR += py-azure-common SUBDIR += py-azure-storage SUBDIR += py-beaker SUBDIR += py-beautifulsoup SUBDIR += py-betamax SUBDIR += py-biscuits SUBDIR += py-bjoern SUBDIR += py-bleach SUBDIR += py-bokeh SUBDIR += py-boto3 SUBDIR += py-botocore-stubs SUBDIR += py-bottle SUBDIR += py-bottle-cork SUBDIR += py-branca SUBDIR += py-bravado SUBDIR += py-bravado-core SUBDIR += py-cachecontrol SUBDIR += py-cachelib SUBDIR += py-caldav SUBDIR += py-channels SUBDIR += py-cheroot SUBDIR += py-cherrypy SUBDIR += py-cinemagoer SUBDIR += py-cookies SUBDIR += py-crossplane SUBDIR += py-css-html-js-minify SUBDIR += py-css-parser SUBDIR += py-csscompressor SUBDIR += py-cssmin SUBDIR += py-cssselect SUBDIR += py-cssutils SUBDIR += py-daphne SUBDIR += py-dj-database-url SUBDIR += py-dj42-channels SUBDIR += py-dj42-channels-redis SUBDIR += py-dj42-django-allauth SUBDIR += py-dj42-django-auditlog SUBDIR += py-dj42-django-auth-ldap SUBDIR += py-dj42-django-celery-results SUBDIR += py-dj42-django-compression-middleware SUBDIR += py-dj42-django-cors-headers SUBDIR += py-dj42-django-crispy-forms SUBDIR += py-dj42-django-debug-toolbar SUBDIR += py-dj42-django-extensions SUBDIR += py-dj42-django-filter SUBDIR += py-dj42-django-graphiql-debug-toolbar SUBDIR += py-dj42-django-guardian SUBDIR += py-dj42-django-js-asset SUBDIR += py-dj42-django-mptt SUBDIR += py-dj42-django-multiselectfield SUBDIR += py-dj42-django-prometheus SUBDIR += py-dj42-django-redis SUBDIR += py-dj42-django-rich SUBDIR += py-dj42-django-tables2 SUBDIR += py-dj42-django-taggit SUBDIR += py-dj42-django-timezone-field SUBDIR += py-dj42-djangoql SUBDIR += py-dj42-djangorestframework SUBDIR += py-dj42-djangorestframework-guardian SUBDIR += py-dj42-drf-spectacular SUBDIR += py-dj42-drf-spectacular-sidecar SUBDIR += py-dj42-drf-writable-nested SUBDIR += py-django-admin-rangefilter SUBDIR += py-django-advanced-filters SUBDIR += py-django-allauth SUBDIR += py-django-annoying SUBDIR += py-django-appconf SUBDIR += py-django-assets SUBDIR += py-django-auth-ldap SUBDIR += py-django-autocomplete-light SUBDIR += py-django-bakery SUBDIR += py-django-bitfield SUBDIR += py-django-bleach SUBDIR += py-django-bootstrap-pagination SUBDIR += py-django-bootstrap3 SUBDIR += py-django-bootstrap4 SUBDIR += py-django-braces SUBDIR += py-django-cacheops SUBDIR += py-django-ckeditor-5 SUBDIR += py-django-classy-tags SUBDIR += py-django-cms SUBDIR += py-django-configurations SUBDIR += py-django-constance SUBDIR += py-django-contact-form SUBDIR += py-django-context-decorator SUBDIR += py-django-contrib-comments SUBDIR += py-django-cors-headers SUBDIR += py-django-countries SUBDIR += py-django-crispy-forms SUBDIR += py-django-cron SUBDIR += py-django-csp SUBDIR += py-django-debreach SUBDIR += py-django-debug-toolbar SUBDIR += py-django-dpaste SUBDIR += py-django-extensions SUBDIR += py-django-filer SUBDIR += py-django-filter SUBDIR += py-django-formset-js-improved SUBDIR += py-django-formtools SUBDIR += py-django-graphiql-debug-toolbar SUBDIR += py-django-gravatar2 SUBDIR += py-django-guardian SUBDIR += py-django-hashid-field SUBDIR += py-django-haystack SUBDIR += py-django-hierarkey SUBDIR += py-django-hijack SUBDIR += py-django-htmlmin SUBDIR += py-django-htmx SUBDIR += py-django-i18nfield SUBDIR += py-django-jquery-js SUBDIR += py-django-js-asset SUBDIR += py-django-jsonview SUBDIR += py-django-ldapdb SUBDIR += py-django-libsass SUBDIR += py-django-markdownx SUBDIR += py-django-markwhat SUBDIR += py-django-mezzanine-filebrowser SUBDIR += py-django-mezzanine-grappelli SUBDIR += py-django-model-utils SUBDIR += py-django-modelcluster SUBDIR += py-django-mptt SUBDIR += py-django-netfields SUBDIR += py-django-object-actions SUBDIR += py-django-otp SUBDIR += py-django-otp-yubikey SUBDIR += py-django-permissionedforms SUBDIR += py-django-pglocks SUBDIR += py-django-photologue SUBDIR += py-django-picklefield SUBDIR += py-django-pipeline SUBDIR += py-django-polymorphic SUBDIR += py-django-post_office SUBDIR += py-django-prometheus SUBDIR += py-django-pyscss SUBDIR += py-django-radius SUBDIR += py-django-ranged-response SUBDIR += py-django-recaptcha SUBDIR += py-django-redis SUBDIR += py-django-registration SUBDIR += py-django-registration-redux SUBDIR += py-django-requests-debug-toolbar SUBDIR += py-django-reversion SUBDIR += py-django-reversion-compare SUBDIR += py-django-rich SUBDIR += py-django-sekizai SUBDIR += py-django-simple-captcha SUBDIR += py-django-simple-history SUBDIR += py-django-smart-selects SUBDIR += py-django-solo SUBDIR += py-django-sortedm2m SUBDIR += py-django-star-ratings SUBDIR += py-django-statici18n SUBDIR += py-django-staticinline SUBDIR += py-django-storages SUBDIR += py-django-tables2 SUBDIR += py-django-tagging SUBDIR += py-django-taggit SUBDIR += py-django-tastypie SUBDIR += py-django-templatetag-sugar SUBDIR += py-django-timezone-field SUBDIR += py-django-tinymce SUBDIR += py-django-treebeard SUBDIR += py-django-voting SUBDIR += py-django-webpack-loader SUBDIR += py-django-widget-tweaks SUBDIR += py-django32 SUBDIR += py-django42 SUBDIR += py-django50 SUBDIR += py-django_compressor SUBDIR += py-djangocms-admin-style SUBDIR += py-djangoql SUBDIR += py-djangorestframework SUBDIR += py-djangorestframework-csv SUBDIR += py-djangorestframework-filters SUBDIR += py-djangorestframework-xml SUBDIR += py-djangosaml2 SUBDIR += py-draftjs-exporter SUBDIR += py-drf-spectacular SUBDIR += py-drf-spectacular-sidecar SUBDIR += py-drf-yasg SUBDIR += py-dropbox SUBDIR += py-dtflickr SUBDIR += py-enmerkar SUBDIR += py-fake-useragent SUBDIR += py-falcon SUBDIR += py-fastapi SUBDIR += py-fastapi-users SUBDIR += py-feedgenerator SUBDIR += py-flasgger SUBDIR += py-flask SUBDIR += py-flask-admin SUBDIR += py-flask-api SUBDIR += py-flask-apscheduler SUBDIR += py-flask-assets SUBDIR += py-flask-babelex SUBDIR += py-flask-bootstrap SUBDIR += py-flask-cache SUBDIR += py-flask-caching SUBDIR += py-flask-collect SUBDIR += py-flask-compress SUBDIR += py-flask-cors SUBDIR += py-flask-flatpages SUBDIR += py-flask-json SUBDIR += py-flask-jwt SUBDIR += py-flask-jwt-extended SUBDIR += py-flask-limiter SUBDIR += py-flask-login SUBDIR += py-flask-marshmallow SUBDIR += py-flask-migrate SUBDIR += py-flask-moment SUBDIR += py-flask-mongoengine SUBDIR += py-flask-oauthlib SUBDIR += py-flask-peewee SUBDIR += py-flask-principal SUBDIR += py-flask-restful SUBDIR += py-flask-restx SUBDIR += py-flask-script SUBDIR += py-flask-security SUBDIR += py-flask-smorest SUBDIR += py-flask-socketio SUBDIR += py-flask-sockets SUBDIR += py-flask-uploads SUBDIR += py-flask-wtf SUBDIR += py-flower SUBDIR += py-flup6 SUBDIR += py-folium SUBDIR += py-forcediphttpsadapter SUBDIR += py-formencode SUBDIR += py-fqdn SUBDIR += py-freenit SUBDIR += py-frozen-flask SUBDIR += py-gandi.cli SUBDIR += py-gevent-websocket SUBDIR += py-ghp-import SUBDIR += py-google SUBDIR += py-google-api-core SUBDIR += py-google-api-python-client SUBDIR += py-google-cloud-aiplatform SUBDIR += py-google-cloud-appengine-logging SUBDIR += py-google-cloud-audit-log SUBDIR += py-google-cloud-bigquery SUBDIR += py-google-cloud-bigtable SUBDIR += py-google-cloud-core SUBDIR += py-google-cloud-datastore SUBDIR += py-google-cloud-dlp SUBDIR += py-google-cloud-logging SUBDIR += py-google-cloud-resource-manager SUBDIR += py-google-cloud-speech SUBDIR += py-google-cloud-storage SUBDIR += py-google-cloud-testutils SUBDIR += py-google-cloud-translate SUBDIR += py-google-cloud-vision SUBDIR += py-google-resumable-media SUBDIR += py-grafana-dashboard-manager SUBDIR += py-graphite-api SUBDIR += py-grequests SUBDIR += py-grip SUBDIR += py-gunicorn SUBDIR += py-h2 SUBDIR += py-habanero SUBDIR += py-hdfs SUBDIR += py-horizon SUBDIR += py-hpack SUBDIR += py-hstspreload SUBDIR += py-html3 SUBDIR += py-html5-parser SUBDIR += py-html5lib SUBDIR += py-htmldate SUBDIR += py-httmock SUBDIR += py-http-parser SUBDIR += py-httpbin SUBDIR += py-httpcore SUBDIR += py-httpie SUBDIR += py-httplib2 SUBDIR += py-httpretty SUBDIR += py-httptools SUBDIR += py-httpx SUBDIR += py-httpx-cache SUBDIR += py-httpx-gssapi SUBDIR += py-httpx-oauth SUBDIR += py-httpx-socks SUBDIR += py-hypercorn SUBDIR += py-hyperframe SUBDIR += py-hyperlink SUBDIR += py-imdbpy SUBDIR += py-inlinestyler SUBDIR += py-instabot SUBDIR += py-internetarchive SUBDIR += py-jonpy SUBDIR += py-jsonfield SUBDIR += py-kiss-headers SUBDIR += py-lektor SUBDIR += py-lesscpy SUBDIR += py-libsass SUBDIR += py-limits SUBDIR += py-livereload SUBDIR += py-mechanicalsoup SUBDIR += py-mechanize SUBDIR += py-multidict SUBDIR += py-mwoauth SUBDIR += py-nevow SUBDIR += py-nh3 SUBDIR += py-notebook SUBDIR += py-onetimepass SUBDIR += py-openbrokerapi SUBDIR += py-pafy SUBDIR += py-paste SUBDIR += py-pastedeploy SUBDIR += py-path-and-address SUBDIR += py-pecan SUBDIR += py-pelican SUBDIR += py-planet SUBDIR += py-postorius SUBDIR += py-praw SUBDIR += py-prawcore SUBDIR += py-priority SUBDIR += py-priority1 SUBDIR += py-protego SUBDIR += py-puppetboard SUBDIR += py-py-restclient SUBDIR += py-pygsheets SUBDIR += py-pyjwt SUBDIR += py-pyjwt1 SUBDIR += py-pylsqpack SUBDIR += py-pyocclient SUBDIR += py-pyramid SUBDIR += py-pyramid-mako SUBDIR += py-pyramid_rpc SUBDIR += py-pysmartdl SUBDIR += py-python-digitalocean SUBDIR += py-python-dotenv SUBDIR += py-python-multipart SUBDIR += py-pyweblib SUBDIR += py-pywikibot SUBDIR += py-pywry SUBDIR += py-qt5-webengine SUBDIR += py-qt6-webengine SUBDIR += py-quilt3 SUBDIR += py-readability-lxml SUBDIR += py-recaptcha SUBDIR += py-requests SUBDIR += py-requests-aws4auth SUBDIR += py-requests-cache SUBDIR += py-requests-cache0 SUBDIR += py-requests-cache93 SUBDIR += py-requests-file SUBDIR += py-requests-futures SUBDIR += py-requests-gssapi SUBDIR += py-requests-mock SUBDIR += py-requests-oauthlib SUBDIR += py-requests-toolbelt SUBDIR += py-requests-unixsocket SUBDIR += py-requests-wsgi-adapter SUBDIR += py-requests_ntlm SUBDIR += py-respx SUBDIR += py-restclient SUBDIR += py-rfc3986 SUBDIR += py-rfc3987 SUBDIR += py-rollbar SUBDIR += py-routes SUBDIR += py-rules SUBDIR += py-scgi SUBDIR += py-scrapy SUBDIR += py-seafdav SUBDIR += py-seafobj SUBDIR += py-secure-cookie SUBDIR += py-selector SUBDIR += py-selenium SUBDIR += py-selenium-wire SUBDIR += py-semiphemeral SUBDIR += py-sentinelhub SUBDIR += py-simple-websocket SUBDIR += py-slimit SUBDIR += py-slumber SUBDIR += py-social-auth-app-django SUBDIR += py-sockjs-tornado SUBDIR += py-soupsieve SUBDIR += py-splinter SUBDIR += py-spyne SUBDIR += py-sseclient SUBDIR += py-starlette SUBDIR += py-swapper SUBDIR += py-textile SUBDIR += py-ticketutil SUBDIR += py-tornado SUBDIR += py-tornado4 SUBDIR += py-tornado5 SUBDIR += py-treq SUBDIR += py-ttrv SUBDIR += py-tuir SUBDIR += py-turbogears2 SUBDIR += py-tvdb_api SUBDIR += py-urlgrabber SUBDIR += py-urlman SUBDIR += py-urlobject SUBDIR += py-urlwatch SUBDIR += py-user_agent SUBDIR += py-utidylib SUBDIR += py-uvicorn SUBDIR += py-w3lib SUBDIR += py-wagtail SUBDIR += py-wagtail-2fa SUBDIR += py-wagtail-airtable SUBDIR += py-wagtail-bakery SUBDIR += py-wagtail-factories SUBDIR += py-wagtail-localize SUBDIR += py-wagtail-transfer SUBDIR += py-waitress SUBDIR += py-wcag-contrast-ratio SUBDIR += py-webargs SUBDIR += py-webassets SUBDIR += py-webdriver_manager SUBDIR += py-webob SUBDIR += py-websocket-client SUBDIR += py-webtest SUBDIR += py-webunit SUBDIR += py-werkzeug SUBDIR += py-werkzeug2 SUBDIR += py-wfuzz SUBDIR += py-whitenoise SUBDIR += py-wikipedia SUBDIR += py-wikitools SUBDIR += py-woob SUBDIR += py-woob-qt SUBDIR += py-wsaccel SUBDIR += py-wsgidav SUBDIR += py-xandikos SUBDIR += py-xyzservices SUBDIR += py-yarl SUBDIR += py-yt-dlp SUBDIR += pydio-cells SUBDIR += qdecoder SUBDIR += qhttpengine SUBDIR += qt5-webchannel SUBDIR += qt5-webengine SUBDIR += qt5-webglplugin SUBDIR += qt5-websockets SUBDIR += qt5-websockets-qml SUBDIR += qt5-webview SUBDIR += qt6-httpserver SUBDIR += qt6-webchannel SUBDIR += qt6-webengine SUBDIR += qt6-websockets SUBDIR += qt6-webview SUBDIR += quark SUBDIR += qutebrowser SUBDIR += radicale SUBDIR += rearx SUBDIR += reddsaver SUBDIR += redmine50 SUBDIR += rejik SUBDIR += remark42 SUBDIR += reportmagic SUBDIR += repos-style SUBDIR += reproxy SUBDIR += restbed SUBDIR += restinio SUBDIR += retawq SUBDIR += rss-bridge SUBDIR += rsskit SUBDIR += rssroll SUBDIR += rsstail SUBDIR += rsstool SUBDIR += rt44 SUBDIR += rt50 SUBDIR += rubygem-ace-rails-ap SUBDIR += rubygem-actioncable5 SUBDIR += rubygem-actioncable50 SUBDIR += rubygem-actioncable52 SUBDIR += rubygem-actioncable60 SUBDIR += rubygem-actioncable61 SUBDIR += rubygem-actioncable70 SUBDIR += rubygem-actioncable71 SUBDIR += rubygem-actionpack4 SUBDIR += rubygem-actionpack5 SUBDIR += rubygem-actionpack50 SUBDIR += rubygem-actionpack52 SUBDIR += rubygem-actionpack60 SUBDIR += rubygem-actionpack61 SUBDIR += rubygem-actionpack70 SUBDIR += rubygem-actionpack71 SUBDIR += rubygem-activeresource SUBDIR += rubygem-activeresource4 SUBDIR += rubygem-acts-as-taggable-on SUBDIR += rubygem-acts_as_taggable SUBDIR += rubygem-addressable SUBDIR += rubygem-adsf SUBDIR += rubygem-akami SUBDIR += rubygem-amazon-ecs SUBDIR += rubygem-anemone SUBDIR += rubygem-asana SUBDIR += rubygem-async-http SUBDIR += rubygem-async-pool SUBDIR += rubygem-async-rest SUBDIR += rubygem-async-websocket SUBDIR += rubygem-async_sinatra SUBDIR += rubygem-atlassian-jwt SUBDIR += rubygem-best_in_place SUBDIR += rubygem-best_in_place-rails5 SUBDIR += rubygem-bluecloth SUBDIR += rubygem-bootstrap-sass SUBDIR += rubygem-browser SUBDIR += rubygem-cal-heatmap-rails SUBDIR += rubygem-carrierwave SUBDIR += rubygem-carrierwave1 SUBDIR += rubygem-cgi SUBDIR += rubygem-cgi_multipart_eof_fix SUBDIR += rubygem-chosen-rails SUBDIR += rubygem-chromedriver-helper SUBDIR += rubygem-circuitbox SUBDIR += rubygem-cookiejar SUBDIR += rubygem-crass SUBDIR += rubygem-cssbundling-rails SUBDIR += rubygem-cssbundling-rails-rails70 SUBDIR += rubygem-cssbundling-rails13-rails70 SUBDIR += rubygem-cuba SUBDIR += rubygem-d3_rails SUBDIR += rubygem-davclient SUBDIR += rubygem-deckar01-task_list SUBDIR += rubygem-domainatrix SUBDIR += rubygem-dropzonejs-rails SUBDIR += rubygem-em-http-request SUBDIR += rubygem-em-socksify SUBDIR += rubygem-em-twitter SUBDIR += rubygem-em-websocket SUBDIR += rubygem-emk-sinatra-url-for SUBDIR += rubygem-erubi SUBDIR += rubygem-erubis SUBDIR += rubygem-ethon SUBDIR += rubygem-eventmachine_httpserver SUBDIR += rubygem-faraday SUBDIR += rubygem-faraday-em_http SUBDIR += rubygem-faraday-em_http1 SUBDIR += rubygem-faraday-em_synchrony SUBDIR += rubygem-faraday-follow_redirects SUBDIR += rubygem-faraday-http-cache SUBDIR += rubygem-faraday-httpclient SUBDIR += rubygem-faraday-httpclient1 SUBDIR += rubygem-faraday-multipart SUBDIR += rubygem-faraday-net_http SUBDIR += rubygem-faraday-net_http1 SUBDIR += rubygem-faraday-net_http_persistent SUBDIR += rubygem-faraday-net_http_persistent1 SUBDIR += rubygem-faraday-patron SUBDIR += rubygem-faraday-patron1 SUBDIR += rubygem-faraday-rack SUBDIR += rubygem-faraday-rack1 SUBDIR += rubygem-faraday-retry SUBDIR += rubygem-faraday-retry1 SUBDIR += rubygem-faraday0 SUBDIR += rubygem-faraday1 SUBDIR += rubygem-faraday14 SUBDIR += rubygem-faraday_middleware SUBDIR += rubygem-faraday_middleware0 SUBDIR += rubygem-faye SUBDIR += rubygem-faye-websocket SUBDIR += rubygem-fcgi SUBDIR += rubygem-feed-normalizer SUBDIR += rubygem-feedjira SUBDIR += rubygem-flowdock SUBDIR += rubygem-fuzzyurl SUBDIR += rubygem-geminabox SUBDIR += rubygem-gitlab-flowdock-git-hook SUBDIR += rubygem-gitlab-gollum-lib SUBDIR += rubygem-gitlab-gollum-rugged_adapter SUBDIR += rubygem-gitlab-turbolinks-classic SUBDIR += rubygem-goldfinger SUBDIR += rubygem-gollum SUBDIR += rubygem-gollum-grit_adapter SUBDIR += rubygem-gollum-grit_adapter10 SUBDIR += rubygem-gollum-lib SUBDIR += rubygem-gollum-rugged_adapter SUBDIR += rubygem-gon-rails5 SUBDIR += rubygem-gon-rails50 SUBDIR += rubygem-gon-rails60 SUBDIR += rubygem-gon-rails61 SUBDIR += rubygem-gon-rails70 SUBDIR += rubygem-hackpad-cli SUBDIR += rubygem-haml SUBDIR += rubygem-haml-coderay SUBDIR += rubygem-haml-contrib SUBDIR += rubygem-haml-rails-rails4 SUBDIR += rubygem-haml5 SUBDIR += rubygem-hamlit SUBDIR += rubygem-hamlit-rails SUBDIR += rubygem-hamlit-rails-rails5 SUBDIR += rubygem-hamlit-rails-rails50 SUBDIR += rubygem-hamlit-rails-rails61 SUBDIR += rubygem-hashicorp-checkpoint SUBDIR += rubygem-heroics SUBDIR += rubygem-heroku-api SUBDIR += rubygem-heroku-nav SUBDIR += rubygem-hpricot SUBDIR += rubygem-html2haml SUBDIR += rubygem-http SUBDIR += rubygem-http-accept SUBDIR += rubygem-http-cookie SUBDIR += rubygem-http-form_data SUBDIR += rubygem-http3 SUBDIR += rubygem-http4 SUBDIR += rubygem-http_router SUBDIR += rubygem-httparty SUBDIR += rubygem-httparty020 SUBDIR += rubygem-httpclient SUBDIR += rubygem-httpi SUBDIR += rubygem-hurley SUBDIR += rubygem-importmap-rails SUBDIR += rubygem-importmap-rails-rails70 SUBDIR += rubygem-innate SUBDIR += rubygem-jekyll SUBDIR += rubygem-jekyll-sanity SUBDIR += rubygem-jekyll-seo-tag SUBDIR += rubygem-jekyll-watch SUBDIR += rubygem-jekyll3 SUBDIR += rubygem-journey SUBDIR += rubygem-jquery-atwho-rails SUBDIR += rubygem-jquery-rails SUBDIR += rubygem-jquery-rails-rails5 SUBDIR += rubygem-jquery-rails-rails50 SUBDIR += rubygem-jquery-scrollto-rails SUBDIR += rubygem-jquery-turbolinks SUBDIR += rubygem-jquery-ui-rails-rails4 SUBDIR += rubygem-jruby-rack SUBDIR += rubygem-jsbundling-rails SUBDIR += rubygem-jsbundling-rails-rails70 SUBDIR += rubygem-jsobfu SUBDIR += rubygem-json-jwt SUBDIR += rubygem-json-jwt115 SUBDIR += rubygem-jsonb_accessor SUBDIR += rubygem-jwt SUBDIR += rubygem-kaminari SUBDIR += rubygem-kaminari-actionview SUBDIR += rubygem-kaminari-actionview-rails5 SUBDIR += rubygem-kaminari-actionview-rails50 SUBDIR += rubygem-kaminari-actionview-rails52 SUBDIR += rubygem-kaminari-actionview-rails60 SUBDIR += rubygem-kaminari-actionview-rails61 SUBDIR += rubygem-kaminari-actionview-rails70 SUBDIR += rubygem-kaminari-activerecord SUBDIR += rubygem-kaminari-activerecord-rails5 SUBDIR += rubygem-kaminari-activerecord-rails50 SUBDIR += rubygem-kaminari-activerecord-rails52 SUBDIR += rubygem-kaminari-activerecord-rails60 SUBDIR += rubygem-kaminari-activerecord-rails61 SUBDIR += rubygem-kaminari-activerecord-rails70 SUBDIR += rubygem-kaminari-core SUBDIR += rubygem-kaminari-rails4 SUBDIR += rubygem-kaminari-rails5 SUBDIR += rubygem-kaminari-rails50 SUBDIR += rubygem-kaminari-rails52 SUBDIR += rubygem-kaminari-rails60 SUBDIR += rubygem-kaminari-rails61 SUBDIR += rubygem-kaminari-rails70 SUBDIR += rubygem-kubeclient SUBDIR += rubygem-layout_yullio_generator SUBDIR += rubygem-less SUBDIR += rubygem-lighthouse-api SUBDIR += rubygem-link_header SUBDIR += rubygem-llhttp-ffi SUBDIR += rubygem-lograge SUBDIR += rubygem-lograge-rails5 SUBDIR += rubygem-lograge-rails52 SUBDIR += rubygem-lograge-rails60 SUBDIR += rubygem-lograge-rails61 SUBDIR += rubygem-lograge-rails70 SUBDIR += rubygem-maruku SUBDIR += rubygem-mechanize SUBDIR += rubygem-merb-assets SUBDIR += rubygem-merb-core SUBDIR += rubygem-merb-haml SUBDIR += rubygem-merb-helpers SUBDIR += rubygem-merb-param-protection SUBDIR += rubygem-mousetrap-rails SUBDIR += rubygem-multipart-post SUBDIR += rubygem-nanoc SUBDIR += rubygem-nanoc-checking SUBDIR += rubygem-nanoc-cli SUBDIR += rubygem-nanoc-core SUBDIR += rubygem-nanoc-deploying SUBDIR += rubygem-nested_form SUBDIR += rubygem-net-http SUBDIR += rubygem-net-http-digest_auth SUBDIR += rubygem-net-http-persistent SUBDIR += rubygem-net-http-persistent2 SUBDIR += rubygem-net-http-pipeline SUBDIR += rubygem-net-http011 SUBDIR += rubygem-nicovideo SUBDIR += rubygem-ntlm-http SUBDIR += rubygem-octopress SUBDIR += rubygem-oembed SUBDIR += rubygem-ostatus2 SUBDIR += rubygem-pagerduty SUBDIR += rubygem-passenger SUBDIR += rubygem-patron SUBDIR += rubygem-platform-api SUBDIR += rubygem-propshaft SUBDIR += rubygem-propshaft-rails70 SUBDIR += rubygem-protocol-hpack SUBDIR += rubygem-protocol-http SUBDIR += rubygem-protocol-http1 SUBDIR += rubygem-protocol-http2 SUBDIR += rubygem-protocol-rack SUBDIR += rubygem-protocol-websocket SUBDIR += rubygem-puma SUBDIR += rubygem-puma_worker_killer SUBDIR += rubygem-pusher-client SUBDIR += rubygem-rabbirack SUBDIR += rubygem-rack SUBDIR += rubygem-rack-accept SUBDIR += rubygem-rack-attack SUBDIR += rubygem-rack-cache SUBDIR += rubygem-rack-contrib SUBDIR += rubygem-rack-cors SUBDIR += rubygem-rack-mount SUBDIR += rubygem-rack-openid SUBDIR += rubygem-rack-protection SUBDIR += rubygem-rack-protection1 SUBDIR += rubygem-rack-protection2 SUBDIR += rubygem-rack-protection3 SUBDIR += rubygem-rack-proxy SUBDIR += rubygem-rack-session SUBDIR += rubygem-rack-ssl SUBDIR += rubygem-rack-test SUBDIR += rubygem-rack-timeout SUBDIR += rubygem-rack16 SUBDIR += rubygem-rack22 SUBDIR += rubygem-rack_csrf SUBDIR += rubygem-rackup SUBDIR += rubygem-rails-settings-cached SUBDIR += rubygem-rails-settings-cached-rails5 SUBDIR += rubygem-rails-settings-cached-rails50 SUBDIR += rubygem-rails-settings-cached-rails61 SUBDIR += rubygem-rails4 SUBDIR += rubygem-rails5 SUBDIR += rubygem-rails50 SUBDIR += rubygem-rails52 SUBDIR += rubygem-rails60 SUBDIR += rubygem-rails61 SUBDIR += rubygem-rails70 SUBDIR += rubygem-rails71 SUBDIR += rubygem-rails_12factor SUBDIR += rubygem-rails_autolink SUBDIR += rubygem-rails_serve_static_assets SUBDIR += rubygem-rails_stdout_logging SUBDIR += rubygem-railties4 SUBDIR += rubygem-railties5 SUBDIR += rubygem-railties50 SUBDIR += rubygem-railties52 SUBDIR += rubygem-railties60 SUBDIR += rubygem-railties61 SUBDIR += rubygem-railties70 SUBDIR += rubygem-railties71 SUBDIR += rubygem-raindrops SUBDIR += rubygem-ramaze SUBDIR += rubygem-raphael-rails SUBDIR += rubygem-rate_throttle_client SUBDIR += rubygem-rbovirt SUBDIR += rubygem-rdf SUBDIR += rubygem-rdf-normalize SUBDIR += rubygem-redcloth SUBDIR += rubygem-redis-rack SUBDIR += rubygem-redis-rack2 SUBDIR += rubygem-redis-rails SUBDIR += rubygem-redis-rails-rails5 SUBDIR += rubygem-redis-rails-rails50 SUBDIR += rubygem-redis-rails-rails52 SUBDIR += rubygem-redis-rails-rails60 SUBDIR += rubygem-redis-rails-rails61 SUBDIR += rubygem-responders SUBDIR += rubygem-responders-rails5 SUBDIR += rubygem-responders-rails52 SUBDIR += rubygem-responders-rails60 SUBDIR += rubygem-responders-rails61 SUBDIR += rubygem-responders-rails70 SUBDIR += rubygem-rest-client SUBDIR += rubygem-rfacebook SUBDIR += rubygem-rfeedfinder SUBDIR += rubygem-rinku SUBDIR += rubygem-rkelly-remix SUBDIR += rubygem-robotex SUBDIR += rubygem-robots SUBDIR += rubygem-roda SUBDIR += rubygem-rqrcode SUBDIR += rubygem-rqrcode2 SUBDIR += rubygem-rqrcode_core SUBDIR += rubygem-rss SUBDIR += rubygem-rtlit SUBDIR += rubygem-ruby-oembed SUBDIR += rubygem-ruby-openai SUBDIR += rubygem-ruby-openai37 SUBDIR += rubygem-ruby-readability SUBDIR += rubygem-savon SUBDIR += rubygem-sawyer SUBDIR += rubygem-select2-rails SUBDIR += rubygem-selenium-webdriver SUBDIR += rubygem-semantic-ui-sass SUBDIR += rubygem-simple-rss SUBDIR += rubygem-sinatra SUBDIR += rubygem-sinatra-contrib SUBDIR += rubygem-sinatra-contrib1 SUBDIR += rubygem-sinatra-contrib2 SUBDIR += rubygem-sinatra-r18n SUBDIR += rubygem-sinatra-respond_to SUBDIR += rubygem-sinatra1 SUBDIR += rubygem-sinatra2 SUBDIR += rubygem-sitemap_generator SUBDIR += rubygem-smashing SUBDIR += rubygem-socksify SUBDIR += rubygem-stimulus-rails SUBDIR += rubygem-stimulus-rails-rails70 SUBDIR += rubygem-swd SUBDIR += rubygem-tailwindcss-rails SUBDIR += rubygem-tailwindcss-rails-rails70 SUBDIR += rubygem-task_list SUBDIR += rubygem-thin SUBDIR += rubygem-tinyatom SUBDIR += rubygem-tinymce-rails SUBDIR += rubygem-toml-rb SUBDIR += rubygem-totoridipjp SUBDIR += rubygem-tumblr_client SUBDIR += rubygem-turbo-rails SUBDIR += rubygem-turbo-rails-rails70 SUBDIR += rubygem-turbolinks SUBDIR += rubygem-turbolinks-source SUBDIR += rubygem-typhoeus SUBDIR += rubygem-uglifier SUBDIR += rubygem-underscore-rails SUBDIR += rubygem-unicorn SUBDIR += rubygem-unicorn-worker-killer SUBDIR += rubygem-url_escape SUBDIR += rubygem-url_mount SUBDIR += rubygem-vcr SUBDIR += rubygem-vegas SUBDIR += rubygem-wasabi SUBDIR += rubygem-webdrivers SUBDIR += rubygem-webmock SUBDIR += rubygem-webrick SUBDIR += rubygem-webrobots SUBDIR += rubygem-websocket SUBDIR += rubygem-websocket-client-simple SUBDIR += rubygem-websocket-driver SUBDIR += rubygem-websocket-extensions SUBDIR += rubygem-yapra SUBDIR += rustypaste SUBDIR += rustypaste-cli SUBDIR += s SUBDIR += sabredav SUBDIR += sahi SUBDIR += samdruckerserver SUBDIR += sarg SUBDIR += scloader SUBDIR += screego SUBDIR += script4rss SUBDIR += seahub SUBDIR += searx SUBDIR += selenium SUBDIR += serendipity SUBDIR += serf SUBDIR += servlet-api SUBDIR += sfeed SUBDIR += shellinabox SUBDIR += shiori SUBDIR += silicon SUBDIR += simple-web-server SUBDIR += sitecopy SUBDIR += slowcgi SUBDIR += slowhttptest SUBDIR += smarty SUBDIR += smarty2 SUBDIR += smarty3 SUBDIR += smb_auth SUBDIR += snarf SUBDIR += so SUBDIR += sogo SUBDIR += sogo-activesync SUBDIR += sogo2 SUBDIR += sogo2-activesync SUBDIR += spawn-fcgi SUBDIR += spreadlogd SUBDIR += sqlpage SUBDIR += sqstat SUBDIR += squid SUBDIR += squid-langpack SUBDIR += squid_radius_auth SUBDIR += squidanalyzer SUBDIR += squidclamav SUBDIR += squidguard SUBDIR += squidpurge SUBDIR += squidview SUBDIR += srg SUBDIR += srt SUBDIR += stagit SUBDIR += stork SUBDIR += subsonic-standalone SUBDIR += suitecrm SUBDIR += suphp SUBDIR += surf SUBDIR += swiggle SUBDIR += sws SUBDIR += tcexam SUBDIR += tclhttpd SUBDIR += tclwebtest SUBDIR += tdiary SUBDIR += tdom SUBDIR += template_ SUBDIR += templatelite SUBDIR += thirtybees SUBDIR += threejs SUBDIR += threema-web SUBDIR += thttpd SUBDIR += thumbnail_index SUBDIR += thundercache SUBDIR += tidy SUBDIR += tidy-devel SUBDIR += tidy-html5 SUBDIR += tidy-lib SUBDIR += tikiwiki SUBDIR += tinymce SUBDIR += tinyproxy SUBDIR += tivoka SUBDIR += tntnet SUBDIR += tokyopromenade SUBDIR += tomcat-devel SUBDIR += tomcat-native SUBDIR += tomcat101 SUBDIR += tomcat85 SUBDIR += tomcat9 SUBDIR += tomee SUBDIR += tor-browser SUBDIR += trac SUBDIR += trac-accountmanager SUBDIR += trac-markdownmacro SUBDIR += trac-spamfilter SUBDIR += trac-tocmacro SUBDIR += trafficserver SUBDIR += transproxy SUBDIR += trunk SUBDIR += tt-rss SUBDIR += tuifeed SUBDIR += tusc SUBDIR += tusd SUBDIR += twiki SUBDIR += twiki-BehaviourContrib SUBDIR += twiki-BlogAddOn SUBDIR += twiki-BugzillaLinkPlugin SUBDIR += twiki-ClassicSkin SUBDIR += twiki-CommentPlugin SUBDIR += twiki-EditTablePlugin SUBDIR += twiki-EmptyPlugin SUBDIR += twiki-GluePlugin SUBDIR += twiki-InterwikiPlugin SUBDIR += twiki-JSCalendarContrib SUBDIR += twiki-LDAPPasswordChangerPlugin SUBDIR += twiki-LdapContrib SUBDIR += twiki-LdapNgPlugin SUBDIR += twiki-MailerContrib SUBDIR += twiki-MathModePlugin SUBDIR += twiki-NewUserPlugin SUBDIR += twiki-PatternSkin SUBDIR += twiki-PreferencesPlugin SUBDIR += twiki-RenderListPlugin SUBDIR += twiki-SlideShowPlugin SUBDIR += twiki-SmiliesPlugin SUBDIR += twiki-SpreadSheetPlugin SUBDIR += twiki-SubscribePlugin SUBDIR += twiki-TWikiUserMappingContrib SUBDIR += twiki-TablePlugin SUBDIR += twiki-TagMePlugin SUBDIR += twiki-TinyMCEPlugin SUBDIR += twiki-TipsContrib SUBDIR += twiki-TopicVarsPlugin SUBDIR += twiki-TwistyContrib SUBDIR += twiki-TwistyPlugin SUBDIR += twiki-WysiwygPlugin SUBDIR += twms SUBDIR += typo3-11 SUBDIR += typo3-12 SUBDIR += uchiwa SUBDIR += ufdbguard SUBDIR += ulfius SUBDIR += ungoogled-chromium SUBDIR += unit SUBDIR += unit-java SUBDIR += unit-perl SUBDIR += unit-php SUBDIR += unit-python SUBDIR += unit-ruby SUBDIR += unit-wasm SUBDIR += unitc SUBDIR += uwebsockets SUBDIR += uwsgi SUBDIR += uwsgitop SUBDIR += validator SUBDIR += varnish-ip2location SUBDIR += varnish-ip2proxy SUBDIR += varnish-libvmod-digest SUBDIR += varnish-libvmod-dynamic SUBDIR += varnish-libvmod-fileserver SUBDIR += varnish-libvmod-geoip2 SUBDIR += varnish-libvmod-maxminddb SUBDIR += varnish-libvmod-querystring SUBDIR += varnish-libvmod-redis SUBDIR += varnish-modules SUBDIR += varnish7 SUBDIR += varnish_exporter SUBDIR += vaultwarden-web_vault SUBDIR += vdradmin-am SUBDIR += vertx SUBDIR += vger SUBDIR += vieb SUBDIR += vigil SUBDIR += vimb SUBDIR += visitors SUBDIR += volta SUBDIR += vultr-cli SUBDIR += w3m SUBDIR += w3m-img SUBDIR += w3mir SUBDIR += wabt SUBDIR += wasm-pack SUBDIR += web2ldap SUBDIR += webalizer SUBDIR += webbrowser SUBDIR += webcopy SUBDIR += webcrawl SUBDIR += webfs SUBDIR += webgrind SUBDIR += webhook SUBDIR += webinject SUBDIR += webkit2-gtk3 SUBDIR += webkit2-gtk4 SUBDIR += weblint++ SUBDIR += webpy SUBDIR += webresolve SUBDIR += websh SUBDIR += websocat SUBDIR += websocketd SUBDIR += webstone SUBDIR += webtrees20 SUBDIR += webtrees21 SUBDIR += wget2 SUBDIR += wgetpaste SUBDIR += wiki-tui SUBDIR += wikicalc SUBDIR += wordpress SUBDIR += wpebackend-fdo SUBDIR += writeas-cli SUBDIR += writefreely SUBDIR += wsdlpull SUBDIR += wslay SUBDIR += wsmake SUBDIR += wt SUBDIR += wuzz SUBDIR += wwwoffle SUBDIR += xapian-omega SUBDIR += xcaddy SUBDIR += xfce4-smartbookmark-plugin SUBDIR += xh SUBDIR += xist SUBDIR += xoops SUBDIR += xsp SUBDIR += xurls SUBDIR += yabb SUBDIR += yarn SUBDIR += yarn-node16 SUBDIR += yarn-node18 SUBDIR += yarn-node20 SUBDIR += yarn-node21 SUBDIR += yarr SUBDIR += yaws SUBDIR += you-get SUBDIR += yourls SUBDIR += youtube_dl SUBDIR += yt-dlp SUBDIR += ytdl SUBDIR += yuicompressor SUBDIR += zend-framework SUBDIR += zenphoto SUBDIR += zerowait-httpd SUBDIR += zgrab2 SUBDIR += zola .include 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 + +.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 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 ; ++ ++ To set host key for various tokens: ++ ++ quic_host_key ; ++ ++ QUIC requires TLSv1.3 protocol, which is enabled by the default ++ by "ssl_protocols" directive. ++ ++ By default, GSO Linux-specific optimization [10] is disabled. ++ Enable it in case a corresponding network interface is configured to ++ support GSO. ++ ++ A number of directives were added that configure HTTP/3: ++ ++ http3 ++ http3_hq ++ http3_stream_buffer_size ++ http3_max_concurrent_pushes ++ http3_max_concurrent_streams ++ http3_push ++ http3_push_preload ++ ++ In http, an additional variable is available: $http3. ++ The value of $http3 is "h3" for HTTP/3 connections, ++ "hq" for hq connections, or an empty string otherwise. ++ ++ In stream, an additional variable is available: $quic. ++ The value of $quic is "quic" if QUIC connection is used, ++ or an empty string otherwise. ++ ++Example configuration: ++ ++ http { ++ log_format quic '$remote_addr - $remote_user [$time_local] ' ++ '"$request" $status $body_bytes_sent ' ++ '"$http_referer" "$http_user_agent" "$http3"'; ++ ++ access_log logs/access.log quic; ++ ++ server { ++ # for better compatibility it's recommended ++ # to use the same port for quic and https ++ listen 8443 quic reuseport; ++ listen 8443 ssl; ++ ++ ssl_certificate certs/example.com.crt; ++ ssl_certificate_key certs/example.com.key; ++ ++ location / { ++ # required for browsers to direct them into quic port ++ add_header Alt-Svc 'h3=":8443"; ma=86400'; ++ } ++ } ++ } ++ ++4. Directives ++ ++ Syntax: quic_bpf on | off; ++ Default: quic_bpf off; ++ Context: main ++ ++ Enables routing of QUIC packets using eBPF. ++ When enabled, this allows to support QUIC connection migration. ++ The directive is only supported on Linux 5.7+. ++ ++ ++ Syntax: quic_retry on | off; ++ Default: quic_retry off; ++ Context: http | stream, server ++ ++ Enables the QUIC Address Validation feature. This includes: ++ - sending a new token in a Retry packet or a NEW_TOKEN frame ++ - validating a token received in the Initial packet ++ ++ ++ Syntax: quic_gso on | off; ++ Default: quic_gso off; ++ Context: http | stream, server ++ ++ Enables sending in optimized batch mode using segmentation offloading. ++ Optimized sending is only supported on Linux featuring UDP_SEGMENT. ++ ++ ++ Syntax: quic_mtu size; ++ Default: quic_mtu 65527; ++ Context: http | stream, server ++ ++ Sets the QUIC max_udp_payload_size transport parameter value. ++ This is the maximum UDP payload that we are willing to receive. ++ ++ ++ Syntax: quic_host_key file; ++ Default: - ++ Context: http | stream, server ++ ++ Specifies a file with the secret key used to encrypt stateless reset and ++ address validation tokens. By default, a randomly generated key is used. ++ ++ ++ Syntax: quic_active_connection_id_limit number; ++ Default: quic_active_connection_id_limit 2; ++ Context: http | stream, server ++ ++ Sets the QUIC active_connection_id_limit transport parameter value. ++ This is the maximum number of connection IDs we are willing to store. ++ ++ ++ Syntax: quic_timeout time; ++ Default: quic_timeout 60s; ++ Context: stream, server ++ ++ Defines a timeout used to negotiate the QUIC idle timeout. ++ In the http module, it is taken from the keepalive_timeout directive. ++ ++ ++ Syntax: quic_stream_buffer_size size; ++ Default: quic_stream_buffer_size 64k; ++ Context: stream, server ++ ++ Syntax: http3_stream_buffer_size size; ++ Default: http3_stream_buffer_size 64k; ++ Context: http, server ++ ++ Sets buffer size for reading and writing of the QUIC STREAM payload. ++ The buffer size is used to calculate initial flow control limits ++ in the following QUIC transport parameters: ++ - initial_max_data ++ - initial_max_stream_data_bidi_local ++ - initial_max_stream_data_bidi_remote ++ - initial_max_stream_data_uni ++ ++ ++ Syntax: http3_max_concurrent_pushes number; ++ Default: http3_max_concurrent_pushes 10; ++ Context: http, server ++ ++ Limits the maximum number of concurrent push requests in a connection. ++ ++ ++ Syntax: http3_max_concurrent_streams number; ++ Default: http3_max_concurrent_streams 128; ++ Context: http, server ++ ++ Sets the maximum number of concurrent HTTP/3 streams in a connection. ++ ++ ++ Syntax: http3_push uri | off; ++ Default: http3_push off; ++ Context: http, server, location ++ ++ Pre-emptively sends (pushes) a request to the specified uri along with ++ the response to the original request. Only relative URIs with absolute ++ path will be processed, for example: ++ ++ http3_push /static/css/main.css; ++ ++ The uri value can contain variables. ++ ++ Several http3_push directives can be specified on the same configuration ++ level. The off parameter cancels the effect of the http3_push directives ++ inherited from the previous configuration level. ++ ++ ++ Syntax: http3_push_preload on | off; ++ Default: http3_push_preload off; ++ Context: http, server, location ++ ++ Enables automatic conversion of preload links specified in the “Link” ++ response header fields into push requests. ++ ++ ++ Syntax: http3 on | off; ++ Default: http3 on; ++ Context: http, server ++ ++ Enables HTTP/3 protocol negotiation. ++ ++ ++ Syntax: http3_hq on | off; ++ Default: http3_hq off; ++ Context: http, server ++ ++ Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests. ++ ++5. Clients ++ ++ * Browsers ++ ++ Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1) ++ ++ Beware of strange issues: sometimes browser may decide to ignore QUIC ++ Cache clearing/restart might help. Always check access.log and ++ error.log to make sure the browser is using HTTP/3 and not TCP https. ++ ++ * Console clients ++ ++ Known to work: ngtcp2, firefox's neqo and chromium's console clients: ++ ++ $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html ++ ++ $ ./neqo-client https://127.0.0.1:8443/ ++ ++ $ chromium-build/out/my_build/quic_client http://example.com:8443 ++ ++ ++ In case everyhing is right, the access log should show something like: ++ ++ 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" ++ "nghttp3/ngtcp2 client" "quic" ++ ++ ++6. Troubleshooting ++ ++ Here are some tips that may help to identify problems: ++ ++ + Ensure nginx is built with proper SSL library that supports QUIC ++ ++ + Ensure nginx is using the proper SSL library in runtime ++ (`nginx -V` shows what it's using) ++ ++ + Ensure a client is actually sending requests over QUIC ++ (see "Clients" section about browsers and cache) ++ ++ We recommend to start with simple console client like ngtcp2 ++ to ensure the server is configured properly before trying ++ with real browsers that may be very picky with certificates, ++ for example. ++ ++ + Build nginx with debug support [9] and check the debug log. ++ It should contain all details about connection and why it ++ failed. All related messages contain "quic " prefix and can ++ be easily filtered out. ++ ++ + For a deeper investigation, please enable additional debugging ++ in src/event/quic/ngx_event_quic_connection.h: ++ ++ #define NGX_QUIC_DEBUG_PACKETS ++ #define NGX_QUIC_DEBUG_FRAMES ++ #define NGX_QUIC_DEBUG_ALLOC ++ #define NGX_QUIC_DEBUG_CRYPTO ++ ++7. Contributing ++ ++ Please refer to ++ http://nginx.org/en/docs/contributing_changes.html ++ ++8. Links ++ ++ [1] https://datatracker.ietf.org/doc/html/rfc9000 ++ [2] https://datatracker.ietf.org/doc/html/rfc9114 ++ [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel ++ [4] https://boringssl.googlesource.com/boringssl/ ++ [5] https://www.libressl.org/ ++ [6] https://github.com/quictls/openssl ++ [7] https://github.com/libressl-portable/portable/releases/tag/v3.6.0 ++ [8] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen ++ [9] https://nginx.org/en/docs/debugging_log.html ++ [10] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf +diff -r ac779115ed6e auto/lib/openssl/conf +--- a/auto/lib/openssl/conf Tue Mar 28 18:01:53 2023 +0300 ++++ b/auto/lib/openssl/conf Thu May 11 11:48:37 2023 -0400 +@@ -5,12 +5,17 @@ + + if [ $OPENSSL != NONE ]; then + ++ have=NGX_OPENSSL . auto/have ++ have=NGX_SSL . auto/have ++ ++ if [ $USE_OPENSSL_QUIC = YES ]; then ++ have=NGX_QUIC . auto/have ++ have=NGX_QUIC_OPENSSL_COMPAT . auto/have ++ fi ++ + case "$CC" in + + cl | bcc32) +- have=NGX_OPENSSL . auto/have +- have=NGX_SSL . auto/have +- + CFLAGS="$CFLAGS -DNO_SYS_TYPES_H" + + CORE_INCS="$CORE_INCS $OPENSSL/openssl/include" +@@ -33,9 +38,6 @@ if [ $OPENSSL != NONE ]; then + ;; + + *) +- have=NGX_OPENSSL . auto/have +- have=NGX_SSL . auto/have +- + CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include" + CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h" + CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a" +@@ -123,6 +125,35 @@ else + CORE_INCS="$CORE_INCS $ngx_feature_path" + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" + OPENSSL=YES ++ ++ if [ $USE_OPENSSL_QUIC = YES ]; then ++ ++ ngx_feature="OpenSSL QUIC support" ++ ngx_feature_name="NGX_QUIC" ++ ngx_feature_test="SSL_set_quic_method(NULL, NULL)" ++ . auto/feature ++ ++ if [ $ngx_found = no ]; then ++ have=NGX_QUIC_OPENSSL_COMPAT . auto/have ++ ++ ngx_feature="OpenSSL QUIC compatibility" ++ ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0, ++ NULL, NULL, NULL, NULL, NULL)" ++ . auto/feature ++ fi ++ ++ if [ $ngx_found = no ]; then ++cat << END ++ ++$0: error: certain modules require OpenSSL QUIC support. ++You can either do not enable the modules, or install the OpenSSL library with ++QUIC support into the system, or build the OpenSSL library with QUIC support ++statically from the source with nginx by using --with-openssl= option. ++ ++END ++ exit 1 ++ fi ++ fi + fi + fi + +diff -r ac779115ed6e auto/make +--- a/auto/make Tue Mar 28 18:01:53 2023 +0300 ++++ b/auto/make Thu May 11 11:48:37 2023 -0400 +@@ -6,9 +6,10 @@ + echo "creating $NGX_MAKEFILE" + + mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ ++ $NGX_OBJS/src/event/quic \ + $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ +- $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ +- $NGX_OBJS/src/http/modules/perl \ ++ $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ ++ $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \ + $NGX_OBJS/src/mail \ + $NGX_OBJS/src/stream \ + $NGX_OBJS/src/misc +diff -r ac779115ed6e auto/modules +--- a/auto/modules Tue Mar 28 18:01:53 2023 +0300 ++++ b/auto/modules Thu May 11 11:48:37 2023 -0400 +@@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then + fi + + +- if [ $HTTP_V2 = YES ]; then ++ if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then + HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS" + fi + +@@ -124,6 +124,7 @@ if [ $HTTP = YES ]; then + # ngx_http_header_filter + # ngx_http_chunked_filter + # ngx_http_v2_filter ++ # ngx_http_v3_filter + # ngx_http_range_header_filter + # ngx_http_gzip_filter + # ngx_http_postpone_filter +@@ -156,6 +157,7 @@ if [ $HTTP = YES ]; then + ngx_http_header_filter_module \ + ngx_http_chunked_filter_module \ + ngx_http_v2_filter_module \ ++ ngx_http_v3_filter_module \ + ngx_http_range_header_filter_module \ + ngx_http_gzip_filter_module \ + ngx_http_postpone_filter_module \ +@@ -217,6 +219,17 @@ if [ $HTTP = YES ]; then + . auto/module + fi + ++ if [ $HTTP_V3 = YES ]; then ++ ngx_module_name=ngx_http_v3_filter_module ++ ngx_module_incs= ++ ngx_module_deps= ++ ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c ++ ngx_module_libs= ++ ngx_module_link=$HTTP_V3 ++ ++ . auto/module ++ fi ++ + if :; then + ngx_module_name=ngx_http_range_header_filter_module + ngx_module_incs= +@@ -426,6 +439,33 @@ if [ $HTTP = YES ]; then + . auto/module + fi + ++ if [ $HTTP_V3 = YES ]; then ++ USE_OPENSSL_QUIC=YES ++ HTTP_SSL=YES ++ ++ have=NGX_HTTP_V3 . auto/have ++ have=NGX_HTTP_HEADERS . auto/have ++ ++ ngx_module_name=ngx_http_v3_module ++ ngx_module_incs=src/http/v3 ++ ngx_module_deps="src/http/v3/ngx_http_v3.h \ ++ src/http/v3/ngx_http_v3_encode.h \ ++ src/http/v3/ngx_http_v3_parse.h \ ++ src/http/v3/ngx_http_v3_table.h \ ++ src/http/v3/ngx_http_v3_uni.h" ++ ngx_module_srcs="src/http/v3/ngx_http_v3.c \ ++ src/http/v3/ngx_http_v3_encode.c \ ++ src/http/v3/ngx_http_v3_parse.c \ ++ src/http/v3/ngx_http_v3_table.c \ ++ src/http/v3/ngx_http_v3_uni.c \ ++ src/http/v3/ngx_http_v3_request.c \ ++ src/http/v3/ngx_http_v3_module.c" ++ ngx_module_libs= ++ ngx_module_link=$HTTP_V3 ++ ++ . auto/module ++ fi ++ + if :; then + ngx_module_name=ngx_http_static_module + ngx_module_incs= +@@ -1035,6 +1075,20 @@ if [ $STREAM != NO ]; then + + ngx_module_incs= + ++ if [ $STREAM_QUIC = YES ]; then ++ USE_OPENSSL_QUIC=YES ++ have=NGX_STREAM_QUIC . auto/have ++ STREAM_SSL=YES ++ ++ ngx_module_name=ngx_stream_quic_module ++ ngx_module_deps=src/stream/ngx_stream_quic_module.h ++ ngx_module_srcs=src/stream/ngx_stream_quic_module.c ++ ngx_module_libs= ++ ngx_module_link=$STREAM_QUIC ++ ++ . auto/module ++ fi ++ + if [ $STREAM_SSL = YES ]; then + USE_OPENSSL=YES + have=NGX_STREAM_SSL . auto/have +@@ -1272,6 +1326,63 @@ if [ $USE_OPENSSL = YES ]; then + fi + + ++if [ $USE_OPENSSL_QUIC = YES ]; then ++ ngx_module_type=CORE ++ ngx_module_name=ngx_quic_module ++ ngx_module_incs= ++ ngx_module_deps="src/event/quic/ngx_event_quic.h \ ++ src/event/quic/ngx_event_quic_transport.h \ ++ src/event/quic/ngx_event_quic_protection.h \ ++ src/event/quic/ngx_event_quic_connection.h \ ++ src/event/quic/ngx_event_quic_frames.h \ ++ src/event/quic/ngx_event_quic_connid.h \ ++ src/event/quic/ngx_event_quic_migration.h \ ++ src/event/quic/ngx_event_quic_streams.h \ ++ src/event/quic/ngx_event_quic_ssl.h \ ++ src/event/quic/ngx_event_quic_tokens.h \ ++ src/event/quic/ngx_event_quic_ack.h \ ++ src/event/quic/ngx_event_quic_output.h \ ++ src/event/quic/ngx_event_quic_socket.h \ ++ src/event/quic/ngx_event_quic_openssl_compat.h" ++ ngx_module_srcs="src/event/quic/ngx_event_quic.c \ ++ src/event/quic/ngx_event_quic_udp.c \ ++ src/event/quic/ngx_event_quic_transport.c \ ++ src/event/quic/ngx_event_quic_protection.c \ ++ src/event/quic/ngx_event_quic_frames.c \ ++ src/event/quic/ngx_event_quic_connid.c \ ++ src/event/quic/ngx_event_quic_migration.c \ ++ src/event/quic/ngx_event_quic_streams.c \ ++ src/event/quic/ngx_event_quic_ssl.c \ ++ src/event/quic/ngx_event_quic_tokens.c \ ++ src/event/quic/ngx_event_quic_ack.c \ ++ src/event/quic/ngx_event_quic_output.c \ ++ src/event/quic/ngx_event_quic_socket.c \ ++ src/event/quic/ngx_event_quic_openssl_compat.c" ++ ++ ngx_module_libs= ++ ngx_module_link=YES ++ ngx_module_order= ++ ++ . auto/module ++ ++ if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then ++ ngx_module_type=CORE ++ ngx_module_name=ngx_quic_bpf_module ++ ngx_module_incs= ++ ngx_module_deps= ++ ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \ ++ src/event/quic/ngx_event_quic_bpf_code.c" ++ ngx_module_libs= ++ ngx_module_link=YES ++ ngx_module_order= ++ ++ . auto/module ++ ++ have=NGX_QUIC_BPF . auto/have ++ fi ++fi ++ ++ + if [ $USE_PCRE = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_regex_module +diff -r ac779115ed6e auto/options +--- a/auto/options Tue Mar 28 18:01:53 2023 +0300 ++++ b/auto/options Thu May 11 11:48:37 2023 -0400 +@@ -45,6 +45,8 @@ USE_THREADS=NO + + NGX_FILE_AIO=NO + ++QUIC_BPF=NO ++ + HTTP=YES + + NGX_HTTP_LOG_PATH= +@@ -59,6 +61,7 @@ HTTP_CHARSET=YES + HTTP_GZIP=YES + HTTP_SSL=NO + HTTP_V2=NO ++HTTP_V3=NO + HTTP_SSI=YES + HTTP_REALIP=NO + HTTP_XSLT=NO +@@ -116,6 +119,7 @@ MAIL_SMTP=YES + + STREAM=NO + STREAM_SSL=NO ++STREAM_QUIC=NO + STREAM_REALIP=NO + STREAM_LIMIT_CONN=YES + STREAM_ACCESS=YES +@@ -149,6 +153,7 @@ PCRE_JIT=NO + PCRE2=YES + + USE_OPENSSL=NO ++USE_OPENSSL_QUIC=NO + OPENSSL=NONE + + USE_ZLIB=NO +@@ -166,6 +171,8 @@ USE_GEOIP=NO + NGX_GOOGLE_PERFTOOLS=NO + NGX_CPP_TEST=NO + ++SO_COOKIE_FOUND=NO ++ + NGX_LIBATOMIC=NO + + NGX_CPU_CACHE_LINE= +@@ -211,6 +218,8 @@ do + + --with-file-aio) NGX_FILE_AIO=YES ;; + ++ --without-quic_bpf_module) QUIC_BPF=NONE ;; ++ + --with-ipv6) + NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG + $0: warning: the \"--with-ipv6\" option is deprecated" +@@ -228,6 +237,7 @@ do + + --with-http_ssl_module) HTTP_SSL=YES ;; + --with-http_v2_module) HTTP_V2=YES ;; ++ --with-http_v3_module) HTTP_V3=YES ;; + --with-http_realip_module) HTTP_REALIP=YES ;; + --with-http_addition_module) HTTP_ADDITION=YES ;; + --with-http_xslt_module) HTTP_XSLT=YES ;; +@@ -314,6 +324,7 @@ use the \"--with-mail_ssl_module\" optio + --with-stream) STREAM=YES ;; + --with-stream=dynamic) STREAM=DYNAMIC ;; + --with-stream_ssl_module) STREAM_SSL=YES ;; ++ --with-stream_quic_module) STREAM_QUIC=YES ;; + --with-stream_realip_module) STREAM_REALIP=YES ;; + --with-stream_geoip_module) STREAM_GEOIP=YES ;; + --with-stream_geoip_module=dynamic) +@@ -443,8 +454,11 @@ cat << END + + --with-file-aio enable file AIO support + ++ --without-quic_bpf_module disable ngx_quic_bpf_module ++ + --with-http_ssl_module enable ngx_http_ssl_module + --with-http_v2_module enable ngx_http_v2_module ++ --with-http_v3_module enable ngx_http_v3_module + --with-http_realip_module enable ngx_http_realip_module + --with-http_addition_module enable ngx_http_addition_module + --with-http_xslt_module enable ngx_http_xslt_module +@@ -533,6 +547,7 @@ cat << END + --with-stream enable TCP/UDP proxy module + --with-stream=dynamic enable dynamic TCP/UDP proxy module + --with-stream_ssl_module enable ngx_stream_ssl_module ++ --with-stream_quic_module enable ngx_stream_quic_module + --with-stream_realip_module enable ngx_stream_realip_module + --with-stream_geoip_module enable ngx_stream_geoip_module + --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module +diff -r ac779115ed6e auto/os/linux +--- a/auto/os/linux Tue Mar 28 18:01:53 2023 +0300 ++++ b/auto/os/linux Thu May 11 11:48:37 2023 -0400 +@@ -232,6 +232,50 @@ ngx_feature_test="struct crypt_data cd; + ngx_include="sys/vfs.h"; . auto/include + + ++# BPF sockhash ++ ++ngx_feature="BPF sockhash" ++ngx_feature_name="NGX_HAVE_BPF" ++ngx_feature_run=no ++ngx_feature_incs="#include ++ #include " ++ngx_feature_path= ++ngx_feature_libs= ++ngx_feature_test="union bpf_attr attr = { 0 }; ++ ++ attr.map_flags = 0; ++ attr.map_type = BPF_MAP_TYPE_SOCKHASH; ++ ++ syscall(__NR_bpf, 0, &attr, 0);" ++. auto/feature ++ ++if [ $ngx_found = yes ]; then ++ CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" ++ CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" ++ ++ if [ $QUIC_BPF != NONE ]; then ++ QUIC_BPF=YES ++ fi ++fi ++ ++ ++ngx_feature="SO_COOKIE" ++ngx_feature_name="NGX_HAVE_SO_COOKIE" ++ngx_feature_run=no ++ngx_feature_incs="#include ++ $NGX_INCLUDE_INTTYPES_H" ++ngx_feature_path= ++ngx_feature_libs= ++ngx_feature_test="socklen_t optlen = sizeof(uint64_t); ++ uint64_t cookie; ++ getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)" ++. auto/feature ++ ++if [ $ngx_found = yes ]; then ++ SO_COOKIE_FOUND=YES ++fi ++ ++ + # UDP segmentation offloading + + ngx_feature="UDP_SEGMENT" +diff -r ac779115ed6e auto/sources +--- a/auto/sources Tue Mar 28 18:01:53 2023 +0300 ++++ b/auto/sources Thu May 11 11:48:37 2023 -0400 +@@ -83,7 +83,7 @@ CORE_SRCS="src/core/nginx.c \ + + EVENT_MODULES="ngx_events_module ngx_event_core_module" + +-EVENT_INCS="src/event src/event/modules" ++EVENT_INCS="src/event src/event/modules src/event/quic" + + EVENT_DEPS="src/event/ngx_event.h \ + src/event/ngx_event_timer.h \ +diff -r ac779115ed6e auto/unix +--- a/auto/unix Tue Mar 28 18:01:53 2023 +0300 ++++ b/auto/unix Thu May 11 11:48:37 2023 -0400 +@@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_ + . auto/feature + + ++# IP packet fragmentation ++ ++ngx_feature="IP_MTU_DISCOVER" ++ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER" ++ngx_feature_run=no ++ngx_feature_incs="#include ++ #include " ++ngx_feature_path= ++ngx_feature_libs= ++ngx_feature_test="(void) IP_PMTUDISC_DO; ++ setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)" ++. auto/feature ++ ++ ++ngx_feature="IPV6_MTU_DISCOVER" ++ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER" ++ngx_feature_run=no ++ngx_feature_incs="#include ++ #include " ++ngx_feature_path= ++ngx_feature_libs= ++ngx_feature_test="(void) IPV6_PMTUDISC_DO; ++ setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)" ++. auto/feature ++ ++ ++ngx_feature="IP_DONTFRAG" ++ngx_feature_name="NGX_HAVE_IP_DONTFRAG" ++ngx_feature_run=no ++ngx_feature_incs="#include ++ #include " ++ngx_feature_path= ++ngx_feature_libs= ++ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" ++. auto/feature ++ ++ ++ngx_feature="IPV6_DONTFRAG" ++ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" ++ngx_feature_run=no ++ngx_feature_incs="#include ++ #include " ++ngx_feature_path= ++ngx_feature_libs= ++ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)" ++. auto/feature ++ ++ + ngx_feature="TCP_DEFER_ACCEPT" + ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" + ngx_feature_run=no +diff -r ac779115ed6e src/core/nginx.c +--- a/src/core/nginx.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/core/nginx.c Thu May 11 11:48:37 2023 -0400 +@@ -680,6 +680,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle, + + ls = cycle->listening.elts; + for (i = 0; i < cycle->listening.nelts; i++) { ++ if (ls[i].ignore) { ++ continue; ++ } + p = ngx_sprintf(p, "%ud;", ls[i].fd); + } + +diff -r ac779115ed6e src/core/ngx_bpf.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/core/ngx_bpf.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,143 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++ ++#define NGX_BPF_LOGBUF_SIZE (16 * 1024) ++ ++ ++static ngx_inline int ++ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) ++{ ++ return syscall(__NR_bpf, cmd, attr, size); ++} ++ ++ ++void ++ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd) ++{ ++ ngx_uint_t i; ++ ngx_bpf_reloc_t *rl; ++ ++ rl = program->relocs; ++ ++ for (i = 0; i < program->nrelocs; i++) { ++ if (ngx_strcmp(rl[i].name, symbol) == 0) { ++ program->ins[rl[i].offset].src_reg = 1; ++ program->ins[rl[i].offset].imm = fd; ++ } ++ } ++} ++ ++ ++int ++ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program) ++{ ++ int fd; ++ union bpf_attr attr; ++#if (NGX_DEBUG) ++ char buf[NGX_BPF_LOGBUF_SIZE]; ++#endif ++ ++ ngx_memzero(&attr, sizeof(union bpf_attr)); ++ ++ attr.license = (uintptr_t) program->license; ++ attr.prog_type = program->type; ++ attr.insns = (uintptr_t) program->ins; ++ attr.insn_cnt = program->nins; ++ ++#if (NGX_DEBUG) ++ /* for verifier errors */ ++ attr.log_buf = (uintptr_t) buf; ++ attr.log_size = NGX_BPF_LOGBUF_SIZE; ++ attr.log_level = 1; ++#endif ++ ++ fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); ++ if (fd < 0) { ++ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ++ "failed to load BPF program"); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, ++ "bpf verifier: %s", buf); ++ ++ return -1; ++ } ++ ++ return fd; ++} ++ ++ ++int ++ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, ++ int value_size, int max_entries, uint32_t map_flags) ++{ ++ int fd; ++ union bpf_attr attr; ++ ++ ngx_memzero(&attr, sizeof(union bpf_attr)); ++ ++ attr.map_type = type; ++ attr.key_size = key_size; ++ attr.value_size = value_size; ++ attr.max_entries = max_entries; ++ attr.map_flags = map_flags; ++ ++ fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); ++ if (fd < 0) { ++ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ++ "failed to create BPF map"); ++ return NGX_ERROR; ++ } ++ ++ return fd; ++} ++ ++ ++int ++ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags) ++{ ++ union bpf_attr attr; ++ ++ ngx_memzero(&attr, sizeof(union bpf_attr)); ++ ++ attr.map_fd = fd; ++ attr.key = (uintptr_t) key; ++ attr.value = (uintptr_t) value; ++ attr.flags = flags; ++ ++ return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); ++} ++ ++ ++int ++ngx_bpf_map_delete(int fd, const void *key) ++{ ++ union bpf_attr attr; ++ ++ ngx_memzero(&attr, sizeof(union bpf_attr)); ++ ++ attr.map_fd = fd; ++ attr.key = (uintptr_t) key; ++ ++ return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); ++} ++ ++ ++int ++ngx_bpf_map_lookup(int fd, const void *key, void *value) ++{ ++ union bpf_attr attr; ++ ++ ngx_memzero(&attr, sizeof(union bpf_attr)); ++ ++ attr.map_fd = fd; ++ attr.key = (uintptr_t) key; ++ attr.value = (uintptr_t) value; ++ ++ return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); ++} +diff -r ac779115ed6e src/core/ngx_bpf.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/core/ngx_bpf.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,43 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_BPF_H_INCLUDED_ ++#define _NGX_BPF_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++#include ++ ++ ++typedef struct { ++ char *name; ++ int offset; ++} ngx_bpf_reloc_t; ++ ++typedef struct { ++ char *license; ++ enum bpf_prog_type type; ++ struct bpf_insn *ins; ++ size_t nins; ++ ngx_bpf_reloc_t *relocs; ++ size_t nrelocs; ++} ngx_bpf_program_t; ++ ++ ++void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, ++ int fd); ++int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program); ++ ++int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, ++ int value_size, int max_entries, uint32_t map_flags); ++int ngx_bpf_map_update(int fd, const void *key, const void *value, ++ uint64_t flags); ++int ngx_bpf_map_delete(int fd, const void *key); ++int ngx_bpf_map_lookup(int fd, const void *key, void *value); ++ ++#endif /* _NGX_BPF_H_INCLUDED_ */ +diff -r ac779115ed6e src/core/ngx_connection.c +--- a/src/core/ngx_connection.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/core/ngx_connection.c Thu May 11 11:48:37 2023 -0400 +@@ -72,10 +72,6 @@ ngx_create_listening(ngx_conf_t *cf, str + + ngx_memcpy(ls->addr_text.data, text, len); + +-#if !(NGX_WIN32) +- ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ngx_udp_rbtree_insert_value); +-#endif +- + ls->fd = (ngx_socket_t) -1; + ls->type = SOCK_STREAM; + +@@ -1014,6 +1010,78 @@ ngx_configure_listening_sockets(ngx_cycl + } + + #endif ++ ++#if (NGX_HAVE_IP_MTU_DISCOVER) ++ ++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { ++ value = IP_PMTUDISC_DO; ++ ++ if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER, ++ (const void *) &value, sizeof(int)) ++ == -1) ++ { ++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, ++ "setsockopt(IP_MTU_DISCOVER) " ++ "for %V failed, ignored", ++ &ls[i].addr_text); ++ } ++ } ++ ++#elif (NGX_HAVE_IP_DONTFRAG) ++ ++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { ++ value = 1; ++ ++ if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, ++ (const void *) &value, sizeof(int)) ++ == -1) ++ { ++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, ++ "setsockopt(IP_DONTFRAG) " ++ "for %V failed, ignored", ++ &ls[i].addr_text); ++ } ++ } ++ ++#endif ++ ++#if (NGX_HAVE_INET6) ++ ++#if (NGX_HAVE_IPV6_MTU_DISCOVER) ++ ++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { ++ value = IPV6_PMTUDISC_DO; ++ ++ if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, ++ (const void *) &value, sizeof(int)) ++ == -1) ++ { ++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, ++ "setsockopt(IPV6_MTU_DISCOVER) " ++ "for %V failed, ignored", ++ &ls[i].addr_text); ++ } ++ } ++ ++#elif (NGX_HAVE_IP_DONTFRAG) ++ ++ if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { ++ value = 1; ++ ++ if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, ++ (const void *) &value, sizeof(int)) ++ == -1) ++ { ++ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, ++ "setsockopt(IPV6_DONTFRAG) " ++ "for %V failed, ignored", ++ &ls[i].addr_text); ++ } ++ } ++ ++#endif ++ ++#endif + } + + return; +@@ -1037,6 +1105,12 @@ ngx_close_listening_sockets(ngx_cycle_t + ls = cycle->listening.elts; + for (i = 0; i < cycle->listening.nelts; i++) { + ++#if (NGX_QUIC) ++ if (ls[i].quic) { ++ continue; ++ } ++#endif ++ + c = ls[i].connection; + + if (c) { +diff -r ac779115ed6e src/core/ngx_connection.h +--- a/src/core/ngx_connection.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/core/ngx_connection.h Thu May 11 11:48:37 2023 -0400 +@@ -73,6 +73,7 @@ struct ngx_listening_s { + unsigned reuseport:1; + unsigned add_reuseport:1; + unsigned keepalive:2; ++ unsigned quic:1; + + unsigned deferred_accept:1; + unsigned delete_deferred:1; +@@ -147,6 +148,10 @@ struct ngx_connection_s { + + ngx_proxy_protocol_t *proxy_protocol; + ++#if (NGX_QUIC || NGX_COMPAT) ++ ngx_quic_stream_t *quic; ++#endif ++ + #if (NGX_SSL || NGX_COMPAT) + ngx_ssl_connection_t *ssl; + #endif +diff -r ac779115ed6e src/core/ngx_core.h +--- a/src/core/ngx_core.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/core/ngx_core.h Thu May 11 11:48:37 2023 -0400 +@@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx + typedef struct ngx_thread_task_s ngx_thread_task_t; + typedef struct ngx_ssl_s ngx_ssl_t; + typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; ++typedef struct ngx_quic_stream_s ngx_quic_stream_t; + typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; + typedef struct ngx_udp_connection_s ngx_udp_connection_t; + +@@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt + #include + #if (NGX_OPENSSL) + #include ++#if (NGX_QUIC) ++#include ++#endif + #endif + #include + #include +@@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt + #include + #include + #include ++#if (NGX_HAVE_BPF) ++#include ++#endif + + + #define LF (u_char) '\n' +diff -r ac779115ed6e src/event/ngx_event.c +--- a/src/event/ngx_event.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/event/ngx_event.c Thu May 11 11:48:37 2023 -0400 +@@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_ + ngx_int_t + ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) + { ++#if (NGX_QUIC) ++ ++ ngx_connection_t *c; ++ ++ c = rev->data; ++ ++ if (c->quic) { ++ return NGX_OK; ++ } ++ ++#endif ++ + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { + + /* kqueue, epoll */ +@@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev, + { + ngx_connection_t *c; + ++ c = wev->data; ++ ++#if (NGX_QUIC) ++ if (c->quic) { ++ return NGX_OK; ++ } ++#endif ++ + if (lowat) { +- c = wev->data; +- + if (ngx_send_lowat(c, lowat) == NGX_ERROR) { + return NGX_ERROR; + } +@@ -873,8 +891,16 @@ ngx_event_process_init(ngx_cycle_t *cycl + + #else + +- rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept +- : ngx_event_recvmsg; ++ if (c->type == SOCK_STREAM) { ++ rev->handler = ngx_event_accept; ++ ++#if (NGX_QUIC) ++ } else if (ls[i].quic) { ++ rev->handler = ngx_quic_recvmsg; ++#endif ++ } else { ++ rev->handler = ngx_event_recvmsg; ++ } + + #if (NGX_HAVE_REUSEPORT) + +diff -r ac779115ed6e src/event/ngx_event_openssl.c +--- a/src/event/ngx_event_openssl.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/event/ngx_event_openssl.c Thu May 11 11:48:37 2023 -0400 +@@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ng + #ifdef SSL_READ_EARLY_DATA_SUCCESS + static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c); + #endif +-#if (NGX_DEBUG) +-static void ngx_ssl_handshake_log(ngx_connection_t *c); +-#endif + static void ngx_ssl_handshake_handler(ngx_event_t *ev); + #ifdef SSL_READ_EARLY_DATA_SUCCESS + static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, +@@ -2052,7 +2049,7 @@ ngx_ssl_try_early_data(ngx_connection_t + + #if (NGX_DEBUG) + +-static void ++void + ngx_ssl_handshake_log(ngx_connection_t *c) + { + char buf[129], *s, *d; +@@ -3202,6 +3199,13 @@ ngx_ssl_shutdown(ngx_connection_t *c) + ngx_err_t err; + ngx_uint_t tries; + ++#if (NGX_QUIC) ++ if (c->quic) { ++ /* QUIC streams inherit SSL object */ ++ return NGX_OK; ++ } ++#endif ++ + rc = NGX_OK; + + ngx_ssl_ocsp_cleanup(c); +diff -r ac779115ed6e src/event/ngx_event_openssl.h +--- a/src/event/ngx_event_openssl.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/event/ngx_event_openssl.h Thu May 11 11:48:37 2023 -0400 +@@ -24,6 +24,14 @@ + #include + #endif + #include ++#if (NGX_QUIC) ++#ifdef OPENSSL_IS_BORINGSSL ++#include ++#include ++#else ++#include ++#endif ++#endif + #include + #ifndef OPENSSL_NO_OCSP + #include +@@ -302,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ng + + + ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); ++#if (NGX_DEBUG) ++void ngx_ssl_handshake_log(ngx_connection_t *c); ++#endif + ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size); + ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size); + ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit); +diff -r ac779115ed6e src/event/ngx_event_udp.c +--- a/src/event/ngx_event_udp.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/event/ngx_event_udp.c Thu May 11 11:48:37 2023 -0400 +@@ -12,13 +12,6 @@ + + #if !(NGX_WIN32) + +-struct ngx_udp_connection_s { +- ngx_rbtree_node_t node; +- ngx_connection_t *connection; +- ngx_buf_t *buffer; +-}; +- +- + static void ngx_close_accepted_udp_connection(ngx_connection_t *c); + static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, + size_t size); +diff -r ac779115ed6e src/event/ngx_event_udp.h +--- a/src/event/ngx_event_udp.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/event/ngx_event_udp.h Thu May 11 11:48:37 2023 -0400 +@@ -23,6 +23,13 @@ + #endif + + ++struct ngx_udp_connection_s { ++ ngx_rbtree_node_t node; ++ ngx_connection_t *connection; ++ ngx_buf_t *buffer; ++}; ++ ++ + #if (NGX_HAVE_ADDRINFO_CMSG) + + typedef union { +diff -r ac779115ed6e src/event/quic/bpf/bpfgen.sh +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/bpf/bpfgen.sh Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,113 @@ ++#!/bin/bash ++ ++export LANG=C ++ ++set -e ++ ++if [ $# -lt 1 ]; then ++ echo "Usage: PROGNAME=foo LICENSE=bar $0 " ++ exit 1 ++fi ++ ++ ++self=$0 ++filename=$1 ++funcname=$PROGNAME ++ ++generate_head() ++{ ++ cat << END ++/* AUTO-GENERATED, DO NOT EDIT. */ ++ ++#include ++#include ++ ++#include "ngx_bpf.h" ++ ++ ++END ++} ++ ++generate_tail() ++{ ++ cat << END ++ ++ngx_bpf_program_t $PROGNAME = { ++ .relocs = bpf_reloc_prog_$funcname, ++ .nrelocs = sizeof(bpf_reloc_prog_$funcname) ++ / sizeof(bpf_reloc_prog_$funcname[0]), ++ .ins = bpf_insn_prog_$funcname, ++ .nins = sizeof(bpf_insn_prog_$funcname) ++ / sizeof(bpf_insn_prog_$funcname[0]), ++ .license = "$LICENSE", ++ .type = BPF_PROG_TYPE_SK_REUSEPORT, ++}; ++ ++END ++} ++ ++process_relocations() ++{ ++ echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {" ++ ++ objdump -r $filename | awk '{ ++ ++ if (enabled && $NF > 0) { ++ off = strtonum(sprintf("0x%s", $1)); ++ name = $3; ++ ++ printf(" { \"%s\", %d },\n", name, off/8); ++ } ++ ++ if ($1 == "OFFSET") { ++ enabled=1; ++ } ++}' ++ echo "};" ++ echo ++} ++ ++process_section() ++{ ++ echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {" ++ echo " /* opcode dst src offset imm */" ++ ++ section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname") ++ ++ # dd doesn't know hex ++ length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3)) ++ offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6)) ++ ++ for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8) ++ do ++ opcode=0x${ins:0:2} ++ srcdst=0x${ins:2:2} ++ ++ # bytes are dumped in LE order ++ offset=0x${ins:6:2}${ins:4:2} # short ++ immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int ++ ++ dst="$(($srcdst & 0xF))" ++ src="$(($srcdst & 0xF0))" ++ src="$(($src >> 4))" ++ ++ opcode=$(printf "0x%x" $opcode) ++ dst=$(printf "BPF_REG_%d" $dst) ++ src=$(printf "BPF_REG_%d" $src) ++ offset=$(printf "%d" $offset) ++ immedi=$(printf "0x%x" $immedi) ++ ++ printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi ++ done ++ ++cat << END ++}; ++ ++END ++} ++ ++generate_head ++process_relocations ++process_section ++generate_tail ++ +diff -r ac779115ed6e src/event/quic/bpf/makefile +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/bpf/makefile Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,30 @@ ++CFLAGS=-O2 -Wall ++ ++LICENSE=BSD ++ ++PROGNAME=ngx_quic_reuseport_helper ++RESULT=ngx_event_quic_bpf_code ++DEST=../$(RESULT).c ++ ++all: $(RESULT) ++ ++$(RESULT): $(PROGNAME).o ++ LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@ ++ ++DEFS=-DPROGNAME=\"$(PROGNAME)\" \ ++ -DLICENSE_$(LICENSE) \ ++ -DLICENSE=\"$(LICENSE)\" \ ++ ++$(PROGNAME).o: $(PROGNAME).c ++ clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@ ++ ++install: $(RESULT) ++ cp $(RESULT) $(DEST) ++ ++clean: ++ @rm -f $(RESULT) *.o ++ ++debug: $(PROGNAME).o ++ llvm-objdump -S -no-show-raw-insn $< ++ ++.DELETE_ON_ERROR: +diff -r ac779115ed6e src/event/quic/bpf/ngx_quic_reuseport_helper.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,140 @@ ++#include ++#include ++#include ++#include ++/* ++ * the bpf_helpers.h is not included into linux-headers, only available ++ * with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf. ++ */ ++#include ++ ++ ++#if !defined(SEC) ++#define SEC(NAME) __attribute__((section(NAME), used)) ++#endif ++ ++ ++#if defined(LICENSE_GPL) ++ ++/* ++ * To see debug: ++ * ++ * echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable ++ * cat /sys/kernel/debug/tracing/trace_pipe ++ * echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable ++ */ ++ ++#define debugmsg(fmt, ...) \ ++do { \ ++ char __buf[] = fmt; \ ++ bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \ ++} while (0) ++ ++#else ++ ++#define debugmsg(fmt, ...) ++ ++#endif ++ ++char _license[] SEC("license") = LICENSE; ++ ++/*****************************************************************************/ ++ ++#define NGX_QUIC_PKT_LONG 0x80 /* header form */ ++#define NGX_QUIC_SERVER_CID_LEN 20 ++ ++ ++#define advance_data(nbytes) \ ++ offset += nbytes; \ ++ if (start + offset > end) { \ ++ debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \ ++ goto failed; \ ++ } \ ++ data = start + offset - 1; ++ ++ ++#define ngx_quic_parse_uint64(p) \ ++ (((__u64)(p)[0] << 56) | \ ++ ((__u64)(p)[1] << 48) | \ ++ ((__u64)(p)[2] << 40) | \ ++ ((__u64)(p)[3] << 32) | \ ++ ((__u64)(p)[4] << 24) | \ ++ ((__u64)(p)[5] << 16) | \ ++ ((__u64)(p)[6] << 8) | \ ++ ((__u64)(p)[7])) ++ ++/* ++ * actual map object is created by the "bpf" system call, ++ * all pointers to this variable are replaced by the bpf loader ++ */ ++struct bpf_map_def SEC("maps") ngx_quic_sockmap; ++ ++ ++SEC(PROGNAME) ++int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) ++{ ++ int rc; ++ __u64 key; ++ size_t len, offset; ++ unsigned char *start, *end, *data, *dcid; ++ ++ start = ctx->data; ++ end = (unsigned char *) ctx->data_end; ++ offset = 0; ++ ++ advance_data(sizeof(struct udphdr)); /* data at UDP header */ ++ advance_data(1); /* data at QUIC flags */ ++ ++ if (data[0] & NGX_QUIC_PKT_LONG) { ++ ++ advance_data(4); /* data at QUIC version */ ++ advance_data(1); /* data at DCID len */ ++ ++ len = data[0]; /* read DCID length */ ++ ++ if (len < 8) { ++ /* it's useless to search for key in such short DCID */ ++ return SK_PASS; ++ } ++ ++ } else { ++ len = NGX_QUIC_SERVER_CID_LEN; ++ } ++ ++ dcid = &data[1]; ++ advance_data(len); /* we expect the packet to have full DCID */ ++ ++ /* make verifier happy */ ++ if (dcid + sizeof(__u64) > end) { ++ goto failed; ++ } ++ ++ key = ngx_quic_parse_uint64(dcid); ++ ++ rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0); ++ ++ switch (rc) { ++ case 0: ++ debugmsg("nginx quic socket selected by key 0x%llx", key); ++ return SK_PASS; ++ ++ /* kernel returns positive error numbers, errno.h defines positive */ ++ case -ENOENT: ++ debugmsg("nginx quic default route for key 0x%llx", key); ++ /* let the default reuseport logic decide which socket to choose */ ++ return SK_PASS; ++ ++ default: ++ debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx", ++ rc, key); ++ goto failed; ++ } ++ ++failed: ++ /* ++ * SK_DROP will generate ICMP, but we may want to process "invalid" packet ++ * in userspace quic to investigate further and finally react properly ++ * (maybe ignore, maybe send something in response or close connection) ++ */ ++ return SK_PASS; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,1445 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, ++ ngx_quic_header_t *pkt); ++static void ngx_quic_input_handler(ngx_event_t *rev); ++static void ngx_quic_close_handler(ngx_event_t *ev); ++ ++static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, ++ ngx_quic_conf_t *conf); ++static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, ++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c, ++ ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, ++ ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, ++ ngx_quic_header_t *pkt); ++ ++static void ngx_quic_push_handler(ngx_event_t *ev); ++ ++ ++static ngx_core_module_t ngx_quic_module_ctx = { ++ ngx_string("quic"), ++ NULL, ++ NULL ++}; ++ ++ ++ngx_module_t ngx_quic_module = { ++ NGX_MODULE_V1, ++ &ngx_quic_module_ctx, /* module context */ ++ NULL, /* module directives */ ++ NGX_CORE_MODULE, /* module type */ ++ NULL, /* init master */ ++ NULL, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ ++ ++#if (NGX_DEBUG) ++ ++void ++ngx_quic_connstate_dbg(ngx_connection_t *c) ++{ ++ u_char *p, *last; ++ ngx_quic_connection_t *qc; ++ u_char buf[NGX_MAX_ERROR_STR]; ++ ++ p = buf; ++ last = p + sizeof(buf); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ p = ngx_slprintf(p, last, "state:"); ++ ++ if (qc) { ++ ++ if (qc->error != (ngx_uint_t) -1) { ++ p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); ++ p = ngx_slprintf(p, last, " error:%ui", qc->error); ++ ++ if (qc->error_reason) { ++ p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); ++ } ++ } ++ ++ p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : ""); ++ p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : ""); ++ p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); ++ p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); ++ ++ } else { ++ p = ngx_slprintf(p, last, " early"); ++ } ++ ++ if (c->read->timer_set) { ++ p = ngx_slprintf(p, last, ++ qc && qc->send_timer_set ? " send:%M" : " read:%M", ++ c->read->timer.key - ngx_current_msec); ++ } ++ ++ if (qc) { ++ ++ if (qc->push.timer_set) { ++ p = ngx_slprintf(p, last, " push:%M", ++ qc->push.timer.key - ngx_current_msec); ++ } ++ ++ if (qc->pto.timer_set) { ++ p = ngx_slprintf(p, last, " pto:%M", ++ qc->pto.timer.key - ngx_current_msec); ++ } ++ ++ if (qc->close.timer_set) { ++ p = ngx_slprintf(p, last, " close:%M", ++ qc->close.timer.key - ngx_current_msec); ++ } ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic %*s", p - buf, buf); ++} ++ ++#endif ++ ++ ++ngx_int_t ++ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) ++{ ++ ngx_str_t scid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ scid.data = qc->path->cid->id; ++ scid.len = qc->path->cid->len; ++ ++ if (scid.len != ctp->initial_scid.len ++ || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic client initial_source_connection_id mismatch"); ++ return NGX_ERROR; ++ } ++ ++ if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE ++ || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) ++ { ++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ++ qc->error_reason = "invalid maximum packet size"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic maximum packet size is invalid"); ++ return NGX_ERROR; ++ ++ } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) { ++ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic client maximum packet size truncated"); ++ } ++ ++ if (ctp->active_connection_id_limit < 2) { ++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ++ qc->error_reason = "invalid active_connection_id_limit"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic active_connection_id_limit is invalid"); ++ return NGX_ERROR; ++ } ++ ++ if (ctp->ack_delay_exponent > 20) { ++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ++ qc->error_reason = "invalid ack_delay_exponent"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic ack_delay_exponent is invalid"); ++ return NGX_ERROR; ++ } ++ ++ if (ctp->max_ack_delay >= 16384) { ++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ++ qc->error_reason = "invalid max_ack_delay"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic max_ack_delay is invalid"); ++ return NGX_ERROR; ++ } ++ ++ if (ctp->max_idle_timeout > 0 ++ && ctp->max_idle_timeout < qc->tp.max_idle_timeout) ++ { ++ qc->tp.max_idle_timeout = ctp->max_idle_timeout; ++ } ++ ++ qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi; ++ qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni; ++ ++ ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t)); ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) ++{ ++ ngx_int_t rc; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); ++ ++ rc = ngx_quic_handle_datagram(c, c->buffer, conf); ++ if (rc != NGX_OK) { ++ ngx_quic_close_connection(c, rc); ++ return; ++ } ++ ++ /* quic connection is now created */ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_add_timer(c->read, qc->tp.max_idle_timeout); ++ ngx_quic_connstate_dbg(c); ++ ++ c->read->handler = ngx_quic_input_handler; ++ ++ return; ++} ++ ++ ++static ngx_quic_connection_t * ++ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ++ ngx_quic_header_t *pkt) ++{ ++ ngx_uint_t i; ++ ngx_quic_tp_t *ctp; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); ++ if (qc == NULL) { ++ return NULL; ++ } ++ ++ qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t)); ++ if (qc->keys == NULL) { ++ return NULL; ++ } ++ ++ qc->version = pkt->version; ++ ++ ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ++ ngx_quic_rbtree_insert_stream); ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ngx_queue_init(&qc->send_ctx[i].frames); ++ ngx_queue_init(&qc->send_ctx[i].sending); ++ ngx_queue_init(&qc->send_ctx[i].sent); ++ qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; ++ qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; ++ qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; ++ qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ ++ qc->send_ctx[0].level = ssl_encryption_initial; ++ qc->send_ctx[1].level = ssl_encryption_handshake; ++ qc->send_ctx[2].level = ssl_encryption_application; ++ ++ ngx_queue_init(&qc->free_frames); ++ ++ qc->avg_rtt = NGX_QUIC_INITIAL_RTT; ++ qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; ++ qc->min_rtt = NGX_TIMER_INFINITE; ++ qc->first_rtt = NGX_TIMER_INFINITE; ++ ++ /* ++ * qc->latest_rtt = 0 ++ */ ++ ++ qc->pto.log = c->log; ++ qc->pto.data = c; ++ qc->pto.handler = ngx_quic_pto_handler; ++ ++ qc->push.log = c->log; ++ qc->push.data = c; ++ qc->push.handler = ngx_quic_push_handler; ++ ++ qc->close.log = c->log; ++ qc->close.data = c; ++ qc->close.handler = ngx_quic_close_handler; ++ ++ qc->path_validation.log = c->log; ++ qc->path_validation.data = c; ++ qc->path_validation.handler = ngx_quic_path_validation_handler; ++ ++ qc->conf = conf; ++ ++ if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { ++ return NULL; ++ } ++ ++ ctp = &qc->ctp; ++ ++ /* defaults to be used before actual client parameters are received */ ++ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); ++ ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ++ ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; ++ ctp->active_connection_id_limit = 2; ++ ++ ngx_queue_init(&qc->streams.uninitialized); ++ ngx_queue_init(&qc->streams.free); ++ ++ qc->streams.recv_max_data = qc->tp.initial_max_data; ++ qc->streams.recv_window = qc->streams.recv_max_data; ++ ++ qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni; ++ qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi; ++ ++ qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, ++ ngx_max(2 * qc->tp.max_udp_payload_size, ++ 14720)); ++ qc->congestion.ssthresh = (size_t) -1; ++ qc->congestion.recovery_start = ngx_current_msec; ++ ++ if (pkt->validated && pkt->retried) { ++ qc->tp.retry_scid.len = pkt->dcid.len; ++ qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); ++ if (qc->tp.retry_scid.data == NULL) { ++ return NULL; ++ } ++ } ++ ++ if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) ++ != NGX_OK) ++ { ++ return NULL; ++ } ++ ++ qc->validated = pkt->validated; ++ ++ if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { ++ return NULL; ++ } ++ ++ c->idle = 1; ++ ngx_reusable_connection(c, 1); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic connection created"); ++ ++ return qc; ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ u_char *tail, ch; ++ ngx_uint_t i; ++ ngx_queue_t *q; ++ ngx_quic_client_id_t *cid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* A stateless reset uses an entire UDP datagram */ ++ if (!pkt->first) { ++ return NGX_DECLINED; ++ } ++ ++ tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; ++ ++ for (q = ngx_queue_head(&qc->client_ids); ++ q != ngx_queue_sentinel(&qc->client_ids); ++ q = ngx_queue_next(q)) ++ { ++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); ++ ++ if (cid->seqnum == 0 || !cid->used) { ++ /* ++ * No stateless reset token in initial connection id. ++ * Don't accept a token from an unused connection id. ++ */ ++ continue; ++ } ++ ++ /* constant time comparison */ ++ ++ for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { ++ ch |= tail[i] ^ cid->sr_token[i]; ++ } ++ ++ if (ch == 0) { ++ return NGX_OK; ++ } ++ } ++ ++ return NGX_DECLINED; ++} ++ ++ ++static void ++ngx_quic_input_handler(ngx_event_t *rev) ++{ ++ ngx_int_t rc; ++ ngx_buf_t *b; ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); ++ ++ c = rev->data; ++ qc = ngx_quic_get_connection(c); ++ ++ c->log->action = "handling quic input"; ++ ++ if (rev->timedout) { ++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, ++ "quic client timed out"); ++ ngx_quic_close_connection(c, NGX_DONE); ++ return; ++ } ++ ++ if (c->close) { ++ c->close = 0; ++ ++ if (!ngx_exiting) { ++ qc->error = NGX_QUIC_ERR_NO_ERROR; ++ qc->error_reason = "graceful shutdown"; ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ ++ if (!qc->closing && qc->conf->shutdown) { ++ qc->conf->shutdown(c); ++ } ++ ++ return; ++ } ++ ++ b = c->udp->buffer; ++ if (b == NULL) { ++ return; ++ } ++ ++ rc = ngx_quic_handle_datagram(c, b, NULL); ++ ++ if (rc == NGX_ERROR) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ ++ if (rc == NGX_DONE) { ++ return; ++ } ++ ++ /* rc == NGX_OK */ ++ ++ qc->send_timer_set = 0; ++ ngx_add_timer(rev, qc->tp.max_idle_timeout); ++ ++ ngx_quic_connstate_dbg(c); ++} ++ ++ ++void ++ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ++{ ++ ngx_uint_t i; ++ ngx_pool_t *pool; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc == NULL) { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet rejected rc:%i, cleanup connection", rc); ++ goto quic_done; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic close %s rc:%i", ++ qc->closing ? "resumed": "initiated", rc); ++ ++ if (!qc->closing) { ++ ++ /* drop packets from retransmit queues, no ack is expected */ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ngx_quic_free_frames(c, &qc->send_ctx[i].frames); ++ ngx_quic_free_frames(c, &qc->send_ctx[i].sent); ++ } ++ ++ if (rc == NGX_DONE) { ++ ++ /* ++ * RFC 9000, 10.1. Idle Timeout ++ * ++ * If a max_idle_timeout is specified by either endpoint in its ++ * transport parameters (Section 18.2), the connection is silently ++ * closed and its state is discarded when it remains idle ++ */ ++ ++ /* this case also handles some errors from ngx_quic_run() */ ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic close silent drain:%d timedout:%d", ++ qc->draining, c->read->timedout); ++ } else { ++ ++ /* ++ * RFC 9000, 10.2. Immediate Close ++ * ++ * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) ++ * to terminate the connection immediately. ++ */ ++ ++ qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) ++ : ssl_encryption_initial; ++ ++ if (qc->error == (ngx_uint_t) -1) { ++ qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; ++ qc->error_app = 0; ++ } ++ ++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic close immediate term:%d drain:%d " ++ "%serror:%ui \"%s\"", ++ rc == NGX_ERROR ? 1 : 0, qc->draining, ++ qc->error_app ? "app " : "", qc->error, ++ qc->error_reason ? qc->error_reason : ""); ++ ++ if (rc == NGX_OK) { ++ ctx = ngx_quic_get_send_ctx(qc, qc->error_level); ++ ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); ++ } ++ ++ (void) ngx_quic_send_cc(c); ++ ++ if (qc->error_level == ssl_encryption_handshake) { ++ /* for clients that might not have handshake keys */ ++ qc->error_level = ssl_encryption_initial; ++ (void) ngx_quic_send_cc(c); ++ } ++ } ++ ++ qc->closing = 1; ++ } ++ ++ if (rc == NGX_ERROR && qc->close.timer_set) { ++ /* do not wait for timer in case of fatal error */ ++ ngx_del_timer(&qc->close); ++ } ++ ++ if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { ++ return; ++ } ++ ++ if (qc->push.timer_set) { ++ ngx_del_timer(&qc->push); ++ } ++ ++ if (qc->pto.timer_set) { ++ ngx_del_timer(&qc->pto); ++ } ++ ++ if (qc->path_validation.timer_set) { ++ ngx_del_timer(&qc->path_validation); ++ } ++ ++ if (qc->push.posted) { ++ ngx_delete_posted_event(&qc->push); ++ } ++ ++ if (qc->close.timer_set) { ++ return; ++ } ++ ++ if (qc->close.posted) { ++ ngx_delete_posted_event(&qc->close); ++ } ++ ++ ngx_quic_close_sockets(c); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed"); ++ ++ /* may be tested from SSL callback during SSL shutdown */ ++ c->udp = NULL; ++ ++quic_done: ++ ++ if (c->ssl) { ++ (void) ngx_ssl_shutdown(c); ++ } ++ ++ if (c->read->timer_set) { ++ ngx_del_timer(c->read); ++ } ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_active, -1); ++#endif ++ ++ c->destroyed = 1; ++ ++ pool = c->pool; ++ ++ ngx_close_connection(c); ++ ++ ngx_destroy_pool(pool); ++} ++ ++ ++void ++ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, ++ const char *reason) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc->closing) { ++ return; ++ } ++ ++ qc->error = err; ++ qc->error_reason = reason; ++ qc->error_app = 1; ++ qc->error_ftype = 0; ++ ++ ngx_post_event(&qc->close, &ngx_posted_events); ++} ++ ++ ++void ++ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, ++ const char *reason) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ qc->shutdown = 1; ++ qc->shutdown_code = err; ++ qc->shutdown_reason = reason; ++ ++ ngx_quic_shutdown_quic(c); ++} ++ ++ ++static void ++ngx_quic_close_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); ++ ++ c = ev->data; ++ ++ ngx_quic_close_connection(c, NGX_OK); ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, ++ ngx_quic_conf_t *conf) ++{ ++ size_t size; ++ u_char *p, *start; ++ ngx_int_t rc; ++ ngx_uint_t good; ++ ngx_quic_path_t *path; ++ ngx_quic_header_t pkt; ++ ngx_quic_connection_t *qc; ++ ++ good = 0; ++ path = NULL; ++ ++ size = b->last - b->pos; ++ ++ p = start = b->pos; ++ ++ while (p < b->last) { ++ ++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); ++ pkt.raw = b; ++ pkt.data = p; ++ pkt.len = b->last - p; ++ pkt.log = c->log; ++ pkt.first = (p == start) ? 1 : 0; ++ pkt.path = path; ++ pkt.flags = p[0]; ++ pkt.raw->pos++; ++ ++ rc = ngx_quic_handle_packet(c, conf, &pkt); ++ ++#if (NGX_DEBUG) ++ if (pkt.parsed) { ++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet done rc:%i level:%s" ++ " decr:%d pn:%L perr:%ui", ++ rc, ngx_quic_level_name(pkt.level), ++ pkt.decrypted, pkt.pn, pkt.error); ++ } else { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet done rc:%i parse failed", rc); ++ } ++#endif ++ ++ if (rc == NGX_ERROR || rc == NGX_DONE) { ++ return rc; ++ } ++ ++ if (rc == NGX_OK) { ++ good = 1; ++ } ++ ++ path = pkt.path; /* preserve packet path from 1st packet */ ++ ++ /* NGX_OK || NGX_DECLINED */ ++ ++ /* ++ * we get NGX_DECLINED when there are no keys [yet] available ++ * to decrypt packet. ++ * Instead of queueing it, we ignore it and rely on the sender's ++ * retransmission: ++ * ++ * RFC 9000, 12.2. Coalescing Packets ++ * ++ * For example, if decryption fails (because the keys are ++ * not available or for any other reason), the receiver MAY either ++ * discard or buffer the packet for later processing and MUST ++ * attempt to process the remaining packets. ++ * ++ * We also skip packets that don't match connection state ++ * or cannot be parsed properly. ++ */ ++ ++ /* b->pos is at header end, adjust by actual packet length */ ++ b->pos = pkt.data + pkt.len; ++ ++ p = b->pos; ++ } ++ ++ if (!good) { ++ return NGX_DONE; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc) { ++ qc->received += size; ++ ++ if ((uint64_t) (c->sent + qc->received) / 8 > ++ (qc->streams.sent + qc->streams.recv_last) + 1048576) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); ++ ++ qc->error = NGX_QUIC_ERR_NO_ERROR; ++ qc->error_reason = "QUIC flood detected"; ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ++ ngx_quic_header_t *pkt) ++{ ++ ngx_int_t rc; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ c->log->action = "parsing quic packet"; ++ ++ rc = ngx_quic_parse_packet(pkt); ++ ++ if (rc == NGX_ERROR) { ++ return NGX_DECLINED; ++ } ++ ++ pkt->parsed = 1; ++ ++ c->log->action = "handling quic packet"; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet rx dcid len:%uz %xV", ++ pkt->dcid.len, &pkt->dcid); ++ ++#if (NGX_DEBUG) ++ if (pkt->level != ssl_encryption_application) { ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet rx scid len:%uz %xV", ++ pkt->scid.len, &pkt->scid); ++ } ++ ++ if (pkt->level == ssl_encryption_initial) { ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic address validation token len:%uz %xV", ++ pkt->token.len, &pkt->token); ++ } ++#endif ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc) { ++ ++ if (rc == NGX_ABORT) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic unsupported version: 0x%xD", pkt->version); ++ return NGX_DECLINED; ++ } ++ ++ if (pkt->level != ssl_encryption_application) { ++ ++ if (pkt->version != qc->version) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic version mismatch: 0x%xD", pkt->version); ++ return NGX_DECLINED; ++ } ++ ++ if (pkt->first) { ++ qsock = ngx_quic_get_socket(c); ++ ++ if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, ++ qc->path->sockaddr, qc->path->socklen, 1) ++ != NGX_OK) ++ { ++ /* packet comes from unknown path, possibly migration */ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic too early migration attempt"); ++ return NGX_DONE; ++ } ++ } ++ ++ if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { ++ return NGX_DECLINED; ++ } ++ ++ } ++ ++ rc = ngx_quic_handle_payload(c, pkt); ++ ++ if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) { ++ if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic stateless reset packet detected"); ++ ++ qc->draining = 1; ++ ngx_quic_close_connection(c, NGX_OK); ++ ++ return NGX_OK; ++ } ++ } ++ ++ return rc; ++ } ++ ++ /* packet does not belong to a connection */ ++ ++ if (rc == NGX_ABORT) { ++ return ngx_quic_negotiate_version(c, pkt); ++ } ++ ++ if (pkt->level == ssl_encryption_application) { ++ return ngx_quic_send_stateless_reset(c, conf, pkt); ++ } ++ ++ if (pkt->level != ssl_encryption_initial) { ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic expected initial, got handshake"); ++ return NGX_ERROR; ++ } ++ ++ c->log->action = "handling initial packet"; ++ ++ if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { ++ /* RFC 9000, 7.2. Negotiating Connection IDs */ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic too short dcid in initial" ++ " packet: len:%i", pkt->dcid.len); ++ return NGX_ERROR; ++ } ++ ++ /* process retry and initialize connection IDs */ ++ ++ if (pkt->token.len) { ++ ++ rc = ngx_quic_validate_token(c, conf->av_token_key, pkt); ++ ++ if (rc == NGX_ERROR) { ++ /* internal error */ ++ return NGX_ERROR; ++ ++ } else if (rc == NGX_ABORT) { ++ /* token cannot be decrypted */ ++ return ngx_quic_send_early_cc(c, pkt, ++ NGX_QUIC_ERR_INVALID_TOKEN, ++ "cannot decrypt token"); ++ } else if (rc == NGX_DECLINED) { ++ /* token is invalid */ ++ ++ if (pkt->retried) { ++ /* invalid address validation token */ ++ return ngx_quic_send_early_cc(c, pkt, ++ NGX_QUIC_ERR_INVALID_TOKEN, ++ "invalid address validation token"); ++ } else if (conf->retry) { ++ /* invalid NEW_TOKEN */ ++ return ngx_quic_send_retry(c, conf, pkt); ++ } ++ } ++ ++ /* NGX_OK */ ++ ++ } else if (conf->retry) { ++ return ngx_quic_send_retry(c, conf, pkt); ++ ++ } else { ++ pkt->odcid = pkt->dcid; ++ } ++ ++ if (ngx_terminate || ngx_exiting) { ++ if (conf->retry) { ++ return ngx_quic_send_retry(c, conf, pkt); ++ } ++ ++ return NGX_ERROR; ++ } ++ ++ c->log->action = "creating quic connection"; ++ ++ qc = ngx_quic_new_connection(c, conf, pkt); ++ if (qc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ return ngx_quic_handle_payload(c, pkt); ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ ngx_int_t rc; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ qc->error = (ngx_uint_t) -1; ++ qc->error_reason = 0; ++ ++ c->log->action = "decrypting packet"; ++ ++ if (!ngx_quic_keys_available(qc->keys, pkt->level)) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic no %s keys, ignoring packet", ++ ngx_quic_level_name(pkt->level)); ++ return NGX_DECLINED; ++ } ++ ++#if !defined (OPENSSL_IS_BORINGSSL) ++ /* OpenSSL provides read keys for an application level before it's ready */ ++ ++ if (pkt->level == ssl_encryption_application ++ && SSL_quic_read_level(c->ssl->connection) ++ < ssl_encryption_application) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic no %s keys ready, ignoring packet", ++ ngx_quic_level_name(pkt->level)); ++ return NGX_DECLINED; ++ } ++#endif ++ ++ pkt->keys = qc->keys; ++ pkt->key_phase = qc->key_phase; ++ pkt->plaintext = buf; ++ ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ ++ rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); ++ if (rc != NGX_OK) { ++ qc->error = pkt->error; ++ qc->error_reason = "failed to decrypt packet"; ++ return rc; ++ } ++ ++ pkt->decrypted = 1; ++ ++ c->log->action = "handling decrypted packet"; ++ ++ if (pkt->path == NULL) { ++ rc = ngx_quic_set_path(c, pkt); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ } ++ ++ if (c->ssl == NULL) { ++ if (ngx_quic_init_connection(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (pkt->level == ssl_encryption_handshake) { ++ /* ++ * RFC 9001, 4.9.1. Discarding Initial Keys ++ * ++ * The successful use of Handshake packets indicates ++ * that no more Initial packets need to be exchanged ++ */ ++ ngx_quic_discard_ctx(c, ssl_encryption_initial); ++ ++ if (!qc->path->validated) { ++ qc->path->validated = 1; ++ qc->path->limited = 0; ++ ngx_quic_path_dbg(c, "in handshake", qc->path); ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++ } ++ ++ if (qc->closing) { ++ /* ++ * RFC 9000, 10.2. Immediate Close ++ * ++ * ... delayed or reordered packets are properly discarded. ++ * ++ * In the closing state, an endpoint retains only enough information ++ * to generate a packet containing a CONNECTION_CLOSE frame and to ++ * identify packets as belonging to the connection. ++ */ ++ ++ qc->error_level = pkt->level; ++ qc->error = NGX_QUIC_ERR_NO_ERROR; ++ qc->error_reason = "connection is closing, packet discarded"; ++ qc->error_ftype = 0; ++ qc->error_app = 0; ++ ++ return ngx_quic_send_cc(c); ++ } ++ ++ pkt->received = ngx_current_msec; ++ ++ c->log->action = "handling payload"; ++ ++ if (pkt->level != ssl_encryption_application) { ++ return ngx_quic_handle_frames(c, pkt); ++ } ++ ++ if (!pkt->key_update) { ++ return ngx_quic_handle_frames(c, pkt); ++ } ++ ++ /* switch keys and generate next on Key Phase change */ ++ ++ qc->key_phase ^= 1; ++ ngx_quic_keys_switch(c, qc->keys); ++ ++ rc = ngx_quic_handle_frames(c, pkt); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ return ngx_quic_keys_update(c, qc->keys); ++} ++ ++ ++void ++ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ++{ ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (!ngx_quic_keys_available(qc->keys, level)) { ++ return; ++ } ++ ++ ngx_quic_keys_discard(qc->keys, level); ++ ++ qc->pto_count = 0; ++ ++ ctx = ngx_quic_get_send_ctx(qc, level); ++ ++ ngx_quic_free_buffer(c, &ctx->crypto); ++ ++ while (!ngx_queue_empty(&ctx->sent)) { ++ q = ngx_queue_head(&ctx->sent); ++ ngx_queue_remove(q); ++ ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ngx_quic_congestion_ack(c, f); ++ ngx_quic_free_frame(c, f); ++ } ++ ++ while (!ngx_queue_empty(&ctx->frames)) { ++ q = ngx_queue_head(&ctx->frames); ++ ngx_queue_remove(q); ++ ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ngx_quic_free_frame(c, f); ++ } ++ ++ if (level == ssl_encryption_initial) { ++ /* close temporary listener with odcid */ ++ qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN); ++ if (qsock) { ++ ngx_quic_close_socket(c, qsock); ++ } ++ } ++ ++ ctx->send_ack = 0; ++ ++ ngx_quic_set_lost_timer(c); ++} ++ ++ ++static ngx_int_t ++ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) ++{ ++ ngx_queue_t *q; ++ ngx_quic_client_id_t *cid; ++ ++ for (q = ngx_queue_head(&qc->client_ids); ++ q != ngx_queue_sentinel(&qc->client_ids); ++ q = ngx_queue_next(q)) ++ { ++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); ++ ++ if (pkt->scid.len == cid->len ++ && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0) ++ { ++ return NGX_OK; ++ } ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ u_char *end, *p; ++ ssize_t len; ++ ngx_buf_t buf; ++ ngx_uint_t do_close, nonprobing; ++ ngx_chain_t chain; ++ ngx_quic_frame_t frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ p = pkt->payload.data; ++ end = p + pkt->payload.len; ++ ++ do_close = 0; ++ nonprobing = 0; ++ ++ while (p < end) { ++ ++ c->log->action = "parsing frames"; ++ ++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); ++ ngx_memzero(&buf, sizeof(ngx_buf_t)); ++ buf.temporary = 1; ++ ++ chain.buf = &buf; ++ chain.next = NULL; ++ frame.data = &chain; ++ ++ len = ngx_quic_parse_frame(pkt, p, end, &frame); ++ ++ if (len < 0) { ++ qc->error = pkt->error; ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_log_frame(c->log, &frame, 0); ++ ++ c->log->action = "handling frames"; ++ ++ p += len; ++ ++ switch (frame.type) { ++ /* probing frames */ ++ case NGX_QUIC_FT_PADDING: ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ break; ++ ++ /* non-probing frames */ ++ default: ++ nonprobing = 1; ++ break; ++ } ++ ++ switch (frame.type) { ++ ++ case NGX_QUIC_FT_ACK: ++ if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ continue; ++ ++ case NGX_QUIC_FT_PADDING: ++ /* no action required */ ++ continue; ++ ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP: ++ do_close = 1; ++ continue; ++ } ++ ++ /* got there with ack-eliciting packet */ ++ pkt->need_ack = 1; ++ ++ switch (frame.type) { ++ ++ case NGX_QUIC_FT_CRYPTO: ++ ++ if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PING: ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ ++ if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ ++ if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAMS_BLOCKED: ++ case NGX_QUIC_FT_STREAMS_BLOCKED2: ++ ++ if (ngx_quic_handle_streams_blocked_frame(c, pkt, ++ &frame.u.streams_blocked) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_DATA_BLOCKED: ++ ++ if (ngx_quic_handle_data_blocked_frame(c, pkt, ++ &frame.u.data_blocked) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED: ++ ++ if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, ++ &frame.u.stream_data_blocked) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ ++ if (ngx_quic_handle_max_stream_data_frame(c, pkt, ++ &frame.u.max_stream_data) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ ++ if (ngx_quic_handle_reset_stream_frame(c, pkt, ++ &frame.u.reset_stream) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STOP_SENDING: ++ ++ if (ngx_quic_handle_stop_sending_frame(c, pkt, ++ &frame.u.stop_sending) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ case NGX_QUIC_FT_MAX_STREAMS2: ++ ++ if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ ++ if (ngx_quic_handle_path_challenge_frame(c, pkt, ++ &frame.u.path_challenge) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ ++ if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ ++ if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID: ++ ++ if (ngx_quic_handle_retire_connection_id_frame(c, ++ &frame.u.retire_cid) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ break; ++ ++ default: ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic missing frame handler"); ++ return NGX_ERROR; ++ } ++ } ++ ++ if (p != end) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic trailing garbage in payload:%ui bytes", end - p); ++ ++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ return NGX_ERROR; ++ } ++ ++ if (do_close) { ++ qc->draining = 1; ++ ngx_quic_close_connection(c, NGX_OK); ++ } ++ ++ if (pkt->path != qc->path && nonprobing) { ++ ++ /* ++ * RFC 9000, 9.2. Initiating Connection Migration ++ * ++ * An endpoint can migrate a connection to a new local ++ * address by sending packets containing non-probing frames ++ * from that address. ++ */ ++ if (ngx_quic_handle_migration(c, pkt) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_push_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler"); ++ ++ c = ev->data; ++ ++ if (ngx_quic_output(c) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ ++ ngx_quic_connstate_dbg(c); ++} ++ ++ ++void ++ngx_quic_shutdown_quic(ngx_connection_t *c) ++{ ++ ngx_quic_connection_t *qc; ++ ++ if (c->reusable) { ++ qc = ngx_quic_get_connection(c); ++ ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); ++ } ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,131 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 ++ ++#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 ++#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 ++#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32 ++#define NGX_QUIC_SR_KEY_LEN 32 ++#define NGX_QUIC_AV_KEY_LEN 32 ++ ++#define NGX_QUIC_SR_TOKEN_LEN 16 ++ ++#define NGX_QUIC_MIN_INITIAL_SIZE 1200 ++ ++#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 ++#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 ++ ++ ++typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c); ++typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); ++ ++ ++typedef enum { ++ NGX_QUIC_STREAM_SEND_READY = 0, ++ NGX_QUIC_STREAM_SEND_SEND, ++ NGX_QUIC_STREAM_SEND_DATA_SENT, ++ NGX_QUIC_STREAM_SEND_DATA_RECVD, ++ NGX_QUIC_STREAM_SEND_RESET_SENT, ++ NGX_QUIC_STREAM_SEND_RESET_RECVD ++} ngx_quic_stream_send_state_e; ++ ++ ++typedef enum { ++ NGX_QUIC_STREAM_RECV_RECV = 0, ++ NGX_QUIC_STREAM_RECV_SIZE_KNOWN, ++ NGX_QUIC_STREAM_RECV_DATA_RECVD, ++ NGX_QUIC_STREAM_RECV_DATA_READ, ++ NGX_QUIC_STREAM_RECV_RESET_RECVD, ++ NGX_QUIC_STREAM_RECV_RESET_READ ++} ngx_quic_stream_recv_state_e; ++ ++ ++typedef struct { ++ uint64_t size; ++ uint64_t offset; ++ uint64_t last_offset; ++ ngx_chain_t *chain; ++ ngx_chain_t *last_chain; ++} ngx_quic_buffer_t; ++ ++ ++typedef struct { ++ ngx_ssl_t *ssl; ++ ++ ngx_flag_t retry; ++ ngx_flag_t gso_enabled; ++ ngx_flag_t disable_active_migration; ++ ngx_msec_t timeout; ++ ngx_str_t host_key; ++ size_t mtu; ++ size_t stream_buffer_size; ++ ngx_uint_t max_concurrent_streams_bidi; ++ ngx_uint_t max_concurrent_streams_uni; ++ ngx_uint_t active_connection_id_limit; ++ ngx_int_t stream_close_code; ++ ngx_int_t stream_reject_code_uni; ++ ngx_int_t stream_reject_code_bidi; ++ ++ ngx_quic_init_pt init; ++ ngx_quic_shutdown_pt shutdown; ++ ++ u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; ++ u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; ++} ngx_quic_conf_t; ++ ++ ++struct ngx_quic_stream_s { ++ ngx_rbtree_node_t node; ++ ngx_queue_t queue; ++ ngx_connection_t *parent; ++ ngx_connection_t *connection; ++ uint64_t id; ++ uint64_t sent; ++ uint64_t acked; ++ uint64_t send_max_data; ++ uint64_t send_offset; ++ uint64_t send_final_size; ++ uint64_t recv_max_data; ++ uint64_t recv_offset; ++ uint64_t recv_window; ++ uint64_t recv_last; ++ uint64_t recv_final_size; ++ ngx_quic_buffer_t send; ++ ngx_quic_buffer_t recv; ++ ngx_quic_stream_send_state_e send_state; ++ ngx_quic_stream_recv_state_e recv_state; ++ unsigned cancelable:1; ++ unsigned fin_acked:1; ++}; ++ ++ ++void ngx_quic_recvmsg(ngx_event_t *ev); ++void ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, ++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); ++void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); ++ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); ++void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, ++ const char *reason); ++void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, ++ const char *reason); ++ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); ++ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how); ++void ngx_quic_cancelable_stream(ngx_connection_t *c); ++ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, ++ ngx_str_t *dcid); ++ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, ++ ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); ++ ++#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_ack.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_ack.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,1192 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_QUIC_MAX_ACK_GAP 2 ++ ++/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */ ++#define NGX_QUIC_PKT_THR 3 /* packets */ ++/* RFC 9002, 6.1.2. Time Threshold: kGranularity */ ++#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ ++ ++/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */ ++#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3 ++ ++ ++/* send time of ACK'ed packets */ ++typedef struct { ++ ngx_msec_t max_pn; ++ ngx_msec_t oldest; ++ ngx_msec_t newest; ++} ngx_quic_ack_stat_t; ++ ++ ++static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc); ++static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, ++ enum ssl_encryption_level_t level, ngx_msec_t send_time); ++static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ++ ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, ++ ngx_quic_ack_stat_t *st); ++static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, ++ ngx_quic_send_ctx_t *ctx, uint64_t pn); ++static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, ++ ngx_quic_ack_stat_t *st); ++static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c); ++static void ngx_quic_persistent_congestion(ngx_connection_t *c); ++static void ngx_quic_congestion_lost(ngx_connection_t *c, ++ ngx_quic_frame_t *frame); ++static void ngx_quic_lost_handler(ngx_event_t *ev); ++ ++ ++/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */ ++static ngx_inline ngx_msec_t ++ngx_quic_lost_threshold(ngx_quic_connection_t *qc) ++{ ++ ngx_msec_t thr; ++ ++ thr = ngx_max(qc->latest_rtt, qc->avg_rtt); ++ thr += thr >> 3; ++ ++ return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ++ ngx_quic_frame_t *f) ++{ ++ ssize_t n; ++ u_char *pos, *end; ++ uint64_t min, max, gap, range; ++ ngx_uint_t i; ++ ngx_quic_ack_stat_t send_time; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_ack_frame_t *ack; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_handle_ack_frame level:%d", pkt->level); ++ ++ ack = &f->u.ack; ++ ++ /* ++ * RFC 9000, 19.3.1. ACK Ranges ++ * ++ * If any computed packet number is negative, an endpoint MUST ++ * generate a connection error of type FRAME_ENCODING_ERROR. ++ */ ++ ++ if (ack->first_range > ack->largest) { ++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic invalid first range in ack frame"); ++ return NGX_ERROR; ++ } ++ ++ min = ack->largest - ack->first_range; ++ max = ack->largest; ++ ++ send_time.oldest = NGX_TIMER_INFINITE; ++ send_time.newest = NGX_TIMER_INFINITE; ++ ++ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ /* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */ ++ if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { ++ ctx->largest_ack = max; ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic updated largest received ack:%uL", max); ++ ++ /* ++ * RFC 9002, 5.1. Generating RTT Samples ++ * ++ * An endpoint generates an RTT sample on receiving an ++ * ACK frame that meets the following two conditions: ++ * ++ * - the largest acknowledged packet number is newly acknowledged ++ * - at least one of the newly acknowledged packets was ack-eliciting. ++ */ ++ ++ if (send_time.max_pn != NGX_TIMER_INFINITE) { ++ ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn); ++ } ++ } ++ ++ if (f->data) { ++ pos = f->data->buf->pos; ++ end = f->data->buf->last; ++ ++ } else { ++ pos = NULL; ++ end = NULL; ++ } ++ ++ for (i = 0; i < ack->range_count; i++) { ++ ++ n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); ++ if (n == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ pos += n; ++ ++ if (gap + 2 > min) { ++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic invalid range:%ui in ack frame", i); ++ return NGX_ERROR; ++ } ++ ++ max = min - gap - 2; ++ ++ if (range > max) { ++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic invalid range:%ui in ack frame", i); ++ return NGX_ERROR; ++ } ++ ++ min = max - range; ++ ++ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ } ++ ++ return ngx_quic_detect_lost(c, &send_time); ++} ++ ++ ++static void ++ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, ++ enum ssl_encryption_level_t level, ngx_msec_t send_time) ++{ ++ ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ latest_rtt = ngx_current_msec - send_time; ++ qc->latest_rtt = latest_rtt; ++ ++ if (qc->min_rtt == NGX_TIMER_INFINITE) { ++ qc->min_rtt = latest_rtt; ++ qc->avg_rtt = latest_rtt; ++ qc->rttvar = latest_rtt / 2; ++ qc->first_rtt = ngx_current_msec; ++ ++ } else { ++ qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); ++ ++ ack_delay = (ack->delay << qc->ctp.ack_delay_exponent) / 1000; ++ ++ if (c->ssl->handshaked) { ++ ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); ++ } ++ ++ adjusted_rtt = latest_rtt; ++ ++ if (qc->min_rtt + ack_delay < latest_rtt) { ++ adjusted_rtt -= ack_delay; ++ } ++ ++ qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3); ++ rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); ++ qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2); ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic rtt sample latest:%M min:%M avg:%M var:%M", ++ latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); ++} ++ ++ ++static ngx_int_t ++ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st) ++{ ++ ngx_uint_t found; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ st->max_pn = NGX_TIMER_INFINITE; ++ found = 0; ++ ++ q = ngx_queue_head(&ctx->sent); ++ ++ while (q != ngx_queue_sentinel(&ctx->sent)) { ++ ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ q = ngx_queue_next(q); ++ ++ if (f->pnum > max) { ++ break; ++ } ++ ++ if (f->pnum >= min) { ++ ngx_quic_congestion_ack(c, f); ++ ++ switch (f->type) { ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ case NGX_QUIC_FT_RESET_STREAM: ++ ngx_quic_handle_stream_ack(c, f); ++ break; ++ } ++ ++ if (f->pnum == max) { ++ st->max_pn = f->last; ++ } ++ ++ /* save earliest and latest send times of frames ack'ed */ ++ if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) { ++ st->oldest = f->last; ++ } ++ ++ if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) { ++ st->newest = f->last; ++ } ++ ++ ngx_queue_remove(&f->queue); ++ ngx_quic_free_frame(c, f); ++ found = 1; ++ } ++ } ++ ++ if (!found) { ++ ++ if (max < ctx->pnum) { ++ /* duplicate ACK or ACK for non-ack-eliciting frame */ ++ return NGX_OK; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic ACK for the packet not sent"); ++ ++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ qc->error_ftype = NGX_QUIC_FT_ACK; ++ qc->error_reason = "unknown packet number"; ++ ++ return NGX_ERROR; ++ } ++ ++ if (!qc->push.timer_set) { ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++ ++ qc->pto_count = 0; ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ++{ ++ ngx_uint_t blocked; ++ ngx_msec_t timer; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ if (f->plen == 0) { ++ return; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ ++ blocked = (cg->in_flight >= cg->window) ? 1 : 0; ++ ++ cg->in_flight -= f->plen; ++ ++ timer = f->last - cg->recovery_start; ++ ++ if ((ngx_msec_int_t) timer <= 0) { ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion ack recovery win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++ goto done; ++ } ++ ++ if (cg->window < cg->ssthresh) { ++ cg->window += f->plen; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion slow start win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++ } else { ++ cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion avoidance win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ } ++ ++ /* prevent recovery_start from wrapping */ ++ ++ timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; ++ ++ if ((ngx_msec_int_t) timer < 0) { ++ cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; ++ } ++ ++done: ++ ++ if (blocked && cg->in_flight < cg->window) { ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++} ++ ++ ++static void ++ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t pn) ++{ ++ uint64_t base; ++ ngx_uint_t i, smallest, largest; ++ ngx_quic_ack_range_t *r; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" ++ " fr:%uL nranges:%ui", pn, ctx->largest_range, ++ ctx->first_range, ctx->nranges); ++ ++ base = ctx->largest_range; ++ ++ if (base == NGX_QUIC_UNSET_PN) { ++ return; ++ } ++ ++ if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { ++ ctx->pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ ++ largest = base; ++ smallest = largest - ctx->first_range; ++ ++ if (pn >= largest) { ++ ctx->largest_range = NGX_QUIC_UNSET_PN; ++ ctx->first_range = 0; ++ ctx->nranges = 0; ++ return; ++ } ++ ++ if (pn >= smallest) { ++ ctx->first_range = largest - pn - 1; ++ ctx->nranges = 0; ++ return; ++ } ++ ++ for (i = 0; i < ctx->nranges; i++) { ++ r = &ctx->ranges[i]; ++ ++ largest = smallest - r->gap - 2; ++ smallest = largest - r->range; ++ ++ if (pn >= largest) { ++ ctx->nranges = i; ++ return; ++ } ++ if (pn >= smallest) { ++ r->range = largest - pn - 1; ++ ctx->nranges = i + 1; ++ return; ++ } ++ } ++} ++ ++ ++static ngx_int_t ++ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st) ++{ ++ ngx_uint_t i, nlost; ++ ngx_msec_t now, wait, thr, oldest, newest; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *start; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ now = ngx_current_msec; ++ thr = ngx_quic_lost_threshold(qc); ++ ++ /* send time of lost packets across all send contexts */ ++ oldest = NGX_TIMER_INFINITE; ++ newest = NGX_TIMER_INFINITE; ++ ++ nlost = 0; ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ++ ctx = &qc->send_ctx[i]; ++ ++ if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { ++ continue; ++ } ++ ++ while (!ngx_queue_empty(&ctx->sent)) { ++ ++ q = ngx_queue_head(&ctx->sent); ++ start = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (start->pnum > ctx->largest_ack) { ++ break; ++ } ++ ++ wait = start->last + thr - now; ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", ++ start->pnum, thr, (ngx_int_t) wait, start->level); ++ ++ if ((ngx_msec_int_t) wait > 0 ++ && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) ++ { ++ break; ++ } ++ ++ if (start->last > qc->first_rtt) { ++ ++ if (oldest == NGX_TIMER_INFINITE || start->last < oldest) { ++ oldest = start->last; ++ } ++ ++ if (newest == NGX_TIMER_INFINITE || start->last > newest) { ++ newest = start->last; ++ } ++ ++ nlost++; ++ } ++ ++ ngx_quic_resend_frames(c, ctx); ++ } ++ } ++ ++ ++ /* RFC 9002, 7.6.2. Establishing Persistent Congestion */ ++ ++ /* ++ * Once acknowledged, packets are no longer tracked. Thus no send time ++ * information is available for such packets. This limits persistent ++ * congestion algorithm to packets mentioned within ACK ranges of the ++ * latest ACK frame. ++ */ ++ ++ if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) { ++ ++ if (newest - oldest > ngx_quic_pcg_duration(c)) { ++ ngx_quic_persistent_congestion(c); ++ } ++ } ++ ++ ngx_quic_set_lost_timer(c); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_msec_t ++ngx_quic_pcg_duration(ngx_connection_t *c) ++{ ++ ngx_msec_t duration; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ duration = qc->avg_rtt; ++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); ++ duration += qc->ctp.max_ack_delay; ++ duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR; ++ ++ return duration; ++} ++ ++ ++static void ++ngx_quic_persistent_congestion(ngx_connection_t *c) ++{ ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ ++ cg->recovery_start = ngx_current_msec; ++ cg->window = qc->tp.max_udp_payload_size * 2; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic persistent congestion win:%uz", cg->window); ++} ++ ++ ++void ++ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f, *start; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ q = ngx_queue_head(&ctx->sent); ++ start = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic resend packet pnum:%uL", start->pnum); ++ ++ ngx_quic_congestion_lost(c, start); ++ ++ do { ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->pnum != start->pnum) { ++ break; ++ } ++ ++ q = ngx_queue_next(q); ++ ++ ngx_queue_remove(&f->queue); ++ ++ switch (f->type) { ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ if (ctx->level == ssl_encryption_application) { ++ /* force generation of most recent acknowledgment */ ++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; ++ } ++ ++ ngx_quic_free_frame(c, f); ++ break; ++ ++ case NGX_QUIC_FT_PING: ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ ngx_quic_free_frame(c, f); ++ break; ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ f->u.max_data.max_data = qc->streams.recv_max_data; ++ ngx_quic_queue_frame(qc, f); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ case NGX_QUIC_FT_MAX_STREAMS2: ++ f->u.max_streams.limit = f->u.max_streams.bidi ++ ? qc->streams.client_max_streams_bidi ++ : qc->streams.client_max_streams_uni; ++ ngx_quic_queue_frame(qc, f); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ qs = ngx_quic_find_stream(&qc->streams.tree, ++ f->u.max_stream_data.id); ++ if (qs == NULL) { ++ ngx_quic_free_frame(c, f); ++ break; ++ } ++ ++ f->u.max_stream_data.limit = qs->recv_max_data; ++ ngx_quic_queue_frame(qc, f); ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); ++ ++ if (qs) { ++ if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT ++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) ++ { ++ ngx_quic_free_frame(c, f); ++ break; ++ } ++ } ++ ++ /* fall through */ ++ ++ default: ++ ngx_queue_insert_tail(&ctx->frames, &f->queue); ++ } ++ ++ } while (q != ngx_queue_sentinel(&ctx->sent)); ++ ++ if (qc->closing) { ++ return; ++ } ++ ++ ngx_post_event(&qc->push, &ngx_posted_events); ++} ++ ++ ++static void ++ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) ++{ ++ ngx_uint_t blocked; ++ ngx_msec_t timer; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ if (f->plen == 0) { ++ return; ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ ++ blocked = (cg->in_flight >= cg->window) ? 1 : 0; ++ ++ cg->in_flight -= f->plen; ++ f->plen = 0; ++ ++ timer = f->last - cg->recovery_start; ++ ++ if ((ngx_msec_int_t) timer <= 0) { ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion lost recovery win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++ goto done; ++ } ++ ++ cg->recovery_start = ngx_current_msec; ++ cg->window /= 2; ++ ++ if (cg->window < qc->tp.max_udp_payload_size * 2) { ++ cg->window = qc->tp.max_udp_payload_size * 2; ++ } ++ ++ cg->ssthresh = cg->window; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion lost win:%uz ss:%z if:%uz", ++ cg->window, cg->ssthresh, cg->in_flight); ++ ++done: ++ ++ if (blocked && cg->in_flight < cg->window) { ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++} ++ ++ ++void ++ngx_quic_set_lost_timer(ngx_connection_t *c) ++{ ++ ngx_uint_t i; ++ ngx_msec_t now; ++ ngx_queue_t *q; ++ ngx_msec_int_t lost, pto, w; ++ ngx_quic_frame_t *f; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ now = ngx_current_msec; ++ ++ lost = -1; ++ pto = -1; ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ctx = &qc->send_ctx[i]; ++ ++ if (ngx_queue_empty(&ctx->sent)) { ++ continue; ++ } ++ ++ if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { ++ q = ngx_queue_head(&ctx->sent); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); ++ ++ if (f->pnum <= ctx->largest_ack) { ++ if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { ++ w = 0; ++ } ++ ++ if (lost == -1 || w < lost) { ++ lost = w; ++ } ++ } ++ } ++ ++ q = ngx_queue_last(&ctx->sent); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) ++ - now); ++ ++ if (w < 0) { ++ w = 0; ++ } ++ ++ if (pto == -1 || w < pto) { ++ pto = w; ++ } ++ } ++ ++ if (qc->pto.timer_set) { ++ ngx_del_timer(&qc->pto); ++ } ++ ++ if (lost != -1) { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic lost timer lost:%M", lost); ++ ++ qc->pto.handler = ngx_quic_lost_handler; ++ ngx_add_timer(&qc->pto, lost); ++ return; ++ } ++ ++ if (pto != -1) { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic lost timer pto:%M", pto); ++ ++ qc->pto.handler = ngx_quic_pto_handler; ++ ngx_add_timer(&qc->pto, pto); ++ return; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); ++} ++ ++ ++ngx_msec_t ++ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ ngx_msec_t duration; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */ ++ ++ duration = qc->avg_rtt; ++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); ++ ++ if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { ++ duration += qc->ctp.max_ack_delay; ++ } ++ ++ return duration; ++} ++ ++ ++static ++void ngx_quic_lost_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); ++ ++ c = ev->data; ++ ++ if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ } ++ ++ ngx_quic_connstate_dbg(c); ++} ++ ++ ++void ++ngx_quic_pto_handler(ngx_event_t *ev) ++{ ++ ngx_uint_t i; ++ ngx_msec_t now; ++ ngx_queue_t *q, *next; ++ ngx_connection_t *c; ++ ngx_quic_frame_t *f; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); ++ ++ c = ev->data; ++ qc = ngx_quic_get_connection(c); ++ now = ngx_current_msec; ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ++ ctx = &qc->send_ctx[i]; ++ ++ if (ngx_queue_empty(&ctx->sent)) { ++ continue; ++ } ++ ++ q = ngx_queue_head(&ctx->sent); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->pnum <= ctx->largest_ack ++ && ctx->largest_ack != NGX_QUIC_UNSET_PN) ++ { ++ continue; ++ } ++ ++ if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) ++ - now) > 0) ++ { ++ continue; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic pto %s pto_count:%ui", ++ ngx_quic_level_name(ctx->level), qc->pto_count); ++ ++ for (q = ngx_queue_head(&ctx->frames); ++ q != ngx_queue_sentinel(&ctx->frames); ++ /* void */) ++ { ++ next = ngx_queue_next(q); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->type == NGX_QUIC_FT_PING) { ++ ngx_queue_remove(q); ++ ngx_quic_free_frame(c, f); ++ } ++ ++ q = next; ++ } ++ ++ for (q = ngx_queue_head(&ctx->sent); ++ q != ngx_queue_sentinel(&ctx->sent); ++ /* void */) ++ { ++ next = ngx_queue_next(q); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->type == NGX_QUIC_FT_PING) { ++ ngx_quic_congestion_lost(c, f); ++ ngx_queue_remove(q); ++ ngx_quic_free_frame(c, f); ++ } ++ ++ q = next; ++ } ++ ++ /* enforce 2 udp datagrams */ ++ ++ f = ngx_quic_alloc_frame(c); ++ if (f == NULL) { ++ break; ++ } ++ ++ f->level = ctx->level; ++ f->type = NGX_QUIC_FT_PING; ++ f->flush = 1; ++ ++ ngx_quic_queue_frame(qc, f); ++ ++ f = ngx_quic_alloc_frame(c); ++ if (f == NULL) { ++ break; ++ } ++ ++ f->level = ctx->level; ++ f->type = NGX_QUIC_FT_PING; ++ ++ ngx_quic_queue_frame(qc, f); ++ } ++ ++ qc->pto_count++; ++ ++ ngx_quic_connstate_dbg(c); ++} ++ ++ ++ngx_int_t ++ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ uint64_t base, largest, smallest, gs, ge, gap, range, pn; ++ uint64_t prev_pending; ++ ngx_uint_t i, nr; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_ack_range_t *r; ++ ngx_quic_connection_t *qc; ++ ++ c->log->action = "preparing ack"; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" ++ " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, ++ ctx->first_range, ctx->nranges); ++ ++ prev_pending = ctx->pending_ack; ++ ++ if (pkt->need_ack) { ++ ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ ++ if (ctx->send_ack == 0) { ++ ctx->ack_delay_start = ngx_current_msec; ++ } ++ ++ ctx->send_ack++; ++ ++ if (ctx->pending_ack == NGX_QUIC_UNSET_PN ++ || ctx->pending_ack < pkt->pn) ++ { ++ ctx->pending_ack = pkt->pn; ++ } ++ } ++ ++ base = ctx->largest_range; ++ pn = pkt->pn; ++ ++ if (base == NGX_QUIC_UNSET_PN) { ++ ctx->largest_range = pn; ++ ctx->largest_received = pkt->received; ++ return NGX_OK; ++ } ++ ++ if (base == pn) { ++ return NGX_OK; ++ } ++ ++ largest = base; ++ smallest = largest - ctx->first_range; ++ ++ if (pn > base) { ++ ++ if (pn - base == 1) { ++ ctx->first_range++; ++ ctx->largest_range = pn; ++ ctx->largest_received = pkt->received; ++ ++ return NGX_OK; ++ ++ } else { ++ /* new gap in front of current largest */ ++ ++ /* no place for new range, send current range as is */ ++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) { ++ ++ if (prev_pending != NGX_QUIC_UNSET_PN) { ++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) { ++ ctx->pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ } ++ ++ gap = pn - base - 2; ++ range = ctx->first_range; ++ ++ ctx->first_range = 0; ++ ctx->largest_range = pn; ++ ctx->largest_received = pkt->received; ++ ++ /* packet is out of order, force send */ ++ if (pkt->need_ack) { ++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; ++ } ++ ++ i = 0; ++ ++ goto insert; ++ } ++ } ++ ++ /* pn < base, perform lookup in existing ranges */ ++ ++ /* packet is out of order */ ++ if (pkt->need_ack) { ++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; ++ } ++ ++ if (pn >= smallest && pn <= largest) { ++ return NGX_OK; ++ } ++ ++#if (NGX_SUPPRESS_WARN) ++ r = NULL; ++#endif ++ ++ for (i = 0; i < ctx->nranges; i++) { ++ r = &ctx->ranges[i]; ++ ++ ge = smallest - 1; ++ gs = ge - r->gap; ++ ++ if (pn >= gs && pn <= ge) { ++ ++ if (gs == ge) { ++ /* gap size is exactly one packet, now filled */ ++ ++ /* data moves to previous range, current is removed */ ++ ++ if (i == 0) { ++ ctx->first_range += r->range + 2; ++ ++ } else { ++ ctx->ranges[i - 1].range += r->range + 2; ++ } ++ ++ nr = ctx->nranges - i - 1; ++ if (nr) { ++ ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], ++ sizeof(ngx_quic_ack_range_t) * nr); ++ } ++ ++ ctx->nranges--; ++ ++ } else if (pn == gs) { ++ /* current gap shrinks from tail (current range grows) */ ++ r->gap--; ++ r->range++; ++ ++ } else if (pn == ge) { ++ /* current gap shrinks from head (previous range grows) */ ++ r->gap--; ++ ++ if (i == 0) { ++ ctx->first_range++; ++ ++ } else { ++ ctx->ranges[i - 1].range++; ++ } ++ ++ } else { ++ /* current gap is split into two parts */ ++ ++ gap = ge - pn - 1; ++ range = 0; ++ ++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) { ++ if (prev_pending != NGX_QUIC_UNSET_PN) { ++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) { ++ ctx->pending_ack = NGX_QUIC_UNSET_PN; ++ } ++ } ++ ++ r->gap = pn - gs - 1; ++ goto insert; ++ } ++ ++ return NGX_OK; ++ } ++ ++ largest = smallest - r->gap - 2; ++ smallest = largest - r->range; ++ ++ if (pn >= smallest && pn <= largest) { ++ /* this packet number is already known */ ++ return NGX_OK; ++ } ++ ++ } ++ ++ if (pn == smallest - 1) { ++ /* extend first or last range */ ++ ++ if (i == 0) { ++ ctx->first_range++; ++ ++ } else { ++ r->range++; ++ } ++ ++ return NGX_OK; ++ } ++ ++ /* nothing found, add new range at the tail */ ++ ++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) { ++ /* packet is too old to keep it */ ++ ++ if (pkt->need_ack) { ++ return ngx_quic_send_ack_range(c, ctx, pn, pn); ++ } ++ ++ return NGX_OK; ++ } ++ ++ gap = smallest - 2 - pn; ++ range = 0; ++ ++insert: ++ ++ if (ctx->nranges < NGX_QUIC_MAX_RANGES) { ++ ctx->nranges++; ++ } ++ ++ ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], ++ sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); ++ ++ ctx->ranges[i].gap = gap; ++ ctx->ranges[i].range = range; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ ngx_msec_t delay; ++ ngx_quic_connection_t *qc; ++ ++ if (!ctx->send_ack) { ++ return NGX_OK; ++ } ++ ++ if (ctx->level == ssl_encryption_application) { ++ ++ delay = ngx_current_msec - ctx->ack_delay_start; ++ qc = ngx_quic_get_connection(c); ++ ++ if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP ++ && delay < qc->tp.max_ack_delay) ++ { ++ if (!qc->push.timer_set && !qc->closing) { ++ ngx_add_timer(&qc->push, ++ qc->tp.max_ack_delay - delay); ++ } ++ ++ return NGX_OK; ++ } ++ } ++ ++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ctx->send_ack = 0; ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_ack.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_ack.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,30 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_frame_t *f); ++ ++void ngx_quic_congestion_ack(ngx_connection_t *c, ++ ngx_quic_frame_t *frame); ++void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); ++void ngx_quic_set_lost_timer(ngx_connection_t *c); ++void ngx_quic_pto_handler(ngx_event_t *ev); ++ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); ++ ++ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, ++ ngx_quic_header_t *pkt); ++ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, ++ ngx_quic_send_ctx_t *ctx); ++ ++#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_bpf.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_bpf.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,657 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++ ++ ++#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS" ++#define NGX_QUIC_BPF_VARSEP ';' ++#define NGX_QUIC_BPF_ADDRSEP '#' ++ ++ ++#define ngx_quic_bpf_get_conf(cycle) \ ++ (ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module) ++ ++#define ngx_quic_bpf_get_old_conf(cycle) \ ++ cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \ ++ : NULL ++ ++#define ngx_core_get_conf(cycle) \ ++ (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module) ++ ++ ++typedef struct { ++ ngx_queue_t queue; ++ int map_fd; ++ ++ struct sockaddr *sockaddr; ++ socklen_t socklen; ++ ngx_uint_t unused; /* unsigned unused:1; */ ++} ngx_quic_sock_group_t; ++ ++ ++typedef struct { ++ ngx_flag_t enabled; ++ ngx_uint_t map_size; ++ ngx_queue_t groups; /* of ngx_quic_sock_group_t */ ++} ngx_quic_bpf_conf_t; ++ ++ ++static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle); ++static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle); ++ ++static void ngx_quic_bpf_cleanup(void *data); ++static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd, ++ const char *name); ++ ++static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ++ ngx_listening_t *ls); ++static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, ++ struct sockaddr *sa, socklen_t socklen); ++static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ++ ngx_listening_t *ls); ++static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ++ ngx_listening_t *ls); ++static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ++ ngx_listening_t *ls); ++static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log); ++ ++static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle); ++static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle); ++ ++extern ngx_bpf_program_t ngx_quic_reuseport_helper; ++ ++ ++static ngx_command_t ngx_quic_bpf_commands[] = { ++ ++ { ngx_string("quic_bpf"), ++ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ 0, ++ offsetof(ngx_quic_bpf_conf_t, enabled), ++ NULL }, ++ ++ ngx_null_command ++}; ++ ++ ++static ngx_core_module_t ngx_quic_bpf_module_ctx = { ++ ngx_string("quic_bpf"), ++ ngx_quic_bpf_create_conf, ++ NULL ++}; ++ ++ ++ngx_module_t ngx_quic_bpf_module = { ++ NGX_MODULE_V1, ++ &ngx_quic_bpf_module_ctx, /* module context */ ++ ngx_quic_bpf_commands, /* module directives */ ++ NGX_CORE_MODULE, /* module type */ ++ NULL, /* init master */ ++ ngx_quic_bpf_module_init, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ ++ ++static void * ++ngx_quic_bpf_create_conf(ngx_cycle_t *cycle) ++{ ++ ngx_quic_bpf_conf_t *bcf; ++ ++ bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t)); ++ if (bcf == NULL) { ++ return NULL; ++ } ++ ++ bcf->enabled = NGX_CONF_UNSET; ++ bcf->map_size = NGX_CONF_UNSET_UINT; ++ ++ ngx_queue_init(&bcf->groups); ++ ++ return bcf; ++} ++ ++ ++static ngx_int_t ++ngx_quic_bpf_module_init(ngx_cycle_t *cycle) ++{ ++ ngx_uint_t i; ++ ngx_listening_t *ls; ++ ngx_core_conf_t *ccf; ++ ngx_pool_cleanup_t *cln; ++ ngx_quic_bpf_conf_t *bcf; ++ ++ if (ngx_test_config) { ++ /* ++ * during config test, SO_REUSEPORT socket option is ++ * not set, thus making further processing meaningless ++ */ ++ return NGX_OK; ++ } ++ ++ ccf = ngx_core_get_conf(cycle); ++ bcf = ngx_quic_bpf_get_conf(cycle); ++ ++ ngx_conf_init_value(bcf->enabled, 0); ++ ++ bcf->map_size = ccf->worker_processes * 4; ++ ++ cln = ngx_pool_cleanup_add(cycle->pool, 0); ++ if (cln == NULL) { ++ goto failed; ++ } ++ ++ cln->data = bcf; ++ cln->handler = ngx_quic_bpf_cleanup; ++ ++ if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) { ++ if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) { ++ goto failed; ++ } ++ } ++ ++ ls = cycle->listening.elts; ++ ++ for (i = 0; i < cycle->listening.nelts; i++) { ++ if (ls[i].quic && ls[i].reuseport) { ++ if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) { ++ goto failed; ++ } ++ } ++ } ++ ++ if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ if (ngx_is_init_cycle(cycle->old_cycle)) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, ++ "ngx_quic_bpf_module failed to initialize, check limits"); ++ ++ /* refuse to start */ ++ return NGX_ERROR; ++ } ++ ++ /* ++ * returning error now will lead to master process exiting immediately ++ * leaving worker processes orphaned, what is really unexpected. ++ * Instead, just issue a not about failed initialization and try ++ * to cleanup a bit. Still program can be already loaded to kernel ++ * for some reuseport groups, and there is no way to revert, so ++ * behaviour may be inconsistent. ++ */ ++ ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, ++ "ngx_quic_bpf_module failed to initialize properly, ignored." ++ "please check limits and note that nginx state now " ++ "can be inconsistent and restart may be required"); ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_bpf_cleanup(void *data) ++{ ++ ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data; ++ ++ ngx_queue_t *q; ++ ngx_quic_sock_group_t *grp; ++ ++ for (q = ngx_queue_head(&bcf->groups); ++ q != ngx_queue_sentinel(&bcf->groups); ++ q = ngx_queue_next(q)) ++ { ++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); ++ ++ ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map"); ++ } ++} ++ ++ ++static ngx_inline void ++ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name) ++{ ++ if (close(fd) != -1) { ++ return; ++ } ++ ++ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, ++ "quic bpf close %s fd:%d failed", name, fd); ++} ++ ++ ++static ngx_quic_sock_group_t * ++ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls) ++{ ++ ngx_queue_t *q; ++ ngx_quic_sock_group_t *grp; ++ ++ for (q = ngx_queue_head(&bcf->groups); ++ q != ngx_queue_sentinel(&bcf->groups); ++ q = ngx_queue_next(q)) ++ { ++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); ++ ++ if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen, ++ grp->sockaddr, grp->socklen, 1) ++ == NGX_OK) ++ { ++ return grp; ++ } ++ } ++ ++ return NULL; ++} ++ ++ ++static ngx_quic_sock_group_t * ++ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa, ++ socklen_t socklen) ++{ ++ ngx_quic_bpf_conf_t *bcf; ++ ngx_quic_sock_group_t *grp; ++ ++ bcf = ngx_quic_bpf_get_conf(cycle); ++ ++ grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t)); ++ if (grp == NULL) { ++ return NULL; ++ } ++ ++ grp->socklen = socklen; ++ grp->sockaddr = ngx_palloc(cycle->pool, socklen); ++ if (grp->sockaddr == NULL) { ++ return NULL; ++ } ++ ngx_memcpy(grp->sockaddr, sa, socklen); ++ ++ ngx_queue_insert_tail(&bcf->groups, &grp->queue); ++ ++ return grp; ++} ++ ++ ++static ngx_quic_sock_group_t * ++ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls) ++{ ++ int progfd, failed, flags, rc; ++ ngx_quic_bpf_conf_t *bcf; ++ ngx_quic_sock_group_t *grp; ++ ++ bcf = ngx_quic_bpf_get_conf(cycle); ++ ++ if (!bcf->enabled) { ++ return NULL; ++ } ++ ++ grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); ++ if (grp == NULL) { ++ return NULL; ++ } ++ ++ grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH, ++ sizeof(uint64_t), sizeof(uint64_t), ++ bcf->map_size, 0); ++ if (grp->map_fd == -1) { ++ goto failed; ++ } ++ ++ flags = fcntl(grp->map_fd, F_GETFD); ++ if (flags == -1) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, ++ "quic bpf getfd failed"); ++ goto failed; ++ } ++ ++ /* need to inherit map during binary upgrade after exec */ ++ flags &= ~FD_CLOEXEC; ++ ++ rc = fcntl(grp->map_fd, F_SETFD, flags); ++ if (rc == -1) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, ++ "quic bpf setfd failed"); ++ goto failed; ++ } ++ ++ ngx_bpf_program_link(&ngx_quic_reuseport_helper, ++ "ngx_quic_sockmap", grp->map_fd); ++ ++ progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper); ++ if (progfd < 0) { ++ goto failed; ++ } ++ ++ failed = 0; ++ ++ if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, ++ &progfd, sizeof(int)) ++ == -1) ++ { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, ++ "quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed"); ++ failed = 1; ++ } ++ ++ ngx_quic_bpf_close(cycle->log, progfd, "program"); ++ ++ if (failed) { ++ goto failed; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, ++ "quic bpf sockmap created fd:%d", grp->map_fd); ++ return grp; ++ ++failed: ++ ++ if (grp->map_fd != -1) { ++ ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); ++ } ++ ++ ngx_queue_remove(&grp->queue); ++ ++ return NULL; ++} ++ ++ ++static ngx_quic_sock_group_t * ++ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls) ++{ ++ ngx_quic_bpf_conf_t *bcf, *old_bcf; ++ ngx_quic_sock_group_t *grp, *ogrp; ++ ++ bcf = ngx_quic_bpf_get_conf(cycle); ++ ++ grp = ngx_quic_bpf_find_group(bcf, ls); ++ if (grp) { ++ return grp; ++ } ++ ++ old_bcf = ngx_quic_bpf_get_old_conf(cycle); ++ ++ if (old_bcf == NULL) { ++ return ngx_quic_bpf_create_group(cycle, ls); ++ } ++ ++ ogrp = ngx_quic_bpf_find_group(old_bcf, ls); ++ if (ogrp == NULL) { ++ return ngx_quic_bpf_create_group(cycle, ls); ++ } ++ ++ grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); ++ if (grp == NULL) { ++ return NULL; ++ } ++ ++ grp->map_fd = dup(ogrp->map_fd); ++ if (grp->map_fd == -1) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, ++ "quic bpf failed to duplicate bpf map descriptor"); ++ ++ ngx_queue_remove(&grp->queue); ++ ++ return NULL; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, ++ "quic bpf sockmap fd duplicated old:%d new:%d", ++ ogrp->map_fd, grp->map_fd); ++ ++ return grp; ++} ++ ++ ++static ngx_int_t ++ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls) ++{ ++ uint64_t cookie; ++ ngx_quic_bpf_conf_t *bcf; ++ ngx_quic_sock_group_t *grp; ++ ++ bcf = ngx_quic_bpf_get_conf(cycle); ++ ++ grp = ngx_quic_bpf_get_group(cycle, ls); ++ ++ if (grp == NULL) { ++ if (!bcf->enabled) { ++ return NGX_OK; ++ } ++ ++ return NGX_ERROR; ++ } ++ ++ grp->unused = 0; ++ ++ cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log); ++ if (cookie == (uint64_t) NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ /* map[cookie] = socket; for use in kernel helper */ ++ if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, ++ "quic bpf failed to update socket map key=%xL", cookie); ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, ++ "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui", ++ grp->map_fd, ls->fd, cookie, ls->worker); ++ ++ /* do not inherit this socket */ ++ ls->ignore = 1; ++ ++ return NGX_OK; ++} ++ ++ ++static uint64_t ++ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log) ++{ ++ uint64_t cookie; ++ socklen_t optlen; ++ ++ optlen = sizeof(cookie); ++ ++ if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { ++ ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, ++ "quic bpf getsockopt(SO_COOKIE) failed"); ++ ++ return (ngx_uint_t) NGX_ERROR; ++ } ++ ++ return cookie; ++} ++ ++ ++static ngx_int_t ++ngx_quic_bpf_export_maps(ngx_cycle_t *cycle) ++{ ++ u_char *p, *buf; ++ size_t len; ++ ngx_str_t *var; ++ ngx_queue_t *q; ++ ngx_core_conf_t *ccf; ++ ngx_quic_bpf_conf_t *bcf; ++ ngx_quic_sock_group_t *grp; ++ ++ ccf = ngx_core_get_conf(cycle); ++ bcf = ngx_quic_bpf_get_conf(cycle); ++ ++ len = sizeof(NGX_QUIC_BPF_VARNAME) + 1; ++ ++ q = ngx_queue_head(&bcf->groups); ++ ++ while (q != ngx_queue_sentinel(&bcf->groups)) { ++ ++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); ++ ++ q = ngx_queue_next(q); ++ ++ if (grp->unused) { ++ /* ++ * map was inherited, but it is not used in this configuration; ++ * do not pass such map further and drop the group to prevent ++ * interference with changes during reload ++ */ ++ ++ ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); ++ ngx_queue_remove(&grp->queue); ++ ++ continue; ++ } ++ ++ len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1; ++ } ++ ++ len++; ++ ++ buf = ngx_palloc(cycle->pool, len); ++ if (buf == NULL) { ++ return NGX_ERROR; ++ } ++ ++ p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=", ++ sizeof(NGX_QUIC_BPF_VARNAME)); ++ ++ for (q = ngx_queue_head(&bcf->groups); ++ q != ngx_queue_sentinel(&bcf->groups); ++ q = ngx_queue_next(q)) ++ { ++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); ++ ++ p = ngx_sprintf(p, "%ud", grp->map_fd); ++ ++ *p++ = NGX_QUIC_BPF_ADDRSEP; ++ ++ p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p, ++ NGX_SOCKADDR_STRLEN, 1); ++ ++ *p++ = NGX_QUIC_BPF_VARSEP; ++ } ++ ++ *p = '\0'; ++ ++ var = ngx_array_push(&ccf->env); ++ if (var == NULL) { ++ return NGX_ERROR; ++ } ++ ++ var->data = buf; ++ var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_bpf_import_maps(ngx_cycle_t *cycle) ++{ ++ int s; ++ u_char *inherited, *p, *v; ++ ngx_uint_t in_fd; ++ ngx_addr_t tmp; ++ ngx_quic_bpf_conf_t *bcf; ++ ngx_quic_sock_group_t *grp; ++ ++ inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME); ++ ++ if (inherited == NULL) { ++ return NGX_OK; ++ } ++ ++ bcf = ngx_quic_bpf_get_conf(cycle); ++ ++#if (NGX_SUPPRESS_WARN) ++ s = -1; ++#endif ++ ++ in_fd = 1; ++ ++ for (p = inherited, v = p; *p; p++) { ++ ++ switch (*p) { ++ ++ case NGX_QUIC_BPF_ADDRSEP: ++ ++ if (!in_fd) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, ++ "quic bpf failed to parse inherited env"); ++ return NGX_ERROR; ++ } ++ in_fd = 0; ++ ++ s = ngx_atoi(v, p - v); ++ if (s == NGX_ERROR) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, ++ "quic bpf failed to parse inherited map fd"); ++ return NGX_ERROR; ++ } ++ ++ v = p + 1; ++ break; ++ ++ case NGX_QUIC_BPF_VARSEP: ++ ++ if (in_fd) { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, ++ "quic bpf failed to parse inherited env"); ++ return NGX_ERROR; ++ } ++ in_fd = 1; ++ ++ grp = ngx_pcalloc(cycle->pool, ++ sizeof(ngx_quic_sock_group_t)); ++ if (grp == NULL) { ++ return NGX_ERROR; ++ } ++ ++ grp->map_fd = s; ++ ++ if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v) ++ != NGX_OK) ++ { ++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, ++ "quic bpf failed to parse inherited" ++ " address '%*s'", p - v , v); ++ ++ ngx_quic_bpf_close(cycle->log, s, "inherited map"); ++ ++ return NGX_ERROR; ++ } ++ ++ grp->sockaddr = tmp.sockaddr; ++ grp->socklen = tmp.socklen; ++ ++ grp->unused = 1; ++ ++ ngx_queue_insert_tail(&bcf->groups, &grp->queue); ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, ++ "quic bpf sockmap inherited with " ++ "fd:%d address:%*s", ++ grp->map_fd, p - v, v); ++ v = p + 1; ++ break; ++ ++ default: ++ break; ++ } ++ } ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_bpf_code.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_bpf_code.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,88 @@ ++/* AUTO-GENERATED, DO NOT EDIT. */ ++ ++#include ++#include ++ ++#include "ngx_bpf.h" ++ ++ ++static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = { ++ { "ngx_quic_sockmap", 55 }, ++}; ++ ++static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { ++ /* opcode dst src offset imm */ ++ { 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 }, ++ { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 }, ++ { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 }, ++ { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 }, ++ { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 }, ++ { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 }, ++ { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 }, ++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 }, ++ { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 }, ++ { 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, ++ { 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, ++ { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff }, ++ { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd }, ++ { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 }, ++ { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe }, ++ { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 }, ++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe }, ++ { 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 }, ++ { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 }, ++ { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 }, ++ { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 }, ++ { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 }, ++ { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 }, ++ { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 }, ++ { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 }, ++ { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 }, ++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 }, ++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, ++ { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 }, ++ { 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 }, ++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 }, ++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 }, ++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 }, ++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, ++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 }, ++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 }, ++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 }, ++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 }, ++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 }, ++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 }, ++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, ++ { 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 }, ++ { 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 }, ++ { 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 }, ++ { 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 }, ++ { 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 }, ++ { 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 }, ++ { 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, ++ { 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 }, ++ { 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 }, ++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 }, ++ { 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, ++}; ++ ++ ++ngx_bpf_program_t ngx_quic_reuseport_helper = { ++ .relocs = bpf_reloc_prog_ngx_quic_reuseport_helper, ++ .nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper) ++ / sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]), ++ .ins = bpf_insn_prog_ngx_quic_reuseport_helper, ++ .nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper) ++ / sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]), ++ .license = "BSD", ++ .type = BPF_PROG_TYPE_SK_REUSEPORT, ++}; +diff -r ac779115ed6e src/event/quic/ngx_event_quic_connection.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_connection.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,283 @@ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++ ++/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ ++/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ ++/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ ++/* #define NGX_QUIC_DEBUG_CRYPTO */ ++ ++typedef struct ngx_quic_connection_s ngx_quic_connection_t; ++typedef struct ngx_quic_server_id_s ngx_quic_server_id_t; ++typedef struct ngx_quic_client_id_s ngx_quic_client_id_t; ++typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; ++typedef struct ngx_quic_socket_s ngx_quic_socket_t; ++typedef struct ngx_quic_path_s ngx_quic_path_t; ++typedef struct ngx_quic_keys_s ngx_quic_keys_t; ++ ++#if (NGX_QUIC_OPENSSL_COMPAT) ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */ ++#define NGX_QUIC_INITIAL_RTT 333 /* ms */ ++ ++#define NGX_QUIC_UNSET_PN (uint64_t) -1 ++ ++#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) ++ ++/* 0-RTT and 1-RTT data exist in the same packet number space, ++ * so we have 3 packet number spaces: ++ * ++ * 0 - Initial ++ * 1 - Handshake ++ * 2 - 0-RTT and 1-RTT ++ */ ++#define ngx_quic_get_send_ctx(qc, level) \ ++ ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ ++ : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ ++ : &((qc)->send_ctx[2])) ++ ++#define ngx_quic_get_connection(c) \ ++ (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL) ++ ++#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp)) ++ ++ ++struct ngx_quic_client_id_s { ++ ngx_queue_t queue; ++ uint64_t seqnum; ++ size_t len; ++ u_char id[NGX_QUIC_CID_LEN_MAX]; ++ u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; ++ ngx_uint_t used; /* unsigned used:1; */ ++}; ++ ++ ++struct ngx_quic_server_id_s { ++ uint64_t seqnum; ++ size_t len; ++ u_char id[NGX_QUIC_CID_LEN_MAX]; ++}; ++ ++ ++struct ngx_quic_path_s { ++ ngx_queue_t queue; ++ struct sockaddr *sockaddr; ++ ngx_sockaddr_t sa; ++ socklen_t socklen; ++ ngx_quic_client_id_t *cid; ++ ngx_msec_t expires; ++ ngx_uint_t tries; ++ ngx_uint_t tag; ++ off_t sent; ++ off_t received; ++ u_char challenge1[8]; ++ u_char challenge2[8]; ++ uint64_t seqnum; ++ ngx_str_t addr_text; ++ u_char text[NGX_SOCKADDR_STRLEN]; ++ unsigned validated:1; ++ unsigned validating:1; ++ unsigned limited:1; ++}; ++ ++ ++struct ngx_quic_socket_s { ++ ngx_udp_connection_t udp; ++ ngx_quic_connection_t *quic; ++ ngx_queue_t queue; ++ ngx_quic_server_id_t sid; ++ ngx_sockaddr_t sockaddr; ++ socklen_t socklen; ++ ngx_uint_t used; /* unsigned used:1; */ ++}; ++ ++ ++typedef struct { ++ ngx_rbtree_t tree; ++ ngx_rbtree_node_t sentinel; ++ ++ ngx_queue_t uninitialized; ++ ngx_queue_t free; ++ ++ uint64_t sent; ++ uint64_t recv_offset; ++ uint64_t recv_window; ++ uint64_t recv_last; ++ uint64_t recv_max_data; ++ uint64_t send_offset; ++ uint64_t send_max_data; ++ ++ uint64_t server_max_streams_uni; ++ uint64_t server_max_streams_bidi; ++ uint64_t server_streams_uni; ++ uint64_t server_streams_bidi; ++ ++ uint64_t client_max_streams_uni; ++ uint64_t client_max_streams_bidi; ++ uint64_t client_streams_uni; ++ uint64_t client_streams_bidi; ++ ++ ngx_uint_t initialized; ++ /* unsigned initialized:1; */ ++} ngx_quic_streams_t; ++ ++ ++typedef struct { ++ size_t in_flight; ++ size_t window; ++ size_t ssthresh; ++ ngx_msec_t recovery_start; ++} ngx_quic_congestion_t; ++ ++ ++/* ++ * RFC 9000, 12.3. Packet Numbers ++ * ++ * Conceptually, a packet number space is the context in which a packet ++ * can be processed and acknowledged. Initial packets can only be sent ++ * with Initial packet protection keys and acknowledged in packets that ++ * are also Initial packets. ++ */ ++struct ngx_quic_send_ctx_s { ++ enum ssl_encryption_level_t level; ++ ++ ngx_quic_buffer_t crypto; ++ uint64_t crypto_sent; ++ ++ uint64_t pnum; /* to be sent */ ++ uint64_t largest_ack; /* received from peer */ ++ uint64_t largest_pn; /* received from peer */ ++ ++ ngx_queue_t frames; /* generated frames */ ++ ngx_queue_t sending; /* frames assigned to pkt */ ++ ngx_queue_t sent; /* frames waiting ACK */ ++ ++ uint64_t pending_ack; /* non sent ack-eliciting */ ++ uint64_t largest_range; ++ uint64_t first_range; ++ ngx_msec_t largest_received; ++ ngx_msec_t ack_delay_start; ++ ngx_uint_t nranges; ++ ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; ++ ngx_uint_t send_ack; ++}; ++ ++ ++struct ngx_quic_connection_s { ++ uint32_t version; ++ ++ ngx_quic_path_t *path; ++ ++ ngx_queue_t sockets; ++ ngx_queue_t paths; ++ ngx_queue_t client_ids; ++ ngx_queue_t free_sockets; ++ ngx_queue_t free_paths; ++ ngx_queue_t free_client_ids; ++ ++ ngx_uint_t nsockets; ++ ngx_uint_t nclient_ids; ++ uint64_t max_retired_seqnum; ++ uint64_t client_seqnum; ++ uint64_t server_seqnum; ++ uint64_t path_seqnum; ++ ++ ngx_quic_tp_t tp; ++ ngx_quic_tp_t ctp; ++ ++ ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; ++ ++ ngx_quic_keys_t *keys; ++ ++ ngx_quic_conf_t *conf; ++ ++ ngx_event_t push; ++ ngx_event_t pto; ++ ngx_event_t close; ++ ngx_event_t path_validation; ++ ngx_msec_t last_cc; ++ ++ ngx_msec_t first_rtt; ++ ngx_msec_t latest_rtt; ++ ngx_msec_t avg_rtt; ++ ngx_msec_t min_rtt; ++ ngx_msec_t rttvar; ++ ++ ngx_uint_t pto_count; ++ ++ ngx_queue_t free_frames; ++ ngx_buf_t *free_bufs; ++ ngx_buf_t *free_shadow_bufs; ++ ++ ngx_uint_t nframes; ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_uint_t nbufs; ++ ngx_uint_t nshadowbufs; ++#endif ++ ++#if (NGX_QUIC_OPENSSL_COMPAT) ++ ngx_quic_compat_t *compat; ++#endif ++ ++ ngx_quic_streams_t streams; ++ ngx_quic_congestion_t congestion; ++ ++ off_t received; ++ ++ ngx_uint_t error; ++ enum ssl_encryption_level_t error_level; ++ ngx_uint_t error_ftype; ++ const char *error_reason; ++ ++ ngx_uint_t shutdown_code; ++ const char *shutdown_reason; ++ ++ unsigned error_app:1; ++ unsigned send_timer_set:1; ++ unsigned closing:1; ++ unsigned shutdown:1; ++ unsigned draining:1; ++ unsigned key_phase:1; ++ unsigned validated:1; ++ unsigned client_tp_done:1; ++}; ++ ++ ++ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ++ ngx_quic_tp_t *ctp); ++void ngx_quic_discard_ctx(ngx_connection_t *c, ++ enum ssl_encryption_level_t level); ++void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); ++void ngx_quic_shutdown_quic(ngx_connection_t *c); ++ ++#if (NGX_DEBUG) ++void ngx_quic_connstate_dbg(ngx_connection_t *c); ++#else ++#define ngx_quic_connstate_dbg(c) ++#endif ++ ++#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_connid.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_connid.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,502 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++#define NGX_QUIC_MAX_SERVER_IDS 8 ++ ++ ++#if (NGX_QUIC_BPF) ++static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); ++#endif ++static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c, ++ ngx_quic_client_id_t *cid); ++static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, ++ ngx_quic_connection_t *qc); ++static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, ++ ngx_quic_server_id_t *sid); ++ ++ ++ngx_int_t ++ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) ++{ ++ if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { ++ return NGX_ERROR; ++ } ++ ++#if (NGX_QUIC_BPF) ++ if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "quic bpf failed to generate socket key"); ++ /* ignore error, things still may work */ ++ } ++#endif ++ ++ return NGX_OK; ++} ++ ++ ++#if (NGX_QUIC_BPF) ++ ++static ngx_int_t ++ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) ++{ ++ int fd; ++ uint64_t cookie; ++ socklen_t optlen; ++ ++ fd = c->listening->fd; ++ ++ optlen = sizeof(cookie); ++ ++ if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { ++ ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, ++ "quic getsockopt(SO_COOKIE) failed"); ++ ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_dcid_encode_key(id, cookie); ++ ++ return NGX_OK; ++} ++ ++#endif ++ ++ ++ngx_int_t ++ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ++ ngx_quic_new_conn_id_frame_t *f) ++{ ++ ngx_str_t id; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *frame; ++ ngx_quic_client_id_t *cid, *item; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (f->seqnum < qc->max_retired_seqnum) { ++ /* ++ * RFC 9000, 19.15. NEW_CONNECTION_ID Frame ++ * ++ * An endpoint that receives a NEW_CONNECTION_ID frame with ++ * a sequence number smaller than the Retire Prior To field ++ * of a previously received NEW_CONNECTION_ID frame MUST send ++ * a corresponding RETIRE_CONNECTION_ID frame that retires ++ * the newly received connection ID, unless it has already ++ * done so for that sequence number. ++ */ ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; ++ frame->u.retire_cid.sequence_number = f->seqnum; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ goto retire; ++ } ++ ++ cid = NULL; ++ ++ for (q = ngx_queue_head(&qc->client_ids); ++ q != ngx_queue_sentinel(&qc->client_ids); ++ q = ngx_queue_next(q)) ++ { ++ item = ngx_queue_data(q, ngx_quic_client_id_t, queue); ++ ++ if (item->seqnum == f->seqnum) { ++ cid = item; ++ break; ++ } ++ } ++ ++ if (cid) { ++ /* ++ * Transmission errors, timeouts, and retransmissions might cause the ++ * same NEW_CONNECTION_ID frame to be received multiple times. ++ */ ++ ++ if (cid->len != f->len ++ || ngx_strncmp(cid->id, f->cid, f->len) != 0 ++ || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) ++ { ++ /* ++ * ..if a sequence number is used for different connection IDs, ++ * the endpoint MAY treat that receipt as a connection error ++ * of type PROTOCOL_VIOLATION. ++ */ ++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ qc->error_reason = "seqnum refers to different connection id/token"; ++ return NGX_ERROR; ++ } ++ ++ } else { ++ ++ id.data = f->cid; ++ id.len = f->len; ++ ++ if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) { ++ return NGX_ERROR; ++ } ++ } ++ ++retire: ++ ++ if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { ++ /* ++ * Once a sender indicates a Retire Prior To value, smaller values sent ++ * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver ++ * MUST ignore any Retire Prior To fields that do not increase the ++ * largest received Retire Prior To value. ++ */ ++ goto done; ++ } ++ ++ qc->max_retired_seqnum = f->retire; ++ ++ q = ngx_queue_head(&qc->client_ids); ++ ++ while (q != ngx_queue_sentinel(&qc->client_ids)) { ++ ++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); ++ q = ngx_queue_next(q); ++ ++ if (cid->seqnum >= f->retire) { ++ continue; ++ } ++ ++ if (ngx_quic_retire_client_id(c, cid) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++done: ++ ++ if (qc->nclient_ids > qc->tp.active_connection_id_limit) { ++ /* ++ * RFC 9000, 5.1.1. Issuing Connection IDs ++ * ++ * After processing a NEW_CONNECTION_ID frame and ++ * adding and retiring active connection IDs, if the number of active ++ * connection IDs exceeds the value advertised in its ++ * active_connection_id_limit transport parameter, an endpoint MUST ++ * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. ++ */ ++ qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; ++ qc->error_reason = "too many connection ids received"; ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) ++{ ++ ngx_queue_t *q; ++ ngx_quic_path_t *path; ++ ngx_quic_client_id_t *new_cid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (!cid->used) { ++ return ngx_quic_free_client_id(c, cid); ++ } ++ ++ /* we are going to retire client id which is in use */ ++ ++ q = ngx_queue_head(&qc->paths); ++ ++ while (q != ngx_queue_sentinel(&qc->paths)) { ++ ++ path = ngx_queue_data(q, ngx_quic_path_t, queue); ++ q = ngx_queue_next(q); ++ ++ if (path->cid != cid) { ++ continue; ++ } ++ ++ if (path == qc->path) { ++ /* this is the active path: update it with new CID */ ++ new_cid = ngx_quic_next_client_id(c); ++ if (new_cid == NULL) { ++ return NGX_ERROR; ++ } ++ ++ qc->path->cid = new_cid; ++ new_cid->used = 1; ++ ++ return ngx_quic_free_client_id(c, cid); ++ } ++ ++ return ngx_quic_free_path(c, path); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_quic_client_id_t * ++ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) ++{ ++ ngx_queue_t *q; ++ ngx_quic_client_id_t *cid; ++ ++ if (!ngx_queue_empty(&qc->free_client_ids)) { ++ ++ q = ngx_queue_head(&qc->free_client_ids); ++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); ++ ++ ngx_queue_remove(&cid->queue); ++ ++ ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); ++ ++ } else { ++ ++ cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); ++ if (cid == NULL) { ++ return NULL; ++ } ++ } ++ ++ return cid; ++} ++ ++ ++ngx_quic_client_id_t * ++ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, ++ uint64_t seqnum, u_char *token) ++{ ++ ngx_quic_client_id_t *cid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ cid = ngx_quic_alloc_client_id(c, qc); ++ if (cid == NULL) { ++ return NULL; ++ } ++ ++ cid->seqnum = seqnum; ++ ++ cid->len = id->len; ++ ngx_memcpy(cid->id, id->data, id->len); ++ ++ if (token) { ++ ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN); ++ } ++ ++ ngx_queue_insert_tail(&qc->client_ids, &cid->queue); ++ qc->nclient_ids++; ++ ++ if (seqnum > qc->client_seqnum) { ++ qc->client_seqnum = seqnum; ++ } ++ ++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic cid seq:%uL received id:%uz:%xV:%*xs", ++ cid->seqnum, id->len, id, ++ (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token); ++ ++ return cid; ++} ++ ++ ++ngx_quic_client_id_t * ++ngx_quic_next_client_id(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ ngx_quic_client_id_t *cid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ for (q = ngx_queue_head(&qc->client_ids); ++ q != ngx_queue_sentinel(&qc->client_ids); ++ q = ngx_queue_next(q)) ++ { ++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); ++ ++ if (!cid->used) { ++ return cid; ++ } ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, ++ ngx_quic_retire_cid_frame_t *f) ++{ ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (f->sequence_number >= qc->server_seqnum) { ++ /* ++ * RFC 9000, 19.16. ++ * ++ * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence ++ * number greater than any previously sent to the peer MUST be ++ * treated as a connection error of type PROTOCOL_VIOLATION. ++ */ ++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ qc->error_reason = "sequence number of id to retire was never issued"; ++ ++ return NGX_ERROR; ++ } ++ ++ qsock = ngx_quic_get_socket(c); ++ ++ if (qsock->sid.seqnum == f->sequence_number) { ++ ++ /* ++ * RFC 9000, 19.16. ++ * ++ * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST ++ * NOT refer to the Destination Connection ID field of the packet in ++ * which the frame is contained. The peer MAY treat this as a ++ * connection error of type PROTOCOL_VIOLATION. ++ */ ++ ++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ qc->error_reason = "sequence number of id to retire refers DCID"; ++ ++ return NGX_ERROR; ++ } ++ ++ qsock = ngx_quic_find_socket(c, f->sequence_number); ++ if (qsock == NULL) { ++ return NGX_OK; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic socket seq:%uL is retired", qsock->sid.seqnum); ++ ++ ngx_quic_close_socket(c, qsock); ++ ++ /* restore socket count up to a limit after deletion */ ++ if (ngx_quic_create_sockets(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_create_sockets(ngx_connection_t *c) ++{ ++ ngx_uint_t n; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic create sockets has:%ui max:%ui", qc->nsockets, n); ++ ++ while (qc->nsockets < n) { ++ ++ qsock = ngx_quic_create_socket(c, qc); ++ if (qsock == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid) ++{ ++ ngx_str_t dcid; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ dcid.len = sid->len; ++ dcid.data = sid->id; ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; ++ frame->u.ncid.seqnum = sid->seqnum; ++ frame->u.ncid.retire = 0; ++ frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; ++ ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN); ++ ++ if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, ++ frame->u.ncid.srt) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) ++{ ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; ++ frame->u.retire_cid.sequence_number = cid->seqnum; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ /* we are no longer going to use this client id */ ++ ++ ngx_queue_remove(&cid->queue); ++ ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); ++ ++ qc->nclient_ids--; ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_connid.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_connid.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,29 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, ++ ngx_quic_retire_cid_frame_t *f); ++ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ++ ngx_quic_new_conn_id_frame_t *f); ++ ++ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c); ++ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); ++ ++ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, ++ ngx_str_t *id, uint64_t seqnum, u_char *token); ++ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c); ++ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c, ++ ngx_quic_client_id_t *cid); ++ ++#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_frames.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_frames.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,894 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_QUIC_BUFFER_SIZE 4096 ++ ++#define ngx_quic_buf_refs(b) (b)->shadow->num ++#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++ ++#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)-- ++#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v ++ ++ ++static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c); ++static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b); ++static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b); ++static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, ++ off_t offset); ++ ++ ++static ngx_buf_t * ++ngx_quic_alloc_buf(ngx_connection_t *c) ++{ ++ u_char *p; ++ ngx_buf_t *b; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ b = qc->free_bufs; ++ ++ if (b) { ++ qc->free_bufs = b->shadow; ++ p = b->start; ++ ++ } else { ++ b = qc->free_shadow_bufs; ++ ++ if (b) { ++ qc->free_shadow_bufs = b->shadow; ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic use shadow buffer n:%ui %ui", ++ ++qc->nbufs, --qc->nshadowbufs); ++#endif ++ ++ } else { ++ b = ngx_palloc(c->pool, sizeof(ngx_buf_t)); ++ if (b == NULL) { ++ return NULL; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic new buffer n:%ui", ++qc->nbufs); ++#endif ++ } ++ ++ p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE); ++ if (p == NULL) { ++ return NULL; ++ } ++ } ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b); ++#endif ++ ++ ngx_memzero(b, sizeof(ngx_buf_t)); ++ ++ b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; ++ b->temporary = 1; ++ b->shadow = b; ++ ++ b->start = p; ++ b->pos = p; ++ b->last = p; ++ b->end = p + NGX_QUIC_BUFFER_SIZE; ++ ++ ngx_quic_buf_set_refs(b, 1); ++ ++ return b; ++} ++ ++ ++static void ++ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b) ++{ ++ ngx_buf_t *shadow; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_quic_buf_dec_refs(b); ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic free buffer %p r:%ui", ++ b, (ngx_uint_t) ngx_quic_buf_refs(b)); ++#endif ++ ++ shadow = b->shadow; ++ ++ if (ngx_quic_buf_refs(b) == 0) { ++ shadow->shadow = qc->free_bufs; ++ qc->free_bufs = shadow; ++ } ++ ++ if (b != shadow) { ++ b->shadow = qc->free_shadow_bufs; ++ qc->free_shadow_bufs = b; ++ } ++ ++} ++ ++ ++static ngx_buf_t * ++ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b) ++{ ++ ngx_buf_t *nb; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ nb = qc->free_shadow_bufs; ++ ++ if (nb) { ++ qc->free_shadow_bufs = nb->shadow; ++ ++ } else { ++ nb = ngx_palloc(c->pool, sizeof(ngx_buf_t)); ++ if (nb == NULL) { ++ return NULL; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic new shadow buffer n:%ui", ++qc->nshadowbufs); ++#endif ++ } ++ ++ *nb = *b; ++ ++ ngx_quic_buf_inc_refs(b); ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic clone buffer %p %p r:%ui", ++ b, nb, (ngx_uint_t) ngx_quic_buf_refs(b)); ++#endif ++ ++ return nb; ++} ++ ++ ++static ngx_int_t ++ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset) ++{ ++ ngx_buf_t *b, *tb; ++ ngx_chain_t *tail; ++ ++ b = cl->buf; ++ ++ tail = ngx_alloc_chain_link(c->pool); ++ if (tail == NULL) { ++ return NGX_ERROR; ++ } ++ ++ tb = ngx_quic_clone_buf(c, b); ++ if (tb == NULL) { ++ return NGX_ERROR; ++ } ++ ++ tail->buf = tb; ++ ++ tb->pos += offset; ++ ++ b->last = tb->pos; ++ b->last_buf = 0; ++ ++ tail->next = cl->next; ++ cl->next = tail; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_quic_frame_t * ++ngx_quic_alloc_frame(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (!ngx_queue_empty(&qc->free_frames)) { ++ ++ q = ngx_queue_head(&qc->free_frames); ++ frame = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ ngx_queue_remove(&frame->queue); ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic reuse frame n:%ui", qc->nframes); ++#endif ++ ++ } else if (qc->nframes < 10000) { ++ frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t)); ++ if (frame == NULL) { ++ return NULL; ++ } ++ ++ ++qc->nframes; ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic alloc frame n:%ui", qc->nframes); ++#endif ++ ++ } else { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); ++ return NULL; ++ } ++ ++ ngx_memzero(frame, sizeof(ngx_quic_frame_t)); ++ ++ return frame; ++} ++ ++ ++void ++ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (frame->data) { ++ ngx_quic_free_chain(c, frame->data); ++ } ++ ++ ngx_queue_insert_head(&qc->free_frames, &frame->queue); ++ ++#ifdef NGX_QUIC_DEBUG_ALLOC ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic free frame n:%ui", qc->nframes); ++#endif ++} ++ ++ ++void ++ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) ++{ ++ ngx_chain_t *cl; ++ ++ while (in) { ++ cl = in; ++ in = in->next; ++ ++ ngx_quic_free_buf(c, cl->buf); ++ ngx_free_chain(c->pool, cl); ++ } ++} ++ ++ ++void ++ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) ++{ ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ++ do { ++ q = ngx_queue_head(frames); ++ ++ if (q == ngx_queue_sentinel(frames)) { ++ break; ++ } ++ ++ ngx_queue_remove(q); ++ ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ ngx_quic_free_frame(c, f); ++ } while (1); ++} ++ ++ ++void ++ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) ++{ ++ ngx_quic_send_ctx_t *ctx; ++ ++ ctx = ngx_quic_get_send_ctx(qc, frame->level); ++ ++ ngx_queue_insert_tail(&ctx->frames, &frame->queue); ++ ++ frame->len = ngx_quic_create_frame(NULL, frame); ++ /* always succeeds */ ++ ++ if (qc->closing) { ++ return; ++ } ++ ++ ngx_post_event(&qc->push, &ngx_posted_events); ++} ++ ++ ++ngx_int_t ++ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) ++{ ++ size_t shrink; ++ ngx_chain_t *out; ++ ngx_quic_frame_t *nf; ++ ngx_quic_buffer_t qb; ++ ngx_quic_ordered_frame_t *of, *onf; ++ ++ switch (f->type) { ++ case NGX_QUIC_FT_CRYPTO: ++ case NGX_QUIC_FT_STREAM: ++ break; ++ ++ default: ++ return NGX_DECLINED; ++ } ++ ++ if ((size_t) f->len <= len) { ++ return NGX_OK; ++ } ++ ++ shrink = f->len - len; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic split frame now:%uz need:%uz shrink:%uz", ++ f->len, len, shrink); ++ ++ of = &f->u.ord; ++ ++ if (of->length <= shrink) { ++ return NGX_DECLINED; ++ } ++ ++ of->length -= shrink; ++ f->len = ngx_quic_create_frame(NULL, f); ++ ++ if ((size_t) f->len > len) { ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); ++ return NGX_ERROR; ++ } ++ ++ ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); ++ qb.chain = f->data; ++ ++ out = ngx_quic_read_buffer(c, &qb, of->length); ++ if (out == NGX_CHAIN_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ f->data = out; ++ ++ nf = ngx_quic_alloc_frame(c); ++ if (nf == NULL) { ++ return NGX_ERROR; ++ } ++ ++ *nf = *f; ++ onf = &nf->u.ord; ++ onf->offset += of->length; ++ onf->length = shrink; ++ nf->len = ngx_quic_create_frame(NULL, nf); ++ nf->data = qb.chain; ++ ++ if (f->type == NGX_QUIC_FT_STREAM) { ++ f->u.stream.fin = 0; ++ } ++ ++ ngx_queue_insert_after(&f->queue, &nf->queue); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_chain_t * ++ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, size_t len) ++{ ++ ngx_buf_t buf; ++ ngx_chain_t cl, *out; ++ ngx_quic_buffer_t qb; ++ ++ ngx_memzero(&buf, sizeof(ngx_buf_t)); ++ ++ buf.pos = data; ++ buf.last = buf.pos + len; ++ buf.temporary = 1; ++ ++ cl.buf = &buf; ++ cl.next = NULL; ++ ++ ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); ++ ++ if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ out = ngx_quic_read_buffer(c, &qb, len); ++ if (out == NGX_CHAIN_ERROR) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ ngx_quic_free_buffer(c, &qb); ++ ++ return out; ++} ++ ++ ++ngx_chain_t * ++ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit) ++{ ++ uint64_t n; ++ ngx_buf_t *b; ++ ngx_chain_t *out, **ll; ++ ++ out = qb->chain; ++ ++ for (ll = &out; *ll; ll = &(*ll)->next) { ++ b = (*ll)->buf; ++ ++ if (b->sync) { ++ /* hole */ ++ break; ++ } ++ ++ if (limit == 0) { ++ break; ++ } ++ ++ n = b->last - b->pos; ++ ++ if (n > limit) { ++ if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ n = limit; ++ } ++ ++ limit -= n; ++ qb->offset += n; ++ } ++ ++ if (qb->offset >= qb->last_offset) { ++ qb->last_chain = NULL; ++ } ++ ++ qb->chain = *ll; ++ *ll = NULL; ++ ++ return out; ++} ++ ++ ++void ++ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, ++ uint64_t offset) ++{ ++ size_t n; ++ ngx_buf_t *b; ++ ngx_chain_t *cl; ++ ++ while (qb->chain) { ++ if (qb->offset >= offset) { ++ break; ++ } ++ ++ cl = qb->chain; ++ b = cl->buf; ++ n = b->last - b->pos; ++ ++ if (qb->offset + n > offset) { ++ n = offset - qb->offset; ++ b->pos += n; ++ qb->offset += n; ++ break; ++ } ++ ++ qb->offset += n; ++ qb->chain = cl->next; ++ ++ cl->next = NULL; ++ ngx_quic_free_chain(c, cl); ++ } ++ ++ if (qb->chain == NULL) { ++ qb->offset = offset; ++ } ++ ++ if (qb->offset >= qb->last_offset) { ++ qb->last_chain = NULL; ++ } ++} ++ ++ ++ngx_chain_t * ++ngx_quic_alloc_chain(ngx_connection_t *c) ++{ ++ ngx_chain_t *cl; ++ ++ cl = ngx_alloc_chain_link(c->pool); ++ if (cl == NULL) { ++ return NULL; ++ } ++ ++ cl->buf = ngx_quic_alloc_buf(c); ++ if (cl->buf == NULL) { ++ return NULL; ++ } ++ ++ return cl; ++} ++ ++ ++ngx_chain_t * ++ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, ++ ngx_chain_t *in, uint64_t limit, uint64_t offset) ++{ ++ u_char *p; ++ uint64_t n, base; ++ ngx_buf_t *b; ++ ngx_chain_t *cl, **chain; ++ ++ if (qb->last_chain && offset >= qb->last_offset) { ++ base = qb->last_offset; ++ chain = &qb->last_chain; ++ ++ } else { ++ base = qb->offset; ++ chain = &qb->chain; ++ } ++ ++ while (in && limit) { ++ ++ if (offset < base) { ++ n = ngx_min((uint64_t) (in->buf->last - in->buf->pos), ++ ngx_min(base - offset, limit)); ++ ++ in->buf->pos += n; ++ offset += n; ++ limit -= n; ++ ++ if (in->buf->pos == in->buf->last) { ++ in = in->next; ++ } ++ ++ continue; ++ } ++ ++ cl = *chain; ++ ++ if (cl == NULL) { ++ cl = ngx_quic_alloc_chain(c); ++ if (cl == NULL) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ cl->buf->last = cl->buf->end; ++ cl->buf->sync = 1; /* hole */ ++ cl->next = NULL; ++ *chain = cl; ++ } ++ ++ b = cl->buf; ++ n = b->last - b->pos; ++ ++ if (base + n <= offset) { ++ base += n; ++ chain = &cl->next; ++ continue; ++ } ++ ++ if (b->sync && offset > base) { ++ if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ continue; ++ } ++ ++ p = b->pos + (offset - base); ++ ++ while (in) { ++ ++ if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) { ++ in = in->next; ++ continue; ++ } ++ ++ if (p == b->last || limit == 0) { ++ break; ++ } ++ ++ n = ngx_min(b->last - p, in->buf->last - in->buf->pos); ++ n = ngx_min(n, limit); ++ ++ if (b->sync) { ++ ngx_memcpy(p, in->buf->pos, n); ++ qb->size += n; ++ } ++ ++ p += n; ++ in->buf->pos += n; ++ offset += n; ++ limit -= n; ++ } ++ ++ if (b->sync && p == b->last) { ++ b->sync = 0; ++ continue; ++ } ++ ++ if (b->sync && p != b->pos) { ++ if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ b->sync = 0; ++ } ++ } ++ ++ qb->last_offset = base; ++ qb->last_chain = *chain; ++ ++ return in; ++} ++ ++ ++void ++ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb) ++{ ++ ngx_quic_free_chain(c, qb->chain); ++ ++ qb->chain = NULL; ++} ++ ++ ++#if (NGX_DEBUG) ++ ++void ++ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) ++{ ++ u_char *p, *last, *pos, *end; ++ ssize_t n; ++ uint64_t gap, range, largest, smallest; ++ ngx_uint_t i; ++ u_char buf[NGX_MAX_ERROR_STR]; ++ ++ p = buf; ++ last = buf + sizeof(buf); ++ ++ switch (f->type) { ++ ++ case NGX_QUIC_FT_CRYPTO: ++ p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", ++ f->u.crypto.length, f->u.crypto.offset); ++ ++#ifdef NGX_QUIC_DEBUG_FRAMES ++ { ++ ngx_chain_t *cl; ++ ++ p = ngx_slprintf(p, last, " data:"); ++ ++ for (cl = f->data; cl; cl = cl->next) { ++ p = ngx_slprintf(p, last, "%*xs", ++ cl->buf->last - cl->buf->pos, cl->buf->pos); ++ } ++ } ++#endif ++ ++ break; ++ ++ case NGX_QUIC_FT_PADDING: ++ p = ngx_slprintf(p, last, "PADDING"); ++ break; ++ ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ ++ p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", ++ f->u.ack.range_count, f->u.ack.delay); ++ ++ if (f->data) { ++ pos = f->data->buf->pos; ++ end = f->data->buf->last; ++ ++ } else { ++ pos = NULL; ++ end = NULL; ++ } ++ ++ largest = f->u.ack.largest; ++ smallest = f->u.ack.largest - f->u.ack.first_range; ++ ++ if (largest == smallest) { ++ p = ngx_slprintf(p, last, "%uL", largest); ++ ++ } else { ++ p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); ++ } ++ ++ for (i = 0; i < f->u.ack.range_count; i++) { ++ n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); ++ if (n == NGX_ERROR) { ++ break; ++ } ++ ++ pos += n; ++ ++ largest = smallest - gap - 2; ++ smallest = largest - range; ++ ++ if (largest == smallest) { ++ p = ngx_slprintf(p, last, " %uL", largest); ++ ++ } else { ++ p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); ++ } ++ } ++ ++ if (f->type == NGX_QUIC_FT_ACK_ECN) { ++ p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", ++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); ++ } ++ break; ++ ++ case NGX_QUIC_FT_PING: ++ p = ngx_slprintf(p, last, "PING"); ++ break; ++ ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ p = ngx_slprintf(p, last, ++ "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", ++ f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); ++ break; ++ ++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID: ++ p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", ++ f->u.retire_cid.sequence_number); ++ break; ++ ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP: ++ p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", ++ f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", ++ f->u.close.error_code); ++ ++ if (f->u.close.reason.len) { ++ p = ngx_slprintf(p, last, " %V", &f->u.close.reason); ++ } ++ ++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { ++ p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); ++ ++ if (f->u.stream.off) { ++ p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); ++ } ++ ++ if (f->u.stream.len) { ++ p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); ++ } ++ ++ if (f->u.stream.fin) { ++ p = ngx_slprintf(p, last, " fin:1"); ++ } ++ ++#ifdef NGX_QUIC_DEBUG_FRAMES ++ { ++ ngx_chain_t *cl; ++ ++ p = ngx_slprintf(p, last, " data:"); ++ ++ for (cl = f->data; cl; cl = cl->next) { ++ p = ngx_slprintf(p, last, "%*xs", ++ cl->buf->last - cl->buf->pos, cl->buf->pos); ++ } ++ } ++#endif ++ ++ break; ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", ++ f->u.max_data.max_data); ++ break; ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ p = ngx_slprintf(p, last, "RESET_STREAM" ++ " id:0x%xL error_code:0x%xL final_size:0x%xL", ++ f->u.reset_stream.id, f->u.reset_stream.error_code, ++ f->u.reset_stream.final_size); ++ break; ++ ++ case NGX_QUIC_FT_STOP_SENDING: ++ p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", ++ f->u.stop_sending.id, f->u.stop_sending.error_code); ++ break; ++ ++ case NGX_QUIC_FT_STREAMS_BLOCKED: ++ case NGX_QUIC_FT_STREAMS_BLOCKED2: ++ p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", ++ f->u.streams_blocked.limit, f->u.streams_blocked.bidi); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ case NGX_QUIC_FT_MAX_STREAMS2: ++ p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", ++ f->u.max_streams.limit, f->u.max_streams.bidi); ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", ++ f->u.max_stream_data.id, f->u.max_stream_data.limit); ++ break; ++ ++ ++ case NGX_QUIC_FT_DATA_BLOCKED: ++ p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", ++ f->u.data_blocked.limit); ++ break; ++ ++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED: ++ p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", ++ f->u.stream_data_blocked.id, ++ f->u.stream_data_blocked.limit); ++ break; ++ ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", ++ sizeof(f->u.path_challenge.data), ++ f->u.path_challenge.data); ++ break; ++ ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", ++ sizeof(f->u.path_challenge.data), ++ f->u.path_challenge.data); ++ break; ++ ++ case NGX_QUIC_FT_NEW_TOKEN: ++ p = ngx_slprintf(p, last, "NEW_TOKEN"); ++ ++#ifdef NGX_QUIC_DEBUG_FRAMES ++ { ++ ngx_chain_t *cl; ++ ++ p = ngx_slprintf(p, last, " token:"); ++ ++ for (cl = f->data; cl; cl = cl->next) { ++ p = ngx_slprintf(p, last, "%*xs", ++ cl->buf->last - cl->buf->pos, cl->buf->pos); ++ } ++ } ++#endif ++ ++ break; ++ ++ case NGX_QUIC_FT_HANDSHAKE_DONE: ++ p = ngx_slprintf(p, last, "HANDSHAKE DONE"); ++ break; ++ ++ default: ++ p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); ++ break; ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", ++ tx ? "tx" : "rx", ngx_quic_level_name(f->level), ++ p - buf, buf); ++} ++ ++#endif +diff -r ac779115ed6e src/event/quic/ngx_event_quic_frames.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_frames.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,45 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, ++ ngx_quic_frame_t *frame, void *data); ++ ++ ++ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); ++void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); ++void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); ++void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); ++ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, ++ size_t len); ++ ++ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); ++void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); ++ ++ngx_chain_t *ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, ++ size_t len); ++ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, ++ uint64_t limit); ++ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, ++ ngx_chain_t *in, uint64_t limit, uint64_t offset); ++void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, ++ uint64_t offset); ++void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb); ++ ++#if (NGX_DEBUG) ++void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); ++#else ++#define ngx_quic_log_frame(log, f, tx) ++#endif ++ ++#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_migration.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_migration.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,714 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++static void ngx_quic_set_connection_path(ngx_connection_t *c, ++ ngx_quic_path_t *path); ++static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, ++ ngx_quic_path_t *path); ++static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ++ ngx_quic_path_t *path); ++static void ngx_quic_set_path_timer(ngx_connection_t *c); ++static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); ++ ++ ++ngx_int_t ++ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) ++{ ++ ngx_quic_frame_t frame, *fp; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); ++ ++ frame.level = ssl_encryption_application; ++ frame.type = NGX_QUIC_FT_PATH_RESPONSE; ++ frame.u.path_response = *f; ++ ++ /* ++ * RFC 9000, 8.2.2. Path Validation Responses ++ * ++ * A PATH_RESPONSE frame MUST be sent on the network path where the ++ * PATH_CHALLENGE frame was received. ++ */ ++ ++ /* ++ * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame ++ * to at least the smallest allowed maximum datagram size of 1200 bytes. ++ */ ++ if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (pkt->path == qc->path) { ++ /* ++ * RFC 9000, 9.3.3. Off-Path Packet Forwarding ++ * ++ * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD ++ * send a non-probing packet in response. ++ */ ++ ++ fp = ngx_quic_alloc_frame(c); ++ if (fp == NULL) { ++ return NGX_ERROR; ++ } ++ ++ fp->level = ssl_encryption_application; ++ fp->type = NGX_QUIC_FT_PING; ++ ++ ngx_quic_queue_frame(qc, fp); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_path_response_frame(ngx_connection_t *c, ++ ngx_quic_path_challenge_frame_t *f) ++{ ++ ngx_uint_t rst; ++ ngx_queue_t *q; ++ ngx_quic_path_t *path, *prev; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ /* ++ * RFC 9000, 8.2.3. Successful Path Validation ++ * ++ * A PATH_RESPONSE frame received on any network path validates the path ++ * on which the PATH_CHALLENGE was sent. ++ */ ++ ++ for (q = ngx_queue_head(&qc->paths); ++ q != ngx_queue_sentinel(&qc->paths); ++ q = ngx_queue_next(q)) ++ { ++ path = ngx_queue_data(q, ngx_quic_path_t, queue); ++ ++ if (!path->validating) { ++ continue; ++ } ++ ++ if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0 ++ || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0) ++ { ++ goto valid; ++ } ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stale PATH_RESPONSE ignored"); ++ ++ return NGX_OK; ++ ++valid: ++ ++ /* ++ * RFC 9000, 9.4. Loss Detection and Congestion Control ++ * ++ * On confirming a peer's ownership of its new address, ++ * an endpoint MUST immediately reset the congestion controller ++ * and round-trip time estimator for the new path to initial values ++ * unless the only change in the peer's address is its port number. ++ */ ++ ++ rst = 1; ++ ++ prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); ++ ++ if (prev != NULL) { ++ ++ if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, ++ path->sockaddr, path->socklen, 0) ++ == NGX_OK) ++ { ++ /* address did not change */ ++ rst = 0; ++ } ++ } ++ ++ if (rst) { ++ ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); ++ ++ qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, ++ ngx_max(2 * qc->tp.max_udp_payload_size, ++ 14720)); ++ qc->congestion.ssthresh = (size_t) -1; ++ qc->congestion.recovery_start = ngx_current_msec; ++ } ++ ++ /* ++ * RFC 9000, 9.3. Responding to Connection Migration ++ * ++ * After verifying a new client address, the server SHOULD ++ * send new address validation tokens (Section 8) to the client. ++ */ ++ ++ if (ngx_quic_send_new_token(c, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic path seq:%uL addr:%V successfully validated", ++ path->seqnum, &path->addr_text); ++ ++ ngx_quic_path_dbg(c, "is validated", path); ++ ++ path->validated = 1; ++ path->validating = 0; ++ path->limited = 0; ++ ++ ngx_quic_set_path_timer(c); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_quic_path_t * ++ngx_quic_new_path(ngx_connection_t *c, ++ struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid) ++{ ++ ngx_queue_t *q; ++ ngx_quic_path_t *path; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (!ngx_queue_empty(&qc->free_paths)) { ++ ++ q = ngx_queue_head(&qc->free_paths); ++ path = ngx_queue_data(q, ngx_quic_path_t, queue); ++ ++ ngx_queue_remove(&path->queue); ++ ++ ngx_memzero(path, sizeof(ngx_quic_path_t)); ++ ++ } else { ++ ++ path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); ++ if (path == NULL) { ++ return NULL; ++ } ++ } ++ ++ ngx_queue_insert_tail(&qc->paths, &path->queue); ++ ++ path->cid = cid; ++ cid->used = 1; ++ ++ path->limited = 1; ++ ++ path->seqnum = qc->path_seqnum++; ++ ++ path->sockaddr = &path->sa.sockaddr; ++ path->socklen = socklen; ++ ngx_memcpy(path->sockaddr, sockaddr, socklen); ++ ++ path->addr_text.data = path->text; ++ path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, ++ NGX_SOCKADDR_STRLEN, 1); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic path seq:%uL created addr:%V", ++ path->seqnum, &path->addr_text); ++ return path; ++} ++ ++ ++static ngx_quic_path_t * ++ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag) ++{ ++ ngx_queue_t *q; ++ ngx_quic_path_t *path; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ for (q = ngx_queue_head(&qc->paths); ++ q != ngx_queue_sentinel(&qc->paths); ++ q = ngx_queue_next(q)) ++ { ++ path = ngx_queue_data(q, ngx_quic_path_t, queue); ++ ++ if (path->tag == tag) { ++ return path; ++ } ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_int_t ++ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ off_t len; ++ ngx_queue_t *q; ++ ngx_quic_path_t *path, *probe; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_client_id_t *cid; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ qsock = ngx_quic_get_socket(c); ++ ++ len = pkt->raw->last - pkt->raw->start; ++ ++ if (c->udp->buffer == NULL) { ++ /* first ever packet in connection, path already exists */ ++ path = qc->path; ++ goto update; ++ } ++ ++ probe = NULL; ++ ++ for (q = ngx_queue_head(&qc->paths); ++ q != ngx_queue_sentinel(&qc->paths); ++ q = ngx_queue_next(q)) ++ { ++ path = ngx_queue_data(q, ngx_quic_path_t, queue); ++ ++ if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, ++ path->sockaddr, path->socklen, 1) ++ == NGX_OK) ++ { ++ goto update; ++ } ++ ++ if (path->tag == NGX_QUIC_PATH_PROBE) { ++ probe = path; ++ } ++ } ++ ++ /* packet from new path, drop current probe, if any */ ++ ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ ++ /* ++ * only accept highest-numbered packets to prevent connection id ++ * exhaustion by excessive probing packets from unknown paths ++ */ ++ if (pkt->pn != ctx->largest_pn) { ++ return NGX_DONE; ++ } ++ ++ if (probe && ngx_quic_free_path(c, probe) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ /* new path requires new client id */ ++ cid = ngx_quic_next_client_id(c); ++ if (cid == NULL) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic no available client ids for new path"); ++ /* stop processing of this datagram */ ++ return NGX_DONE; ++ } ++ ++ path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid); ++ if (path == NULL) { ++ return NGX_ERROR; ++ } ++ ++ path->tag = NGX_QUIC_PATH_PROBE; ++ ++ /* ++ * client arrived using new path and previously seen DCID, ++ * this indicates NAT rebinding (or bad client) ++ */ ++ if (qsock->used) { ++ pkt->rebound = 1; ++ } ++ ++update: ++ ++ qsock->used = 1; ++ pkt->path = path; ++ ++ /* TODO: this may be too late in some cases; ++ * for example, if error happens during decrypt(), we cannot ++ * send CC, if error happens in 1st packet, due to amplification ++ * limit, because path->received = 0 ++ * ++ * should we account garbage as received or only decrypting packets? ++ */ ++ path->received += len; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet len:%O via sock seq:%L path seq:%uL", ++ len, (int64_t) qsock->sid.seqnum, path->seqnum); ++ ngx_quic_path_dbg(c, "status", path); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_queue_remove(&path->queue); ++ ngx_queue_insert_head(&qc->free_paths, &path->queue); ++ ++ /* ++ * invalidate CID that is no longer usable for any other path; ++ * this also requests new CIDs from client ++ */ ++ if (path->cid) { ++ if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic path seq:%uL addr:%V retired", ++ path->seqnum, &path->addr_text); ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ size_t len; ++ ++ ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); ++ c->socklen = path->socklen; ++ ++ if (c->addr_text.data) { ++ len = ngx_min(c->addr_text.len, path->addr_text.len); ++ ++ ngx_memcpy(c->addr_text.data, path->addr_text.data, len); ++ c->addr_text.len = len; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic send path set to seq:%uL addr:%V", ++ path->seqnum, &path->addr_text); ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) ++{ ++ ngx_quic_path_t *next, *bkp; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ /* got non-probing packet via non-active path */ ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ ++ /* ++ * RFC 9000, 9.3. Responding to Connection Migration ++ * ++ * An endpoint only changes the address to which it sends packets in ++ * response to the highest-numbered non-probing packet. ++ */ ++ if (pkt->pn != ctx->largest_pn) { ++ return NGX_OK; ++ } ++ ++ next = pkt->path; ++ ++ /* ++ * RFC 9000, 9.3.3: ++ * ++ * In response to an apparent migration, endpoints MUST validate the ++ * previously active path using a PATH_CHALLENGE frame. ++ */ ++ if (pkt->rebound) { ++ ++ /* NAT rebinding: client uses new path with old SID */ ++ if (ngx_quic_validate_path(c, qc->path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (qc->path->validated) { ++ ++ if (next->tag != NGX_QUIC_PATH_BACKUP) { ++ /* can delete backup path, if any */ ++ bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); ++ ++ if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ qc->path->tag = NGX_QUIC_PATH_BACKUP; ++ ngx_quic_path_dbg(c, "is now backup", qc->path); ++ ++ } else { ++ if (ngx_quic_free_path(c, qc->path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ /* switch active path to migrated */ ++ qc->path = next; ++ qc->path->tag = NGX_QUIC_PATH_ACTIVE; ++ ++ ngx_quic_set_connection_path(c, next); ++ ++ if (!next->validated && !next->validating) { ++ if (ngx_quic_validate_path(c, next) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic migrated to path seq:%uL addr:%V", ++ qc->path->seqnum, &qc->path->addr_text); ++ ++ ngx_quic_path_dbg(c, "is now active", qc->path); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ ngx_msec_t pto; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic initiated validation of path seq:%uL", path->seqnum); ++ ++ path->validating = 1; ++ path->tries = 0; ++ ++ if (RAND_bytes(path->challenge1, 8) != 1) { ++ return NGX_ERROR; ++ } ++ ++ if (RAND_bytes(path->challenge2, 8) != 1) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_quic_send_path_challenge(c, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); ++ pto = ngx_max(ngx_quic_pto(c, ctx), 1000); ++ ++ path->expires = ngx_current_msec + pto; ++ ++ ngx_quic_set_path_timer(c); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ ngx_quic_frame_t frame; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic path seq:%uL send path_challenge tries:%ui", ++ path->seqnum, path->tries); ++ ++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); ++ ++ frame.level = ssl_encryption_application; ++ frame.type = NGX_QUIC_FT_PATH_CHALLENGE; ++ ++ ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); ++ ++ /* ++ * RFC 9000, 8.2.1. Initiating Path Validation ++ * ++ * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame ++ * to at least the smallest allowed maximum datagram size of 1200 bytes, ++ * unless the anti-amplification limit for the path does not permit ++ * sending a datagram of this size. ++ */ ++ ++ /* same applies to PATH_RESPONSE frames */ ++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); ++ ++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_set_path_timer(ngx_connection_t *c) ++{ ++ ngx_msec_t now; ++ ngx_queue_t *q; ++ ngx_msec_int_t left, next; ++ ngx_quic_path_t *path; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ now = ngx_current_msec; ++ next = -1; ++ ++ for (q = ngx_queue_head(&qc->paths); ++ q != ngx_queue_sentinel(&qc->paths); ++ q = ngx_queue_next(q)) ++ { ++ path = ngx_queue_data(q, ngx_quic_path_t, queue); ++ ++ if (!path->validating) { ++ continue; ++ } ++ ++ left = path->expires - now; ++ left = ngx_max(left, 1); ++ ++ if (next == -1 || left < next) { ++ next = left; ++ } ++ } ++ ++ if (next != -1) { ++ ngx_add_timer(&qc->path_validation, next); ++ ++ } else if (qc->path_validation.timer_set) { ++ ngx_del_timer(&qc->path_validation); ++ } ++} ++ ++ ++void ++ngx_quic_path_validation_handler(ngx_event_t *ev) ++{ ++ ngx_msec_t now; ++ ngx_queue_t *q; ++ ngx_msec_int_t left, next, pto; ++ ngx_quic_path_t *path, *bkp; ++ ngx_connection_t *c; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ c = ev->data; ++ qc = ngx_quic_get_connection(c); ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); ++ ++ next = -1; ++ now = ngx_current_msec; ++ ++ q = ngx_queue_head(&qc->paths); ++ ++ while (q != ngx_queue_sentinel(&qc->paths)) { ++ ++ path = ngx_queue_data(q, ngx_quic_path_t, queue); ++ q = ngx_queue_next(q); ++ ++ if (!path->validating) { ++ continue; ++ } ++ ++ left = path->expires - now; ++ ++ if (left > 0) { ++ ++ if (next == -1 || left < next) { ++ next = left; ++ } ++ ++ continue; ++ } ++ ++ if (++path->tries < NGX_QUIC_PATH_RETRIES) { ++ pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries; ++ ++ path->expires = ngx_current_msec + pto; ++ ++ if (next == -1 || pto < next) { ++ next = pto; ++ } ++ ++ /* retransmit */ ++ (void) ngx_quic_send_path_challenge(c, path); ++ ++ continue; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, ++ "quic path seq:%uL validation failed", path->seqnum); ++ ++ /* found expired path */ ++ ++ path->validated = 0; ++ path->validating = 0; ++ path->limited = 1; ++ ++ ++ /* RFC 9000, 9.3.2. On-Path Address Spoofing ++ * ++ * To protect the connection from failing due to such a spurious ++ * migration, an endpoint MUST revert to using the last validated ++ * peer address when validation of a new peer address fails. ++ */ ++ ++ if (qc->path == path) { ++ /* active path validation failed */ ++ ++ bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); ++ ++ if (bkp == NULL) { ++ qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; ++ qc->error_reason = "no viable path"; ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ ++ qc->path = bkp; ++ qc->path->tag = NGX_QUIC_PATH_ACTIVE; ++ ++ ngx_quic_set_connection_path(c, qc->path); ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic path seq:%uL addr:%V is restored from backup", ++ qc->path->seqnum, &qc->path->addr_text); ++ ++ ngx_quic_path_dbg(c, "is active", qc->path); ++ } ++ ++ if (ngx_quic_free_path(c, path) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ } ++ ++ if (next != -1) { ++ ngx_add_timer(&qc->path_validation, next); ++ } ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_migration.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_migration.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,42 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++#define NGX_QUIC_PATH_RETRIES 3 ++ ++#define NGX_QUIC_PATH_PROBE 0 ++#define NGX_QUIC_PATH_ACTIVE 1 ++#define NGX_QUIC_PATH_BACKUP 2 ++ ++#define ngx_quic_path_dbg(c, msg, path) \ ++ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \ ++ "quic path seq:%uL %s sent:%O recvd:%O state:%s%s%s", \ ++ path->seqnum, msg, path->sent, path->received, \ ++ path->limited ? "L" : "", path->validated ? "V": "N", \ ++ path->validating ? "R": ""); ++ ++ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); ++ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, ++ ngx_quic_path_challenge_frame_t *f); ++ ++ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c, ++ struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid); ++ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path); ++ ++ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt); ++ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, ++ ngx_quic_header_t *pkt); ++ ++void ngx_quic_path_validation_handler(ngx_event_t *ev); ++ ++#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_openssl_compat.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_openssl_compat.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,646 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#if (NGX_QUIC_OPENSSL_COMPAT) ++ ++#define NGX_QUIC_COMPAT_RECORD_SIZE 1024 ++ ++#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39 ++ ++#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET" ++#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET" ++#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0" ++#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0" ++ ++ ++typedef struct { ++ ngx_quic_secret_t secret; ++ ngx_uint_t cipher; ++} ngx_quic_compat_keys_t; ++ ++ ++typedef struct { ++ ngx_log_t *log; ++ ++ u_char type; ++ ngx_str_t payload; ++ uint64_t number; ++ ngx_quic_compat_keys_t *keys; ++ ++ enum ssl_encryption_level_t level; ++} ngx_quic_compat_record_t; ++ ++ ++struct ngx_quic_compat_s { ++ const SSL_QUIC_METHOD *method; ++ ++ enum ssl_encryption_level_t write_level; ++ enum ssl_encryption_level_t read_level; ++ ++ uint64_t read_record; ++ ngx_quic_compat_keys_t keys; ++ ++ ngx_str_t tp; ++ ngx_str_t ctp; ++}; ++ ++ ++static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line); ++static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log, ++ ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, ++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); ++static int ngx_quic_compat_add_transport_params_callback(SSL *ssl, ++ unsigned int ext_type, unsigned int context, const unsigned char **out, ++ size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg); ++static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl, ++ unsigned int ext_type, unsigned int context, const unsigned char *in, ++ size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg); ++static void ngx_quic_compat_message_callback(int write_p, int version, ++ int content_type, const void *buf, size_t len, SSL *ssl, void *arg); ++static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, ++ u_char *out, ngx_uint_t plain); ++static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ++ ngx_str_t *res); ++ ++ ++ngx_int_t ++ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx) ++{ ++ SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback); ++ ++ if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) { ++ return NGX_OK; ++ } ++ ++ if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT, ++ SSL_EXT_CLIENT_HELLO ++ |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, ++ ngx_quic_compat_add_transport_params_callback, ++ NULL, ++ NULL, ++ ngx_quic_compat_parse_transport_params_callback, ++ NULL) ++ == 0) ++ { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "SSL_CTX_add_custom_ext() failed"); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) ++{ ++ u_char ch, *p, *start, value; ++ size_t n; ++ ngx_uint_t write; ++ const SSL_CIPHER *cipher; ++ ngx_quic_compat_t *com; ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ enum ssl_encryption_level_t level; ++ u_char secret[EVP_MAX_MD_SIZE]; ++ ++ c = ngx_ssl_get_connection(ssl); ++ if (c->type != SOCK_DGRAM) { ++ return; ++ } ++ ++ p = (u_char *) line; ++ ++ for (start = p; *p && *p != ' '; p++); ++ ++ n = p - start; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic compat secret %*s", n, start); ++ ++ if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1 ++ && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0) ++ { ++ level = ssl_encryption_handshake; ++ write = 0; ++ ++ } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1 ++ && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0) ++ { ++ level = ssl_encryption_handshake; ++ write = 1; ++ ++ } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1 ++ && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n) ++ == 0) ++ { ++ level = ssl_encryption_application; ++ write = 0; ++ ++ } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1 ++ && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n) ++ == 0) ++ { ++ level = ssl_encryption_application; ++ write = 1; ++ ++ } else { ++ return; ++ } ++ ++ if (*p++ == '\0') { ++ return; ++ } ++ ++ for ( /* void */ ; *p && *p != ' '; p++); ++ ++ if (*p++ == '\0') { ++ return; ++ } ++ ++ for (n = 0, start = p; *p; p++) { ++ ch = *p; ++ ++ if (ch >= '0' && ch <= '9') { ++ value = ch - '0'; ++ goto next; ++ } ++ ++ ch = (u_char) (ch | 0x20); ++ ++ if (ch >= 'a' && ch <= 'f') { ++ value = ch - 'a' + 10; ++ goto next; ++ } ++ ++ ngx_log_error(NGX_LOG_EMERG, c->log, 0, ++ "invalid OpenSSL QUIC secret format"); ++ ++ return; ++ ++ next: ++ ++ if ((p - start) % 2) { ++ secret[n++] += value; ++ ++ } else { ++ if (n >= EVP_MAX_MD_SIZE) { ++ ngx_log_error(NGX_LOG_EMERG, c->log, 0, ++ "too big OpenSSL QUIC secret"); ++ return; ++ } ++ ++ secret[n] = (value << 4); ++ } ++ } ++ ++ qc = ngx_quic_get_connection(c); ++ com = qc->compat; ++ cipher = SSL_get_current_cipher(ssl); ++ ++ if (write) { ++ com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); ++ com->write_level = level; ++ ++ } else { ++ com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); ++ com->read_level = level; ++ com->read_record = 0; ++ ++ (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level, ++ cipher, secret, n); ++ } ++} ++ ++ ++static ngx_int_t ++ngx_quic_compat_set_encryption_secret(ngx_log_t *log, ++ ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, ++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) ++{ ++ ngx_int_t key_len; ++ ngx_str_t secret_str; ++ ngx_uint_t i; ++ ngx_quic_hkdf_t seq[2]; ++ ngx_quic_secret_t *peer_secret; ++ ngx_quic_ciphers_t ciphers; ++ ++ peer_secret = &keys->secret; ++ ++ keys->cipher = SSL_CIPHER_get_id(cipher); ++ ++ key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); ++ ++ if (key_len == NGX_ERROR) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); ++ return NGX_ERROR; ++ } ++ ++ if (sizeof(peer_secret->secret.data) < secret_len) { ++ ngx_log_error(NGX_LOG_ALERT, log, 0, ++ "unexpected secret len: %uz", secret_len); ++ return NGX_ERROR; ++ } ++ ++ peer_secret->secret.len = secret_len; ++ ngx_memcpy(peer_secret->secret.data, secret, secret_len); ++ ++ peer_secret->key.len = key_len; ++ peer_secret->iv.len = NGX_QUIC_IV_LEN; ++ ++ secret_str.len = secret_len; ++ secret_str.data = (u_char *) secret; ++ ++ ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str); ++ ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str); ++ ++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { ++ if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static int ++ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type, ++ unsigned int context, const unsigned char **out, size_t *outlen, X509 *x, ++ size_t chainidx, int *al, void *add_arg) ++{ ++ ngx_connection_t *c; ++ ngx_quic_compat_t *com; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection(ssl); ++ if (c->type != SOCK_DGRAM) { ++ return 0; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic compat add transport params"); ++ ++ qc = ngx_quic_get_connection(c); ++ com = qc->compat; ++ ++ *out = com->tp.data; ++ *outlen = com->tp.len; ++ ++ return 1; ++} ++ ++ ++static int ++ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type, ++ unsigned int context, const unsigned char *in, size_t inlen, X509 *x, ++ size_t chainidx, int *al, void *parse_arg) ++{ ++ u_char *p; ++ ngx_connection_t *c; ++ ngx_quic_compat_t *com; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection(ssl); ++ if (c->type != SOCK_DGRAM) { ++ return 0; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic compat parse transport params"); ++ ++ qc = ngx_quic_get_connection(c); ++ com = qc->compat; ++ ++ p = ngx_pnalloc(c->pool, inlen); ++ if (p == NULL) { ++ return 0; ++ } ++ ++ ngx_memcpy(p, in, inlen); ++ ++ com->ctp.data = p; ++ com->ctp.len = inlen; ++ ++ return 1; ++} ++ ++ ++int ++SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) ++{ ++ BIO *rbio, *wbio; ++ ngx_connection_t *c; ++ ngx_quic_compat_t *com; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection(ssl); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method"); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t)); ++ if (qc->compat == NULL) { ++ return 0; ++ } ++ ++ com = qc->compat; ++ com->method = quic_method; ++ ++ rbio = BIO_new(BIO_s_mem()); ++ if (rbio == NULL) { ++ return 0; ++ } ++ ++ wbio = BIO_new(BIO_s_null()); ++ if (wbio == NULL) { ++ return 0; ++ } ++ ++ SSL_set_bio(ssl, rbio, wbio); ++ ++ SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback); ++ ++ /* early data is not supported */ ++ SSL_set_max_early_data(ssl, 0); ++ ++ return 1; ++} ++ ++ ++static void ++ngx_quic_compat_message_callback(int write_p, int version, int content_type, ++ const void *buf, size_t len, SSL *ssl, void *arg) ++{ ++ ngx_uint_t alert; ++ ngx_connection_t *c; ++ ngx_quic_compat_t *com; ++ ngx_quic_connection_t *qc; ++ enum ssl_encryption_level_t level; ++ ++ if (!write_p) { ++ return; ++ } ++ ++ c = ngx_ssl_get_connection(ssl); ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc == NULL) { ++ /* closing */ ++ return; ++ } ++ ++ com = qc->compat; ++ level = com->write_level; ++ ++ switch (content_type) { ++ ++ case SSL3_RT_HANDSHAKE: ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic compat tx %s len:%uz ", ++ ngx_quic_level_name(level), len); ++ ++ (void) com->method->add_handshake_data(ssl, level, buf, len); ++ ++ break; ++ ++ case SSL3_RT_ALERT: ++ if (len >= 2) { ++ alert = ((u_char *) buf)[1]; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic compat %s alert:%ui len:%uz ", ++ ngx_quic_level_name(level), alert, len); ++ ++ (void) com->method->send_alert(ssl, level, alert); ++ } ++ ++ break; ++ } ++} ++ ++ ++int ++SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, ++ const uint8_t *data, size_t len) ++{ ++ BIO *rbio; ++ size_t n; ++ u_char *p; ++ ngx_str_t res; ++ ngx_connection_t *c; ++ ngx_quic_compat_t *com; ++ ngx_quic_connection_t *qc; ++ ngx_quic_compat_record_t rec; ++ u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1]; ++ u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1 ++ + SSL3_RT_HEADER_LENGTH ++ + EVP_GCM_TLS_TAG_LEN]; ++ ++ c = ngx_ssl_get_connection(ssl); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz", ++ ngx_quic_level_name(level), len); ++ ++ qc = ngx_quic_get_connection(c); ++ com = qc->compat; ++ rbio = SSL_get_rbio(ssl); ++ ++ while (len) { ++ ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t)); ++ ++ rec.type = SSL3_RT_HANDSHAKE; ++ rec.log = c->log; ++ rec.number = com->read_record++; ++ rec.keys = &com->keys; ++ ++ if (level == ssl_encryption_initial) { ++ n = ngx_min(len, 65535); ++ ++ rec.payload.len = n; ++ rec.payload.data = (u_char *) data; ++ ++ ngx_quic_compat_create_header(&rec, out, 1); ++ ++ BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH); ++ BIO_write(rbio, data, n); ++ ++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) ++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic compat record len:%uz %*xs%*xs", ++ n + SSL3_RT_HEADER_LENGTH, ++ (size_t) SSL3_RT_HEADER_LENGTH, out, n, data); ++#endif ++ ++ } else { ++ n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE); ++ ++ p = ngx_cpymem(in, data, n); ++ *p++ = SSL3_RT_HANDSHAKE; ++ ++ rec.payload.len = p - in; ++ rec.payload.data = in; ++ ++ res.data = out; ++ ++ if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) { ++ return 0; ++ } ++ ++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic compat record len:%uz %xV", res.len, &res); ++#endif ++ ++ BIO_write(rbio, res.data, res.len); ++ } ++ ++ data += n; ++ len -= n; ++ } ++ ++ return 1; ++} ++ ++ ++static size_t ++ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out, ++ ngx_uint_t plain) ++{ ++ u_char type; ++ size_t len; ++ ++ len = rec->payload.len; ++ ++ if (plain) { ++ type = rec->type; ++ ++ } else { ++ type = SSL3_RT_APPLICATION_DATA; ++ len += EVP_GCM_TLS_TAG_LEN; ++ } ++ ++ out[0] = type; ++ out[1] = 0x03; ++ out[2] = 0x03; ++ out[3] = (len >> 8); ++ out[4] = len; ++ ++ return 5; ++} ++ ++ ++static ngx_int_t ++ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res) ++{ ++ ngx_str_t ad, out; ++ ngx_quic_secret_t *secret; ++ ngx_quic_ciphers_t ciphers; ++ u_char nonce[NGX_QUIC_IV_LEN]; ++ ++ ad.data = res->data; ++ ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); ++ ++ out.len = rec->payload.len + EVP_GCM_TLS_TAG_LEN; ++ out.data = res->data + ad.len; ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0, ++ "quic compat ad len:%uz %xV", ad.len, &ad); ++#endif ++ ++ if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == NGX_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ secret = &rec->keys->secret; ++ ++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ++ ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); ++ ++ if (ngx_quic_tls_seal(ciphers.c, secret, &out, ++ nonce, &rec->payload, &ad, rec->log) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ res->len = ad.len + out.len; ++ ++ return NGX_OK; ++} ++ ++ ++enum ssl_encryption_level_t ++SSL_quic_read_level(const SSL *ssl) ++{ ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection(ssl); ++ qc = ngx_quic_get_connection(c); ++ ++ return qc->compat->read_level; ++} ++ ++ ++enum ssl_encryption_level_t ++SSL_quic_write_level(const SSL *ssl) ++{ ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection(ssl); ++ qc = ngx_quic_get_connection(c); ++ ++ return qc->compat->write_level; ++} ++ ++ ++int ++SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, ++ size_t params_len) ++{ ++ ngx_connection_t *c; ++ ngx_quic_compat_t *com; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection(ssl); ++ qc = ngx_quic_get_connection(c); ++ com = qc->compat; ++ ++ com->tp.len = params_len; ++ com->tp.data = (u_char *) params; ++ ++ return 1; ++} ++ ++ ++void ++SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params, ++ size_t *out_params_len) ++{ ++ ngx_connection_t *c; ++ ngx_quic_compat_t *com; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection(ssl); ++ qc = ngx_quic_get_connection(c); ++ com = qc->compat; ++ ++ *out_params = com->ctp.data; ++ *out_params_len = com->ctp.len; ++} ++ ++#endif /* NGX_QUIC_OPENSSL_COMPAT */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_openssl_compat.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_openssl_compat.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,60 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ ++ ++#ifdef TLSEXT_TYPE_quic_transport_parameters ++#undef NGX_QUIC_OPENSSL_COMPAT ++#else ++ ++ ++#include ++#include ++ ++ ++typedef struct ngx_quic_compat_s ngx_quic_compat_t; ++ ++ ++enum ssl_encryption_level_t { ++ ssl_encryption_initial = 0, ++ ssl_encryption_early_data, ++ ssl_encryption_handshake, ++ ssl_encryption_application ++}; ++ ++ ++typedef struct ssl_quic_method_st { ++ int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level, ++ const SSL_CIPHER *cipher, ++ const uint8_t *rsecret, size_t secret_len); ++ int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level, ++ const SSL_CIPHER *cipher, ++ const uint8_t *wsecret, size_t secret_len); ++ int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level, ++ const uint8_t *data, size_t len); ++ int (*flush_flight)(SSL *ssl); ++ int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, ++ uint8_t alert); ++} SSL_QUIC_METHOD; ++ ++ ++ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx); ++ ++int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method); ++int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, ++ const uint8_t *data, size_t len); ++enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl); ++enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl); ++int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, ++ size_t params_len); ++void SSL_get_peer_quic_transport_params(const SSL *ssl, ++ const uint8_t **out_params, size_t *out_params_len); ++ ++ ++#endif /* TLSEXT_TYPE_quic_transport_parameters */ ++ ++#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_output.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_output.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,1293 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 ++#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 ++ ++#define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */ ++#define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */ ++ ++#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ ++#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ ++#define NGX_QUIC_RETRY_BUFFER_SIZE 256 ++ /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ ++ ++/* ++ * RFC 9000, 10.3. Stateless Reset ++ * ++ * Endpoints MUST discard packets that are too small to be valid QUIC ++ * packets. With the set of AEAD functions defined in [QUIC-TLS], ++ * short header packets that are smaller than 21 bytes are never valid. ++ */ ++#define NGX_QUIC_MIN_PKT_LEN 21 ++ ++#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */ ++#define NGX_QUIC_MAX_SR_PACKET 1200 ++ ++#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ ++ ++#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */ ++ ++ ++static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c); ++static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); ++static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t pnum); ++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) ++static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c); ++static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c); ++static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, ++ size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment); ++#endif ++static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ++ ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); ++static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ ngx_quic_header_t *pkt, ngx_quic_path_t *path); ++static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); ++static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, ++ struct sockaddr *sockaddr, socklen_t socklen); ++static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ++ ngx_quic_send_ctx_t *ctx); ++static size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, ++ size_t size); ++ ++ ++size_t ++ngx_quic_max_udp_payload(ngx_connection_t *c) ++{ ++ /* TODO: path MTU discovery */ ++ ++#if (NGX_HAVE_INET6) ++ if (c->sockaddr->sa_family == AF_INET6) { ++ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6; ++ } ++#endif ++ ++ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT; ++} ++ ++ ++ngx_int_t ++ngx_quic_output(ngx_connection_t *c) ++{ ++ size_t in_flight; ++ ngx_int_t rc; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ c->log->action = "sending frames"; ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ ++ in_flight = cg->in_flight; ++ ++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) ++ if (ngx_quic_allow_segmentation(c)) { ++ rc = ngx_quic_create_segments(c); ++ } else ++#endif ++ { ++ rc = ngx_quic_create_datagrams(c); ++ } ++ ++ if (rc != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (in_flight == cg->in_flight || qc->closing) { ++ /* no ack-eliciting data was sent or we are done */ ++ return NGX_OK; ++ } ++ ++ if (!qc->send_timer_set) { ++ qc->send_timer_set = 1; ++ ngx_add_timer(c->read, qc->tp.max_idle_timeout); ++ } ++ ++ ngx_quic_set_lost_timer(c); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_create_datagrams(ngx_connection_t *c) ++{ ++ size_t len, min; ++ ssize_t n; ++ u_char *p; ++ uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST]; ++ ngx_uint_t i, pad; ++ ngx_quic_path_t *path; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ path = qc->path; ++ ++ while (cg->in_flight < cg->window) { ++ ++ p = dst; ++ ++ len = ngx_min(qc->ctp.max_udp_payload_size, ++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); ++ ++ len = ngx_quic_path_limit(c, path, len); ++ ++ pad = ngx_quic_get_padding_level(c); ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ++ ctx = &qc->send_ctx[i]; ++ ++ preserved_pnum[i] = ctx->pnum; ++ ++ if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) ++ ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; ++ ++ if (min > len) { ++ /* padding can't be applied - avoid sending the packet */ ++ ++ while (i-- > 0) { ++ ctx = &qc->send_ctx[i]; ++ ngx_quic_revert_send(c, ctx, preserved_pnum[i]); ++ } ++ ++ return NGX_OK; ++ } ++ ++ n = ngx_quic_output_packet(c, ctx, p, len, min); ++ if (n == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ p += n; ++ len -= n; ++ } ++ ++ len = p - dst; ++ if (len == 0) { ++ break; ++ } ++ ++ n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen); ++ ++ if (n == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (n == NGX_AGAIN) { ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]); ++ } ++ ++ ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); ++ break; ++ } ++ ++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ngx_quic_commit_send(c, &qc->send_ctx[i]); ++ } ++ ++ path->sent += len; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ cg = &qc->congestion; ++ ++ while (!ngx_queue_empty(&ctx->sending)) { ++ ++ q = ngx_queue_head(&ctx->sending); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ ngx_queue_remove(q); ++ ++ if (f->pkt_need_ack && !qc->closing) { ++ ngx_queue_insert_tail(&ctx->sent, q); ++ ++ cg->in_flight += f->plen; ++ ++ } else { ++ ngx_quic_free_frame(c, f); ++ } ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic congestion send if:%uz", cg->in_flight); ++} ++ ++ ++static void ++ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t pnum) ++{ ++ ngx_queue_t *q; ++ ++ while (!ngx_queue_empty(&ctx->sending)) { ++ ++ q = ngx_queue_last(&ctx->sending); ++ ngx_queue_remove(q); ++ ngx_queue_insert_head(&ctx->frames, q); ++ } ++ ++ ctx->pnum = pnum; ++} ++ ++ ++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) ++ ++static ngx_uint_t ++ngx_quic_allow_segmentation(ngx_connection_t *c) ++{ ++ size_t bytes, len; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (!qc->conf->gso_enabled) { ++ return 0; ++ } ++ ++ if (qc->path->limited) { ++ /* don't even try to be faster on non-validated paths */ ++ return 0; ++ } ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); ++ if (!ngx_queue_empty(&ctx->frames)) { ++ return 0; ++ } ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); ++ if (!ngx_queue_empty(&ctx->frames)) { ++ return 0; ++ } ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); ++ ++ bytes = 0; ++ ++ len = ngx_min(qc->ctp.max_udp_payload_size, ++ NGX_QUIC_MAX_UDP_SEGMENT_BUF); ++ ++ for (q = ngx_queue_head(&ctx->frames); ++ q != ngx_queue_sentinel(&ctx->frames); ++ q = ngx_queue_next(q)) ++ { ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ bytes += f->len; ++ ++ if (bytes > len * 3) { ++ /* require at least ~3 full packets to batch */ ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++ ++static ngx_int_t ++ngx_quic_create_segments(ngx_connection_t *c) ++{ ++ size_t len, segsize; ++ ssize_t n; ++ u_char *p, *end; ++ uint64_t preserved_pnum; ++ ngx_uint_t nseg; ++ ngx_quic_path_t *path; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_congestion_t *cg; ++ ngx_quic_connection_t *qc; ++ static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF]; ++ ++ qc = ngx_quic_get_connection(c); ++ cg = &qc->congestion; ++ path = qc->path; ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); ++ ++ if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ segsize = ngx_min(qc->ctp.max_udp_payload_size, ++ NGX_QUIC_MAX_UDP_SEGMENT_BUF); ++ p = dst; ++ end = dst + sizeof(dst); ++ ++ nseg = 0; ++ ++ preserved_pnum = ctx->pnum; ++ ++ for ( ;; ) { ++ ++ len = ngx_min(segsize, (size_t) (end - p)); ++ ++ if (len && cg->in_flight < cg->window) { ++ ++ n = ngx_quic_output_packet(c, ctx, p, len, len); ++ if (n == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (n) { ++ p += n; ++ nseg++; ++ } ++ ++ } else { ++ n = 0; ++ } ++ ++ if (p == dst) { ++ break; ++ } ++ ++ if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) { ++ n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr, ++ path->socklen, segsize); ++ if (n == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (n == NGX_AGAIN) { ++ ngx_quic_revert_send(c, ctx, preserved_pnum); ++ ++ ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); ++ break; ++ } ++ ++ ngx_quic_commit_send(c, ctx); ++ ++ path->sent += n; ++ ++ p = dst; ++ nseg = 0; ++ preserved_pnum = ctx->pnum; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ssize_t ++ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, ++ struct sockaddr *sockaddr, socklen_t socklen, size_t segment) ++{ ++ size_t clen; ++ ssize_t n; ++ uint16_t *valp; ++ struct iovec iov; ++ struct msghdr msg; ++ struct cmsghdr *cmsg; ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ char msg_control[CMSG_SPACE(sizeof(uint16_t)) ++ + CMSG_SPACE(sizeof(ngx_addrinfo_t))]; ++#else ++ char msg_control[CMSG_SPACE(sizeof(uint16_t))]; ++#endif ++ ++ ngx_memzero(&msg, sizeof(struct msghdr)); ++ ngx_memzero(msg_control, sizeof(msg_control)); ++ ++ iov.iov_len = len; ++ iov.iov_base = buf; ++ ++ msg.msg_iov = &iov; ++ msg.msg_iovlen = 1; ++ ++ msg.msg_name = sockaddr; ++ msg.msg_namelen = socklen; ++ ++ msg.msg_control = msg_control; ++ msg.msg_controllen = sizeof(msg_control); ++ ++ cmsg = CMSG_FIRSTHDR(&msg); ++ ++ cmsg->cmsg_level = SOL_UDP; ++ cmsg->cmsg_type = UDP_SEGMENT; ++ cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t)); ++ ++ clen = CMSG_SPACE(sizeof(uint16_t)); ++ ++ valp = (void *) CMSG_DATA(cmsg); ++ *valp = segment; ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ if (c->listening && c->listening->wildcard && c->local_sockaddr) { ++ cmsg = CMSG_NXTHDR(&msg, cmsg); ++ clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); ++ } ++#endif ++ ++ msg.msg_controllen = clen; ++ ++ n = ngx_sendmsg(c, &msg, 0); ++ if (n < 0) { ++ return n; ++ } ++ ++ c->sent += n; ++ ++ return n; ++} ++ ++#endif ++ ++ ++ ++static ngx_uint_t ++ngx_quic_get_padding_level(ngx_connection_t *c) ++{ ++ ngx_uint_t i; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ /* ++ * RFC 9000, 14.1. Initial Datagram Size ++ * ++ * Similarly, a server MUST expand the payload of all UDP datagrams ++ * carrying ack-eliciting Initial packets to at least the smallest ++ * allowed maximum datagram size of 1200 bytes. ++ */ ++ ++ qc = ngx_quic_get_connection(c); ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); ++ ++ for (q = ngx_queue_head(&ctx->frames); ++ q != ngx_queue_sentinel(&ctx->frames); ++ q = ngx_queue_next(q)) ++ { ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (f->need_ack) { ++ for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) { ++ ctx = &qc->send_ctx[i + 1]; ++ ++ if (ngx_queue_empty(&ctx->frames)) { ++ break; ++ } ++ } ++ ++ return i; ++ } ++ } ++ ++ return NGX_QUIC_SEND_CTX_LAST; ++} ++ ++ ++static ssize_t ++ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ u_char *data, size_t max, size_t min) ++{ ++ size_t len, pad, min_payload, max_payload; ++ u_char *p; ++ ssize_t flen; ++ ngx_str_t res; ++ ngx_int_t rc; ++ ngx_uint_t nframes, expand; ++ ngx_msec_t now; ++ ngx_queue_t *q; ++ ngx_quic_frame_t *f; ++ ngx_quic_header_t pkt; ++ ngx_quic_connection_t *qc; ++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ if (ngx_queue_empty(&ctx->frames)) { ++ return 0; ++ } ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic output %s packet max:%uz min:%uz", ++ ngx_quic_level_name(ctx->level), max, min); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_quic_init_packet(c, ctx, &pkt, qc->path); ++ ++ min_payload = ngx_quic_payload_size(&pkt, min); ++ max_payload = ngx_quic_payload_size(&pkt, max); ++ ++ /* RFC 9001, 5.4.2. Header Protection Sample */ ++ pad = 4 - pkt.num_len; ++ min_payload = ngx_max(min_payload, pad); ++ ++ if (min_payload > max_payload) { ++ return 0; ++ } ++ ++ now = ngx_current_msec; ++ nframes = 0; ++ p = src; ++ len = 0; ++ expand = 0; ++ ++ for (q = ngx_queue_head(&ctx->frames); ++ q != ngx_queue_sentinel(&ctx->frames); ++ q = ngx_queue_next(q)) ++ { ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ if (!expand && (f->type == NGX_QUIC_FT_PATH_RESPONSE ++ || f->type == NGX_QUIC_FT_PATH_CHALLENGE)) ++ { ++ /* ++ * RFC 9000, 8.2.1. Initiating Path Validation ++ * ++ * An endpoint MUST expand datagrams that contain a ++ * PATH_CHALLENGE frame to at least the smallest allowed ++ * maximum datagram size of 1200 bytes... ++ * ++ * (same applies to PATH_RESPONSE frames) ++ */ ++ ++ if (max < 1200) { ++ /* expanded packet will not fit */ ++ break; ++ } ++ ++ if (min < 1200) { ++ min = 1200; ++ ++ min_payload = ngx_quic_payload_size(&pkt, min); ++ } ++ ++ expand = 1; ++ } ++ ++ if (len >= max_payload) { ++ break; ++ } ++ ++ if (len + f->len > max_payload) { ++ rc = ngx_quic_split_frame(c, f, max_payload - len); ++ ++ if (rc == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_DECLINED) { ++ break; ++ } ++ } ++ ++ if (f->need_ack) { ++ pkt.need_ack = 1; ++ } ++ ++ ngx_quic_log_frame(c->log, f, 1); ++ ++ flen = ngx_quic_create_frame(p, f); ++ if (flen == -1) { ++ return NGX_ERROR; ++ } ++ ++ len += flen; ++ p += flen; ++ ++ f->pnum = ctx->pnum; ++ f->first = now; ++ f->last = now; ++ f->plen = 0; ++ ++ nframes++; ++ ++ if (f->flush) { ++ break; ++ } ++ } ++ ++ if (nframes == 0) { ++ return 0; ++ } ++ ++ if (len < min_payload) { ++ ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len); ++ len = min_payload; ++ } ++ ++ pkt.payload.data = src; ++ pkt.payload.len = len; ++ ++ res.data = data; ++ ++ ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet tx %s bytes:%ui" ++ " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", ++ ngx_quic_level_name(ctx->level), pkt.payload.len, ++ pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); ++ ++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ctx->pnum++; ++ ++ if (pkt.need_ack) { ++ q = ngx_queue_head(&ctx->frames); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ f->plen = res.len; ++ } ++ ++ while (nframes--) { ++ q = ngx_queue_head(&ctx->frames); ++ f = ngx_queue_data(q, ngx_quic_frame_t, queue); ++ ++ f->pkt_need_ack = pkt.need_ack; ++ ++ ngx_queue_remove(q); ++ ngx_queue_insert_tail(&ctx->sending, q); ++ } ++ ++ return res.len; ++} ++ ++ ++static void ++ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ ngx_quic_header_t *pkt, ngx_quic_path_t *path) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_memzero(pkt, sizeof(ngx_quic_header_t)); ++ ++ pkt->flags = NGX_QUIC_PKT_FIXED_BIT; ++ ++ if (ctx->level == ssl_encryption_initial) { ++ pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; ++ ++ } else if (ctx->level == ssl_encryption_handshake) { ++ pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; ++ ++ } else { ++ if (qc->key_phase) { ++ pkt->flags |= NGX_QUIC_PKT_KPHASE; ++ } ++ } ++ ++ pkt->dcid.data = path->cid->id; ++ pkt->dcid.len = path->cid->len; ++ ++ pkt->scid = qc->tp.initial_scid; ++ ++ pkt->version = qc->version; ++ pkt->log = c->log; ++ pkt->level = ctx->level; ++ ++ pkt->keys = qc->keys; ++ ++ ngx_quic_set_packet_number(pkt, ctx); ++} ++ ++ ++static ssize_t ++ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, ++ struct sockaddr *sockaddr, socklen_t socklen) ++{ ++ ssize_t n; ++ struct iovec iov; ++ struct msghdr msg; ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ struct cmsghdr *cmsg; ++ char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; ++#endif ++ ++ ngx_memzero(&msg, sizeof(struct msghdr)); ++ ++ iov.iov_len = len; ++ iov.iov_base = buf; ++ ++ msg.msg_iov = &iov; ++ msg.msg_iovlen = 1; ++ ++ msg.msg_name = sockaddr; ++ msg.msg_namelen = socklen; ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ if (c->listening && c->listening->wildcard && c->local_sockaddr) { ++ ++ msg.msg_control = msg_control; ++ msg.msg_controllen = sizeof(msg_control); ++ ngx_memzero(msg_control, sizeof(msg_control)); ++ ++ cmsg = CMSG_FIRSTHDR(&msg); ++ ++ msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); ++ } ++#endif ++ ++ n = ngx_sendmsg(c, &msg, 0); ++ if (n < 0) { ++ return n; ++ } ++ ++ c->sent += n; ++ ++ return n; ++} ++ ++ ++static void ++ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) ++{ ++ uint64_t delta; ++ ++ delta = ctx->pnum - ctx->largest_ack; ++ pkt->number = ctx->pnum; ++ ++ if (delta <= 0x7F) { ++ pkt->num_len = 1; ++ pkt->trunc = ctx->pnum & 0xff; ++ ++ } else if (delta <= 0x7FFF) { ++ pkt->num_len = 2; ++ pkt->flags |= 0x1; ++ pkt->trunc = ctx->pnum & 0xffff; ++ ++ } else if (delta <= 0x7FFFFF) { ++ pkt->num_len = 3; ++ pkt->flags |= 0x2; ++ pkt->trunc = ctx->pnum & 0xffffff; ++ ++ } else { ++ pkt->num_len = 4; ++ pkt->flags |= 0x3; ++ pkt->trunc = ctx->pnum & 0xffffffff; ++ } ++} ++ ++ ++ngx_int_t ++ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) ++{ ++ size_t len; ++ ngx_quic_header_t pkt; ++ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "sending version negotiation packet"); ++ ++ pkt.log = c->log; ++ pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; ++ pkt.dcid = inpkt->scid; ++ pkt.scid = inpkt->dcid; ++ ++ len = ngx_quic_create_version_negotiation(&pkt, buf); ++ ++#ifdef NGX_QUIC_DEBUG_PACKETS ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic vnego packet to send len:%uz %*xs", len, len, buf); ++#endif ++ ++ (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); ++ ++ return NGX_DONE; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ++ ngx_quic_header_t *pkt) ++{ ++ u_char *token; ++ size_t len, max; ++ uint16_t rndbytes; ++ u_char buf[NGX_QUIC_MAX_SR_PACKET]; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic handle stateless reset output"); ++ ++ if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { ++ return NGX_DECLINED; ++ } ++ ++ if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { ++ len = pkt->len - 1; ++ ++ } else { ++ max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); ++ ++ if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { ++ return NGX_ERROR; ++ } ++ ++ len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) ++ + NGX_QUIC_MIN_SR_PACKET; ++ } ++ ++ if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] &= ~NGX_QUIC_PKT_LONG; ++ buf[0] |= NGX_QUIC_PKT_FIXED_BIT; ++ ++ token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; ++ ++ if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); ++ ++ return NGX_DECLINED; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_cc(ngx_connection_t *c) ++{ ++ ngx_quic_frame_t frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc->draining) { ++ return NGX_OK; ++ } ++ ++ if (qc->closing ++ && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) ++ { ++ /* dot not send CC too often */ ++ return NGX_OK; ++ } ++ ++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); ++ ++ frame.level = qc->error_level; ++ frame.type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP ++ : NGX_QUIC_FT_CONNECTION_CLOSE; ++ frame.u.close.error_code = qc->error; ++ frame.u.close.frame_type = qc->error_ftype; ++ ++ if (qc->error_reason) { ++ frame.u.close.reason.len = ngx_strlen(qc->error_reason); ++ frame.u.close.reason.data = (u_char *) qc->error_reason; ++ } ++ ++ qc->last_cc = ngx_current_msec; ++ ++ return ngx_quic_frame_sendto(c, &frame, 0, qc->path); ++} ++ ++ ++ngx_int_t ++ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, ++ ngx_uint_t err, const char *reason) ++{ ++ ssize_t len; ++ ngx_str_t res; ++ ngx_quic_keys_t keys; ++ ngx_quic_frame_t frame; ++ ngx_quic_header_t pkt; ++ ++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); ++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); ++ ++ frame.level = inpkt->level; ++ frame.type = NGX_QUIC_FT_CONNECTION_CLOSE; ++ frame.u.close.error_code = err; ++ ++ frame.u.close.reason.data = (u_char *) reason; ++ frame.u.close.reason.len = ngx_strlen(reason); ++ ++ len = ngx_quic_create_frame(NULL, &frame); ++ if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_log_frame(c->log, &frame, 1); ++ ++ len = ngx_quic_create_frame(src, &frame); ++ if (len == -1) { ++ return NGX_ERROR; ++ } ++ ++ ngx_memzero(&keys, sizeof(ngx_quic_keys_t)); ++ ++ pkt.keys = &keys; ++ ++ if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG ++ | NGX_QUIC_PKT_INITIAL; ++ ++ pkt.num_len = 1; ++ /* ++ * pkt.num = 0; ++ * pkt.trunc = 0; ++ */ ++ ++ pkt.version = inpkt->version; ++ pkt.log = c->log; ++ pkt.level = inpkt->level; ++ pkt.dcid = inpkt->scid; ++ pkt.scid = inpkt->dcid; ++ pkt.payload.data = src; ++ pkt.payload.len = len; ++ ++ res.data = dst; ++ ++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_DONE; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, ++ ngx_quic_header_t *inpkt) ++{ ++ time_t expires; ++ ssize_t len; ++ ngx_str_t res, token; ++ ngx_quic_header_t pkt; ++ ++ u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; ++ u_char dcid[NGX_QUIC_SERVER_CID_LEN]; ++ u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; ++ ++ expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; ++ ++ token.data = tbuf; ++ token.len = NGX_QUIC_TOKEN_BUF_SIZE; ++ ++ if (ngx_quic_new_token(c->log, c->sockaddr, c->socklen, conf->av_token_key, ++ &token, &inpkt->dcid, expires, 1) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); ++ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; ++ pkt.version = inpkt->version; ++ pkt.log = c->log; ++ ++ pkt.odcid = inpkt->dcid; ++ pkt.dcid = inpkt->scid; ++ ++ /* TODO: generate routable dcid */ ++ if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { ++ return NGX_ERROR; ++ } ++ ++ pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; ++ pkt.scid.data = dcid; ++ ++ pkt.token = token; ++ ++ res.data = buf; ++ ++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_PACKETS ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic packet to send len:%uz %xV", res.len, &res); ++#endif ++ ++ len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); ++ if (len < 0) { ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic retry packet sent to %xV", &pkt.dcid); ++ ++ /* ++ * RFC 9000, 17.2.5.1. Sending a Retry Packet ++ * ++ * A server MUST NOT send more than one Retry ++ * packet in response to a single UDP datagram. ++ * NGX_DONE will stop quic_input() from processing further ++ */ ++ return NGX_DONE; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) ++{ ++ time_t expires; ++ ngx_str_t token; ++ ngx_chain_t *out; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; ++ ++ token.data = tbuf; ++ token.len = NGX_QUIC_TOKEN_BUF_SIZE; ++ ++ if (ngx_quic_new_token(c->log, path->sockaddr, path->socklen, ++ qc->conf->av_token_key, &token, NULL, expires, 0) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ out = ngx_quic_copy_buffer(c, token.data, token.len); ++ if (out == NGX_CHAIN_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_NEW_TOKEN; ++ frame->data = out; ++ frame->u.token.length = token.len; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ++{ ++ size_t len, left; ++ uint64_t ack_delay; ++ ngx_buf_t *b; ++ ngx_uint_t i; ++ ngx_chain_t *cl, **ll; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ack_delay = ngx_current_msec - ctx->largest_received; ++ ack_delay *= 1000; ++ ack_delay >>= qc->tp.ack_delay_exponent; ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ll = &frame->data; ++ b = NULL; ++ ++ for (i = 0; i < ctx->nranges; i++) { ++ len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, ++ ctx->ranges[i].range); ++ ++ left = b ? b->end - b->last : 0; ++ ++ if (left < len) { ++ cl = ngx_quic_alloc_chain(c); ++ if (cl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ *ll = cl; ++ ll = &cl->next; ++ ++ b = cl->buf; ++ left = b->end - b->last; ++ ++ if (left < len) { ++ return NGX_ERROR; ++ } ++ } ++ ++ b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, ++ ctx->ranges[i].range); ++ ++ frame->u.ack.ranges_length += len; ++ } ++ ++ *ll = NULL; ++ ++ frame->level = ctx->level; ++ frame->type = NGX_QUIC_FT_ACK; ++ frame->u.ack.largest = ctx->largest_range; ++ frame->u.ack.delay = ack_delay; ++ frame->u.ack.range_count = ctx->nranges; ++ frame->u.ack.first_range = ctx->first_range; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ++ uint64_t smallest, uint64_t largest) ++{ ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ctx->level; ++ frame->type = NGX_QUIC_FT_ACK; ++ frame->u.ack.largest = largest; ++ frame->u.ack.delay = 0; ++ frame->u.ack.range_count = 0; ++ frame->u.ack.first_range = largest - smallest; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ++ size_t min, ngx_quic_path_t *path) ++{ ++ size_t min_payload, pad; ++ ssize_t len, sent; ++ ngx_str_t res; ++ ngx_quic_header_t pkt; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ++ ++ qc = ngx_quic_get_connection(c); ++ ctx = ngx_quic_get_send_ctx(qc, frame->level); ++ ++ ngx_quic_init_packet(c, ctx, &pkt, path); ++ ++ min = ngx_quic_path_limit(c, path, min); ++ ++ min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0; ++ ++ pad = 4 - pkt.num_len; ++ min_payload = ngx_max(min_payload, pad); ++ ++ len = ngx_quic_create_frame(NULL, frame); ++ if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_log_frame(c->log, frame, 1); ++ ++ len = ngx_quic_create_frame(src, frame); ++ if (len == -1) { ++ return NGX_ERROR; ++ } ++ ++ if (len < (ssize_t) min_payload) { ++ ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len); ++ len = min_payload; ++ } ++ ++ pkt.payload.data = src; ++ pkt.payload.len = len; ++ ++ res.data = dst; ++ ++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ctx->pnum++; ++ ++ sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen); ++ if (sent < 0) { ++ return NGX_ERROR; ++ } ++ ++ path->sent += sent; ++ ++ return NGX_OK; ++} ++ ++ ++static size_t ++ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) ++{ ++ off_t max; ++ ++ if (path->limited) { ++ max = path->received * 3; ++ max = (path->sent >= max) ? 0 : max - path->sent; ++ ++ if ((off_t) size > max) { ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic path limit %uz - %O", size, max); ++ return max; ++ } ++ } ++ ++ return size; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_output.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_output.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,40 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++size_t ngx_quic_max_udp_payload(ngx_connection_t *c); ++ ++ngx_int_t ngx_quic_output(ngx_connection_t *c); ++ ++ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ++ ngx_quic_header_t *inpkt); ++ ++ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); ++ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); ++ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, ++ ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); ++ ++ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, ++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); ++ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path); ++ ++ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ++ ngx_quic_send_ctx_t *ctx); ++ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, ++ ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); ++ ++ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ++ size_t min, ngx_quic_path_t *path); ++ ++#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_protection.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_protection.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,1087 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ ++#define NGX_QUIC_HP_LEN 5 ++ ++#define NGX_QUIC_AES_128_KEY_LEN 16 ++ ++#ifndef TLS1_3_CK_AES_128_GCM_SHA256 ++#define TLS1_3_CK_AES_128_GCM_SHA256 0x03001301 ++#define TLS1_3_CK_AES_256_GCM_SHA384 0x03001302 ++#define TLS1_3_CK_CHACHA20_POLY1305_SHA256 \ ++ 0x03001303 ++#endif ++ ++ ++static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, ++ const EVP_MD *digest, const u_char *prk, size_t prk_len, ++ const u_char *info, size_t info_len); ++static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, ++ const EVP_MD *digest, const u_char *secret, size_t secret_len, ++ const u_char *salt, size_t salt_len); ++ ++static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, ++ uint64_t *largest_pn); ++ ++static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ++ ngx_str_t *ad, ngx_log_t *log); ++static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ++ ngx_quic_secret_t *s, u_char *out, u_char *in); ++ ++static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ++ ngx_str_t *res); ++static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ++ ngx_str_t *res); ++ ++ ++ngx_int_t ++ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, ++ enum ssl_encryption_level_t level) ++{ ++ ngx_int_t len; ++ ++ if (level == ssl_encryption_initial) { ++ id = TLS1_3_CK_AES_128_GCM_SHA256; ++ } ++ ++ switch (id) { ++ ++ case TLS1_3_CK_AES_128_GCM_SHA256: ++#ifdef OPENSSL_IS_BORINGSSL ++ ciphers->c = EVP_aead_aes_128_gcm(); ++#else ++ ciphers->c = EVP_aes_128_gcm(); ++#endif ++ ciphers->hp = EVP_aes_128_ctr(); ++ ciphers->d = EVP_sha256(); ++ len = 16; ++ break; ++ ++ case TLS1_3_CK_AES_256_GCM_SHA384: ++#ifdef OPENSSL_IS_BORINGSSL ++ ciphers->c = EVP_aead_aes_256_gcm(); ++#else ++ ciphers->c = EVP_aes_256_gcm(); ++#endif ++ ciphers->hp = EVP_aes_256_ctr(); ++ ciphers->d = EVP_sha384(); ++ len = 32; ++ break; ++ ++ case TLS1_3_CK_CHACHA20_POLY1305_SHA256: ++#ifdef OPENSSL_IS_BORINGSSL ++ ciphers->c = EVP_aead_chacha20_poly1305(); ++#else ++ ciphers->c = EVP_chacha20_poly1305(); ++#endif ++#ifdef OPENSSL_IS_BORINGSSL ++ ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); ++#else ++ ciphers->hp = EVP_chacha20(); ++#endif ++ ciphers->d = EVP_sha256(); ++ len = 32; ++ break; ++ ++ default: ++ return NGX_ERROR; ++ } ++ ++ return len; ++} ++ ++ ++ngx_int_t ++ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, ++ ngx_log_t *log) ++{ ++ size_t is_len; ++ uint8_t is[SHA256_DIGEST_LENGTH]; ++ ngx_str_t iss; ++ ngx_uint_t i; ++ const EVP_MD *digest; ++ ngx_quic_hkdf_t seq[8]; ++ ngx_quic_secret_t *client, *server; ++ ++ static const uint8_t salt[20] = ++ "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" ++ "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; ++ ++ client = &keys->secrets[ssl_encryption_initial].client; ++ server = &keys->secrets[ssl_encryption_initial].server; ++ ++ /* ++ * RFC 9001, section 5. Packet Protection ++ * ++ * Initial packets use AEAD_AES_128_GCM. The hash function ++ * for HKDF when deriving initial secrets and keys is SHA-256. ++ */ ++ ++ digest = EVP_sha256(); ++ is_len = SHA256_DIGEST_LENGTH; ++ ++ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, ++ salt, sizeof(salt)) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ iss.len = is_len; ++ iss.data = is; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic ngx_quic_set_initial_secret"); ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic initial secret len:%uz %*xs", is_len, is_len, is); ++#endif ++ ++ client->secret.len = SHA256_DIGEST_LENGTH; ++ server->secret.len = SHA256_DIGEST_LENGTH; ++ ++ client->key.len = NGX_QUIC_AES_128_KEY_LEN; ++ server->key.len = NGX_QUIC_AES_128_KEY_LEN; ++ ++ client->hp.len = NGX_QUIC_AES_128_KEY_LEN; ++ server->hp.len = NGX_QUIC_AES_128_KEY_LEN; ++ ++ client->iv.len = NGX_QUIC_IV_LEN; ++ server->iv.len = NGX_QUIC_IV_LEN; ++ ++ /* labels per RFC 9001, 5.1. Packet Protection Keys */ ++ ngx_quic_hkdf_set(&seq[0], "tls13 client in", &client->secret, &iss); ++ ngx_quic_hkdf_set(&seq[1], "tls13 quic key", &client->key, &client->secret); ++ ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", &client->iv, &client->secret); ++ ngx_quic_hkdf_set(&seq[3], "tls13 quic hp", &client->hp, &client->secret); ++ ngx_quic_hkdf_set(&seq[4], "tls13 server in", &server->secret, &iss); ++ ngx_quic_hkdf_set(&seq[5], "tls13 quic key", &server->key, &server->secret); ++ ngx_quic_hkdf_set(&seq[6], "tls13 quic iv", &server->iv, &server->secret); ++ ngx_quic_hkdf_set(&seq[7], "tls13 quic hp", &server->hp, &server->secret); ++ ++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { ++ if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log) ++{ ++ size_t info_len; ++ uint8_t *p; ++ uint8_t info[20]; ++ ++ info_len = 2 + 1 + h->label_len + 1; ++ ++ info[0] = 0; ++ info[1] = h->out_len; ++ info[2] = h->label_len; ++ ++ p = ngx_cpymem(&info[3], h->label, h->label_len); ++ *p = '\0'; ++ ++ if (ngx_hkdf_expand(h->out, h->out_len, digest, ++ h->prk, h->prk_len, info, info_len) ++ != NGX_OK) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "ngx_hkdf_expand(%*s) failed", h->label_len, h->label); ++ return NGX_ERROR; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic expand \"%*s\" len:%uz %*xs", ++ h->label_len, h->label, h->out_len, h->out_len, h->out); ++#endif ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, ++ const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) ++{ ++#ifdef OPENSSL_IS_BORINGSSL ++ ++ if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) ++ == 0) ++ { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++#else ++ ++ EVP_PKEY_CTX *pctx; ++ ++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); ++ if (pctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (EVP_PKEY_derive_init(pctx) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { ++ goto failed; ++ } ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_OK; ++ ++failed: ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_ERROR; ++ ++#endif ++} ++ ++ ++static ngx_int_t ++ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, ++ const u_char *secret, size_t secret_len, const u_char *salt, ++ size_t salt_len) ++{ ++#ifdef OPENSSL_IS_BORINGSSL ++ ++ if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, ++ salt_len) ++ == 0) ++ { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++#else ++ ++ EVP_PKEY_CTX *pctx; ++ ++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); ++ if (pctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (EVP_PKEY_derive_init(pctx) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { ++ goto failed; ++ } ++ ++ if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { ++ goto failed; ++ } ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_OK; ++ ++failed: ++ ++ EVP_PKEY_CTX_free(pctx); ++ ++ return NGX_ERROR; ++ ++#endif ++} ++ ++ ++static ngx_int_t ++ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ++ ngx_log_t *log) ++{ ++ ++#ifdef OPENSSL_IS_BORINGSSL ++ EVP_AEAD_CTX *ctx; ++ ++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, ++ EVP_AEAD_DEFAULT_TAG_LENGTH); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, ++ in->data, in->len, ad->data, ad->len) ++ != 1) ++ { ++ EVP_AEAD_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); ++ return NGX_ERROR; ++ } ++ ++ EVP_AEAD_CTX_free(ctx); ++#else ++ int len; ++ u_char *tag; ++ EVP_CIPHER_CTX *ctx; ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, ++ in->len - EVP_GCM_TLS_TAG_LEN) ++ != 1) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len = len; ++ tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len += len; ++ ++ EVP_CIPHER_CTX_free(ctx); ++#endif ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) ++{ ++ ++#ifdef OPENSSL_IS_BORINGSSL ++ EVP_AEAD_CTX *ctx; ++ ++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, ++ EVP_AEAD_DEFAULT_TAG_LENGTH); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, ++ in->data, in->len, ad->data, ad->len) ++ != 1) ++ { ++ EVP_AEAD_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); ++ return NGX_ERROR; ++ } ++ ++ EVP_AEAD_CTX_free(ctx); ++#else ++ int len; ++ EVP_CIPHER_CTX *ctx; ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len = len; ++ ++ if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed"); ++ return NGX_ERROR; ++ } ++ ++ out->len += len; ++ ++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, ++ out->data + in->len) ++ == 0) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); ++ return NGX_ERROR; ++ } ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ out->len += EVP_GCM_TLS_TAG_LEN; ++#endif ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ++ ngx_quic_secret_t *s, u_char *out, u_char *in) ++{ ++ int outlen; ++ EVP_CIPHER_CTX *ctx; ++ u_char zero[NGX_QUIC_HP_LEN] = {0}; ++ ++#ifdef OPENSSL_IS_BORINGSSL ++ uint32_t cnt; ++ ++ ngx_memcpy(&cnt, in, sizeof(uint32_t)); ++ ++ if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { ++ CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); ++ return NGX_OK; ++ } ++#endif ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); ++ goto failed; ++ } ++ ++ if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); ++ goto failed; ++ } ++ ++ if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); ++ goto failed; ++ } ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ return NGX_OK; ++ ++failed: ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, ++ ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, ++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) ++{ ++ ngx_int_t key_len; ++ ngx_str_t secret_str; ++ ngx_uint_t i; ++ ngx_quic_hkdf_t seq[3]; ++ ngx_quic_secret_t *peer_secret; ++ ngx_quic_ciphers_t ciphers; ++ ++ peer_secret = is_write ? &keys->secrets[level].server ++ : &keys->secrets[level].client; ++ ++ keys->cipher = SSL_CIPHER_get_id(cipher); ++ ++ key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); ++ ++ if (key_len == NGX_ERROR) { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); ++ return NGX_ERROR; ++ } ++ ++ if (sizeof(peer_secret->secret.data) < secret_len) { ++ ngx_log_error(NGX_LOG_ALERT, log, 0, ++ "unexpected secret len: %uz", secret_len); ++ return NGX_ERROR; ++ } ++ ++ peer_secret->secret.len = secret_len; ++ ngx_memcpy(peer_secret->secret.data, secret, secret_len); ++ ++ peer_secret->key.len = key_len; ++ peer_secret->iv.len = NGX_QUIC_IV_LEN; ++ peer_secret->hp.len = key_len; ++ ++ secret_str.len = secret_len; ++ secret_str.data = (u_char *) secret; ++ ++ ngx_quic_hkdf_set(&seq[0], "tls13 quic key", ++ &peer_secret->key, &secret_str); ++ ngx_quic_hkdf_set(&seq[1], "tls13 quic iv", &peer_secret->iv, &secret_str); ++ ngx_quic_hkdf_set(&seq[2], "tls13 quic hp", &peer_secret->hp, &secret_str); ++ ++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { ++ if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_uint_t ++ngx_quic_keys_available(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level) ++{ ++ return keys->secrets[level].client.key.len != 0; ++} ++ ++ ++void ++ngx_quic_keys_discard(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level) ++{ ++ keys->secrets[level].client.key.len = 0; ++} ++ ++ ++void ++ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) ++{ ++ ngx_quic_secrets_t *current, *next, tmp; ++ ++ current = &keys->secrets[ssl_encryption_application]; ++ next = &keys->next_key; ++ ++ tmp = *current; ++ *current = *next; ++ *next = tmp; ++} ++ ++ ++ngx_int_t ++ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) ++{ ++ ngx_uint_t i; ++ ngx_quic_hkdf_t seq[6]; ++ ngx_quic_ciphers_t ciphers; ++ ngx_quic_secrets_t *current, *next; ++ ++ current = &keys->secrets[ssl_encryption_application]; ++ next = &keys->next_key; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); ++ ++ if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application) ++ == NGX_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ next->client.secret.len = current->client.secret.len; ++ next->client.key.len = current->client.key.len; ++ next->client.iv.len = NGX_QUIC_IV_LEN; ++ next->client.hp = current->client.hp; ++ ++ next->server.secret.len = current->server.secret.len; ++ next->server.key.len = current->server.key.len; ++ next->server.iv.len = NGX_QUIC_IV_LEN; ++ next->server.hp = current->server.hp; ++ ++ ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", ++ &next->client.secret, ¤t->client.secret); ++ ngx_quic_hkdf_set(&seq[1], "tls13 quic key", ++ &next->client.key, &next->client.secret); ++ ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", ++ &next->client.iv, &next->client.secret); ++ ngx_quic_hkdf_set(&seq[3], "tls13 quic ku", ++ &next->server.secret, ¤t->server.secret); ++ ngx_quic_hkdf_set(&seq[4], "tls13 quic key", ++ &next->server.key, &next->server.secret); ++ ngx_quic_hkdf_set(&seq[5], "tls13 quic iv", ++ &next->server.iv, &next->server.secret); ++ ++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { ++ if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ++{ ++ u_char *pnp, *sample; ++ ngx_str_t ad, out; ++ ngx_uint_t i; ++ ngx_quic_secret_t *secret; ++ ngx_quic_ciphers_t ciphers; ++ u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; ++ ++ ad.data = res->data; ++ ad.len = ngx_quic_create_header(pkt, ad.data, &pnp); ++ ++ out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; ++ out.data = res->data + ad.len; ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic ad len:%uz %xV", ad.len, &ad); ++#endif ++ ++ if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ secret = &pkt->keys->secrets[pkt->level].server; ++ ++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ++ ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); ++ ++ if (ngx_quic_tls_seal(ciphers.c, secret, &out, ++ nonce, &pkt->payload, &ad, pkt->log) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ sample = &out.data[4 - pkt->num_len]; ++ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ /* RFC 9001, 5.4.1. Header Protection Application */ ++ ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); ++ ++ for (i = 0; i < pkt->num_len; i++) { ++ pnp[i] ^= mask[i + 1]; ++ } ++ ++ res->len = ad.len + out.len; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ++{ ++ u_char *start; ++ ngx_str_t ad, itag; ++ ngx_quic_secret_t secret; ++ ngx_quic_ciphers_t ciphers; ++ ++ /* 5.8. Retry Packet Integrity */ ++ static u_char key[16] = ++ "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; ++ static u_char nonce[NGX_QUIC_IV_LEN] = ++ "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; ++ static ngx_str_t in = ngx_string(""); ++ ++ ad.data = res->data; ++ ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); ++ ++ itag.data = ad.data + ad.len; ++ itag.len = EVP_GCM_TLS_TAG_LEN; ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic retry itag len:%uz %xV", ad.len, &ad); ++#endif ++ ++ if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ secret.key.len = sizeof(key); ++ ngx_memcpy(secret.key.data, key, sizeof(key)); ++ secret.iv.len = NGX_QUIC_IV_LEN; ++ ++ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ res->len = itag.data + itag.len - start; ++ res->data = start; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, ++ ngx_str_t *salt, u_char *out, size_t len) ++{ ++ size_t is_len, info_len; ++ uint8_t *p; ++ const EVP_MD *digest; ++ ++ uint8_t is[SHA256_DIGEST_LENGTH]; ++ uint8_t info[20]; ++ ++ digest = EVP_sha256(); ++ is_len = SHA256_DIGEST_LENGTH; ++ ++ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, ++ salt->data, salt->len) ++ != NGX_OK) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "ngx_hkdf_extract(%s) failed", label); ++ return NGX_ERROR; ++ } ++ ++ info[0] = 0; ++ info[1] = len; ++ info[2] = ngx_strlen(label); ++ ++ info_len = 2 + 1 + info[2] + 1; ++ ++ if (info_len >= 20) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "ngx_quic_create_key label \"%s\" too long", label); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_cpymem(&info[3], label, info[2]); ++ *p = '\0'; ++ ++ if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, log, 0, ++ "ngx_hkdf_expand(%s) failed", label); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static uint64_t ++ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, ++ uint64_t *largest_pn) ++{ ++ u_char *p; ++ uint64_t truncated_pn, expected_pn, candidate_pn; ++ uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; ++ ++ pn_nbits = ngx_min(len * 8, 62); ++ ++ p = *pos; ++ truncated_pn = *p++ ^ *mask++; ++ ++ while (--len) { ++ truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); ++ } ++ ++ *pos = p; ++ ++ expected_pn = *largest_pn + 1; ++ pn_win = 1ULL << pn_nbits; ++ pn_hwin = pn_win / 2; ++ pn_mask = pn_win - 1; ++ ++ candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; ++ ++ if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) ++ && candidate_pn < (1ULL << 62) - pn_win) ++ { ++ candidate_pn += pn_win; ++ ++ } else if (candidate_pn > expected_pn + pn_hwin ++ && candidate_pn >= pn_win) ++ { ++ candidate_pn -= pn_win; ++ } ++ ++ *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); ++ ++ return candidate_pn; ++} ++ ++ ++void ++ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) ++{ ++ nonce[len - 8] ^= (pn >> 56) & 0x3f; ++ nonce[len - 7] ^= (pn >> 48) & 0xff; ++ nonce[len - 6] ^= (pn >> 40) & 0xff; ++ nonce[len - 5] ^= (pn >> 32) & 0xff; ++ nonce[len - 4] ^= (pn >> 24) & 0xff; ++ nonce[len - 3] ^= (pn >> 16) & 0xff; ++ nonce[len - 2] ^= (pn >> 8) & 0xff; ++ nonce[len - 1] ^= pn & 0xff; ++} ++ ++ ++ngx_int_t ++ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) ++{ ++ if (ngx_quic_pkt_retry(pkt->flags)) { ++ return ngx_quic_create_retry_packet(pkt, res); ++ } ++ ++ return ngx_quic_create_packet(pkt, res); ++} ++ ++ ++ngx_int_t ++ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) ++{ ++ u_char *p, *sample; ++ size_t len; ++ uint64_t pn, lpn; ++ ngx_int_t pnl, rc; ++ ngx_str_t in, ad; ++ ngx_uint_t key_phase; ++ ngx_quic_secret_t *secret; ++ ngx_quic_ciphers_t ciphers; ++ uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; ++ ++ if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ secret = &pkt->keys->secrets[pkt->level].client; ++ ++ p = pkt->raw->pos; ++ len = pkt->data + pkt->len - p; ++ ++ /* ++ * RFC 9001, 5.4.2. Header Protection Sample ++ * 5.4.3. AES-Based Header Protection ++ * 5.4.4. ChaCha20-Based Header Protection ++ * ++ * the Packet Number field is assumed to be 4 bytes long ++ * AES and ChaCha20 algorithms sample 16 bytes ++ */ ++ ++ if (len < EVP_GCM_TLS_TAG_LEN + 4) { ++ return NGX_DECLINED; ++ } ++ ++ sample = p + 4; ++ ++ /* header protection */ ++ ++ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) ++ != NGX_OK) ++ { ++ return NGX_DECLINED; ++ } ++ ++ pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); ++ ++ if (ngx_quic_short_pkt(pkt->flags)) { ++ key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; ++ ++ if (key_phase != pkt->key_phase) { ++ secret = &pkt->keys->next_key.client; ++ pkt->key_update = 1; ++ } ++ } ++ ++ lpn = *largest_pn; ++ ++ pnl = (pkt->flags & 0x03) + 1; ++ pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); ++ ++ pkt->pn = pn; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx clearflags:%xd", pkt->flags); ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx number:%uL len:%xi", pn, pnl); ++ ++ /* packet protection */ ++ ++ in.data = p; ++ in.len = len - pnl; ++ ++ ad.len = p - pkt->data; ++ ad.data = pkt->plaintext; ++ ++ ngx_memcpy(ad.data, pkt->data, ad.len); ++ ad.data[0] = pkt->flags; ++ ++ do { ++ ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; ++ } while (--pnl); ++ ++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ++ ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic ad len:%uz %xV", ad.len, &ad); ++#endif ++ ++ pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; ++ pkt->payload.data = pkt->plaintext + ad.len; ++ ++ rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, ++ nonce, &in, &ad, pkt->log); ++ if (rc != NGX_OK) { ++ return NGX_DECLINED; ++ } ++ ++ if (pkt->payload.len == 0) { ++ /* ++ * RFC 9000, 12.4. Frames and Frame Types ++ * ++ * An endpoint MUST treat receipt of a packet containing no ++ * frames as a connection error of type PROTOCOL_VIOLATION. ++ */ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); ++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ return NGX_ERROR; ++ } ++ ++ if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { ++ /* ++ * RFC 9000, Reserved Bits ++ * ++ * An endpoint MUST treat receipt of a packet that has ++ * a non-zero value for these bits, after removing both ++ * packet and header protection, as a connection error ++ * of type PROTOCOL_VIOLATION. ++ */ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic reserved bit set in packet"); ++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ return NGX_ERROR; ++ } ++ ++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet payload len:%uz %xV", ++ pkt->payload.len, &pkt->payload); ++#endif ++ ++ *largest_pn = lpn; ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_protection.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_protection.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,114 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++#include ++ ++ ++#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) ++ ++/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */ ++#define NGX_QUIC_IV_LEN 12 ++ ++/* largest hash used in TLS is SHA-384 */ ++#define NGX_QUIC_MAX_MD_SIZE 48 ++ ++ ++#ifdef OPENSSL_IS_BORINGSSL ++#define ngx_quic_cipher_t EVP_AEAD ++#else ++#define ngx_quic_cipher_t EVP_CIPHER ++#endif ++ ++ ++typedef struct { ++ size_t len; ++ u_char data[NGX_QUIC_MAX_MD_SIZE]; ++} ngx_quic_md_t; ++ ++ ++typedef struct { ++ size_t len; ++ u_char data[NGX_QUIC_IV_LEN]; ++} ngx_quic_iv_t; ++ ++ ++typedef struct { ++ ngx_quic_md_t secret; ++ ngx_quic_md_t key; ++ ngx_quic_iv_t iv; ++ ngx_quic_md_t hp; ++} ngx_quic_secret_t; ++ ++ ++typedef struct { ++ ngx_quic_secret_t client; ++ ngx_quic_secret_t server; ++} ngx_quic_secrets_t; ++ ++ ++struct ngx_quic_keys_s { ++ ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; ++ ngx_quic_secrets_t next_key; ++ ngx_uint_t cipher; ++}; ++ ++ ++typedef struct { ++ const ngx_quic_cipher_t *c; ++ const EVP_CIPHER *hp; ++ const EVP_MD *d; ++} ngx_quic_ciphers_t; ++ ++ ++typedef struct { ++ size_t out_len; ++ u_char *out; ++ ++ size_t prk_len; ++ const uint8_t *prk; ++ ++ size_t label_len; ++ const u_char *label; ++} ngx_quic_hkdf_t; ++ ++#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \ ++ (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \ ++ (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \ ++ (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label); ++ ++ ++ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ++ ngx_str_t *secret, ngx_log_t *log); ++ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ++ ngx_uint_t is_write, ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *secret, size_t secret_len); ++ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level); ++void ngx_quic_keys_discard(ngx_quic_keys_t *keys, ++ enum ssl_encryption_level_t level); ++void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); ++ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); ++ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); ++ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); ++void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); ++ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, ++ enum ssl_encryption_level_t level); ++ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ++ ngx_str_t *ad, ngx_log_t *log); ++ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, ++ ngx_log_t *log); ++ ++ ++#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_socket.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_socket.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,237 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++ngx_int_t ++ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, ++ ngx_quic_header_t *pkt) ++{ ++ ngx_quic_socket_t *qsock, *tmp; ++ ngx_quic_client_id_t *cid; ++ ++ /* ++ * qc->path = NULL ++ * ++ * qc->nclient_ids = 0 ++ * qc->nsockets = 0 ++ * qc->max_retired_seqnum = 0 ++ * qc->client_seqnum = 0 ++ */ ++ ++ ngx_queue_init(&qc->sockets); ++ ngx_queue_init(&qc->free_sockets); ++ ++ ngx_queue_init(&qc->paths); ++ ngx_queue_init(&qc->free_paths); ++ ++ ngx_queue_init(&qc->client_ids); ++ ngx_queue_init(&qc->free_client_ids); ++ ++ qc->tp.original_dcid.len = pkt->odcid.len; ++ qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid); ++ if (qc->tp.original_dcid.data == NULL) { ++ return NGX_ERROR; ++ } ++ ++ /* socket to use for further processing (id auto-generated) */ ++ qsock = ngx_quic_create_socket(c, qc); ++ if (qsock == NULL) { ++ return NGX_ERROR; ++ } ++ ++ /* socket is listening at new server id */ ++ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ qsock->used = 1; ++ ++ qc->tp.initial_scid.len = qsock->sid.len; ++ qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len); ++ if (qc->tp.initial_scid.data == NULL) { ++ goto failed; ++ } ++ ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len); ++ ++ /* for all packets except first, this is set at udp layer */ ++ c->udp = &qsock->udp; ++ ++ /* ngx_quic_get_connection(c) macro is now usable */ ++ ++ /* we have a client identified by scid */ ++ cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL); ++ if (cid == NULL) { ++ goto failed; ++ } ++ ++ /* path of the first packet is our initial active path */ ++ qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid); ++ if (qc->path == NULL) { ++ goto failed; ++ } ++ ++ qc->path->tag = NGX_QUIC_PATH_ACTIVE; ++ ++ if (pkt->validated) { ++ qc->path->validated = 1; ++ qc->path->limited = 0; ++ } ++ ++ ngx_quic_path_dbg(c, "set active", qc->path); ++ ++ tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); ++ if (tmp == NULL) { ++ goto failed; ++ } ++ ++ tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */ ++ ++ ngx_memcpy(tmp->sid.id, pkt->odcid.data, pkt->odcid.len); ++ tmp->sid.len = pkt->odcid.len; ++ ++ if (ngx_quic_listen(c, qc, tmp) != NGX_OK) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); ++ c->udp = NULL; ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_quic_socket_t * ++ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) ++{ ++ ngx_queue_t *q; ++ ngx_quic_socket_t *sock; ++ ++ if (!ngx_queue_empty(&qc->free_sockets)) { ++ ++ q = ngx_queue_head(&qc->free_sockets); ++ sock = ngx_queue_data(q, ngx_quic_socket_t, queue); ++ ++ ngx_queue_remove(&sock->queue); ++ ++ ngx_memzero(sock, sizeof(ngx_quic_socket_t)); ++ ++ } else { ++ ++ sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); ++ if (sock == NULL) { ++ return NULL; ++ } ++ } ++ ++ sock->sid.len = NGX_QUIC_SERVER_CID_LEN; ++ if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) { ++ return NULL; ++ } ++ ++ sock->sid.seqnum = qc->server_seqnum++; ++ ++ return sock; ++} ++ ++ ++void ++ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_queue_remove(&qsock->queue); ++ ngx_queue_insert_head(&qc->free_sockets, &qsock->queue); ++ ++ ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); ++ qc->nsockets--; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic socket seq:%L closed nsock:%ui", ++ (int64_t) qsock->sid.seqnum, qc->nsockets); ++} ++ ++ ++ngx_int_t ++ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, ++ ngx_quic_socket_t *qsock) ++{ ++ ngx_str_t id; ++ ngx_quic_server_id_t *sid; ++ ++ sid = &qsock->sid; ++ ++ id.data = sid->id; ++ id.len = sid->len; ++ ++ qsock->udp.connection = c; ++ qsock->udp.node.key = ngx_crc32_long(id.data, id.len); ++ ++ ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); ++ ++ ngx_queue_insert_tail(&qc->sockets, &qsock->queue); ++ ++ qc->nsockets++; ++ qsock->quic = qc; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic socket seq:%L listening at sid:%xV nsock:%ui", ++ (int64_t) sid->seqnum, &id, qc->nsockets); ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_close_sockets(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ while (!ngx_queue_empty(&qc->sockets)) { ++ q = ngx_queue_head(&qc->sockets); ++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); ++ ++ ngx_quic_close_socket(c, qsock); ++ } ++} ++ ++ ++ngx_quic_socket_t * ++ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum) ++{ ++ ngx_queue_t *q; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ for (q = ngx_queue_head(&qc->sockets); ++ q != ngx_queue_sentinel(&qc->sockets); ++ q = ngx_queue_next(q)) ++ { ++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); ++ ++ if (qsock->sid.seqnum == seqnum) { ++ return qsock; ++ } ++ } ++ ++ return NULL; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_socket.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_socket.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,28 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, ++ ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); ++void ngx_quic_close_sockets(ngx_connection_t *c); ++ ++ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c, ++ ngx_quic_connection_t *qc); ++ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, ++ ngx_quic_socket_t *qsock); ++void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); ++ ++ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); ++ ++ ++#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_ssl.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_ssl.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,600 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#if defined OPENSSL_IS_BORINGSSL \ ++ || defined LIBRESSL_VERSION_NUMBER \ ++ || NGX_QUIC_OPENSSL_COMPAT ++#define NGX_QUIC_BORINGSSL_API 1 ++#endif ++ ++ ++/* ++ * RFC 9000, 7.5. Cryptographic Message Buffering ++ * ++ * Implementations MUST support buffering at least 4096 bytes of data ++ */ ++#define NGX_QUIC_MAX_BUFFERED 65535 ++ ++ ++#if (NGX_QUIC_BORINGSSL_API) ++static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *secret, size_t secret_len); ++static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *secret, size_t secret_len); ++#else ++static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const uint8_t *read_secret, ++ const uint8_t *write_secret, size_t secret_len); ++#endif ++ ++static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const uint8_t *data, size_t len); ++static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); ++static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, uint8_t alert); ++static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); ++ ++ ++#if (NGX_QUIC_BORINGSSL_API) ++ ++static int ++ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *rsecret, size_t secret_len) ++{ ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_set_read_secret() level:%d", level); ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic read secret len:%uz %*xs", secret_len, ++ secret_len, rsecret); ++#endif ++ ++ if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, ++ cipher, rsecret, secret_len) ++ != NGX_OK) ++ { ++ return 0; ++ } ++ ++ return 1; ++} ++ ++ ++static int ++ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, ++ const uint8_t *wsecret, size_t secret_len) ++{ ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_set_write_secret() level:%d", level); ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic write secret len:%uz %*xs", secret_len, ++ secret_len, wsecret); ++#endif ++ ++ if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, ++ cipher, wsecret, secret_len) ++ != NGX_OK) ++ { ++ return 0; ++ } ++ ++ return 1; ++} ++ ++#else ++ ++static int ++ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const uint8_t *rsecret, ++ const uint8_t *wsecret, size_t secret_len) ++{ ++ ngx_connection_t *c; ++ const SSL_CIPHER *cipher; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_set_encryption_secrets() level:%d", level); ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic read secret len:%uz %*xs", secret_len, ++ secret_len, rsecret); ++#endif ++ ++ cipher = SSL_get_current_cipher(ssl_conn); ++ ++ if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, ++ cipher, rsecret, secret_len) ++ != NGX_OK) ++ { ++ return 0; ++ } ++ ++ if (level == ssl_encryption_early_data) { ++ return 1; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_CRYPTO ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic write secret len:%uz %*xs", secret_len, ++ secret_len, wsecret); ++#endif ++ ++ if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, ++ cipher, wsecret, secret_len) ++ != NGX_OK) ++ { ++ return 0; ++ } ++ ++ return 1; ++} ++ ++#endif ++ ++ ++static int ++ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ++ enum ssl_encryption_level_t level, const uint8_t *data, size_t len) ++{ ++ u_char *p, *end; ++ size_t client_params_len; ++ ngx_chain_t *out; ++ const uint8_t *client_params; ++ ngx_quic_tp_t ctp; ++ ngx_quic_frame_t *frame; ++ ngx_connection_t *c; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) ++ unsigned int alpn_len; ++ const unsigned char *alpn_data; ++#endif ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ qc = ngx_quic_get_connection(c); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_add_handshake_data"); ++ ++ if (!qc->client_tp_done) { ++ /* ++ * things to do once during handshake: check ALPN and transport ++ * parameters; we want to break handshake if something is wrong ++ * here; ++ */ ++ ++#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) ++ ++ SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); ++ ++ if (alpn_len == 0) { ++ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); ++ qc->error_reason = "unsupported protocol in ALPN extension"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic unsupported protocol in ALPN extension"); ++ return 0; ++ } ++ ++#endif ++ ++ SSL_get_peer_quic_transport_params(ssl_conn, &client_params, ++ &client_params_len); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic SSL_get_peer_quic_transport_params():" ++ " params_len:%ui", client_params_len); ++ ++ if (client_params_len == 0) { ++ /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ ++ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); ++ qc->error_reason = "missing transport parameters"; ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "missing transport parameters"); ++ return 0; ++ } ++ ++ p = (u_char *) client_params; ++ end = p + client_params_len; ++ ++ /* defaults for parameters not sent by client */ ++ ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t)); ++ ++ if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) ++ != NGX_OK) ++ { ++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ++ qc->error_reason = "failed to process transport parameters"; ++ ++ return 0; ++ } ++ ++ if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { ++ return 0; ++ } ++ ++ qc->client_tp_done = 1; ++ } ++ ++ ctx = ngx_quic_get_send_ctx(qc, level); ++ ++ out = ngx_quic_copy_buffer(c, (u_char *) data, len); ++ if (out == NGX_CHAIN_ERROR) { ++ return 0; ++ } ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return 0; ++ } ++ ++ frame->data = out; ++ frame->level = level; ++ frame->type = NGX_QUIC_FT_CRYPTO; ++ frame->u.crypto.offset = ctx->crypto_sent; ++ frame->u.crypto.length = len; ++ ++ ctx->crypto_sent += len; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return 1; ++} ++ ++ ++static int ++ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) ++{ ++#if (NGX_DEBUG) ++ ngx_connection_t *c; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_flush_flight()"); ++#endif ++ return 1; ++} ++ ++ ++static int ++ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, ++ uint8_t alert) ++{ ++ ngx_connection_t *c; ++ ngx_quic_connection_t *qc; ++ ++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic ngx_quic_send_alert() level:%s alert:%d", ++ ngx_quic_level_name(level), (int) alert); ++ ++ /* already closed on regular shutdown */ ++ ++ qc = ngx_quic_get_connection(c); ++ if (qc == NULL) { ++ return 1; ++ } ++ ++ qc->error = NGX_QUIC_ERR_CRYPTO(alert); ++ qc->error_reason = "handshake failed"; ++ ++ return 1; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ++ ngx_quic_frame_t *frame) ++{ ++ uint64_t last; ++ ngx_chain_t *cl; ++ ngx_quic_send_ctx_t *ctx; ++ ngx_quic_connection_t *qc; ++ ngx_quic_crypto_frame_t *f; ++ ++ qc = ngx_quic_get_connection(c); ++ ctx = ngx_quic_get_send_ctx(qc, pkt->level); ++ f = &frame->u.crypto; ++ ++ /* no overflow since both values are 62-bit */ ++ last = f->offset + f->length; ++ ++ if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) { ++ qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; ++ return NGX_ERROR; ++ } ++ ++ if (last <= ctx->crypto.offset) { ++ if (pkt->level == ssl_encryption_initial) { ++ /* speeding up handshake completion */ ++ ++ if (!ngx_queue_empty(&ctx->sent)) { ++ ngx_quic_resend_frames(c, ctx); ++ ++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); ++ while (!ngx_queue_empty(&ctx->sent)) { ++ ngx_quic_resend_frames(c, ctx); ++ } ++ } ++ } ++ ++ return NGX_OK; ++ } ++ ++ if (f->offset == ctx->crypto.offset) { ++ if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_skip_buffer(c, &ctx->crypto, last); ++ ++ } else { ++ if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length, ++ f->offset) ++ == NGX_CHAIN_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ } ++ ++ cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); ++ ++ if (cl) { ++ if (ngx_quic_crypto_input(c, cl) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_quic_free_chain(c, cl); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) ++{ ++ int n, sslerr; ++ ngx_buf_t *b; ++ ngx_chain_t *cl; ++ ngx_ssl_conn_t *ssl_conn; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ ssl_conn = c->ssl->connection; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", ++ (int) SSL_quic_read_level(ssl_conn), ++ (int) SSL_quic_write_level(ssl_conn)); ++ ++ for (cl = data; cl; cl = cl->next) { ++ b = cl->buf; ++ ++ if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), ++ b->pos, b->last - b->pos)) ++ { ++ ngx_ssl_error(NGX_LOG_INFO, c->log, 0, ++ "SSL_provide_quic_data() failed"); ++ return NGX_ERROR; ++ } ++ } ++ ++ n = SSL_do_handshake(ssl_conn); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", ++ (int) SSL_quic_read_level(ssl_conn), ++ (int) SSL_quic_write_level(ssl_conn)); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); ++ ++ if (n <= 0) { ++ sslerr = SSL_get_error(ssl_conn, n); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", ++ sslerr); ++ ++ if (sslerr != SSL_ERROR_WANT_READ) { ++ ++ if (c->ssl->handshake_rejected) { ++ ngx_connection_error(c, 0, "handshake rejected"); ++ ERR_clear_error(); ++ ++ return NGX_ERROR; ++ } ++ ++ ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); ++ return NGX_ERROR; ++ } ++ } ++ ++ if (n <= 0 || SSL_in_init(ssl_conn)) { ++ if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data) ++ && qc->client_tp_done) ++ { ++ if (ngx_quic_init_streams(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++ } ++ ++#if (NGX_DEBUG) ++ ngx_ssl_handshake_log(c); ++#endif ++ ++ c->ssl->handshaked = 1; ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; ++ ngx_quic_queue_frame(qc, frame); ++ ++ if (qc->conf->retry) { ++ if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ /* ++ * RFC 9001, 9.5. Header Protection Timing Side Channels ++ * ++ * Generating next keys before a key update is received. ++ */ ++ ++ if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ /* ++ * RFC 9001, 4.9.2. Discarding Handshake Keys ++ * ++ * An endpoint MUST discard its Handshake keys ++ * when the TLS handshake is confirmed. ++ */ ++ ngx_quic_discard_ctx(c, ssl_encryption_handshake); ++ ++ /* start accepting clients on negotiated number of server ids */ ++ if (ngx_quic_create_sockets(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_quic_init_streams(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_init_connection(ngx_connection_t *c) ++{ ++ u_char *p; ++ size_t clen; ++ ssize_t len; ++ ngx_str_t dcid; ++ ngx_ssl_conn_t *ssl_conn; ++ ngx_quic_socket_t *qsock; ++ ngx_quic_connection_t *qc; ++ static SSL_QUIC_METHOD quic_method; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ c->ssl->no_wait_shutdown = 1; ++ ++ ssl_conn = c->ssl->connection; ++ ++ if (!quic_method.send_alert) { ++#if (NGX_QUIC_BORINGSSL_API) ++ quic_method.set_read_secret = ngx_quic_set_read_secret; ++ quic_method.set_write_secret = ngx_quic_set_write_secret; ++#else ++ quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets; ++#endif ++ quic_method.add_handshake_data = ngx_quic_add_handshake_data; ++ quic_method.flush_flight = ngx_quic_flush_flight; ++ quic_method.send_alert = ngx_quic_send_alert; ++ } ++ ++ if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic SSL_set_quic_method() failed"); ++ return NGX_ERROR; ++ } ++ ++#ifdef OPENSSL_INFO_QUIC ++ if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { ++ SSL_set_quic_early_data_enabled(ssl_conn, 1); ++ } ++#endif ++ ++ qsock = ngx_quic_get_socket(c); ++ ++ dcid.data = qsock->sid.id; ++ dcid.len = qsock->sid.len; ++ ++ if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); ++ /* always succeeds */ ++ ++ p = ngx_pnalloc(c->pool, len); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ ++ len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); ++ if (len < 0) { ++ return NGX_ERROR; ++ } ++ ++#ifdef NGX_QUIC_DEBUG_PACKETS ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic transport parameters len:%uz %*xs", len, len, p); ++#endif ++ ++ if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic SSL_set_quic_transport_params() failed"); ++ return NGX_ERROR; ++ } ++ ++#ifdef OPENSSL_IS_BORINGSSL ++ if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "quic SSL_set_quic_early_data_context() failed"); ++ return NGX_ERROR; ++ } ++#endif ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_ssl.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_ssl.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,19 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); ++ ++ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); ++ ++#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_streams.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_streams.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,1779 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_QUIC_STREAM_GONE (void *) -1 ++ ++ ++static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ++ ngx_uint_t err); ++static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c); ++static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c); ++static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id); ++static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id); ++static void ngx_quic_init_stream_handler(ngx_event_t *ev); ++static void ngx_quic_init_streams_handler(ngx_connection_t *c); ++static ngx_int_t ngx_quic_do_init_streams(ngx_connection_t *c); ++static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, ++ uint64_t id); ++static void ngx_quic_empty_handler(ngx_event_t *ev); ++static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, ++ size_t size); ++static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, ++ size_t size); ++static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ++ ngx_chain_t *in, off_t limit); ++static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs); ++static void ngx_quic_stream_cleanup_handler(void *data); ++static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs); ++static ngx_int_t ngx_quic_can_shutdown(ngx_connection_t *c); ++static ngx_int_t ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last); ++static ngx_int_t ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last); ++static ngx_int_t ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs); ++static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); ++static void ngx_quic_set_event(ngx_event_t *ev); ++ ++ ++ngx_connection_t * ++ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) ++{ ++ uint64_t id; ++ ngx_connection_t *pc, *sc; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ pc = c->quic ? c->quic->parent : c; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (qc->closing) { ++ return NULL; ++ } ++ ++ if (bidi) { ++ if (qc->streams.server_streams_bidi ++ >= qc->streams.server_max_streams_bidi) ++ { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic too many server bidi streams:%uL", ++ qc->streams.server_streams_bidi); ++ return NULL; ++ } ++ ++ id = (qc->streams.server_streams_bidi << 2) ++ | NGX_QUIC_STREAM_SERVER_INITIATED; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic creating server bidi stream" ++ " streams:%uL max:%uL id:0x%xL", ++ qc->streams.server_streams_bidi, ++ qc->streams.server_max_streams_bidi, id); ++ ++ qc->streams.server_streams_bidi++; ++ ++ } else { ++ if (qc->streams.server_streams_uni ++ >= qc->streams.server_max_streams_uni) ++ { ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic too many server uni streams:%uL", ++ qc->streams.server_streams_uni); ++ return NULL; ++ } ++ ++ id = (qc->streams.server_streams_uni << 2) ++ | NGX_QUIC_STREAM_SERVER_INITIATED ++ | NGX_QUIC_STREAM_UNIDIRECTIONAL; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic creating server uni stream" ++ " streams:%uL max:%uL id:0x%xL", ++ qc->streams.server_streams_uni, ++ qc->streams.server_max_streams_uni, id); ++ ++ qc->streams.server_streams_uni++; ++ } ++ ++ qs = ngx_quic_create_stream(pc, id); ++ if (qs == NULL) { ++ return NULL; ++ } ++ ++ sc = qs->connection; ++ ++ sc->write->active = 1; ++ sc->write->ready = 1; ++ ++ if (bidi) { ++ sc->read->active = 1; ++ } ++ ++ return sc; ++} ++ ++ ++void ++ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) ++{ ++ ngx_rbtree_node_t **p; ++ ngx_quic_stream_t *qn, *qnt; ++ ++ for ( ;; ) { ++ qn = (ngx_quic_stream_t *) node; ++ qnt = (ngx_quic_stream_t *) temp; ++ ++ p = (qn->id < qnt->id) ? &temp->left : &temp->right; ++ ++ if (*p == sentinel) { ++ break; ++ } ++ ++ temp = *p; ++ } ++ ++ *p = node; ++ node->parent = temp; ++ node->left = sentinel; ++ node->right = sentinel; ++ ngx_rbt_red(node); ++} ++ ++ ++ngx_quic_stream_t * ++ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) ++{ ++ ngx_rbtree_node_t *node, *sentinel; ++ ngx_quic_stream_t *qn; ++ ++ node = rbtree->root; ++ sentinel = rbtree->sentinel; ++ ++ while (node != sentinel) { ++ qn = (ngx_quic_stream_t *) node; ++ ++ if (id == qn->id) { ++ return qn; ++ } ++ ++ node = (id < qn->id) ? node->left : node->right; ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_int_t ++ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) ++{ ++ ngx_pool_t *pool; ++ ngx_queue_t *q; ++ ngx_rbtree_t *tree; ++ ngx_connection_t *sc; ++ ngx_rbtree_node_t *node; ++ ngx_quic_stream_t *qs; ++ ++ while (!ngx_queue_empty(&qc->streams.uninitialized)) { ++ q = ngx_queue_head(&qc->streams.uninitialized); ++ ngx_queue_remove(q); ++ ++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue); ++ pool = qs->connection->pool; ++ ++ ngx_close_connection(qs->connection); ++ ngx_destroy_pool(pool); ++ } ++ ++ tree = &qc->streams.tree; ++ ++ if (tree->root == tree->sentinel) { ++ return NGX_OK; ++ } ++ ++ node = ngx_rbtree_min(tree->root, tree->sentinel); ++ ++ while (node) { ++ qs = (ngx_quic_stream_t *) node; ++ node = ngx_rbtree_next(tree, node); ++ sc = qs->connection; ++ ++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; ++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; ++ ++ if (sc == NULL) { ++ ngx_quic_close_stream(qs); ++ continue; ++ } ++ ++ sc->read->error = 1; ++ sc->write->error = 1; ++ ++ ngx_quic_set_event(sc->read); ++ ngx_quic_set_event(sc->write); ++ ++ sc->close = 1; ++ sc->read->handler(sc->read); ++ } ++ ++ if (tree->root == tree->sentinel) { ++ return NGX_OK; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic connection has active streams"); ++ ++ return NGX_AGAIN; ++} ++ ++ ++ngx_int_t ++ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) ++{ ++ return ngx_quic_do_reset_stream(c->quic, err); ++} ++ ++ ++static ngx_int_t ++ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err) ++{ ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD ++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT ++ || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) ++ { ++ return NGX_OK; ++ } ++ ++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; ++ qs->send_final_size = qs->send_offset; ++ ++ if (qs->connection) { ++ qs->connection->write->error = 1; ++ } ++ ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL reset", qs->id); ++ ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_RESET_STREAM; ++ frame->u.reset_stream.id = qs->id; ++ frame->u.reset_stream.error_code = err; ++ frame->u.reset_stream.final_size = qs->send_offset; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ ngx_quic_free_buffer(pc, &qs->send); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_shutdown_stream(ngx_connection_t *c, int how) ++{ ++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { ++ if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) { ++ if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_shutdown_stream_send(ngx_connection_t *c) ++{ ++ ngx_quic_stream_t *qs; ++ ++ qs = c->quic; ++ ++ if (qs->send_state != NGX_QUIC_STREAM_SEND_READY ++ && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) ++ { ++ return NGX_OK; ++ } ++ ++ qs->send_state = NGX_QUIC_STREAM_SEND_SEND; ++ qs->send_final_size = c->sent; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, ++ "quic stream id:0x%xL send shutdown", qs->id); ++ ++ return ngx_quic_stream_flush(qs); ++} ++ ++ ++static ngx_int_t ++ngx_quic_shutdown_stream_recv(ngx_connection_t *c) ++{ ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qs = c->quic; ++ ++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV ++ && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) ++ { ++ return NGX_OK; ++ } ++ ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (qc->conf->stream_close_code == 0) { ++ return NGX_OK; ++ } ++ ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL recv shutdown", qs->id); ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_STOP_SENDING; ++ frame->u.stop_sending.id = qs->id; ++ frame->u.stop_sending.error_code = qc->conf->stream_close_code; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_quic_stream_t * ++ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) ++{ ++ uint64_t min_id; ++ ngx_event_t *rev; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ qs = ngx_quic_find_stream(&qc->streams.tree, id); ++ ++ if (qs) { ++ return qs; ++ } ++ ++ if (qc->shutdown || qc->closing) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL is missing", id); ++ ++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ ++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { ++ if ((id >> 2) < qc->streams.server_streams_uni) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NULL; ++ } ++ ++ if ((id >> 2) < qc->streams.client_streams_uni) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ if ((id >> 2) >= qc->streams.client_max_streams_uni) { ++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; ++ return NULL; ++ } ++ ++ min_id = (qc->streams.client_streams_uni << 2) ++ | NGX_QUIC_STREAM_UNIDIRECTIONAL; ++ qc->streams.client_streams_uni = (id >> 2) + 1; ++ ++ } else { ++ ++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { ++ if ((id >> 2) < qc->streams.server_streams_bidi) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NULL; ++ } ++ ++ if ((id >> 2) < qc->streams.client_streams_bidi) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ if ((id >> 2) >= qc->streams.client_max_streams_bidi) { ++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; ++ return NULL; ++ } ++ ++ min_id = (qc->streams.client_streams_bidi << 2); ++ qc->streams.client_streams_bidi = (id >> 2) + 1; ++ } ++ ++ /* ++ * RFC 9000, 2.1. Stream Types and Identifiers ++ * ++ * successive streams of each type are created with numerically increasing ++ * stream IDs. A stream ID that is used out of order results in all ++ * streams of that type with lower-numbered stream IDs also being opened. ++ */ ++ ++#if (NGX_SUPPRESS_WARN) ++ qs = NULL; ++#endif ++ ++ for ( /* void */ ; min_id <= id; min_id += 0x04) { ++ ++ qs = ngx_quic_create_stream(c, min_id); ++ ++ if (qs == NULL) { ++ if (ngx_quic_reject_stream(c, min_id) != NGX_OK) { ++ return NULL; ++ } ++ ++ continue; ++ } ++ ++ ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); ++ ++ rev = qs->connection->read; ++ rev->handler = ngx_quic_init_stream_handler; ++ ++ if (qc->streams.initialized) { ++ ngx_post_event(rev, &ngx_posted_events); ++ ++ if (qc->push.posted) { ++ /* ++ * The posted stream can produce output immediately. ++ * By postponing the push event, we coalesce the stream ++ * output with queued frames in one UDP datagram. ++ */ ++ ++ ngx_delete_posted_event(&qc->push); ++ ngx_post_event(&qc->push, &ngx_posted_events); ++ } ++ } ++ } ++ ++ if (qs == NULL) { ++ return NGX_QUIC_STREAM_GONE; ++ } ++ ++ return qs; ++} ++ ++ ++static ngx_int_t ++ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) ++{ ++ uint64_t code; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ++ ? qc->conf->stream_reject_code_uni ++ : qc->conf->stream_reject_code_bidi; ++ ++ if (code == 0) { ++ return NGX_DECLINED; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL reject err:0x%xL", id, code); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_RESET_STREAM; ++ frame->u.reset_stream.id = id; ++ frame->u.reset_stream.error_code = code; ++ frame->u.reset_stream.final_size = 0; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_STOP_SENDING; ++ frame->u.stop_sending.id = id; ++ frame->u.stop_sending.error_code = code; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_init_stream_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ngx_quic_stream_t *qs; ++ ++ c = ev->data; ++ qs = c->quic; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); ++ ++ if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { ++ c->write->active = 1; ++ c->write->ready = 1; ++ } ++ ++ c->read->active = 1; ++ ++ ngx_queue_remove(&qs->queue); ++ ++ c->listening->handler(c); ++} ++ ++ ++ngx_int_t ++ngx_quic_init_streams(ngx_connection_t *c) ++{ ++ ngx_int_t rc; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc->streams.initialized) { ++ return NGX_OK; ++ } ++ ++ rc = ngx_ssl_ocsp_validate(c); ++ ++ if (rc == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_AGAIN) { ++ c->ssl->handler = ngx_quic_init_streams_handler; ++ return NGX_OK; ++ } ++ ++ return ngx_quic_do_init_streams(c); ++} ++ ++ ++static void ++ngx_quic_init_streams_handler(ngx_connection_t *c) ++{ ++ if (ngx_quic_do_init_streams(c) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ } ++} ++ ++ ++static ngx_int_t ++ngx_quic_do_init_streams(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams"); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (qc->conf->init) { ++ if (qc->conf->init(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ for (q = ngx_queue_head(&qc->streams.uninitialized); ++ q != ngx_queue_sentinel(&qc->streams.uninitialized); ++ q = ngx_queue_next(q)) ++ { ++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue); ++ ngx_post_event(qs->connection->read, &ngx_posted_events); ++ } ++ ++ qc->streams.initialized = 1; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_quic_stream_t * ++ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) ++{ ++ ngx_log_t *log; ++ ngx_pool_t *pool; ++ ngx_uint_t reusable; ++ ngx_queue_t *q; ++ ngx_connection_t *sc; ++ ngx_quic_stream_t *qs; ++ ngx_pool_cleanup_t *cln; ++ ngx_quic_connection_t *qc; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL create", id); ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (!ngx_queue_empty(&qc->streams.free)) { ++ q = ngx_queue_head(&qc->streams.free); ++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue); ++ ngx_queue_remove(&qs->queue); ++ ++ } else { ++ /* ++ * the number of streams is limited by transport ++ * parameters and application requirements ++ */ ++ ++ qs = ngx_palloc(c->pool, sizeof(ngx_quic_stream_t)); ++ if (qs == NULL) { ++ return NULL; ++ } ++ } ++ ++ ngx_memzero(qs, sizeof(ngx_quic_stream_t)); ++ ++ qs->node.key = id; ++ qs->parent = c; ++ qs->id = id; ++ qs->send_final_size = (uint64_t) -1; ++ qs->recv_final_size = (uint64_t) -1; ++ ++ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); ++ if (pool == NULL) { ++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue); ++ return NULL; ++ } ++ ++ log = ngx_palloc(pool, sizeof(ngx_log_t)); ++ if (log == NULL) { ++ ngx_destroy_pool(pool); ++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue); ++ return NULL; ++ } ++ ++ *log = *c->log; ++ pool->log = log; ++ ++ reusable = c->reusable; ++ ngx_reusable_connection(c, 0); ++ ++ sc = ngx_get_connection(c->fd, log); ++ if (sc == NULL) { ++ ngx_destroy_pool(pool); ++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue); ++ ngx_reusable_connection(c, reusable); ++ return NULL; ++ } ++ ++ qs->connection = sc; ++ ++ sc->quic = qs; ++ sc->shared = 1; ++ sc->type = SOCK_STREAM; ++ sc->pool = pool; ++ sc->ssl = c->ssl; ++ sc->sockaddr = c->sockaddr; ++ sc->listening = c->listening; ++ sc->addr_text = c->addr_text; ++ sc->local_sockaddr = c->local_sockaddr; ++ sc->local_socklen = c->local_socklen; ++ sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); ++ sc->start_time = c->start_time; ++ sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; ++ ++ sc->recv = ngx_quic_stream_recv; ++ sc->send = ngx_quic_stream_send; ++ sc->send_chain = ngx_quic_stream_send_chain; ++ ++ sc->read->log = log; ++ sc->write->log = log; ++ ++ sc->read->handler = ngx_quic_empty_handler; ++ sc->write->handler = ngx_quic_empty_handler; ++ ++ log->connection = sc->number; ++ ++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { ++ qs->send_max_data = qc->ctp.initial_max_stream_data_uni; ++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; ++ qs->send_state = NGX_QUIC_STREAM_SEND_READY; ++ ++ } else { ++ qs->recv_max_data = qc->tp.initial_max_stream_data_uni; ++ qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; ++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; ++ } ++ ++ } else { ++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { ++ qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; ++ qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; ++ ++ } else { ++ qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; ++ qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; ++ } ++ ++ qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; ++ qs->send_state = NGX_QUIC_STREAM_SEND_READY; ++ } ++ ++ qs->recv_window = qs->recv_max_data; ++ ++ cln = ngx_pool_cleanup_add(pool, 0); ++ if (cln == NULL) { ++ ngx_close_connection(sc); ++ ngx_destroy_pool(pool); ++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue); ++ ngx_reusable_connection(c, reusable); ++ return NULL; ++ } ++ ++ cln->handler = ngx_quic_stream_cleanup_handler; ++ cln->data = sc; ++ ++ ngx_rbtree_insert(&qc->streams.tree, &qs->node); ++ ++ return qs; ++} ++ ++ ++void ++ngx_quic_cancelable_stream(ngx_connection_t *c) ++{ ++ ngx_connection_t *pc; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qs = c->quic; ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (!qs->cancelable) { ++ qs->cancelable = 1; ++ ++ if (ngx_quic_can_shutdown(pc) == NGX_OK) { ++ ngx_reusable_connection(pc, 1); ++ ++ if (qc->shutdown) { ++ ngx_quic_shutdown_quic(pc); ++ } ++ } ++ } ++} ++ ++ ++static void ++ngx_quic_empty_handler(ngx_event_t *ev) ++{ ++} ++ ++ ++static ssize_t ++ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ++{ ++ ssize_t len; ++ ngx_buf_t *b; ++ ngx_chain_t *cl, *in; ++ ngx_event_t *rev; ++ ngx_connection_t *pc; ++ ngx_quic_stream_t *qs; ++ ++ qs = c->quic; ++ pc = qs->parent; ++ rev = c->read; ++ ++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD ++ || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) ++ { ++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL recv buf:%uz", qs->id, size); ++ ++ if (size == 0) { ++ return 0; ++ } ++ ++ in = ngx_quic_read_buffer(pc, &qs->recv, size); ++ if (in == NGX_CHAIN_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ len = 0; ++ ++ for (cl = in; cl; cl = cl->next) { ++ b = cl->buf; ++ len += b->last - b->pos; ++ buf = ngx_cpymem(buf, b->pos, b->last - b->pos); ++ } ++ ++ ngx_quic_free_chain(pc, in); ++ ++ if (len == 0) { ++ rev->ready = 0; ++ ++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD ++ && qs->recv_offset == qs->recv_final_size) ++ { ++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; ++ } ++ ++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) { ++ rev->eof = 1; ++ return 0; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL recv() not ready", qs->id); ++ return NGX_AGAIN; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL recv len:%z", qs->id, len); ++ ++ if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return len; ++} ++ ++ ++static ssize_t ++ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ++{ ++ ngx_buf_t b; ++ ngx_chain_t cl; ++ ++ ngx_memzero(&b, sizeof(ngx_buf_t)); ++ ++ b.memory = 1; ++ b.pos = buf; ++ b.last = buf + size; ++ ++ cl.buf = &b; ++ cl.next = NULL; ++ ++ if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (b.pos == buf) { ++ return NGX_AGAIN; ++ } ++ ++ return b.pos - buf; ++} ++ ++ ++static ngx_chain_t * ++ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) ++{ ++ uint64_t n, flow; ++ ngx_event_t *wev; ++ ngx_connection_t *pc; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qs = c->quic; ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ wev = c->write; ++ ++ if (qs->send_state != NGX_QUIC_STREAM_SEND_READY ++ && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) ++ { ++ wev->error = 1; ++ return NGX_CHAIN_ERROR; ++ } ++ ++ qs->send_state = NGX_QUIC_STREAM_SEND_SEND; ++ ++ flow = qs->acked + qc->conf->stream_buffer_size - qs->sent; ++ ++ if (flow == 0) { ++ wev->ready = 0; ++ return in; ++ } ++ ++ if (limit == 0 || limit > (off_t) flow) { ++ limit = flow; ++ } ++ ++ n = qs->send.size; ++ ++ in = ngx_quic_write_buffer(pc, &qs->send, in, limit, qs->sent); ++ if (in == NGX_CHAIN_ERROR) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ n = qs->send.size - n; ++ c->sent += n; ++ qs->sent += n; ++ qc->streams.sent += n; ++ ++ if (flow == n) { ++ wev->ready = 0; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic send_chain sent:%uL", n); ++ ++ if (ngx_quic_stream_flush(qs) != NGX_OK) { ++ return NGX_CHAIN_ERROR; ++ } ++ ++ return in; ++} ++ ++ ++static ngx_int_t ++ngx_quic_stream_flush(ngx_quic_stream_t *qs) ++{ ++ off_t limit, len; ++ ngx_uint_t last; ++ ngx_chain_t *out; ++ ngx_quic_frame_t *frame; ++ ngx_connection_t *pc; ++ ngx_quic_connection_t *qc; ++ ++ if (qs->send_state != NGX_QUIC_STREAM_SEND_SEND) { ++ return NGX_OK; ++ } ++ ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (qc->streams.send_max_data == 0) { ++ qc->streams.send_max_data = qc->ctp.initial_max_data; ++ } ++ ++ limit = ngx_min(qc->streams.send_max_data - qc->streams.send_offset, ++ qs->send_max_data - qs->send_offset); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL flush limit:%O", qs->id, limit); ++ ++ len = qs->send.offset; ++ ++ out = ngx_quic_read_buffer(pc, &qs->send, limit); ++ if (out == NGX_CHAIN_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ len = qs->send.offset - len; ++ last = 0; ++ ++ if (qs->send_final_size != (uint64_t) -1 ++ && qs->send_final_size == qs->send.offset) ++ { ++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; ++ last = 1; ++ } ++ ++ if (len == 0 && !last) { ++ return NGX_OK; ++ } ++ ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_STREAM; ++ frame->data = out; ++ ++ frame->u.stream.off = 1; ++ frame->u.stream.len = 1; ++ frame->u.stream.fin = last; ++ ++ frame->u.stream.stream_id = qs->id; ++ frame->u.stream.offset = qs->send_offset; ++ frame->u.stream.length = len; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ qs->send_offset += len; ++ qc->streams.send_offset += len; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL flush len:%O last:%ui", ++ qs->id, len, last); ++ ++ if (qs->connection == NULL) { ++ return ngx_quic_close_stream(qs); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_stream_cleanup_handler(void *data) ++{ ++ ngx_connection_t *c = data; ++ ++ ngx_quic_stream_t *qs; ++ ++ qs = c->quic; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, ++ "quic stream id:0x%xL cleanup", qs->id); ++ ++ if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++ ++ qs->connection = NULL; ++ ++ if (ngx_quic_close_stream(qs) != NGX_OK) { ++ ngx_quic_close_connection(c, NGX_ERROR); ++ return; ++ } ++} ++ ++ ++static ngx_int_t ++ngx_quic_close_stream(ngx_quic_stream_t *qs) ++{ ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (!qc->closing) { ++ /* make sure everything is sent and final size is received */ ++ ++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV) { ++ return NGX_OK; ++ } ++ ++ if (qs->send_state != NGX_QUIC_STREAM_SEND_DATA_RECVD ++ && qs->send_state != NGX_QUIC_STREAM_SEND_RESET_RECVD) ++ { ++ return NGX_OK; ++ } ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL close", qs->id); ++ ++ ngx_quic_free_buffer(pc, &qs->send); ++ ngx_quic_free_buffer(pc, &qs->recv); ++ ++ ngx_rbtree_delete(&qc->streams.tree, &qs->node); ++ ngx_queue_insert_tail(&qc->streams.free, &qs->queue); ++ ++ if (qc->closing) { ++ /* schedule handler call to continue ngx_quic_close_connection() */ ++ ngx_post_event(&qc->close, &ngx_posted_events); ++ return NGX_OK; ++ } ++ ++ if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) { ++ ngx_reusable_connection(pc, 1); ++ } ++ ++ if (qc->shutdown) { ++ ngx_quic_shutdown_quic(pc); ++ return NGX_OK; ++ } ++ ++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_MAX_STREAMS; ++ ++ if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni; ++ frame->u.max_streams.bidi = 0; ++ ++ } else { ++ frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi; ++ frame->u.max_streams.bidi = 1; ++ } ++ ++ ngx_quic_queue_frame(qc, frame); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_can_shutdown(ngx_connection_t *c) ++{ ++ ngx_rbtree_t *tree; ++ ngx_rbtree_node_t *node; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ tree = &qc->streams.tree; ++ ++ if (tree->root != tree->sentinel) { ++ for (node = ngx_rbtree_min(tree->root, tree->sentinel); ++ node; ++ node = ngx_rbtree_next(tree, node)) ++ { ++ qs = (ngx_quic_stream_t *) node; ++ ++ if (!qs->cancelable) { ++ return NGX_DECLINED; ++ } ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ++ ngx_quic_frame_t *frame) ++{ ++ uint64_t last; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ngx_quic_stream_frame_t *f; ++ ++ qc = ngx_quic_get_connection(c); ++ f = &frame->u.stream; ++ ++ if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ++ && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) ++ { ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ /* no overflow since both values are 62-bit */ ++ last = f->offset + f->length; ++ ++ qs = ngx_quic_get_stream(c, f->stream_id); ++ ++ if (qs == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (qs == NGX_QUIC_STREAM_GONE) { ++ return NGX_OK; ++ } ++ ++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV ++ && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) ++ { ++ return NGX_OK; ++ } ++ ++ if (ngx_quic_control_flow(qs, last) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) { ++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ if (last < qs->recv_offset) { ++ return NGX_OK; ++ } ++ ++ if (f->fin) { ++ if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last) ++ { ++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ if (qs->recv_last > last) { ++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ qs->recv_final_size = last; ++ qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN; ++ } ++ ++ if (ngx_quic_write_buffer(c, &qs->recv, frame->data, f->length, f->offset) ++ == NGX_CHAIN_ERROR) ++ { ++ return NGX_ERROR; ++ } ++ ++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN ++ && qs->recv.size == qs->recv_final_size) ++ { ++ qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD; ++ } ++ ++ if (qs->connection == NULL) { ++ return ngx_quic_close_stream(qs); ++ } ++ ++ if (f->offset <= qs->recv_offset) { ++ ngx_quic_set_event(qs->connection->read); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_max_data_frame(ngx_connection_t *c, ++ ngx_quic_max_data_frame_t *f) ++{ ++ ngx_rbtree_t *tree; ++ ngx_rbtree_node_t *node; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ tree = &qc->streams.tree; ++ ++ if (f->max_data <= qc->streams.send_max_data) { ++ return NGX_OK; ++ } ++ ++ if (tree->root == tree->sentinel ++ || qc->streams.send_offset < qc->streams.send_max_data) ++ { ++ /* not blocked on MAX_DATA */ ++ qc->streams.send_max_data = f->max_data; ++ return NGX_OK; ++ } ++ ++ qc->streams.send_max_data = f->max_data; ++ node = ngx_rbtree_min(tree->root, tree->sentinel); ++ ++ while (node && qc->streams.send_offset < qc->streams.send_max_data) { ++ ++ qs = (ngx_quic_stream_t *) node; ++ node = ngx_rbtree_next(tree, node); ++ ++ if (ngx_quic_stream_flush(qs) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) ++{ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f) ++{ ++ return ngx_quic_update_max_data(c); ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) ++{ ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) ++ { ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ qs = ngx_quic_get_stream(c, f->id); ++ ++ if (qs == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (qs == NGX_QUIC_STREAM_GONE) { ++ return NGX_OK; ++ } ++ ++ return ngx_quic_update_max_stream_data(qs); ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) ++{ ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) ++ { ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ qs = ngx_quic_get_stream(c, f->id); ++ ++ if (qs == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (qs == NGX_QUIC_STREAM_GONE) { ++ return NGX_OK; ++ } ++ ++ if (f->limit <= qs->send_max_data) { ++ return NGX_OK; ++ } ++ ++ if (qs->send_offset < qs->send_max_data) { ++ /* not blocked on MAX_STREAM_DATA */ ++ qs->send_max_data = f->limit; ++ return NGX_OK; ++ } ++ ++ qs->send_max_data = f->limit; ++ ++ return ngx_quic_stream_flush(qs); ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) ++{ ++ ngx_event_t *rev; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) ++ { ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ qs = ngx_quic_get_stream(c, f->id); ++ ++ if (qs == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (qs == NGX_QUIC_STREAM_GONE) { ++ return NGX_OK; ++ } ++ ++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD ++ || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) ++ { ++ return NGX_OK; ++ } ++ ++ qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; ++ ++ if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (qs->recv_final_size != (uint64_t) -1 ++ && qs->recv_final_size != f->final_size) ++ { ++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ if (qs->recv_last > f->final_size) { ++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ qs->recv_final_size = f->final_size; ++ ++ if (ngx_quic_update_flow(qs, qs->recv_final_size) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (qs->connection == NULL) { ++ return ngx_quic_close_stream(qs); ++ } ++ ++ rev = qs->connection->read; ++ rev->error = 1; ++ ++ ngx_quic_set_event(rev); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) ++{ ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) ++ { ++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; ++ return NGX_ERROR; ++ } ++ ++ qs = ngx_quic_get_stream(c, f->id); ++ ++ if (qs == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (qs == NGX_QUIC_STREAM_GONE) { ++ return NGX_OK; ++ } ++ ++ if (ngx_quic_do_reset_stream(qs, f->error_code) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (qs->connection == NULL) { ++ return ngx_quic_close_stream(qs); ++ } ++ ++ ngx_quic_set_event(qs->connection->write); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_handle_max_streams_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) ++{ ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ if (f->bidi) { ++ if (qc->streams.server_max_streams_bidi < f->limit) { ++ qc->streams.server_max_streams_bidi = f->limit; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic max_streams_bidi:%uL", f->limit); ++ } ++ ++ } else { ++ if (qc->streams.server_max_streams_uni < f->limit) { ++ qc->streams.server_max_streams_uni = f->limit; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic max_streams_uni:%uL", f->limit); ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ++{ ++ uint64_t acked; ++ ngx_quic_stream_t *qs; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ switch (f->type) { ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ ++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.reset_stream.id); ++ if (qs == NULL) { ++ return; ++ } ++ ++ qs->send_state = NGX_QUIC_STREAM_SEND_RESET_RECVD; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL ack reset final_size:%uL", ++ qs->id, f->u.reset_stream.final_size); ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ ++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); ++ if (qs == NULL) { ++ return; ++ } ++ ++ acked = qs->acked; ++ qs->acked += f->u.stream.length; ++ ++ if (f->u.stream.fin) { ++ qs->fin_acked = 1; ++ } ++ ++ if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_SENT ++ && qs->acked == qs->sent && qs->fin_acked) ++ { ++ qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stream id:0x%xL ack len:%uL fin:%d unacked:%uL", ++ qs->id, f->u.stream.length, f->u.stream.fin, ++ qs->sent - qs->acked); ++ ++ if (qs->connection ++ && qs->sent - acked == qc->conf->stream_buffer_size ++ && f->u.stream.length > 0) ++ { ++ ngx_quic_set_event(qs->connection->write); ++ } ++ ++ break; ++ ++ default: ++ return; ++ } ++ ++ if (qs->connection == NULL) { ++ ngx_quic_close_stream(qs); ++ } ++} ++ ++ ++static ngx_int_t ++ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last) ++{ ++ uint64_t len; ++ ngx_connection_t *pc; ++ ngx_quic_connection_t *qc; ++ ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (last <= qs->recv_last) { ++ return NGX_OK; ++ } ++ ++ len = last - qs->recv_last; ++ ++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL flow control msd:%uL/%uL md:%uL/%uL", ++ qs->id, last, qs->recv_max_data, qc->streams.recv_last + len, ++ qc->streams.recv_max_data); ++ ++ qs->recv_last += len; ++ ++ if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV ++ && qs->recv_last > qs->recv_max_data) ++ { ++ qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; ++ return NGX_ERROR; ++ } ++ ++ qc->streams.recv_last += len; ++ ++ if (qc->streams.recv_last > qc->streams.recv_max_data) { ++ qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last) ++{ ++ uint64_t len; ++ ngx_connection_t *pc; ++ ngx_quic_connection_t *qc; ++ ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (last <= qs->recv_offset) { ++ return NGX_OK; ++ } ++ ++ len = last - qs->recv_offset; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL flow update %uL", qs->id, last); ++ ++ qs->recv_offset += len; ++ ++ if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) { ++ if (ngx_quic_update_max_stream_data(qs) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ qc->streams.recv_offset += len; ++ ++ if (qc->streams.recv_max_data ++ <= qc->streams.recv_offset + qc->streams.recv_window / 2) ++ { ++ if (ngx_quic_update_max_data(pc) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs) ++{ ++ uint64_t recv_max_data; ++ ngx_connection_t *pc; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ pc = qs->parent; ++ qc = ngx_quic_get_connection(pc); ++ ++ if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) { ++ return NGX_OK; ++ } ++ ++ recv_max_data = qs->recv_offset + qs->recv_window; ++ ++ if (qs->recv_max_data == recv_max_data) { ++ return NGX_OK; ++ } ++ ++ qs->recv_max_data = recv_max_data; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, ++ "quic stream id:0x%xL flow update msd:%uL", ++ qs->id, qs->recv_max_data); ++ ++ frame = ngx_quic_alloc_frame(pc); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; ++ frame->u.max_stream_data.id = qs->id; ++ frame->u.max_stream_data.limit = qs->recv_max_data; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_update_max_data(ngx_connection_t *c) ++{ ++ uint64_t recv_max_data; ++ ngx_quic_frame_t *frame; ++ ngx_quic_connection_t *qc; ++ ++ qc = ngx_quic_get_connection(c); ++ ++ recv_max_data = qc->streams.recv_offset + qc->streams.recv_window; ++ ++ if (qc->streams.recv_max_data == recv_max_data) { ++ return NGX_OK; ++ } ++ ++ qc->streams.recv_max_data = recv_max_data; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic flow update md:%uL", qc->streams.recv_max_data); ++ ++ frame = ngx_quic_alloc_frame(c); ++ if (frame == NULL) { ++ return NGX_ERROR; ++ } ++ ++ frame->level = ssl_encryption_application; ++ frame->type = NGX_QUIC_FT_MAX_DATA; ++ frame->u.max_data.max_data = qc->streams.recv_max_data; ++ ++ ngx_quic_queue_frame(qc, frame); ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_set_event(ngx_event_t *ev) ++{ ++ ev->ready = 1; ++ ++ if (ev->active) { ++ ngx_post_event(ev, &ngx_posted_events); ++ } ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_streams.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_streams.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,44 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); ++void ngx_quic_handle_stream_ack(ngx_connection_t *c, ++ ngx_quic_frame_t *f); ++ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, ++ ngx_quic_max_data_frame_t *f); ++ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); ++ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f); ++ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); ++ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); ++ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); ++ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); ++ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c, ++ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f); ++ ++ngx_int_t ngx_quic_init_streams(ngx_connection_t *c); ++void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); ++ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, ++ uint64_t id); ++ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ++ ngx_quic_connection_t *qc); ++ ++#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_tokens.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_tokens.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,289 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, ++ ngx_uint_t no_port, u_char buf[20]); ++ ++ ++ngx_int_t ++ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, ++ u_char *token) ++{ ++ ngx_str_t tmp; ++ ++ tmp.data = secret; ++ tmp.len = NGX_QUIC_SR_KEY_LEN; ++ ++ if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, ++ NGX_QUIC_SR_TOKEN_LEN) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic stateless reset token %*xs", ++ (size_t) NGX_QUIC_SR_TOKEN_LEN, token); ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, ++ socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, ++ time_t exp, ngx_uint_t is_retry) ++{ ++ int len, iv_len; ++ u_char *p, *iv; ++ EVP_CIPHER_CTX *ctx; ++ const EVP_CIPHER *cipher; ++ ++ u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; ++ ++ ngx_quic_address_hash(sockaddr, socklen, !is_retry, in); ++ ++ p = in + 20; ++ ++ p = ngx_cpymem(p, &exp, sizeof(time_t)); ++ ++ *p++ = is_retry ? 1 : 0; ++ ++ if (odcid) { ++ *p++ = odcid->len; ++ p = ngx_cpymem(p, odcid->data, odcid->len); ++ ++ } else { ++ *p++ = 0; ++ } ++ ++ len = p - in; ++ ++ cipher = EVP_aes_256_cbc(); ++ iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; ++ ++ if ((size_t) (iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) > token->len) ++ { ++ ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small"); ++ return NGX_ERROR; ++ } ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ iv = token->data; ++ ++ if (RAND_bytes(iv, iv_len) <= 0 ++ || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) ++ { ++ EVP_CIPHER_CTX_free(ctx); ++ return NGX_ERROR; ++ } ++ ++ token->len = iv_len; ++ ++ if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ return NGX_ERROR; ++ } ++ ++ token->len += len; ++ ++ if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { ++ EVP_CIPHER_CTX_free(ctx); ++ return NGX_ERROR; ++ } ++ ++ token->len += len; ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++#ifdef NGX_QUIC_DEBUG_PACKETS ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic new token len:%uz %xV", token->len, token); ++#endif ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, ++ ngx_uint_t no_port, u_char buf[20]) ++{ ++ size_t len; ++ u_char *data; ++ ngx_sha1_t sha1; ++ struct sockaddr_in *sin; ++#if (NGX_HAVE_INET6) ++ struct sockaddr_in6 *sin6; ++#endif ++ ++ len = (size_t) socklen; ++ data = (u_char *) sockaddr; ++ ++ if (no_port) { ++ switch (sockaddr->sa_family) { ++ ++#if (NGX_HAVE_INET6) ++ case AF_INET6: ++ sin6 = (struct sockaddr_in6 *) sockaddr; ++ ++ len = sizeof(struct in6_addr); ++ data = sin6->sin6_addr.s6_addr; ++ ++ break; ++#endif ++ ++ case AF_INET: ++ sin = (struct sockaddr_in *) sockaddr; ++ ++ len = sizeof(in_addr_t); ++ data = (u_char *) &sin->sin_addr; ++ ++ break; ++ } ++ } ++ ++ ngx_sha1_init(&sha1); ++ ngx_sha1_update(&sha1, data, len); ++ ngx_sha1_final(buf, &sha1); ++} ++ ++ ++ngx_int_t ++ngx_quic_validate_token(ngx_connection_t *c, u_char *key, ++ ngx_quic_header_t *pkt) ++{ ++ int len, tlen, iv_len; ++ u_char *iv, *p; ++ time_t now, exp; ++ size_t total; ++ ngx_str_t odcid; ++ EVP_CIPHER_CTX *ctx; ++ const EVP_CIPHER *cipher; ++ ++ u_char addr_hash[20]; ++ u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; ++ ++#if NGX_SUPPRESS_WARN ++ ngx_str_null(&odcid); ++#endif ++ ++ /* Retry token or NEW_TOKEN in a previous connection */ ++ ++ cipher = EVP_aes_256_cbc(); ++ iv = pkt->token.data; ++ iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; ++ ++ /* sanity checks */ ++ ++ if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) { ++ goto garbage; ++ } ++ ++ if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { ++ goto garbage; ++ } ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (ctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { ++ EVP_CIPHER_CTX_free(ctx); ++ return NGX_ERROR; ++ } ++ ++ p = pkt->token.data + iv_len; ++ len = pkt->token.len - iv_len; ++ ++ if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { ++ EVP_CIPHER_CTX_free(ctx); ++ goto garbage; ++ } ++ total = len; ++ ++ if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { ++ EVP_CIPHER_CTX_free(ctx); ++ goto garbage; ++ } ++ total += tlen; ++ ++ EVP_CIPHER_CTX_free(ctx); ++ ++ if (total < (20 + sizeof(time_t) + 2)) { ++ goto garbage; ++ } ++ ++ p = tdec + 20; ++ ++ ngx_memcpy(&exp, p, sizeof(time_t)); ++ p += sizeof(time_t); ++ ++ pkt->retried = (*p++ == 1); ++ ++ ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash); ++ ++ if (ngx_memcmp(tdec, addr_hash, 20) != 0) { ++ goto bad_token; ++ } ++ ++ odcid.len = *p++; ++ if (odcid.len) { ++ if (odcid.len > NGX_QUIC_MAX_CID_LEN) { ++ goto bad_token; ++ } ++ ++ if ((size_t)(tdec + total - p) < odcid.len) { ++ goto bad_token; ++ } ++ ++ odcid.data = p; ++ } ++ ++ now = ngx_time(); ++ ++ if (now > exp) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); ++ return NGX_DECLINED; ++ } ++ ++ if (odcid.len) { ++ pkt->odcid.len = odcid.len; ++ pkt->odcid.data = pkt->odcid_buf; ++ ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len); ++ ++ } else { ++ pkt->odcid = pkt->dcid; ++ } ++ ++ pkt->validated = 1; ++ ++ return NGX_OK; ++ ++garbage: ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); ++ ++ return NGX_ABORT; ++ ++bad_token: ++ ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); ++ ++ return NGX_DECLINED; ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_tokens.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_tokens.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,35 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++#define NGX_QUIC_MAX_TOKEN_SIZE 64 ++ /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ ++ ++/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */ ++#define NGX_QUIC_AES_256_CBC_IV_LEN 16 ++#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16 ++ ++#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_CBC_IV_LEN \ ++ + NGX_QUIC_MAX_TOKEN_SIZE \ ++ + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) ++ ++ ++ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ++ u_char *secret, u_char *token); ++ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, ++ socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, ++ time_t expires, ngx_uint_t is_retry); ++ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, ++ u_char *key, ngx_quic_header_t *pkt); ++ ++#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_transport.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_transport.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,2199 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 ++#define NGX_QUIC_LONG_DCID_OFFSET 6 ++#define NGX_QUIC_SHORT_DCID_OFFSET 1 ++ ++#define NGX_QUIC_STREAM_FRAME_FIN 0x01 ++#define NGX_QUIC_STREAM_FRAME_LEN 0x02 ++#define NGX_QUIC_STREAM_FRAME_OFF 0x04 ++ ++ ++#if (NGX_HAVE_NONALIGNED) ++ ++#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) ++#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) ++ ++#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned ++#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned ++ ++#else ++ ++#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) ++#define ngx_quic_parse_uint32(p) \ ++ ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) ++ ++#define ngx_quic_write_uint16(p, s) \ ++ ((p)[0] = (u_char) ((s) >> 8), \ ++ (p)[1] = (u_char) (s), \ ++ (p) + sizeof(uint16_t)) ++ ++#define ngx_quic_write_uint32(p, s) \ ++ ((p)[0] = (u_char) ((s) >> 24), \ ++ (p)[1] = (u_char) ((s) >> 16), \ ++ (p)[2] = (u_char) ((s) >> 8), \ ++ (p)[3] = (u_char) (s), \ ++ (p) + sizeof(uint32_t)) ++ ++#endif ++ ++#define ngx_quic_write_uint64(p, s) \ ++ ((p)[0] = (u_char) ((s) >> 56), \ ++ (p)[1] = (u_char) ((s) >> 48), \ ++ (p)[2] = (u_char) ((s) >> 40), \ ++ (p)[3] = (u_char) ((s) >> 32), \ ++ (p)[4] = (u_char) ((s) >> 24), \ ++ (p)[5] = (u_char) ((s) >> 16), \ ++ (p)[6] = (u_char) ((s) >> 8), \ ++ (p)[7] = (u_char) (s), \ ++ (p) + sizeof(uint64_t)) ++ ++#define ngx_quic_write_uint24(p, s) \ ++ ((p)[0] = (u_char) ((s) >> 16), \ ++ (p)[1] = (u_char) ((s) >> 8), \ ++ (p)[2] = (u_char) (s), \ ++ (p) + 3) ++ ++#define ngx_quic_write_uint16_aligned(p, s) \ ++ (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) ++ ++#define ngx_quic_write_uint32_aligned(p, s) \ ++ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) ++ ++#define ngx_quic_build_int_set(p, value, len, bits) \ ++ (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6)) ++ ++ ++static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); ++static ngx_uint_t ngx_quic_varint_len(uint64_t value); ++static void ngx_quic_build_int(u_char **pos, uint64_t value); ++ ++static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); ++static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); ++static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, ++ u_char **out); ++static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, ++ u_char *dst); ++ ++static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ++ size_t dcid_len); ++static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); ++static ngx_int_t ngx_quic_supported_version(uint32_t version); ++static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); ++ ++static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp); ++static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp); ++ ++static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ++ ngx_uint_t frame_type); ++static size_t ngx_quic_create_ping(u_char *p); ++static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ++ ngx_chain_t *ranges); ++static size_t ngx_quic_create_reset_stream(u_char *p, ++ ngx_quic_reset_stream_frame_t *rs); ++static size_t ngx_quic_create_stop_sending(u_char *p, ++ ngx_quic_stop_sending_frame_t *ss); ++static size_t ngx_quic_create_crypto(u_char *p, ++ ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); ++static size_t ngx_quic_create_hs_done(u_char *p); ++static size_t ngx_quic_create_new_token(u_char *p, ++ ngx_quic_new_token_frame_t *token, ngx_chain_t *data); ++static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, ++ ngx_chain_t *data); ++static size_t ngx_quic_create_max_streams(u_char *p, ++ ngx_quic_max_streams_frame_t *ms); ++static size_t ngx_quic_create_max_stream_data(u_char *p, ++ ngx_quic_max_stream_data_frame_t *ms); ++static size_t ngx_quic_create_max_data(u_char *p, ++ ngx_quic_max_data_frame_t *md); ++static size_t ngx_quic_create_path_challenge(u_char *p, ++ ngx_quic_path_challenge_frame_t *pc); ++static size_t ngx_quic_create_path_response(u_char *p, ++ ngx_quic_path_challenge_frame_t *pc); ++static size_t ngx_quic_create_new_connection_id(u_char *p, ++ ngx_quic_new_conn_id_frame_t *rcid); ++static size_t ngx_quic_create_retire_connection_id(u_char *p, ++ ngx_quic_retire_cid_frame_t *rcid); ++static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f); ++ ++static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, ++ uint16_t id, ngx_quic_tp_t *dst); ++ ++ ++uint32_t ngx_quic_versions[] = { ++ /* QUICv1 */ ++ 0x00000001, ++}; ++ ++#define NGX_QUIC_NVERSIONS \ ++ (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) ++ ++ ++static ngx_inline u_char * ++ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) ++{ ++ u_char *p; ++ uint64_t value; ++ ngx_uint_t len; ++ ++ if (pos >= end) { ++ return NULL; ++ } ++ ++ p = pos; ++ len = 1 << (*p >> 6); ++ ++ value = *p++ & 0x3f; ++ ++ if ((size_t)(end - p) < (len - 1)) { ++ return NULL; ++ } ++ ++ while (--len) { ++ value = (value << 8) + *p++; ++ } ++ ++ *out = value; ++ ++ return p; ++} ++ ++ ++static ngx_inline u_char * ++ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) ++{ ++ if ((size_t)(end - pos) < 1) { ++ return NULL; ++ } ++ ++ *value = *pos; ++ ++ return pos + 1; ++} ++ ++ ++static ngx_inline u_char * ++ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) ++{ ++ if ((size_t)(end - pos) < sizeof(uint32_t)) { ++ return NULL; ++ } ++ ++ *value = ngx_quic_parse_uint32(pos); ++ ++ return pos + sizeof(uint32_t); ++} ++ ++ ++static ngx_inline u_char * ++ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out) ++{ ++ if ((size_t)(end - pos) < len) { ++ return NULL; ++ } ++ ++ *out = pos; ++ ++ return pos + len; ++} ++ ++ ++static u_char * ++ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) ++{ ++ if ((size_t)(end - pos) < len) { ++ return NULL; ++ } ++ ++ ngx_memcpy(dst, pos, len); ++ ++ return pos + len; ++} ++ ++ ++static ngx_inline ngx_uint_t ++ngx_quic_varint_len(uint64_t value) ++{ ++ if (value < (1 << 6)) { ++ return 1; ++ } ++ ++ if (value < (1 << 14)) { ++ return 2; ++ } ++ ++ if (value < (1 << 30)) { ++ return 4; ++ } ++ ++ return 8; ++} ++ ++ ++static ngx_inline void ++ngx_quic_build_int(u_char **pos, uint64_t value) ++{ ++ u_char *p; ++ ++ p = *pos; ++ ++ if (value < (1 << 6)) { ++ ngx_quic_build_int_set(p, value, 0, 0); ++ ++ } else if (value < (1 << 14)) { ++ ngx_quic_build_int_set(p, value, 1, 1); ++ ngx_quic_build_int_set(p, value, 0, 0); ++ ++ } else if (value < (1 << 30)) { ++ ngx_quic_build_int_set(p, value, 3, 2); ++ ngx_quic_build_int_set(p, value, 2, 0); ++ ngx_quic_build_int_set(p, value, 1, 0); ++ ngx_quic_build_int_set(p, value, 0, 0); ++ ++ } else { ++ ngx_quic_build_int_set(p, value, 7, 3); ++ ngx_quic_build_int_set(p, value, 6, 0); ++ ngx_quic_build_int_set(p, value, 5, 0); ++ ngx_quic_build_int_set(p, value, 4, 0); ++ ngx_quic_build_int_set(p, value, 3, 0); ++ ngx_quic_build_int_set(p, value, 2, 0); ++ ngx_quic_build_int_set(p, value, 1, 0); ++ ngx_quic_build_int_set(p, value, 0, 0); ++ } ++ ++ *pos = p; ++} ++ ++ ++ngx_int_t ++ngx_quic_parse_packet(ngx_quic_header_t *pkt) ++{ ++ if (!ngx_quic_long_pkt(pkt->flags)) { ++ pkt->level = ssl_encryption_application; ++ ++ if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ } ++ ++ if (ngx_quic_parse_long_header(pkt) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (!ngx_quic_supported_version(pkt->version)) { ++ return NGX_ABORT; ++ } ++ ++ if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) ++{ ++ u_char *p, *end; ++ ++ p = pkt->raw->pos; ++ end = pkt->data + pkt->len; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx short flags:%xd", pkt->flags); ++ ++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); ++ return NGX_ERROR; ++ } ++ ++ pkt->dcid.len = dcid_len; ++ ++ p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read dcid"); ++ return NGX_ERROR; ++ } ++ ++ pkt->raw->pos = p; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_long_header(ngx_quic_header_t *pkt) ++{ ++ u_char *p, *end; ++ uint8_t idlen; ++ ++ p = pkt->raw->pos; ++ end = pkt->data + pkt->len; ++ ++ p = ngx_quic_read_uint32(p, end, &pkt->version); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read version"); ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx long flags:%xd version:%xD", ++ pkt->flags, pkt->version); ++ ++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_read_uint8(p, end, &idlen); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read dcid len"); ++ return NGX_ERROR; ++ } ++ ++ if (idlen > NGX_QUIC_CID_LEN_MAX) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet dcid is too long"); ++ return NGX_ERROR; ++ } ++ ++ pkt->dcid.len = idlen; ++ ++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read dcid"); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_read_uint8(p, end, &idlen); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read scid len"); ++ return NGX_ERROR; ++ } ++ ++ if (idlen > NGX_QUIC_CID_LEN_MAX) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet scid is too long"); ++ return NGX_ERROR; ++ } ++ ++ pkt->scid.len = idlen; ++ ++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet is too small to read scid"); ++ return NGX_ERROR; ++ } ++ ++ pkt->raw->pos = p; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_quic_supported_version(uint32_t version) ++{ ++ ngx_uint_t i; ++ ++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { ++ if (ngx_quic_versions[i] == version) { ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) ++{ ++ u_char *p, *end; ++ uint64_t varint; ++ ++ p = pkt->raw->pos; ++ end = pkt->raw->last; ++ ++ pkt->log->action = "parsing quic long header"; ++ ++ if (ngx_quic_pkt_in(pkt->flags)) { ++ ++ if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic UDP datagram is too small for initial packet"); ++ return NGX_DECLINED; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic failed to parse token length"); ++ return NGX_ERROR; ++ } ++ ++ pkt->token.len = varint; ++ ++ p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic packet too small to read token data"); ++ return NGX_ERROR; ++ } ++ ++ pkt->level = ssl_encryption_initial; ++ ++ } else if (ngx_quic_pkt_zrtt(pkt->flags)) { ++ pkt->level = ssl_encryption_early_data; ++ ++ } else if (ngx_quic_pkt_hs(pkt->flags)) { ++ pkt->level = ssl_encryption_handshake; ++ ++ } else { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic bad packet type"); ++ return NGX_DECLINED; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic packet rx %s len:%uL", ++ ngx_quic_level_name(pkt->level), varint); ++ ++ if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", ++ ngx_quic_level_name(pkt->level)); ++ return NGX_ERROR; ++ } ++ ++ pkt->raw->pos = p; ++ pkt->len = p + varint - pkt->data; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, ++ ngx_str_t *dcid) ++{ ++ size_t len, offset; ++ ++ if (n == 0) { ++ goto failed; ++ } ++ ++ if (ngx_quic_long_pkt(*data)) { ++ if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { ++ goto failed; ++ } ++ ++ len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; ++ offset = NGX_QUIC_LONG_DCID_OFFSET; ++ ++ } else { ++ len = NGX_QUIC_SERVER_CID_LEN; ++ offset = NGX_QUIC_SHORT_DCID_OFFSET; ++ } ++ ++ if (n < len + offset) { ++ goto failed; ++ } ++ ++ dcid->len = len; ++ dcid->data = &data[offset]; ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); ++ ++ return NGX_ERROR; ++} ++ ++ ++size_t ++ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) ++{ ++ u_char *p, *start; ++ ngx_uint_t i; ++ ++ p = start = out; ++ ++ *p++ = pkt->flags; ++ ++ /* ++ * The Version field of a Version Negotiation packet ++ * MUST be set to 0x00000000 ++ */ ++ p = ngx_quic_write_uint32(p, 0); ++ ++ *p++ = pkt->dcid.len; ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *p++ = pkt->scid.len; ++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); ++ ++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { ++ p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); ++ } ++ ++ return p - start; ++} ++ ++ ++/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */ ++size_t ++ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) ++{ ++ size_t len; ++ ++ if (ngx_quic_short_pkt(pkt->flags)) { ++ ++ len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN; ++ if (len > pkt_len) { ++ return 0; ++ } ++ ++ return pkt_len - len; ++ } ++ ++ /* flags, version, dcid and scid with lengths and zero-length token */ ++ len = 5 + 2 + pkt->dcid.len + pkt->scid.len ++ + (pkt->level == ssl_encryption_initial ? 1 : 0); ++ ++ if (len > pkt_len) { ++ return 0; ++ } ++ ++ /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */ ++ len += ngx_quic_varint_len(pkt_len - len) ++ + pkt->num_len + EVP_GCM_TLS_TAG_LEN; ++ ++ if (len > pkt_len) { ++ return 0; ++ } ++ ++ return pkt_len - len; ++} ++ ++ ++size_t ++ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) ++{ ++ return ngx_quic_short_pkt(pkt->flags) ++ ? ngx_quic_create_short_header(pkt, out, pnp) ++ : ngx_quic_create_long_header(pkt, out, pnp); ++} ++ ++ ++static size_t ++ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp) ++{ ++ size_t rem_len; ++ u_char *p, *start; ++ ++ rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN; ++ ++ if (out == NULL) { ++ return 5 + 2 + pkt->dcid.len + pkt->scid.len ++ + ngx_quic_varint_len(rem_len) + pkt->num_len ++ + (pkt->level == ssl_encryption_initial ? 1 : 0); ++ } ++ ++ p = start = out; ++ ++ *p++ = pkt->flags; ++ ++ p = ngx_quic_write_uint32(p, pkt->version); ++ ++ *p++ = pkt->dcid.len; ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *p++ = pkt->scid.len; ++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); ++ ++ if (pkt->level == ssl_encryption_initial) { ++ ngx_quic_build_int(&p, 0); ++ } ++ ++ ngx_quic_build_int(&p, rem_len); ++ ++ *pnp = p; ++ ++ switch (pkt->num_len) { ++ case 1: ++ *p++ = pkt->trunc; ++ break; ++ case 2: ++ p = ngx_quic_write_uint16(p, pkt->trunc); ++ break; ++ case 3: ++ p = ngx_quic_write_uint24(p, pkt->trunc); ++ break; ++ case 4: ++ p = ngx_quic_write_uint32(p, pkt->trunc); ++ break; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp) ++{ ++ u_char *p, *start; ++ ++ if (out == NULL) { ++ return 1 + pkt->dcid.len + pkt->num_len; ++ } ++ ++ p = start = out; ++ ++ *p++ = pkt->flags; ++ ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *pnp = p; ++ ++ switch (pkt->num_len) { ++ case 1: ++ *p++ = pkt->trunc; ++ break; ++ case 2: ++ p = ngx_quic_write_uint16(p, pkt->trunc); ++ break; ++ case 3: ++ p = ngx_quic_write_uint24(p, pkt->trunc); ++ break; ++ case 4: ++ p = ngx_quic_write_uint32(p, pkt->trunc); ++ break; ++ } ++ ++ return p - start; ++} ++ ++ ++size_t ++ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, ++ u_char **start) ++{ ++ u_char *p; ++ ++ p = out; ++ ++ *p++ = pkt->odcid.len; ++ p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); ++ ++ *start = p; ++ ++ *p++ = 0xff; ++ ++ p = ngx_quic_write_uint32(p, pkt->version); ++ ++ *p++ = pkt->dcid.len; ++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); ++ ++ *p++ = pkt->scid.len; ++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); ++ ++ p = ngx_cpymem(p, pkt->token.data, pkt->token.len); ++ ++ return p - out; ++} ++ ++ ++ssize_t ++ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ++ ngx_quic_frame_t *f) ++{ ++ u_char *p; ++ uint64_t varint; ++ ngx_buf_t *b; ++ ngx_uint_t i; ++ ++ b = f->data->buf; ++ ++ p = start; ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic failed to obtain quic frame type"); ++ return NGX_ERROR; ++ } ++ ++ if (varint > NGX_QUIC_FT_LAST) { ++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic unknown frame type 0x%xL", varint); ++ return NGX_ERROR; ++ } ++ ++ f->type = varint; ++ ++ if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { ++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; ++ return NGX_ERROR; ++ } ++ ++ switch (f->type) { ++ ++ case NGX_QUIC_FT_CRYPTO: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.crypto.length); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ b->last = p; ++ ++ break; ++ ++ case NGX_QUIC_FT_PADDING: ++ ++ while (p < end && *p == NGX_QUIC_FT_PADDING) { ++ p++; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_ACK: ++ case NGX_QUIC_FT_ACK_ECN: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ack.largest); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ack.delay); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ack.range_count); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ack.first_range); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ b->pos = p; ++ ++ /* process all ranges to get bounds, values are ignored */ ++ for (i = 0; i < f->u.ack.range_count; i++) { ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ goto error; ++ } ++ } ++ ++ b->last = p; ++ ++ f->u.ack.ranges_length = b->last - b->pos; ++ ++ if (f->type == NGX_QUIC_FT_ACK_ECN) { ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ack.ect0); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ack.ect1); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ack.ce); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, ++ "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", ++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PING: ++ break; ++ ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.ncid.retire > f->u.ncid.seqnum) { ++ goto error; ++ } ++ ++ p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { ++ goto error; ++ } ++ ++ p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.close.error_code); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { ++ p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); ++ if (p == NULL) { ++ goto error; ++ } ++ } ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ f->u.close.reason.len = varint; ++ ++ p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, ++ &f->u.close.reason.data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM: ++ case NGX_QUIC_FT_STREAM1: ++ case NGX_QUIC_FT_STREAM2: ++ case NGX_QUIC_FT_STREAM3: ++ case NGX_QUIC_FT_STREAM4: ++ case NGX_QUIC_FT_STREAM5: ++ case NGX_QUIC_FT_STREAM6: ++ case NGX_QUIC_FT_STREAM7: ++ ++ f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0; ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->type & NGX_QUIC_STREAM_FRAME_OFF) { ++ f->u.stream.off = 1; ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream.offset); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ } else { ++ f->u.stream.off = 0; ++ f->u.stream.offset = 0; ++ } ++ ++ if (f->type & NGX_QUIC_STREAM_FRAME_LEN) { ++ f->u.stream.len = 1; ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream.length); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ } else { ++ f->u.stream.len = 0; ++ f->u.stream.length = end - p; /* up to packet end */ ++ } ++ ++ p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ b->last = p; ++ ++ f->type = NGX_QUIC_FT_STREAM; ++ break; ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STOP_SENDING: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAMS_BLOCKED: ++ case NGX_QUIC_FT_STREAMS_BLOCKED2: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.streams_blocked.limit > 0x1000000000000000) { ++ goto error; ++ } ++ ++ f->u.streams_blocked.bidi = ++ (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ case NGX_QUIC_FT_MAX_STREAMS2: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ if (f->u.max_streams.limit > 0x1000000000000000) { ++ goto error; ++ } ++ ++ f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; ++ ++ break; ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_DATA_BLOCKED: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED: ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ ++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ ++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); ++ if (p == NULL) { ++ goto error; ++ } ++ ++ break; ++ ++ default: ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic unknown frame type 0x%xi", f->type); ++ return NGX_ERROR; ++ } ++ ++ f->level = pkt->level; ++ ++ return p - start; ++ ++error: ++ ++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ++ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic failed to parse frame type:0x%xi", f->type); ++ ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) ++{ ++ uint8_t ptype; ++ ++ /* ++ * RFC 9000, 12.4. Frames and Frame Types: Table 3 ++ * ++ * Frame permissions per packet: 4 bits: IH01 ++ */ ++ static uint8_t ngx_quic_frame_masks[] = { ++ /* PADDING */ 0xF, ++ /* PING */ 0xF, ++ /* ACK */ 0xD, ++ /* ACK_ECN */ 0xD, ++ /* RESET_STREAM */ 0x3, ++ /* STOP_SENDING */ 0x3, ++ /* CRYPTO */ 0xD, ++ /* NEW_TOKEN */ 0x0, /* only sent by server */ ++ /* STREAM */ 0x3, ++ /* STREAM1 */ 0x3, ++ /* STREAM2 */ 0x3, ++ /* STREAM3 */ 0x3, ++ /* STREAM4 */ 0x3, ++ /* STREAM5 */ 0x3, ++ /* STREAM6 */ 0x3, ++ /* STREAM7 */ 0x3, ++ /* MAX_DATA */ 0x3, ++ /* MAX_STREAM_DATA */ 0x3, ++ /* MAX_STREAMS */ 0x3, ++ /* MAX_STREAMS2 */ 0x3, ++ /* DATA_BLOCKED */ 0x3, ++ /* STREAM_DATA_BLOCKED */ 0x3, ++ /* STREAMS_BLOCKED */ 0x3, ++ /* STREAMS_BLOCKED2 */ 0x3, ++ /* NEW_CONNECTION_ID */ 0x3, ++ /* RETIRE_CONNECTION_ID */ 0x3, ++ /* PATH_CHALLENGE */ 0x3, ++ /* PATH_RESPONSE */ 0x1, ++ /* CONNECTION_CLOSE */ 0xF, ++ /* CONNECTION_CLOSE2 */ 0x3, ++ /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ ++ }; ++ ++ if (ngx_quic_long_pkt(pkt->flags)) { ++ ++ if (ngx_quic_pkt_in(pkt->flags)) { ++ ptype = 8; /* initial */ ++ ++ } else if (ngx_quic_pkt_hs(pkt->flags)) { ++ ptype = 4; /* handshake */ ++ ++ } else { ++ ptype = 2; /* zero-rtt */ ++ } ++ ++ } else { ++ ptype = 1; /* application data */ ++ } ++ ++ if (ptype & ngx_quic_frame_masks[frame_type]) { ++ return NGX_OK; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, ++ "quic frame type 0x%xi is not " ++ "allowed in packet with flags 0x%xd", ++ frame_type, pkt->flags); ++ ++ return NGX_DECLINED; ++} ++ ++ ++ssize_t ++ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, ++ uint64_t *gap, uint64_t *range) ++{ ++ u_char *p; ++ ++ p = start; ++ ++ p = ngx_quic_parse_int(p, end, gap); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse ack frame gap"); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_parse_int(p, end, range); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse ack frame range"); ++ return NGX_ERROR; ++ } ++ ++ return p - start; ++} ++ ++ ++size_t ++ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(gap); ++ len += ngx_quic_varint_len(range); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, gap); ++ ngx_quic_build_int(&p, range); ++ ++ return p - start; ++} ++ ++ ++ssize_t ++ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) ++{ ++ /* ++ * RFC 9002, 2. Conventions and Definitions ++ * ++ * Ack-eliciting frames: All frames other than ACK, PADDING, and ++ * CONNECTION_CLOSE are considered ack-eliciting. ++ */ ++ f->need_ack = 1; ++ ++ switch (f->type) { ++ case NGX_QUIC_FT_PING: ++ return ngx_quic_create_ping(p); ++ ++ case NGX_QUIC_FT_ACK: ++ f->need_ack = 0; ++ return ngx_quic_create_ack(p, &f->u.ack, f->data); ++ ++ case NGX_QUIC_FT_RESET_STREAM: ++ return ngx_quic_create_reset_stream(p, &f->u.reset_stream); ++ ++ case NGX_QUIC_FT_STOP_SENDING: ++ return ngx_quic_create_stop_sending(p, &f->u.stop_sending); ++ ++ case NGX_QUIC_FT_CRYPTO: ++ return ngx_quic_create_crypto(p, &f->u.crypto, f->data); ++ ++ case NGX_QUIC_FT_HANDSHAKE_DONE: ++ return ngx_quic_create_hs_done(p); ++ ++ case NGX_QUIC_FT_NEW_TOKEN: ++ return ngx_quic_create_new_token(p, &f->u.token, f->data); ++ ++ case NGX_QUIC_FT_STREAM: ++ return ngx_quic_create_stream(p, &f->u.stream, f->data); ++ ++ case NGX_QUIC_FT_CONNECTION_CLOSE: ++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP: ++ f->need_ack = 0; ++ return ngx_quic_create_close(p, f); ++ ++ case NGX_QUIC_FT_MAX_STREAMS: ++ return ngx_quic_create_max_streams(p, &f->u.max_streams); ++ ++ case NGX_QUIC_FT_MAX_STREAM_DATA: ++ return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); ++ ++ case NGX_QUIC_FT_MAX_DATA: ++ return ngx_quic_create_max_data(p, &f->u.max_data); ++ ++ case NGX_QUIC_FT_PATH_CHALLENGE: ++ return ngx_quic_create_path_challenge(p, &f->u.path_challenge); ++ ++ case NGX_QUIC_FT_PATH_RESPONSE: ++ return ngx_quic_create_path_response(p, &f->u.path_response); ++ ++ case NGX_QUIC_FT_NEW_CONNECTION_ID: ++ return ngx_quic_create_new_connection_id(p, &f->u.ncid); ++ ++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID: ++ return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); ++ ++ default: ++ /* BUG: unsupported frame type generated */ ++ return NGX_ERROR; ++ } ++} ++ ++ ++static size_t ++ngx_quic_create_ping(u_char *p) ++{ ++ u_char *start; ++ ++ if (p == NULL) { ++ return ngx_quic_varint_len(NGX_QUIC_FT_PING); ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_PING); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) ++{ ++ size_t len; ++ u_char *start; ++ ngx_buf_t *b; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); ++ len += ngx_quic_varint_len(ack->largest); ++ len += ngx_quic_varint_len(ack->delay); ++ len += ngx_quic_varint_len(ack->range_count); ++ len += ngx_quic_varint_len(ack->first_range); ++ len += ack->ranges_length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); ++ ngx_quic_build_int(&p, ack->largest); ++ ngx_quic_build_int(&p, ack->delay); ++ ngx_quic_build_int(&p, ack->range_count); ++ ngx_quic_build_int(&p, ack->first_range); ++ ++ while (ranges) { ++ b = ranges->buf; ++ p = ngx_cpymem(p, b->pos, b->last - b->pos); ++ ranges = ranges->next; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM); ++ len += ngx_quic_varint_len(rs->id); ++ len += ngx_quic_varint_len(rs->error_code); ++ len += ngx_quic_varint_len(rs->final_size); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM); ++ ngx_quic_build_int(&p, rs->id); ++ ngx_quic_build_int(&p, rs->error_code); ++ ngx_quic_build_int(&p, rs->final_size); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); ++ len += ngx_quic_varint_len(ss->id); ++ len += ngx_quic_varint_len(ss->error_code); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); ++ ngx_quic_build_int(&p, ss->id); ++ ngx_quic_build_int(&p, ss->error_code); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, ++ ngx_chain_t *data) ++{ ++ size_t len; ++ u_char *start; ++ ngx_buf_t *b; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); ++ len += ngx_quic_varint_len(crypto->offset); ++ len += ngx_quic_varint_len(crypto->length); ++ len += crypto->length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); ++ ngx_quic_build_int(&p, crypto->offset); ++ ngx_quic_build_int(&p, crypto->length); ++ ++ while (data) { ++ b = data->buf; ++ p = ngx_cpymem(p, b->pos, b->last - b->pos); ++ data = data->next; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_hs_done(u_char *p) ++{ ++ u_char *start; ++ ++ if (p == NULL) { ++ return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token, ++ ngx_chain_t *data) ++{ ++ size_t len; ++ u_char *start; ++ ngx_buf_t *b; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); ++ len += ngx_quic_varint_len(token->length); ++ len += token->length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); ++ ngx_quic_build_int(&p, token->length); ++ ++ while (data) { ++ b = data->buf; ++ p = ngx_cpymem(p, b->pos, b->last - b->pos); ++ data = data->next; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, ++ ngx_chain_t *data) ++{ ++ size_t len; ++ u_char *start, type; ++ ngx_buf_t *b; ++ ++ type = NGX_QUIC_FT_STREAM; ++ ++ if (sf->off) { ++ type |= NGX_QUIC_STREAM_FRAME_OFF; ++ } ++ ++ if (sf->len) { ++ type |= NGX_QUIC_STREAM_FRAME_LEN; ++ } ++ ++ if (sf->fin) { ++ type |= NGX_QUIC_STREAM_FRAME_FIN; ++ } ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(type); ++ len += ngx_quic_varint_len(sf->stream_id); ++ ++ if (sf->off) { ++ len += ngx_quic_varint_len(sf->offset); ++ } ++ ++ if (sf->len) { ++ len += ngx_quic_varint_len(sf->length); ++ } ++ ++ len += sf->length; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, type); ++ ngx_quic_build_int(&p, sf->stream_id); ++ ++ if (sf->off) { ++ ngx_quic_build_int(&p, sf->offset); ++ } ++ ++ if (sf->len) { ++ ngx_quic_build_int(&p, sf->length); ++ } ++ ++ while (data) { ++ b = data->buf; ++ p = ngx_cpymem(p, b->pos, b->last - b->pos); ++ data = data->next; ++ } ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) ++{ ++ size_t len; ++ u_char *start; ++ ngx_uint_t type; ++ ++ type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(type); ++ len += ngx_quic_varint_len(ms->limit); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, type); ++ ngx_quic_build_int(&p, ms->limit); ++ ++ return p - start; ++} ++ ++ ++static ngx_int_t ++ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, ++ ngx_quic_tp_t *dst) ++{ ++ uint64_t varint; ++ ngx_str_t str; ++ ++ varint = 0; ++ ngx_str_null(&str); ++ ++ switch (id) { ++ ++ case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: ++ /* zero-length option */ ++ if (end - p != 0) { ++ return NGX_ERROR; ++ } ++ dst->disable_active_migration = 1; ++ return NGX_OK; ++ ++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: ++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: ++ case NGX_QUIC_TP_INITIAL_MAX_DATA: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: ++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT: ++ case NGX_QUIC_TP_MAX_ACK_DELAY: ++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: ++ ++ p = ngx_quic_parse_int(p, end, &varint); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_SCID: ++ ++ str.len = end - p; ++ str.data = p; ++ break; ++ ++ default: ++ return NGX_DECLINED; ++ } ++ ++ switch (id) { ++ ++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: ++ dst->max_idle_timeout = varint; ++ break; ++ ++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: ++ dst->max_udp_payload_size = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_DATA: ++ dst->initial_max_data = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: ++ dst->initial_max_stream_data_bidi_local = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: ++ dst->initial_max_stream_data_bidi_remote = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: ++ dst->initial_max_stream_data_uni = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: ++ dst->initial_max_streams_bidi = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: ++ dst->initial_max_streams_uni = varint; ++ break; ++ ++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT: ++ dst->ack_delay_exponent = varint; ++ break; ++ ++ case NGX_QUIC_TP_MAX_ACK_DELAY: ++ dst->max_ack_delay = varint; ++ break; ++ ++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: ++ dst->active_connection_id_limit = varint; ++ break; ++ ++ case NGX_QUIC_TP_INITIAL_SCID: ++ dst->initial_scid = str; ++ break; ++ ++ default: ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ++ ngx_log_t *log) ++{ ++ uint64_t id, len; ++ ngx_int_t rc; ++ ++ while (p < end) { ++ p = ngx_quic_parse_int(p, end, &id); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse transport param id"); ++ return NGX_ERROR; ++ } ++ ++ switch (id) { ++ case NGX_QUIC_TP_ORIGINAL_DCID: ++ case NGX_QUIC_TP_PREFERRED_ADDRESS: ++ case NGX_QUIC_TP_RETRY_SCID: ++ case NGX_QUIC_TP_SR_TOKEN: ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic client sent forbidden transport param" ++ " id:0x%xL", id); ++ return NGX_ERROR; ++ } ++ ++ p = ngx_quic_parse_int(p, end, &len); ++ if (p == NULL) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse" ++ " transport param id:0x%xL length", id); ++ return NGX_ERROR; ++ } ++ ++ rc = ngx_quic_parse_transport_param(p, p + len, id, tp); ++ ++ if (rc == NGX_ERROR) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic failed to parse" ++ " transport param id:0x%xL data", id); ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_DECLINED) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic %s transport param id:0x%xL, skipped", ++ (id % 31 == 27) ? "reserved" : "unknown", id); ++ } ++ ++ p += len; ++ } ++ ++ if (p != end) { ++ ngx_log_error(NGX_LOG_INFO, log, 0, ++ "quic trailing garbage in" ++ " transport parameters: bytes:%ui", ++ end - p); ++ return NGX_ERROR; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic transport parameters parsed ok"); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp disable active migration: %ui", ++ tp->disable_active_migration); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", ++ tp->max_idle_timeout); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_udp_payload_size:%ui", ++ tp->max_udp_payload_size); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", ++ tp->initial_max_data); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_stream_data_bidi_local:%ui", ++ tp->initial_max_stream_data_bidi_local); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_stream_data_bidi_remote:%ui", ++ tp->initial_max_stream_data_bidi_remote); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp max_stream_data_uni:%ui", ++ tp->initial_max_stream_data_uni); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp initial_max_streams_bidi:%ui", ++ tp->initial_max_streams_bidi); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp initial_max_streams_uni:%ui", ++ tp->initial_max_streams_uni); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp ack_delay_exponent:%ui", ++ tp->ack_delay_exponent); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", ++ tp->max_ack_delay); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp active_connection_id_limit:%ui", ++ tp->active_connection_id_limit); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, ++ "quic tp initial source_connection_id len:%uz %xV", ++ tp->initial_scid.len, &tp->initial_scid); ++ ++ return NGX_OK; ++} ++ ++ ++static size_t ++ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); ++ len += ngx_quic_varint_len(ms->id); ++ len += ngx_quic_varint_len(ms->limit); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); ++ ngx_quic_build_int(&p, ms->id); ++ ngx_quic_build_int(&p, ms->limit); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); ++ len += ngx_quic_varint_len(md->max_data); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); ++ ngx_quic_build_int(&p, md->max_data); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE); ++ len += sizeof(pc->data); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE); ++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); ++ len += sizeof(pc->data); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); ++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); ++ len += ngx_quic_varint_len(ncid->seqnum); ++ len += ngx_quic_varint_len(ncid->retire); ++ len++; ++ len += ncid->len; ++ len += NGX_QUIC_SR_TOKEN_LEN; ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); ++ ngx_quic_build_int(&p, ncid->seqnum); ++ ngx_quic_build_int(&p, ncid->retire); ++ *p++ = ncid->len; ++ p = ngx_cpymem(p, ncid->cid, ncid->len); ++ p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); ++ ++ return p - start; ++} ++ ++ ++static size_t ++ngx_quic_create_retire_connection_id(u_char *p, ++ ngx_quic_retire_cid_frame_t *rcid) ++{ ++ size_t len; ++ u_char *start; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); ++ len += ngx_quic_varint_len(rcid->sequence_number); ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); ++ ngx_quic_build_int(&p, rcid->sequence_number); ++ ++ return p - start; ++} ++ ++ ++ngx_int_t ++ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) ++{ ++ ngx_uint_t nstreams; ++ ++ ngx_memzero(tp, sizeof(ngx_quic_tp_t)); ++ ++ /* ++ * set by ngx_memzero(): ++ * ++ * tp->disable_active_migration = 0; ++ * tp->original_dcid = { 0, NULL }; ++ * tp->initial_scid = { 0, NULL }; ++ * tp->retry_scid = { 0, NULL }; ++ * tp->sr_token = { 0 } ++ * tp->sr_enabled = 0 ++ * tp->preferred_address = NULL ++ */ ++ ++ tp->max_idle_timeout = qcf->timeout; ++ ++ tp->max_udp_payload_size = qcf->mtu; ++ ++ nstreams = qcf->max_concurrent_streams_bidi ++ + qcf->max_concurrent_streams_uni; ++ ++ tp->initial_max_data = nstreams * qcf->stream_buffer_size; ++ tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size; ++ tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size; ++ tp->initial_max_stream_data_uni = qcf->stream_buffer_size; ++ ++ tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi; ++ tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni; ++ ++ tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; ++ tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ++ ++ tp->active_connection_id_limit = qcf->active_connection_id_limit; ++ tp->disable_active_migration = qcf->disable_active_migration; ++ ++ return NGX_OK; ++} ++ ++ ++ssize_t ++ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, ++ size_t *clen) ++{ ++ u_char *p; ++ size_t len; ++ ++#define ngx_quic_tp_len(id, value) \ ++ ngx_quic_varint_len(id) \ ++ + ngx_quic_varint_len(value) \ ++ + ngx_quic_varint_len(ngx_quic_varint_len(value)) ++ ++#define ngx_quic_tp_vint(id, value) \ ++ do { \ ++ ngx_quic_build_int(&p, id); \ ++ ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \ ++ ngx_quic_build_int(&p, value); \ ++ } while (0) ++ ++#define ngx_quic_tp_strlen(id, value) \ ++ ngx_quic_varint_len(id) \ ++ + ngx_quic_varint_len(value.len) \ ++ + value.len ++ ++#define ngx_quic_tp_str(id, value) \ ++ do { \ ++ ngx_quic_build_int(&p, id); \ ++ ngx_quic_build_int(&p, value.len); \ ++ p = ngx_cpymem(p, value.data, value.len); \ ++ } while (0) ++ ++ len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, ++ tp->initial_max_streams_uni); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, ++ tp->initial_max_streams_bidi); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, ++ tp->initial_max_stream_data_bidi_local); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, ++ tp->initial_max_stream_data_bidi_remote); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, ++ tp->initial_max_stream_data_uni); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, ++ tp->max_idle_timeout); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, ++ tp->max_udp_payload_size); ++ ++ if (tp->disable_active_migration) { ++ len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); ++ len += ngx_quic_varint_len(0); ++ } ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, ++ tp->active_connection_id_limit); ++ ++ /* transport parameters listed above will be saved in 0-RTT context */ ++ if (clen) { ++ *clen = len; ++ } ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY, ++ tp->max_ack_delay); ++ ++ len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT, ++ tp->ack_delay_exponent); ++ ++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); ++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); ++ ++ if (tp->retry_scid.len) { ++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); ++ } ++ ++ len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN); ++ len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN); ++ len += NGX_QUIC_SR_TOKEN_LEN; ++ ++ if (pos == NULL) { ++ return len; ++ } ++ ++ p = pos; ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, ++ tp->initial_max_data); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, ++ tp->initial_max_streams_uni); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, ++ tp->initial_max_streams_bidi); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, ++ tp->initial_max_stream_data_bidi_local); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, ++ tp->initial_max_stream_data_bidi_remote); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, ++ tp->initial_max_stream_data_uni); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, ++ tp->max_idle_timeout); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, ++ tp->max_udp_payload_size); ++ ++ if (tp->disable_active_migration) { ++ ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); ++ ngx_quic_build_int(&p, 0); ++ } ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, ++ tp->active_connection_id_limit); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY, ++ tp->max_ack_delay); ++ ++ ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT, ++ tp->ack_delay_exponent); ++ ++ ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); ++ ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); ++ ++ if (tp->retry_scid.len) { ++ ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); ++ } ++ ++ ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN); ++ ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN); ++ p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN); ++ ++ return p - pos; ++} ++ ++ ++static size_t ++ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f) ++{ ++ size_t len; ++ u_char *start; ++ ngx_quic_close_frame_t *cl; ++ ++ cl = &f->u.close; ++ ++ if (p == NULL) { ++ len = ngx_quic_varint_len(f->type); ++ len += ngx_quic_varint_len(cl->error_code); ++ ++ if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { ++ len += ngx_quic_varint_len(cl->frame_type); ++ } ++ ++ len += ngx_quic_varint_len(cl->reason.len); ++ len += cl->reason.len; ++ ++ return len; ++ } ++ ++ start = p; ++ ++ ngx_quic_build_int(&p, f->type); ++ ngx_quic_build_int(&p, cl->error_code); ++ ++ if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { ++ ngx_quic_build_int(&p, cl->frame_type); ++ } ++ ++ ngx_quic_build_int(&p, cl->reason.len); ++ p = ngx_cpymem(p, cl->reason.data, cl->reason.len); ++ ++ return p - start; ++} ++ ++ ++void ++ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key) ++{ ++ (void) ngx_quic_write_uint64(dcid, key); ++} +diff -r ac779115ed6e src/event/quic/ngx_event_quic_transport.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_transport.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,397 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ ++#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ ++ ++ ++#include ++#include ++ ++ ++/* ++ * RFC 9000, 17.2. Long Header Packets ++ * 17.3. Short Header Packets ++ * ++ * QUIC flags in first byte ++ */ ++#define NGX_QUIC_PKT_LONG 0x80 /* header form */ ++#define NGX_QUIC_PKT_FIXED_BIT 0x40 ++#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ ++#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ ++ ++#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) ++#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) ++ ++/* Long packet types */ ++#define NGX_QUIC_PKT_INITIAL 0x00 ++#define NGX_QUIC_PKT_ZRTT 0x10 ++#define NGX_QUIC_PKT_HANDSHAKE 0x20 ++#define NGX_QUIC_PKT_RETRY 0x30 ++ ++#define ngx_quic_pkt_in(flags) \ ++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL) ++#define ngx_quic_pkt_zrtt(flags) \ ++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT) ++#define ngx_quic_pkt_hs(flags) \ ++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE) ++#define ngx_quic_pkt_retry(flags) \ ++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) ++ ++#define ngx_quic_pkt_rb_mask(flags) \ ++ (ngx_quic_long_pkt(flags) ? 0x0C : 0x18) ++#define ngx_quic_pkt_hp_mask(flags) \ ++ (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F) ++ ++#define ngx_quic_level_name(lvl) \ ++ (lvl == ssl_encryption_application) ? "app" \ ++ : (lvl == ssl_encryption_initial) ? "init" \ ++ : (lvl == ssl_encryption_handshake) ? "hs" : "early" ++ ++#define NGX_QUIC_MAX_CID_LEN 20 ++#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN ++ ++/* 12.4. Frames and Frame Types */ ++#define NGX_QUIC_FT_PADDING 0x00 ++#define NGX_QUIC_FT_PING 0x01 ++#define NGX_QUIC_FT_ACK 0x02 ++#define NGX_QUIC_FT_ACK_ECN 0x03 ++#define NGX_QUIC_FT_RESET_STREAM 0x04 ++#define NGX_QUIC_FT_STOP_SENDING 0x05 ++#define NGX_QUIC_FT_CRYPTO 0x06 ++#define NGX_QUIC_FT_NEW_TOKEN 0x07 ++#define NGX_QUIC_FT_STREAM 0x08 ++#define NGX_QUIC_FT_STREAM1 0x09 ++#define NGX_QUIC_FT_STREAM2 0x0A ++#define NGX_QUIC_FT_STREAM3 0x0B ++#define NGX_QUIC_FT_STREAM4 0x0C ++#define NGX_QUIC_FT_STREAM5 0x0D ++#define NGX_QUIC_FT_STREAM6 0x0E ++#define NGX_QUIC_FT_STREAM7 0x0F ++#define NGX_QUIC_FT_MAX_DATA 0x10 ++#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 ++#define NGX_QUIC_FT_MAX_STREAMS 0x12 ++#define NGX_QUIC_FT_MAX_STREAMS2 0x13 ++#define NGX_QUIC_FT_DATA_BLOCKED 0x14 ++#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 ++#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 ++#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 ++#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 ++#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 ++#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A ++#define NGX_QUIC_FT_PATH_RESPONSE 0x1B ++#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C ++#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D ++#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E ++ ++#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE ++ ++/* 22.5. QUIC Transport Error Codes Registry */ ++#define NGX_QUIC_ERR_NO_ERROR 0x00 ++#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 ++#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 ++#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 ++#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 ++#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 ++#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 ++#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 ++#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 ++#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 ++#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A ++#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B ++#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C ++#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D ++#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E ++#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F ++#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10 ++ ++#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 ++ ++#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) ++ ++ ++/* 22.3. QUIC Transport Parameters Registry */ ++#define NGX_QUIC_TP_ORIGINAL_DCID 0x00 ++#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 ++#define NGX_QUIC_TP_SR_TOKEN 0x02 ++#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 ++#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 ++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 ++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 ++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 ++#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 ++#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 ++#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A ++#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B ++#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C ++#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D ++#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E ++#define NGX_QUIC_TP_INITIAL_SCID 0x0F ++#define NGX_QUIC_TP_RETRY_SCID 0x10 ++ ++#define NGX_QUIC_CID_LEN_MIN 8 ++#define NGX_QUIC_CID_LEN_MAX 20 ++ ++#define NGX_QUIC_MAX_RANGES 10 ++ ++ ++typedef struct { ++ uint64_t gap; ++ uint64_t range; ++} ngx_quic_ack_range_t; ++ ++ ++typedef struct { ++ uint64_t largest; ++ uint64_t delay; ++ uint64_t range_count; ++ uint64_t first_range; ++ uint64_t ect0; ++ uint64_t ect1; ++ uint64_t ce; ++ uint64_t ranges_length; ++} ngx_quic_ack_frame_t; ++ ++ ++typedef struct { ++ uint64_t seqnum; ++ uint64_t retire; ++ uint8_t len; ++ u_char cid[NGX_QUIC_CID_LEN_MAX]; ++ u_char srt[NGX_QUIC_SR_TOKEN_LEN]; ++} ngx_quic_new_conn_id_frame_t; ++ ++ ++typedef struct { ++ uint64_t length; ++} ngx_quic_new_token_frame_t; ++ ++/* ++ * common layout for CRYPTO and STREAM frames; ++ * conceptually, CRYPTO frame is also a stream ++ * frame lacking some properties ++ */ ++typedef struct { ++ uint64_t offset; ++ uint64_t length; ++} ngx_quic_ordered_frame_t; ++ ++typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; ++ ++ ++typedef struct { ++ /* initial fields same as in ngx_quic_ordered_frame_t */ ++ uint64_t offset; ++ uint64_t length; ++ ++ uint64_t stream_id; ++ unsigned off:1; ++ unsigned len:1; ++ unsigned fin:1; ++} ngx_quic_stream_frame_t; ++ ++ ++typedef struct { ++ uint64_t max_data; ++} ngx_quic_max_data_frame_t; ++ ++ ++typedef struct { ++ uint64_t error_code; ++ uint64_t frame_type; ++ ngx_str_t reason; ++} ngx_quic_close_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t error_code; ++ uint64_t final_size; ++} ngx_quic_reset_stream_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t error_code; ++} ngx_quic_stop_sending_frame_t; ++ ++ ++typedef struct { ++ uint64_t limit; ++ ngx_uint_t bidi; /* unsigned: bidi:1 */ ++} ngx_quic_streams_blocked_frame_t; ++ ++ ++typedef struct { ++ uint64_t limit; ++ ngx_uint_t bidi; /* unsigned: bidi:1 */ ++} ngx_quic_max_streams_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t limit; ++} ngx_quic_max_stream_data_frame_t; ++ ++ ++typedef struct { ++ uint64_t limit; ++} ngx_quic_data_blocked_frame_t; ++ ++ ++typedef struct { ++ uint64_t id; ++ uint64_t limit; ++} ngx_quic_stream_data_blocked_frame_t; ++ ++ ++typedef struct { ++ uint64_t sequence_number; ++} ngx_quic_retire_cid_frame_t; ++ ++ ++typedef struct { ++ u_char data[8]; ++} ngx_quic_path_challenge_frame_t; ++ ++ ++typedef struct ngx_quic_frame_s ngx_quic_frame_t; ++ ++struct ngx_quic_frame_s { ++ ngx_uint_t type; ++ enum ssl_encryption_level_t level; ++ ngx_queue_t queue; ++ uint64_t pnum; ++ size_t plen; ++ ngx_msec_t first; ++ ngx_msec_t last; ++ ssize_t len; ++ unsigned need_ack:1; ++ unsigned pkt_need_ack:1; ++ unsigned flush:1; ++ ++ ngx_chain_t *data; ++ union { ++ ngx_quic_ack_frame_t ack; ++ ngx_quic_crypto_frame_t crypto; ++ ngx_quic_ordered_frame_t ord; ++ ngx_quic_new_conn_id_frame_t ncid; ++ ngx_quic_new_token_frame_t token; ++ ngx_quic_stream_frame_t stream; ++ ngx_quic_max_data_frame_t max_data; ++ ngx_quic_close_frame_t close; ++ ngx_quic_reset_stream_frame_t reset_stream; ++ ngx_quic_stop_sending_frame_t stop_sending; ++ ngx_quic_streams_blocked_frame_t streams_blocked; ++ ngx_quic_max_streams_frame_t max_streams; ++ ngx_quic_max_stream_data_frame_t max_stream_data; ++ ngx_quic_data_blocked_frame_t data_blocked; ++ ngx_quic_stream_data_blocked_frame_t stream_data_blocked; ++ ngx_quic_retire_cid_frame_t retire_cid; ++ ngx_quic_path_challenge_frame_t path_challenge; ++ ngx_quic_path_challenge_frame_t path_response; ++ } u; ++}; ++ ++ ++typedef struct { ++ ngx_log_t *log; ++ ngx_quic_path_t *path; ++ ++ ngx_quic_keys_t *keys; ++ ++ ngx_msec_t received; ++ uint64_t number; ++ uint8_t num_len; ++ uint32_t trunc; ++ uint8_t flags; ++ uint32_t version; ++ ngx_str_t token; ++ enum ssl_encryption_level_t level; ++ ngx_uint_t error; ++ ++ /* filled in by parser */ ++ ngx_buf_t *raw; /* udp datagram */ ++ ++ u_char *data; /* quic packet */ ++ size_t len; ++ ++ /* cleartext fields */ ++ ngx_str_t odcid; /* retry packet tag */ ++ u_char odcid_buf[NGX_QUIC_MAX_CID_LEN]; ++ ngx_str_t dcid; ++ ngx_str_t scid; ++ uint64_t pn; ++ u_char *plaintext; ++ ngx_str_t payload; /* decrypted data */ ++ ++ unsigned need_ack:1; ++ unsigned key_phase:1; ++ unsigned key_update:1; ++ unsigned parsed:1; ++ unsigned decrypted:1; ++ unsigned validated:1; ++ unsigned retried:1; ++ unsigned first:1; ++ unsigned rebound:1; ++} ngx_quic_header_t; ++ ++ ++typedef struct { ++ ngx_msec_t max_idle_timeout; ++ ngx_msec_t max_ack_delay; ++ ++ size_t max_udp_payload_size; ++ size_t initial_max_data; ++ size_t initial_max_stream_data_bidi_local; ++ size_t initial_max_stream_data_bidi_remote; ++ size_t initial_max_stream_data_uni; ++ ngx_uint_t initial_max_streams_bidi; ++ ngx_uint_t initial_max_streams_uni; ++ ngx_uint_t ack_delay_exponent; ++ ngx_uint_t active_connection_id_limit; ++ ngx_flag_t disable_active_migration; ++ ++ ngx_str_t original_dcid; ++ ngx_str_t initial_scid; ++ ngx_str_t retry_scid; ++ u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; ++ ++ /* TODO */ ++ void *preferred_address; ++} ngx_quic_tp_t; ++ ++ ++ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); ++ ++size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); ++ ++size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len); ++ ++size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, ++ u_char **pnp); ++ ++size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, ++ u_char **start); ++ ++ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ++ ngx_quic_frame_t *frame); ++ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); ++ ++ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, ++ u_char *end, uint64_t *gap, uint64_t *range); ++size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); ++ ++ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ++ ngx_quic_conf_t *qcf); ++ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ++ ngx_quic_tp_t *tp, ngx_log_t *log); ++ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, ++ ngx_quic_tp_t *tp, size_t *clen); ++ ++void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key); ++ ++#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */ +diff -r ac779115ed6e src/event/quic/ngx_event_quic_udp.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/event/quic/ngx_event_quic_udp.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,473 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++ ++ ++static void ngx_quic_close_accepted_connection(ngx_connection_t *c); ++static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls, ++ ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); ++ ++ ++void ++ngx_quic_recvmsg(ngx_event_t *ev) ++{ ++ ssize_t n; ++ ngx_str_t key; ++ ngx_buf_t buf; ++ ngx_log_t *log; ++ ngx_err_t err; ++ socklen_t socklen, local_socklen; ++ ngx_event_t *rev, *wev; ++ struct iovec iov[1]; ++ struct msghdr msg; ++ ngx_sockaddr_t sa, lsa; ++ struct sockaddr *sockaddr, *local_sockaddr; ++ ngx_listening_t *ls; ++ ngx_event_conf_t *ecf; ++ ngx_connection_t *c, *lc; ++ ngx_quic_socket_t *qsock; ++ static u_char buffer[65535]; ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; ++#endif ++ ++ if (ev->timedout) { ++ if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) { ++ return; ++ } ++ ++ ev->timedout = 0; ++ } ++ ++ ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module); ++ ++ if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) { ++ ev->available = ecf->multi_accept; ++ } ++ ++ lc = ev->data; ++ ls = lc->listening; ++ ev->ready = 0; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, ++ "quic recvmsg on %V, ready: %d", ++ &ls->addr_text, ev->available); ++ ++ do { ++ ngx_memzero(&msg, sizeof(struct msghdr)); ++ ++ iov[0].iov_base = (void *) buffer; ++ iov[0].iov_len = sizeof(buffer); ++ ++ msg.msg_name = &sa; ++ msg.msg_namelen = sizeof(ngx_sockaddr_t); ++ msg.msg_iov = iov; ++ msg.msg_iovlen = 1; ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ if (ls->wildcard) { ++ msg.msg_control = &msg_control; ++ msg.msg_controllen = sizeof(msg_control); ++ ++ ngx_memzero(&msg_control, sizeof(msg_control)); ++ } ++#endif ++ ++ n = recvmsg(lc->fd, &msg, 0); ++ ++ if (n == -1) { ++ err = ngx_socket_errno; ++ ++ if (err == NGX_EAGAIN) { ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err, ++ "quic recvmsg() not ready"); ++ return; ++ } ++ ++ ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed"); ++ ++ return; ++ } ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { ++ ngx_log_error(NGX_LOG_ALERT, ev->log, 0, ++ "quic recvmsg() truncated data"); ++ continue; ++ } ++#endif ++ ++ sockaddr = msg.msg_name; ++ socklen = msg.msg_namelen; ++ ++ if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { ++ socklen = sizeof(ngx_sockaddr_t); ++ } ++ ++#if (NGX_HAVE_UNIX_DOMAIN) ++ ++ if (sockaddr->sa_family == AF_UNIX) { ++ struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; ++ ++ if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) ++ || saun->sun_path[0] == '\0') ++ { ++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, ++ "unbound unix socket"); ++ goto next; ++ } ++ } ++ ++#endif ++ ++ local_sockaddr = ls->sockaddr; ++ local_socklen = ls->socklen; ++ ++#if (NGX_HAVE_ADDRINFO_CMSG) ++ ++ if (ls->wildcard) { ++ struct cmsghdr *cmsg; ++ ++ ngx_memcpy(&lsa, local_sockaddr, local_socklen); ++ local_sockaddr = &lsa.sockaddr; ++ ++ for (cmsg = CMSG_FIRSTHDR(&msg); ++ cmsg != NULL; ++ cmsg = CMSG_NXTHDR(&msg, cmsg)) ++ { ++ if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { ++ break; ++ } ++ } ++ } ++ ++#endif ++ ++ if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { ++ goto next; ++ } ++ ++ c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen); ++ ++ if (c) { ++ ++#if (NGX_DEBUG) ++ if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { ++ ngx_log_handler_pt handler; ++ ++ handler = c->log->handler; ++ c->log->handler = NULL; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ++ "quic recvmsg: fd:%d n:%z", c->fd, n); ++ ++ c->log->handler = handler; ++ } ++#endif ++ ++ ngx_memzero(&buf, sizeof(ngx_buf_t)); ++ ++ buf.pos = buffer; ++ buf.last = buffer + n; ++ buf.start = buf.pos; ++ buf.end = buffer + sizeof(buffer); ++ ++ qsock = ngx_quic_get_socket(c); ++ ++ ngx_memcpy(&qsock->sockaddr.sockaddr, sockaddr, socklen); ++ qsock->socklen = socklen; ++ ++ c->udp->buffer = &buf; ++ ++ rev = c->read; ++ rev->ready = 1; ++ rev->active = 0; ++ ++ rev->handler(rev); ++ ++ if (c->udp) { ++ c->udp->buffer = NULL; ++ } ++ ++ rev->ready = 0; ++ rev->active = 1; ++ ++ goto next; ++ } ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); ++#endif ++ ++ ngx_accept_disabled = ngx_cycle->connection_n / 8 ++ - ngx_cycle->free_connection_n; ++ ++ c = ngx_get_connection(lc->fd, ev->log); ++ if (c == NULL) { ++ return; ++ } ++ ++ c->shared = 1; ++ c->type = SOCK_DGRAM; ++ c->socklen = socklen; ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1); ++#endif ++ ++ c->pool = ngx_create_pool(ls->pool_size, ev->log); ++ if (c->pool == NULL) { ++ ngx_quic_close_accepted_connection(c); ++ return; ++ } ++ ++ c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); ++ if (c->sockaddr == NULL) { ++ ngx_quic_close_accepted_connection(c); ++ return; ++ } ++ ++ ngx_memcpy(c->sockaddr, sockaddr, socklen); ++ ++ log = ngx_palloc(c->pool, sizeof(ngx_log_t)); ++ if (log == NULL) { ++ ngx_quic_close_accepted_connection(c); ++ return; ++ } ++ ++ *log = ls->log; ++ ++ c->log = log; ++ c->pool->log = log; ++ c->listening = ls; ++ ++ if (local_sockaddr == &lsa.sockaddr) { ++ local_sockaddr = ngx_palloc(c->pool, local_socklen); ++ if (local_sockaddr == NULL) { ++ ngx_quic_close_accepted_connection(c); ++ return; ++ } ++ ++ ngx_memcpy(local_sockaddr, &lsa, local_socklen); ++ } ++ ++ c->local_sockaddr = local_sockaddr; ++ c->local_socklen = local_socklen; ++ ++ c->buffer = ngx_create_temp_buf(c->pool, n); ++ if (c->buffer == NULL) { ++ ngx_quic_close_accepted_connection(c); ++ return; ++ } ++ ++ c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n); ++ ++ rev = c->read; ++ wev = c->write; ++ ++ rev->active = 1; ++ wev->ready = 1; ++ ++ rev->log = log; ++ wev->log = log; ++ ++ /* ++ * TODO: MT: - ngx_atomic_fetch_add() ++ * or protection by critical section or light mutex ++ * ++ * TODO: MP: - allocated in a shared memory ++ * - ngx_atomic_fetch_add() ++ * or protection by critical section or light mutex ++ */ ++ ++ c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); ++ ++ c->start_time = ngx_current_msec; ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); ++#endif ++ ++ if (ls->addr_ntop) { ++ c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); ++ if (c->addr_text.data == NULL) { ++ ngx_quic_close_accepted_connection(c); ++ return; ++ } ++ ++ c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, ++ c->addr_text.data, ++ ls->addr_text_max_len, 0); ++ if (c->addr_text.len == 0) { ++ ngx_quic_close_accepted_connection(c); ++ return; ++ } ++ } ++ ++#if (NGX_DEBUG) ++ { ++ ngx_str_t addr; ++ u_char text[NGX_SOCKADDR_STRLEN]; ++ ++ ngx_debug_accepted_connection(ecf, c); ++ ++ if (log->log_level & NGX_LOG_DEBUG_EVENT) { ++ addr.data = text; ++ addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text, ++ NGX_SOCKADDR_STRLEN, 1); ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, ++ "*%uA quic recvmsg: %V fd:%d n:%z", ++ c->number, &addr, c->fd, n); ++ } ++ ++ } ++#endif ++ ++ log->data = NULL; ++ log->handler = NULL; ++ ++ ls->handler(c); ++ ++ next: ++ ++ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { ++ ev->available -= n; ++ } ++ ++ } while (ev->available); ++} ++ ++ ++static void ++ngx_quic_close_accepted_connection(ngx_connection_t *c) ++{ ++ ngx_free_connection(c); ++ ++ c->fd = (ngx_socket_t) -1; ++ ++ if (c->pool) { ++ ngx_destroy_pool(c->pool); ++ } ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_active, -1); ++#endif ++} ++ ++ ++void ++ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, ++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) ++{ ++ ngx_int_t rc; ++ ngx_connection_t *c, *ct; ++ ngx_rbtree_node_t **p; ++ ngx_quic_socket_t *qsock, *qsockt; ++ ++ for ( ;; ) { ++ ++ if (node->key < temp->key) { ++ ++ p = &temp->left; ++ ++ } else if (node->key > temp->key) { ++ ++ p = &temp->right; ++ ++ } else { /* node->key == temp->key */ ++ ++ qsock = (ngx_quic_socket_t *) node; ++ c = qsock->udp.connection; ++ ++ qsockt = (ngx_quic_socket_t *) temp; ++ ct = qsockt->udp.connection; ++ ++ rc = ngx_memn2cmp(qsock->sid.id, qsockt->sid.id, ++ qsock->sid.len, qsockt->sid.len); ++ ++ if (rc == 0 && c->listening->wildcard) { ++ rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, ++ ct->local_sockaddr, ct->local_socklen, 1); ++ } ++ ++ p = (rc < 0) ? &temp->left : &temp->right; ++ } ++ ++ if (*p == sentinel) { ++ break; ++ } ++ ++ temp = *p; ++ } ++ ++ *p = node; ++ node->parent = temp; ++ node->left = sentinel; ++ node->right = sentinel; ++ ngx_rbt_red(node); ++} ++ ++ ++static ngx_connection_t * ++ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, ++ struct sockaddr *local_sockaddr, socklen_t local_socklen) ++{ ++ uint32_t hash; ++ ngx_int_t rc; ++ ngx_connection_t *c; ++ ngx_rbtree_node_t *node, *sentinel; ++ ngx_quic_socket_t *qsock; ++ ++ if (key->len == 0) { ++ return NULL; ++ } ++ ++ node = ls->rbtree.root; ++ sentinel = ls->rbtree.sentinel; ++ hash = ngx_crc32_long(key->data, key->len); ++ ++ while (node != sentinel) { ++ ++ if (hash < node->key) { ++ node = node->left; ++ continue; ++ } ++ ++ if (hash > node->key) { ++ node = node->right; ++ continue; ++ } ++ ++ /* hash == node->key */ ++ ++ qsock = (ngx_quic_socket_t *) node; ++ ++ rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len); ++ ++ c = qsock->udp.connection; ++ ++ if (rc == 0 && ls->wildcard) { ++ rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, ++ c->local_sockaddr, c->local_socklen, 1); ++ } ++ ++ if (rc == 0) { ++ c->udp = &qsock->udp; ++ return c; ++ } ++ ++ node = (rc < 0) ? node->left : node->right; ++ } ++ ++ return NULL; ++} +diff -r ac779115ed6e src/http/modules/ngx_http_ssl_module.c +--- a/src/http/modules/ngx_http_ssl_module.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/modules/ngx_http_ssl_module.c Thu May 11 11:48:37 2023 -0400 +@@ -9,6 +9,10 @@ + #include + #include + ++#if (NGX_QUIC_OPENSSL_COMPAT) ++#include ++#endif ++ + + typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, + ngx_pool_t *pool, ngx_str_t *s); +@@ -52,6 +56,10 @@ static char *ngx_http_ssl_conf_command_c + void *data); + + static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); ++#if (NGX_QUIC_OPENSSL_COMPAT) ++static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ++ ngx_http_conf_addr_t *addr); ++#endif + + + static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = { +@@ -419,16 +427,19 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t + unsigned char *outlen, const unsigned char *in, unsigned int inlen, + void *arg) + { +- unsigned int srvlen; +- unsigned char *srv; ++ unsigned int srvlen; ++ unsigned char *srv; + #if (NGX_DEBUG) +- unsigned int i; ++ unsigned int i; + #endif +-#if (NGX_HTTP_V2) +- ngx_http_connection_t *hc; ++#if (NGX_HTTP_V2 || NGX_HTTP_V3) ++ ngx_http_connection_t *hc; + #endif +-#if (NGX_HTTP_V2 || NGX_DEBUG) +- ngx_connection_t *c; ++#if (NGX_HTTP_V3) ++ ngx_http_v3_srv_conf_t *h3scf; ++#endif ++#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) ++ ngx_connection_t *c; + + c = ngx_ssl_get_connection(ssl_conn); + #endif +@@ -441,14 +452,41 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t + } + #endif + +-#if (NGX_HTTP_V2) ++#if (NGX_HTTP_V2 || NGX_HTTP_V3) + hc = c->data; ++#endif + ++#if (NGX_HTTP_V2) + if (hc->addr_conf->http2) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; + } else + #endif ++#if (NGX_HTTP_V3) ++ if (hc->addr_conf->quic) { ++ ++ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); ++ ++ if (h3scf->enable && h3scf->enable_hq) { ++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO ++ NGX_HTTP_V3_HQ_ALPN_PROTO; ++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO) ++ - 1; ++ ++ } else if (h3scf->enable_hq) { ++ srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; ++ srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; ++ ++ } else if (h3scf->enable || hc->addr_conf->http3) { ++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; ++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; ++ ++ } else { ++ return SSL_TLSEXT_ERR_ALERT_FATAL; ++ } ++ ++ } else ++#endif + { + srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; +@@ -1241,6 +1279,7 @@ static ngx_int_t + ngx_http_ssl_init(ngx_conf_t *cf) + { + ngx_uint_t a, p, s; ++ const char *name; + ngx_http_conf_addr_t *addr; + ngx_http_conf_port_t *port; + ngx_http_ssl_srv_conf_t *sscf; +@@ -1290,22 +1329,44 @@ ngx_http_ssl_init(ngx_conf_t *cf) + addr = port[p].addrs.elts; + for (a = 0; a < port[p].addrs.nelts; a++) { + +- if (!addr[a].opt.ssl) { ++ if (!addr[a].opt.ssl && !addr[a].opt.quic) { + continue; + } + ++ if (addr[a].opt.quic) { ++ name = "quic"; ++ ++#if (NGX_QUIC_OPENSSL_COMPAT) ++ if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) { ++ return NGX_ERROR; ++ } ++#endif ++ ++ } else { ++ name = "ssl"; ++ } ++ + cscf = addr[a].default_server; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + + if (sscf->certificates) { ++ ++ if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "\"ssl_protocols\" must enable TLSv1.3 for " ++ "the \"listen ... %s\" directive in %s:%ui", ++ name, cscf->file_name, cscf->line); ++ return NGX_ERROR; ++ } ++ + continue; + } + + if (!sscf->reject_handshake) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " +- "the \"listen ... ssl\" directive in %s:%ui", +- cscf->file_name, cscf->line); ++ "the \"listen ... %s\" directive in %s:%ui", ++ name, cscf->file_name, cscf->line); + return NGX_ERROR; + } + +@@ -1326,8 +1387,8 @@ ngx_http_ssl_init(ngx_conf_t *cf) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " +- "the \"listen ... ssl\" directive in %s:%ui", +- cscf->file_name, cscf->line); ++ "the \"listen ... %s\" directive in %s:%ui", ++ name, cscf->file_name, cscf->line); + return NGX_ERROR; + } + } +@@ -1335,3 +1396,31 @@ ngx_http_ssl_init(ngx_conf_t *cf) + + return NGX_OK; + } ++ ++ ++#if (NGX_QUIC_OPENSSL_COMPAT) ++ ++static ngx_int_t ++ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ++{ ++ ngx_uint_t s; ++ ngx_http_ssl_srv_conf_t *sscf; ++ ngx_http_core_srv_conf_t **cscfp, *cscf; ++ ++ cscfp = addr->servers.elts; ++ for (s = 0; s < addr->servers.nelts; s++) { ++ ++ cscf = cscfp[s]; ++ sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; ++ ++ if (sscf->certificates || sscf->reject_handshake) { ++ if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++#endif +diff -r ac779115ed6e src/http/ngx_http.c +--- a/src/http/ngx_http.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http.c Thu May 11 11:48:37 2023 -0400 +@@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_ + port = cmcf->ports->elts; + for (i = 0; i < cmcf->ports->nelts; i++) { + +- if (p != port[i].port || sa->sa_family != port[i].family) { ++ if (p != port[i].port ++ || lsopt->type != port[i].type ++ || sa->sa_family != port[i].family) ++ { + continue; + } + +@@ -1217,6 +1220,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_ + } + + port->family = sa->sa_family; ++ port->type = lsopt->type; + port->port = p; + port->addrs.elts = NULL; + +@@ -1237,6 +1241,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n + #if (NGX_HTTP_V2) + ngx_uint_t http2; + #endif ++#if (NGX_HTTP_V3) ++ ngx_uint_t http3; ++ ngx_uint_t quic; ++#endif + + /* + * we cannot compare whole sockaddr struct's as kernel +@@ -1278,6 +1286,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n + protocols |= lsopt->http2 << 2; + protocols_prev |= addr[i].opt.http2 << 2; + #endif ++#if (NGX_HTTP_V3) ++ http3 = lsopt->http3 || addr[i].opt.http3; ++ quic = lsopt->quic || addr[i].opt.quic; ++#endif + + if (lsopt->set) { + +@@ -1365,6 +1377,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, n + #if (NGX_HTTP_V2) + addr[i].opt.http2 = http2; + #endif ++#if (NGX_HTTP_V3) ++ addr[i].opt.http3 = http3; ++ addr[i].opt.quic = quic; ++#endif + + return NGX_OK; + } +@@ -1831,6 +1847,7 @@ ngx_http_add_listening(ngx_conf_t *cf, n + } + #endif + ++ ls->type = addr->opt.type; + ls->backlog = addr->opt.backlog; + ls->rcvbuf = addr->opt.rcvbuf; + ls->sndbuf = addr->opt.sndbuf; +@@ -1866,6 +1883,19 @@ ngx_http_add_listening(ngx_conf_t *cf, n + ls->reuseport = addr->opt.reuseport; + #endif + ++ ls->wildcard = addr->opt.wildcard; ++ ++#if (NGX_HTTP_V3) ++ ++ ls->quic = addr->opt.quic; ++ ++ if (ls->quic) { ++ ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ++ ngx_quic_rbtree_insert_value); ++ } ++ ++#endif ++ + return ls; + } + +@@ -1898,6 +1928,10 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h + #if (NGX_HTTP_V2) + addrs[i].conf.http2 = addr[i].opt.http2; + #endif ++#if (NGX_HTTP_V3) ++ addrs[i].conf.http3 = addr[i].opt.http3; ++ addrs[i].conf.quic = addr[i].opt.quic; ++#endif + addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL +@@ -1963,6 +1997,10 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_ + #if (NGX_HTTP_V2) + addrs6[i].conf.http2 = addr[i].opt.http2; + #endif ++#if (NGX_HTTP_V3) ++ addrs6[i].conf.http3 = addr[i].opt.http3; ++ addrs6[i].conf.quic = addr[i].opt.quic; ++#endif + addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL +diff -r ac779115ed6e src/http/ngx_http.h +--- a/src/http/ngx_http.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http.h Thu May 11 11:48:37 2023 -0400 +@@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ng + typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; + typedef struct ngx_http_chunked_s ngx_http_chunked_t; + typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; ++typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t; ++typedef struct ngx_http_v3_session_s ngx_http_v3_session_t; + + typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +@@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_p + #if (NGX_HTTP_V2) + #include + #endif ++#if (NGX_HTTP_V3) ++#include ++#endif + #if (NGX_HTTP_CACHE) + #include + #endif +@@ -124,6 +129,11 @@ void ngx_http_handler(ngx_http_request_t + void ngx_http_run_posted_requests(ngx_connection_t *c); + ngx_int_t ngx_http_post_request(ngx_http_request_t *r, + ngx_http_posted_request_t *pr); ++ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, ++ ngx_str_t *host); ++ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ++ ngx_uint_t alloc); ++void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc); + void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc); + void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc); + +@@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_r + #endif + + +-#if (NGX_HTTP_V2) ++#if (NGX_HTTP_V2 || NGX_HTTP_V3) + ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, + u_char **dst, ngx_uint_t last, ngx_log_t *log); + size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, +diff -r ac779115ed6e src/http/ngx_http_core_module.c +--- a/src/http/ngx_http_core_module.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http_core_module.c Thu May 11 11:48:37 2023 -0400 +@@ -3005,6 +3005,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx + lsopt.socklen = sizeof(struct sockaddr_in); + + lsopt.backlog = NGX_LISTEN_BACKLOG; ++ lsopt.type = SOCK_STREAM; + lsopt.rcvbuf = -1; + lsopt.sndbuf = -1; + #if (NGX_HAVE_SETFIB) +@@ -3986,6 +3987,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx + ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); + + lsopt.backlog = NGX_LISTEN_BACKLOG; ++ lsopt.type = SOCK_STREAM; + lsopt.rcvbuf = -1; + lsopt.sndbuf = -1; + #if (NGX_HAVE_SETFIB) +@@ -4184,6 +4186,36 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx + #endif + } + ++ if (ngx_strcmp(value[n].data, "http3") == 0) { ++#if (NGX_HTTP_V3) ++ ngx_conf_log_error(NGX_LOG_WARN, cf, 0, ++ "the \"http3\" parameter is deprecated, " ++ "use \"quic\" parameter instead"); ++ lsopt.quic = 1; ++ lsopt.http3 = 1; ++ lsopt.type = SOCK_DGRAM; ++ continue; ++#else ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "the \"http3\" parameter requires " ++ "ngx_http_v3_module"); ++ return NGX_CONF_ERROR; ++#endif ++ } ++ ++ if (ngx_strcmp(value[n].data, "quic") == 0) { ++#if (NGX_HTTP_V3) ++ lsopt.quic = 1; ++ lsopt.type = SOCK_DGRAM; ++ continue; ++#else ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "the \"quic\" parameter requires " ++ "ngx_http_v3_module"); ++ return NGX_CONF_ERROR; ++#endif ++ } ++ + if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { + + if (ngx_strcmp(&value[n].data[13], "on") == 0) { +@@ -4285,6 +4317,28 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx + return NGX_CONF_ERROR; + } + ++#if (NGX_HTTP_V3) ++ ++ if (lsopt.quic) { ++#if (NGX_HTTP_SSL) ++ if (lsopt.ssl) { ++ return "\"ssl\" parameter is incompatible with \"quic\""; ++ } ++#endif ++ ++#if (NGX_HTTP_V2) ++ if (lsopt.http2) { ++ return "\"http2\" parameter is incompatible with \"quic\""; ++ } ++#endif ++ ++ if (lsopt.proxy_protocol) { ++ return "\"proxy_protocol\" parameter is incompatible with \"quic\""; ++ } ++ } ++ ++#endif ++ + for (n = 0; n < u.naddrs; n++) { + + for (i = 0; i < n; i++) { +diff -r ac779115ed6e src/http/ngx_http_core_module.h +--- a/src/http/ngx_http_core_module.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http_core_module.h Thu May 11 11:48:37 2023 -0400 +@@ -75,6 +75,8 @@ typedef struct { + unsigned wildcard:1; + unsigned ssl:1; + unsigned http2:1; ++ unsigned http3:1; ++ unsigned quic:1; + #if (NGX_HAVE_INET6) + unsigned ipv6only:1; + #endif +@@ -86,6 +88,7 @@ typedef struct { + int backlog; + int rcvbuf; + int sndbuf; ++ int type; + #if (NGX_HAVE_SETFIB) + int setfib; + #endif +@@ -237,6 +240,8 @@ struct ngx_http_addr_conf_s { + + unsigned ssl:1; + unsigned http2:1; ++ unsigned http3:1; ++ unsigned quic:1; + unsigned proxy_protocol:1; + }; + +@@ -266,6 +271,7 @@ typedef struct { + + typedef struct { + ngx_int_t family; ++ ngx_int_t type; + in_port_t port; + ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ + } ngx_http_conf_port_t; +diff -r ac779115ed6e src/http/ngx_http_request.c +--- a/src/http/ngx_http_request.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http_request.c Thu May 11 11:48:37 2023 -0400 +@@ -29,10 +29,6 @@ static ngx_int_t ngx_http_process_connec + static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); + +-static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, +- ngx_uint_t alloc); +-static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, +- ngx_str_t *host); + static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c, + ngx_http_virtual_names_t *virtual_names, ngx_str_t *host, + ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp); +@@ -50,7 +46,6 @@ static void ngx_http_keepalive_handler(n + static void ngx_http_set_lingering_close(ngx_connection_t *c); + static void ngx_http_lingering_close_handler(ngx_event_t *ev); + static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); +-static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); + static void ngx_http_log_request(ngx_http_request_t *r); + + static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); +@@ -329,6 +324,13 @@ ngx_http_init_connection(ngx_connection_ + } + #endif + ++#if (NGX_HTTP_V3) ++ if (hc->addr_conf->quic) { ++ ngx_http_v3_init_stream(c); ++ return; ++ } ++#endif ++ + #if (NGX_HTTP_SSL) + { + ngx_http_ssl_srv_conf_t *sscf; +@@ -950,6 +952,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t * + #ifdef SSL_OP_NO_RENEGOTIATION + SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); + #endif ++ ++#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT ++#if (NGX_HTTP_V3) ++ if (c->listening->quic) { ++ SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); ++ } ++#endif ++#endif + } + + done: +@@ -2095,7 +2105,7 @@ ngx_http_process_request(ngx_http_reques + } + + +-static ngx_int_t ++ngx_int_t + ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) + { + u_char *h, ch; +@@ -2187,7 +2197,7 @@ ngx_http_validate_host(ngx_str_t *host, + } + + +-static ngx_int_t ++ngx_int_t + ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host) + { + ngx_int_t rc; +@@ -2710,6 +2720,13 @@ ngx_http_finalize_connection(ngx_http_re + } + #endif + ++#if (NGX_HTTP_V3) ++ if (r->connection->quic) { ++ ngx_http_close_request(r, 0); ++ return; ++ } ++#endif ++ + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->main->count != 1) { +@@ -2925,6 +2942,20 @@ ngx_http_test_reading(ngx_http_request_t + + #endif + ++#if (NGX_HTTP_V3) ++ ++ if (c->quic) { ++ if (rev->error) { ++ c->error = 1; ++ err = 0; ++ goto closed; ++ } ++ ++ return; ++ } ++ ++#endif ++ + #if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { +@@ -3590,7 +3621,7 @@ ngx_http_post_action(ngx_http_request_t + } + + +-static void ++void + ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) + { + ngx_connection_t *c; +@@ -3677,7 +3708,12 @@ ngx_http_free_request(ngx_http_request_t + + log->action = "closing request"; + +- if (r->connection->timedout) { ++ if (r->connection->timedout ++#if (NGX_HTTP_V3) ++ && r->connection->quic == NULL ++#endif ++ ) ++ { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->reset_timedout_connection) { +@@ -3750,6 +3786,12 @@ ngx_http_close_connection(ngx_connection + + #endif + ++#if (NGX_HTTP_V3) ++ if (c->quic) { ++ ngx_http_v3_reset_stream(c); ++ } ++#endif ++ + #if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); + #endif +diff -r ac779115ed6e src/http/ngx_http_request.h +--- a/src/http/ngx_http_request.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http_request.h Thu May 11 11:48:37 2023 -0400 +@@ -24,6 +24,7 @@ + #define NGX_HTTP_VERSION_10 1000 + #define NGX_HTTP_VERSION_11 1001 + #define NGX_HTTP_VERSION_20 2000 ++#define NGX_HTTP_VERSION_30 3000 + + #define NGX_HTTP_UNKNOWN 0x00000001 + #define NGX_HTTP_GET 0x00000002 +@@ -323,6 +324,10 @@ typedef struct { + #endif + #endif + ++#if (NGX_HTTP_V3 || NGX_COMPAT) ++ ngx_http_v3_session_t *v3_session; ++#endif ++ + ngx_chain_t *busy; + ngx_int_t nbusy; + +@@ -451,6 +456,7 @@ struct ngx_http_request_s { + + ngx_http_connection_t *http_connection; + ngx_http_v2_stream_t *stream; ++ ngx_http_v3_parse_t *v3_parse; + + ngx_http_log_handler_pt log_handler; + +@@ -543,6 +549,7 @@ struct ngx_http_request_s { + unsigned request_complete:1; + unsigned request_output:1; + unsigned header_sent:1; ++ unsigned response_sent:1; + unsigned expect_tested:1; + unsigned root_tested:1; + unsigned done:1; +diff -r ac779115ed6e src/http/ngx_http_request_body.c +--- a/src/http/ngx_http_request_body.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http_request_body.c Thu May 11 11:48:37 2023 -0400 +@@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_ht + } + #endif + ++#if (NGX_HTTP_V3) ++ if (r->http_version == NGX_HTTP_VERSION_30) { ++ rc = ngx_http_v3_read_request_body(r); ++ goto done; ++ } ++#endif ++ + preread = r->header_in->last - r->header_in->pos; + + if (preread) { +@@ -238,6 +245,18 @@ ngx_http_read_unbuffered_request_body(ng + } + #endif + ++#if (NGX_HTTP_V3) ++ if (r->http_version == NGX_HTTP_VERSION_30) { ++ rc = ngx_http_v3_read_unbuffered_request_body(r); ++ ++ if (rc == NGX_OK) { ++ r->reading_body = 0; ++ } ++ ++ return rc; ++ } ++#endif ++ + if (r->connection->read->timedout) { + r->connection->timedout = 1; + return NGX_HTTP_REQUEST_TIME_OUT; +@@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_r + } + #endif + ++#if (NGX_HTTP_V3) ++ if (r->http_version == NGX_HTTP_VERSION_30) { ++ return NGX_OK; ++ } ++#endif ++ + if (ngx_http_test_expect(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } +@@ -921,6 +946,9 @@ ngx_http_test_expect(ngx_http_request_t + #if (NGX_HTTP_V2) + || r->stream != NULL + #endif ++#if (NGX_HTTP_V3) ++ || r->connection->quic != NULL ++#endif + ) + { + return NGX_OK; +diff -r ac779115ed6e src/http/ngx_http_upstream.c +--- a/src/http/ngx_http_upstream.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http_upstream.c Thu May 11 11:48:37 2023 -0400 +@@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_ + } + #endif + ++#if (NGX_HTTP_V3) ++ if (c->quic) { ++ ngx_http_upstream_init_request(r); ++ return; ++ } ++#endif ++ + if (c->read->timer_set) { + ngx_del_timer(c->read); + } +@@ -1354,6 +1361,19 @@ ngx_http_upstream_check_broken_connectio + } + #endif + ++#if (NGX_HTTP_V3) ++ ++ if (c->quic) { ++ if (c->write->error) { ++ ngx_http_upstream_finalize_request(r, u, ++ NGX_HTTP_CLIENT_CLOSED_REQUEST); ++ } ++ ++ return; ++ } ++ ++#endif ++ + #if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { +diff -r ac779115ed6e src/http/ngx_http_write_filter_module.c +--- a/src/http/ngx_http_write_filter_module.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/http/ngx_http_write_filter_module.c Thu May 11 11:48:37 2023 -0400 +@@ -240,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t + r->out = NULL; + c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + ++ if (last) { ++ r->response_sent = 1; ++ } ++ + return NGX_OK; + } + +@@ -346,6 +350,10 @@ ngx_http_write_filter(ngx_http_request_t + + c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + ++ if (last) { ++ r->response_sent = 1; ++ } ++ + if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { + return NGX_AGAIN; + } +diff -r ac779115ed6e src/http/v3/ngx_http_v3.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,113 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); ++static void ngx_http_v3_cleanup_session(void *data); ++ ++ ++ngx_int_t ++ngx_http_v3_init_session(ngx_connection_t *c) ++{ ++ ngx_pool_cleanup_t *cln; ++ ngx_http_connection_t *hc; ++ ngx_http_v3_session_t *h3c; ++ ++ hc = c->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); ++ ++ h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t)); ++ if (h3c == NULL) { ++ goto failed; ++ } ++ ++ h3c->max_push_id = (uint64_t) -1; ++ h3c->goaway_push_id = (uint64_t) -1; ++ ++ ngx_queue_init(&h3c->blocked); ++ ngx_queue_init(&h3c->pushing); ++ ++ h3c->keepalive.log = c->log; ++ h3c->keepalive.data = c; ++ h3c->keepalive.handler = ngx_http_v3_keepalive_handler; ++ ++ h3c->table.send_insert_count.log = c->log; ++ h3c->table.send_insert_count.data = c; ++ h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler; ++ ++ cln = ngx_pool_cleanup_add(c->pool, 0); ++ if (cln == NULL) { ++ goto failed; ++ } ++ ++ cln->handler = ngx_http_v3_cleanup_session; ++ cln->data = h3c; ++ ++ hc->v3_session = h3c; ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); ++ return NGX_ERROR; ++} ++ ++ ++static void ++ngx_http_v3_keepalive_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ++ c = ev->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, ++ "keepalive timeout"); ++} ++ ++ ++static void ++ngx_http_v3_cleanup_session(void *data) ++{ ++ ngx_http_v3_session_t *h3c = data; ++ ++ ngx_http_v3_cleanup_table(h3c); ++ ++ if (h3c->keepalive.timer_set) { ++ ngx_del_timer(&h3c->keepalive); ++ } ++ ++ if (h3c->table.send_insert_count.posted) { ++ ngx_delete_posted_event(&h3c->table.send_insert_count); ++ } ++} ++ ++ ++ngx_int_t ++ngx_http_v3_check_flood(ngx_connection_t *c) ++{ ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, ++ "HTTP/3 flood detected"); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,170 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_HTTP_V3_H_INCLUDED_ ++#define _NGX_HTTP_V3_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++ ++#define NGX_HTTP_V3_ALPN_PROTO "\x02h3" ++#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" ++#define NGX_HTTP_V3_HQ_PROTO "hq-interop" ++ ++#define NGX_HTTP_V3_VARLEN_INT_LEN 4 ++#define NGX_HTTP_V3_PREFIX_INT_LEN 11 ++ ++#define NGX_HTTP_V3_STREAM_CONTROL 0x00 ++#define NGX_HTTP_V3_STREAM_PUSH 0x01 ++#define NGX_HTTP_V3_STREAM_ENCODER 0x02 ++#define NGX_HTTP_V3_STREAM_DECODER 0x03 ++ ++#define NGX_HTTP_V3_FRAME_DATA 0x00 ++#define NGX_HTTP_V3_FRAME_HEADERS 0x01 ++#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 ++#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 ++#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 ++#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 ++#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d ++ ++#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01 ++#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06 ++#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 ++ ++#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096 ++ ++#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0 ++#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1 ++#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2 ++#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3 ++#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4 ++#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 ++#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 ++#define NGX_HTTP_V3_MAX_UNI_STREAMS 3 ++ ++/* HTTP/3 errors */ ++#define NGX_HTTP_V3_ERR_NO_ERROR 0x100 ++#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101 ++#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102 ++#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103 ++#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104 ++#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105 ++#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106 ++#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107 ++#define NGX_HTTP_V3_ERR_ID_ERROR 0x108 ++#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109 ++#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a ++#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b ++#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c ++#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d ++#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f ++#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110 ++ ++/* QPACK errors */ ++#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200 ++#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201 ++#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 ++ ++ ++#define ngx_http_quic_get_connection(c) \ ++ ((ngx_http_connection_t *) ((c)->quic ? (c)->quic->parent->data \ ++ : (c)->data)) ++ ++#define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session ++ ++#define ngx_http_v3_get_module_loc_conf(c, module) \ ++ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ ++ module) ++ ++#define ngx_http_v3_get_module_srv_conf(c, module) \ ++ ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ ++ module) ++ ++#define ngx_http_v3_finalize_connection(c, code, reason) \ ++ ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \ ++ code, reason) ++ ++#define ngx_http_v3_shutdown_connection(c, code, reason) \ ++ ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \ ++ code, reason) ++ ++ ++typedef struct { ++ ngx_flag_t enable; ++ ngx_flag_t enable_hq; ++ size_t max_table_capacity; ++ ngx_uint_t max_blocked_streams; ++ ngx_uint_t max_concurrent_pushes; ++ ngx_uint_t max_concurrent_streams; ++ ngx_quic_conf_t quic; ++} ngx_http_v3_srv_conf_t; ++ ++ ++typedef struct { ++ ngx_flag_t push_preload; ++ ngx_flag_t push; ++ ngx_array_t *pushes; ++} ngx_http_v3_loc_conf_t; ++ ++ ++struct ngx_http_v3_parse_s { ++ size_t header_limit; ++ ngx_http_v3_parse_headers_t headers; ++ ngx_http_v3_parse_data_t body; ++ ngx_array_t *cookies; ++}; ++ ++ ++struct ngx_http_v3_session_s { ++ ngx_http_v3_dynamic_table_t table; ++ ++ ngx_event_t keepalive; ++ ngx_uint_t nrequests; ++ ++ ngx_queue_t blocked; ++ ngx_uint_t nblocked; ++ ++ ngx_queue_t pushing; ++ ngx_uint_t npushing; ++ uint64_t next_push_id; ++ uint64_t max_push_id; ++ uint64_t goaway_push_id; ++ uint64_t next_request_id; ++ ++ off_t total_bytes; ++ off_t payload_bytes; ++ ++ unsigned goaway:1; ++ unsigned hq:1; ++ ++ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; ++}; ++ ++ ++void ngx_http_v3_init_stream(ngx_connection_t *c); ++void ngx_http_v3_reset_stream(ngx_connection_t *c); ++ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); ++ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); ++ngx_int_t ngx_http_v3_init(ngx_connection_t *c); ++void ngx_http_v3_shutdown(ngx_connection_t *c); ++ ++ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); ++ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); ++ ++ ++extern ngx_module_t ngx_http_v3_module; ++ ++ ++#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ +diff -r ac779115ed6e src/http/v3/ngx_http_v3_encode.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_encode.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,304 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++uintptr_t ++ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) ++{ ++ if (value <= 63) { ++ if (p == NULL) { ++ return 1; ++ } ++ ++ *p++ = value; ++ return (uintptr_t) p; ++ } ++ ++ if (value <= 16383) { ++ if (p == NULL) { ++ return 2; ++ } ++ ++ *p++ = 0x40 | (value >> 8); ++ *p++ = value; ++ return (uintptr_t) p; ++ } ++ ++ if (value <= 1073741823) { ++ if (p == NULL) { ++ return 4; ++ } ++ ++ *p++ = 0x80 | (value >> 24); ++ *p++ = (value >> 16); ++ *p++ = (value >> 8); ++ *p++ = value; ++ return (uintptr_t) p; ++ } ++ ++ if (p == NULL) { ++ return 8; ++ } ++ ++ *p++ = 0xc0 | (value >> 56); ++ *p++ = (value >> 48); ++ *p++ = (value >> 40); ++ *p++ = (value >> 32); ++ *p++ = (value >> 24); ++ *p++ = (value >> 16); ++ *p++ = (value >> 8); ++ *p++ = value; ++ return (uintptr_t) p; ++} ++ ++ ++uintptr_t ++ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) ++{ ++ ngx_uint_t thresh, n; ++ ++ thresh = (1 << prefix) - 1; ++ ++ if (value < thresh) { ++ if (p == NULL) { ++ return 1; ++ } ++ ++ *p++ |= value; ++ return (uintptr_t) p; ++ } ++ ++ value -= thresh; ++ ++ if (p == NULL) { ++ for (n = 2; value >= 128; n++) { ++ value >>= 7; ++ } ++ ++ return n; ++ } ++ ++ *p++ |= thresh; ++ ++ while (value >= 128) { ++ *p++ = 0x80 | value; ++ value >>= 7; ++ } ++ ++ *p++ = value; ++ ++ return (uintptr_t) p; ++} ++ ++ ++uintptr_t ++ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count, ++ ngx_uint_t sign, ngx_uint_t delta_base) ++{ ++ if (p == NULL) { ++ return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8) ++ + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7); ++ } ++ ++ *p = 0; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8); ++ ++ *p = sign ? 0x80 : 0; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7); ++ ++ return (uintptr_t) p; ++} ++ ++ ++uintptr_t ++ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) ++{ ++ /* Indexed Field Line */ ++ ++ if (p == NULL) { ++ return ngx_http_v3_encode_prefix_int(NULL, index, 6); ++ } ++ ++ *p = dynamic ? 0x80 : 0xc0; ++ ++ return ngx_http_v3_encode_prefix_int(p, index, 6); ++} ++ ++ ++uintptr_t ++ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, ++ u_char *data, size_t len) ++{ ++ size_t hlen; ++ u_char *p1, *p2; ++ ++ /* Literal Field Line With Name Reference */ ++ ++ if (p == NULL) { ++ return ngx_http_v3_encode_prefix_int(NULL, index, 4) ++ + ngx_http_v3_encode_prefix_int(NULL, len, 7) ++ + len; ++ } ++ ++ *p = dynamic ? 0x40 : 0x50; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); ++ ++ p1 = p; ++ *p = 0; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); ++ ++ if (data) { ++ p2 = p; ++ hlen = ngx_http_huff_encode(data, len, p, 0); ++ ++ if (hlen) { ++ p = p1; ++ *p = 0x80; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); ++ ++ if (p != p2) { ++ ngx_memmove(p, p2, hlen); ++ } ++ ++ p += hlen; ++ ++ } else { ++ p = ngx_cpymem(p, data, len); ++ } ++ } ++ ++ return (uintptr_t) p; ++} ++ ++ ++uintptr_t ++ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) ++{ ++ size_t hlen; ++ u_char *p1, *p2; ++ ++ /* Literal Field Line With Literal Name */ ++ ++ if (p == NULL) { ++ return ngx_http_v3_encode_prefix_int(NULL, name->len, 3) ++ + name->len ++ + ngx_http_v3_encode_prefix_int(NULL, value->len, 7) ++ + value->len; ++ } ++ ++ p1 = p; ++ *p = 0x20; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); ++ ++ p2 = p; ++ hlen = ngx_http_huff_encode(name->data, name->len, p, 1); ++ ++ if (hlen) { ++ p = p1; ++ *p = 0x28; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3); ++ ++ if (p != p2) { ++ ngx_memmove(p, p2, hlen); ++ } ++ ++ p += hlen; ++ ++ } else { ++ ngx_strlow(p, name->data, name->len); ++ p += name->len; ++ } ++ ++ p1 = p; ++ *p = 0; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); ++ ++ p2 = p; ++ hlen = ngx_http_huff_encode(value->data, value->len, p, 0); ++ ++ if (hlen) { ++ p = p1; ++ *p = 0x80; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); ++ ++ if (p != p2) { ++ ngx_memmove(p, p2, hlen); ++ } ++ ++ p += hlen; ++ ++ } else { ++ p = ngx_cpymem(p, value->data, value->len); ++ } ++ ++ return (uintptr_t) p; ++} ++ ++ ++uintptr_t ++ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index) ++{ ++ /* Indexed Field Line With Post-Base Index */ ++ ++ if (p == NULL) { ++ return ngx_http_v3_encode_prefix_int(NULL, index, 4); ++ } ++ ++ *p = 0x10; ++ ++ return ngx_http_v3_encode_prefix_int(p, index, 4); ++} ++ ++ ++uintptr_t ++ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, ++ size_t len) ++{ ++ size_t hlen; ++ u_char *p1, *p2; ++ ++ /* Literal Field Line With Post-Base Name Reference */ ++ ++ if (p == NULL) { ++ return ngx_http_v3_encode_prefix_int(NULL, index, 3) ++ + ngx_http_v3_encode_prefix_int(NULL, len, 7) ++ + len; ++ } ++ ++ *p = 0; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); ++ ++ p1 = p; ++ *p = 0; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); ++ ++ if (data) { ++ p2 = p; ++ hlen = ngx_http_huff_encode(data, len, p, 0); ++ ++ if (hlen) { ++ p = p1; ++ *p = 0x80; ++ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); ++ ++ if (p != p2) { ++ ngx_memmove(p, p2, hlen); ++ } ++ ++ p += hlen; ++ ++ } else { ++ p = ngx_cpymem(p, data, len); ++ } ++ } ++ ++ return (uintptr_t) p; ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3_encode.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_encode.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,34 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_ ++#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++ ++uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); ++uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ++ ngx_uint_t prefix); ++ ++uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p, ++ ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); ++uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ++ ngx_uint_t index); ++uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ++ ngx_uint_t index, u_char *data, size_t len); ++uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ++ ngx_str_t *value); ++uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index); ++uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, ++ u_char *data, size_t len); ++ ++ ++#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */ +diff -r ac779115ed6e src/http/v3/ngx_http_v3_filter_module.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_filter_module.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,1536 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++/* static table indices */ ++#define NGX_HTTP_V3_HEADER_AUTHORITY 0 ++#define NGX_HTTP_V3_HEADER_PATH_ROOT 1 ++#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 ++#define NGX_HTTP_V3_HEADER_DATE 6 ++#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 ++#define NGX_HTTP_V3_HEADER_LOCATION 12 ++#define NGX_HTTP_V3_HEADER_METHOD_GET 17 ++#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 ++#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 ++#define NGX_HTTP_V3_HEADER_STATUS_200 25 ++#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 ++#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 ++#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 ++#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 ++#define NGX_HTTP_V3_HEADER_SERVER 92 ++#define NGX_HTTP_V3_HEADER_USER_AGENT 95 ++ ++ ++typedef struct { ++ ngx_chain_t *free; ++ ngx_chain_t *busy; ++} ngx_http_v3_filter_ctx_t; ++ ++ ++static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, ++ ngx_chain_t ***out); ++static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, ++ ngx_str_t *path, ngx_chain_t ***out); ++static ngx_int_t ngx_http_v3_create_push_request( ++ ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); ++static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, ++ const char *name, ngx_str_t *value); ++static void ngx_http_v3_push_request_handler(ngx_event_t *ev); ++static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, ++ ngx_str_t *path, uint64_t push_id); ++static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ++ ngx_chain_t *in); ++static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, ++ ngx_http_v3_filter_ctx_t *ctx); ++static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); ++ ++ ++static ngx_http_module_t ngx_http_v3_filter_module_ctx = { ++ NULL, /* preconfiguration */ ++ ngx_http_v3_filter_init, /* postconfiguration */ ++ ++ NULL, /* create main configuration */ ++ NULL, /* init main configuration */ ++ ++ NULL, /* create server configuration */ ++ NULL, /* merge server configuration */ ++ ++ NULL, /* create location configuration */ ++ NULL /* merge location configuration */ ++}; ++ ++ ++ngx_module_t ngx_http_v3_filter_module = { ++ NGX_MODULE_V1, ++ &ngx_http_v3_filter_module_ctx, /* module context */ ++ NULL, /* module directives */ ++ NGX_HTTP_MODULE, /* module type */ ++ NULL, /* init master */ ++ NULL, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ ++ ++static ngx_http_output_header_filter_pt ngx_http_next_header_filter; ++static ngx_http_output_body_filter_pt ngx_http_next_body_filter; ++ ++ ++static ngx_int_t ++ngx_http_v3_header_filter(ngx_http_request_t *r) ++{ ++ u_char *p; ++ size_t len, n; ++ ngx_buf_t *b; ++ ngx_str_t host, location; ++ ngx_uint_t i, port; ++ ngx_chain_t *out, *hl, *cl, **ll; ++ ngx_list_part_t *part; ++ ngx_table_elt_t *header; ++ ngx_connection_t *c; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_filter_ctx_t *ctx; ++ ngx_http_core_loc_conf_t *clcf; ++ ngx_http_core_srv_conf_t *cscf; ++ u_char addr[NGX_SOCKADDR_STRLEN]; ++ ++ if (r->http_version != NGX_HTTP_VERSION_30) { ++ return ngx_http_next_header_filter(r); ++ } ++ ++ if (r->header_sent) { ++ return NGX_OK; ++ } ++ ++ r->header_sent = 1; ++ ++ if (r != r->main) { ++ return NGX_OK; ++ } ++ ++ h3c = ngx_http_v3_get_session(r->connection); ++ ++ if (r->method == NGX_HTTP_HEAD) { ++ r->header_only = 1; ++ } ++ ++ if (r->headers_out.last_modified_time != -1) { ++ if (r->headers_out.status != NGX_HTTP_OK ++ && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT ++ && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) ++ { ++ r->headers_out.last_modified_time = -1; ++ r->headers_out.last_modified = NULL; ++ } ++ } ++ ++ if (r->headers_out.status == NGX_HTTP_NO_CONTENT) { ++ r->header_only = 1; ++ ngx_str_null(&r->headers_out.content_type); ++ r->headers_out.last_modified_time = -1; ++ r->headers_out.last_modified = NULL; ++ r->headers_out.content_length = NULL; ++ r->headers_out.content_length_n = -1; ++ } ++ ++ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { ++ r->header_only = 1; ++ } ++ ++ c = r->connection; ++ ++ out = NULL; ++ ll = &out; ++ ++ if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 ++ && r->method != NGX_HTTP_HEAD) ++ { ++ if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); ++ ++ if (r->headers_out.status == NGX_HTTP_OK) { ++ len += ngx_http_v3_encode_field_ri(NULL, 0, ++ NGX_HTTP_V3_HEADER_STATUS_200); ++ ++ } else { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_STATUS_200, ++ NULL, 3); ++ } ++ ++ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ++ ++ if (r->headers_out.server == NULL) { ++ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { ++ n = sizeof(NGINX_VER) - 1; ++ ++ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { ++ n = sizeof(NGINX_VER_BUILD) - 1; ++ ++ } else { ++ n = sizeof("nginx") - 1; ++ } ++ ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_SERVER, ++ NULL, n); ++ } ++ ++ if (r->headers_out.date == NULL) { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, ++ NULL, ngx_cached_http_time.len); ++ } ++ ++ if (r->headers_out.content_type.len) { ++ n = r->headers_out.content_type.len; ++ ++ if (r->headers_out.content_type_len == r->headers_out.content_type.len ++ && r->headers_out.charset.len) ++ { ++ n += sizeof("; charset=") - 1 + r->headers_out.charset.len; ++ } ++ ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, ++ NULL, n); ++ } ++ ++ if (r->headers_out.content_length == NULL) { ++ if (r->headers_out.content_length_n > 0) { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, ++ NULL, NGX_OFF_T_LEN); ++ ++ } else if (r->headers_out.content_length_n == 0) { ++ len += ngx_http_v3_encode_field_ri(NULL, 0, ++ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); ++ } ++ } ++ ++ if (r->headers_out.last_modified == NULL ++ && r->headers_out.last_modified_time != -1) ++ { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, ++ sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); ++ } ++ ++ if (r->headers_out.location && r->headers_out.location->value.len) { ++ ++ if (r->headers_out.location->value.data[0] == '/' ++ && clcf->absolute_redirect) ++ { ++ if (clcf->server_name_in_redirect) { ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ host = cscf->server_name; ++ ++ } else if (r->headers_in.server.len) { ++ host = r->headers_in.server; ++ ++ } else { ++ host.len = NGX_SOCKADDR_STRLEN; ++ host.data = addr; ++ ++ if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ port = ngx_inet_get_port(c->local_sockaddr); ++ ++ location.len = sizeof("https://") - 1 + host.len ++ + r->headers_out.location->value.len; ++ ++ if (clcf->port_in_redirect) { ++ port = (port == 443) ? 0 : port; ++ ++ } else { ++ port = 0; ++ } ++ ++ if (port) { ++ location.len += sizeof(":65535") - 1; ++ } ++ ++ location.data = ngx_pnalloc(r->pool, location.len); ++ if (location.data == NULL) { ++ return NGX_ERROR; ++ } ++ ++ p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1); ++ p = ngx_cpymem(p, host.data, host.len); ++ ++ if (port) { ++ p = ngx_sprintf(p, ":%ui", port); ++ } ++ ++ p = ngx_cpymem(p, r->headers_out.location->value.data, ++ r->headers_out.location->value.len); ++ ++ /* update r->headers_out.location->value for possible logging */ ++ ++ r->headers_out.location->value.len = p - location.data; ++ r->headers_out.location->value.data = location.data; ++ ngx_str_set(&r->headers_out.location->key, "Location"); ++ } ++ ++ r->headers_out.location->hash = 0; ++ ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_LOCATION, NULL, ++ r->headers_out.location->value.len); ++ } ++ ++#if (NGX_HTTP_GZIP) ++ if (r->gzip_vary) { ++ if (clcf->gzip_vary) { ++ len += ngx_http_v3_encode_field_ri(NULL, 0, ++ NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); ++ ++ } else { ++ r->gzip_vary = 0; ++ } ++ } ++#endif ++ ++ part = &r->headers_out.headers.part; ++ header = part->elts; ++ ++ for (i = 0; /* void */; i++) { ++ ++ if (i >= part->nelts) { ++ if (part->next == NULL) { ++ break; ++ } ++ ++ part = part->next; ++ header = part->elts; ++ i = 0; ++ } ++ ++ if (header[i].hash == 0) { ++ continue; ++ } ++ ++ len += ngx_http_v3_encode_field_l(NULL, &header[i].key, ++ &header[i].value); ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); ++ ++ b = ngx_create_temp_buf(r->pool, len); ++ if (b == NULL) { ++ return NGX_ERROR; ++ } ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, ++ 0, 0, 0); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \":status: %03ui\"", ++ r->headers_out.status); ++ ++ if (r->headers_out.status == NGX_HTTP_OK) { ++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, ++ NGX_HTTP_V3_HEADER_STATUS_200); ++ ++ } else { ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_STATUS_200, ++ NULL, 3); ++ b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); ++ } ++ ++ if (r->headers_out.server == NULL) { ++ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { ++ p = (u_char *) NGINX_VER; ++ n = sizeof(NGINX_VER) - 1; ++ ++ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { ++ p = (u_char *) NGINX_VER_BUILD; ++ n = sizeof(NGINX_VER_BUILD) - 1; ++ ++ } else { ++ p = (u_char *) "nginx"; ++ n = sizeof("nginx") - 1; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"server: %*s\"", n, p); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_SERVER, ++ p, n); ++ } ++ ++ if (r->headers_out.date == NULL) { ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"date: %V\"", ++ &ngx_cached_http_time); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_DATE, ++ ngx_cached_http_time.data, ++ ngx_cached_http_time.len); ++ } ++ ++ if (r->headers_out.content_type.len) { ++ if (r->headers_out.content_type_len == r->headers_out.content_type.len ++ && r->headers_out.charset.len) ++ { ++ n = r->headers_out.content_type.len + sizeof("; charset=") - 1 ++ + r->headers_out.charset.len; ++ ++ p = ngx_pnalloc(r->pool, n); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ ++ p = ngx_cpymem(p, r->headers_out.content_type.data, ++ r->headers_out.content_type.len); ++ ++ p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); ++ ++ p = ngx_cpymem(p, r->headers_out.charset.data, ++ r->headers_out.charset.len); ++ ++ /* updated r->headers_out.content_type is also needed for logging */ ++ ++ r->headers_out.content_type.len = n; ++ r->headers_out.content_type.data = p - n; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"content-type: %V\"", ++ &r->headers_out.content_type); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, ++ r->headers_out.content_type.data, ++ r->headers_out.content_type.len); ++ } ++ ++ if (r->headers_out.content_length == NULL ++ && r->headers_out.content_length_n >= 0) ++ { ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"content-length: %O\"", ++ r->headers_out.content_length_n); ++ ++ if (r->headers_out.content_length_n > 0) { ++ p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); ++ n = p - b->last; ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, ++ NULL, n); ++ ++ b->last = ngx_sprintf(b->last, "%O", ++ r->headers_out.content_length_n); ++ ++ } else { ++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, ++ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); ++ } ++ } ++ ++ if (r->headers_out.last_modified == NULL ++ && r->headers_out.last_modified_time != -1) ++ { ++ n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; ++ ++ p = ngx_pnalloc(r->pool, n); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ngx_http_time(p, r->headers_out.last_modified_time); ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"last-modified: %*s\"", n, p); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_LAST_MODIFIED, ++ p, n); ++ } ++ ++ if (r->headers_out.location && r->headers_out.location->value.len) { ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"location: %V\"", ++ &r->headers_out.location->value); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_LOCATION, ++ r->headers_out.location->value.data, ++ r->headers_out.location->value.len); ++ } ++ ++#if (NGX_HTTP_GZIP) ++ if (r->gzip_vary) { ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"vary: Accept-Encoding\""); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, ++ NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); ++ } ++#endif ++ ++ part = &r->headers_out.headers.part; ++ header = part->elts; ++ ++ for (i = 0; /* void */; i++) { ++ ++ if (i >= part->nelts) { ++ if (part->next == NULL) { ++ break; ++ } ++ ++ part = part->next; ++ header = part->elts; ++ i = 0; ++ } ++ ++ if (header[i].hash == 0) { ++ continue; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 output header: \"%V: %V\"", ++ &header[i].key, &header[i].value); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, ++ &header[i].key, ++ &header[i].value); ++ } ++ ++ if (r->header_only) { ++ b->last_buf = 1; ++ } ++ ++ cl = ngx_alloc_chain_link(r->pool); ++ if (cl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ cl->buf = b; ++ cl->next = NULL; ++ ++ n = b->last - b->pos; ++ ++ h3c->payload_bytes += n; ++ ++ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) ++ + ngx_http_v3_encode_varlen_int(NULL, n); ++ ++ b = ngx_create_temp_buf(r->pool, len); ++ if (b == NULL) { ++ return NGX_ERROR; ++ } ++ ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, ++ NGX_HTTP_V3_FRAME_HEADERS); ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); ++ ++ hl = ngx_alloc_chain_link(r->pool); ++ if (hl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ hl->buf = b; ++ hl->next = cl; ++ ++ *ll = hl; ++ ll = &cl->next; ++ ++ if (r->headers_out.content_length_n >= 0 ++ && !r->header_only && !r->expect_trailers) ++ { ++ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) ++ + ngx_http_v3_encode_varlen_int(NULL, ++ r->headers_out.content_length_n); ++ ++ b = ngx_create_temp_buf(r->pool, len); ++ if (b == NULL) { ++ return NGX_ERROR; ++ } ++ ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, ++ NGX_HTTP_V3_FRAME_DATA); ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, ++ r->headers_out.content_length_n); ++ ++ h3c->payload_bytes += r->headers_out.content_length_n; ++ h3c->total_bytes += r->headers_out.content_length_n; ++ ++ cl = ngx_alloc_chain_link(r->pool); ++ if (cl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ cl->buf = b; ++ cl->next = NULL; ++ ++ *ll = cl; ++ ++ } else { ++ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t)); ++ if (ctx == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); ++ } ++ ++ for (cl = out; cl; cl = cl->next) { ++ h3c->total_bytes += cl->buf->last - cl->buf->pos; ++ } ++ ++ return ngx_http_write_filter(r, out); ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) ++{ ++ u_char *start, *end, *last; ++ ngx_str_t path; ++ ngx_int_t rc; ++ ngx_uint_t i, push; ++ ngx_table_elt_t *h; ++ ngx_http_v3_loc_conf_t *h3lcf; ++ ngx_http_complex_value_t *pushes; ++ ++ h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); ++ ++ if (h3lcf->pushes) { ++ pushes = h3lcf->pushes->elts; ++ ++ for (i = 0; i < h3lcf->pushes->nelts; i++) { ++ ++ if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (path.len == 0) { ++ continue; ++ } ++ ++ if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { ++ continue; ++ } ++ ++ rc = ngx_http_v3_push_resource(r, &path, out); ++ ++ if (rc == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_ABORT) { ++ return NGX_OK; ++ } ++ ++ /* NGX_OK, NGX_DECLINED */ ++ } ++ } ++ ++ if (!h3lcf->push_preload) { ++ return NGX_OK; ++ } ++ ++ for (h = r->headers_out.link; h; h = h->next) { ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 parse link: \"%V\"", &h->value); ++ ++ start = h->value.data; ++ end = h->value.data + h->value.len; ++ ++ next_link: ++ ++ while (start < end && *start == ' ') { start++; } ++ ++ if (start == end || *start++ != '<') { ++ continue; ++ } ++ ++ while (start < end && *start == ' ') { start++; } ++ ++ for (last = start; last < end && *last != '>'; last++) { ++ /* void */ ++ } ++ ++ if (last == start || last == end) { ++ continue; ++ } ++ ++ path.len = last - start; ++ path.data = start; ++ ++ start = last + 1; ++ ++ while (start < end && *start == ' ') { start++; } ++ ++ if (start == end) { ++ continue; ++ } ++ ++ if (*start == ',') { ++ start++; ++ goto next_link; ++ } ++ ++ if (*start++ != ';') { ++ continue; ++ } ++ ++ last = ngx_strlchr(start, end, ','); ++ ++ if (last == NULL) { ++ last = end; ++ } ++ ++ push = 0; ++ ++ for ( ;; ) { ++ ++ while (start < last && *start == ' ') { start++; } ++ ++ if (last - start >= 6 ++ && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) ++ { ++ start += 6; ++ ++ if (start == last || *start == ' ' || *start == ';') { ++ push = 0; ++ break; ++ } ++ ++ goto next_param; ++ } ++ ++ if (last - start >= 11 ++ && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) ++ { ++ start += 11; ++ ++ if (start == last || *start == ' ' || *start == ';') { ++ push = 1; ++ } ++ ++ goto next_param; ++ } ++ ++ if (last - start >= 4 ++ && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) ++ { ++ start += 4; ++ ++ while (start < last && *start == ' ') { start++; } ++ ++ if (start == last || *start++ != '"') { ++ goto next_param; ++ } ++ ++ for ( ;; ) { ++ ++ while (start < last && *start == ' ') { start++; } ++ ++ if (last - start >= 7 ++ && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) ++ { ++ start += 7; ++ ++ if (start < last && (*start == ' ' || *start == '"')) { ++ push = 1; ++ break; ++ } ++ } ++ ++ while (start < last && *start != ' ' && *start != '"') { ++ start++; ++ } ++ ++ if (start == last) { ++ break; ++ } ++ ++ if (*start == '"') { ++ break; ++ } ++ ++ start++; ++ } ++ } ++ ++ next_param: ++ ++ start = ngx_strlchr(start, last, ';'); ++ ++ if (start == NULL) { ++ break; ++ } ++ ++ start++; ++ } ++ ++ if (push) { ++ while (path.len && path.data[path.len - 1] == ' ') { ++ path.len--; ++ } ++ } ++ ++ if (push && path.len ++ && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) ++ { ++ rc = ngx_http_v3_push_resource(r, &path, out); ++ ++ if (rc == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ if (rc == NGX_ABORT) { ++ return NGX_OK; ++ } ++ ++ /* NGX_OK, NGX_DECLINED */ ++ } ++ ++ if (last < end) { ++ start = last + 1; ++ goto next_link; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, ++ ngx_chain_t ***ll) ++{ ++ uint64_t push_id; ++ ngx_int_t rc; ++ ngx_chain_t *cl; ++ ngx_connection_t *c; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ c = r->connection; ++ h3c = ngx_http_v3_get_session(c); ++ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); ++ ++ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", ++ path, h3c->npushing, h3scf->max_concurrent_pushes, ++ h3c->next_push_id, h3c->max_push_id); ++ ++ if (!ngx_path_separator(path->data[0])) { ++ ngx_log_error(NGX_LOG_WARN, c->log, 0, ++ "non-absolute path \"%V\" not pushed", path); ++ return NGX_DECLINED; ++ } ++ ++ if (h3c->max_push_id == (uint64_t) -1 ++ || h3c->next_push_id > h3c->max_push_id) ++ { ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 abort pushes due to max_push_id"); ++ return NGX_ABORT; ++ } ++ ++ if (h3c->goaway_push_id != (uint64_t) -1) { ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 abort pushes due to goaway"); ++ return NGX_ABORT; ++ } ++ ++ if (h3c->npushing >= h3scf->max_concurrent_pushes) { ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 abort pushes due to max_concurrent_pushes"); ++ return NGX_ABORT; ++ } ++ ++ if (r->headers_in.server.len == 0) { ++ return NGX_ABORT; ++ } ++ ++ push_id = h3c->next_push_id++; ++ ++ rc = ngx_http_v3_create_push_request(r, path, push_id); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ cl = ngx_http_v3_create_push_promise(r, path, push_id); ++ if (cl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ for (**ll = cl; **ll; *ll = &(**ll)->next); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, ++ uint64_t push_id) ++{ ++ ngx_connection_t *c, *pc; ++ ngx_http_request_t *r; ++ ngx_http_log_ctx_t *ctx; ++ ngx_http_connection_t *hc, *phc; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ pc = pr->connection; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, ++ "http3 create push request id:%uL", push_id); ++ ++ c = ngx_http_v3_create_push_stream(pc, push_id); ++ if (c == NULL) { ++ return NGX_ABORT; ++ } ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1); ++#endif ++ ++ hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); ++ if (hc == NULL) { ++ ngx_http_close_connection(c); ++ return NGX_ERROR; ++ } ++ ++ phc = ngx_http_quic_get_connection(pc); ++ ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); ++ c->data = hc; ++ ++ ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); ++ if (ctx == NULL) { ++ ngx_http_close_connection(c); ++ return NGX_ERROR; ++ } ++ ++ ctx->connection = c; ++ ctx->request = NULL; ++ ctx->current_request = NULL; ++ ++ c->log->handler = pc->log->handler; ++ c->log->data = ctx; ++ c->log->action = "processing pushed request headers"; ++ ++ c->log_error = NGX_ERROR_INFO; ++ ++ r = ngx_http_create_request(c); ++ if (r == NULL) { ++ ngx_http_close_connection(c); ++ return NGX_ERROR; ++ } ++ ++ c->data = r; ++ ++ ngx_str_set(&r->http_protocol, "HTTP/3.0"); ++ ++ r->http_version = NGX_HTTP_VERSION_30; ++ r->method_name = ngx_http_core_get_method; ++ r->method = NGX_HTTP_GET; ++ ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ r->header_in = ngx_create_temp_buf(r->pool, ++ cscf->client_header_buffer_size); ++ if (r->header_in == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ if (ngx_list_init(&r->headers_in.headers, r->pool, 4, ++ sizeof(ngx_table_elt_t)) ++ != NGX_OK) ++ { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; ++ ++ r->schema.data = ngx_pstrdup(r->pool, &pr->schema); ++ if (r->schema.data == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ r->schema.len = pr->schema.len; ++ ++ r->uri_start = ngx_pstrdup(r->pool, path); ++ if (r->uri_start == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ r->uri_end = r->uri_start + path->len; ++ ++ if (ngx_http_parse_uri(r) != NGX_OK) { ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++ } ++ ++ if (ngx_http_process_request_uri(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ if (pr->headers_in.accept_encoding) { ++ if (ngx_http_v3_set_push_header(r, "accept-encoding", ++ &pr->headers_in.accept_encoding->value) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (pr->headers_in.accept_language) { ++ if (ngx_http_v3_set_push_header(r, "accept-language", ++ &pr->headers_in.accept_language->value) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ } ++ ++ if (pr->headers_in.user_agent) { ++ if (ngx_http_v3_set_push_header(r, "user-agent", ++ &pr->headers_in.user_agent->value) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ } ++ ++ c->read->handler = ngx_http_v3_push_request_handler; ++ c->read->handler = ngx_http_v3_push_request_handler; ++ ++ ngx_post_event(c->read, &ngx_posted_events); ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, ++ ngx_str_t *value) ++{ ++ u_char *p; ++ ngx_table_elt_t *h; ++ ngx_http_header_t *hh; ++ ngx_http_core_main_conf_t *cmcf; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 push header \"%s\": \"%V\"", name, value); ++ ++ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ++ ++ p = ngx_pnalloc(r->pool, value->len + 1); ++ if (p == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ ngx_memcpy(p, value->data, value->len); ++ p[value->len] = '\0'; ++ ++ h = ngx_list_push(&r->headers_in.headers); ++ if (h == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ h->key.data = (u_char *) name; ++ h->key.len = ngx_strlen(name); ++ h->hash = ngx_hash_key(h->key.data, h->key.len); ++ h->lowcase_key = (u_char *) name; ++ h->value.data = p; ++ h->value.len = value->len; ++ ++ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, ++ h->lowcase_key, h->key.len); ++ ++ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_http_v3_push_request_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ngx_http_request_t *r; ++ ++ c = ev->data; ++ r = c->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); ++ ++ ngx_http_process_request(r); ++} ++ ++ ++static ngx_chain_t * ++ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, ++ uint64_t push_id) ++{ ++ size_t n, len; ++ ngx_buf_t *b; ++ ngx_chain_t *hl, *cl; ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(r->connection); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 create push promise id:%uL", push_id); ++ ++ len = ngx_http_v3_encode_varlen_int(NULL, push_id); ++ ++ len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); ++ ++ len += ngx_http_v3_encode_field_ri(NULL, 0, ++ NGX_HTTP_V3_HEADER_METHOD_GET); ++ ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_AUTHORITY, ++ NULL, r->headers_in.server.len); ++ ++ if (path->len == 1 && path->data[0] == '/') { ++ len += ngx_http_v3_encode_field_ri(NULL, 0, ++ NGX_HTTP_V3_HEADER_PATH_ROOT); ++ ++ } else { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_PATH_ROOT, ++ NULL, path->len); ++ } ++ ++ if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { ++ len += ngx_http_v3_encode_field_ri(NULL, 0, ++ NGX_HTTP_V3_HEADER_SCHEME_HTTPS); ++ ++ } else if (r->schema.len == 4 ++ && ngx_strncmp(r->schema.data, "http", 4) == 0) ++ { ++ len += ngx_http_v3_encode_field_ri(NULL, 0, ++ NGX_HTTP_V3_HEADER_SCHEME_HTTP); ++ ++ } else { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_SCHEME_HTTP, ++ NULL, r->schema.len); ++ } ++ ++ if (r->headers_in.accept_encoding) { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, ++ r->headers_in.accept_encoding->value.len); ++ } ++ ++ if (r->headers_in.accept_language) { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, ++ r->headers_in.accept_language->value.len); ++ } ++ ++ if (r->headers_in.user_agent) { ++ len += ngx_http_v3_encode_field_lri(NULL, 0, ++ NGX_HTTP_V3_HEADER_USER_AGENT, NULL, ++ r->headers_in.user_agent->value.len); ++ } ++ ++ b = ngx_create_temp_buf(r->pool, len); ++ if (b == NULL) { ++ return NULL; ++ } ++ ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, ++ 0, 0, 0); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, ++ NGX_HTTP_V3_HEADER_METHOD_GET); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_AUTHORITY, ++ r->headers_in.server.data, ++ r->headers_in.server.len); ++ ++ if (path->len == 1 && path->data[0] == '/') { ++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, ++ NGX_HTTP_V3_HEADER_PATH_ROOT); ++ ++ } else { ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_PATH_ROOT, ++ path->data, path->len); ++ } ++ ++ if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { ++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, ++ NGX_HTTP_V3_HEADER_SCHEME_HTTPS); ++ ++ } else if (r->schema.len == 4 ++ && ngx_strncmp(r->schema.data, "http", 4) == 0) ++ { ++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, ++ NGX_HTTP_V3_HEADER_SCHEME_HTTP); ++ ++ } else { ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_SCHEME_HTTP, ++ r->schema.data, r->schema.len); ++ } ++ ++ if (r->headers_in.accept_encoding) { ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, ++ r->headers_in.accept_encoding->value.data, ++ r->headers_in.accept_encoding->value.len); ++ } ++ ++ if (r->headers_in.accept_language) { ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, ++ r->headers_in.accept_language->value.data, ++ r->headers_in.accept_language->value.len); ++ } ++ ++ if (r->headers_in.user_agent) { ++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, ++ NGX_HTTP_V3_HEADER_USER_AGENT, ++ r->headers_in.user_agent->value.data, ++ r->headers_in.user_agent->value.len); ++ } ++ ++ cl = ngx_alloc_chain_link(r->pool); ++ if (cl == NULL) { ++ return NULL; ++ } ++ ++ cl->buf = b; ++ cl->next = NULL; ++ ++ n = b->last - b->pos; ++ ++ h3c->payload_bytes += n; ++ ++ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) ++ + ngx_http_v3_encode_varlen_int(NULL, n); ++ ++ b = ngx_create_temp_buf(r->pool, len); ++ if (b == NULL) { ++ return NULL; ++ } ++ ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, ++ NGX_HTTP_V3_FRAME_PUSH_PROMISE); ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); ++ ++ hl = ngx_alloc_chain_link(r->pool); ++ if (hl == NULL) { ++ return NULL; ++ } ++ ++ hl->buf = b; ++ hl->next = cl; ++ ++ return hl; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ++{ ++ u_char *chunk; ++ off_t size; ++ ngx_int_t rc; ++ ngx_buf_t *b; ++ ngx_chain_t *out, *cl, *tl, **ll; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_filter_ctx_t *ctx; ++ ++ if (in == NULL) { ++ return ngx_http_next_body_filter(r, in); ++ } ++ ++ ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module); ++ if (ctx == NULL) { ++ return ngx_http_next_body_filter(r, in); ++ } ++ ++ h3c = ngx_http_v3_get_session(r->connection); ++ ++ out = NULL; ++ ll = &out; ++ ++ size = 0; ++ cl = in; ++ ++ for ( ;; ) { ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 chunk: %O", ngx_buf_size(cl->buf)); ++ ++ size += ngx_buf_size(cl->buf); ++ ++ if (cl->buf->flush ++ || cl->buf->sync ++ || ngx_buf_in_memory(cl->buf) ++ || cl->buf->in_file) ++ { ++ tl = ngx_alloc_chain_link(r->pool); ++ if (tl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ tl->buf = cl->buf; ++ *ll = tl; ++ ll = &tl->next; ++ } ++ ++ if (cl->next == NULL) { ++ break; ++ } ++ ++ cl = cl->next; ++ } ++ ++ if (size) { ++ tl = ngx_chain_get_free_buf(r->pool, &ctx->free); ++ if (tl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ b = tl->buf; ++ chunk = b->start; ++ ++ if (chunk == NULL) { ++ chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); ++ if (chunk == NULL) { ++ return NGX_ERROR; ++ } ++ ++ b->start = chunk; ++ b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2; ++ } ++ ++ b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; ++ b->memory = 0; ++ b->temporary = 1; ++ b->pos = chunk; ++ ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, ++ NGX_HTTP_V3_FRAME_DATA); ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); ++ ++ tl->next = out; ++ out = tl; ++ ++ h3c->payload_bytes += size; ++ } ++ ++ if (cl->buf->last_buf) { ++ tl = ngx_http_v3_create_trailers(r, ctx); ++ if (tl == NULL) { ++ return NGX_ERROR; ++ } ++ ++ cl->buf->last_buf = 0; ++ ++ *ll = tl; ++ ++ } else { ++ *ll = NULL; ++ } ++ ++ for (cl = out; cl; cl = cl->next) { ++ h3c->total_bytes += cl->buf->last - cl->buf->pos; ++ } ++ ++ rc = ngx_http_next_body_filter(r, out); ++ ++ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, ++ (ngx_buf_tag_t) &ngx_http_v3_filter_module); ++ ++ return rc; ++} ++ ++ ++static ngx_chain_t * ++ngx_http_v3_create_trailers(ngx_http_request_t *r, ++ ngx_http_v3_filter_ctx_t *ctx) ++{ ++ size_t len, n; ++ u_char *p; ++ ngx_buf_t *b; ++ ngx_uint_t i; ++ ngx_chain_t *cl, *hl; ++ ngx_list_part_t *part; ++ ngx_table_elt_t *header; ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(r->connection); ++ ++ len = 0; ++ ++ part = &r->headers_out.trailers.part; ++ header = part->elts; ++ ++ for (i = 0; /* void */; i++) { ++ ++ if (i >= part->nelts) { ++ if (part->next == NULL) { ++ break; ++ } ++ ++ part = part->next; ++ header = part->elts; ++ i = 0; ++ } ++ ++ if (header[i].hash == 0) { ++ continue; ++ } ++ ++ len += ngx_http_v3_encode_field_l(NULL, &header[i].key, ++ &header[i].value); ++ } ++ ++ cl = ngx_chain_get_free_buf(r->pool, &ctx->free); ++ if (cl == NULL) { ++ return NULL; ++ } ++ ++ b = cl->buf; ++ ++ b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; ++ b->memory = 0; ++ b->last_buf = 1; ++ ++ if (len == 0) { ++ b->temporary = 0; ++ b->pos = b->last = NULL; ++ return cl; ++ } ++ ++ b->temporary = 1; ++ ++ len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); ++ ++ b->pos = ngx_palloc(r->pool, len); ++ if (b->pos == NULL) { ++ return NULL; ++ } ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos, ++ 0, 0, 0); ++ ++ part = &r->headers_out.trailers.part; ++ header = part->elts; ++ ++ for (i = 0; /* void */; i++) { ++ ++ if (i >= part->nelts) { ++ if (part->next == NULL) { ++ break; ++ } ++ ++ part = part->next; ++ header = part->elts; ++ i = 0; ++ } ++ ++ if (header[i].hash == 0) { ++ continue; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 output trailer: \"%V: %V\"", ++ &header[i].key, &header[i].value); ++ ++ b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, ++ &header[i].key, ++ &header[i].value); ++ } ++ ++ n = b->last - b->pos; ++ ++ h3c->payload_bytes += n; ++ ++ hl = ngx_chain_get_free_buf(r->pool, &ctx->free); ++ if (hl == NULL) { ++ return NULL; ++ } ++ ++ b = hl->buf; ++ p = b->start; ++ ++ if (p == NULL) { ++ p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); ++ if (p == NULL) { ++ return NULL; ++ } ++ ++ b->start = p; ++ b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2; ++ } ++ ++ b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; ++ b->memory = 0; ++ b->temporary = 1; ++ b->pos = p; ++ ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(p, ++ NGX_HTTP_V3_FRAME_HEADERS); ++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); ++ ++ hl->next = cl; ++ ++ return hl; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_filter_init(ngx_conf_t *cf) ++{ ++ ngx_http_next_header_filter = ngx_http_top_header_filter; ++ ngx_http_top_header_filter = ngx_http_v3_header_filter; ++ ++ ngx_http_next_body_filter = ngx_http_top_body_filter; ++ ngx_http_top_body_filter = ngx_http_v3_body_filter; ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3_module.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_module.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,554 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ * Copyright (C) Roman Arutyunyan ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, ++ ngx_http_variable_value_t *v, uintptr_t data); ++static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); ++static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); ++static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, ++ void *child); ++static char *ngx_http_quic_mtu(ngx_conf_t *cf, void *post, ++ void *data); ++static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf); ++static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); ++static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, ++ void *child); ++static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ++ ++ ++static ngx_conf_post_t ngx_http_quic_mtu_post = ++ { ngx_http_quic_mtu }; ++ ++ ++static ngx_command_t ngx_http_v3_commands[] = { ++ ++ { ngx_string("http3"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, enable), ++ NULL }, ++ ++ { ngx_string("http3_hq"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, enable_hq), ++ NULL }, ++ ++ { ngx_string("http3_max_concurrent_pushes"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_num_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), ++ NULL }, ++ ++ { ngx_string("http3_max_concurrent_streams"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_num_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), ++ NULL }, ++ ++ { ngx_string("http3_push"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ++ ngx_http_v3_push, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ 0, ++ NULL }, ++ ++ { ngx_string("http3_push_preload"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ offsetof(ngx_http_v3_loc_conf_t, push_preload), ++ NULL }, ++ ++ { ngx_string("http3_stream_buffer_size"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_size_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size), ++ NULL }, ++ ++ { ngx_string("quic_retry"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.retry), ++ NULL }, ++ ++ { ngx_string("quic_gso"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), ++ NULL }, ++ ++ { ngx_string("quic_mtu"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_size_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.mtu), ++ &ngx_http_quic_mtu_post }, ++ ++ { ngx_string("quic_host_key"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_http_quic_host_key, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ 0, ++ NULL }, ++ ++ { ngx_string("quic_active_connection_id_limit"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_num_slot, ++ NGX_HTTP_SRV_CONF_OFFSET, ++ offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), ++ NULL }, ++ ++ ngx_null_command ++}; ++ ++ ++static ngx_http_module_t ngx_http_v3_module_ctx = { ++ ngx_http_v3_add_variables, /* preconfiguration */ ++ NULL, /* postconfiguration */ ++ ++ NULL, /* create main configuration */ ++ NULL, /* init main configuration */ ++ ++ ngx_http_v3_create_srv_conf, /* create server configuration */ ++ ngx_http_v3_merge_srv_conf, /* merge server configuration */ ++ ++ ngx_http_v3_create_loc_conf, /* create location configuration */ ++ ngx_http_v3_merge_loc_conf /* merge location configuration */ ++}; ++ ++ ++ngx_module_t ngx_http_v3_module = { ++ NGX_MODULE_V1, ++ &ngx_http_v3_module_ctx, /* module context */ ++ ngx_http_v3_commands, /* module directives */ ++ NGX_HTTP_MODULE, /* module type */ ++ NULL, /* init master */ ++ NULL, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ ++ ++static ngx_http_variable_t ngx_http_v3_vars[] = { ++ ++ { ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 }, ++ ++ ngx_http_null_variable ++}; ++ ++static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); ++ ++ ++static ngx_int_t ++ngx_http_v3_variable(ngx_http_request_t *r, ++ ngx_http_variable_value_t *v, uintptr_t data) ++{ ++ ngx_http_v3_session_t *h3c; ++ ++ if (r->connection->quic) { ++ h3c = ngx_http_v3_get_session(r->connection); ++ ++ if (h3c->hq) { ++ v->len = sizeof("hq") - 1; ++ v->valid = 1; ++ v->no_cacheable = 0; ++ v->not_found = 0; ++ v->data = (u_char *) "hq"; ++ ++ return NGX_OK; ++ } ++ ++ v->len = sizeof("h3") - 1; ++ v->valid = 1; ++ v->no_cacheable = 0; ++ v->not_found = 0; ++ v->data = (u_char *) "h3"; ++ ++ return NGX_OK; ++ } ++ ++ *v = ngx_http_variable_null_value; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_add_variables(ngx_conf_t *cf) ++{ ++ ngx_http_variable_t *var, *v; ++ ++ for (v = ngx_http_v3_vars; v->name.len; v++) { ++ var = ngx_http_add_variable(cf, &v->name, v->flags); ++ if (var == NULL) { ++ return NGX_ERROR; ++ } ++ ++ var->get_handler = v->get_handler; ++ var->data = v->data; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void * ++ngx_http_v3_create_srv_conf(ngx_conf_t *cf) ++{ ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); ++ if (h3scf == NULL) { ++ return NULL; ++ } ++ ++ /* ++ * set by ngx_pcalloc(): ++ * ++ * h3scf->quic.host_key = { 0, NULL } ++ * h3scf->quic.stream_reject_code_uni = 0; ++ * h3scf->quic.disable_active_migration = 0; ++ * h3scf->quic.timeout = 0; ++ * h3scf->max_blocked_streams = 0; ++ */ ++ ++ h3scf->enable = NGX_CONF_UNSET; ++ h3scf->enable_hq = NGX_CONF_UNSET; ++ h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; ++ h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; ++ h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; ++ ++ h3scf->quic.mtu = NGX_CONF_UNSET_SIZE; ++ h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; ++ h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; ++ h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS; ++ h3scf->quic.retry = NGX_CONF_UNSET; ++ h3scf->quic.gso_enabled = NGX_CONF_UNSET; ++ h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; ++ h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; ++ h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; ++ ++ h3scf->quic.init = ngx_http_v3_init; ++ h3scf->quic.shutdown = ngx_http_v3_shutdown; ++ ++ return h3scf; ++} ++ ++ ++static char * ++ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ++{ ++ ngx_http_v3_srv_conf_t *prev = parent; ++ ngx_http_v3_srv_conf_t *conf = child; ++ ++ ngx_http_ssl_srv_conf_t *sscf; ++ ++ ngx_conf_merge_value(conf->enable, prev->enable, 1); ++ ++ ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); ++ ++ ngx_conf_merge_uint_value(conf->max_concurrent_pushes, ++ prev->max_concurrent_pushes, 10); ++ ++ ngx_conf_merge_uint_value(conf->max_concurrent_streams, ++ prev->max_concurrent_streams, 128); ++ ++ conf->max_blocked_streams = conf->max_concurrent_streams; ++ ++ ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu, ++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); ++ ++ ngx_conf_merge_size_value(conf->quic.stream_buffer_size, ++ prev->quic.stream_buffer_size, ++ 65536); ++ ++ conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams; ++ ++ ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); ++ ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0); ++ ++ ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, ""); ++ ++ ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, ++ prev->quic.active_connection_id_limit, ++ 2); ++ ++ if (conf->quic.host_key.len == 0) { ++ ++ conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; ++ conf->quic.host_key.data = ngx_palloc(cf->pool, ++ conf->quic.host_key.len); ++ if (conf->quic.host_key.data == NULL) { ++ return NGX_CONF_ERROR; ++ } ++ ++ if (RAND_bytes(conf->quic.host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) ++ <= 0) ++ { ++ return NGX_CONF_ERROR; ++ } ++ } ++ ++ if (ngx_quic_derive_key(cf->log, "av_token_key", ++ &conf->quic.host_key, &ngx_http_quic_salt, ++ conf->quic.av_token_key, NGX_QUIC_AV_KEY_LEN) ++ != NGX_OK) ++ { ++ return NGX_CONF_ERROR; ++ } ++ ++ if (ngx_quic_derive_key(cf->log, "sr_token_key", ++ &conf->quic.host_key, &ngx_http_quic_salt, ++ conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN) ++ != NGX_OK) ++ { ++ return NGX_CONF_ERROR; ++ } ++ ++ sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); ++ conf->quic.ssl = &sscf->ssl; ++ ++ return NGX_CONF_OK; ++} ++ ++ ++static char * ++ngx_http_quic_mtu(ngx_conf_t *cf, void *post, void *data) ++{ ++ size_t *sp = data; ++ ++ if (*sp < NGX_QUIC_MIN_INITIAL_SIZE ++ || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) ++ { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "\"quic_mtu\" must be between %d and %d", ++ NGX_QUIC_MIN_INITIAL_SIZE, ++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ return NGX_CONF_OK; ++} ++ ++ ++static char * ++ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ++{ ++ ngx_http_v3_srv_conf_t *h3scf = conf; ++ ++ u_char *buf; ++ size_t size; ++ ssize_t n; ++ ngx_str_t *value; ++ ngx_file_t file; ++ ngx_file_info_t fi; ++ ngx_quic_conf_t *qcf; ++ ++ qcf = &h3scf->quic; ++ ++ if (qcf->host_key.len) { ++ return "is duplicate"; ++ } ++ ++ buf = NULL; ++#if (NGX_SUPPRESS_WARN) ++ size = 0; ++#endif ++ ++ value = cf->args->elts; ++ ++ if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { ++ return NGX_CONF_ERROR; ++ } ++ ++ ngx_memzero(&file, sizeof(ngx_file_t)); ++ file.name = value[1]; ++ file.log = cf->log; ++ ++ file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); ++ ++ if (file.fd == NGX_INVALID_FILE) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ++ ngx_open_file_n " \"%V\" failed", &file.name); ++ return NGX_CONF_ERROR; ++ } ++ ++ if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { ++ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ++ ngx_fd_info_n " \"%V\" failed", &file.name); ++ goto failed; ++ } ++ ++ size = ngx_file_size(&fi); ++ ++ if (size == 0) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "\"%V\" zero key size", &file.name); ++ goto failed; ++ } ++ ++ buf = ngx_pnalloc(cf->pool, size); ++ if (buf == NULL) { ++ goto failed; ++ } ++ ++ n = ngx_read_file(&file, buf, size, 0); ++ ++ if (n == NGX_ERROR) { ++ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ++ ngx_read_file_n " \"%V\" failed", &file.name); ++ goto failed; ++ } ++ ++ if ((size_t) n != size) { ++ ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, ++ ngx_read_file_n " \"%V\" returned only " ++ "%z bytes instead of %uz", &file.name, n, size); ++ goto failed; ++ } ++ ++ qcf->host_key.data = buf; ++ qcf->host_key.len = n; ++ ++ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ++ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, ++ ngx_close_file_n " \"%V\" failed", &file.name); ++ } ++ ++ return NGX_CONF_OK; ++ ++failed: ++ ++ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ++ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, ++ ngx_close_file_n " \"%V\" failed", &file.name); ++ } ++ ++ if (buf) { ++ ngx_explicit_memzero(buf, size); ++ } ++ ++ return NGX_CONF_ERROR; ++} ++ ++ ++static void * ++ngx_http_v3_create_loc_conf(ngx_conf_t *cf) ++{ ++ ngx_http_v3_loc_conf_t *h3lcf; ++ ++ h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t)); ++ if (h3lcf == NULL) { ++ return NULL; ++ } ++ ++ /* ++ * set by ngx_pcalloc(): ++ * ++ * h3lcf->pushes = NULL; ++ */ ++ ++ h3lcf->push_preload = NGX_CONF_UNSET; ++ h3lcf->push = NGX_CONF_UNSET; ++ ++ return h3lcf; ++} ++ ++ ++static char * ++ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ++{ ++ ngx_http_v3_loc_conf_t *prev = parent; ++ ngx_http_v3_loc_conf_t *conf = child; ++ ++ ngx_conf_merge_value(conf->push, prev->push, 1); ++ ++ if (conf->push && conf->pushes == NULL) { ++ conf->pushes = prev->pushes; ++ } ++ ++ ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); ++ ++ return NGX_CONF_OK; ++} ++ ++ ++static char * ++ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ++{ ++ ngx_http_v3_loc_conf_t *h3lcf = conf; ++ ++ ngx_str_t *value; ++ ngx_http_complex_value_t *cv; ++ ngx_http_compile_complex_value_t ccv; ++ ++ value = cf->args->elts; ++ ++ if (ngx_strcmp(value[1].data, "off") == 0) { ++ ++ if (h3lcf->pushes) { ++ return "\"off\" parameter cannot be used with URI"; ++ } ++ ++ if (h3lcf->push == 0) { ++ return "is duplicate"; ++ } ++ ++ h3lcf->push = 0; ++ return NGX_CONF_OK; ++ } ++ ++ if (h3lcf->push == 0) { ++ return "URI cannot be used with \"off\" parameter"; ++ } ++ ++ h3lcf->push = 1; ++ ++ if (h3lcf->pushes == NULL) { ++ h3lcf->pushes = ngx_array_create(cf->pool, 1, ++ sizeof(ngx_http_complex_value_t)); ++ if (h3lcf->pushes == NULL) { ++ return NGX_CONF_ERROR; ++ } ++ } ++ ++ cv = ngx_array_push(h3lcf->pushes); ++ if (cv == NULL) { ++ return NGX_CONF_ERROR; ++ } ++ ++ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ++ ++ ccv.cf = cf; ++ ccv.value = &value[1]; ++ ccv.complex_value = cv; ++ ++ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { ++ return NGX_CONF_ERROR; ++ } ++ ++ return NGX_CONF_OK; ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3_parse.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_parse.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,2013 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++#define ngx_http_v3_is_v2_frame(type) \ ++ ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) ++ ++ ++static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ++ ngx_uint_t n); ++static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ++ ngx_uint_t *n); ++static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length); ++ ++static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ++ ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, ++ ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b); ++ ++static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, ++ ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c, ++ ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, ++ ngx_http_v3_parse_literal_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b); ++ ++static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, ++ ngx_http_v3_parse_control_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, ++ ngx_http_v3_parse_settings_t *st, ngx_buf_t *b); ++ ++static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, ++ ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b); ++static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b); ++ ++static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, ++ ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b); ++ ++static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c, ++ ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value); ++ ++ ++static void ++ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n) ++{ ++ *loc = *b; ++ ++ if ((size_t) (loc->last - loc->pos) > n) { ++ loc->last = loc->pos + n; ++ } ++} ++ ++ ++static void ++ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn) ++{ ++ *pn -= loc->pos - b->pos; ++ b->pos = loc->pos; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length) ++{ ++ if ((size_t) (b->last - b->pos) < *length) { ++ *length -= b->last - b->pos; ++ b->pos = b->last; ++ return NGX_AGAIN; ++ } ++ ++ b->pos += *length; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ++ ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b) ++{ ++ u_char ch; ++ enum { ++ sw_start = 0, ++ sw_length_2, ++ sw_length_3, ++ sw_length_4, ++ sw_length_5, ++ sw_length_6, ++ sw_length_7, ++ sw_length_8 ++ }; ++ ++ for ( ;; ) { ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos++; ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ st->value = ch; ++ if (st->value & 0xc0) { ++ st->state = sw_length_2; ++ break; ++ } ++ ++ goto done; ++ ++ case sw_length_2: ++ ++ st->value = (st->value << 8) + ch; ++ if ((st->value & 0xc000) == 0x4000) { ++ st->value &= 0x3fff; ++ goto done; ++ } ++ ++ st->state = sw_length_3; ++ break; ++ ++ case sw_length_4: ++ ++ st->value = (st->value << 8) + ch; ++ if ((st->value & 0xc0000000) == 0x80000000) { ++ st->value &= 0x3fffffff; ++ goto done; ++ } ++ ++ st->state = sw_length_5; ++ break; ++ ++ case sw_length_3: ++ case sw_length_5: ++ case sw_length_6: ++ case sw_length_7: ++ ++ st->value = (st->value << 8) + ch; ++ st->state++; ++ break; ++ ++ case sw_length_8: ++ ++ st->value = (st->value << 8) + ch; ++ st->value &= 0x3fffffffffffffff; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse varlen int %uL", st->value); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_prefix_int(ngx_connection_t *c, ++ ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_uint_t mask; ++ enum { ++ sw_start = 0, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos++; ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ mask = (1 << prefix) - 1; ++ st->value = ch & mask; ++ ++ if (st->value != mask) { ++ goto done; ++ } ++ ++ st->shift = 0; ++ st->state = sw_value; ++ break; ++ ++ case sw_value: ++ ++ st->value += (uint64_t) (ch & 0x7f) << st->shift; ++ ++ if (st->shift == 56 ++ && ((ch & 0x80) || (st->value & 0xc000000000000000))) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client exceeded integer size limit"); ++ return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; ++ } ++ ++ if (ch & 0x80) { ++ st->shift += 7; ++ break; ++ } ++ ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse prefix int %uL", st->value); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, ++ ngx_buf_t *b) ++{ ++ ngx_buf_t loc; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_type, ++ sw_length, ++ sw_skip, ++ sw_prefix, ++ sw_verify, ++ sw_field_rep, ++ sw_done ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse headers"); ++ ++ st->state = sw_type; ++ ++ /* fall through */ ++ ++ case sw_type: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->type = st->vlint.value; ++ ++ if (ngx_http_v3_is_v2_frame(st->type) ++ || st->type == NGX_HTTP_V3_FRAME_DATA ++ || st->type == NGX_HTTP_V3_FRAME_GOAWAY ++ || st->type == NGX_HTTP_V3_FRAME_SETTINGS ++ || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID ++ || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH ++ || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) ++ { ++ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; ++ } ++ ++ st->state = sw_length; ++ break; ++ ++ case sw_length: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->length = st->vlint.value; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse headers type:%ui, len:%ui", ++ st->type, st->length); ++ ++ if (st->type != NGX_HTTP_V3_FRAME_HEADERS) { ++ st->state = st->length > 0 ? sw_skip : sw_type; ++ break; ++ } ++ ++ if (st->length == 0) { ++ return NGX_HTTP_V3_ERR_FRAME_ERROR; ++ } ++ ++ st->state = sw_prefix; ++ break; ++ ++ case sw_skip: ++ ++ rc = ngx_http_v3_parse_skip(b, &st->length); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->state = sw_type; ++ break; ++ ++ case sw_prefix: ++ ++ ngx_http_v3_parse_start_local(b, &loc, st->length); ++ ++ rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &loc); ++ ++ ngx_http_v3_parse_end_local(b, &loc, &st->length); ++ ++ if (st->length == 0 && rc == NGX_AGAIN) { ++ return NGX_HTTP_V3_ERR_FRAME_ERROR; ++ } ++ ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->state = sw_verify; ++ break; ++ ++ case sw_verify: ++ ++ rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_field_rep; ++ ++ /* fall through */ ++ ++ case sw_field_rep: ++ ++ ngx_http_v3_parse_start_local(b, &loc, st->length); ++ ++ rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, ++ &loc); ++ ++ ngx_http_v3_parse_end_local(b, &loc, &st->length); ++ ++ if (st->length == 0 && rc == NGX_AGAIN) { ++ return NGX_HTTP_V3_ERR_FRAME_ERROR; ++ } ++ ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ if (st->length == 0) { ++ goto done; ++ } ++ ++ return NGX_OK; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); ++ ++ if (st->prefix.insert_count > 0) { ++ if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_http_v3_ack_insert_count(c, st->prefix.insert_count); ++ } ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, ++ ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_req_insert_count, ++ sw_delta_base, ++ sw_read_delta_base ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field section prefix"); ++ ++ st->state = sw_req_insert_count; ++ ++ /* fall through */ ++ ++ case sw_req_insert_count: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->insert_count = st->pint.value; ++ st->state = sw_delta_base; ++ break; ++ ++ case sw_delta_base: ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->sign = (ch & 0x80) ? 1 : 0; ++ st->state = sw_read_delta_base; ++ ++ /* fall through */ ++ ++ case sw_read_delta_base: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->delta_base = st->pint.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ rc = ngx_http_v3_decode_insert_count(c, &st->insert_count); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ if (st->sign) { ++ if (st->insert_count <= st->delta_base) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent negative base"); ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ st->base = st->insert_count - st->delta_base - 1; ++ ++ } else { ++ st->base = st->insert_count + st->delta_base; ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field section prefix done " ++ "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui", ++ st->insert_count, st->sign, st->delta_base, st->base); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_rep(ngx_connection_t *c, ++ ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_field_ri, ++ sw_field_lri, ++ sw_field_l, ++ sw_field_pbi, ++ sw_field_lpbi ++ }; ++ ++ if (st->state == sw_start) { ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field representation"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t)); ++ ++ st->field.base = base; ++ ++ if (ch & 0x80) { ++ /* Indexed Field Line */ ++ ++ st->state = sw_field_ri; ++ ++ } else if (ch & 0x40) { ++ /* Literal Field Line With Name Reference */ ++ ++ st->state = sw_field_lri; ++ ++ } else if (ch & 0x20) { ++ /* Literal Field Line With Literal Name */ ++ ++ st->state = sw_field_l; ++ ++ } else if (ch & 0x10) { ++ /* Indexed Field Line With Post-Base Index */ ++ ++ st->state = sw_field_pbi; ++ ++ } else { ++ /* Literal Field Line With Post-Base Name Reference */ ++ ++ st->state = sw_field_lpbi; ++ } ++ } ++ ++ switch (st->state) { ++ ++ case sw_field_ri: ++ rc = ngx_http_v3_parse_field_ri(c, &st->field, b); ++ break; ++ ++ case sw_field_lri: ++ rc = ngx_http_v3_parse_field_lri(c, &st->field, b); ++ break; ++ ++ case sw_field_l: ++ rc = ngx_http_v3_parse_field_l(c, &st->field, b); ++ break; ++ ++ case sw_field_pbi: ++ rc = ngx_http_v3_parse_field_pbi(c, &st->field, b); ++ break; ++ ++ case sw_field_lpbi: ++ rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b); ++ break; ++ ++ default: ++ rc = NGX_OK; ++ } ++ ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field representation done"); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, ++ ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_uint_t n; ++ ngx_http_core_srv_conf_t *cscf; ++ enum { ++ sw_start = 0, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse literal huff:%ui, len:%ui", ++ st->huffman, st->length); ++ ++ n = st->length; ++ ++ cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); ++ ++ if (n > cscf->large_client_header_buffers.size) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client sent too large field line"); ++ return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; ++ } ++ ++ if (st->huffman) { ++ n = n * 8 / 5; ++ st->huffstate = 0; ++ } ++ ++ st->last = ngx_pnalloc(c->pool, n + 1); ++ if (st->last == NULL) { ++ return NGX_ERROR; ++ } ++ ++ st->value.data = st->last; ++ st->state = sw_value; ++ ++ /* fall through */ ++ ++ case sw_value: ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos++; ++ ++ if (st->huffman) { ++ if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last, ++ st->length == 1, c->log) ++ != NGX_OK) ++ { ++ return NGX_ERROR; ++ } ++ ++ } else { ++ *st->last++ = ch; ++ } ++ ++ if (--st->length) { ++ break; ++ } ++ ++ st->value.len = st->last - st->value.data; ++ *st->last = '\0'; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse literal done \"%V\"", &st->value); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, ++ ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_index ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field ri"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->dynamic = (ch & 0x40) ? 0 : 1; ++ st->state = sw_index; ++ ++ /* fall through */ ++ ++ case sw_index: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->index = st->pint.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field ri done %s%ui]", ++ st->dynamic ? "dynamic[-" : "static[", st->index); ++ ++ if (st->dynamic) { ++ st->index = st->base - st->index - 1; ++ } ++ ++ rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, ++ &st->value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_lri(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_index, ++ sw_value_len, ++ sw_read_value_len, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field lri"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->dynamic = (ch & 0x10) ? 0 : 1; ++ st->state = sw_index; ++ ++ /* fall through */ ++ ++ case sw_index: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->index = st->pint.value; ++ st->state = sw_value_len; ++ break; ++ ++ case sw_value_len: ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->literal.huffman = (ch & 0x80) ? 1 : 0; ++ st->state = sw_read_value_len; ++ ++ /* fall through */ ++ ++ case sw_read_value_len: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->literal.length = st->pint.value; ++ if (st->literal.length == 0) { ++ goto done; ++ } ++ ++ st->state = sw_value; ++ break; ++ ++ case sw_value: ++ ++ rc = ngx_http_v3_parse_literal(c, &st->literal, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->value = st->literal.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field lri done %s%ui] \"%V\"", ++ st->dynamic ? "dynamic[-" : "static[", ++ st->index, &st->value); ++ ++ if (st->dynamic) { ++ st->index = st->base - st->index - 1; ++ } ++ ++ rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_l(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_name_len, ++ sw_name, ++ sw_value_len, ++ sw_read_value_len, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->literal.huffman = (ch & 0x08) ? 1 : 0; ++ st->state = sw_name_len; ++ ++ /* fall through */ ++ ++ case sw_name_len: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->literal.length = st->pint.value; ++ if (st->literal.length == 0) { ++ return NGX_ERROR; ++ } ++ ++ st->state = sw_name; ++ break; ++ ++ case sw_name: ++ ++ rc = ngx_http_v3_parse_literal(c, &st->literal, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->name = st->literal.value; ++ st->state = sw_value_len; ++ break; ++ ++ case sw_value_len: ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->literal.huffman = (ch & 0x80) ? 1 : 0; ++ st->state = sw_read_value_len; ++ ++ /* fall through */ ++ ++ case sw_read_value_len: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->literal.length = st->pint.value; ++ if (st->literal.length == 0) { ++ goto done; ++ } ++ ++ st->state = sw_value; ++ break; ++ ++ case sw_value: ++ ++ rc = ngx_http_v3_parse_literal(c, &st->literal, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->value = st->literal.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field l done \"%V\" \"%V\"", ++ &st->name, &st->value); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_pbi(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b) ++{ ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_index ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field pbi"); ++ ++ st->state = sw_index; ++ ++ /* fall through */ ++ ++ case sw_index: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->index = st->pint.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field pbi done dynamic[+%ui]", st->index); ++ ++ rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, ++ &st->value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_index, ++ sw_value_len, ++ sw_read_value_len, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field lpbi"); ++ ++ st->state = sw_index; ++ ++ /* fall through */ ++ ++ case sw_index: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->index = st->pint.value; ++ st->state = sw_value_len; ++ break; ++ ++ case sw_value_len: ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->literal.huffman = (ch & 0x80) ? 1 : 0; ++ st->state = sw_read_value_len; ++ ++ /* fall through */ ++ ++ case sw_read_value_len: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->literal.length = st->pint.value; ++ if (st->literal.length == 0) { ++ goto done; ++ } ++ ++ st->state = sw_value; ++ break; ++ ++ case sw_value: ++ ++ rc = ngx_http_v3_parse_literal(c, &st->literal, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->value = st->literal.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field lpbi done dynamic[+%ui] \"%V\"", ++ st->index, &st->value); ++ ++ rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, ++ ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) ++{ ++ u_char *p; ++ ++ if (!dynamic) { ++ if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ return NGX_OK; ++ } ++ ++ if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ if (name) { ++ p = ngx_pnalloc(c->pool, name->len + 1); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ngx_memcpy(p, name->data, name->len); ++ p[name->len] = '\0'; ++ name->data = p; ++ } ++ ++ if (value) { ++ p = ngx_pnalloc(c->pool, value->len + 1); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ngx_memcpy(p, value->data, value->len); ++ p[value->len] = '\0'; ++ value->data = p; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, ++ ngx_buf_t *b) ++{ ++ ngx_buf_t loc; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_first_type, ++ sw_type, ++ sw_length, ++ sw_cancel_push, ++ sw_settings, ++ sw_max_push_id, ++ sw_goaway, ++ sw_skip ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse control"); ++ ++ st->state = sw_first_type; ++ ++ /* fall through */ ++ ++ case sw_first_type: ++ case sw_type: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->type = st->vlint.value; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse frame type:%ui", st->type); ++ ++ if (st->state == sw_first_type ++ && st->type != NGX_HTTP_V3_FRAME_SETTINGS) ++ { ++ return NGX_HTTP_V3_ERR_MISSING_SETTINGS; ++ } ++ ++ if (st->state != sw_first_type ++ && st->type == NGX_HTTP_V3_FRAME_SETTINGS) ++ { ++ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; ++ } ++ ++ if (ngx_http_v3_is_v2_frame(st->type) ++ || st->type == NGX_HTTP_V3_FRAME_DATA ++ || st->type == NGX_HTTP_V3_FRAME_HEADERS ++ || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) ++ { ++ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; ++ } ++ ++ st->state = sw_length; ++ break; ++ ++ case sw_length: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse frame len:%uL", st->vlint.value); ++ ++ st->length = st->vlint.value; ++ if (st->length == 0) { ++ st->state = sw_type; ++ break; ++ } ++ ++ switch (st->type) { ++ ++ case NGX_HTTP_V3_FRAME_CANCEL_PUSH: ++ st->state = sw_cancel_push; ++ break; ++ ++ case NGX_HTTP_V3_FRAME_SETTINGS: ++ st->state = sw_settings; ++ break; ++ ++ case NGX_HTTP_V3_FRAME_MAX_PUSH_ID: ++ st->state = sw_max_push_id; ++ break; ++ ++ case NGX_HTTP_V3_FRAME_GOAWAY: ++ st->state = sw_goaway; ++ break; ++ ++ default: ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse skip unknown frame"); ++ st->state = sw_skip; ++ } ++ ++ break; ++ ++ case sw_cancel_push: ++ ++ ngx_http_v3_parse_start_local(b, &loc, st->length); ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); ++ ++ ngx_http_v3_parse_end_local(b, &loc, &st->length); ++ ++ if (st->length == 0 && rc == NGX_AGAIN) { ++ return NGX_HTTP_V3_ERR_FRAME_ERROR; ++ } ++ ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_cancel_push(c, st->vlint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_type; ++ break; ++ ++ case sw_settings: ++ ++ ngx_http_v3_parse_start_local(b, &loc, st->length); ++ ++ rc = ngx_http_v3_parse_settings(c, &st->settings, &loc); ++ ++ ngx_http_v3_parse_end_local(b, &loc, &st->length); ++ ++ if (st->length == 0 && rc == NGX_AGAIN) { ++ return NGX_HTTP_V3_ERR_SETTINGS_ERROR; ++ } ++ ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ if (st->length == 0) { ++ st->state = sw_type; ++ } ++ ++ break; ++ ++ case sw_max_push_id: ++ ++ ngx_http_v3_parse_start_local(b, &loc, st->length); ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); ++ ++ ngx_http_v3_parse_end_local(b, &loc, &st->length); ++ ++ if (st->length == 0 && rc == NGX_AGAIN) { ++ return NGX_HTTP_V3_ERR_FRAME_ERROR; ++ } ++ ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_set_max_push_id(c, st->vlint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_type; ++ break; ++ ++ case sw_goaway: ++ ++ ngx_http_v3_parse_start_local(b, &loc, st->length); ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); ++ ++ ngx_http_v3_parse_end_local(b, &loc, &st->length); ++ ++ if (st->length == 0 && rc == NGX_AGAIN) { ++ return NGX_HTTP_V3_ERR_FRAME_ERROR; ++ } ++ ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_goaway(c, st->vlint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_type; ++ break; ++ ++ case sw_skip: ++ ++ rc = ngx_http_v3_parse_skip(b, &st->length); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->state = sw_type; ++ break; ++ } ++ } ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_settings(ngx_connection_t *c, ++ ngx_http_v3_parse_settings_t *st, ngx_buf_t *b) ++{ ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_id, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse settings"); ++ ++ st->state = sw_id; ++ ++ /* fall through */ ++ ++ case sw_id: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->id = st->vlint.value; ++ st->state = sw_value; ++ break; ++ ++ case sw_value: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_SETTINGS_ERROR; ++ } ++ ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done"); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, ++ ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_inr, ++ sw_iln, ++ sw_capacity, ++ sw_duplicate ++ }; ++ ++ for ( ;; ) { ++ ++ if (st->state == sw_start) { ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse encoder instruction"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ if (ch & 0x80) { ++ /* Insert With Name Reference */ ++ ++ st->state = sw_inr; ++ ++ } else if (ch & 0x40) { ++ /* Insert With Literal Name */ ++ ++ st->state = sw_iln; ++ ++ } else if (ch & 0x20) { ++ /* Set Dynamic Table Capacity */ ++ ++ st->state = sw_capacity; ++ ++ } else { ++ /* Duplicate */ ++ ++ st->state = sw_duplicate; ++ } ++ } ++ ++ switch (st->state) { ++ ++ case sw_inr: ++ ++ rc = ngx_http_v3_parse_field_inr(c, &st->field, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ break; ++ ++ case sw_iln: ++ ++ rc = ngx_http_v3_parse_field_iln(c, &st->field, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ break; ++ ++ case sw_capacity: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_set_capacity(c, st->pint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ break; ++ ++ default: /* sw_duplicate */ ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_duplicate(c, st->pint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ break; ++ } ++ } ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_inr(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_name_index, ++ sw_value_len, ++ sw_read_value_len, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field inr"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->dynamic = (ch & 0x40) ? 0 : 1; ++ st->state = sw_name_index; ++ ++ /* fall through */ ++ ++ case sw_name_index: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->index = st->pint.value; ++ st->state = sw_value_len; ++ break; ++ ++ case sw_value_len: ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->literal.huffman = (ch & 0x80) ? 1 : 0; ++ st->state = sw_read_value_len; ++ ++ /* fall through */ ++ ++ case sw_read_value_len: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->literal.length = st->pint.value; ++ if (st->literal.length == 0) { ++ st->value.len = 0; ++ goto done; ++ } ++ ++ st->state = sw_value; ++ break; ++ ++ case sw_value: ++ ++ rc = ngx_http_v3_parse_literal(c, &st->literal, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->value = st->literal.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field inr done %s[%ui] \"%V\"", ++ st->dynamic ? "dynamic" : "static", ++ st->index, &st->value); ++ ++ rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_field_iln(ngx_connection_t *c, ++ ngx_http_v3_parse_field_t *st, ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_name_len, ++ sw_name, ++ sw_value_len, ++ sw_read_value_len, ++ sw_value ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field iln"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->literal.huffman = (ch & 0x20) ? 1 : 0; ++ st->state = sw_name_len; ++ ++ /* fall through */ ++ ++ case sw_name_len: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->literal.length = st->pint.value; ++ if (st->literal.length == 0) { ++ return NGX_ERROR; ++ } ++ ++ st->state = sw_name; ++ break; ++ ++ case sw_name: ++ ++ rc = ngx_http_v3_parse_literal(c, &st->literal, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->name = st->literal.value; ++ st->state = sw_value_len; ++ break; ++ ++ case sw_value_len: ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ st->literal.huffman = (ch & 0x80) ? 1 : 0; ++ st->state = sw_read_value_len; ++ ++ /* fall through */ ++ ++ case sw_read_value_len: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->literal.length = st->pint.value; ++ if (st->literal.length == 0) { ++ st->value.len = 0; ++ goto done; ++ } ++ ++ st->state = sw_value; ++ break; ++ ++ case sw_value: ++ ++ rc = ngx_http_v3_parse_literal(c, &st->literal, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->value = st->literal.value; ++ goto done; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse field iln done \"%V\":\"%V\"", ++ &st->name, &st->value); ++ ++ rc = ngx_http_v3_insert(c, &st->name, &st->value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, ++ ngx_buf_t *b) ++{ ++ u_char ch; ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_ack_section, ++ sw_cancel_stream, ++ sw_inc_insert_count ++ }; ++ ++ for ( ;; ) { ++ ++ if (st->state == sw_start) { ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse decoder instruction"); ++ ++ if (b->pos == b->last) { ++ return NGX_AGAIN; ++ } ++ ++ ch = *b->pos; ++ ++ if (ch & 0x80) { ++ /* Section Acknowledgment */ ++ ++ st->state = sw_ack_section; ++ ++ } else if (ch & 0x40) { ++ /* Stream Cancellation */ ++ ++ st->state = sw_cancel_stream; ++ ++ } else { ++ /* Insert Count Increment */ ++ ++ st->state = sw_inc_insert_count; ++ } ++ } ++ ++ switch (st->state) { ++ ++ case sw_ack_section: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_ack_section(c, st->pint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ break; ++ ++ case sw_cancel_stream: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_cancel_stream(c, st->pint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ break; ++ ++ case sw_inc_insert_count: ++ ++ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_inc_insert_count(c, st->pint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ st->state = sw_start; ++ break; ++ } ++ } ++} ++ ++ ++ngx_int_t ++ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, ++ ngx_buf_t *b) ++{ ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_type, ++ sw_length, ++ sw_skip ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data"); ++ ++ st->state = sw_type; ++ ++ /* fall through */ ++ ++ case sw_type: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->type = st->vlint.value; ++ ++ if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { ++ /* trailers */ ++ goto done; ++ } ++ ++ if (ngx_http_v3_is_v2_frame(st->type) ++ || st->type == NGX_HTTP_V3_FRAME_GOAWAY ++ || st->type == NGX_HTTP_V3_FRAME_SETTINGS ++ || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID ++ || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH ++ || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) ++ { ++ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; ++ } ++ ++ st->state = sw_length; ++ break; ++ ++ case sw_length: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->length = st->vlint.value; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 parse data type:%ui, len:%ui", ++ st->type, st->length); ++ ++ if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) { ++ st->state = sw_skip; ++ break; ++ } ++ ++ st->state = sw_type; ++ return NGX_OK; ++ ++ case sw_skip: ++ ++ rc = ngx_http_v3_parse_skip(b, &st->length); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ st->state = sw_type; ++ break; ++ } ++ } ++ ++done: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done"); ++ ++ st->state = sw_start; ++ return NGX_DONE; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st, ++ ngx_buf_t *b) ++{ ++ ngx_int_t rc; ++ enum { ++ sw_start = 0, ++ sw_type, ++ sw_control, ++ sw_encoder, ++ sw_decoder, ++ sw_unknown ++ }; ++ ++ for ( ;; ) { ++ ++ switch (st->state) { ++ case sw_start: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); ++ ++ st->state = sw_type; ++ ++ /* fall through */ ++ ++ case sw_type: ++ ++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); ++ if (rc != NGX_DONE) { ++ return rc; ++ } ++ ++ rc = ngx_http_v3_register_uni_stream(c, st->vlint.value); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ switch (st->vlint.value) { ++ case NGX_HTTP_V3_STREAM_CONTROL: ++ st->state = sw_control; ++ break; ++ ++ case NGX_HTTP_V3_STREAM_ENCODER: ++ st->state = sw_encoder; ++ break; ++ ++ case NGX_HTTP_V3_STREAM_DECODER: ++ st->state = sw_decoder; ++ break; ++ ++ default: ++ st->state = sw_unknown; ++ } ++ ++ break; ++ ++ case sw_control: ++ ++ return ngx_http_v3_parse_control(c, &st->u.control, b); ++ ++ case sw_encoder: ++ ++ return ngx_http_v3_parse_encoder(c, &st->u.encoder, b); ++ ++ case sw_decoder: ++ ++ return ngx_http_v3_parse_decoder(c, &st->u.decoder, b); ++ ++ case sw_unknown: ++ ++ b->pos = b->last; ++ return NGX_AGAIN; ++ } ++ } ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3_parse.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_parse.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,146 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_ ++#define _NGX_HTTP_V3_PARSE_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ uint64_t value; ++} ngx_http_v3_parse_varlen_int_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_uint_t shift; ++ uint64_t value; ++} ngx_http_v3_parse_prefix_int_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ uint64_t id; ++ ngx_http_v3_parse_varlen_int_t vlint; ++} ngx_http_v3_parse_settings_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_uint_t insert_count; ++ ngx_uint_t delta_base; ++ ngx_uint_t sign; ++ ngx_uint_t base; ++ ngx_http_v3_parse_prefix_int_t pint; ++} ngx_http_v3_parse_field_section_prefix_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_uint_t length; ++ ngx_uint_t huffman; ++ ngx_str_t value; ++ u_char *last; ++ u_char huffstate; ++} ngx_http_v3_parse_literal_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_uint_t index; ++ ngx_uint_t base; ++ ngx_uint_t dynamic; ++ ++ ngx_str_t name; ++ ngx_str_t value; ++ ++ ngx_http_v3_parse_prefix_int_t pint; ++ ngx_http_v3_parse_literal_t literal; ++} ngx_http_v3_parse_field_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_http_v3_parse_field_t field; ++} ngx_http_v3_parse_field_rep_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_uint_t type; ++ ngx_uint_t length; ++ ngx_http_v3_parse_varlen_int_t vlint; ++ ngx_http_v3_parse_field_section_prefix_t prefix; ++ ngx_http_v3_parse_field_rep_t field_rep; ++} ngx_http_v3_parse_headers_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_http_v3_parse_field_t field; ++ ngx_http_v3_parse_prefix_int_t pint; ++} ngx_http_v3_parse_encoder_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_http_v3_parse_prefix_int_t pint; ++} ngx_http_v3_parse_decoder_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_uint_t type; ++ ngx_uint_t length; ++ ngx_http_v3_parse_varlen_int_t vlint; ++ ngx_http_v3_parse_settings_t settings; ++} ngx_http_v3_parse_control_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_http_v3_parse_varlen_int_t vlint; ++ union { ++ ngx_http_v3_parse_encoder_t encoder; ++ ngx_http_v3_parse_decoder_t decoder; ++ ngx_http_v3_parse_control_t control; ++ } u; ++} ngx_http_v3_parse_uni_t; ++ ++ ++typedef struct { ++ ngx_uint_t state; ++ ngx_uint_t type; ++ ngx_uint_t length; ++ ngx_http_v3_parse_varlen_int_t vlint; ++} ngx_http_v3_parse_data_t; ++ ++ ++/* ++ * Parse functions return codes: ++ * NGX_DONE - parsing done ++ * NGX_OK - sub-element done ++ * NGX_AGAIN - more data expected ++ * NGX_BUSY - waiting for external event ++ * NGX_ERROR - internal error ++ * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error ++ */ ++ ++ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, ++ ngx_http_v3_parse_headers_t *st, ngx_buf_t *b); ++ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, ++ ngx_http_v3_parse_data_t *st, ngx_buf_t *b); ++ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c, ++ ngx_http_v3_parse_uni_t *st, ngx_buf_t *b); ++ ++ ++#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */ +diff -r ac779115ed6e src/http/v3/ngx_http_v3_request.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_request.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,1718 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++static void ngx_http_v3_init_request_stream(ngx_connection_t *c); ++static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); ++static void ngx_http_v3_cleanup_connection(void *data); ++static void ngx_http_v3_cleanup_request(void *data); ++static void ngx_http_v3_process_request(ngx_event_t *rev); ++static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ++ ngx_str_t *name, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, ++ ngx_str_t *name, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ++ ngx_str_t *name, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value); ++static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); ++static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); ++static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, ++ ngx_chain_t *in); ++ ++ ++static const struct { ++ ngx_str_t name; ++ ngx_uint_t method; ++} ngx_http_v3_methods[] = { ++ ++ { ngx_string("GET"), NGX_HTTP_GET }, ++ { ngx_string("POST"), NGX_HTTP_POST }, ++ { ngx_string("HEAD"), NGX_HTTP_HEAD }, ++ { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, ++ { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, ++ { ngx_string("PUT"), NGX_HTTP_PUT }, ++ { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, ++ { ngx_string("DELETE"), NGX_HTTP_DELETE }, ++ { ngx_string("COPY"), NGX_HTTP_COPY }, ++ { ngx_string("MOVE"), NGX_HTTP_MOVE }, ++ { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, ++ { ngx_string("LOCK"), NGX_HTTP_LOCK }, ++ { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, ++ { ngx_string("PATCH"), NGX_HTTP_PATCH }, ++ { ngx_string("TRACE"), NGX_HTTP_TRACE }, ++ { ngx_string("CONNECT"), NGX_HTTP_CONNECT } ++}; ++ ++ ++void ++ngx_http_v3_init_stream(ngx_connection_t *c) ++{ ++ ngx_http_v3_session_t *h3c; ++ ngx_http_connection_t *hc, *phc; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_core_loc_conf_t *clcf; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ hc = c->data; ++ ++ hc->ssl = 1; ++ ++ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); ++ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); ++ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); ++ ++ if (c->quic == NULL) { ++ if (ngx_http_v3_init_session(c) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ h3c = hc->v3_session; ++ ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); ++ ++ h3scf->quic.timeout = clcf->keepalive_timeout; ++ ngx_quic_run(c, &h3scf->quic); ++ return; ++ } ++ ++ phc = ngx_http_quic_get_connection(c); ++ ++ if (phc->ssl_servername) { ++ hc->ssl_servername = phc->ssl_servername; ++#if (NGX_PCRE) ++ hc->ssl_servername_regex = phc->ssl_servername_regex; ++#endif ++ hc->conf_ctx = phc->conf_ctx; ++ ++ ngx_set_connection_log(c, clcf->error_log); ++ } ++ ++ if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ++ ngx_http_v3_init_uni_stream(c); ++ ++ } else { ++ ngx_http_v3_init_request_stream(c); ++ } ++} ++ ++ ++ngx_int_t ++ngx_http_v3_init(ngx_connection_t *c) ++{ ++ unsigned int len; ++ const unsigned char *data; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); ++ ++ h3c = ngx_http_v3_get_session(c); ++ clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); ++ ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); ++ ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ if (h3scf->enable_hq) { ++ if (!h3scf->enable) { ++ h3c->hq = 1; ++ return NGX_OK; ++ } ++ ++ SSL_get0_alpn_selected(c->ssl->connection, &data, &len); ++ ++ if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1 ++ && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0) ++ { ++ h3c->hq = 1; ++ return NGX_OK; ++ } ++ } ++ ++ return ngx_http_v3_send_settings(c); ++} ++ ++ ++void ++ngx_http_v3_shutdown(ngx_connection_t *c) ++{ ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown"); ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (h3c == NULL) { ++ ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, ++ "connection shutdown"); ++ return; ++ } ++ ++ if (!h3c->goaway) { ++ h3c->goaway = 1; ++ ++ if (!h3c->hq) { ++ (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); ++ } ++ ++ ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, ++ "connection shutdown"); ++ } ++} ++ ++ ++static void ++ngx_http_v3_init_request_stream(ngx_connection_t *c) ++{ ++ uint64_t n; ++ ngx_event_t *rev; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_connection_t *hc; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_core_loc_conf_t *clcf; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); ++ ++#if (NGX_STAT_STUB) ++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1); ++#endif ++ ++ hc = c->data; ++ ++ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ n = c->quic->id >> 2; ++ ++ if (n >= clcf->keepalive_requests * 2) { ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "too many requests per connection"); ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (h3c->goaway) { ++ c->close = 1; ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ h3c->next_request_id = c->quic->id + 0x04; ++ ++ if (n + 1 == clcf->keepalive_requests ++ || ngx_current_msec - c->start_time > clcf->keepalive_time) ++ { ++ h3c->goaway = 1; ++ ++ if (!h3c->hq) { ++ if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ } ++ ++ ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, ++ "reached maximum number of requests"); ++ } ++ ++ cln = ngx_pool_cleanup_add(c->pool, 0); ++ if (cln == NULL) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ cln->handler = ngx_http_v3_cleanup_connection; ++ cln->data = c; ++ ++ h3c->nrequests++; ++ ++ if (h3c->keepalive.timer_set) { ++ ngx_del_timer(&h3c->keepalive); ++ } ++ ++ rev = c->read; ++ ++ if (!h3c->hq) { ++ rev->handler = ngx_http_v3_wait_request_handler; ++ c->write->handler = ngx_http_empty_handler; ++ } ++ ++ if (rev->ready) { ++ rev->handler(rev); ++ return; ++ } ++ ++ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ ngx_reusable_connection(c, 1); ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++} ++ ++ ++static void ++ngx_http_v3_wait_request_handler(ngx_event_t *rev) ++{ ++ size_t size; ++ ssize_t n; ++ ngx_buf_t *b; ++ ngx_connection_t *c; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_request_t *r; ++ ngx_http_connection_t *hc; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ c = rev->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler"); ++ ++ if (rev->timedout) { ++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ++ c->timedout = 1; ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ if (c->close) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ hc = c->data; ++ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); ++ ++ size = cscf->client_header_buffer_size; ++ ++ b = c->buffer; ++ ++ if (b == NULL) { ++ b = ngx_create_temp_buf(c->pool, size); ++ if (b == NULL) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ c->buffer = b; ++ ++ } else if (b->start == NULL) { ++ ++ b->start = ngx_palloc(c->pool, size); ++ if (b->start == NULL) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ b->pos = b->start; ++ b->last = b->start; ++ b->end = b->last + size; ++ } ++ ++ n = c->recv(c, b->last, size); ++ ++ if (n == NGX_AGAIN) { ++ ++ if (!rev->timer_set) { ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ ngx_reusable_connection(c, 1); ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ /* ++ * We are trying to not hold c->buffer's memory for an idle connection. ++ */ ++ ++ if (ngx_pfree(c->pool, b->start) == NGX_OK) { ++ b->start = NULL; ++ } ++ ++ return; ++ } ++ ++ if (n == NGX_ERROR) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ if (n == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client closed connection"); ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ b->last += n; ++ ++ c->log->action = "reading client request"; ++ ++ ngx_reusable_connection(c, 0); ++ ++ r = ngx_http_create_request(c); ++ if (r == NULL) { ++ ngx_http_close_connection(c); ++ return; ++ } ++ ++ r->http_version = NGX_HTTP_VERSION_30; ++ ++ r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); ++ if (r->v3_parse == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return; ++ } ++ ++ r->v3_parse->header_limit = cscf->large_client_header_buffers.size ++ * cscf->large_client_header_buffers.num; ++ ++ c->data = r; ++ c->requests = (c->quic->id >> 2) + 1; ++ ++ cln = ngx_pool_cleanup_add(r->pool, 0); ++ if (cln == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return; ++ } ++ ++ cln->handler = ngx_http_v3_cleanup_request; ++ cln->data = r; ++ ++ rev->handler = ngx_http_v3_process_request; ++ ngx_http_v3_process_request(rev); ++} ++ ++ ++void ++ngx_http_v3_reset_stream(ngx_connection_t *c) ++{ ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (h3scf->max_table_capacity > 0 && !c->read->eof && !h3c->hq ++ && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) ++ { ++ (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); ++ } ++ ++ if (c->timedout) { ++ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); ++ ++ } else if (c->close) { ++ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); ++ ++ } else if (c->requests == 0 || c->error) { ++ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR); ++ } ++} ++ ++ ++static void ++ngx_http_v3_cleanup_connection(void *data) ++{ ++ ngx_connection_t *c = data; ++ ++ ngx_http_v3_session_t *h3c; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (--h3c->nrequests == 0) { ++ clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); ++ ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); ++ } ++} ++ ++ ++static void ++ngx_http_v3_cleanup_request(void *data) ++{ ++ ngx_http_request_t *r = data; ++ ++ if (!r->response_sent) { ++ r->connection->error = 1; ++ } ++} ++ ++ ++static void ++ngx_http_v3_process_request(ngx_event_t *rev) ++{ ++ u_char *p; ++ ssize_t n; ++ ngx_buf_t *b; ++ ngx_int_t rc; ++ ngx_connection_t *c; ++ ngx_http_request_t *r; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_core_srv_conf_t *cscf; ++ ngx_http_v3_parse_headers_t *st; ++ ++ c = rev->data; ++ r = c->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request"); ++ ++ if (rev->timedout) { ++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ++ c->timedout = 1; ++ ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); ++ return; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ st = &r->v3_parse->headers; ++ ++ b = r->header_in; ++ ++ for ( ;; ) { ++ ++ if (b->pos == b->last) { ++ ++ if (rev->ready) { ++ n = c->recv(c, b->start, b->end - b->start); ++ ++ } else { ++ n = NGX_AGAIN; ++ } ++ ++ if (n == NGX_AGAIN) { ++ if (!rev->timer_set) { ++ cscf = ngx_http_get_module_srv_conf(r, ++ ngx_http_core_module); ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ } ++ ++ break; ++ } ++ ++ if (n == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client prematurely closed connection"); ++ } ++ ++ if (n == 0 || n == NGX_ERROR) { ++ c->error = 1; ++ c->log->action = "reading client request"; ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ break; ++ } ++ ++ b->pos = b->start; ++ b->last = b->start + n; ++ } ++ ++ p = b->pos; ++ ++ rc = ngx_http_v3_parse_headers(c, st, b); ++ ++ if (rc > 0) { ++ ngx_quic_reset_stream(c, rc); ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "client sent invalid header"); ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ break; ++ } ++ ++ if (rc == NGX_ERROR) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ break; ++ } ++ ++ r->request_length += b->pos - p; ++ h3c->total_bytes += b->pos - p; ++ ++ if (ngx_http_v3_check_flood(c) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_CLOSE); ++ break; ++ } ++ ++ if (rc == NGX_BUSY) { ++ if (rev->error) { ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ break; ++ } ++ ++ if (!rev->timer_set) { ++ cscf = ngx_http_get_module_srv_conf(r, ++ ngx_http_core_module); ++ ngx_add_timer(rev, cscf->client_header_timeout); ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ } ++ ++ break; ++ } ++ ++ if (rc == NGX_AGAIN) { ++ continue; ++ } ++ ++ /* rc == NGX_OK || rc == NGX_DONE */ ++ ++ h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, ++ &st->field_rep.field.name, ++ &st->field_rep.field.value); ++ ++ if (ngx_http_v3_process_header(r, &st->field_rep.field.name, ++ &st->field_rep.field.value) ++ != NGX_OK) ++ { ++ break; ++ } ++ ++ if (rc == NGX_DONE) { ++ if (ngx_http_v3_process_request_header(r) != NGX_OK) { ++ break; ++ } ++ ++ ngx_http_process_request(r); ++ break; ++ } ++ } ++ ++ ngx_http_run_posted_requests(c); ++ ++ return; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ size_t len; ++ ngx_table_elt_t *h; ++ ngx_http_header_t *hh; ++ ngx_http_core_srv_conf_t *cscf; ++ ngx_http_core_main_conf_t *cmcf; ++ ++ static ngx_str_t cookie = ngx_string("cookie"); ++ ++ len = name->len + value->len; ++ ++ if (len > r->v3_parse->header_limit) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent too large header"); ++ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); ++ return NGX_ERROR; ++ } ++ ++ r->v3_parse->header_limit -= len; ++ ++ if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++ } ++ ++ if (r->invalid_header) { ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ if (cscf->ignore_invalid_headers) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid header: \"%V\"", name); ++ ++ return NGX_OK; ++ } ++ } ++ ++ if (name->len && name->data[0] == ':') { ++ return ngx_http_v3_process_pseudo_header(r, name, value); ++ } ++ ++ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (name->len == cookie.len ++ && ngx_memcmp(name->data, cookie.data, cookie.len) == 0) ++ { ++ if (ngx_http_v3_cookie(r, value) != NGX_OK) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ } else { ++ h = ngx_list_push(&r->headers_in.headers); ++ if (h == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ h->key = *name; ++ h->value = *value; ++ h->lowcase_key = h->key.data; ++ h->hash = ngx_hash_key(h->key.data, h->key.len); ++ ++ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ++ ++ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, ++ h->lowcase_key, h->key.len); ++ ++ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 header: \"%V: %V\"", name, value); ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ u_char ch; ++ ngx_uint_t i; ++ ngx_http_core_srv_conf_t *cscf; ++ ++ r->invalid_header = 0; ++ ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ for (i = (name->data[0] == ':'); i != name->len; i++) { ++ ch = name->data[i]; ++ ++ if ((ch >= 'a' && ch <= 'z') ++ || (ch == '-') ++ || (ch >= '0' && ch <= '9') ++ || (ch == '_' && cscf->underscores_in_headers)) ++ { ++ continue; ++ } ++ ++ if (ch <= 0x20 || ch == 0x7f || ch == ':' ++ || (ch >= 'A' && ch <= 'Z')) ++ { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid header name: \"%V\"", name); ++ ++ return NGX_ERROR; ++ } ++ ++ r->invalid_header = 1; ++ } ++ ++ for (i = 0; i != value->len; i++) { ++ ch = value->data[i]; ++ ++ if (ch == '\0' || ch == LF || ch == CR) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent header \"%V\" with " ++ "invalid value: \"%V\"", name, value); ++ ++ return NGX_ERROR; ++ } ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ u_char ch, c; ++ ngx_uint_t i; ++ ++ if (r->request_line.len) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent out of order pseudo-headers"); ++ goto failed; ++ } ++ ++ if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { ++ ++ if (r->method_name.len) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":method\" header"); ++ goto failed; ++ } ++ ++ if (value->len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent empty \":method\" header"); ++ goto failed; ++ } ++ ++ r->method_name = *value; ++ ++ for (i = 0; i < sizeof(ngx_http_v3_methods) ++ / sizeof(ngx_http_v3_methods[0]); i++) ++ { ++ if (value->len == ngx_http_v3_methods[i].name.len ++ && ngx_strncmp(value->data, ++ ngx_http_v3_methods[i].name.data, value->len) ++ == 0) ++ { ++ r->method = ngx_http_v3_methods[i].method; ++ break; ++ } ++ } ++ ++ for (i = 0; i < value->len; i++) { ++ ch = value->data[i]; ++ ++ if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid method: \"%V\"", value); ++ goto failed; ++ } ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 method \"%V\" %ui", value, r->method); ++ return NGX_OK; ++ } ++ ++ if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { ++ ++ if (r->uri_start) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":path\" header"); ++ goto failed; ++ } ++ ++ if (value->len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent empty \":path\" header"); ++ goto failed; ++ } ++ ++ r->uri_start = value->data; ++ r->uri_end = value->data + value->len; ++ ++ if (ngx_http_parse_uri(r) != NGX_OK) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid \":path\" header: \"%V\"", ++ value); ++ goto failed; ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 path \"%V\"", value); ++ return NGX_OK; ++ } ++ ++ if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { ++ ++ if (r->schema.len) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":scheme\" header"); ++ goto failed; ++ } ++ ++ if (value->len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent empty \":scheme\" header"); ++ goto failed; ++ } ++ ++ for (i = 0; i < value->len; i++) { ++ ch = value->data[i]; ++ ++ c = (u_char) (ch | 0x20); ++ if (c >= 'a' && c <= 'z') { ++ continue; ++ } ++ ++ if (((ch >= '0' && ch <= '9') ++ || ch == '+' || ch == '-' || ch == '.') ++ && i > 0) ++ { ++ continue; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid \":scheme\" header: \"%V\"", ++ value); ++ goto failed; ++ } ++ ++ r->schema = *value; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 schema \"%V\"", value); ++ return NGX_OK; ++ } ++ ++ if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { ++ ++ if (r->host_start) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent duplicate \":authority\" header"); ++ goto failed; ++ } ++ ++ r->host_start = value->data; ++ r->host_end = value->data + value->len; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 authority \"%V\"", value); ++ return NGX_OK; ++ } ++ ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent unknown pseudo-header \"%V\"", name); ++ ++failed: ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) ++{ ++ size_t len; ++ u_char *p; ++ ngx_int_t rc; ++ ngx_str_t host; ++ ++ if (r->request_line.len) { ++ return NGX_OK; ++ } ++ ++ if (r->method_name.len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent no \":method\" header"); ++ goto failed; ++ } ++ ++ if (r->schema.len == 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent no \":scheme\" header"); ++ goto failed; ++ } ++ ++ if (r->uri_start == NULL) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent no \":path\" header"); ++ goto failed; ++ } ++ ++ len = r->method_name.len + 1 ++ + (r->uri_end - r->uri_start) + 1 ++ + sizeof("HTTP/3.0") - 1; ++ ++ p = ngx_pnalloc(r->pool, len); ++ if (p == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ r->request_line.data = p; ++ ++ p = ngx_cpymem(p, r->method_name.data, r->method_name.len); ++ *p++ = ' '; ++ p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); ++ *p++ = ' '; ++ p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); ++ ++ r->request_line.len = p - r->request_line.data; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 request line: \"%V\"", &r->request_line); ++ ++ ngx_str_set(&r->http_protocol, "HTTP/3.0"); ++ ++ if (ngx_http_process_request_uri(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (r->host_end) { ++ ++ host.len = r->host_end - r->host_start; ++ host.data = r->host_start; ++ ++ rc = ngx_http_validate_host(&host, r->pool, 0); ++ ++ if (rc == NGX_DECLINED) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent invalid host in request line"); ++ goto failed; ++ } ++ ++ if (rc == NGX_ERROR) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { ++ return NGX_ERROR; ++ } ++ ++ r->headers_in.server = host; ++ } ++ ++ if (ngx_list_init(&r->headers_in.headers, r->pool, 20, ++ sizeof(ngx_table_elt_t)) ++ != NGX_OK) ++ { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_process_request_header(ngx_http_request_t *r) ++{ ++ ssize_t n; ++ ngx_buf_t *b; ++ ngx_connection_t *c; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ c = r->connection; ++ ++ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); ++ ++ if (!r->http_connection->addr_conf->http3) { ++ if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client attempted to request the server name " ++ "for which the negotiated protocol is disabled"); ++ ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); ++ return NGX_ERROR; ++ } ++ } ++ ++ if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ if (r->headers_in.server.len == 0) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client sent neither \":authority\" nor \"Host\" header"); ++ goto failed; ++ } ++ ++ if (r->headers_in.host) { ++ if (r->headers_in.host->value.len != r->headers_in.server.len ++ || ngx_memcmp(r->headers_in.host->value.data, ++ r->headers_in.server.data, ++ r->headers_in.server.len) ++ != 0) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client sent \":authority\" and \"Host\" headers " ++ "with different values"); ++ goto failed; ++ } ++ } ++ ++ if (r->headers_in.content_length) { ++ r->headers_in.content_length_n = ++ ngx_atoof(r->headers_in.content_length->value.data, ++ r->headers_in.content_length->value.len); ++ ++ if (r->headers_in.content_length_n == NGX_ERROR) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client sent invalid \"Content-Length\" header"); ++ goto failed; ++ } ++ ++ } else { ++ b = r->header_in; ++ n = b->last - b->pos; ++ ++ if (n == 0) { ++ n = c->recv(c, b->start, b->end - b->start); ++ ++ if (n == NGX_ERROR) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ if (n > 0) { ++ b->pos = b->start; ++ b->last = b->start + n; ++ } ++ } ++ ++ if (n != 0) { ++ r->headers_in.chunked = 1; ++ } ++ } ++ ++ if (r->method == NGX_HTTP_CONNECT) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); ++ ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); ++ return NGX_ERROR; ++ } ++ ++ if (r->method == NGX_HTTP_TRACE) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); ++ ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); ++ return NGX_ERROR; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value) ++{ ++ ngx_str_t *val; ++ ngx_array_t *cookies; ++ ++ cookies = r->v3_parse->cookies; ++ ++ if (cookies == NULL) { ++ cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); ++ if (cookies == NULL) { ++ return NGX_ERROR; ++ } ++ ++ r->v3_parse->cookies = cookies; ++ } ++ ++ val = ngx_array_push(cookies); ++ if (val == NULL) { ++ return NGX_ERROR; ++ } ++ ++ *val = *value; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) ++{ ++ u_char *buf, *p, *end; ++ size_t len; ++ ngx_str_t *vals; ++ ngx_uint_t i; ++ ngx_array_t *cookies; ++ ngx_table_elt_t *h; ++ ngx_http_header_t *hh; ++ ngx_http_core_main_conf_t *cmcf; ++ ++ static ngx_str_t cookie = ngx_string("cookie"); ++ ++ cookies = r->v3_parse->cookies; ++ ++ if (cookies == NULL) { ++ return NGX_OK; ++ } ++ ++ vals = cookies->elts; ++ ++ i = 0; ++ len = 0; ++ ++ do { ++ len += vals[i].len + 2; ++ } while (++i != cookies->nelts); ++ ++ len -= 2; ++ ++ buf = ngx_pnalloc(r->pool, len + 1); ++ if (buf == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ p = buf; ++ end = buf + len; ++ ++ for (i = 0; /* void */ ; i++) { ++ ++ p = ngx_cpymem(p, vals[i].data, vals[i].len); ++ ++ if (p == end) { ++ *p = '\0'; ++ break; ++ } ++ ++ *p++ = ';'; *p++ = ' '; ++ } ++ ++ h = ngx_list_push(&r->headers_in.headers); ++ if (h == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( ++ ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); ++ ++ h->key.len = cookie.len; ++ h->key.data = cookie.data; ++ ++ h->value.len = len; ++ h->value.data = buf; ++ ++ h->lowcase_key = cookie.data; ++ ++ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); ++ ++ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, ++ h->lowcase_key, h->key.len); ++ ++ if (hh == NULL) { ++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); ++ return NGX_ERROR; ++ } ++ ++ if (hh->handler(r, h, hh->offset) != NGX_OK) { ++ /* ++ * request has been finalized already ++ * in ngx_http_process_multi_header_lines() ++ */ ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_read_request_body(ngx_http_request_t *r) ++{ ++ size_t preread; ++ ngx_int_t rc; ++ ngx_chain_t *cl, out; ++ ngx_http_request_body_t *rb; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ rb = r->request_body; ++ ++ preread = r->header_in->last - r->header_in->pos; ++ ++ if (preread) { ++ ++ /* there is the pre-read part of the request body */ ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 client request body preread %uz", preread); ++ ++ out.buf = r->header_in; ++ out.next = NULL; ++ cl = &out; ++ ++ } else { ++ cl = NULL; ++ } ++ ++ rc = ngx_http_v3_request_body_filter(r, cl); ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ if (rb->rest == 0 && rb->last_saved) { ++ /* the whole request body was pre-read */ ++ r->request_body_no_buffering = 0; ++ rb->post_handler(r); ++ return NGX_OK; ++ } ++ ++ if (rb->rest < 0) { ++ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, ++ "negative request body rest"); ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ++ ++ rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size); ++ if (rb->buf == NULL) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ r->read_event_handler = ngx_http_v3_read_client_request_body_handler; ++ r->write_event_handler = ngx_http_request_empty_handler; ++ ++ return ngx_http_v3_do_read_client_request_body(r); ++} ++ ++ ++static void ++ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) ++{ ++ ngx_int_t rc; ++ ++ if (r->connection->read->timedout) { ++ r->connection->timedout = 1; ++ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); ++ return; ++ } ++ ++ rc = ngx_http_v3_do_read_client_request_body(r); ++ ++ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { ++ ngx_http_finalize_request(r, rc); ++ } ++} ++ ++ ++ngx_int_t ++ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) ++{ ++ ngx_int_t rc; ++ ++ if (r->connection->read->timedout) { ++ r->connection->timedout = 1; ++ return NGX_HTTP_REQUEST_TIME_OUT; ++ } ++ ++ rc = ngx_http_v3_do_read_client_request_body(r); ++ ++ if (rc == NGX_OK) { ++ r->reading_body = 0; ++ } ++ ++ return rc; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) ++{ ++ off_t rest; ++ size_t size; ++ ssize_t n; ++ ngx_int_t rc; ++ ngx_uint_t flush; ++ ngx_chain_t out; ++ ngx_connection_t *c; ++ ngx_http_request_body_t *rb; ++ ngx_http_core_loc_conf_t *clcf; ++ ++ c = r->connection; ++ rb = r->request_body; ++ flush = 1; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 read client request body"); ++ ++ for ( ;; ) { ++ for ( ;; ) { ++ if (rb->rest == 0) { ++ break; ++ } ++ ++ if (rb->buf->last == rb->buf->end) { ++ ++ /* update chains */ ++ ++ rc = ngx_http_v3_request_body_filter(r, NULL); ++ ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ if (rb->busy != NULL) { ++ if (r->request_body_no_buffering) { ++ if (c->read->timer_set) { ++ ngx_del_timer(c->read); ++ } ++ ++ if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ return NGX_AGAIN; ++ } ++ ++ if (rb->filter_need_buffering) { ++ clcf = ngx_http_get_module_loc_conf(r, ++ ngx_http_core_module); ++ ngx_add_timer(c->read, clcf->client_body_timeout); ++ ++ if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ return NGX_AGAIN; ++ } ++ ++ ngx_log_error(NGX_LOG_ALERT, c->log, 0, ++ "busy buffers after request body flush"); ++ ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ flush = 0; ++ rb->buf->pos = rb->buf->start; ++ rb->buf->last = rb->buf->start; ++ } ++ ++ size = rb->buf->end - rb->buf->last; ++ rest = rb->rest - (rb->buf->last - rb->buf->pos); ++ ++ if ((off_t) size > rest) { ++ size = (size_t) rest; ++ } ++ ++ if (size == 0) { ++ break; ++ } ++ ++ n = c->recv(c, rb->buf->last, size); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 client request body recv %z", n); ++ ++ if (n == NGX_AGAIN) { ++ break; ++ } ++ ++ if (n == 0) { ++ rb->buf->last_buf = 1; ++ } ++ ++ if (n == NGX_ERROR) { ++ c->error = 1; ++ return NGX_HTTP_BAD_REQUEST; ++ } ++ ++ rb->buf->last += n; ++ ++ /* pass buffer to request body filter chain */ ++ ++ flush = 0; ++ out.buf = rb->buf; ++ out.next = NULL; ++ ++ rc = ngx_http_v3_request_body_filter(r, &out); ++ ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ ++ if (rb->rest == 0) { ++ break; ++ } ++ ++ if (rb->buf->last < rb->buf->end) { ++ break; ++ } ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 client request body rest %O", rb->rest); ++ ++ if (flush) { ++ rc = ngx_http_v3_request_body_filter(r, NULL); ++ ++ if (rc != NGX_OK) { ++ return rc; ++ } ++ } ++ ++ if (rb->rest == 0 && rb->last_saved) { ++ break; ++ } ++ ++ if (!c->read->ready || rb->rest == 0) { ++ ++ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ++ ngx_add_timer(c->read, clcf->client_body_timeout); ++ ++ if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ return NGX_AGAIN; ++ } ++ } ++ ++ if (c->read->timer_set) { ++ ngx_del_timer(c->read); ++ } ++ ++ if (!r->request_body_no_buffering) { ++ r->read_event_handler = ngx_http_block_reading; ++ rb->post_handler(r); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ++{ ++ off_t max; ++ size_t size; ++ u_char *p; ++ ngx_int_t rc; ++ ngx_buf_t *b; ++ ngx_uint_t last; ++ ngx_chain_t *cl, *out, *tl, **ll; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_request_body_t *rb; ++ ngx_http_core_loc_conf_t *clcf; ++ ngx_http_core_srv_conf_t *cscf; ++ ngx_http_v3_parse_data_t *st; ++ ++ rb = r->request_body; ++ st = &r->v3_parse->body; ++ ++ h3c = ngx_http_v3_get_session(r->connection); ++ ++ if (rb->rest == -1) { ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 request body filter"); ++ ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ rb->rest = cscf->large_client_header_buffers.size; ++ } ++ ++ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ++ ++ max = r->headers_in.content_length_n; ++ ++ if (max == -1 && clcf->client_max_body_size) { ++ max = clcf->client_max_body_size; ++ } ++ ++ out = NULL; ++ ll = &out; ++ last = 0; ++ ++ for (cl = in; cl; cl = cl->next) { ++ ++ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, ++ "http3 body buf " ++ "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O", ++ cl->buf->temporary, cl->buf->in_file, ++ cl->buf->start, cl->buf->pos, ++ cl->buf->last - cl->buf->pos, ++ cl->buf->file_pos, ++ cl->buf->file_last - cl->buf->file_pos); ++ ++ if (cl->buf->last_buf) { ++ last = 1; ++ } ++ ++ b = NULL; ++ ++ while (cl->buf->pos < cl->buf->last) { ++ ++ if (st->length == 0) { ++ p = cl->buf->pos; ++ ++ rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); ++ ++ r->request_length += cl->buf->pos - p; ++ h3c->total_bytes += cl->buf->pos - p; ++ ++ if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { ++ return NGX_HTTP_CLOSE; ++ } ++ ++ if (rc == NGX_AGAIN) { ++ continue; ++ } ++ ++ if (rc == NGX_DONE) { ++ last = 1; ++ goto done; ++ } ++ ++ if (rc > 0) { ++ ngx_quic_reset_stream(r->connection, rc); ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "client sent invalid body"); ++ return NGX_HTTP_BAD_REQUEST; ++ } ++ ++ if (rc == NGX_ERROR) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ /* rc == NGX_OK */ ++ ++ if (max != -1 && (uint64_t) (max - rb->received) < st->length) { ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ++ "client intended to send too large " ++ "body: %O+%ui bytes", ++ rb->received, st->length); ++ ++ return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; ++ } ++ ++ continue; ++ } ++ ++ if (b ++ && st->length <= 128 ++ && (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length) ++ { ++ rb->received += st->length; ++ r->request_length += st->length; ++ h3c->total_bytes += st->length; ++ h3c->payload_bytes += st->length; ++ ++ if (st->length < 8) { ++ ++ while (st->length) { ++ *b->last++ = *cl->buf->pos++; ++ st->length--; ++ } ++ ++ } else { ++ ngx_memmove(b->last, cl->buf->pos, st->length); ++ b->last += st->length; ++ cl->buf->pos += st->length; ++ st->length = 0; ++ } ++ ++ continue; ++ } ++ ++ tl = ngx_chain_get_free_buf(r->pool, &rb->free); ++ if (tl == NULL) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ b = tl->buf; ++ ++ ngx_memzero(b, sizeof(ngx_buf_t)); ++ ++ b->temporary = 1; ++ b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; ++ b->start = cl->buf->pos; ++ b->pos = cl->buf->pos; ++ b->last = cl->buf->last; ++ b->end = cl->buf->end; ++ b->flush = r->request_body_no_buffering; ++ ++ *ll = tl; ++ ll = &tl->next; ++ ++ size = cl->buf->last - cl->buf->pos; ++ ++ if (size > st->length) { ++ cl->buf->pos += (size_t) st->length; ++ rb->received += st->length; ++ r->request_length += st->length; ++ h3c->total_bytes += st->length; ++ h3c->payload_bytes += st->length; ++ st->length = 0; ++ ++ } else { ++ st->length -= size; ++ rb->received += size; ++ r->request_length += size; ++ h3c->total_bytes += size; ++ h3c->payload_bytes += size; ++ cl->buf->pos = cl->buf->last; ++ } ++ ++ b->last = cl->buf->pos; ++ } ++ } ++ ++done: ++ ++ if (last) { ++ ++ if (st->length > 0) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client prematurely closed stream"); ++ r->connection->error = 1; ++ return NGX_HTTP_BAD_REQUEST; ++ } ++ ++ if (r->headers_in.content_length_n == -1) { ++ r->headers_in.content_length_n = rb->received; ++ ++ } else if (r->headers_in.content_length_n != rb->received) { ++ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ++ "client sent less body data than expected: " ++ "%O out of %O bytes of request body received", ++ rb->received, r->headers_in.content_length_n); ++ return NGX_HTTP_BAD_REQUEST; ++ } ++ ++ rb->rest = 0; ++ ++ tl = ngx_chain_get_free_buf(r->pool, &rb->free); ++ if (tl == NULL) { ++ return NGX_HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ b = tl->buf; ++ ++ ngx_memzero(b, sizeof(ngx_buf_t)); ++ ++ b->last_buf = 1; ++ ++ *ll = tl; ++ ++ } else { ++ ++ /* set rb->rest, amount of data we want to see next time */ ++ ++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); ++ ++ rb->rest = (off_t) cscf->large_client_header_buffers.size; ++ } ++ ++ rc = ngx_http_top_request_body_filter(r, out); ++ ++ ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, ++ (ngx_buf_tag_t) &ngx_http_read_client_request_body); ++ ++ return rc; ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3_table.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_table.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,715 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) ++ ++ ++static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target); ++static void ngx_http_v3_unblock(void *data); ++static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); ++ ++ ++typedef struct { ++ ngx_queue_t queue; ++ ngx_connection_t *connection; ++ ngx_uint_t *nblocked; ++} ngx_http_v3_block_t; ++ ++ ++static ngx_http_v3_field_t ngx_http_v3_static_table[] = { ++ ++ { ngx_string(":authority"), ngx_string("") }, ++ { ngx_string(":path"), ngx_string("/") }, ++ { ngx_string("age"), ngx_string("0") }, ++ { ngx_string("content-disposition"), ngx_string("") }, ++ { ngx_string("content-length"), ngx_string("0") }, ++ { ngx_string("cookie"), ngx_string("") }, ++ { ngx_string("date"), ngx_string("") }, ++ { ngx_string("etag"), ngx_string("") }, ++ { ngx_string("if-modified-since"), ngx_string("") }, ++ { ngx_string("if-none-match"), ngx_string("") }, ++ { ngx_string("last-modified"), ngx_string("") }, ++ { ngx_string("link"), ngx_string("") }, ++ { ngx_string("location"), ngx_string("") }, ++ { ngx_string("referer"), ngx_string("") }, ++ { ngx_string("set-cookie"), ngx_string("") }, ++ { ngx_string(":method"), ngx_string("CONNECT") }, ++ { ngx_string(":method"), ngx_string("DELETE") }, ++ { ngx_string(":method"), ngx_string("GET") }, ++ { ngx_string(":method"), ngx_string("HEAD") }, ++ { ngx_string(":method"), ngx_string("OPTIONS") }, ++ { ngx_string(":method"), ngx_string("POST") }, ++ { ngx_string(":method"), ngx_string("PUT") }, ++ { ngx_string(":scheme"), ngx_string("http") }, ++ { ngx_string(":scheme"), ngx_string("https") }, ++ { ngx_string(":status"), ngx_string("103") }, ++ { ngx_string(":status"), ngx_string("200") }, ++ { ngx_string(":status"), ngx_string("304") }, ++ { ngx_string(":status"), ngx_string("404") }, ++ { ngx_string(":status"), ngx_string("503") }, ++ { ngx_string("accept"), ngx_string("*/*") }, ++ { ngx_string("accept"), ++ ngx_string("application/dns-message") }, ++ { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, ++ { ngx_string("accept-ranges"), ngx_string("bytes") }, ++ { ngx_string("access-control-allow-headers"), ++ ngx_string("cache-control") }, ++ { ngx_string("access-control-allow-headers"), ++ ngx_string("content-type") }, ++ { ngx_string("access-control-allow-origin"), ++ ngx_string("*") }, ++ { ngx_string("cache-control"), ngx_string("max-age=0") }, ++ { ngx_string("cache-control"), ngx_string("max-age=2592000") }, ++ { ngx_string("cache-control"), ngx_string("max-age=604800") }, ++ { ngx_string("cache-control"), ngx_string("no-cache") }, ++ { ngx_string("cache-control"), ngx_string("no-store") }, ++ { ngx_string("cache-control"), ++ ngx_string("public, max-age=31536000") }, ++ { ngx_string("content-encoding"), ngx_string("br") }, ++ { ngx_string("content-encoding"), ngx_string("gzip") }, ++ { ngx_string("content-type"), ++ ngx_string("application/dns-message") }, ++ { ngx_string("content-type"), ++ ngx_string("application/javascript") }, ++ { ngx_string("content-type"), ngx_string("application/json") }, ++ { ngx_string("content-type"), ++ ngx_string("application/x-www-form-urlencoded") }, ++ { ngx_string("content-type"), ngx_string("image/gif") }, ++ { ngx_string("content-type"), ngx_string("image/jpeg") }, ++ { ngx_string("content-type"), ngx_string("image/png") }, ++ { ngx_string("content-type"), ngx_string("text/css") }, ++ { ngx_string("content-type"), ++ ngx_string("text/html;charset=utf-8") }, ++ { ngx_string("content-type"), ngx_string("text/plain") }, ++ { ngx_string("content-type"), ++ ngx_string("text/plain;charset=utf-8") }, ++ { ngx_string("range"), ngx_string("bytes=0-") }, ++ { ngx_string("strict-transport-security"), ++ ngx_string("max-age=31536000") }, ++ { ngx_string("strict-transport-security"), ++ ngx_string("max-age=31536000;includesubdomains") }, ++ { ngx_string("strict-transport-security"), ++ ngx_string("max-age=31536000;includesubdomains;preload") }, ++ { ngx_string("vary"), ngx_string("accept-encoding") }, ++ { ngx_string("vary"), ngx_string("origin") }, ++ { ngx_string("x-content-type-options"), ++ ngx_string("nosniff") }, ++ { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, ++ { ngx_string(":status"), ngx_string("100") }, ++ { ngx_string(":status"), ngx_string("204") }, ++ { ngx_string(":status"), ngx_string("206") }, ++ { ngx_string(":status"), ngx_string("302") }, ++ { ngx_string(":status"), ngx_string("400") }, ++ { ngx_string(":status"), ngx_string("403") }, ++ { ngx_string(":status"), ngx_string("421") }, ++ { ngx_string(":status"), ngx_string("425") }, ++ { ngx_string(":status"), ngx_string("500") }, ++ { ngx_string("accept-language"), ngx_string("") }, ++ { ngx_string("access-control-allow-credentials"), ++ ngx_string("FALSE") }, ++ { ngx_string("access-control-allow-credentials"), ++ ngx_string("TRUE") }, ++ { ngx_string("access-control-allow-headers"), ++ ngx_string("*") }, ++ { ngx_string("access-control-allow-methods"), ++ ngx_string("get") }, ++ { ngx_string("access-control-allow-methods"), ++ ngx_string("get, post, options") }, ++ { ngx_string("access-control-allow-methods"), ++ ngx_string("options") }, ++ { ngx_string("access-control-expose-headers"), ++ ngx_string("content-length") }, ++ { ngx_string("access-control-request-headers"), ++ ngx_string("content-type") }, ++ { ngx_string("access-control-request-method"), ++ ngx_string("get") }, ++ { ngx_string("access-control-request-method"), ++ ngx_string("post") }, ++ { ngx_string("alt-svc"), ngx_string("clear") }, ++ { ngx_string("authorization"), ngx_string("") }, ++ { ngx_string("content-security-policy"), ++ ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, ++ { ngx_string("early-data"), ngx_string("1") }, ++ { ngx_string("expect-ct"), ngx_string("") }, ++ { ngx_string("forwarded"), ngx_string("") }, ++ { ngx_string("if-range"), ngx_string("") }, ++ { ngx_string("origin"), ngx_string("") }, ++ { ngx_string("purpose"), ngx_string("prefetch") }, ++ { ngx_string("server"), ngx_string("") }, ++ { ngx_string("timing-allow-origin"), ngx_string("*") }, ++ { ngx_string("upgrade-insecure-requests"), ++ ngx_string("1") }, ++ { ngx_string("user-agent"), ngx_string("") }, ++ { ngx_string("x-forwarded-for"), ngx_string("") }, ++ { ngx_string("x-frame-options"), ngx_string("deny") }, ++ { ngx_string("x-frame-options"), ngx_string("sameorigin") } ++}; ++ ++ ++ngx_int_t ++ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ++ ngx_uint_t index, ngx_str_t *value) ++{ ++ ngx_str_t name; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ if (dynamic) { ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 ref insert dynamic[%ui] \"%V\"", index, value); ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (dt->base + dt->nelts <= index) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ index = dt->base + dt->nelts - 1 - index; ++ ++ if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ } else { ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 ref insert static[%ui] \"%V\"", index, value); ++ ++ if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ } ++ ++ return ngx_http_v3_insert(c, &name, value); ++} ++ ++ ++ngx_int_t ++ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) ++{ ++ u_char *p; ++ size_t size; ++ ngx_http_v3_field_t *field; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ size = ngx_http_v3_table_entry_size(name, value); ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (size > dt->capacity) { ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "not enough dynamic table capacity"); ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 insert [%ui] \"%V\":\"%V\", size:%uz", ++ dt->base + dt->nelts, name, value, size); ++ ++ p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, ++ c->log); ++ if (p == NULL) { ++ return NGX_ERROR; ++ } ++ ++ field = (ngx_http_v3_field_t *) p; ++ ++ field->name.data = p + sizeof(ngx_http_v3_field_t); ++ field->name.len = name->len; ++ field->value.data = ngx_cpymem(field->name.data, name->data, name->len); ++ field->value.len = value->len; ++ ngx_memcpy(field->value.data, value->data, value->len); ++ ++ dt->elts[dt->nelts++] = field; ++ dt->size += size; ++ ++ dt->insert_count++; ++ ++ if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ ngx_post_event(&dt->send_insert_count, &ngx_posted_events); ++ ++ if (ngx_http_v3_new_entry(c) != NGX_OK) { ++ return NGX_ERROR; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev) ++{ ++ ngx_connection_t *c; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ c = ev->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 inc insert count handler"); ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (dt->insert_count > dt->ack_insert_count) { ++ if (ngx_http_v3_send_inc_insert_count(c, ++ dt->insert_count - dt->ack_insert_count) ++ != NGX_OK) ++ { ++ return; ++ } ++ ++ dt->ack_insert_count = dt->insert_count; ++ } ++} ++ ++ ++ngx_int_t ++ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ++{ ++ ngx_uint_t max, prev_max; ++ ngx_http_v3_field_t **elts; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 set capacity %ui", capacity); ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ if (capacity > h3scf->max_table_capacity) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client exceeded http3_max_table_capacity limit"); ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ if (ngx_http_v3_evict(c, capacity) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ dt = &h3c->table; ++ max = capacity / 32; ++ prev_max = dt->capacity / 32; ++ ++ if (max > prev_max) { ++ elts = ngx_alloc(max * sizeof(void *), c->log); ++ if (elts == NULL) { ++ return NGX_ERROR; ++ } ++ ++ if (dt->elts) { ++ ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); ++ ngx_free(dt->elts); ++ } ++ ++ dt->elts = elts; ++ } ++ ++ dt->capacity = capacity; ++ ++ return NGX_OK; ++} ++ ++ ++void ++ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) ++{ ++ ngx_uint_t n; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ dt = &h3c->table; ++ ++ if (dt->elts == NULL) { ++ return; ++ } ++ ++ for (n = 0; n < dt->nelts; n++) { ++ ngx_free(dt->elts[n]); ++ } ++ ++ ngx_free(dt->elts); ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_evict(ngx_connection_t *c, size_t target) ++{ ++ size_t size; ++ ngx_uint_t n; ++ ngx_http_v3_field_t *field; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ n = 0; ++ ++ while (dt->size > target) { ++ field = dt->elts[n++]; ++ size = ngx_http_v3_table_entry_size(&field->name, &field->value); ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 evict [%ui] \"%V\":\"%V\" size:%uz", ++ dt->base, &field->name, &field->value, size); ++ ++ ngx_free(field); ++ dt->size -= size; ++ } ++ ++ if (n) { ++ dt->nelts -= n; ++ dt->base += n; ++ ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) ++{ ++ ngx_str_t name, value; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (dt->base + dt->nelts <= index) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ index = dt->base + dt->nelts - 1 - index; ++ ++ if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { ++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; ++ } ++ ++ return ngx_http_v3_insert(c, &name, &value); ++} ++ ++ ++ngx_int_t ++ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 ack section %ui", stream_id); ++ ++ /* we do not use dynamic tables */ ++ ++ return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ++{ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 increment insert count %ui", inc); ++ ++ /* we do not use dynamic tables */ ++ ++ return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, ++ ngx_str_t *name, ngx_str_t *value) ++{ ++ ngx_uint_t nelts; ++ ngx_http_v3_field_t *field; ++ ++ nelts = sizeof(ngx_http_v3_static_table) ++ / sizeof(ngx_http_v3_static_table[0]); ++ ++ if (index >= nelts) { ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 static[%ui] lookup out of bounds: %ui", ++ index, nelts); ++ return NGX_ERROR; ++ } ++ ++ field = &ngx_http_v3_static_table[index]; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 static[%ui] lookup \"%V\":\"%V\"", ++ index, &field->name, &field->value); ++ ++ if (name) { ++ *name = field->name; ++ } ++ ++ if (value) { ++ *value = field->value; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ++ ngx_str_t *value) ++{ ++ ngx_http_v3_field_t *field; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (index < dt->base || index - dt->base >= dt->nelts) { ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", ++ index, dt->base, dt->base + dt->nelts); ++ return NGX_ERROR; ++ } ++ ++ field = dt->elts[index - dt->base]; ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 dynamic[%ui] lookup \"%V\":\"%V\"", ++ index, &field->name, &field->value); ++ ++ if (name) { ++ *name = field->name; ++ } ++ ++ if (value) { ++ *value = field->value; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) ++{ ++ ngx_uint_t max_entries, full_range, max_value, ++ max_wrapped, req_insert_count; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ /* QPACK 4.5.1.1. Required Insert Count */ ++ ++ if (*insert_count == 0) { ++ return NGX_OK; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ max_entries = h3scf->max_table_capacity / 32; ++ full_range = 2 * max_entries; ++ ++ if (*insert_count > full_range) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ max_value = dt->base + dt->nelts + max_entries; ++ max_wrapped = (max_value / full_range) * full_range; ++ req_insert_count = max_wrapped + *insert_count - 1; ++ ++ if (req_insert_count > max_value) { ++ if (req_insert_count <= full_range) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ req_insert_count -= full_range; ++ } ++ ++ if (req_insert_count == 0) { ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 decode insert_count %ui -> %ui", ++ *insert_count, req_insert_count); ++ ++ *insert_count = req_insert_count; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) ++{ ++ size_t n; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_v3_block_t *block; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ n = dt->base + dt->nelts; ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 check insert count req:%ui, have:%ui", ++ insert_count, n); ++ ++ if (n >= insert_count) { ++ return NGX_OK; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); ++ ++ block = NULL; ++ ++ for (cln = c->pool->cleanup; cln; cln = cln->next) { ++ if (cln->handler == ngx_http_v3_unblock) { ++ block = cln->data; ++ break; ++ } ++ } ++ ++ if (block == NULL) { ++ cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); ++ if (cln == NULL) { ++ return NGX_ERROR; ++ } ++ ++ cln->handler = ngx_http_v3_unblock; ++ ++ block = cln->data; ++ block->queue.prev = NULL; ++ block->connection = c; ++ block->nblocked = &h3c->nblocked; ++ } ++ ++ if (block->queue.prev == NULL) { ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ if (h3c->nblocked == h3scf->max_blocked_streams) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, ++ "client exceeded http3_max_blocked_streams limit"); ++ ++ ngx_http_v3_finalize_connection(c, ++ NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, ++ "too many blocked streams"); ++ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; ++ } ++ ++ h3c->nblocked++; ++ ngx_queue_insert_tail(&h3c->blocked, &block->queue); ++ } ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 blocked:%ui", h3c->nblocked); ++ ++ return NGX_BUSY; ++} ++ ++ ++void ++ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count) ++{ ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_dynamic_table_t *dt; ++ ++ h3c = ngx_http_v3_get_session(c); ++ dt = &h3c->table; ++ ++ if (dt->ack_insert_count < insert_count) { ++ dt->ack_insert_count = insert_count; ++ } ++} ++ ++ ++static void ++ngx_http_v3_unblock(void *data) ++{ ++ ngx_http_v3_block_t *block = data; ++ ++ if (block->queue.prev) { ++ ngx_queue_remove(&block->queue); ++ block->queue.prev = NULL; ++ (*block->nblocked)--; ++ } ++} ++ ++ ++static ngx_int_t ++ngx_http_v3_new_entry(ngx_connection_t *c) ++{ ++ ngx_queue_t *q; ++ ngx_connection_t *bc; ++ ngx_http_v3_block_t *block; ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 new dynamic entry, blocked:%ui", h3c->nblocked); ++ ++ while (!ngx_queue_empty(&h3c->blocked)) { ++ q = ngx_queue_head(&h3c->blocked); ++ block = (ngx_http_v3_block_t *) q; ++ bc = block->connection; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream"); ++ ++ ngx_http_v3_unblock(block); ++ ngx_post_event(bc->read, &ngx_posted_events); ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) ++{ ++ switch (id) { ++ ++ case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY: ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); ++ break; ++ ++ case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE: ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL", ++ value); ++ break; ++ ++ case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 param QPACK_BLOCKED_STREAMS:%uL", value); ++ break; ++ ++ default: ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 param #%uL:%uL", id, value); ++ } ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3_table.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_table.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,58 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_ ++#define _NGX_HTTP_V3_TABLE_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++ ++typedef struct { ++ ngx_str_t name; ++ ngx_str_t value; ++} ngx_http_v3_field_t; ++ ++ ++typedef struct { ++ ngx_http_v3_field_t **elts; ++ ngx_uint_t nelts; ++ ngx_uint_t base; ++ size_t size; ++ size_t capacity; ++ uint64_t insert_count; ++ uint64_t ack_insert_count; ++ ngx_event_t send_insert_count; ++} ngx_http_v3_dynamic_table_t; ++ ++ ++void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev); ++void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); ++ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ++ ngx_uint_t index, ngx_str_t *value); ++ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ++ ngx_str_t *value); ++ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); ++ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); ++ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); ++ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); ++ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, ++ ngx_str_t *name, ngx_str_t *value); ++ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ++ ngx_str_t *name, ngx_str_t *value); ++ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, ++ ngx_uint_t *insert_count); ++ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, ++ ngx_uint_t insert_count); ++void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count); ++ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, ++ uint64_t value); ++ ++ ++#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */ +diff -r ac779115ed6e src/http/v3/ngx_http_v3_uni.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_uni.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,781 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++typedef struct { ++ ngx_http_v3_parse_uni_t parse; ++ ngx_int_t index; ++} ngx_http_v3_uni_stream_t; ++ ++ ++typedef struct { ++ ngx_queue_t queue; ++ uint64_t id; ++ ngx_connection_t *connection; ++ ngx_uint_t *npushing; ++} ngx_http_v3_push_t; ++ ++ ++static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); ++static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); ++static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); ++static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); ++static void ngx_http_v3_push_cleanup(void *data); ++static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ++ ngx_uint_t type); ++ ++ ++void ++ngx_http_v3_init_uni_stream(ngx_connection_t *c) ++{ ++ uint64_t n; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ h3c = ngx_http_v3_get_session(c); ++ if (h3c->hq) { ++ ngx_http_v3_finalize_connection(c, ++ NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, ++ "uni stream in hq mode"); ++ c->data = NULL; ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); ++ ++ n = c->quic->id >> 2; ++ ++ if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { ++ ngx_http_v3_finalize_connection(c, ++ NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, ++ "reached maximum number of uni streams"); ++ c->data = NULL; ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ ngx_quic_cancelable_stream(c); ++ ++ us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); ++ if (us == NULL) { ++ ngx_http_v3_finalize_connection(c, ++ NGX_HTTP_V3_ERR_INTERNAL_ERROR, ++ "memory allocation error"); ++ c->data = NULL; ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ us->index = -1; ++ ++ c->data = us; ++ ++ c->read->handler = ngx_http_v3_uni_read_handler; ++ c->write->handler = ngx_http_v3_uni_dummy_write_handler; ++ ++ ngx_http_v3_uni_read_handler(c->read); ++} ++ ++ ++static void ++ngx_http_v3_close_uni_stream(ngx_connection_t *c) ++{ ++ ngx_pool_t *pool; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); ++ ++ us = c->data; ++ ++ if (us && us->index >= 0) { ++ h3c = ngx_http_v3_get_session(c); ++ h3c->known_streams[us->index] = NULL; ++ } ++ ++ c->destroyed = 1; ++ ++ pool = c->pool; ++ ++ ngx_close_connection(c); ++ ++ ngx_destroy_pool(pool); ++} ++ ++ ++ngx_int_t ++ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) ++{ ++ ngx_int_t index; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ switch (type) { ++ ++ case NGX_HTTP_V3_STREAM_ENCODER: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 encoder stream"); ++ index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; ++ break; ++ ++ case NGX_HTTP_V3_STREAM_DECODER: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 decoder stream"); ++ index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; ++ break; ++ ++ case NGX_HTTP_V3_STREAM_CONTROL: ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 control stream"); ++ index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; ++ ++ break; ++ ++ default: ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 stream 0x%02xL", type); ++ ++ if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL ++ || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL ++ || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) ++ { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); ++ return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; ++ } ++ ++ index = -1; ++ } ++ ++ if (index >= 0) { ++ if (h3c->known_streams[index]) { ++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); ++ return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; ++ } ++ ++ h3c->known_streams[index] = c; ++ ++ us = c->data; ++ us->index = index; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void ++ngx_http_v3_uni_read_handler(ngx_event_t *rev) ++{ ++ u_char buf[128]; ++ ssize_t n; ++ ngx_buf_t b; ++ ngx_int_t rc; ++ ngx_connection_t *c; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ c = rev->data; ++ us = c->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); ++ ++ if (c->close) { ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ ngx_memzero(&b, sizeof(ngx_buf_t)); ++ ++ while (rev->ready) { ++ ++ n = c->recv(c, buf, sizeof(buf)); ++ ++ if (n == NGX_ERROR) { ++ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; ++ goto failed; ++ } ++ ++ if (n == 0) { ++ if (us->index >= 0) { ++ rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; ++ goto failed; ++ } ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ if (n == NGX_AGAIN) { ++ break; ++ } ++ ++ b.pos = buf; ++ b.last = buf + n; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (ngx_http_v3_check_flood(c) != NGX_OK) { ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ rc = ngx_http_v3_parse_uni(c, &us->parse, &b); ++ ++ if (rc == NGX_DONE) { ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 read done"); ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ if (rc > 0) { ++ goto failed; ++ } ++ ++ if (rc != NGX_AGAIN) { ++ rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; ++ goto failed; ++ } ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; ++ goto failed; ++ } ++ ++ return; ++ ++failed: ++ ++ ngx_http_v3_finalize_connection(c, rc, "stream error"); ++ ngx_http_v3_close_uni_stream(c); ++} ++ ++ ++static void ++ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev) ++{ ++ u_char ch; ++ ngx_connection_t *c; ++ ++ c = rev->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler"); ++ ++ if (c->close) { ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ ++ if (rev->ready) { ++ if (c->recv(c, &ch, 1) != 0) { ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL); ++ ngx_http_v3_close_uni_stream(c); ++ return; ++ } ++ } ++ ++ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, ++ NULL); ++ ngx_http_v3_close_uni_stream(c); ++ } ++} ++ ++ ++static void ++ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev) ++{ ++ ngx_connection_t *c; ++ ++ c = wev->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); ++ ++ if (ngx_handle_write_event(wev, 0) != NGX_OK) { ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, ++ NULL); ++ ngx_http_v3_close_uni_stream(c); ++ } ++} ++ ++ ++ngx_connection_t * ++ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) ++{ ++ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; ++ size_t n; ++ ngx_connection_t *sc; ++ ngx_pool_cleanup_t *cln; ++ ngx_http_v3_push_t *push; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 create push stream id:%uL", push_id); ++ ++ sc = ngx_quic_open_stream(c, 0); ++ if (sc == NULL) { ++ goto failed; ++ } ++ ++ p = buf; ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); ++ n = p - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (sc->send(sc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); ++ if (cln == NULL) { ++ goto failed; ++ } ++ ++ h3c->npushing++; ++ ++ cln->handler = ngx_http_v3_push_cleanup; ++ ++ push = cln->data; ++ push->id = push_id; ++ push->connection = sc; ++ push->npushing = &h3c->npushing; ++ ++ ngx_queue_insert_tail(&h3c->pushing, &push->queue); ++ ++ return sc; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, ++ "failed to create push stream"); ++ if (sc) { ++ ngx_http_v3_close_uni_stream(sc); ++ } ++ ++ return NULL; ++} ++ ++ ++static void ++ngx_http_v3_push_cleanup(void *data) ++{ ++ ngx_http_v3_push_t *push = data; ++ ++ ngx_queue_remove(&push->queue); ++ (*push->npushing)--; ++} ++ ++ ++static ngx_connection_t * ++ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) ++{ ++ u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; ++ size_t n; ++ ngx_int_t index; ++ ngx_connection_t *sc; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_uni_stream_t *us; ++ ++ switch (type) { ++ case NGX_HTTP_V3_STREAM_ENCODER: ++ index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; ++ break; ++ case NGX_HTTP_V3_STREAM_DECODER: ++ index = NGX_HTTP_V3_STREAM_SERVER_DECODER; ++ break; ++ case NGX_HTTP_V3_STREAM_CONTROL: ++ index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; ++ break; ++ default: ++ index = -1; ++ } ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ if (index >= 0) { ++ if (h3c->known_streams[index]) { ++ return h3c->known_streams[index]; ++ } ++ } ++ ++ sc = ngx_quic_open_stream(c, 0); ++ if (sc == NULL) { ++ goto failed; ++ } ++ ++ ngx_quic_cancelable_stream(sc); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 create uni stream, type:%ui", type); ++ ++ us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); ++ if (us == NULL) { ++ goto failed; ++ } ++ ++ us->index = index; ++ ++ sc->data = us; ++ ++ sc->read->handler = ngx_http_v3_uni_dummy_read_handler; ++ sc->write->handler = ngx_http_v3_uni_dummy_write_handler; ++ ++ if (index >= 0) { ++ h3c->known_streams[index] = sc; ++ } ++ ++ n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (sc->send(sc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ ngx_post_event(sc->read, &ngx_posted_events); ++ ++ return sc; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, ++ "failed to create server stream"); ++ if (sc) { ++ ngx_http_v3_close_uni_stream(sc); ++ } ++ ++ return NULL; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_settings(ngx_connection_t *c) ++{ ++ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; ++ size_t n; ++ ngx_connection_t *cc; ++ ngx_http_v3_session_t *h3c; ++ ngx_http_v3_srv_conf_t *h3scf; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); ++ ++ cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); ++ if (cc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); ++ ++ n = ngx_http_v3_encode_varlen_int(NULL, ++ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); ++ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity); ++ n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); ++ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams); ++ ++ p = (u_char *) ngx_http_v3_encode_varlen_int(buf, ++ NGX_HTTP_V3_FRAME_SETTINGS); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, ++ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, ++ NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); ++ n = p - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (cc->send(cc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send settings"); ++ ngx_http_v3_close_uni_stream(cc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) ++{ ++ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; ++ size_t n; ++ ngx_connection_t *cc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); ++ ++ cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); ++ if (cc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ n = ngx_http_v3_encode_varlen_int(NULL, id); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); ++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); ++ n = p - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (cc->send(cc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send goaway"); ++ ngx_http_v3_close_uni_stream(cc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; ++ size_t n; ++ ngx_connection_t *dc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 send section acknowledgement %ui", stream_id); ++ ++ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); ++ if (dc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] = 0x80; ++ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (dc->send(dc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "failed to send section acknowledgement"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send section acknowledgement"); ++ ngx_http_v3_close_uni_stream(dc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; ++ size_t n; ++ ngx_connection_t *dc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 send stream cancellation %ui", stream_id); ++ ++ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); ++ if (dc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] = 0x40; ++ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (dc->send(dc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send stream cancellation"); ++ ngx_http_v3_close_uni_stream(dc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ++{ ++ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; ++ size_t n; ++ ngx_connection_t *dc; ++ ngx_http_v3_session_t *h3c; ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 send insert count increment %ui", inc); ++ ++ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); ++ if (dc == NULL) { ++ return NGX_ERROR; ++ } ++ ++ buf[0] = 0; ++ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; ++ ++ h3c = ngx_http_v3_get_session(c); ++ h3c->total_bytes += n; ++ ++ if (dc->send(dc, buf, n) != (ssize_t) n) { ++ goto failed; ++ } ++ ++ return NGX_OK; ++ ++failed: ++ ++ ngx_log_error(NGX_LOG_ERR, c->log, 0, ++ "failed to send insert count increment"); ++ ++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, ++ "failed to send insert count increment"); ++ ngx_http_v3_close_uni_stream(dc); ++ ++ return NGX_ERROR; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) ++{ ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 MAX_PUSH_ID:%uL", max_push_id); ++ ++ if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { ++ return NGX_HTTP_V3_ERR_ID_ERROR; ++ } ++ ++ h3c->max_push_id = max_push_id; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) ++{ ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); ++ ++ h3c->goaway_push_id = push_id; ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) ++{ ++ ngx_queue_t *q; ++ ngx_http_request_t *r; ++ ngx_http_v3_push_t *push; ++ ngx_http_v3_session_t *h3c; ++ ++ h3c = ngx_http_v3_get_session(c); ++ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 CANCEL_PUSH:%uL", push_id); ++ ++ if (push_id >= h3c->next_push_id) { ++ return NGX_HTTP_V3_ERR_ID_ERROR; ++ } ++ ++ for (q = ngx_queue_head(&h3c->pushing); ++ q != ngx_queue_sentinel(&h3c->pushing); ++ q = ngx_queue_next(q)) ++ { ++ push = (ngx_http_v3_push_t *) q; ++ ++ if (push->id != push_id) { ++ continue; ++ } ++ ++ r = push->connection->data; ++ ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http3 cancel push"); ++ ++ ngx_http_finalize_request(r, NGX_HTTP_CLOSE); ++ ++ break; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++ngx_int_t ++ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ++{ ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, ++ "http3 cancel stream %ui", stream_id); ++ ++ /* we do not use dynamic tables */ ++ ++ return NGX_OK; ++} +diff -r ac779115ed6e src/http/v3/ngx_http_v3_uni.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/http/v3/ngx_http_v3_uni.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,38 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_ ++#define _NGX_HTTP_V3_UNI_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++ ++void ngx_http_v3_init_uni_stream(ngx_connection_t *c); ++ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); ++ ++ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, ++ uint64_t push_id); ++ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, ++ uint64_t max_push_id); ++ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); ++ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); ++ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); ++ ++ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); ++ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); ++ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, ++ ngx_uint_t stream_id); ++ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ++ ngx_uint_t stream_id); ++ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ++ ngx_uint_t inc); ++ ++ ++#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */ +diff -r ac779115ed6e src/os/unix/ngx_socket.h +--- a/src/os/unix/ngx_socket.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/os/unix/ngx_socket.h Thu May 11 11:48:37 2023 -0400 +@@ -13,6 +13,8 @@ + + + #define NGX_WRITE_SHUTDOWN SHUT_WR ++#define NGX_READ_SHUTDOWN SHUT_RD ++#define NGX_RDWR_SHUTDOWN SHUT_RDWR + + typedef int ngx_socket_t; + +diff -r ac779115ed6e src/os/win32/ngx_socket.h +--- a/src/os/win32/ngx_socket.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/os/win32/ngx_socket.h Thu May 11 11:48:37 2023 -0400 +@@ -14,6 +14,8 @@ + + + #define NGX_WRITE_SHUTDOWN SD_SEND ++#define NGX_READ_SHUTDOWN SD_RECEIVE ++#define NGX_RDWR_SHUTDOWN SD_BOTH + + + typedef SOCKET ngx_socket_t; +diff -r ac779115ed6e src/stream/ngx_stream.c +--- a/src/stream/ngx_stream.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/stream/ngx_stream.c Thu May 11 11:48:37 2023 -0400 +@@ -518,6 +518,24 @@ ngx_stream_optimize_servers(ngx_conf_t * + ls->reuseport = addr[i].opt.reuseport; + #endif + ++#if (NGX_STREAM_QUIC) ++ ++ ls->quic = addr[i].opt.quic; ++ ++ if (ls->quic) { ++ ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ++ ngx_quic_rbtree_insert_value); ++ } ++ ++#endif ++ ++#if !(NGX_WIN32) ++ if (!ls->quic) { ++ ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ++ ngx_udp_rbtree_insert_value); ++ } ++#endif ++ + stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); + if (stport == NULL) { + return NGX_CONF_ERROR; +@@ -576,6 +594,9 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx + #if (NGX_STREAM_SSL) + addrs[i].conf.ssl = addr[i].opt.ssl; + #endif ++#if (NGX_STREAM_QUIC) ++ addrs[i].conf.quic = addr[i].opt.quic; ++#endif + addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + addrs[i].conf.addr_text = addr[i].opt.addr_text; + } +@@ -611,6 +632,9 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ng + #if (NGX_STREAM_SSL) + addrs6[i].conf.ssl = addr[i].opt.ssl; + #endif ++#if (NGX_STREAM_QUIC) ++ addrs6[i].conf.quic = addr[i].opt.quic; ++#endif + addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; + addrs6[i].conf.addr_text = addr[i].opt.addr_text; + } +diff -r ac779115ed6e src/stream/ngx_stream.h +--- a/src/stream/ngx_stream.h Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/stream/ngx_stream.h Thu May 11 11:48:37 2023 -0400 +@@ -16,6 +16,10 @@ + #include + #endif + ++#if (NGX_STREAM_QUIC) ++#include ++#endif ++ + + typedef struct ngx_stream_session_s ngx_stream_session_t; + +@@ -51,6 +55,7 @@ typedef struct { + unsigned bind:1; + unsigned wildcard:1; + unsigned ssl:1; ++ unsigned quic:1; + #if (NGX_HAVE_INET6) + unsigned ipv6only:1; + #endif +@@ -76,6 +81,7 @@ typedef struct { + ngx_stream_conf_ctx_t *ctx; + ngx_str_t addr_text; + unsigned ssl:1; ++ unsigned quic:1; + unsigned proxy_protocol:1; + } ngx_stream_addr_conf_t; + +diff -r ac779115ed6e src/stream/ngx_stream_core_module.c +--- a/src/stream/ngx_stream_core_module.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/stream/ngx_stream_core_module.c Thu May 11 11:48:37 2023 -0400 +@@ -760,6 +760,29 @@ ngx_stream_core_listen(ngx_conf_t *cf, n + #endif + } + ++ if (ngx_strcmp(value[i].data, "quic") == 0) { ++#if (NGX_STREAM_QUIC) ++ ngx_stream_ssl_conf_t *sslcf; ++ ++ sslcf = ngx_stream_conf_get_module_srv_conf(cf, ++ ngx_stream_ssl_module); ++ ++ sslcf->listen = 1; ++ sslcf->file = cf->conf_file->file.name.data; ++ sslcf->line = cf->conf_file->line; ++ ++ ls->quic = 1; ++ ls->type = SOCK_DGRAM; ++ ++ continue; ++#else ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "the \"quic\" parameter requires " ++ "ngx_stream_quic_module"); ++ return NGX_CONF_ERROR; ++#endif ++ } ++ + if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { + + if (ngx_strcmp(&value[i].data[13], "on") == 0) { +@@ -871,6 +894,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, n + } + #endif + ++#if (NGX_STREAM_SSL && NGX_STREAM_QUIC) ++ if (ls->ssl && ls->quic) { ++ return "\"ssl\" parameter is incompatible with \"quic\""; ++ } ++#endif ++ + if (ls->so_keepalive) { + return "\"so_keepalive\" parameter is incompatible with \"udp\""; + } +diff -r ac779115ed6e src/stream/ngx_stream_handler.c +--- a/src/stream/ngx_stream_handler.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/stream/ngx_stream_handler.c Thu May 11 11:48:37 2023 -0400 +@@ -129,6 +129,10 @@ ngx_stream_init_connection(ngx_connectio + s->ssl = addr_conf->ssl; + #endif + ++#if (NGX_STREAM_QUIC) ++ s->ssl |= addr_conf->quic; ++#endif ++ + if (c->buffer) { + s->received += c->buffer->last - c->buffer->pos; + } +@@ -173,6 +177,21 @@ ngx_stream_init_connection(ngx_connectio + s->start_sec = tp->sec; + s->start_msec = tp->msec; + ++#if (NGX_STREAM_QUIC) ++ ++ if (addr_conf->quic) { ++ ngx_quic_conf_t *qcf; ++ ++ if (c->quic == NULL) { ++ qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, ++ ngx_stream_quic_module); ++ ngx_quic_run(c, qcf); ++ return; ++ } ++ } ++ ++#endif ++ + rev = c->read; + rev->handler = ngx_stream_session_handler; + +diff -r ac779115ed6e src/stream/ngx_stream_proxy_module.c +--- a/src/stream/ngx_stream_proxy_module.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/stream/ngx_stream_proxy_module.c Thu May 11 11:48:37 2023 -0400 +@@ -1772,6 +1772,21 @@ ngx_stream_proxy_process(ngx_stream_sess + if (dst->type == SOCK_STREAM && pscf->half_close + && src->read->eof && !u->half_closed && !dst->buffered) + { ++ ++#if (NGX_STREAM_QUIC) ++ if (dst->quic) { ++ ++ if (ngx_quic_shutdown_stream(dst, NGX_WRITE_SHUTDOWN) ++ != NGX_OK) ++ { ++ ngx_stream_proxy_finalize(s, ++ NGX_STREAM_INTERNAL_SERVER_ERROR); ++ return; ++ } ++ ++ } else ++#endif ++ + if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) { + ngx_connection_error(c, ngx_socket_errno, + ngx_shutdown_socket_n " failed"); +diff -r ac779115ed6e src/stream/ngx_stream_quic_module.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/stream/ngx_stream_quic_module.c Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,377 @@ ++ ++/* ++ * Copyright (C) Nginx, Inc. ++ * Copyright (C) Roman Arutyunyan ++ */ ++ ++ ++#include ++#include ++#include ++ ++ ++static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, ++ ngx_stream_variable_value_t *v, uintptr_t data); ++static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf); ++static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); ++static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, ++ void *child); ++static char *ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data); ++static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf); ++ ++static ngx_conf_post_t ngx_stream_quic_mtu_post = ++ { ngx_stream_quic_mtu }; ++ ++static ngx_command_t ngx_stream_quic_commands[] = { ++ ++ { ngx_string("quic_timeout"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_msec_slot, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ offsetof(ngx_quic_conf_t, timeout), ++ NULL }, ++ ++ { ngx_string("quic_mtu"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_size_slot, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ offsetof(ngx_quic_conf_t, mtu), ++ &ngx_stream_quic_mtu_post }, ++ ++ { ngx_string("quic_stream_buffer_size"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_size_slot, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ offsetof(ngx_quic_conf_t, stream_buffer_size), ++ NULL }, ++ ++ { ngx_string("quic_retry"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ offsetof(ngx_quic_conf_t, retry), ++ NULL }, ++ ++ { ngx_string("quic_gso"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ++ ngx_conf_set_flag_slot, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ offsetof(ngx_quic_conf_t, gso_enabled), ++ NULL }, ++ ++ { ngx_string("quic_host_key"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ++ ngx_stream_quic_host_key, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ 0, ++ NULL }, ++ ++ { ngx_string("quic_active_connection_id_limit"), ++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_num_slot, ++ NGX_STREAM_SRV_CONF_OFFSET, ++ offsetof(ngx_quic_conf_t, active_connection_id_limit), ++ NULL }, ++ ++ ngx_null_command ++}; ++ ++ ++static ngx_stream_module_t ngx_stream_quic_module_ctx = { ++ ngx_stream_quic_add_variables, /* preconfiguration */ ++ NULL, /* postconfiguration */ ++ ++ NULL, /* create main configuration */ ++ NULL, /* init main configuration */ ++ ++ ngx_stream_quic_create_srv_conf, /* create server configuration */ ++ ngx_stream_quic_merge_srv_conf, /* merge server configuration */ ++}; ++ ++ ++ngx_module_t ngx_stream_quic_module = { ++ NGX_MODULE_V1, ++ &ngx_stream_quic_module_ctx, /* module context */ ++ ngx_stream_quic_commands, /* module directives */ ++ NGX_STREAM_MODULE, /* module type */ ++ NULL, /* init master */ ++ NULL, /* init module */ ++ NULL, /* init process */ ++ NULL, /* init thread */ ++ NULL, /* exit thread */ ++ NULL, /* exit process */ ++ NULL, /* exit master */ ++ NGX_MODULE_V1_PADDING ++}; ++ ++ ++static ngx_stream_variable_t ngx_stream_quic_vars[] = { ++ ++ { ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 }, ++ ++ ngx_stream_null_variable ++}; ++ ++static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic"); ++ ++ ++static ngx_int_t ++ngx_stream_variable_quic(ngx_stream_session_t *s, ++ ngx_stream_variable_value_t *v, uintptr_t data) ++{ ++ if (s->connection->quic) { ++ ++ v->len = 4; ++ v->valid = 1; ++ v->no_cacheable = 1; ++ v->not_found = 0; ++ v->data = (u_char *) "quic"; ++ return NGX_OK; ++ } ++ ++ v->not_found = 1; ++ ++ return NGX_OK; ++} ++ ++ ++static ngx_int_t ++ngx_stream_quic_add_variables(ngx_conf_t *cf) ++{ ++ ngx_stream_variable_t *var, *v; ++ ++ for (v = ngx_stream_quic_vars; v->name.len; v++) { ++ var = ngx_stream_add_variable(cf, &v->name, v->flags); ++ if (var == NULL) { ++ return NGX_ERROR; ++ } ++ ++ var->get_handler = v->get_handler; ++ var->data = v->data; ++ } ++ ++ return NGX_OK; ++} ++ ++ ++static void * ++ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) ++{ ++ ngx_quic_conf_t *conf; ++ ++ conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); ++ if (conf == NULL) { ++ return NULL; ++ } ++ ++ /* ++ * set by ngx_pcalloc(): ++ * ++ * conf->host_key = { 0, NULL } ++ * conf->stream_close_code = 0; ++ * conf->stream_reject_code_uni = 0; ++ * conf->stream_reject_code_bidi= 0; ++ */ ++ ++ conf->timeout = NGX_CONF_UNSET_MSEC; ++ conf->mtu = NGX_CONF_UNSET_SIZE; ++ conf->stream_buffer_size = NGX_CONF_UNSET_SIZE; ++ conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; ++ conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT; ++ ++ conf->retry = NGX_CONF_UNSET; ++ conf->gso_enabled = NGX_CONF_UNSET; ++ ++ conf->active_connection_id_limit = NGX_CONF_UNSET_UINT; ++ ++ return conf; ++} ++ ++ ++static char * ++ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ++{ ++ ngx_quic_conf_t *prev = parent; ++ ngx_quic_conf_t *conf = child; ++ ++ ngx_stream_ssl_conf_t *scf; ++ ++ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); ++ ++ ngx_conf_merge_size_value(conf->mtu, prev->mtu, ++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); ++ ++ ngx_conf_merge_size_value(conf->stream_buffer_size, ++ prev->stream_buffer_size, ++ 65536); ++ ++ ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi, ++ prev->max_concurrent_streams_bidi, 16); ++ ++ ngx_conf_merge_uint_value(conf->max_concurrent_streams_uni, ++ prev->max_concurrent_streams_uni, 3); ++ ++ ngx_conf_merge_value(conf->retry, prev->retry, 0); ++ ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); ++ ++ ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); ++ ++ ngx_conf_merge_uint_value(conf->active_connection_id_limit, ++ conf->active_connection_id_limit, ++ 2); ++ ++ if (conf->host_key.len == 0) { ++ ++ conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; ++ conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); ++ if (conf->host_key.data == NULL) { ++ return NGX_CONF_ERROR; ++ } ++ ++ if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) ++ <= 0) ++ { ++ return NGX_CONF_ERROR; ++ } ++ } ++ ++ if (ngx_quic_derive_key(cf->log, "av_token_key", ++ &conf->host_key, &ngx_stream_quic_salt, ++ conf->av_token_key, NGX_QUIC_AV_KEY_LEN) ++ != NGX_OK) ++ { ++ return NGX_CONF_ERROR; ++ } ++ ++ if (ngx_quic_derive_key(cf->log, "sr_token_key", ++ &conf->host_key, &ngx_stream_quic_salt, ++ conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) ++ != NGX_OK) ++ { ++ return NGX_CONF_ERROR; ++ } ++ ++ scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); ++ conf->ssl = &scf->ssl; ++ ++ return NGX_CONF_OK; ++} ++ ++ ++static char * ++ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data) ++{ ++ size_t *sp = data; ++ ++ if (*sp < NGX_QUIC_MIN_INITIAL_SIZE ++ || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) ++ { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "\"quic_mtu\" must be between %d and %d", ++ NGX_QUIC_MIN_INITIAL_SIZE, ++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); ++ ++ return NGX_CONF_ERROR; ++ } ++ ++ return NGX_CONF_OK; ++} ++ ++ ++static char * ++ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ++{ ++ ngx_quic_conf_t *qcf = conf; ++ ++ u_char *buf; ++ size_t size; ++ ssize_t n; ++ ngx_str_t *value; ++ ngx_file_t file; ++ ngx_file_info_t fi; ++ ++ if (qcf->host_key.len) { ++ return "is duplicate"; ++ } ++ ++ buf = NULL; ++#if (NGX_SUPPRESS_WARN) ++ size = 0; ++#endif ++ ++ value = cf->args->elts; ++ ++ if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { ++ return NGX_CONF_ERROR; ++ } ++ ++ ngx_memzero(&file, sizeof(ngx_file_t)); ++ file.name = value[1]; ++ file.log = cf->log; ++ ++ file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); ++ ++ if (file.fd == NGX_INVALID_FILE) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ++ ngx_open_file_n " \"%V\" failed", &file.name); ++ return NGX_CONF_ERROR; ++ } ++ ++ if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { ++ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ++ ngx_fd_info_n " \"%V\" failed", &file.name); ++ goto failed; ++ } ++ ++ size = ngx_file_size(&fi); ++ ++ if (size == 0) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ++ "\"%V\" zero key size", &file.name); ++ goto failed; ++ } ++ ++ buf = ngx_pnalloc(cf->pool, size); ++ if (buf == NULL) { ++ goto failed; ++ } ++ ++ n = ngx_read_file(&file, buf, size, 0); ++ ++ if (n == NGX_ERROR) { ++ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, ++ ngx_read_file_n " \"%V\" failed", &file.name); ++ goto failed; ++ } ++ ++ if ((size_t) n != size) { ++ ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, ++ ngx_read_file_n " \"%V\" returned only " ++ "%z bytes instead of %uz", &file.name, n, size); ++ goto failed; ++ } ++ ++ qcf->host_key.data = buf; ++ qcf->host_key.len = n; ++ ++ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ++ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, ++ ngx_close_file_n " \"%V\" failed", &file.name); ++ } ++ ++ return NGX_CONF_OK; ++ ++failed: ++ ++ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { ++ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, ++ ngx_close_file_n " \"%V\" failed", &file.name); ++ } ++ ++ if (buf) { ++ ngx_explicit_memzero(buf, size); ++ } ++ ++ return NGX_CONF_ERROR; ++} +diff -r ac779115ed6e src/stream/ngx_stream_quic_module.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/src/stream/ngx_stream_quic_module.h Thu May 11 11:48:37 2023 -0400 +@@ -0,0 +1,20 @@ ++ ++/* ++ * Copyright (C) Roman Arutyunyan ++ * Copyright (C) Nginx, Inc. ++ */ ++ ++ ++#ifndef _NGX_STREAM_QUIC_H_INCLUDED_ ++#define _NGX_STREAM_QUIC_H_INCLUDED_ ++ ++ ++#include ++#include ++#include ++ ++ ++extern ngx_module_t ngx_stream_quic_module; ++ ++ ++#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */ +diff -r ac779115ed6e src/stream/ngx_stream_ssl_module.c +--- a/src/stream/ngx_stream_ssl_module.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/stream/ngx_stream_ssl_module.c Thu May 11 11:48:37 2023 -0400 +@@ -9,6 +9,10 @@ + #include + #include + ++#if (NGX_QUIC_OPENSSL_COMPAT) ++#include ++#endif ++ + + typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, + ngx_pool_t *pool, ngx_str_t *s); +@@ -1195,7 +1199,10 @@ ngx_stream_ssl_conf_command_check(ngx_co + static ngx_int_t + ngx_stream_ssl_init(ngx_conf_t *cf) + { ++ ngx_uint_t i; ++ ngx_stream_listen_t *listen; + ngx_stream_handler_pt *h; ++ ngx_stream_ssl_conf_t *scf; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); +@@ -1207,5 +1214,29 @@ ngx_stream_ssl_init(ngx_conf_t *cf) + + *h = ngx_stream_ssl_handler; + ++ listen = cmcf->listen.elts; ++ ++ for (i = 0; i < cmcf->listen.nelts; i++) { ++ if (!listen[i].quic) { ++ continue; ++ } ++ ++ scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; ++ ++#if (NGX_QUIC_OPENSSL_COMPAT) ++ if (ngx_quic_compat_init(cf, scf->ssl.ctx) != NGX_OK) { ++ return NGX_ERROR; ++ } ++#endif ++ ++ if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) { ++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0, ++ "\"ssl_protocols\" must enable TLSv1.3 for " ++ "the \"listen ... quic\" directive in %s:%ui", ++ scf->file, scf->line); ++ return NGX_ERROR; ++ } ++ } ++ + return NGX_OK; + } +diff -r ac779115ed6e src/stream/ngx_stream_write_filter_module.c +--- a/src/stream/ngx_stream_write_filter_module.c Tue Mar 28 18:01:53 2023 +0300 ++++ b/src/stream/ngx_stream_write_filter_module.c Thu May 11 11:48:37 2023 -0400 +@@ -277,7 +277,12 @@ ngx_stream_write_filter(ngx_stream_sessi + *out = chain; + + if (chain) { +- if (c->shared) { ++ if (c->shared ++#if (NGX_STREAM_QUIC) ++ && c->quic == NULL ++#endif ++ ) ++ { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "shared connection is busy"); + return NGX_ERROR; diff --git a/www/freenginx/files/extra-patch-nginx-ct-LibreSSL b/www/freenginx/files/extra-patch-nginx-ct-LibreSSL new file mode 100644 index 000000000000..9aa89a463a9d --- /dev/null +++ b/www/freenginx/files/extra-patch-nginx-ct-LibreSSL @@ -0,0 +1,20 @@ +--- ../nginx-ct-93e9884/ngx_ssl_ct_module.c.orig 2017-07-23 08:03:35.000000000 -0400 ++++ ../nginx-ct-93e9884/ngx_ssl_ct_module.c 2018-04-24 16:58:27.698435000 -0400 +@@ -158,7 +158,7 @@ + #endif + } + +-#ifndef OPENSSL_IS_BORINGSSL ++#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) + /* add OpenSSL TLS extension */ + # if OPENSSL_VERSION_NUMBER >= 0x10101000L + int context = SSL_EXT_CLIENT_HELLO +@@ -183,7 +183,7 @@ + return NGX_CONF_OK; + } + +-#ifndef OPENSSL_IS_BORINGSSL ++#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) + # if OPENSSL_VERSION_NUMBER >= 0x10101000L + int ngx_ssl_ct_ext_cb(SSL *s, unsigned int ext_type, unsigned int context, + const unsigned char **out, size_t *outlen, X509 *x, size_t chainidx, diff --git a/www/freenginx/files/extra-patch-nginx-http-footer-filter-config b/www/freenginx/files/extra-patch-nginx-http-footer-filter-config new file mode 100644 index 000000000000..880856c7292d --- /dev/null +++ b/www/freenginx/files/extra-patch-nginx-http-footer-filter-config @@ -0,0 +1,12 @@ +--- ../nginx-http-footer-filter-1.2.2/config.orig 2020-04-24 08:37:44.671689000 -0400 ++++ ../nginx-http-footer-filter-1.2.2/config 2020-04-24 08:44:41.868601000 -0400 +@@ -1,3 +1,7 @@ + ngx_addon_name=ngx_http_footer_filter_module +-HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_footer_filter_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_footer_filter_module.c" ++ngx_module_type=HTTP_FILTER ++ngx_module_name="$ngx_addon_name" ++ ++ngx_module_srcs="$ngx_addon_dir/ngx_http_footer_filter_module.c" ++ ++. auto/module diff --git a/www/freenginx/files/extra-patch-nginx-link-function-config b/www/freenginx/files/extra-patch-nginx-link-function-config new file mode 100644 index 000000000000..725490ef14c3 --- /dev/null +++ b/www/freenginx/files/extra-patch-nginx-link-function-config @@ -0,0 +1,42 @@ +--- ../nginx-link-function-3.2.4/config.orig 2020-10-12 22:32:47.000000000 -0400 ++++ ../nginx-link-function-3.2.4/config 2020-11-13 12:26:56.186032000 -0500 +@@ -15,24 +15,6 @@ + #echo "#define NGINX_HTTP_HTTP_LINK_FUNC_VERSION \""$HTTP_LINK_FUNC_VERSION"\"" > $NGX_OBJS/ngx_vod_version.h + + +-# ngx_link_func_module headers +-# to Test this in order to share the header file to other client instead of just depend on it owns +-ngx_feature="ngx_http_link_func" +-ngx_feature_name="NGX_HAVE_HTTP_LINK_FUNC_HEADERS" +-ngx_feature_run=no +-ngx_feature_incs="#include " +-ngx_feature_path= +-ngx_feature_libs= +-# ngx_feature_exit_if_not_found=yes +-ngx_feature_test="int ngx_link_func_module_current_version_=ngx_link_func_module_version_34;" +-. auto/feature +- +-if [ $ngx_found != yes ]; then +-echo "ngx_link_func_module.h not found or version not aligned in your system c header path, please copy latest ngx_link_func_module.h to your /usr/include or /usr/local/include or relavent header search path with read and write permission given." +-echo "e.g install -m 644 ../nginx-link-function/src/ngx_link_func_module.h /usr/local/include/" +-echo +-exit 1 +-else + cat $ngx_addon_dir/build_test_resources/sanity_test_raw_parse.t > $ngx_addon_dir/t/sanity.t + if [ $USE_THREADS = YES ]; then + cat $ngx_addon_dir/build_test_resources/sanity_test_aio_parse.t >> $ngx_addon_dir/t/sanity.t +@@ -42,13 +24,12 @@ + if [ $USE_THREADS = YES ]; then + cat $ngx_addon_dir/build_test_resources/sanity_test_subrequest_aio_parse.t >> $ngx_addon_dir/t/sanity.t + fi +-fi + ABSOLUTE_NGX_LINKFUNC_CURRENT_PATH="$( cd "$ngx_addon_dir" ; pwd -P )" + if [ "$NGX_SYSTEM" = "Darwin" ]; then + clang -dynamiclib -o $ngx_addon_dir/t/liblinkfuntest.dylib -fPIC $ngx_addon_dir/build_test_resources/linkfuntest.c -Wl,-undefined,dynamic_lookup + sed -i '' "s@NGINX_HTTP_LINK_FUNC_TEST_LIB_PATH@$ABSOLUTE_NGX_LINKFUNC_CURRENT_PATH/t/liblinkfuntest.dylib@g" $ngx_addon_dir/t/sanity.t + else +-if [ "$NGX_PLATFORM" != win32 ]; then ++if [ "$NGX_PLATFORM" = win32 ]; then + cc -shared -o $ngx_addon_dir/t/liblinkfuntest.so -fPIC $ngx_addon_dir/build_test_resources/linkfuntest.c + sed -i "s@NGINX_HTTP_LINK_FUNC_TEST_LIB_PATH@$ABSOLUTE_NGX_LINKFUNC_CURRENT_PATH/t/liblinkfuntest.so@g" $ngx_addon_dir/t/sanity.t + fi diff --git a/www/freenginx/files/extra-patch-nginx-notice-config b/www/freenginx/files/extra-patch-nginx-notice-config new file mode 100644 index 000000000000..cd333a115624 --- /dev/null +++ b/www/freenginx/files/extra-patch-nginx-notice-config @@ -0,0 +1,13 @@ +--- ../nginx-notice-3c95966/config.orig 2020-04-25 18:03:28.543102000 -0400 ++++ ../nginx-notice-3c95966/config 2020-04-25 18:04:14.387235000 -0400 +@@ -1,3 +1,8 @@ + ngx_addon_name=ngx_http_notice_module +-HTTP_MODULES="$HTTP_MODULES ngx_http_notice_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_notice_module.c" ++ ++ngx_module_name="$ngx_addon_name" ++ngx_module_type=HTTP ++ ++ngx_module_srcs="$ngx_addon_dir/ngx_http_notice_module.c" ++ ++. auto/module diff --git a/www/freenginx/files/extra-patch-nginx-opentracing-opentracing-config b/www/freenginx/files/extra-patch-nginx-opentracing-opentracing-config new file mode 100644 index 000000000000..1c40dd108b95 --- /dev/null +++ b/www/freenginx/files/extra-patch-nginx-opentracing-opentracing-config @@ -0,0 +1,8 @@ +--- ../nginx-opentracing-0.24.0/opentracing/config.orig 2020-05-07 18:34:12.853828000 -0400 ++++ ../nginx-opentracing-0.24.0/opentracing/config 2020-05-07 18:34:26.521814000 -0400 +@@ -34,4 +34,4 @@ + + . auto/module + +-OT_NGX_SRCS="$ngx_module_srcs" ++#OT_NGX_SRCS="$ngx_module_srcs" diff --git a/www/freenginx/files/extra-patch-nginx_mod_h264_streaming-config b/www/freenginx/files/extra-patch-nginx_mod_h264_streaming-config new file mode 100644 index 000000000000..1acd8cd12405 --- /dev/null +++ b/www/freenginx/files/extra-patch-nginx_mod_h264_streaming-config @@ -0,0 +1,41 @@ +--- ../nginx_mod_h264_streaming-2.2.7/config.orig 2020-04-27 12:01:37.153986000 -0400 ++++ ../nginx_mod_h264_streaming-2.2.7/config 2020-04-27 12:16:52.832788000 -0400 +@@ -1,26 +1,19 @@ + ngx_addon_name=ngx_http_h264_streaming_module +-HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_h264_streaming_module" +-CFLAGS="$CFLAGS -D_LARGEFILE_SOURCE -DBUILDING_NGINX" +- +-H264_STREAMING_MODULE_SOURCES="$ngx_addon_dir/src/ngx_http_h264_streaming_module.c \ ++ngx_module_name="$ngx_addon_name" ++ngx_module_type=HTTP_AUX_FILTER ++ngx_module_srcs="$ngx_addon_dir/src/ngx_http_h264_streaming_module.c \ + $ngx_addon_dir/src/moov.c \ + $ngx_addon_dir/src/mp4_io.c \ + $ngx_addon_dir/src/mp4_reader.c \ + $ngx_addon_dir/src/mp4_writer.c \ + $ngx_addon_dir/src/output_bucket.c \ + $ngx_addon_dir/src/output_mp4.c" +- +-for streaming_module_source in $H264_STREAMING_MODULE_SOURCES +-do +- already_included=NO +- for ngx_addon_src in $NGX_ADDON_SRCS +- do +- if [ "`basename $ngx_addon_src`" = "`basename $streaming_module_source`" ]; then +- already_included=YES +- break +- fi +- done +- if [ "$already_included" = "NO" ]; then +- NGX_ADDON_SRCS="$NGX_ADDON_SRCS $streaming_module_source" +- fi +-done ++ngx_module_deps="$ngx_addon_dir/src/mod_streaming_export.h \ ++ $ngx_addon_dir/src/moov.h \ ++ $ngx_addon_dir/src/mp4_io.h \ ++ $ngx_addon_dir/src/mp4_process.h \ ++ $ngx_addon_dir/src/mp4_reader.h \ ++ $ngx_addon_dir/src/mp4_writer.h \ ++ $ngx_addon_dir/src/output_bucket.h \ ++ $ngx_addon_dir/src/output_mp4.h" ++. auto/module diff --git a/www/freenginx/files/extra-patch-nginx_mogilefs_module-config b/www/freenginx/files/extra-patch-nginx_mogilefs_module-config new file mode 100644 index 000000000000..48a67fffb608 --- /dev/null +++ b/www/freenginx/files/extra-patch-nginx_mogilefs_module-config @@ -0,0 +1,13 @@ +--- ../nginx_mogilefs_module-1.0.4/config.orig 2020-04-25 17:51:45.544308000 -0400 ++++ ../nginx_mogilefs_module-1.0.4/config 2020-04-25 17:54:01.862958000 -0400 +@@ -1,3 +1,8 @@ + ngx_addon_name=ngx_http_mogilefs_module +-HTTP_MODULES="$HTTP_MODULES ngx_http_mogilefs_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mogilefs_module.c" ++ ++ngx_module_name="$ngx_addon_name" ++ngx_module_type=HTTP ++ ++ngx_module_srcs="$ngx_addon_dir/ngx_http_mogilefs_module.c" ++ ++. auto/module diff --git a/www/freenginx/files/extra-patch-ngx_http_auth_ldap_module.c b/www/freenginx/files/extra-patch-ngx_http_auth_ldap_module.c new file mode 100644 index 000000000000..d8bc2f7f65f3 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_auth_ldap_module.c @@ -0,0 +1,10 @@ +--- ../nginx-auth-ldap-83c059b/ngx_http_auth_ldap_module.c.orig 2022-08-21 17:04:57.754760000 +0300 ++++ ../nginx-auth-ldap-83c059b/ngx_http_auth_ldap_module.c 2022-08-21 17:08:46.939318000 +0300 +@@ -1779,6 +1779,7 @@ + } + + r->headers_out.www_authenticate->hash = 1; ++ r->headers_out.www_authenticate->next = NULL; + r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1; + r->headers_out.www_authenticate->key.data = (u_char *) "WWW-Authenticate"; + r->headers_out.www_authenticate->value = *realm; diff --git a/www/freenginx/files/extra-patch-ngx_http_dav_ext_module.c b/www/freenginx/files/extra-patch-ngx_http_dav_ext_module.c new file mode 100644 index 000000000000..cf66be99de49 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_dav_ext_module.c @@ -0,0 +1,15 @@ +--- ../nginx-dav-ext-module-3.0.0/ngx_http_dav_ext_module.c 2018-12-17 11:45:12.000000000 +0300 ++++ ../nginx-dav-ext-module-3.0.0/ngx_http_dav_ext_module.c 2020-03-13 01:20:47.498199000 +0300 +@@ -896,10 +896,9 @@ + ngx_cpystrn(last, name.data, name.len + 1); + + if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) { +- ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, ++ ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + ngx_de_info_n " \"%s\" failed", filename); +- rc = NGX_HTTP_INTERNAL_SERVER_ERROR; +- break; ++ continue; + } + } + diff --git a/www/freenginx/files/extra-patch-ngx_http_json_status_module-config b/www/freenginx/files/extra-patch-ngx_http_json_status_module-config new file mode 100644 index 000000000000..84c4a62ef6cf --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_json_status_module-config @@ -0,0 +1,12 @@ +--- ../ngx_http_json_status_module-1d2f303/config.orig 2020-04-25 16:16:47.024292000 -0400 ++++ ../ngx_http_json_status_module-1d2f303/config 2020-04-25 16:18:39.461340000 -0400 +@@ -1,4 +1,6 @@ + ngx_addon_name=ngx_http_json_status_module +-HTTP_MODULES="$HTTP_MODULES ngx_http_json_status_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_json_status_module.c" +-NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_http_json_status_module.h" ++ngx_module_name="$ngx_addon_name" ++ngx_module_type=HTTP ++ngx_module_srcs="$ngx_addon_dir/ngx_http_json_status_module.c" ++ngx_module_deps="$ngx_addon_dir/ngx_http_json_status_module.h" ++. auto/module diff --git a/www/freenginx/files/extra-patch-ngx_http_mogilefs_module.c b/www/freenginx/files/extra-patch-ngx_http_mogilefs_module.c new file mode 100644 index 000000000000..423483869d57 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_mogilefs_module.c @@ -0,0 +1,12 @@ +--- ../nginx_mogilefs_module-1.0.4/ngx_http_mogilefs_module.c.orig 2015-04-21 21:16:22.251692000 +0300 ++++ ../nginx_mogilefs_module-1.0.4/ngx_http_mogilefs_module.c 2015-04-21 21:16:39.460724000 +0300 +@@ -316,9 +316,6 @@ + + u->peer.log = r->connection->log; + u->peer.log_error = NGX_ERROR_ERR; +-#if (NGX_THREADS) +- u->peer.lock = &r->connection->lock; +-#endif + + u->output.tag = (ngx_buf_tag_t) &ngx_http_mogilefs_module; + diff --git a/www/freenginx/files/extra-patch-ngx_http_notice_module.c b/www/freenginx/files/extra-patch-ngx_http_notice_module.c new file mode 100644 index 000000000000..d62135ed6f9c --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_notice_module.c @@ -0,0 +1,11 @@ +--- ../nginx-notice-3c95966/ngx_http_notice_module.c.orig 2009-07-16 18:37:57.000000000 +0200 ++++ ../nginx-notice-3c95966/ngx_http_notice_module.c 2009-07-16 08:09:38.000000000 +0200 +@@ -153,7 +153,7 @@ + return NGX_HTTP_NOT_ALLOWED; + } + +- rc = ngx_http_discard_body(r); ++ rc = ngx_http_discard_request_body(r); + + if (rc != NGX_OK && rc != NGX_AGAIN) { + return rc; diff --git a/www/freenginx/files/extra-patch-ngx_http_redis_module.c b/www/freenginx/files/extra-patch-ngx_http_redis_module.c new file mode 100644 index 000000000000..3dacd39ee6c4 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_redis_module.c @@ -0,0 +1,34 @@ +--- ../ngx_http_redis-0.3.9/ngx_http_redis_module.c.orig 2022-07-10 22:10:19.031893000 -0400 ++++ ../ngx_http_redis-0.3.9/ngx_http_redis_module.c 2022-07-10 22:09:41.271731000 -0400 +@@ -562,7 +562,7 @@ + /* if defined gzip_flag... */ + if (rlcf->gzip_flag) { + /* hash init */ +- h = ngx_list_push(&r->upstream->headers_in.headers); ++ h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) { + return NGX_ERROR; + } +@@ -571,19 +571,11 @@ + * add Content-Encoding header for future gunzipping + * with ngx_http_gunzip_filter module + */ +- h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( +- ngx_hash(ngx_hash(ngx_hash( +- ngx_hash(ngx_hash(ngx_hash( +- ngx_hash(ngx_hash(ngx_hash( +- ngx_hash(ngx_hash('c', 'o'), 'n'), 't'), 'e'), +- 'n'), 't'), '-'), 'e'), 'n'), 'c'), 'o'), +- 'd'), 'i'), 'n'), 'g'); ++ h->hash = 1; ++ h->next = NULL; + ngx_str_set(&h->key, "Content-Encoding"); + ngx_str_set(&h->value, "gzip"); +- h->lowcase_key = (u_char*) "content-encoding"; +-#if (NGX_HTTP_GZIP) +- u->headers_in.content_encoding = h; +-#endif ++ r->headers_out.content_encoding = h; + } + + /* try to find end of string */ diff --git a/www/freenginx/files/extra-patch-ngx_http_sflow_config.c b/www/freenginx/files/extra-patch-ngx_http_sflow_config.c new file mode 100644 index 000000000000..3e3eaa2260f5 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_sflow_config.c @@ -0,0 +1,10 @@ +--- ../nginx-sflow-module-543c72a/ngx_http_sflow_config.c.orig 2012-06-07 04:52:57.000000000 +0400 ++++ ../nginx-sflow-module-543c72a/ngx_http_sflow_config.c 2012-06-07 04:59:18.000000000 +0400 +@@ -26,7 +26,6 @@ + ngx_log_error(NGX_LOG_ERR, log, 0, "getaddrinfo() failed: %s", gai_strerror(err)); + switch(err) { + case EAI_NONAME: break; +- case EAI_NODATA: break; + case EAI_AGAIN: break; // loop and try again? + default: ngx_log_error(NGX_LOG_ERR, log, 0, "getaddrinfo() error: %s", gai_strerror(err)); break; + } diff --git a/www/freenginx/files/extra-patch-ngx_http_sflow_config.h b/www/freenginx/files/extra-patch-ngx_http_sflow_config.h new file mode 100644 index 000000000000..6b90d4eaaf14 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_sflow_config.h @@ -0,0 +1,11 @@ +--- ../nginx-sflow-module-543c72a/ngx_http_sflow_config.h.orig 2012-07-31 21:36:49.000000000 +0400 ++++ ../nginx-sflow-module-543c72a/ngx_http_sflow_config.h 2012-07-31 21:37:20.000000000 +0400 +@@ -17,7 +17,7 @@ + -----------------___________________________------------------ + */ + +-#define SFWB_DEFAULT_CONFIGFILE "/etc/hsflowd.auto" ++#define SFWB_DEFAULT_CONFIGFILE "%%PREFIX%%/etc/hsflowd.auto" + #define SFWB_SEPARATORS " \t\r\n=" + #define SFWB_QUOTES "'\" \t\r\n" + /* SFWB_MAX LINE LEN must be enough to hold the whole list of targets */ diff --git a/www/freenginx/files/extra-patch-ngx_http_sflow_module.c b/www/freenginx/files/extra-patch-ngx_http_sflow_module.c new file mode 100644 index 000000000000..e6b4bad6e25f --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_sflow_module.c @@ -0,0 +1,59 @@ +--- ../nginx-sflow-module-543c72a/ngx_http_sflow_module.c.orig 2014-09-24 18:20:41.000000000 -0400 ++++ ../nginx-sflow-module-543c72a/ngx_http_sflow_module.c 2016-08-06 21:26:24.515559000 -0400 +@@ -8,9 +8,6 @@ + #include + #include + +-#if (NGX_THREADS) +-#include +-#endif + + #include "ngx_http_sflow_api.h" + #include "ngx_http_sflow_config.h" +@@ -74,18 +71,10 @@ + /* keep log ptr for callbacks */ + ngx_log_t *log; + +-#if (NGX_THREADS) +- ngx_mutex_t *mut; +-#define SFWB_LOCK(_s) ngx_mutex_lock((_s)->mut) +-#define SFWB_UNLOCK(_s) ngx_mutex_unlock((_s)->mut) +-#define SFWB_INC_CTR(_c) ngx_atomic_fetch_add(&(_c), 1) +-#define SFWB_COUNTDOWN(_c) (ngx_atomic_fetch_add(&(_c), -1) == 1) +-#else + #define SFWB_LOCK(_s) /* no-op */ + #define SFWB_UNLOCK(_s) /* no-op */ + #define SFWB_INC_CTR(_c) (_c)++ + #define SFWB_COUNTDOWN(_c) (--(_c) == 0) +-#endif + + /* skip countdown is handled per-worker to reduce lock contention. + * If all processes sample at 1:N it's the same as having only one +@@ -645,13 +634,8 @@ + { + sm->random_seed = ((sm->random_seed * 32719) + 3) % 32749; + ngx_atomic_t next_skip = (sm->random_seed % ((2 * sm->sampling_rate) - 1)) + 1; +-#if (NGX_THREADS) +- ngx_atomic_int_t test_skip = ngx_atomic_fetch_add(&sm->sflow_skip, next_skip); +- return (test_skip + next_skip); +-#else + sm->sflow_skip = next_skip; + return next_skip; +-#endif + } + + /*_________________---------------------------__________________ +@@ -864,13 +848,6 @@ + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "sflow: sfwb_init()"); + +-#if (NGX_THREADS) +- /* a mutex to lock the sFlow agent when taking a sample (only needed if there +- * is more that one worker thread - right now it seems like threads are not even +- * an option in the configure script) +- */ +- sm->mut = ngx_mutex_init(cf->log, 0); +-#endif + + /* look up some vars by name and cache the index numbers -- see ngx_http_variables.c */ + ngx_str_t str_uri = ngx_string("request_uri"); /* the "unparsed" variant */ diff --git a/www/freenginx/files/extra-patch-ngx_http_streaming_module.c b/www/freenginx/files/extra-patch-ngx_http_streaming_module.c new file mode 100644 index 000000000000..3641090a9b5d --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_streaming_module.c @@ -0,0 +1,13 @@ +--- ../nginx_mod_h264_streaming-2.2.7/src/ngx_http_streaming_module.c.orig 2010-05-24 18:04:43.000000000 +0400 ++++ ../nginx_mod_h264_streaming-2.2.7/src/ngx_http_streaming_module.c 2010-05-24 18:05:02.000000000 +0400 +@@ -155,10 +155,6 @@ + } + + /* TODO: Win32 */ +- if (r->zero_in_uri) +- { +- return NGX_DECLINED; +- } + + rc = ngx_http_discard_request_body(r); + diff --git a/www/freenginx/files/extra-patch-ngx_http_tarantool-config b/www/freenginx/files/extra-patch-ngx_http_tarantool-config new file mode 100644 index 000000000000..196a8135ca2d --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_tarantool-config @@ -0,0 +1,24 @@ +--- ../nginx_upstream_module-aeb8696/config.orig 2017-09-18 11:54:00.000000000 -0400 ++++ ../nginx_upstream_module-aeb8696/config 2017-12-04 21:56:41.978227000 -0500 +@@ -7,18 +7,10 @@ + $ngx_addon_dir/third_party \ + " + +-test -f $ngx_addon_dir/third_party/yajl/build/yajl-2.1.0/lib/libyajl_s.a && +-test -f $ngx_addon_dir/third_party/msgpuck/libmsgpuck.a && { +- libs=" \ +- $ngx_addon_dir/third_party/yajl/build/yajl-2.1.0/lib/libyajl_s.a \ +- $ngx_addon_dir/third_party/msgpuck/libmsgpuck.a \ +- " +- +- include_paths="${include_paths} \ +- $ngx_addon_dir/third_party/msgpuck \ +- $ngx_addon_dir/third_party/yajl/build/yajl-2.1.0/include \ ++include_paths="${include_paths} \ ++ %%PREFIX%%/include/msgpuck \ ++ %%PREFIX%%/include/yajl \ + " +-} + + module_src_dir="$ngx_addon_dir/src" + diff --git a/www/freenginx/files/extra-patch-ngx_http_uploadprogress_module.c b/www/freenginx/files/extra-patch-ngx_http_uploadprogress_module.c new file mode 100644 index 000000000000..5dfbdbfea794 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_http_uploadprogress_module.c @@ -0,0 +1,73 @@ +--- ../nginx-upload-progress-module-68b3ab3/ngx_http_uploadprogress_module.c.orig 2021-12-24 10:53:38.000000000 -0500 ++++ ../nginx-upload-progress-module-68b3ab3/ngx_http_uploadprogress_module.c 2022-07-10 22:24:32.435330000 -0400 +@@ -559,12 +559,12 @@ + ngx_chain_t out; + ngx_int_t rc, found=0, done=0, err_status=0; + off_t rest=0, length=0; +- ngx_uint_t len, i; ++ ngx_uint_t len; + ngx_slab_pool_t *shpool; + ngx_http_uploadprogress_conf_t *upcf; + ngx_http_uploadprogress_ctx_t *ctx; + ngx_http_uploadprogress_node_t *up; +- ngx_table_elt_t *expires, *cc, **ccp; ++ ngx_table_elt_t *expires, *cc; + ngx_http_uploadprogress_state_t state; + ngx_http_uploadprogress_template_t *t; + +@@ -637,6 +637,7 @@ + } + + r->headers_out.expires = expires; ++ expires->next = NULL; + + expires->hash = 1; + expires->key.len = sizeof("Expires") - 1; +@@ -646,37 +647,30 @@ + len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT"); + expires->value.len = len - 1; + +- ccp = r->headers_out.cache_control.elts; +- if (ccp == NULL) { ++ cc = r->headers_out.cache_control; + +- if (ngx_array_init(&r->headers_out.cache_control, r->pool, +- 1, sizeof(ngx_table_elt_t *)) +- != NGX_OK) { +- return NGX_HTTP_INTERNAL_SERVER_ERROR; +- } ++ if (cc == NULL) { + +- ccp = ngx_array_push(&r->headers_out.cache_control); +- if (ccp == NULL) { +- return NGX_HTTP_INTERNAL_SERVER_ERROR; +- } +- + cc = ngx_list_push(&r->headers_out.headers); + if (cc == NULL) { ++ expires->hash = 0; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ++ r->headers_out.cache_control = cc; ++ cc->next = NULL; ++ + cc->hash = 1; + cc->key.len = sizeof("Cache-Control") - 1; + cc->key.data = (u_char *) "Cache-Control"; + +- *ccp = cc; +- + } else { +- for (i = 1; i < r->headers_out.cache_control.nelts; i++) { +- ccp[i]->hash = 0; ++ for (cc = cc->next; cc; cc = cc->next) { ++ cc->hash = 0; + } + +- cc = ccp[0]; ++ cc = r->headers_out.cache_control; ++ cc->next = NULL; + } + + expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT"; diff --git a/www/freenginx/files/extra-patch-ngx_link_func_module.c b/www/freenginx/files/extra-patch-ngx_link_func_module.c new file mode 100644 index 000000000000..31348a346dae --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_link_func_module.c @@ -0,0 +1,11 @@ +--- ../nginx-link-function-3.2.4/src/ngx_link_func_module.c.orig 2020-01-24 10:32:50.550797000 -0500 ++++ ../nginx-link-function-3.2.4/src/ngx_link_func_module.c 2020-01-24 10:33:17.135348000 -0500 +@@ -35,7 +35,7 @@ + #include + #include + #include +-#include ++#include "ngx_link_func_module.h" + + #define MODULE_NAME "nginx_link_function" + diff --git a/www/freenginx/files/extra-patch-ngx_postgres-config b/www/freenginx/files/extra-patch-ngx_postgres-config new file mode 100644 index 000000000000..8c26c0f7dd64 --- /dev/null +++ b/www/freenginx/files/extra-patch-ngx_postgres-config @@ -0,0 +1,19 @@ +--- ../ngx_postgres-8aa7359/config.orig 2015-05-26 20:52:25.649166000 +0300 ++++ ../ngx_postgres-8aa7359/config 2015-05-26 20:53:10.331122000 +0300 +@@ -48,12 +48,12 @@ + + if [ $ngx_found = no ]; then + # FreeBSD +- ngx_feature="libpq library in /usr/local/" +- ngx_feature_path="/usr/local/include" ++ ngx_feature="libpq library in %%PREFIX%%/" ++ ngx_feature_path="%%PREFIX%%/include" + if [ $NGX_RPATH = YES ]; then +- ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lpq" ++ ngx_feature_libs="-R%%PREFIX%%/lib -L%%PREFIX%%/lib -lpq" + else +- ngx_feature_libs="-L/usr/local/lib -lpq" ++ ngx_feature_libs="-L%%PREFIX%%/lib -lpq" + fi + . auto/feature + fi diff --git a/www/freenginx/files/extra-patch-openresty-drizzle-nginx-module-config b/www/freenginx/files/extra-patch-openresty-drizzle-nginx-module-config new file mode 100644 index 000000000000..294d22a346cf --- /dev/null +++ b/www/freenginx/files/extra-patch-openresty-drizzle-nginx-module-config @@ -0,0 +1,42 @@ +--- ../drizzle-nginx-module-3504fc6/config.orig 2020-01-22 18:04:58.000000000 -0500 ++++ ../drizzle-nginx-module-3504fc6/config 2020-04-24 06:23:01.264872000 -0400 +@@ -34,12 +34,12 @@ + + if [ $ngx_found = no ]; then + # FreeBSD, OpenBSD +- ngx_feature="libdrizzle library in /usr/local/" +- ngx_feature_path="/usr/local/include/libdrizzle-1.0" ++ ngx_feature="libdrizzle library in %%PREFIX%%" ++ ngx_feature_path="%%PREFIX%%/include/libdrizzle" + if [ $NGX_RPATH = YES ]; then +- ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -ldrizzle" ++ ngx_feature_libs="-R%%PREFIX%%/lib -L%%PREFIX%%/lib -ldrizzle" + else +- ngx_feature_libs="-L/usr/local/lib -ldrizzle" ++ ngx_feature_libs="-L%%PREFIX%%/lib -ldrizzle" + fi + . auto/feature + fi +@@ -80,8 +80,19 @@ + fi + + ngx_addon_name=ngx_http_drizzle_module +-HTTP_MODULES="$HTTP_MODULES ngx_http_drizzle_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_drizzle_module.c $ngx_addon_dir/src/ngx_http_drizzle_handler.c $ngx_addon_dir/src/ngx_http_drizzle_processor.c $ngx_addon_dir/src/ngx_http_drizzle_upstream.c $ngx_addon_dir/src/ngx_http_drizzle_util.c $ngx_addon_dir/src/ngx_http_drizzle_output.c $ngx_addon_dir/src/ngx_http_drizzle_keepalive.c $ngx_addon_dir/src/ngx_http_drizzle_quoting.c $ngx_addon_dir/src/ngx_http_drizzle_checker.c" +-NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/ngx_http_drizzle_module.h $ngx_addon_dir/src/ngx_http_drizzle_handler.h $ngx_addon_dir/src/ngx_http_drizzle_processor.h $ngx_addon_dir/src/ngx_http_drizzle_upstream.h $ngx_addon_dir/src/ngx_http_drizzle_util.h $ngx_addon_dir/src/ngx_http_drizzle_output.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_drizzle_keepalive.h $ngx_addon_dir/src/ngx_http_drizzle_quoting.h $ngx_addon_dir/src/ngx_http_drizzle_checker.h" ++if test -n "$ngx_module_link"; then ++ ngx_module_type=HTTP ++ ngx_module_name=$ngx_addon_name ++ ngx_module_srcs="$ngx_addon_dir/src/ngx_http_drizzle_module.c $ngx_addon_dir/src/ngx_http_drizzle_handler.c $ngx_addon_dir/src/ngx_http_drizzle_processor.c $ngx_addon_dir/src/ngx_http_drizzle_upstream.c $ngx_addon_dir/src/ngx_http_drizzle_util.c $ngx_addon_dir/src/ngx_http_drizzle_output.c $ngx_addon_dir/src/ngx_http_drizzle_keepalive.c $ngx_addon_dir/src/ngx_http_drizzle_quoting.c $ngx_addon_dir/src/ngx_http_drizzle_checker.c" ++ ngx_module_incs="$ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/ngx_http_drizzle_module.h $ngx_addon_dir/src/ngx_http_drizzle_handler.h $ngx_addon_dir/src/ngx_http_drizzle_processor.h $ngx_addon_dir/src/ngx_http_drizzle_upstream.h $ngx_addon_dir/src/ngx_http_drizzle_util.h $ngx_addon_dir/src/ngx_http_drizzle_output.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_drizzle_keepalive.h $ngx_addon_dir/src/ngx_http_drizzle_quoting.h $ngx_addon_dir/src/ngx_http_drizzle_checker.h" ++ ngx_module_deps= ++ ngx_module_libs="-L%%PREFIX%%/lib -ldrizzle" ++ ++ . auto/module ++else ++ HTTP_MODULES="$HTTP_MODULES ngx_http_drizzle_module" ++ NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_drizzle_module.c $ngx_addon_dir/src/ngx_http_drizzle_handler.c $ngx_addon_dir/src/ngx_http_drizzle_processor.c $ngx_addon_dir/src/ngx_http_drizzle_upstream.c $ngx_addon_dir/src/ngx_http_drizzle_util.c $ngx_addon_dir/src/ngx_http_drizzle_output.c $ngx_addon_dir/src/ngx_http_drizzle_keepalive.c $ngx_addon_dir/src/ngx_http_drizzle_quoting.c $ngx_addon_dir/src/ngx_http_drizzle_checker.c" ++ NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/ngx_http_drizzle_module.h $ngx_addon_dir/src/ngx_http_drizzle_handler.h $ngx_addon_dir/src/ngx_http_drizzle_processor.h $ngx_addon_dir/src/ngx_http_drizzle_upstream.h $ngx_addon_dir/src/ngx_http_drizzle_util.h $ngx_addon_dir/src/ngx_http_drizzle_output.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_drizzle_keepalive.h $ngx_addon_dir/src/ngx_http_drizzle_quoting.h $ngx_addon_dir/src/ngx_http_drizzle_checker.h" ++fi + + have=NGX_DRIZZLE_MODULE . auto/have diff --git a/www/freenginx/files/extra-patch-passenger-build-nginx.rb b/www/freenginx/files/extra-patch-passenger-build-nginx.rb new file mode 100644 index 000000000000..8225f4f10931 --- /dev/null +++ b/www/freenginx/files/extra-patch-passenger-build-nginx.rb @@ -0,0 +1,33 @@ +--- ../passenger-6.0.17/build/nginx.rb.orig 2013-10-26 18:00:00.000000000 -0400 ++++ ../passenger-6.0.17/build/nginx.rb 2016-05-09 18:21:22.426777000 -0400 +@@ -33,13 +33,12 @@ + desc "Build Nginx support files" + task :nginx => [ + :nginx_without_native_support, +- NATIVE_SUPPORT_TARGET ++ LIBBOOST_OXT, + ].compact + + desc "Build Nginx support files, including objects suitable for dynamic linking against Nginx" + task 'nginx:as_dynamic_module' => [ + :nginx_dynamic_without_native_support, +- NATIVE_SUPPORT_TARGET + ].compact + + # Workaround for https://github.com/jimweirich/rake/issues/274 +@@ -47,7 +46,6 @@ + + task :nginx_without_native_support => [ + auto_generated_sources, +- AGENT_TARGET, + COMMON_LIBRARY.only(*NGINX_LIBS_SELECTOR).link_objects + ].flatten + +@@ -55,7 +53,6 @@ + # it also creates a namespace:clean task to clean up the output_dir + task :nginx_dynamic_without_native_support => [ + auto_generated_sources, +- AGENT_TARGET, + define_libboost_oxt_task("nginx", NGINX_DYNAMIC_OUTPUT_DIR + "libboost_oxt", "-fPIC"), + COMMON_LIBRARY.only(*NGINX_LIBS_SELECTOR). + set_namespace("nginx").set_output_dir(NGINX_DYNAMIC_OUTPUT_DIR + "module_libpassenger_common").define_tasks("-fPIC"). diff --git a/www/freenginx/files/extra-patch-passenger-disable-telemetry b/www/freenginx/files/extra-patch-passenger-disable-telemetry new file mode 100644 index 000000000000..adfafb141d70 --- /dev/null +++ b/www/freenginx/files/extra-patch-passenger-disable-telemetry @@ -0,0 +1,11 @@ +--- ../passenger-6.0.17/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb.orig 2018-12-03 12:23:06.980728000 -0500 ++++ ../passenger-6.0.17/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb 2018-12-03 12:23:32.978924000 -0500 +@@ -204,7 +204,7 @@ + :name => 'passenger_disable_anonymous_telemetry', + :scope => :global, + :type => :flag, +- :default => false, ++ :default => true, + :context => [:main], + :struct => 'NGX_HTTP_MAIN_CONF_OFFSET' + }, diff --git a/www/freenginx/files/extra-patch-rds-csv-nginx-module-config b/www/freenginx/files/extra-patch-rds-csv-nginx-module-config new file mode 100644 index 000000000000..040c78a481e0 --- /dev/null +++ b/www/freenginx/files/extra-patch-rds-csv-nginx-module-config @@ -0,0 +1,15 @@ +--- ../rds-csv-nginx-module-0.09/config.orig 2020-04-25 11:37:21.896900000 -0400 ++++ ../rds-csv-nginx-module-0.09/config 2020-04-25 11:39:06.371176000 -0400 +@@ -1,5 +1,9 @@ + ngx_addon_name=ngx_http_rds_csv_filter_module +-HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_rds_csv_filter_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_rds_csv_filter_module.c $ngx_addon_dir/src/ngx_http_rds_csv_processor.c $ngx_addon_dir/src/ngx_http_rds_csv_util.c $ngx_addon_dir/src/ngx_http_rds_csv_output.c" +-NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_csv_filter_module.h $ngx_addon_dir/src/ngx_http_rds_csv_processor.h $ngx_addon_dir/src/ngx_http_rds_csv_util.h $ngx_addon_dir/src/ngx_http_rds.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_csv_output.h $ngx_addon_dir/src/ngx_http_rds_utils.h" + ++ngx_module_name="$ngx_addon_name" ++ngx_module_type=HTTP_FILTER ++ ++ngx_module_srcs="$ngx_addon_dir/src/ngx_http_rds_csv_filter_module.c $ngx_addon_dir/src/ngx_http_rds_csv_processor.c $ngx_addon_dir/src/ngx_http_rds_csv_util.c $ngx_addon_dir/src/ngx_http_rds_csv_output.c" ++ngx_module_deps="$ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_csv_filter_module.h $ngx_addon_dir/src/ngx_http_rds_csv_processor.h $ngx_addon_dir/src/ngx_http_rds_csv_util.h $ngx_addon_dir/src/ngx_http_rds.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_csv_output.h $ngx_addon_dir/src/ngx_http_rds_utils.h" ++ ++. auto/module diff --git a/www/freenginx/files/extra-patch-rds-json-nginx-module-config b/www/freenginx/files/extra-patch-rds-json-nginx-module-config new file mode 100644 index 000000000000..f3252ac0ba50 --- /dev/null +++ b/www/freenginx/files/extra-patch-rds-json-nginx-module-config @@ -0,0 +1,15 @@ +--- ../rds-json-nginx-module-0.15/config.orig 2020-04-25 11:39:29.003855000 -0400 ++++ ../rds-json-nginx-module-0.15/config 2020-04-25 11:40:18.345148000 -0400 +@@ -1,5 +1,9 @@ + ngx_addon_name=ngx_http_rds_json_filter_module +-HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_rds_json_filter_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_rds_json_filter_module.c $ngx_addon_dir/src/ngx_http_rds_json_processor.c $ngx_addon_dir/src/ngx_http_rds_json_util.c $ngx_addon_dir/src/ngx_http_rds_json_output.c $ngx_addon_dir/src/ngx_http_rds_json_handler.c" +-NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_json_filter_module.h $ngx_addon_dir/src/ngx_http_rds_json_processor.h $ngx_addon_dir/src/ngx_http_rds_json_util.h $ngx_addon_dir/src/ngx_http_rds.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_json_output.h $ngx_addon_dir/src/ngx_http_rds_utils.h $ngx_addon_dir/src/ngx_http_rds_json_handler.h" + ++ngx_module_name="$ngx_addon_name" ++ngx_module_type=HTTP_FILTER ++ ++ngx_module_srcs="$ngx_addon_dir/src/ngx_http_rds_json_filter_module.c $ngx_addon_dir/src/ngx_http_rds_json_processor.c $ngx_addon_dir/src/ngx_http_rds_json_util.c $ngx_addon_dir/src/ngx_http_rds_json_output.c $ngx_addon_dir/src/ngx_http_rds_json_handler.c" ++ngx_module_deps="$ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_json_filter_module.h $ngx_addon_dir/src/ngx_http_rds_json_processor.h $ngx_addon_dir/src/ngx_http_rds_json_util.h $ngx_addon_dir/src/ngx_http_rds.h $ngx_addon_dir/src/resty_dbd_stream.h $ngx_addon_dir/src/ngx_http_rds_json_output.h $ngx_addon_dir/src/ngx_http_rds_utils.h $ngx_addon_dir/src/ngx_http_rds_json_handler.h" ++ ++. auto/module diff --git a/www/freenginx/files/extra-patch-spnego-http-auth-nginx-module-config b/www/freenginx/files/extra-patch-spnego-http-auth-nginx-module-config new file mode 100644 index 000000000000..a54e89e58a23 --- /dev/null +++ b/www/freenginx/files/extra-patch-spnego-http-auth-nginx-module-config @@ -0,0 +1,14 @@ +--- ../spnego-http-auth-nginx-module-3575542/config.orig 2020-08-27 07:59:28.423636000 -0400 ++++ ../spnego-http-auth-nginx-module-3575542/config 2020-08-27 08:01:42.152121000 -0400 +@@ -1,8 +1,9 @@ + ngx_addon_name=ngx_http_auth_spnego_module +-ngx_feature_libs="-lgssapi_krb5 -lkrb5 -lcom_err" ++ngx_feature_libs="-L%%GSSAPILIBDIR%% %%GSSAPILIBS%%" ++ngx_module_incs="%%GSSAPIINCDIR%%" + + if uname -o | grep -q FreeBSD; then +- ngx_feature_libs="$ngx_feature_libs -lgssapi" ++ ngx_feature_libs="$ngx_feature_libs" + fi + + if uname -a | grep -q NetBSD; then diff --git a/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_hash_module.c b/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_hash_module.c new file mode 100644 index 000000000000..9267e597829f --- /dev/null +++ b/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_hash_module.c @@ -0,0 +1,44 @@ +--- src/http/modules/ngx_http_upstream_hash_module.c.orig 2016-02-24 14:53:24 UTC ++++ src/http/modules/ngx_http_upstream_hash_module.c +@@ -9,6 +9,9 @@ + #include + #include + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++#include "ngx_http_upstream_check_module.h" ++#endif + + typedef struct { + uint32_t hash; +@@ -235,6 +238,15 @@ ngx_http_upstream_get_hash_peer(ngx_peer + goto next; + } + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, ++ "get hash peer, check_index: %ui", ++ peer->check_index); ++ if (ngx_http_upstream_check_peer_down(peer->check_index)) { ++ goto next; ++ } ++#endif ++ + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) +@@ -535,6 +547,15 @@ ngx_http_upstream_get_chash_peer(ngx_pee + continue; + } + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, ++ "get consistent_hash peer, check_index: %ui", ++ peer->check_index); ++ if (ngx_http_upstream_check_peer_down(peer->check_index)) { ++ continue; ++ } ++#endif ++ + if (peer->server.len != server->len + || ngx_strncmp(peer->server.data, server->data, server->len) + != 0) diff --git a/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_ip_hash_module.c b/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_ip_hash_module.c new file mode 100644 index 000000000000..176876b9d23f --- /dev/null +++ b/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_ip_hash_module.c @@ -0,0 +1,28 @@ +--- src/http/modules/ngx_http_upstream_ip_hash_module.c.orig 2016-02-24 14:53:24 UTC ++++ src/http/modules/ngx_http_upstream_ip_hash_module.c +@@ -9,6 +9,9 @@ + #include + #include + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++#include "ngx_http_upstream_check_module.h" ++#endif + + typedef struct { + /* the round robin data must be first */ +@@ -205,6 +208,15 @@ ngx_http_upstream_get_ip_hash_peer(ngx_p + goto next; + } + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, ++ "get ip_hash peer, check_index: %ui", ++ peer->check_index); ++ if (ngx_http_upstream_check_peer_down(peer->check_index)) { ++ goto next; ++ } ++#endif ++ + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) diff --git a/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_least_conn_module.c b/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_least_conn_module.c new file mode 100644 index 000000000000..84f88fd4d8cd --- /dev/null +++ b/www/freenginx/files/extra-patch-src-http-modules-ngx_http_upstream_least_conn_module.c @@ -0,0 +1,46 @@ +--- src/http/modules/ngx_http_upstream_least_conn_module.c.orig 2016-02-24 14:53:24 UTC ++++ src/http/modules/ngx_http_upstream_least_conn_module.c +@@ -9,6 +9,9 @@ + #include + #include + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++#include "ngx_http_upstream_check_module.h" ++#endif + + static ngx_int_t ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r, + ngx_http_upstream_srv_conf_t *us); +@@ -148,6 +151,16 @@ ngx_http_upstream_get_least_conn_peer(ng + continue; + } + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, ++ "get least_conn peer, check_index: %ui", ++ peer->check_index); ++ ++ if (ngx_http_upstream_check_peer_down(peer->check_index)) { ++ continue; ++ } ++#endif ++ + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) +@@ -199,6 +212,16 @@ ngx_http_upstream_get_least_conn_peer(ng + continue; + } + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, ++ "get least_conn peer, check_index: %ui", ++ peer->check_index); ++ ++ if (ngx_http_upstream_check_peer_down(peer->check_index)) { ++ continue; ++ } ++#endif ++ + if (peer->conns * best->weight != best->conns * peer->weight) { + continue; + } diff --git a/www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.c b/www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.c new file mode 100644 index 000000000000..e5012b098309 --- /dev/null +++ b/www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.c @@ -0,0 +1,101 @@ +--- src/http/ngx_http_upstream_round_robin.c.orig 2016-02-24 14:53:24 UTC ++++ src/http/ngx_http_upstream_round_robin.c +@@ -9,6 +9,9 @@ + #include + #include + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++#include "ngx_http_upstream_check_module.h" ++#endif + + #define ngx_http_upstream_tries(p) ((p)->number \ + + ((p)->next ? (p)->next->number : 0)) +@@ -96,7 +99,14 @@ ngx_http_upstream_init_round_robin(ngx_c + peer[n].fail_timeout = server[i].fail_timeout; + peer[n].down = server[i].down; + peer[n].server = server[i].name; +- ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ if (!server[i].down) { ++ peer[n].check_index = ++ ngx_http_upstream_check_add_peer(cf, us, &server[i].addrs[j]); ++ } else { ++ peer[n].check_index = (ngx_uint_t) NGX_ERROR; ++ } ++#endif + *peerp = &peer[n]; + peerp = &peer[n].next; + n++; +@@ -159,7 +169,15 @@ ngx_http_upstream_init_round_robin(ngx_c + peer[n].fail_timeout = server[i].fail_timeout; + peer[n].down = server[i].down; + peer[n].server = server[i].name; +- ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ if (!server[i].down) { ++ peer[n].check_index = ++ ngx_http_upstream_check_add_peer(cf, us, &server[i].addrs[j]); ++ } ++ else { ++ peer[n].check_index = (ngx_uint_t) NGX_ERROR; ++ } ++#endif + *peerp = &peer[n]; + peerp = &peer[n].next; + n++; +@@ -225,6 +243,9 @@ ngx_http_upstream_init_round_robin(ngx_c + peer[i].current_weight = 0; + peer[i].max_fails = 1; + peer[i].fail_timeout = 10; ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ peer[i].check_index = (ngx_uint_t) NGX_ERROR; ++#endif + *peerp = &peer[i]; + peerp = &peer[i].next; + } +@@ -339,6 +360,9 @@ ngx_http_upstream_create_round_robin_pee + peer[0].current_weight = 0; + peer[0].max_fails = 1; + peer[0].fail_timeout = 10; ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ peer[0].check_index = (ngx_uint_t) NGX_ERROR; ++#endif + peers->peer = peer; + + } else { +@@ -381,6 +405,9 @@ ngx_http_upstream_create_round_robin_pee + peer[i].current_weight = 0; + peer[i].max_fails = 1; + peer[i].fail_timeout = 10; ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ peer[i].check_index = (ngx_uint_t) NGX_ERROR; ++#endif + *peerp = &peer[i]; + peerp = &peer[i].next; + } +@@ -441,6 +468,12 @@ ngx_http_upstream_get_round_robin_peer(n + goto failed; + } + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ if (ngx_http_upstream_check_peer_down(peer->check_index)) { ++ goto failed; ++ } ++#endif ++ + rrp->current = peer; + + } else { +@@ -542,6 +575,12 @@ ngx_http_upstream_get_peer(ngx_http_upst + continue; + } + ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ if (ngx_http_upstream_check_peer_down(peer->check_index)) { ++ continue; ++ } ++#endif ++ + if (peer->max_fails + && peer->fails >= peer->max_fails + && now - peer->checked <= peer->fail_timeout) diff --git a/www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.h b/www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.h new file mode 100644 index 000000000000..84c99aaaed61 --- /dev/null +++ b/www/freenginx/files/extra-patch-src-http-ngx_http_upstream_round_robin.h @@ -0,0 +1,14 @@ +--- src/http/ngx_http_upstream_round_robin.h.orig 2016-10-11 11:03:02.000000000 -0400 ++++ src/http/ngx_http_upstream_round_robin.h 2016-10-11 19:56:33.262307000 -0400 +@@ -35,6 +35,11 @@ + + ngx_uint_t max_fails; + time_t fail_timeout; ++ ++#if (NGX_HTTP_UPSTREAM_CHECK) ++ ngx_uint_t check_index; ++#endif ++ + ngx_msec_t slow_start; + ngx_msec_t start_time; + diff --git a/www/freenginx/files/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c b/www/freenginx/files/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c new file mode 100644 index 000000000000..b9eb1f20a439 --- /dev/null +++ b/www/freenginx/files/extra-patch-src_http_modules_ngx_http_slice_read_ahead.c @@ -0,0 +1,456 @@ +--- src/http/modules/ngx_http_slice_filter_module.c.orig 2019-04-23 13:12:58 UTC ++++ src/http/modules/ngx_http_slice_filter_module.c +@@ -2,6 +2,10 @@ + /* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. ++ * Copyright (C) Carey Gister ++ * Copyright (C) Metapeer, Inc. ++ * ++ * Retrieve slices with an optional look-a-head of N slices where N is a float value. + */ + + +@@ -9,13 +13,25 @@ + #include + #include + ++/* ++ * Location Configuration -- size is size of a slice, read_a_heads is number of ++ * blocks to look a head: 0, will not limit the number of blocks. Blocks will be ++ * retrieved as quickly as GETs can be issued and returned. ++ */ + + typedef struct { + size_t size; ++ float read_a_heads; + } ngx_http_slice_loc_conf_t; + + + typedef struct { ++ size_t requested_bytes; ++ size_t received_bytes; ++ size_t skipped_first_slice; ++} ngx_http_slice_read_a_head_t; ++ ++typedef struct { + off_t start; + off_t end; + ngx_str_t range; +@@ -23,6 +39,7 @@ typedef struct { + unsigned last:1; + unsigned active:1; + ngx_http_request_t *sr; ++ ngx_http_slice_read_a_head_t *read_a_head; + } ngx_http_slice_ctx_t; + + +@@ -46,6 +63,8 @@ static char *ngx_http_slice_merge_loc_conf(ngx_conf_t + void *child); + static ngx_int_t ngx_http_slice_add_variables(ngx_conf_t *cf); + static ngx_int_t ngx_http_slice_init(ngx_conf_t *cf); ++static char * ngx_conf_set_float_slot(ngx_conf_t *cf, ngx_command_t *cmd, ++ void *conf); + + + static ngx_command_t ngx_http_slice_filter_commands[] = { +@@ -57,6 +76,13 @@ static ngx_command_t ngx_http_slice_filter_commands[] + offsetof(ngx_http_slice_loc_conf_t, size), + NULL }, + ++ { ngx_string("slice_read_ahead"), ++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ++ ngx_conf_set_float_slot, ++ NGX_HTTP_LOC_CONF_OFFSET, ++ offsetof(ngx_http_slice_loc_conf_t, read_a_heads), ++ NULL }, ++ + ngx_null_command + }; + +@@ -102,11 +128,11 @@ static ngx_int_t + ngx_http_slice_header_filter(ngx_http_request_t *r) + { + off_t end; +- ngx_int_t rc; ++ ngx_int_t rc, rc1; + ngx_table_elt_t *h; + ngx_http_slice_ctx_t *ctx; + ngx_http_slice_loc_conf_t *slcf; +- ngx_http_slice_content_range_t cr; ++ ngx_http_slice_content_range_t cr, cr1; + + ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module); + if (ctx == NULL) { +@@ -187,6 +213,23 @@ ngx_http_slice_header_filter(ngx_http_request_t *r) + rc = ngx_http_next_header_filter(r); + + if (r != r->main) { ++ if (ctx->read_a_head != NULL) { ++ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (subrequest) requested_bytes: %uz, size: %uz, range: %O/%O, end: %O", ++ ctx->read_a_head->requested_bytes, slcf->size, cr.start, ++ cr.end, end); ++ ++ if (end != cr.start) { ++ ctx->read_a_head->requested_bytes += ++ ngx_min(slcf->size, ++ (size_t) end - (size_t) cr.start); ++ } ++ ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (subrequest) new requested_bytes: %uz, size: %uz", ++ ctx->read_a_head->requested_bytes, slcf->size); ++ } ++ + return rc; + } + +@@ -201,8 +244,68 @@ ngx_http_slice_header_filter(ngx_http_request_t *r) + ctx->end = r->headers_out.content_offset + + r->headers_out.content_length_n; + ++ /* Update requested bytes for the new chunk. */ ++ if (ctx->read_a_head != NULL) { ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (main request, partial) requested_bytes: %uz, size: %uz, start/end (%O/%O)", ++ ctx->read_a_head->requested_bytes, slcf->size, ++ ctx->start, ctx->end); ++ ++ if (ctx->end != ctx->start) { ++ ctx->read_a_head->requested_bytes += ++ ngx_min(slcf->size, ++ (size_t) ctx->end - (size_t) ctx->start); ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (main request, partial) new requested_bytes: %uz, size: %uz, start/end (%O/%O)", ++ ctx->read_a_head->requested_bytes, slcf->size, ctx->start, ctx->end); ++ ++ /* Parse the new Content-Range, which may have been set by the Range ++ filter. If the start changed, then adjust the requested_byte count ++ by the difference between the slice start and the actual start. ++ These bytes will never be received. */ ++ ++ rc1 = ngx_http_slice_parse_content_range(r, &cr1); ++ ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (main request, partial): rc1: %d", ++ rc1); ++ ++ if (rc1 == NGX_OK) { ++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (main request, partial new content range) cr1.start: %uz, cr1.end: %uz", ++ cr1.start, cr1.end); ++ ++ if (cr1.start != cr.start) { ++ ctx->read_a_head->skipped_first_slice = ++ cr1.start - (slcf->size * (cr1.start / slcf->size)); ++ ++ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (main request, partial) new cr.start: %uz, initial start: %uz, skipped first slice: %uz", ++ cr1.start, cr.start, ++ ctx->read_a_head->skipped_first_slice); ++ } ++ } ++ } + } else { + ctx->end = cr.complete_length; ++ ++ /* Update the requested bytes for the new chunk. */ ++ if (ctx->read_a_head != NULL) { ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (main request, full) requested_bytes: %uz, size: %uz, start/end (%O/%O)", ++ ctx->read_a_head->requested_bytes, slcf->size, ctx->start, ctx->end); ++ ++ if (ctx->end != ctx->start) { ++ ctx->read_a_head->requested_bytes += ++ ngx_min(slcf->size, ++ (size_t) ctx->end - (size_t) ctx->start); ++ } ++ ++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice header (main request, full) new requested_bytes: %uz, size: %uz, start/end (%O/%O)", ++ ctx->read_a_head->requested_bytes, slcf->size, ctx->start, ctx->end); ++ } + } + + return rc; +@@ -216,14 +319,31 @@ ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_ + ngx_chain_t *cl; + ngx_http_slice_ctx_t *ctx; + ngx_http_slice_loc_conf_t *slcf; ++ size_t received, read_a_head_window, read_a_head_size; + + ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module); + +- if (ctx == NULL || r != r->main) { ++ if (ctx == NULL) { + return ngx_http_next_body_filter(r, in); + } + ++ if (r != r->main) { ++ if (ctx->read_a_head != NULL) { ++ received = 0; ++ for (cl = in; cl; cl = cl->next) { ++ received = received + ngx_buf_size(cl->buf); ++ } ++ ++ ctx->read_a_head->received_bytes += received; ++ } ++ ++ return ngx_http_next_body_filter(r, in); ++ } ++ ++ /* For the main request */ ++ received = 0; + for (cl = in; cl; cl = cl->next) { ++ received = received + ngx_buf_size(cl->buf); + if (cl->buf->last_buf) { + cl->buf->last_buf = 0; + cl->buf->last_in_chain = 1; +@@ -232,6 +352,10 @@ ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_ + } + } + ++ if (ctx->read_a_head != NULL) { ++ ctx->read_a_head->received_bytes += received; ++ } ++ + rc = ngx_http_next_body_filter(r, in); + + if (rc == NGX_ERROR || !ctx->last) { +@@ -258,6 +382,20 @@ ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_ + return rc; + } + ++ slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module); ++ ++ if (ctx->read_a_head != NULL) { ++ read_a_head_size = (size_t) (slcf->size * slcf->read_a_heads); ++ read_a_head_window = r->connection->sent + read_a_head_size; ++ ++ if ((r->connection->sent != 0) && ++ ((read_a_head_window + ctx->read_a_head->skipped_first_slice) < ctx->read_a_head->requested_bytes)) { ++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ++ "http slice body filter defer subrequest: returning NGX_AGAIN"); ++ return NGX_AGAIN; ++ } ++ } ++ + if (ngx_http_subrequest(r, &r->uri, &r->args, &ctx->sr, NULL, + NGX_HTTP_SUBREQUEST_CLONE) + != NGX_OK) +@@ -267,8 +405,6 @@ ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_ + + ngx_http_set_ctx(ctx->sr, ctx, ngx_http_slice_filter_module); + +- slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module); +- + ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start, + ctx->start + (off_t) slcf->size - 1) + - ctx->range.data; +@@ -287,6 +423,7 @@ ngx_http_slice_parse_content_range(ngx_http_request_t + ngx_http_slice_content_range_t *cr) + { + off_t start, end, complete_length, cutoff, cutlim; ++ ssize_t len; + u_char *p; + ngx_table_elt_t *h; + +@@ -300,6 +437,7 @@ ngx_http_slice_parse_content_range(ngx_http_request_t + } + + p = h->value.data + 6; ++ len = h->value.len - 6; + + cutoff = NGX_MAX_OFF_T_VALUE / 10; + cutlim = NGX_MAX_OFF_T_VALUE % 10; +@@ -308,56 +446,62 @@ ngx_http_slice_parse_content_range(ngx_http_request_t + end = 0; + complete_length = 0; + +- while (*p == ' ') { p++; } ++ while ((*p == ' ') && (len != 0)) { p++; len--; } + +- if (*p < '0' || *p > '9') { ++ if ((len == 0) || (*p < '0' || *p > '9')) { + return NGX_ERROR; + } + +- while (*p >= '0' && *p <= '9') { ++ while ((len != 0) && (*p >= '0' && *p <= '9')) { + if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) { + return NGX_ERROR; + } + + start = start * 10 + (*p++ - '0'); ++ len--; + } + +- while (*p == ' ') { p++; } ++ while ((len != 0) && (*p == ' ')) { p++; len--; } + +- if (*p++ != '-') { ++ if ((len == 0) || (*p++ != '-')) { + return NGX_ERROR; + } + +- while (*p == ' ') { p++; } ++ len--; + +- if (*p < '0' || *p > '9') { ++ while ((len != 0) && (*p == ' ')) { p++; len--; } ++ ++ if ((len == 0) || (*p < '0' || *p > '9')) { + return NGX_ERROR; + } + +- while (*p >= '0' && *p <= '9') { ++ while ((len != 0) && (*p >= '0' && *p <= '9')) { + if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) { + return NGX_ERROR; + } + + end = end * 10 + (*p++ - '0'); ++ len--; + } + + end++; + +- while (*p == ' ') { p++; } ++ while ((len != 0) && (*p == ' ')) { p++; len--; } + +- if (*p++ != '/') { ++ if ((len == 0) || (*p++ != '/')) { + return NGX_ERROR; + } + +- while (*p == ' ') { p++; } ++ len--; + +- if (*p != '*') { ++ while ((len != 0) && (*p == ' ')) { p++; len--; } ++ ++ if ((len != 0) && (*p != '*')) { + if (*p < '0' || *p > '9') { + return NGX_ERROR; + } + +- while (*p >= '0' && *p <= '9') { ++ while ((len != 0) && (*p >= '0' && *p <= '9')) { + if (complete_length >= cutoff + && (complete_length > cutoff || *p - '0' > cutlim)) + { +@@ -365,16 +509,18 @@ ngx_http_slice_parse_content_range(ngx_http_request_t + } + + complete_length = complete_length * 10 + (*p++ - '0'); ++ len--; + } + + } else { + complete_length = -1; + p++; ++ len--; + } + +- while (*p == ' ') { p++; } ++ while ((len != 0) && (*p == ' ')) { p++; len--; } + +- if (*p != '\0') { ++ if (len != 0) { + return NGX_ERROR; + } + +@@ -390,9 +536,10 @@ static ngx_int_t + ngx_http_slice_range_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) + { +- u_char *p; +- ngx_http_slice_ctx_t *ctx; +- ngx_http_slice_loc_conf_t *slcf; ++ u_char *p; ++ ngx_http_slice_ctx_t *ctx; ++ ngx_http_slice_loc_conf_t *slcf; ++ ngx_http_slice_read_a_head_t *read_a_head; + + ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module); + +@@ -414,6 +561,15 @@ ngx_http_slice_range_variable(ngx_http_request_t *r, + return NGX_ERROR; + } + ++ if (slcf->read_a_heads != 0.0) { ++ read_a_head = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_read_a_head_t)); ++ if (read_a_head == NULL) { ++ return NGX_ERROR; ++ } ++ ++ ctx->read_a_head = read_a_head; ++ } ++ + ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module); + + p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN); +@@ -488,6 +644,39 @@ ngx_http_slice_get_start(ngx_http_request_t *r) + } + + ++static char * ++ngx_conf_set_float_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ++{ ++ char *p = conf; ++ float *np; ++ ngx_str_t *value; ++ ngx_conf_post_t *post; ++ ngx_int_t val; ++ ++ np = (float *) (p + cmd->offset); ++ ++ if (*np != (float) NGX_CONF_UNSET) { ++ return "is duplicate"; ++ } ++ ++ value = cf->args->elts; ++ val = ngx_atofp(value[1].data, value[1].len, 3); ++ ++ *np = (float) val / 1000.0; ++ ++ if (*np == (float) NGX_ERROR) { ++ return "invalid number"; ++ } ++ ++ if (cmd->post) { ++ post = cmd->post; ++ return post->post_handler(cf, post, np); ++ } ++ ++ return NGX_CONF_OK; ++} ++ ++ + static void * + ngx_http_slice_create_loc_conf(ngx_conf_t *cf) + { +@@ -499,6 +688,7 @@ ngx_http_slice_create_loc_conf(ngx_conf_t *cf) + } + + slcf->size = NGX_CONF_UNSET_SIZE; ++ slcf->read_a_heads = (float) NGX_CONF_UNSET; + + return slcf; + } +@@ -511,6 +701,13 @@ ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *pa + ngx_http_slice_loc_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->size, prev->size, 0); ++ ngx_conf_merge_value(conf->read_a_heads, prev->read_a_heads, 0.0); ++ ++ if (conf->read_a_heads < 0.0) { ++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "read a head must be >= 0"); ++ return NGX_CONF_ERROR; ++ } ++ + + return NGX_CONF_OK; + } diff --git a/www/freenginx/files/extra-patch-xss-nginx-module-config b/www/freenginx/files/extra-patch-xss-nginx-module-config new file mode 100644 index 000000000000..c5dc537b783c --- /dev/null +++ b/www/freenginx/files/extra-patch-xss-nginx-module-config @@ -0,0 +1,15 @@ +--- ../xss-nginx-module-0.06/config.orig 2020-04-24 17:13:57.596040000 -0400 ++++ ../xss-nginx-module-0.06/config 2020-04-24 17:18:16.438437000 -0400 +@@ -1,5 +1,9 @@ + ngx_addon_name=ngx_http_xss_filter_module +-HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_xss_filter_module" +-NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_xss_filter_module.c $ngx_addon_dir/src/ngx_http_xss_util.c" +-NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/ngx_http_xss_filter_module.h $ngx_addon_dir/src/ngx_http_xss_util.h" + ++ngx_module_name="$ngx_addon_name" ++ngx_module_type=HTTP_FILTER ++ ++ngx_module_srcs="$ngx_addon_dir/src/ngx_http_xss_filter_module.c $ngx_addon_dir/src/ngx_http_xss_util.c" ++ngx_module_deps="$ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/ngx_http_xss_filter_module.h $ngx_addon_dir/src/ngx_http_xss_util.h" ++ ++. auto/module diff --git a/www/freenginx/files/nginx.in b/www/freenginx/files/nginx.in new file mode 100644 index 000000000000..652468a97596 --- /dev/null +++ b/www/freenginx/files/nginx.in @@ -0,0 +1,158 @@ +#!/bin/sh + +# PROVIDE: nginx +# REQUIRE: LOGIN cleanvar +# KEYWORD: shutdown + +# +# Add the following lines to /etc/rc.conf to enable nginx: +# nginx_enable (bool): Set to "NO" by default. +# Set it to "YES" to enable nginx +# nginx_profiles (str): Set to "" by default. +# Define your profiles here. +# nginx_pid_prefix (str): Set to "" by default. +# When using profiles manually assign value to "nginx_" +# for prevent collision with other PIDs names. +# nginxlimits_enable (bool): Set to "NO" by default. +# Set it to yes to run `limits $limits_args` +# just before nginx starts. +# nginx_reload_quiet (bool): Set to "NO" by default. +# Set it to yes to suppress info output when testng config. +# nginx_flags (str): Set to "" by default. +# Extra flags passed to start command. +# nginxlimits_args (str): Default to "-e -U %%WWWOWN%%" +# Arguments of pre-start limits run. +# nginx_http_accept_enable (bool): Set to "NO" by default. +# Set to yes to check for accf_http kernel module +# on start-up and load if not loaded. +# nginx_sig_stop (str): Default to "TERM" + +. /etc/rc.subr + +name="nginx" +rcvar=nginx_enable + +start_precmd="nginx_prestart" +stop_precmd="nginx_prestop" +restart_precmd="nginx_checkconfig" +reload_precmd="nginx_checkconfig" +configtest_cmd="nginx_checkconfig" +gracefulstop_cmd="nginx_gracefulstop" +upgrade_precmd="nginx_checkconfig" +upgrade_cmd="nginx_upgrade" +command="%%PREFIX%%/sbin/nginx" +_pidprefix="%%NGINX_RUNDIR%%" +pidfile="${_pidprefix}/${name}.pid" +_tmpprefix="%%NGINX_TMPDIR%%" +required_files=%%PREFIX%%/etc/nginx/nginx.conf +extra_commands="reload configtest upgrade gracefulstop" + +[ -z "$nginx_enable" ] && nginx_enable="NO" +[ -z "$nginxlimits_enable" ] && nginxlimits_enable="NO" +[ -z "$nginxlimits_args" ] && nginxlimits_args="-e -U %%WWWOWN%%" +[ -z "$nginx_http_accept_enable" ] && nginx_http_accept_enable="NO" +[ -z "$nginx_reload_quiet" ] && nginx_reload_quiet="NO" + +load_rc_config $name + +if [ -n "$2" ]; then + profile="$2" + if [ "x${nginx_profiles}" != "x" ]; then + pidfile="${_pidprefix}/${nginx_pid_prefix}${profile}.pid" + eval nginx_configfile="\${nginx_${profile}_configfile:-}" + if [ "x${nginx_configfile}" = "x" ]; then + echo "You must define a configuration file (nginx_${profile}_configfile)" + exit 1 + fi + required_files="${nginx_configfile}" + eval nginx_enable="\${nginx_${profile}_enable:-${nginx_enable}}" + eval nginx_flags="\${nginx_${profile}_flags:-${nginx_flags}}" + eval nginxlimits_enable="\${nginxlimits_${profile}_enable:-${nginxlimits_enable}}" + eval nginxlimits_args="\${nginxlimits_${profile}_args:-${nginxlimits_args}}" + nginx_flags="-c ${nginx_configfile} -g \"pid ${pidfile};\" ${nginx_flags}" + else + echo "$0: extra argument ignored" + fi +else + if [ "x${nginx_profiles}" != "x" -a "x$1" != "x" ]; then + for profile in ${nginx_profiles}; do + echo "===> nginx profile: ${profile}" + %%PREFIX%%/etc/rc.d/nginx $1 ${profile} + retcode="$?" + if [ "0${retcode}" -ne 0 ]; then + failed="${profile} (${retcode}) ${failed:-}" + else + success="${profile} ${success:-}" + fi + done + exit 0 + fi +fi + +# tmpfs(5) +nginx_checktmpdir() +{ + if [ ! -d ${_tmpprefix} ] ; then + install -d -o %%WWWOWN%% -g %%WWWGRP%% -m 755 ${_tmpprefix} + fi +} + +nginx_checkconfig() +{ + nginx_checktmpdir + + if checkyesno nginx_reload_quiet; then + eval ${command} ${nginx_flags} -t -q + else + echo "Performing sanity check on nginx configuration:" + eval ${command} ${nginx_flags} -t + fi +} + +nginx_gracefulstop() +{ + echo "Performing a graceful stop:" + sig_stop="QUIT" + run_rc_command ${rc_prefix}stop $rc_extra_args || return 1 +} + +nginx_upgrade() +{ + echo "Upgrading nginx binary:" + + reload_precmd="" + sig_reload="USR2" + run_rc_command ${rc_prefix}reload $rc_extra_args || return 1 + + sleep 1 + + echo "Stopping old binary:" + + sig_reload="QUIT" + pidfile="$pidfile.oldbin" + run_rc_command ${rc_prefix}reload $rc_extra_args || return 1 +} + +nginx_prestart() +{ + if checkyesno nginx_http_accept_enable + then + required_modules="$required_modules accf_http accf_data" + fi + + nginx_checkconfig + + if checkyesno nginxlimits_enable + then + eval `/usr/bin/limits ${nginxlimits_args}` 2>/dev/null + else + return 0 + fi +} + +nginx_prestop() +{ + sig_stop="${nginx_sig_stop:-TERM}" +} + +run_rc_command "$1" diff --git a/www/freenginx/files/patch-conf-nginx.conf b/www/freenginx/files/patch-conf-nginx.conf new file mode 100644 index 000000000000..fb38c9c3d9cc --- /dev/null +++ b/www/freenginx/files/patch-conf-nginx.conf @@ -0,0 +1,47 @@ +--- conf/nginx.conf.orig 2016-09-06 14:56:32 UTC ++++ conf/nginx.conf +@@ -2,9 +2,14 @@ + #user nobody; + worker_processes 1; + +-#error_log logs/error.log; +-#error_log logs/error.log notice; +-#error_log logs/error.log info; ++# This default error log path is compiled-in to make sure configuration parsing ++# errors are logged somewhere, especially during unattended boot when stderr ++# isn't normally logged anywhere. This path will be touched on every nginx ++# start regardless of error log location configured here. See ++# https://trac.nginx.org/nginx/ticket/147 for more info. ++# ++#error_log %%NGINX_ERRORLOG%%; ++# + + #pid logs/nginx.pid; + +@@ -33,7 +38,7 @@ http { + #gzip on; + + server { +- listen 80; ++ listen %%HTTP_PORT%%; + server_name localhost; + + #charset koi8-r; +@@ -41,7 +46,7 @@ http { + #access_log logs/host.access.log main; + + location / { +- root html; ++ root %%PREFIX%%/www/nginx; + index index.html index.htm; + } + +@@ -51,7 +56,7 @@ http { + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { +- root html; ++ root %%PREFIX%%/www/nginx-dist; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 diff --git a/www/freenginx/files/pkg-message.in b/www/freenginx/files/pkg-message.in new file mode 100644 index 000000000000..adde28032712 --- /dev/null +++ b/www/freenginx/files/pkg-message.in @@ -0,0 +1,25 @@ +[ +{ type: install + message: <