diff --git a/.github/DISCUSSION_TEMPLATE/general.yml b/.github/DISCUSSION_TEMPLATE/general.yml new file mode 100644 index 0000000..0ebcb15 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/general.yml @@ -0,0 +1,15 @@ +--- +body: + - type: markdown + attributes: + value: | + For NGINX troubleshooting/technical help, please visit our community forum instead of asking your questions here. We will politely redirect these types of questions to the forum. + - type: textarea + id: general + attributes: + label: What would you like to discuss? + description: Please provide as much context as possible. Remember that only general discussions related to the NGINX codebase will be addressed on GitHub. For anything else, please visit our [community forum](https://community.nginx.org/). + value: | + I would like to discuss... + validations: + required: true diff --git a/.github/DISCUSSION_TEMPLATE/ideas.yml b/.github/DISCUSSION_TEMPLATE/ideas.yml new file mode 100644 index 0000000..73fb6ec --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/ideas.yml @@ -0,0 +1,15 @@ +--- +body: + - type: markdown + attributes: + value: | + For NGINX troubleshooting/technical help, please visit our community forum instead of asking your questions here. We will politely redirect these types of questions to the forum. + - type: textarea + id: ideas + attributes: + label: What idea would you like to discuss? + description: Please provide as much context as possible. Remember that only ideas related to the NGINX codebase will be addressed on GitHub. For anything else, please visit our [community forum](https://community.nginx.org/). + value: | + I have an idea for... + validations: + required: true diff --git a/.github/DISCUSSION_TEMPLATE/q-a.yml b/.github/DISCUSSION_TEMPLATE/q-a.yml new file mode 100644 index 0000000..f103ef2 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/q-a.yml @@ -0,0 +1,15 @@ +--- +body: + - type: markdown + attributes: + value: | + For NGINX troubleshooting/technical help, please visit our community forum instead of asking your questions here. We will politely redirect these types of questions to the forum. + - type: textarea + id: q-a + attributes: + label: What question do you have? + description: Please provide as much context as possible. Remember that only questions related to the NGINX codebase will be addressed on GitHub. For anything else, please visit our [community forum](https://community.nginx.org/). + value: | + I would like to know... + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index efdc116..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "" -labels: "bug" ---- - -### Environment - -Include the result of the following commands: - - `nginx -V` - - `uname -a` - -### Description - -Describe the bug in full detail including expected and actual behavior. -Specify conditions that caused it. Provide the relevant part of nginx -configuration and debug log. - -- [ ] The bug is reproducible with the latest version of nginx -- [ ] The nginx configuration is minimized to the smallest possible -to reproduce the issue and doesn't contain third-party modules - -#### nginx configuration - -``` -# Your nginx configuration here -``` -or share the configuration in [gist](https://gist.github.com/). - -#### nginx debug log - -It is advised to enable -[debug logging](http://nginx.org/en/docs/debugging_log.html). -``` -# Your nginx debug log here -``` -or share the debug log in [gist](https://gist.github.com/). diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..cd62089 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,106 @@ +--- +name: πŸ› Bug report +description: Create a report to help us improve +labels: bug +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + Before you continue filling out this report, please take a moment to check that your bug has not been [already reported on GitHub][issue search], is reproducible with the latest version of nginx, and does not involve any third-party modules πŸ™Œ + + Remember to redact any sensitive information such as authentication credentials and/or license keys! + + **Note:** If you are seeking community support, please start a new topic in the [NGINX Community forum][forum]. If you wish to discuss the codebase, please start a new thread via [GitHub discussions][discussions]. + + [issue search]: https://github.com/nginx/nginx/search?q=is%3Aissue&type=issues + [discussions]: https://github.com/nginx/nginx/discussions + [forum]: https://community.nginx.org + + - type: textarea + id: overview + attributes: + label: Bug Overview + description: A clear and concise overview of the bug. + placeholder: When I do "X", "Y" happens instead of "Z". + validations: + required: true + + - type: textarea + id: behavior + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + placeholder: When I do "X", I expect "Z" to happen. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce the Bug + description: Detail the series of steps required to reproduce the bug. + placeholder: When I run "X" using [...], "X" fails with "Y" error message. If I check the terminal outputs and/or logs, I see the following info. + validations: + required: true + + - type: textarea + id: configuration + attributes: + label: NGINX Configuration + description: Please provide your NGINX configuration. Minimize it to the smallest possible configuration that reproduces the issue. + value: | + ``` + # Your NGINX configuration + ``` + validations: + required: true + + - type: textarea + id: version + attributes: + label: NGINX version and build configuration options + description: Please provide details about your NGINX build. + value: | + The output of `nginx -V`: [...] + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment where NGINX is being built and/or deployed + description: Please provide details about your environment. + value: | + - Target deployment platform: [e.g. AWS/GCP/local cluster/etc...] + - Target OS: [e.g. RHEL 9/Ubuntu 24.04/etc...] + validations: + required: true + + - type: textarea + id: architecture + attributes: + label: Architecture where NGINX is being built and/or deployed + description: Please provide details about your deployment environment. + value: | + The output of `uname -a`: [...] + validations: + required: true + + - type: textarea + id: logs + attributes: + label: NGINX Debug Log + description: Please provide your NGINX debug log. See this [doc](http://nginx.org/en/docs/debugging_log.html) for details on how to enable it. + value: | + ``` + # Your NGINX debug log + ``` + + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any other context about the problem here. + placeholder: Feel free to add any other context/information/screenshots/etc... that you think might be relevant to this issue in here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3f7850f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,12 @@ +--- +blank_issues_enabled: false +contact_links: + - name: πŸ’¬ Talk to the NGINX community! + url: https://community.nginx.org + about: A community forum for NGINX users, developers, and contributors + - name: πŸ“ Code of Conduct + url: https://www.contributor-covenant.org/version/2/1/code_of_conduct + about: NGINX follows the Contributor Covenant Code of Conduct to ensure a safe and inclusive community + - name: πŸ’Ό For commercial & enterprise users + url: https://www.f5.com/products/nginx + about: F5 offers a wide range of NGINX products for commercial & enterprise users diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 67471b0..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature request -about: Suggest a feature for nginx -title: "" -labels: "feature" ---- - -### Describe the feature you'd like to add to nginx - -A clear and concise description of the feature. - -### Describe the problem this feature solves - -A clear and concise description of the problem. - -### Additional context - -Add any other context about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..cafb5ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,40 @@ +--- +name: ✨ Feature request +description: Suggest an idea for this project +labels: enhancement +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request! + + Before you continue filling out this request, please take a moment to check that your feature has not been [already requested on GitHub][issue search] πŸ™Œ + + **Note:** If you are seeking community support, please start a new topic in the [NGINX Community forum][forum]. If you wish to discuss the codebase, please start a new thread via [GitHub discussions][discussions]. + + [issue search]: https://github.com/nginx/nginx/search?q=is%3Aissue&type=issues + [discussions]: https://github.com/nginx/nginx/discussions + [forum]: https://community.nginx.org + + - type: textarea + id: overview + attributes: + label: Feature Overview + description: A clear and concise description of what the feature request is. + placeholder: I would like this project to be able to do "X". + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Detail any potential alternative solutions/workarounds you've used or considered. + placeholder: I have done/might be able to do "X" in this project by doing "Y". + + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any other context about the problem here. + placeholder: Feel free to add any other context/information/screenshots/etc... that you think might be relevant to this feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 14ac463..1e7e0a9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,10 @@ ### Proposed changes -Describe the use case and detail of the change. +Describe the use case and detail of the change. If this PR addresses an issue on GitHub, make sure to include a link to that issue using one of the [supported keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) in this PR's description or commit message. -If this pull request addresses an issue on GitHub, make sure to reference that -issue using one of the -[supported keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue). +### Checklist -Before creating a pull request, make sure to comply with the -[Contributing Guidelines](https://github.com/nginx/nginx/blob/master/CONTRIBUTING.md). +Before creating a PR, run through this checklist and mark each as complete: + +- [ ] I have read the [contributing guidelines](/CONTRIBUTING.md). +- [ ] I have checked that NGINX compiles and runs after adding my changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 864e798..4436414 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,34 +5,34 @@ We really appreciate that you are considering contributing! ## Table of Contents -- [Ask a Question](#ask-a-question) - [Report a Bug](#report-a-bug) - [Suggest a Feature or Enhancement](#suggest-a-feature-or-enhancement) - [Open a Discussion](#open-a-discussion) - [Submit a Pull Request](#submit-a-pull-request) - [Issue Lifecycle](#issue-lifecycle) -## Ask a Question - -To ask a question, open an issue on GitHub with the label `question`. - ## Report a Bug To report a bug, open an issue on GitHub with the label `bug` using the -available bug report issue template. Before reporting a bug, make sure the -issue has not already been reported. +available [bug report issue form](/.github/ISSUE_TEMPLATE/bug_report.yml). +Please ensure the bug has not already been reported. **If the bug is a +potential security vulnerability, please report it using our +[security policy](/SECURITY.md).** ## Suggest a Feature or Enhancement -To suggest a feature or enhancement, open an issue on GitHub with the label -`feature` or `enhancement` using the available feature request issue template. +To suggest a feature or enhancement, please create an issue on GitHub with the +label `enhancement` using the available +[feature request issue form](/.github/ISSUE_TEMPLATE/feature_request.yml). Please ensure the feature or enhancement has not already been suggested. ## Open a Discussion If you want to engage in a conversation with the community and maintainers, we encourage you to use -[GitHub Discussions](https://github.com/nginx/nginx/discussions). +[GitHub Discussions](https://github.com/nginx/nginx/discussions) to discuss +the NGINX codebase or the [NGINX Community forum](https://community.nginx.org) +to chat anything else NGINX (including troubleshooting). ## Submit a Pull Request @@ -89,7 +89,20 @@ git clone https://github.com/nginx/nginx-tests.git ``` - Submitting a change implies granting project a permission to use it under the -[BSD-2-Clause license](https://github.com/nginx/nginx/blob/master/LICENSE) +[BSD-2-Clause license](/LICENSE) + +### F5 Contributor License Agreement (CLA) + +F5 requires all contributors to agree to the terms of the F5 CLA +(available [here](https://github.com/f5/f5-cla/blob/main/docs/f5_cla.md)) +before any of their changes can be incorporated into an F5 Open Source +repository (even contributions to the F5 CLA itself!). + +If you have not yet agreed to the F5 CLA terms and submit a PR to this +repository, a bot will prompt you to view and agree to the F5 CLA. +You will have to agree to the F5 CLA terms through a comment in the PR +before any of your changes can be merged. Your agreement signature +will be safely stored by F5 and no longer be required in future PRs. ## Issue Lifecycle diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..4e8ab20 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,48 @@ +# Support + +## Ask a Question + +We use GitHub issues for tracking bugs and feature requests +related to this project. + +If you don't know how something in the codebase works, are curious if NGINX +is capable of achieving your desired functionality or want to discuss the +implementation of an existing or in development feature, please start a +GitHub discussion! + +## NGINX Specific Questions and/or Issues + +This project isn't the right place to get support for NGINX and/or NGINX +troubleshooting questions, but the following resources are available below. +Thanks for your understanding! + +### Community Forum + +We have a [community forum](https://community.nginx.org/)! +If you have any NGINX specific questions and/or issues, +try checking out the [`NGINX category`](https://community.nginx.org/c/projects/nginx/23). +For general discussions around anything tangentially NGINX related, +check out the [`General Discussion category`](https://community.nginx.org/c/general-discussion/34). +Both fellow community members and NGINXers might be able to help you! :) + +### Documentation + +For a comprehensive list of all NGINX directives, check out . + +For a comprehensive list of administration and deployment guides for all +NGINX products, check out . + +## Contributing + +Please see the [contributing guide](/CONTRIBUTING.md) for guidelines +on how to best contribute to this project. + +## Commercial Support + +Commercial support for this project is available. +Please get in touch with [F5 sales](https://www.f5.com/products/get-f5/) +or check your contract details for more information! + +## Community Support + +Community support is offered on a best effort basis through any of our active communities. diff --git a/auto/cc/clang b/auto/cc/clang index a962ee2..73b3f94 100644 --- a/auto/cc/clang +++ b/auto/cc/clang @@ -5,15 +5,6 @@ # clang -NGX_CLANG_VER=`$CC -v 2>&1 | grep 'version' 2>&1 \ - | sed -n -e 's/^.*clang version \(.*\)/\1/p' \ - -e 's/^.*LLVM version \(.*\)/\1/p'` - -echo " + clang version: $NGX_CLANG_VER" - -have=NGX_COMPILER value="\"clang $NGX_CLANG_VER\"" . auto/define - - CC_TEST_FLAGS="-pipe" diff --git a/auto/cc/gcc b/auto/cc/gcc index a5c5c18..debd0eb 100644 --- a/auto/cc/gcc +++ b/auto/cc/gcc @@ -8,14 +8,6 @@ # 4.0.0, 4.0.1, 4.1.0 -NGX_GCC_VER=`$CC -v 2>&1 | grep 'gcc version' 2>&1 \ - | sed -e 's/^.* version \(.*\)/\1/'` - -echo " + gcc version: $NGX_GCC_VER" - -have=NGX_COMPILER value="\"gcc $NGX_GCC_VER\"" . auto/define - - # Solaris 7's /usr/ccs/bin/as does not support "-pipe" CC_TEST_FLAGS="-pipe" @@ -112,7 +104,7 @@ esac CC_AUX_FLAGS="$CC_AUX_FLAGS $CPU_OPT" -case "$NGX_GCC_VER" in +case "$NGX_CC_VER" in 2.7*) # batch build CPU_OPT= @@ -145,7 +137,7 @@ CFLAGS="$CFLAGS -Wall -Wpointer-arith" #CFLAGS="$CFLAGS -Winline" #CFLAGS="$CFLAGS -Wmissing-prototypes" -case "$NGX_GCC_VER" in +case "$NGX_CC_VER" in 2.*) # we have a lot of the unused function arguments CFLAGS="$CFLAGS -Wno-unused" diff --git a/auto/cc/icc b/auto/cc/icc index c47f6e4..9694642 100644 --- a/auto/cc/icc +++ b/auto/cc/icc @@ -5,14 +5,6 @@ # Intel C++ compiler 7.1, 8.0, 8.1, 9.0, 11.1 -NGX_ICC_VER=`$CC -V 2>&1 | grep 'Version' 2>&1 \ - | sed -e 's/^.* Version \([^ ]*\) *Build.*$/\1/'` - -echo " + icc version: $NGX_ICC_VER" - -have=NGX_COMPILER value="\"Intel C Compiler $NGX_ICC_VER\"" . auto/define - - # optimizations CFLAGS="$CFLAGS -O" @@ -83,7 +75,7 @@ CFLAGS="$CFLAGS -wd1418" # external declaration in primary source file CFLAGS="$CFLAGS -wd1419" -case "$NGX_ICC_VER" in +case "$NGX_CC_VER" in 9.*) # "cc" clobber ignored, warnings for Linux's htonl()/htons() CFLAGS="$CFLAGS -wd1469" diff --git a/auto/cc/msvc b/auto/cc/msvc index fe7c34e..1d18547 100644 --- a/auto/cc/msvc +++ b/auto/cc/msvc @@ -11,20 +11,12 @@ # MSVC 2015 (14.0) cl 19.00 -NGX_MSVC_VER=`$NGX_WINE $CC 2>&1 | grep 'C/C++.* [0-9][0-9]*\.[0-9]' 2>&1 \ - | sed -e 's/^.* \([0-9][0-9]*\.[0-9].*\)/\1/'` - -echo " + cl version: $NGX_MSVC_VER" - -have=NGX_COMPILER value="\"cl $NGX_MSVC_VER\"" . auto/define - - -ngx_msvc_ver=`echo $NGX_MSVC_VER | sed -e 's/^\([0-9]*\).*/\1/'` +ngx_msvc_ver=`echo $NGX_CC_VER | sed -e 's/^\([0-9]*\).*/\1/'` # detect x64 builds -case "$NGX_MSVC_VER" in +case "$NGX_CC_VER" in *ARM64) NGX_MACHINE=arm64 diff --git a/auto/cc/name b/auto/cc/name index ded93f5..f15a853 100644 --- a/auto/cc/name +++ b/auto/cc/name @@ -28,6 +28,13 @@ if [ "$CC" = cl ]; then NGX_CC_NAME=msvc echo " + using Microsoft Visual C++ compiler" + NGX_CC_VER=`$NGX_WINE $CC 2>&1 \ + | grep 'C/C++.* [0-9][0-9]*\.[0-9]' 2>&1 \ + | sed -e 's/^.* \([0-9][0-9]*\.[0-9].*\)/\1/'` + echo " + cl version: $NGX_CC_VER" + + have=NGX_COMPILER value="\"cl $NGX_CC_VER\"" . auto/define + elif [ "$CC" = wcl386 ]; then NGX_CC_NAME=owc echo " + using Open Watcom C compiler" @@ -40,22 +47,48 @@ elif `$CC -V 2>&1 | grep '^Intel(R) C' >/dev/null 2>&1`; then NGX_CC_NAME=icc echo " + using Intel C++ compiler" + NGX_CC_VER=`$CC -V 2>&1 \ + | sed -n -e 's/^.* Version \([^ ]*\) *Build.*$/\1/p'` + echo " + icc version: $NGX_CC_VER" + + have=NGX_COMPILER value="\"Intel C Compiler $NGX_CC_VER\"" . auto/define + elif `$CC -v 2>&1 | grep 'gcc version' >/dev/null 2>&1`; then NGX_CC_NAME=gcc echo " + using GNU C compiler" + NGX_CC_VER=`$CC -v 2>&1 | sed -n -e 's/^.*gcc version \(.*\)/\1/p'` + echo " + gcc version: $NGX_CC_VER" + + have=NGX_COMPILER value="\"gcc $NGX_CC_VER\"" . auto/define + elif `$CC -v 2>&1 | grep 'clang version' >/dev/null 2>&1`; then NGX_CC_NAME=clang echo " + using Clang C compiler" + NGX_CC_VER=`$CC -v 2>&1 | sed -n -e 's/^.*clang version \(.*\)/\1/p'` + echo " + clang version: $NGX_CC_VER" + + have=NGX_COMPILER value="\"clang $NGX_CC_VER\"" . auto/define + elif `$CC -v 2>&1 | grep 'LLVM version' >/dev/null 2>&1`; then NGX_CC_NAME=clang echo " + using Clang C compiler" + NGX_CC_VER=`$CC -v 2>&1 | sed -n -e 's/^.*LLVM version \(.*\)/\1/p'` + echo " + clang version: $NGX_CC_VER" + + have=NGX_COMPILER value="\"clang $NGX_CC_VER\"" . auto/define + elif `$CC -V 2>&1 | grep 'Sun C' >/dev/null 2>&1`; then NGX_CC_NAME=sunc echo " + using Sun C compiler" + NGX_CC_VER=`$CC -V 2>&1 | sed -n -e 's/^.* Sun C \(.*\)/\1/p'` + echo " + Sun C version: $NGX_CC_VER" + + have=NGX_COMPILER value="\"Sun C $NGX_CC_VER\"" . auto/define + elif `$CC -V 2>&1 | grep '^Compaq C' >/dev/null 2>&1`; then NGX_CC_NAME=ccc echo " + using Compaq C compiler" diff --git a/auto/cc/sunc b/auto/cc/sunc index 552c2d3..fa668a7 100644 --- a/auto/cc/sunc +++ b/auto/cc/sunc @@ -13,13 +13,6 @@ # Sun C 5.13 SunOS_i386 2014/10/20 Oracle Solaris Studio 12.4 # Sun C 5.14 SunOS_i386 2016/05/31 Oracle Developer Studio 12.5 -NGX_SUNC_VER=`$CC -V 2>&1 | grep 'Sun C' 2>&1 \ - | sed -e 's/^.* Sun C \(.*\)/\1/'` - -echo " + Sun C version: $NGX_SUNC_VER" - -have=NGX_COMPILER value="\"Sun C $NGX_SUNC_VER\"" . auto/define - cat << END > $NGX_AUTOTEST.c diff --git a/auto/lib/pcre/make b/auto/lib/pcre/make index 2a987d1..8a20859 100644 --- a/auto/lib/pcre/make +++ b/auto/lib/pcre/make @@ -37,6 +37,7 @@ if [ $PCRE_LIBRARY = PCRE2 ]; then pcre2_xclass.c" ngx_pcre_test="pcre2_chkdint.c \ + pcre2_compile_cgroup.c \ pcre2_compile_class.c \ pcre2_convert.c \ pcre2_extuni.c \ diff --git a/auto/modules b/auto/modules index c9722c9..859c94a 100644 --- a/auto/modules +++ b/auto/modules @@ -733,7 +733,7 @@ if [ $HTTP = YES ]; then ngx_module_name=ngx_http_proxy_module ngx_module_incs= - ngx_module_deps= + ngx_module_deps=src/http/modules/ngx_http_proxy_module.h ngx_module_srcs=src/http/modules/ngx_http_proxy_module.c ngx_module_libs= ngx_module_link=$HTTP_PROXY @@ -785,6 +785,17 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_PROXY = YES -a $HTTP_V2 = YES ]; then + ngx_module_name=ngx_http_proxy_v2_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/modules/ngx_http_proxy_v2_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V2 + + . auto/module + fi + if [ $HTTP_PERL != NO ]; then ngx_module_name=ngx_http_perl_module ngx_module_incs=src/http/modules/perl diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml index 42d28b4..7c1264d 100644 --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -5,6 +5,85 @@ + + + + +ΠΌΠΎΠ΄ΡƒΠ»ΡŒ ngx_http_proxy_module ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ HTTP/2. + + +the ngx_http_proxy_module supports HTTP/2. + + + + + +ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ TLS Encrypted ClientHello +ΠΏΡ€ΠΈ использовании Π²Π΅Ρ‚ΠΊΠΈ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ECH OpenSSL; +Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Π° ssl_ech_file.
+Бпасибо Stephen Farrell. +
+ +Encrypted ClientHello TLS extension support +when using OpenSSL ECH feature branch; +the "ssl_ech_file" directive.
+Thanks to Stephen Farrell. +
+
+ + + +валидация хоста ΠΈ ΠΏΠΎΡ€Ρ‚Π° Π² строкС запроса, +Π² Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ΅ "Host" ΠΈ псСвдо-Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ΅ ":authority" +ΠΈΠ·ΠΌΠ΅Π½Π΅Π½Π° Π½Π° ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΡƒΡŽ RFC 3986. + + +validation of host and port in the request line, +"Host" header field, and ":authority" pseudo-header field +has been changed to follow RFC 3986. + + + + + +Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½Ρ‹ΠΉ символ LF, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹ΠΉ для ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Π° строки +Π² chunked-Ρ‚Π΅Π»Π΅ запроса ΠΈΠ»ΠΈ ΠΎΡ‚Π²Π΅Ρ‚Π°, считаСтся ошибкой. + + +now a single LF used as a line terminator +in a chunked request or response body is considered an error. + + + + + +ΠΏΡ€ΠΈ использовании HTTP/3 с OpenSSL 3.5.1 ΠΈ Π½ΠΎΠ²Π΅Π΅ +Π² Ρ€Π°Π±ΠΎΡ‡Π΅ΠΌ процСссС ΠΌΠΎΠ³ ΠΏΡ€ΠΎΠΈΠ·ΠΎΠΉΡ‚ΠΈ segmentation fault; +ошибка появилась Π² 1.29.1.
+Бпасибо Jan Svojanovsky. +
+ +when using HTTP/3 with OpenSSL 3.5.1 or newer +a segmentation fault might occur in a worker process; +the bug had appeared in 1.29.1.
+Thanks to Jan Svojanovsky. +
+
+ + + +ΠΏΡ€ΠΈ совмСстном использовании Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ² try_files ΠΈ proxy_pass с URI +Π² Ρ€Π°Π±ΠΎΡ‡Π΅ΠΌ процСссС ΠΌΠΎΠ³ ΠΏΡ€ΠΎΠΈΠ·ΠΎΠΉΡ‚ΠΈ segmentation fault. + + +a segmentation fault might occur in a worker process +if the "try_files" directive and "proxy_pass" with a URI were used. + + + +
+ + diff --git a/src/core/nginx.h b/src/core/nginx.h index 87ebe9c..c09cf22 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1029003 -#define NGINX_VERSION "1.29.3" +#define nginx_version 1029004 +#define NGINX_VERSION "1.29.4" #define NGINX_VER "nginx/" NGINX_VERSION " by Hakase" #ifndef NGINX_SERVER diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index ec12a94..8c9c480 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1653,6 +1653,105 @@ ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) } +ngx_int_t +ngx_ssl_ech_files(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *filenames) +{ +#ifdef SSL_OP_ECH_GREASE + int numkeys; + BIO *in; + ngx_int_t rc; + ngx_str_t *filename; + ngx_uint_t i; + OSSL_ECHSTORE *es; + + if (filenames == NULL) { + return NGX_OK; + } + + es = OSSL_ECHSTORE_new(NULL, NULL); + if (es == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "OSSL_ECHSTORE_new() failed"); + return NGX_ERROR; + } + + rc = NGX_ERROR; + filename = filenames->elts; + + for (i = 0; i < filenames->nelts; i++) { + + if (ngx_conf_full_name(cf->cycle, &filename[i], 1) != NGX_OK) { + goto cleanup; + } + + in = BIO_new_file((char *) filename[i].data, "r"); + if (in == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "BIO_new_file(\"%s\") failed", filename[i].data); + goto cleanup; + } + + /* + * We only set the ECHConfigList from the first file read to use + * in ECH retry-configs. + * + * That allows many sensible key rotation schemes so that the + * values sent in ECH retry-configs are smaller and current. + * For example, if the first file name has the current ECH + * private key, and a second one has the previously used key + * that some clients may still use due to DNS caching. + */ + + if (OSSL_ECHSTORE_read_pem(es, in, i ? OSSL_ECH_NO_RETRY + : OSSL_ECH_FOR_RETRY) + != 1) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "OSSL_ECHSTORE_read_pem(%s) failed", + filename[i].data); + BIO_free(in); + goto cleanup; + } + + BIO_free(in); + } + + /* + * load the ECH store after checking there's at least one ECH + * private key in there (the PEM file spec allows zero or one + * private key per file) + */ + + if (OSSL_ECHSTORE_num_keys(es, &numkeys) != 1) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "OSSL_ECHSTORE_num_keys(%s) failed"); + goto cleanup; + } + + if (numkeys > 0 && SSL_CTX_set1_echstore(ssl->ctx, es) != 1) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set1_echstore() failed"); + goto cleanup; + } + + rc = NGX_OK; + +cleanup: + + OSSL_ECHSTORE_free(es); + return rc; + +#else + if (filenames != NULL) { + ngx_log_error(NGX_LOG_WARN, ssl->log, 0, + "\"ssl_ech_file\" is not supported on this platform, " + "ignored"); + } + + return NGX_OK; +#endif +} + + ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name) { @@ -1872,21 +1971,34 @@ ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) } -void -ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, - ngx_ssl_client_hello_arg *cb) +ngx_int_t +ngx_ssl_set_client_hello_callback(ngx_ssl_t *ssl, ngx_ssl_client_hello_arg *cb) { #ifdef SSL_CLIENT_HELLO_SUCCESS - SSL_CTX_set_client_hello_cb(ssl_ctx, ngx_ssl_client_hello_callback, NULL); - SSL_CTX_set_ex_data(ssl_ctx, ngx_ssl_client_hello_arg_index, cb); + SSL_CTX_set_client_hello_cb(ssl->ctx, ngx_ssl_client_hello_callback, NULL); + + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_client_hello_arg_index, cb) == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } #elif defined OPENSSL_IS_BORINGSSL - SSL_CTX_set_select_certificate_cb(ssl_ctx, ngx_ssl_select_certificate); - SSL_CTX_set_ex_data(ssl_ctx, ngx_ssl_client_hello_arg_index, cb); + SSL_CTX_set_select_certificate_cb(ssl->ctx, ngx_ssl_select_certificate); + + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_client_hello_arg_index, cb) == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } #endif + + return NGX_OK; } @@ -5734,6 +5846,81 @@ ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) } +ngx_int_t +ngx_ssl_get_ech_status(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ +#ifdef SSL_OP_ECH_GREASE + int echrv; + char *inner_sni, *outer_sni; + + inner_sni = NULL; + outer_sni = NULL; + + echrv = SSL_ech_get1_status(c->ssl->connection, &inner_sni, &outer_sni); + + switch (echrv) { + case SSL_ECH_STATUS_NOT_TRIED: + ngx_str_set(s, "NOT_TRIED"); + break; + case SSL_ECH_STATUS_SUCCESS: + ngx_str_set(s, "SUCCESS"); + break; + case SSL_ECH_STATUS_GREASE: + ngx_str_set(s, "GREASE"); + break; + case SSL_ECH_STATUS_BACKEND: + ngx_str_set(s, "BACKEND"); + break; + default: + ngx_str_set(s, "FAILED"); + break; + } + + OPENSSL_free(inner_sni); + OPENSSL_free(outer_sni); +#else + s->len = 0; +#endif + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_get_ech_outer_server_name(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s) +{ +#if defined(SSL_OP_ECH_GREASE) + int echrv; + char *inner_sni, *outer_sni; + + inner_sni = NULL; + outer_sni = NULL; + + echrv = SSL_ech_get1_status(c->ssl->connection, &inner_sni, &outer_sni); + + if (echrv == SSL_ECH_STATUS_SUCCESS && outer_sni) { + s->len = ngx_strlen(outer_sni); + + s->data = ngx_pnalloc(pool, s->len); + if (s->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(s->data, outer_sni, s->len); + + } else { + s->len = 0; + } + + OPENSSL_free(inner_sni); + OPENSSL_free(outer_sni); +#else + s->len = 0; +#endif + return NGX_OK; +} + + ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 1134e19..e9160ac 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -285,6 +285,8 @@ ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, ngx_array_t *passwords); ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); +ngx_int_t ngx_ssl_ech_files(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_array_t *filename); ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); @@ -300,7 +302,7 @@ ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths); ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data); -void ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, +ngx_int_t ngx_ssl_set_client_hello_callback(ngx_ssl_t *ssl, ngx_ssl_client_hello_arg *cb); #ifdef SSL_CLIENT_HELLO_SUCCESS int ngx_ssl_client_hello_callback(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); @@ -352,6 +354,10 @@ ngx_int_t ngx_ssl_get_early_data(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_server_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_get_ech_status(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); +ngx_int_t ngx_ssl_get_ech_outer_server_name(ngx_connection_t *c, + ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index a502431..18992ae 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -185,7 +185,13 @@ ngx_quic_cbs_release_rcd(ngx_ssl_conn_t *ssl_conn, size_t bytes_read, void *arg) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_cbs_release_rcd len:%uz", bytes_read); + /* already closed on handshake failure */ + qc = ngx_quic_get_connection(c); + if (qc == NULL) { + return 1; + } + ctx = ngx_quic_get_send_ctx(qc, qc->read_level); cl = ngx_quic_read_buffer(c, &ctx->crypto, bytes_read); diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index ecb1956..9f40c21 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -8,6 +8,7 @@ #include #include #include +#include #define NGX_HTTP_PROXY_COOKIE_SECURE 0x0001 @@ -23,11 +24,6 @@ #define NGX_HTTP_PROXY_COOKIE_SAMESITE_OFF 0x0400 -typedef struct { - ngx_array_t caches; /* ngx_http_file_cache_t * */ -} ngx_http_proxy_main_conf_t; - - typedef struct ngx_http_proxy_rewrite_s ngx_http_proxy_rewrite_t; typedef ngx_int_t (*ngx_http_proxy_rewrite_pt)(ngx_http_request_t *r, @@ -61,94 +57,6 @@ typedef struct { } ngx_http_proxy_cookie_flags_t; -typedef struct { - ngx_str_t key_start; - ngx_str_t schema; - ngx_str_t host_header; - ngx_str_t port; - ngx_str_t uri; -} ngx_http_proxy_vars_t; - - -typedef struct { - ngx_array_t *flushes; - ngx_array_t *lengths; - ngx_array_t *values; - ngx_hash_t hash; -} ngx_http_proxy_headers_t; - - -typedef struct { - ngx_http_upstream_conf_t upstream; - - ngx_array_t *body_flushes; - ngx_array_t *body_lengths; - ngx_array_t *body_values; - ngx_str_t body_source; - - ngx_http_proxy_headers_t headers; -#if (NGX_HTTP_CACHE) - ngx_http_proxy_headers_t headers_cache; -#endif - ngx_array_t *headers_source; - - ngx_array_t *proxy_lengths; - ngx_array_t *proxy_values; - - ngx_array_t *redirects; - ngx_array_t *cookie_domains; - ngx_array_t *cookie_paths; - ngx_array_t *cookie_flags; - - ngx_http_complex_value_t *method; - ngx_str_t location; - ngx_str_t url; - -#if (NGX_HTTP_CACHE) - ngx_http_complex_value_t cache_key; -#endif - - ngx_http_proxy_vars_t vars; - - ngx_flag_t redirect; - - ngx_uint_t http_version; - - ngx_uint_t headers_hash_max_size; - ngx_uint_t headers_hash_bucket_size; - -#if (NGX_HTTP_SSL) - ngx_uint_t ssl; - ngx_uint_t ssl_protocols; - ngx_str_t ssl_ciphers; - ngx_uint_t ssl_verify_depth; - ngx_str_t ssl_trusted_certificate; - ngx_str_t ssl_crl; - ngx_array_t *ssl_conf_commands; -#endif -} ngx_http_proxy_loc_conf_t; - - -typedef struct { - ngx_http_status_t status; - ngx_http_chunked_t chunked; - ngx_http_proxy_vars_t vars; - off_t internal_body_length; - - ngx_chain_t *free; - ngx_chain_t *busy; - - unsigned head:1; - unsigned internal_chunked:1; - unsigned header_sent:1; -} ngx_http_proxy_ctx_t; - - -static ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r, - ngx_http_proxy_ctx_t *ctx, ngx_http_proxy_loc_conf_t *plcf); -#if (NGX_HTTP_CACHE) -static ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r); -#endif static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r); static ngx_int_t ngx_http_proxy_reinit_request(ngx_http_request_t *r); static ngx_int_t ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in); @@ -163,6 +71,8 @@ static ngx_int_t ngx_http_proxy_non_buffered_copy_filter(void *data, ssize_t bytes); static ngx_int_t ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes); +static ngx_int_t ngx_http_proxy_process_trailer(ngx_http_request_t *r, + ngx_buf_t *buf); static void ngx_http_proxy_abort_request(ngx_http_request_t *r); static void ngx_http_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc); @@ -174,15 +84,14 @@ static ngx_int_t ngx_http_proxy_port_variable(ngx_http_request_t *r, static ngx_int_t ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t + ngx_http_proxy_internal_connection_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_proxy_internal_body_length_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_proxy_internal_chunked_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, - ngx_table_elt_t *h, size_t prefix); -static ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, - ngx_table_elt_t *h); static ngx_int_t ngx_http_proxy_parse_cookie(ngx_str_t *value, ngx_array_t *attrs); static ngx_int_t ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, @@ -459,6 +368,13 @@ static ngx_command_t ngx_http_proxy_commands[] = { offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_body), NULL }, + { ngx_string("proxy_pass_trailers"), + 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_proxy_loc_conf_t, upstream.pass_trailers), + NULL }, + { ngx_string("proxy_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -829,8 +745,8 @@ static char ngx_http_proxy_version_11[] = " HTTP/1.1" CRLF; static ngx_keyval_t ngx_http_proxy_headers[] = { - { ngx_string("Host"), ngx_string("$proxy_host") }, - { ngx_string("Connection"), ngx_string("close") }, + { ngx_string("Host"), ngx_string("$proxy_internal_host") }, + { ngx_string("Connection"), ngx_string("$proxy_internal_connection") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") }, { ngx_string("TE"), ngx_string("") }, @@ -857,8 +773,8 @@ static ngx_str_t ngx_http_proxy_hide_headers[] = { #if (NGX_HTTP_CACHE) static ngx_keyval_t ngx_http_proxy_cache_headers[] = { - { ngx_string("Host"), ngx_string("$proxy_host") }, - { ngx_string("Connection"), ngx_string("close") }, + { ngx_string("Host"), ngx_string("$proxy_internal_host") }, + { ngx_string("Connection"), ngx_string("$proxy_internal_connection") }, { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") }, { ngx_string("TE"), ngx_string("") }, @@ -893,6 +809,14 @@ static ngx_http_variable_t ngx_http_proxy_vars[] = { { ngx_string("proxy_add_via"), NULL, NULL, 0, NGX_HTTP_VAR_NOHASH, 0 }, #endif + { ngx_string("proxy_internal_host"), NULL, + ngx_http_proxy_host_variable, 1, + NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, + + { ngx_string("proxy_internal_connection"), NULL, + ngx_http_proxy_internal_connection_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, + { ngx_string("proxy_internal_body_length"), NULL, ngx_http_proxy_internal_body_length_variable, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, @@ -960,6 +884,8 @@ ngx_http_proxy_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } + ctx->legacy = 1; + ngx_http_set_ctx(r, ctx, ngx_http_proxy_module); plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); @@ -1039,7 +965,7 @@ ngx_http_proxy_handler(ngx_http_request_t *r) } -static ngx_int_t +ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx, ngx_http_proxy_loc_conf_t *plcf) { @@ -1143,7 +1069,7 @@ ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx, #if (NGX_HTTP_CACHE) -static ngx_int_t +ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r) { size_t len, loc_len; @@ -1195,7 +1121,8 @@ ngx_http_proxy_create_key(ngx_http_request_t *r) return NGX_OK; } - loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0; + loc_len = (r->valid_location && ctx->vars.uri.len) + ? ngx_min(plcf->location.len, r->uri.len) : 0; if (r->quoted_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, @@ -1307,8 +1234,8 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) uri_len = r->unparsed_uri.len; } else { - loc_len = (r->valid_location && ctx->vars.uri.len) ? - plcf->location.len : 0; + loc_len = (r->valid_location && ctx->vars.uri.len) + ? ngx_min(plcf->location.len, r->uri.len) : 0; if (r->quoted_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, @@ -2107,7 +2034,7 @@ ngx_http_proxy_input_filter_init(void *data) /* chunked */ u->pipe->input_filter = ngx_http_proxy_chunked_filter; - u->pipe->length = 3; /* "0" LF LF */ + u->pipe->length = 5; /* "0" CRLF CRLF */ u->input_filter = ngx_http_proxy_non_buffered_chunked_filter; u->length = 1; @@ -2213,11 +2140,12 @@ ngx_http_proxy_copy_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) static ngx_int_t ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) { - ngx_int_t rc; - ngx_buf_t *b, **prev; - ngx_chain_t *cl; - ngx_http_request_t *r; - ngx_http_proxy_ctx_t *ctx; + ngx_int_t rc; + ngx_buf_t *b, **prev; + ngx_chain_t *cl; + ngx_http_request_t *r; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; if (buf->pos == buf->last) { return NGX_OK; @@ -2248,11 +2176,39 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) } b = NULL; + + if (ctx->trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + + /* a whole response has been parsed successfully */ + + p->length = 0; + r->upstream->keepalive = !r->upstream->headers_in.connection_close; + + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent data after trailers"); + r->upstream->keepalive = 0; + } + } + + goto free_buf; + } + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + prev = &buf->shadow; for ( ;; ) { - rc = ngx_http_parse_chunked(r, buf, &ctx->chunked); + rc = ngx_http_parse_chunked(r, buf, &ctx->chunked, + plcf->upstream.pass_trailers); if (rc == NGX_OK) { @@ -2307,6 +2263,19 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) if (rc == NGX_DONE) { + if (plcf->upstream.pass_trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + p->length = 1; + break; + } + } + /* a whole response has been parsed successfully */ p->length = 0; @@ -2338,6 +2307,8 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) return NGX_ERROR; } +free_buf: + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->log, 0, "http proxy chunked state %ui, length %O", ctx->chunked.state, p->length); @@ -2433,11 +2404,14 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) { ngx_http_request_t *r = data; - ngx_int_t rc; - ngx_buf_t *b, *buf; - ngx_chain_t *cl, **ll; - ngx_http_upstream_t *u; - ngx_http_proxy_ctx_t *ctx; + ngx_int_t rc; + ngx_buf_t *b, *buf; + ngx_chain_t *cl, **ll; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); @@ -2451,13 +2425,38 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) buf->pos = buf->last; buf->last += bytes; + if (ctx->trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + + /* a whole response has been parsed successfully */ + + r->upstream->keepalive = !u->headers_in.connection_close; + u->length = 0; + + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent data after trailers"); + u->keepalive = 0; + } + } + + return NGX_OK; + } + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } for ( ;; ) { - rc = ngx_http_parse_chunked(r, buf, &ctx->chunked); + rc = ngx_http_parse_chunked(r, buf, &ctx->chunked, + plcf->upstream.pass_trailers); if (rc == NGX_OK) { @@ -2499,6 +2498,19 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) if (rc == NGX_DONE) { + if (plcf->upstream.pass_trailers) { + rc = ngx_http_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + u->length = 1; + break; + } + } + /* a whole response has been parsed successfully */ u->keepalive = !u->headers_in.connection_close; @@ -2529,6 +2541,115 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) } +static ngx_int_t +ngx_http_proxy_process_trailer(ngx_http_request_t *r, ngx_buf_t *buf) +{ + size_t len; + ngx_int_t rc; + ngx_buf_t *b; + ngx_table_elt_t *h; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (ctx->trailers == NULL) { + ctx->trailers = ngx_create_temp_buf(r->pool, + plcf->upstream.buffer_size); + if (ctx->trailers == NULL) { + return NGX_ERROR; + } + } + + b = ctx->trailers; + len = ngx_min(buf->last - buf->pos, b->end - b->last); + + b->last = ngx_cpymem(b->last, buf->pos, len); + + for ( ;; ) { + + rc = ngx_http_parse_header_line(r, b, 1); + + if (rc == NGX_OK) { + + /* a header line has been parsed successfully */ + + h = ngx_list_push(&r->upstream->headers_in.trailers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->value.len = r->header_end - r->header_start; + + h->key.data = ngx_pnalloc(r->pool, + h->key.len + 1 + h->value.len + 1 + h->key.len); + if (h->key.data == NULL) { + h->hash = 0; + return NGX_ERROR; + } + + h->value.data = h->key.data + h->key.len + 1; + h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; + + ngx_memcpy(h->key.data, r->header_name_start, h->key.len); + h->key.data[h->key.len] = '\0'; + ngx_memcpy(h->value.data, r->header_start, h->value.len); + h->value.data[h->value.len] = '\0'; + + if (h->key.len == r->lowcase_index) { + ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); + + } else { + ngx_strlow(h->lowcase_key, h->key.data, h->key.len); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy trailer: \"%V: %V\"", + &h->key, &h->value); + continue; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + + /* a whole header has been parsed successfully */ + + buf->pos += len - (b->last - b->pos); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy trailer done"); + + return NGX_OK; + } + + if (rc == NGX_AGAIN) { + buf->pos += len; + + if (b->last == b->end) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too big trailers"); + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid trailer: \"%*s\\x%02xd...\"", + r->header_end - r->header_name_start, + r->header_name_start, *r->header_end); + + return NGX_ERROR; + } +} + + static void ngx_http_proxy_abort_request(ngx_http_request_t *r) { @@ -2562,6 +2683,11 @@ ngx_http_proxy_host_variable(ngx_http_request_t *r, return NGX_OK; } + if (data == 1 && !ctx->legacy) { + v->not_found = 1; + return NGX_OK; + } + v->len = ctx->vars.host_header.len; v->valid = 1; v->no_cacheable = 0; @@ -2642,6 +2768,29 @@ ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r, } +static ngx_int_t +ngx_http_proxy_internal_connection_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_proxy_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (ctx == NULL || !ctx->legacy) { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + ngx_str_set(v, "close"); + + return NGX_OK; +} + + static ngx_int_t ngx_http_proxy_internal_body_length_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) @@ -2695,7 +2844,7 @@ ngx_http_proxy_internal_chunked_variable(ngx_http_request_t *r, } -static ngx_int_t +ngx_int_t ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix) { @@ -2727,7 +2876,7 @@ ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h, } -static ngx_int_t +ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h) { u_char *p; @@ -3372,6 +3521,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) * conf->headers.values = NULL; * conf->headers.hash = { NULL, 0 }; * conf->headers_cache.lengths = NULL; + * conf->host_set = 0; * conf->headers_cache.values = NULL; * conf->headers_cache.hash = { NULL, 0 }; * conf->body_lengths = NULL; @@ -3411,6 +3561,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) conf->upstream.pass_request_headers = NGX_CONF_UNSET; conf->upstream.pass_request_body = NGX_CONF_UNSET; + conf->upstream.pass_trailers = NGX_CONF_UNSET; #if (NGX_HTTP_CACHE) conf->upstream.cache = NGX_CONF_UNSET; @@ -3754,6 +3905,9 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->upstream.pass_request_body, prev->upstream.pass_request_body, 1); + ngx_conf_merge_value(conf->upstream.pass_trailers, + prev->upstream.pass_trailers, 0); + ngx_conf_merge_value(conf->upstream.intercept_errors, prev->upstream.intercept_errors, 0); @@ -3943,6 +4097,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; #endif + conf->host_set = prev->host_set; } rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, @@ -3975,6 +4130,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_CACHE) prev->headers_cache = conf->headers_cache; #endif + prev->host_set = conf->host_set; } return NGX_CONF_OK; @@ -4027,6 +4183,12 @@ ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, src = conf->headers_source->elts; for (i = 0; i < conf->headers_source->nelts; i++) { + if (src[i].key.len == 4 + && ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0) + { + conf->host_set = 1; + } + s = ngx_array_push(&headers_merged); if (s == NULL) { return NGX_ERROR; diff --git a/src/http/modules/ngx_http_proxy_module.h b/src/http/modules/ngx_http_proxy_module.h new file mode 100644 index 0000000..a0f79bc --- /dev/null +++ b/src/http/modules/ngx_http_proxy_module.h @@ -0,0 +1,127 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_PROXY_H_INCLUDED_ +#define _NGX_HTTP_PROXY_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_array_t caches; /* ngx_http_file_cache_t * */ +} ngx_http_proxy_main_conf_t; + + +typedef struct { + ngx_str_t key_start; + ngx_str_t schema; + ngx_str_t host_header; + ngx_str_t port; + ngx_str_t uri; +} ngx_http_proxy_vars_t; + + +typedef struct { + ngx_array_t *flushes; + ngx_array_t *lengths; + ngx_array_t *values; + ngx_hash_t hash; +} ngx_http_proxy_headers_t; + + +typedef struct { + ngx_http_upstream_conf_t upstream; + + ngx_array_t *body_flushes; + ngx_array_t *body_lengths; + ngx_array_t *body_values; + ngx_str_t body_source; + + ngx_http_proxy_headers_t headers; +#if (NGX_HTTP_CACHE) + ngx_http_proxy_headers_t headers_cache; +#endif + ngx_array_t *headers_source; + ngx_uint_t host_set; + + ngx_array_t *proxy_lengths; + ngx_array_t *proxy_values; + + ngx_array_t *redirects; + ngx_array_t *cookie_domains; + ngx_array_t *cookie_paths; + ngx_array_t *cookie_flags; + + ngx_http_complex_value_t *method; + ngx_str_t location; + ngx_str_t url; + +#if (NGX_HTTP_CACHE) + ngx_http_complex_value_t cache_key; +#endif + + ngx_http_proxy_vars_t vars; + + ngx_flag_t redirect; + + ngx_uint_t http_version; + + ngx_uint_t headers_hash_max_size; + ngx_uint_t headers_hash_bucket_size; + +#if (NGX_HTTP_SSL || NGX_COMPAT) + ngx_uint_t ssl; + ngx_uint_t ssl_protocols; + ngx_str_t ssl_ciphers; + ngx_uint_t ssl_verify_depth; + ngx_str_t ssl_trusted_certificate; + ngx_str_t ssl_crl; + ngx_array_t *ssl_conf_commands; +#endif +} ngx_http_proxy_loc_conf_t; + + +typedef struct { + ngx_http_status_t status; + ngx_http_chunked_t chunked; + ngx_http_proxy_vars_t vars; + off_t internal_body_length; + + ngx_chain_t *free; + ngx_chain_t *busy; + + ngx_buf_t *trailers; + + unsigned head:1; + unsigned internal_chunked:1; + unsigned header_sent:1; + unsigned legacy:1; +} ngx_http_proxy_ctx_t; + + +ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx, + ngx_http_proxy_loc_conf_t *plcf); +#if (NGX_HTTP_CACHE) +ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r); +#endif +ngx_int_t ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, + ngx_table_elt_t *h, size_t prefix); +ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, + ngx_table_elt_t *h); + +#if (NGX_HTTP_V2) +ngx_int_t ngx_http_proxy_v2_handler(ngx_http_request_t *r); +#endif + + +extern ngx_module_t ngx_http_proxy_module; + + +#endif /* _NGX_HTTP_PROXY_H_INCLUDED_ */ diff --git a/src/http/modules/ngx_http_proxy_v2_module.c b/src/http/modules/ngx_http_proxy_v2_module.c new file mode 100644 index 0000000..17983a2 --- /dev/null +++ b/src/http/modules/ngx_http_proxy_v2_module.c @@ -0,0 +1,4160 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +typedef enum { + ngx_http_proxy_v2_st_start = 0, + ngx_http_proxy_v2_st_length_2, + ngx_http_proxy_v2_st_length_3, + ngx_http_proxy_v2_st_type, + ngx_http_proxy_v2_st_flags, + ngx_http_proxy_v2_st_stream_id, + ngx_http_proxy_v2_st_stream_id_2, + ngx_http_proxy_v2_st_stream_id_3, + ngx_http_proxy_v2_st_stream_id_4, + ngx_http_proxy_v2_st_payload, + ngx_http_proxy_v2_st_padding +} ngx_http_proxy_v2_state_e; + + +typedef struct { + size_t init_window; + size_t send_window; + size_t recv_window; + ngx_uint_t last_stream_id; +} ngx_http_proxy_v2_conn_t; + + +typedef struct { + ngx_http_proxy_ctx_t ctx; + + ngx_http_proxy_v2_state_e state; + ngx_uint_t frame_state; + ngx_uint_t fragment_state; + + ngx_chain_t *in; + ngx_chain_t *out; + ngx_chain_t *free; + ngx_chain_t *busy; + + ngx_http_proxy_v2_conn_t *connection; + + ngx_uint_t id; + + ngx_uint_t pings; + ngx_uint_t settings; + + off_t length; + + ssize_t send_window; + size_t recv_window; + + size_t rest; + ngx_uint_t stream_id; + u_char type; + u_char flags; + u_char padding; + + ngx_uint_t error; + ngx_uint_t window_update; + + ngx_uint_t setting_id; + ngx_uint_t setting_value; + + u_char ping_data[8]; + + ngx_uint_t index; + ngx_str_t name; + ngx_str_t value; + + u_char *field_end; + size_t field_length; + size_t field_rest; + u_char field_state; + + unsigned literal:1; + unsigned field_huffman:1; + + unsigned header_sent:1; + unsigned output_closed:1; + unsigned output_blocked:1; + unsigned parsing_headers:1; + unsigned end_stream:1; + unsigned done:1; + unsigned status:1; + unsigned rst:1; + unsigned goaway:1; +} ngx_http_proxy_v2_ctx_t; + + +typedef struct { + u_char length_0; + u_char length_1; + u_char length_2; + u_char type; + u_char flags; + u_char stream_id_0; + u_char stream_id_1; + u_char stream_id_2; + u_char stream_id_3; +} ngx_http_proxy_v2_frame_t; + + +static ngx_int_t ngx_http_proxy_v2_create_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_proxy_v2_reinit_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_proxy_v2_body_output_filter(void *data, + ngx_chain_t *in); +static ngx_int_t ngx_http_proxy_v2_process_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_proxy_v2_filter_init(void *data); +static ngx_int_t ngx_http_proxy_v2_non_buffered_filter(void *data, + ssize_t bytes); +static ngx_int_t ngx_http_proxy_v2_body_filter(ngx_event_pipe_t *p, + ngx_buf_t *buf); +static ngx_int_t ngx_http_proxy_v2_process_control_frame(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_skip_frame(ngx_http_proxy_v2_ctx_t *ctx, + ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_process_frames(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); + +static ngx_int_t ngx_http_proxy_v2_parse_frame(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_parse_header(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_validate_header_name(ngx_http_request_t *r, + ngx_str_t *s); +static ngx_int_t ngx_http_proxy_v2_validate_header_value(ngx_http_request_t *r, + ngx_str_t *s); +static ngx_int_t ngx_http_proxy_v2_parse_rst_stream(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_parse_goaway(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_parse_window_update(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_parse_settings(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_proxy_v2_parse_ping(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b); + +static ngx_int_t ngx_http_proxy_v2_send_settings_ack(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx); +static ngx_int_t ngx_http_proxy_v2_send_ping_ack(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx); +static ngx_int_t ngx_http_proxy_v2_send_window_update(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx); + +static ngx_chain_t *ngx_http_proxy_v2_get_buf(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx); +static ngx_http_proxy_v2_ctx_t * + ngx_http_proxy_v2_get_ctx(ngx_http_request_t *r); +static ngx_int_t ngx_http_proxy_v2_get_connection_data(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_peer_connection_t *pc); +static void ngx_http_proxy_v2_cleanup(void *data); + +static void ngx_http_proxy_v2_abort_request(ngx_http_request_t *r); +static void ngx_http_proxy_v2_finalize_request(ngx_http_request_t *r, + ngx_int_t rc); + + +static ngx_http_module_t ngx_http_proxy_v2_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* 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_proxy_v2_module = { + NGX_MODULE_V1, + &ngx_http_proxy_v2_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 u_char ngx_http_proxy_v2_connection_start[] = + "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" /* connection preface */ + + "\x00\x00\x12\x04\x00\x00\x00\x00\x00" /* settings frame */ + "\x00\x01\x00\x00\x00\x00" /* header table size */ + "\x00\x02\x00\x00\x00\x00" /* disable push */ + "\x00\x04\x7f\xff\xff\xff" /* initial window */ + + "\x00\x00\x04\x08\x00\x00\x00\x00\x00" /* window update frame */ + "\x7f\xff\x00\x00"; + + +ngx_int_t +ngx_http_proxy_v2_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_upstream_t *u; + ngx_http_proxy_v2_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; +#if (NGX_HTTP_CACHE) + ngx_http_proxy_main_conf_t *pmcf; +#endif + + if (ngx_http_upstream_create(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_v2_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_proxy_v2_module); + + ngx_http_set_ctx(r, &ctx->ctx, ngx_http_proxy_module); + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + plcf->upstream.preserve_output = 1; + + u = r->upstream; + + if (plcf->proxy_lengths == NULL) { + ctx->ctx.vars = plcf->vars; + u->schema = plcf->vars.schema; +#if (NGX_HTTP_SSL) + u->ssl = plcf->ssl; +#endif + + } else { + if (ngx_http_proxy_eval(r, &ctx->ctx, plcf) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + +#if (NGX_HTTP_SSL) + ngx_str_set(&u->ssl_alpn_protocol, NGX_HTTP_V2_ALPN_PROTO); +#endif + + u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_v2_module; + + u->conf = &plcf->upstream; + +#if (NGX_HTTP_CACHE) + pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module); + + u->caches = &pmcf->caches; + u->create_key = ngx_http_proxy_create_key; +#endif + + u->create_request = ngx_http_proxy_v2_create_request; + u->reinit_request = ngx_http_proxy_v2_reinit_request; + u->process_header = ngx_http_proxy_v2_process_header; + u->abort_request = ngx_http_proxy_v2_abort_request; + u->finalize_request = ngx_http_proxy_v2_finalize_request; + + if (plcf->redirects) { + u->rewrite_redirect = ngx_http_proxy_rewrite_redirect; + } + + if (plcf->cookie_domains || plcf->cookie_paths || plcf->cookie_flags) { + u->rewrite_cookie = ngx_http_proxy_rewrite_cookie; + } + + u->buffering = plcf->upstream.buffering; + + u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); + if (u->pipe == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + u->pipe->input_filter = ngx_http_proxy_v2_body_filter; + u->pipe->input_ctx = r; + + u->input_filter_init = ngx_http_proxy_v2_filter_init; + u->input_filter = ngx_http_proxy_v2_non_buffered_filter; + u->input_filter_ctx = r; + + u->accel = 1; + + if (!plcf->upstream.request_buffering + && plcf->body_values == NULL && plcf->upstream.pass_request_body) + { + r->request_body_no_buffering = 1; + } + + rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_proxy_v2_create_request(ngx_http_request_t *r) +{ + u_char *p, *tmp, *key_tmp, *val_tmp, *headers_frame; + size_t len, tmp_len, key_len, val_len, uri_len, + loc_len, body_len; + uintptr_t escape; + ngx_buf_t *b; + ngx_str_t method, *host; + ngx_uint_t i, next, unparsed_uri; + ngx_chain_t *cl, *body; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_upstream_t *u; + ngx_http_proxy_v2_ctx_t *ctx; + ngx_http_script_code_pt code; + ngx_http_script_engine_t e, le; + ngx_http_proxy_headers_t *headers; + ngx_http_proxy_v2_frame_t *f; + ngx_http_proxy_loc_conf_t *plcf; + ngx_http_script_len_code_pt lcode; + + u = r->upstream; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + +#if (NGX_HTTP_CACHE) + headers = u->cacheable ? &plcf->headers_cache : &plcf->headers; +#else + headers = &plcf->headers; +#endif + + if (u->method.len) { + /* HEAD was changed to GET to cache response */ + method = u->method; + + } else if (plcf->method) { + if (ngx_http_complex_value(r, plcf->method, &method) != NGX_OK) { + return NGX_ERROR; + } + + } else { + method = r->method_name; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_v2_module); + + if (method.len == 4 + && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) + { + ctx->ctx.head = 1; + } + + len = sizeof(ngx_http_proxy_v2_connection_start) - 1 + + sizeof(ngx_http_proxy_v2_frame_t); /* headers frame */ + + /* :method header */ + + if ((method.len == 3 && ngx_strncmp(method.data, "GET", 3) == 0) + || (method.len == 4 && ngx_strncmp(method.data, "POST", 4) == 0)) + { + len += 1; + tmp_len = 0; + + } else { + len += 1 + NGX_HTTP_V2_INT_OCTETS + method.len; + tmp_len = method.len; + } + + /* :scheme header */ + + len += 1; + + /* :path header */ + + escape = 0; + loc_len = 0; + unparsed_uri = 0; + + if (plcf->proxy_lengths && ctx->ctx.vars.uri.len) { + uri_len = ctx->ctx.vars.uri.len; + + } else if (ctx->ctx.vars.uri.len == 0 && r->valid_unparsed_uri) { + unparsed_uri = 1; + uri_len = r->unparsed_uri.len; + + } else { + loc_len = (r->valid_location && ctx->ctx.vars.uri.len) + ? ngx_min(plcf->location.len, r->uri.len) : 0; + + if (r->quoted_uri || r->internal) { + escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, + r->uri.len - loc_len, NGX_ESCAPE_URI); + } + + uri_len = ctx->ctx.vars.uri.len + r->uri.len - loc_len + escape + + sizeof("?") - 1 + r->args.len; + } + + if (uri_len == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "zero length URI to proxy"); + return NGX_ERROR; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + uri_len; + + if (tmp_len < uri_len) { + tmp_len = uri_len; + } + + /* :authority header */ + + host = &ctx->ctx.vars.host_header; + + if (!plcf->host_set) { + len += 1 + NGX_HTTP_V2_INT_OCTETS + host->len; + + if (tmp_len < host->len) { + tmp_len = host->len; + } + } + + /* other headers */ + + ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); + + ngx_http_script_flush_no_cacheable_variables(r, plcf->body_flushes); + ngx_http_script_flush_no_cacheable_variables(r, headers->flushes); + + body_len = 0; + + if (plcf->body_lengths) { + le.ip = plcf->body_lengths->elts; + le.request = r; + le.flushed = 1; + + while (*(uintptr_t *) le.ip) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + body_len += lcode(&le); + } + + ctx->ctx.internal_body_length = body_len; + + len += sizeof(ngx_http_proxy_v2_frame_t); + len += body_len; + + } else if (r->headers_in.chunked && r->reading_body) { + ctx->ctx.internal_body_length = -1; + + } else { + ctx->ctx.internal_body_length = r->headers_in.content_length_n; + } + + le.ip = headers->lengths->elts; + le.request = r; + le.flushed = 1; + + while (*(uintptr_t *) le.ip) { + + lcode = *(ngx_http_script_len_code_pt *) le.ip; + key_len = lcode(&le); + + for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + } + le.ip += sizeof(uintptr_t); + + if (val_len == 0) { + continue; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + key_len + + NGX_HTTP_V2_INT_OCTETS + val_len; + + if (tmp_len < key_len) { + tmp_len = key_len; + } + + if (tmp_len < val_len) { + tmp_len = val_len; + } + } + + if (plcf->upstream.pass_request_headers) { + part = &r->headers_in.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 (ngx_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; + + if (tmp_len < header[i].key.len) { + tmp_len = header[i].key.len; + } + + if (tmp_len < header[i].value.len) { + tmp_len = header[i].value.len; + } + } + } + + /* continuation frames */ + + len += sizeof(ngx_http_proxy_v2_frame_t) + * (len / NGX_HTTP_V2_DEFAULT_FRAME_SIZE); + + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + tmp = ngx_palloc(r->pool, tmp_len * 3); + if (tmp == NULL) { + return NGX_ERROR; + } + + key_tmp = tmp + tmp_len; + val_tmp = tmp + 2 * tmp_len; + + /* connection preface */ + + b->last = ngx_copy(b->last, ngx_http_proxy_v2_connection_start, + sizeof(ngx_http_proxy_v2_connection_start) - 1); + + /* headers frame */ + + headers_frame = b->last; + + f = (ngx_http_proxy_v2_frame_t *) b->last; + b->last += sizeof(ngx_http_proxy_v2_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 0; + f->type = NGX_HTTP_V2_HEADERS_FRAME; + f->flags = 0; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 1; + + if (method.len == 3 && ngx_strncmp(method.data, "GET", 3) == 0) { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: GET\""); + + } else if (method.len == 4 && ngx_strncmp(method.data, "POST", 4) == 0) { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_POST_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: POST\""); + + } else { + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_METHOD_INDEX); + b->last = ngx_http_v2_write_value(b->last, method.data, + method.len, tmp); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: %V\"", &method); + } + +#if (NGX_HTTP_SSL) + if (u->ssl) { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":scheme: https\""); + } else +#endif + { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":scheme: http\""); + } + + if (plcf->proxy_lengths && ctx->ctx.vars.uri.len) { + + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); + b->last = ngx_http_v2_write_value(b->last, ctx->ctx.vars.uri.data, + ctx->ctx.vars.uri.len, tmp); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":path: %V\"", &ctx->ctx.vars.uri); + + } else if (unparsed_uri) { + + if (r->unparsed_uri.len == 1 && r->unparsed_uri.data[0] == '/') { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_PATH_ROOT_INDEX); + + } else { + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); + b->last = ngx_http_v2_write_value(b->last, r->unparsed_uri.data, + r->unparsed_uri.len, tmp); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":path: %V\"", &r->unparsed_uri); + + } else { + p = val_tmp; + + if (r->valid_location) { + p = ngx_copy(p, ctx->ctx.vars.uri.data, ctx->ctx.vars.uri.len); + } + + if (escape) { + ngx_escape_uri(p, r->uri.data + loc_len, + r->uri.len - loc_len, NGX_ESCAPE_URI); + p += r->uri.len - loc_len + escape; + + } else { + p = ngx_copy(p, r->uri.data + loc_len, r->uri.len - loc_len); + } + + if (r->args.len > 0) { + *p++ = '?'; + p = ngx_copy(p, r->args.data, r->args.len); + } + + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); + b->last = ngx_http_v2_write_value(b->last, val_tmp, p - val_tmp, tmp); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":path: %*s\"", p - val_tmp, + val_tmp); + } + + if (!plcf->host_set) { + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); + b->last = ngx_http_v2_write_value(b->last, host->data, host->len, tmp); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":authority: %V\"", host); + } + + ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); + + e.ip = headers->values->elts; + e.request = r; + e.flushed = 1; + + le.ip = headers->lengths->elts; + + while (*(uintptr_t *) le.ip) { + + lcode = *(ngx_http_script_len_code_pt *) le.ip; + key_len = lcode(&le); + + for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + } + le.ip += sizeof(uintptr_t); + + if (val_len == 0) { + e.skip = 1; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + e.ip += sizeof(uintptr_t); + + e.skip = 0; + + continue; + } + + *b->last++ = 0; + + e.pos = key_tmp; + + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + + b->last = ngx_http_v2_write_name(b->last, key_tmp, key_len, tmp); + + e.pos = val_tmp; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + e.ip += sizeof(uintptr_t); + + b->last = ngx_http_v2_write_value(b->last, val_tmp, val_len, tmp); + +#if (NGX_DEBUG) + if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_strlow(key_tmp, key_tmp, key_len); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%*s: %*s\"", + key_len, key_tmp, val_len, val_tmp); + } +#endif + } + + if (plcf->upstream.pass_request_headers) { + part = &r->headers_in.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 (ngx_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + *b->last++ = 0; + + b->last = ngx_http_v2_write_name(b->last, header[i].key.data, + header[i].key.len, tmp); + + b->last = ngx_http_v2_write_value(b->last, header[i].value.data, + header[i].value.len, tmp); + +#if (NGX_DEBUG) + if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_strlow(tmp, header[i].key.data, header[i].key.len); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%*s: %V\"", + header[i].key.len, tmp, &header[i].value); + } +#endif + } + } + + /* update headers frame length */ + + len = b->last - headers_frame - sizeof(ngx_http_proxy_v2_frame_t); + + if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { + len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + next = 1; + + } else { + next = 0; + } + + f = (ngx_http_proxy_v2_frame_t *) headers_frame; + + f->length_0 = (u_char) ((len >> 16) & 0xff); + f->length_1 = (u_char) ((len >> 8) & 0xff); + f->length_2 = (u_char) (len & 0xff); + + /* create additional continuation frames */ + + p = headers_frame; + + while (next) { + p += sizeof(ngx_http_proxy_v2_frame_t) + NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + len = b->last - p; + + ngx_memmove(p + sizeof(ngx_http_proxy_v2_frame_t), p, len); + b->last += sizeof(ngx_http_proxy_v2_frame_t); + + if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { + len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + next = 1; + + } else { + next = 0; + } + + f = (ngx_http_proxy_v2_frame_t *) p; + + f->length_0 = (u_char) ((len >> 16) & 0xff); + f->length_1 = (u_char) ((len >> 8) & 0xff); + f->length_2 = (u_char) (len & 0xff); + f->type = NGX_HTTP_V2_CONTINUATION_FRAME; + f->flags = 0; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 1; + } + + f->flags |= NGX_HTTP_V2_END_HEADERS_FLAG; + + if (plcf->body_values) { + f = (ngx_http_proxy_v2_frame_t *) b->last; + b->last += sizeof(ngx_http_proxy_v2_frame_t); + + f->length_0 = (u_char) ((body_len >> 16) & 0xff); + f->length_1 = (u_char) ((body_len >> 8) & 0xff); + f->length_2 = (u_char) (body_len & 0xff); + f->type = NGX_HTTP_V2_DATA_FRAME; + f->flags = NGX_HTTP_V2_END_STREAM_FLAG; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 1; + + e.ip = plcf->body_values->elts; + e.pos = b->last; + e.request = r; + e.flushed = 1; + e.skip = 0; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + + b->last = e.pos; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: %*xs%s, len: %uz", + (size_t) ngx_min(b->last - b->pos, 256), b->pos, + b->last - b->pos > 256 ? "..." : "", + b->last - b->pos); + + if (r->request_body_no_buffering) { + + u->request_bufs = cl; + + } else if (plcf->body_values == NULL && plcf->upstream.pass_request_body) { + + body = u->request_bufs; + u->request_bufs = cl; + + if (body == NULL) { + f = (ngx_http_proxy_v2_frame_t *) headers_frame; + f->flags |= NGX_HTTP_V2_END_STREAM_FLAG; + } + + while (body) { + b = ngx_alloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); + + cl->next = ngx_alloc_chain_link(r->pool); + if (cl->next == NULL) { + return NGX_ERROR; + } + + cl = cl->next; + cl->buf = b; + + body = body->next; + } + + b->last_buf = 1; + + } else { + u->request_bufs = cl; + + if (plcf->body_values == NULL) { + f = (ngx_http_proxy_v2_frame_t *) headers_frame; + f->flags |= NGX_HTTP_V2_END_STREAM_FLAG; + } + } + + u->output.output_filter = ngx_http_proxy_v2_body_output_filter; + u->output.filter_ctx = r; + + b->flush = 1; + cl->next = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_reinit_request(ngx_http_request_t *r) +{ + ngx_http_proxy_v2_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_v2_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ctx->state = 0; + ctx->header_sent = 0; + ctx->output_closed = 0; + ctx->output_blocked = 0; + ctx->parsing_headers = 0; + ctx->end_stream = 0; + ctx->done = 0; + ctx->status = 0; + ctx->rst = 0; + ctx->goaway = 0; + ctx->connection = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_body_output_filter(void *data, ngx_chain_t *in) +{ + ngx_http_request_t *r = data; + + off_t file_pos; + u_char *p, *pos, *start; + size_t len, limit; + ngx_buf_t *b; + ngx_int_t rc; + ngx_uint_t next, last; + ngx_chain_t *cl, *out, *ln, **ll; + ngx_http_upstream_t *u; + ngx_http_proxy_v2_ctx_t *ctx; + ngx_http_proxy_v2_frame_t *f; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy output filter"); + + ctx = ngx_http_proxy_v2_get_ctx(r); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (in) { + if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) { + return NGX_ERROR; + } + } + + out = NULL; + ll = &out; + + if (!ctx->header_sent) { + /* first buffer contains headers */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy output header"); + + ctx->header_sent = 1; + + if (ctx->id != 1) { + /* + * keepalive connection: skip connection preface, + * update stream identifiers + */ + + b = ctx->in->buf; + b->pos += sizeof(ngx_http_proxy_v2_connection_start) - 1; + + p = b->pos; + + while (p < b->last) { + f = (ngx_http_proxy_v2_frame_t *) p; + p += sizeof(ngx_http_proxy_v2_frame_t); + + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + p += (f->length_0 << 16) + (f->length_1 << 8) + f->length_2; + } + } + + if (ctx->in->buf->last_buf) { + ctx->output_closed = 1; + } + + *ll = ctx->in; + ll = &ctx->in->next; + + ctx->in = ctx->in->next; + } + + if (ctx->out) { + /* queued control frames */ + + *ll = ctx->out; + + for (cl = ctx->out, ll = &cl->next; cl; cl = cl->next) { + ll = &cl->next; + } + + ctx->out = NULL; + } + + f = NULL; + last = 0; + + limit = ngx_max(0, ctx->send_window); + + if (limit > ctx->connection->send_window) { + limit = ctx->connection->send_window; + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy output limit: %uz w:%z:%uz", + limit, ctx->send_window, ctx->connection->send_window); + +#if (NGX_SUPPRESS_WARN) + file_pos = 0; + pos = NULL; + cl = NULL; +#endif + + in = ctx->in; + + while (in && limit > 0) { + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "http proxy output in l:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + in->buf->last_buf, + in->buf->in_file, + in->buf->start, in->buf->pos, + in->buf->last - in->buf->pos, + in->buf->file_pos, + in->buf->file_last - in->buf->file_pos); + + if (ngx_buf_special(in->buf)) { + goto next; + } + + if (in->buf->in_file) { + file_pos = in->buf->file_pos; + + } else { + pos = in->buf->pos; + } + + next = 0; + + do { + + cl = ngx_http_proxy_v2_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + + f = (ngx_http_proxy_v2_frame_t *) b->last; + b->last += sizeof(ngx_http_proxy_v2_frame_t); + + *ll = cl; + ll = &cl->next; + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + start = b->start; + + ngx_memcpy(b, in->buf, sizeof(ngx_buf_t)); + + /* + * restore b->start to preserve memory allocated in the buffer, + * to reuse it later for headers and control frames + */ + + b->start = start; + + if (in->buf->in_file) { + b->file_pos = file_pos; + file_pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit); + + if (file_pos >= in->buf->file_last) { + file_pos = in->buf->file_last; + next = 1; + } + + b->file_last = file_pos; + len = (ngx_uint_t) (file_pos - b->file_pos); + + } else { + b->pos = pos; + pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit); + + if (pos >= in->buf->last) { + pos = in->buf->last; + next = 1; + } + + b->last = pos; + len = (ngx_uint_t) (pos - b->pos); + } + + b->tag = (ngx_buf_tag_t) &ngx_http_proxy_v2_body_output_filter; + b->shadow = in->buf; + b->last_shadow = next; + + b->last_buf = 0; + b->last_in_chain = 0; + + *ll = cl; + ll = &cl->next; + + f->length_0 = (u_char) ((len >> 16) & 0xff); + f->length_1 = (u_char) ((len >> 8) & 0xff); + f->length_2 = (u_char) (len & 0xff); + f->type = NGX_HTTP_V2_DATA_FRAME; + f->flags = 0; + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + limit -= len; + ctx->send_window -= len; + ctx->connection->send_window -= len; + + } while (!next && limit > 0); + + if (!next) { + /* + * if the buffer wasn't fully sent due to flow control limits, + * preserve position for future use + */ + + if (in->buf->in_file) { + in->buf->file_pos = file_pos; + + } else { + in->buf->pos = pos; + } + + break; + } + + next: + + if (in->buf->last_buf) { + last = 1; + } + + ln = in; + in = in->next; + + ngx_free_chain(r->pool, ln); + } + + ctx->in = in; + + if (last) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy output last"); + + ctx->output_closed = 1; + + if (f) { + f->flags |= NGX_HTTP_V2_END_STREAM_FLAG; + + } else { + cl = ngx_http_proxy_v2_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + + f = (ngx_http_proxy_v2_frame_t *) b->last; + b->last += sizeof(ngx_http_proxy_v2_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 0; + f->type = NGX_HTTP_V2_DATA_FRAME; + f->flags = NGX_HTTP_V2_END_STREAM_FLAG; + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + *ll = cl; + ll = &cl->next; + } + + cl->buf->last_buf = 1; + } + + *ll = NULL; + +#if (NGX_DEBUG) + + for (cl = out; cl; cl = cl->next) { + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "http proxy output out l:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + cl->buf->last_buf, + 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); + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy output limit: %uz w:%z:%uz", + limit, ctx->send_window, ctx->connection->send_window); + +#endif + + rc = ngx_chain_writer(&r->upstream->writer, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_proxy_v2_body_output_filter); + + for (cl = ctx->free; cl; cl = cl->next) { + + /* mark original buffers as sent */ + + if (cl->buf->shadow) { + if (cl->buf->last_shadow) { + b = cl->buf->shadow; + b->pos = b->last; + } + + cl->buf->shadow = NULL; + } + } + + if (rc == NGX_OK && ctx->in) { + rc = NGX_AGAIN; + } + + if (rc == NGX_AGAIN) { + ctx->output_blocked = 1; + + } else { + ctx->output_blocked = 0; + } + + if (ctx->done) { + + /* + * We have already got the response and were sending some additional + * control frames. Even if there is still something unsent, stop + * here anyway. + */ + + u = r->upstream; + u->length = 0; + u->pipe->length = 0; + + if (ctx->in == NULL + && ctx->out == NULL + && ctx->output_closed + && !ctx->output_blocked + && !ctx->goaway + && ctx->state == ngx_http_proxy_v2_st_start) + { + u->keepalive = 1; + } + + ngx_post_event(u->peer.connection->read, &ngx_posted_events); + } + + return rc; +} + + +static ngx_int_t +ngx_http_proxy_v2_process_header(ngx_http_request_t *r) +{ + u_char *pos; + ngx_str_t *status_line; + ngx_int_t rc, status; + ngx_buf_t *b; + ngx_table_elt_t *h; + ngx_http_upstream_t *u; + ngx_http_proxy_v2_ctx_t *ctx; + ngx_http_upstream_header_t *hh; + ngx_http_upstream_main_conf_t *umcf; + + u = r->upstream; + b = &u->buffer; + pos = b->pos; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy response: %*xs%s, len: %uz", + (size_t) ngx_min(b->last - b->pos, 256), + b->pos, b->last - b->pos > 256 ? "..." : "", + b->last - b->pos); + + ctx = ngx_http_proxy_v2_get_ctx(r); + + if (ctx == NULL) { + return NGX_ERROR; + } + + umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + + for ( ;; ) { + + if (ctx->state < ngx_http_proxy_v2_st_payload) { + + rc = ngx_http_proxy_v2_parse_frame(r, ctx, b); + + if (rc == NGX_AGAIN) { + + /* + * there can be a lot of window update frames, + * so we reset buffer if it is empty and we haven't + * started parsing headers yet + */ + + if (!ctx->parsing_headers) { + b->pos = pos; + b->last = b->pos; + } + + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + /* + * RFC 7540 says that implementations MUST discard frames + * that have unknown or unsupported types. However, extension + * frames that appear in the middle of a header block are + * not permitted. Also, for obvious reasons CONTINUATION frames + * cannot appear before headers, and DATA frames are not expected + * to appear before all headers are parsed. + */ + + if (ctx->type == NGX_HTTP_V2_DATA_FRAME + || (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME + && !ctx->parsing_headers) + || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME + && ctx->parsing_headers)) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected http2 frame: %d", + ctx->type); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->stream_id && ctx->stream_id != ctx->id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for unknown stream %ui", + ctx->stream_id); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + } + + /* frame payload */ + + if (u->peer.connection) { + + if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) { + rc = ngx_http_proxy_v2_parse_rst_stream(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream rejected request with error %ui", + ctx->error); + + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + rc = ngx_http_proxy_v2_process_control_frame(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (rc == NGX_OK) { + continue; + } + } + + if (ctx->type != NGX_HTTP_V2_HEADERS_FRAME + && ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME) + { + /* priority, unknown frames */ + + rc = ngx_http_proxy_v2_skip_frame(ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + continue; + } + + /* headers */ + + for ( ;; ) { + + rc = ngx_http_proxy_v2_parse_header(r, ctx, b); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_OK) { + + /* a header line has been parsed successfully */ + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%V: %V\"", + &ctx->name, &ctx->value); + + if (ctx->name.len && ctx->name.data[0] == ':') { + + if (ctx->name.len != sizeof(":status") - 1 + || ngx_strncmp(ctx->name.data, ":status", + sizeof(":status") - 1) + != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->status) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent duplicate :status header"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + status_line = &ctx->value; + + if (status_line->len != 3) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid :status \"%V\"", + status_line); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + status = ngx_atoi(status_line->data, 3); + + if (status == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid :status \"%V\"", + status_line); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (status < NGX_HTTP_OK && status != NGX_HTTP_EARLY_HINTS) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected :status \"%V\"", + status_line); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + u->headers_in.status_n = status; + + if (u->state && u->state->status == 0) { + u->state->status = status; + } + + ctx->status = 1; + + continue; + + } else if (!ctx->status) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent no :status header"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + h = ngx_list_push(&u->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key = ctx->name; + h->value = ctx->value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + continue; + } + + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh) { + rc = hh->handler(r, h, hh->offset); + + if (rc != NGX_OK) { + return rc; + } + } + + continue; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + + /* a whole header has been parsed successfully */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header done"); + + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + if (ctx->end_stream) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed stream"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + ctx->status = 0; + return NGX_HTTP_UPSTREAM_EARLY_HINTS; + } + + if (ctx->end_stream + && ctx->in == NULL + && ctx->out == NULL + && ctx->output_closed + && !ctx->output_blocked + && !ctx->goaway + && b->last == b->pos) + { + u->keepalive = 1; + } + + return NGX_OK; + } + + /* there was error while a header line parsing */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header"); + + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + /* rc == NGX_AGAIN */ + + if (ctx->rest == 0) { + ctx->state = ngx_http_proxy_v2_st_start; + continue; + } + + return NGX_AGAIN; + } +} + + +static ngx_int_t +ngx_http_proxy_v2_filter_init(void *data) +{ + ngx_http_request_t *r = data; + ngx_http_upstream_t *u; + ngx_http_proxy_v2_ctx_t *ctx; + + u = r->upstream; + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_v2_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED + || ctx->ctx.head) + { + ctx->length = 0; + + } else { + ctx->length = u->headers_in.content_length_n; + } + + if (ctx->end_stream) { + + if (ctx->length > 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed stream"); + return NGX_ERROR; + } + + u->length = 0; + u->pipe->length = 0; + ctx->done = 1; + + } else { + u->length = 1; + u->pipe->length = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_non_buffered_filter(void *data, ssize_t bytes) +{ + ngx_http_request_t *r = data; + + ngx_int_t rc; + ngx_buf_t *b, *buf; + ngx_chain_t *cl, **ll; + ngx_http_upstream_t *u; + ngx_http_proxy_v2_ctx_t *ctx; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy filter bytes:%z", bytes); + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_v2_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + u = r->upstream; + b = &u->buffer; + + b->pos = b->last; + b->last += bytes; + + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { + ll = &cl->next; + } + + for ( ;; ) { + + rc = ngx_http_proxy_v2_process_frames(r, ctx, b); + + if (rc == NGX_OK) { + + cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + buf = cl->buf; + + buf->flush = 1; + buf->memory = 1; + + buf->pos = b->pos; + buf->tag = u->output.tag; + + if (b->last - b->pos >= (ssize_t) ctx->rest - ctx->padding) { + b->pos += ctx->rest - ctx->padding; + buf->last = b->pos; + ctx->rest = ctx->padding; + + } else { + ctx->rest -= b->last - b->pos; + b->pos = b->last; + buf->last = b->pos; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy output buf %p", buf->pos); + + if (ctx->length != -1) { + + if (buf->last - buf->pos > ctx->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent response body larger " + "than indicated content length"); + return NGX_ERROR; + } + + ctx->length -= buf->last - buf->pos; + } + + continue; + } + + if (rc == NGX_DONE) { + u->length = 0; + break; + } + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + /* invalid response */ + + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_body_filter(ngx_event_pipe_t *p, ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_buf_t *buf, **prev; + ngx_chain_t *cl; + ngx_http_request_t *r; + ngx_http_proxy_v2_ctx_t *ctx; + + if (b->pos == b->last) { + return NGX_OK; + } + + r = p->input_ctx; + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_v2_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + buf = NULL; + prev = &b->shadow; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy filter bytes:%z", b->last - b->pos); + + for ( ;; ) { + + rc = ngx_http_proxy_v2_process_frames(r, ctx, b); + + if (rc == NGX_OK) { + + /* copy data frame payload for buffering */ + + cl = ngx_chain_get_free_buf(p->pool, &p->free); + if (cl == NULL) { + return NGX_ERROR; + } + + buf = cl->buf; + + ngx_memzero(buf, sizeof(ngx_buf_t)); + + buf->pos = b->pos; + buf->start = b->start; + buf->end = b->end; + buf->tag = p->tag; + buf->temporary = 1; + buf->recycled = 1; + + *prev = buf; + prev = &buf->shadow; + + if (p->in) { + *p->last_in = cl; + + } else { + p->in = cl; + } + + p->last_in = &cl->next; + + /* STUB */ buf->num = b->num; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy copy buf %p", buf->pos); + + if (b->last - b->pos >= (ssize_t) ctx->rest - ctx->padding) { + b->pos += ctx->rest - ctx->padding; + buf->last = b->pos; + ctx->rest = ctx->padding; + + } else { + ctx->rest -= b->last - b->pos; + b->pos = b->last; + buf->last = b->pos; + } + + if (ctx->length != -1) { + + if (buf->last - buf->pos > ctx->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent response body larger " + "than indicated content length"); + return NGX_ERROR; + } + + ctx->length -= buf->last - buf->pos; + } + + continue; + } + + if (rc == NGX_DONE) { + p->length = 0; + break; + } + + if (rc == NGX_AGAIN) { + break; + } + + /* invalid response */ + + return NGX_ERROR; + } + + if (buf) { + buf->shadow = b; + buf->last_shadow = 1; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0, + "input buf %p %z", buf->pos, buf->last - buf->pos); + + return NGX_OK; + } + + /* there is no data record in the buf, add it to free chain */ + + if (ngx_event_pipe_add_free_buf(p, b) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_process_control_frame(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_http_upstream_t *u; + + u = r->upstream; + + if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) { + + rc = ngx_http_proxy_v2_parse_goaway(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + /* + * If stream_id is lower than one we use, our + * request won't be processed and needs to be retried. + * If stream_id is greater or equal to the one we use, + * we can continue normally (except we can't use this + * connection for additional requests). If there is + * a real error, the connection will be closed. + */ + + if (ctx->stream_id < ctx->id) { + + /* TODO: we can retry non-idempotent requests */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent goaway with error %ui", + ctx->error); + + return NGX_ERROR; + } + + ctx->goaway = 1; + + return NGX_OK; + } + + if (ctx->type == NGX_HTTP_V2_WINDOW_UPDATE_FRAME) { + + rc = ngx_http_proxy_v2_parse_window_update(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (ctx->in) { + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + } + + return NGX_OK; + } + + if (ctx->type == NGX_HTTP_V2_SETTINGS_FRAME) { + + rc = ngx_http_proxy_v2_parse_settings(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (ctx->in) { + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + } + + return NGX_OK; + } + + if (ctx->type == NGX_HTTP_V2_PING_FRAME) { + + rc = ngx_http_proxy_v2_parse_ping(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + + return NGX_OK; + } + + if (ctx->type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected push promise frame"); + return NGX_ERROR; + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_proxy_v2_skip_frame(ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + if (b->last - b->pos < (ssize_t) ctx->rest) { + ctx->rest -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += ctx->rest; + ctx->rest = 0; + ctx->state = ngx_http_proxy_v2_st_start; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_process_frames(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_http_upstream_t *u; + + u = r->upstream; + + for ( ;; ) { + + if (ctx->state < ngx_http_proxy_v2_st_payload) { + + rc = ngx_http_proxy_v2_parse_frame(r, ctx, b); + + if (rc == NGX_AGAIN) { + + if (ctx->done) { + + if (ctx->length > 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed stream"); + return NGX_ERROR; + } + + /* + * We have finished parsing the response and the + * remaining control frames. If there are unsent + * control frames, post a write event to send them. + */ + + if (ctx->out) { + ngx_post_event(u->peer.connection->write, + &ngx_posted_events); + return NGX_AGAIN; + } + + if (ctx->in == NULL + && ctx->output_closed + && !ctx->output_blocked + && !ctx->goaway + && ctx->state == ngx_http_proxy_v2_st_start) + { + u->keepalive = 1; + } + + return NGX_DONE; + } + + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if ((ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME + && !ctx->parsing_headers) + || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME + && ctx->parsing_headers)) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected http2 frame: %d", + ctx->type); + return NGX_ERROR; + } + + if (ctx->type == NGX_HTTP_V2_DATA_FRAME) { + + if (ctx->stream_id != ctx->id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent data frame " + "for unknown stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->rest > ctx->recv_window) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream violated stream flow control, " + "received %uz data frame with window %uz", + ctx->rest, ctx->recv_window); + return NGX_ERROR; + } + + if (ctx->rest > ctx->connection->recv_window) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream violated connection flow control, " + "received %uz data frame with window %uz", + ctx->rest, ctx->connection->recv_window); + return NGX_ERROR; + } + + ctx->recv_window -= ctx->rest; + ctx->connection->recv_window -= ctx->rest; + + if (ctx->connection->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4 + || ctx->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) + { + if (ngx_http_proxy_v2_send_window_update(r, ctx) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_post_event(u->peer.connection->write, + &ngx_posted_events); + } + } + + if (ctx->stream_id && ctx->stream_id != ctx->id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for unknown stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->stream_id && ctx->done + && ctx->type != NGX_HTTP_V2_RST_STREAM_FRAME + && ctx->type != NGX_HTTP_V2_WINDOW_UPDATE_FRAME) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for closed stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + + ctx->padding = 0; + } + + if (ctx->state == ngx_http_proxy_v2_st_padding) { + + if (b->last - b->pos < (ssize_t) ctx->rest) { + ctx->rest -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += ctx->rest; + ctx->rest = 0; + ctx->state = ngx_http_proxy_v2_st_start; + + if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { + ctx->done = 1; + } + + continue; + } + + /* frame payload */ + + if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) { + + rc = ngx_http_proxy_v2_parse_rst_stream(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (ctx->error || !ctx->done) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream rejected request with error %ui", + ctx->error); + return NGX_ERROR; + } + + if (ctx->rst) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for closed stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + + ctx->rst = 1; + + continue; + } + + rc = ngx_http_proxy_v2_process_control_frame(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + continue; + } + + if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME + || ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME) + { + for ( ;; ) { + + rc = ngx_http_proxy_v2_parse_header(r, ctx, b); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_OK) { + + /* a header line has been parsed successfully */ + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy trailer: \"%V: %V\"", + &ctx->name, &ctx->value); + + if (ctx->name.len && ctx->name.data[0] == ':') { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid " + "trailer \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_ERROR; + } + + h = ngx_list_push(&u->headers_in.trailers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key = ctx->name; + h->value = ctx->value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + continue; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + + /* a whole header has been parsed successfully */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy trailer done"); + + if (ctx->end_stream) { + ctx->done = 1; + break; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent trailer without " + "end stream flag"); + return NGX_ERROR; + } + + /* there was error while a header line parsing */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid trailer"); + + return NGX_ERROR; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + continue; + } + + /* rc == NGX_AGAIN */ + + if (ctx->rest == 0) { + ctx->state = ngx_http_proxy_v2_st_start; + continue; + } + + return NGX_AGAIN; + } + + if (ctx->type != NGX_HTTP_V2_DATA_FRAME) { + + /* priority, unknown frames */ + + rc = ngx_http_proxy_v2_skip_frame(ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + continue; + } + + /* + * data frame: + * + * +---------------+ + * |Pad Length? (8)| + * +---------------+-----------------------------------------------+ + * | Data (*) ... + * +---------------------------------------------------------------+ + * | Padding (*) ... + * +---------------------------------------------------------------+ + */ + + if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) { + + if (ctx->rest == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too short http2 frame"); + return NGX_ERROR; + } + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ctx->flags &= ~NGX_HTTP_V2_PADDED_FLAG; + ctx->padding = *b->pos++; + ctx->rest -= 1; + + if (ctx->padding > ctx->rest) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent http2 frame with too long " + "padding: %d in frame %uz", + ctx->padding, ctx->rest); + return NGX_ERROR; + } + + continue; + } + + if (ctx->padding == ctx->rest) { + + if (ctx->padding) { + ctx->state = ngx_http_proxy_v2_st_padding; + + } else { + ctx->state = ngx_http_proxy_v2_st_start; + + if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { + ctx->done = 1; + } + } + + continue; + } + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + return NGX_OK; + } +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_frame(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p; + ngx_http_proxy_v2_state_e state; + + state = ctx->state; + + for (p = b->pos; p < b->last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy frame byte: %02Xd, s:%d", ch, state); +#endif + + switch (state) { + + case ngx_http_proxy_v2_st_start: + ctx->rest = ch << 16; + state = ngx_http_proxy_v2_st_length_2; + break; + + case ngx_http_proxy_v2_st_length_2: + ctx->rest |= ch << 8; + state = ngx_http_proxy_v2_st_length_3; + break; + + case ngx_http_proxy_v2_st_length_3: + ctx->rest |= ch; + + if (ctx->rest > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 frame: %uz", + ctx->rest); + return NGX_ERROR; + } + + state = ngx_http_proxy_v2_st_type; + break; + + case ngx_http_proxy_v2_st_type: + ctx->type = ch; + state = ngx_http_proxy_v2_st_flags; + break; + + case ngx_http_proxy_v2_st_flags: + ctx->flags = ch; + state = ngx_http_proxy_v2_st_stream_id; + break; + + case ngx_http_proxy_v2_st_stream_id: + ctx->stream_id = (ch & 0x7f) << 24; + state = ngx_http_proxy_v2_st_stream_id_2; + break; + + case ngx_http_proxy_v2_st_stream_id_2: + ctx->stream_id |= ch << 16; + state = ngx_http_proxy_v2_st_stream_id_3; + break; + + case ngx_http_proxy_v2_st_stream_id_3: + ctx->stream_id |= ch << 8; + state = ngx_http_proxy_v2_st_stream_id_4; + break; + + case ngx_http_proxy_v2_st_stream_id_4: + ctx->stream_id |= ch; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy frame: %d, len: %uz, f:%d, i:%ui", + ctx->type, ctx->rest, ctx->flags, ctx->stream_id); + + b->pos = p + 1; + + ctx->state = ngx_http_proxy_v2_st_payload; + ctx->frame_state = 0; + + return NGX_OK; + + /* suppress warning */ + case ngx_http_proxy_v2_st_payload: + case ngx_http_proxy_v2_st_padding: + break; + } + } + + b->pos = p; + ctx->state = state; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_header(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + size_t min; + ngx_int_t rc; + enum { + sw_start = 0, + sw_padding_length, + sw_dependency, + sw_dependency_2, + sw_dependency_3, + sw_dependency_4, + sw_weight, + sw_fragment, + sw_padding + } state; + + state = ctx->frame_state; + + if (state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy parse header: start"); + + if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME) { + ctx->parsing_headers = 1; + ctx->fragment_state = 0; + + min = (ctx->flags & NGX_HTTP_V2_PADDED_FLAG ? 1 : 0) + + (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG ? 5 : 0); + + if (ctx->rest < min) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent headers frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + + if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { + ctx->end_stream = 1; + } + + if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) { + state = sw_padding_length; + + } else if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) { + state = sw_dependency; + + } else { + state = sw_fragment; + } + + } else if (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME) { + state = sw_fragment; + } + + ctx->padding = 0; + ctx->frame_state = state; + } + + if (state < sw_fragment) { + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header byte: %02Xd s:%d", ch, state); +#endif + + /* + * headers frame: + * + * +---------------+ + * |Pad Length? (8)| + * +-+-------------+----------------------------------------------+ + * |E| Stream Dependency? (31) | + * +-+-------------+----------------------------------------------+ + * | Weight? (8) | + * +-+-------------+----------------------------------------------+ + * | Header Block Fragment (*) ... + * +--------------------------------------------------------------+ + * | Padding (*) ... + * +--------------------------------------------------------------+ + */ + + switch (state) { + + case sw_padding_length: + + ctx->padding = ch; + + if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) { + state = sw_dependency; + break; + } + + goto fragment; + + case sw_dependency: + state = sw_dependency_2; + break; + + case sw_dependency_2: + state = sw_dependency_3; + break; + + case sw_dependency_3: + state = sw_dependency_4; + break; + + case sw_dependency_4: + state = sw_weight; + break; + + case sw_weight: + goto fragment; + + /* suppress warning */ + case sw_start: + case sw_fragment: + case sw_padding: + break; + } + } + + ctx->rest -= p - b->pos; + b->pos = p; + + ctx->frame_state = state; + return NGX_AGAIN; + + fragment: + + p++; + ctx->rest -= p - b->pos; + b->pos = p; + + if (ctx->padding > ctx->rest) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent http2 frame with too long " + "padding: %d in frame %uz", + ctx->padding, ctx->rest); + return NGX_ERROR; + } + + state = sw_fragment; + ctx->frame_state = state; + } + + if (state == sw_fragment) { + + rc = ngx_http_proxy_v2_parse_fragment(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + return NGX_OK; + } + + /* rc == NGX_DONE */ + + state = sw_padding; + ctx->frame_state = state; + } + + if (state == sw_padding) { + + if (b->last - b->pos < (ssize_t) ctx->rest) { + + ctx->rest -= b->last - b->pos; + b->pos = b->last; + + return NGX_AGAIN; + } + + b->pos += ctx->rest; + ctx->rest = 0; + + ctx->state = ngx_http_proxy_v2_st_start; + + if (ctx->flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + + if (ctx->fragment_state) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent truncated http2 header"); + return NGX_ERROR; + } + + ctx->parsing_headers = 0; + + return NGX_HTTP_PARSE_HEADER_DONE; + } + + return NGX_AGAIN; + } + + /* unreachable */ + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + size_t size; + ngx_uint_t index, size_update; + enum { + sw_start = 0, + sw_index, + sw_name_length, + sw_name_length_2, + sw_name_length_3, + sw_name_length_4, + sw_name, + sw_name_bytes, + sw_value_length, + sw_value_length_2, + sw_value_length_3, + sw_value_length_4, + sw_value, + sw_value_bytes + } state; + + /* header block fragment */ + +#if 0 + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header fragment %p:%p rest:%uz", + b->pos, b->last, ctx->rest); +#endif + + if (b->last - b->pos < (ssize_t) ctx->rest - ctx->padding) { + last = b->last; + + } else { + last = b->pos + ctx->rest - ctx->padding; + } + + state = ctx->fragment_state; + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->index = 0; + + if ((ch & 0x80) == 0x80) { + /* + * indexed header: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 1 | Index (7+) | + * +---+---------------------------+ + */ + + index = ch & ~0x80; + + if (index == 0 || index > 61) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "table index: %ui", index); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy indexed header: %ui", index); + + ctx->index = index; + ctx->literal = 0; + + goto done; + + } else if ((ch & 0xc0) == 0x40) { + /* + * literal header with incremental indexing: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 1 | Index (6+) | + * +---+---+-----------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 1 | 0 | + * +---+---+-----------------------+ + * | H | Name Length (7+) | + * +---+---------------------------+ + * | Name String (Length octets) | + * +---+---------------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + */ + + index = ch & ~0xc0; + + if (index > 61) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "table index: %ui", index); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy literal header: %ui", index); + + if (index == 0) { + state = sw_name_length; + break; + } + + ctx->index = index; + ctx->literal = 1; + + state = sw_value_length; + break; + + } else if ((ch & 0xe0) == 0x20) { + /* + * dynamic table size update: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 1 | Max size (5+) | + * +---+---------------------------+ + */ + + size_update = ch & ~0xe0; + + if (size_update > 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "dynamic table size update: %ui", + size_update); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy table size update: %ui", + size_update); + + break; + + } else if ((ch & 0xf0) == 0x10) { + /* + * literal header field never indexed: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 1 | Index (4+) | + * +---+---+-----------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 1 | 0 | + * +---+---+-----------------------+ + * | H | Name Length (7+) | + * +---+---------------------------+ + * | Name String (Length octets) | + * +---+---------------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + */ + + index = ch & ~0xf0; + + if (index == 0x0f) { + ctx->index = index; + ctx->literal = 1; + state = sw_index; + break; + } + + if (index == 0) { + state = sw_name_length; + break; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy literal header never indexed: %ui", + index); + + ctx->index = index; + ctx->literal = 1; + + state = sw_value_length; + break; + + } else if ((ch & 0xf0) == 0x00) { + /* + * literal header field without indexing: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 0 | Index (4+) | + * +---+---+-----------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 0 | 0 | + * +---+---+-----------------------+ + * | H | Name Length (7+) | + * +---+---------------------------+ + * | Name String (Length octets) | + * +---+---------------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + */ + + index = ch & ~0xf0; + + if (index == 0x0f) { + ctx->index = index; + ctx->literal = 1; + state = sw_index; + break; + } + + if (index == 0) { + state = sw_name_length; + break; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy literal header without indexing: %ui", + index); + + ctx->index = index; + ctx->literal = 1; + + state = sw_value_length; + break; + } + + /* not reached */ + + return NGX_ERROR; + + case sw_index: + ctx->index = ctx->index + (ch & ~0x80); + + if (ch & 0x80) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent http2 table index " + "with continuation flag"); + return NGX_ERROR; + } + + if (ctx->index > 61) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "table index: %ui", ctx->index); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header index: %ui", ctx->index); + + state = sw_value_length; + break; + + case sw_name_length: + ctx->field_huffman = ch & 0x80 ? 1 : 0; + ctx->field_length = ch & ~0x80; + + if (ctx->field_length == 0x7f) { + state = sw_name_length_2; + break; + } + + if (ctx->field_length == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent zero http2 " + "header name length"); + return NGX_ERROR; + } + + state = sw_name; + break; + + case sw_name_length_2: + ctx->field_length += ch & ~0x80; + + if (ch & 0x80) { + state = sw_name_length_3; + break; + } + + state = sw_name; + break; + + case sw_name_length_3: + ctx->field_length += (ch & ~0x80) << 7; + + if (ch & 0x80) { + state = sw_name_length_4; + break; + } + + state = sw_name; + break; + + case sw_name_length_4: + ctx->field_length += (ch & ~0x80) << 14; + + if (ch & 0x80) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header name length"); + return NGX_ERROR; + } + + state = sw_name; + break; + + case sw_name: + ctx->name.len = ctx->field_huffman ? + ctx->field_length * 8 / 5 : ctx->field_length; + + ctx->name.data = ngx_pnalloc(r->pool, ctx->name.len + 1); + if (ctx->name.data == NULL) { + return NGX_ERROR; + } + + ctx->field_end = ctx->name.data; + ctx->field_rest = ctx->field_length; + ctx->field_state = 0; + + state = sw_name_bytes; + + /* fall through */ + + case sw_name_bytes: + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy name: len:%uz h:%d last:%uz, rest:%uz", + ctx->field_length, + ctx->field_huffman, + last - p, + ctx->rest - (p - b->pos)); + + size = ngx_min(last - p, (ssize_t) ctx->field_rest); + ctx->field_rest -= size; + + if (ctx->field_huffman) { + if (ngx_http_huff_decode(&ctx->field_state, p, size, + &ctx->field_end, + ctx->field_rest == 0, + r->connection->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid encoded header"); + return NGX_ERROR; + } + + ctx->name.len = ctx->field_end - ctx->name.data; + ctx->name.data[ctx->name.len] = '\0'; + + } else { + ctx->field_end = ngx_cpymem(ctx->field_end, p, size); + ctx->name.data[ctx->name.len] = '\0'; + } + + p += size - 1; + + if (ctx->field_rest == 0) { + state = sw_value_length; + } + + break; + + case sw_value_length: + ctx->field_huffman = ch & 0x80 ? 1 : 0; + ctx->field_length = ch & ~0x80; + + if (ctx->field_length == 0x7f) { + state = sw_value_length_2; + break; + } + + if (ctx->field_length == 0) { + ngx_str_set(&ctx->value, ""); + goto done; + } + + state = sw_value; + break; + + case sw_value_length_2: + ctx->field_length += ch & ~0x80; + + if (ch & 0x80) { + state = sw_value_length_3; + break; + } + + state = sw_value; + break; + + case sw_value_length_3: + ctx->field_length += (ch & ~0x80) << 7; + + if (ch & 0x80) { + state = sw_value_length_4; + break; + } + + state = sw_value; + break; + + case sw_value_length_4: + ctx->field_length += (ch & ~0x80) << 14; + + if (ch & 0x80) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header value length"); + return NGX_ERROR; + } + + state = sw_value; + break; + + case sw_value: + ctx->value.len = ctx->field_huffman ? + ctx->field_length * 8 / 5 : ctx->field_length; + + ctx->value.data = ngx_pnalloc(r->pool, ctx->value.len + 1); + if (ctx->value.data == NULL) { + return NGX_ERROR; + } + + ctx->field_end = ctx->value.data; + ctx->field_rest = ctx->field_length; + ctx->field_state = 0; + + state = sw_value_bytes; + + /* fall through */ + + case sw_value_bytes: + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy value: len:%uz h:%d last:%uz, rest:%uz", + ctx->field_length, + ctx->field_huffman, + last - p, + ctx->rest - (p - b->pos)); + + size = ngx_min(last - p, (ssize_t) ctx->field_rest); + ctx->field_rest -= size; + + if (ctx->field_huffman) { + if (ngx_http_huff_decode(&ctx->field_state, p, size, + &ctx->field_end, + ctx->field_rest == 0, + r->connection->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid encoded header"); + return NGX_ERROR; + } + + ctx->value.len = ctx->field_end - ctx->value.data; + ctx->value.data[ctx->value.len] = '\0'; + + } else { + ctx->field_end = ngx_cpymem(ctx->field_end, p, size); + ctx->value.data[ctx->value.len] = '\0'; + } + + p += size - 1; + + if (ctx->field_rest == 0) { + goto done; + } + + break; + } + + continue; + + done: + + p++; + ctx->rest -= p - b->pos; + ctx->fragment_state = sw_start; + b->pos = p; + + if (ctx->index) { + ctx->name = *ngx_http_v2_get_static_name(ctx->index); + } + + if (ctx->index && !ctx->literal) { + ctx->value = *ngx_http_v2_get_static_value(ctx->index); + } + + if (!ctx->index) { + if (ngx_http_proxy_v2_validate_header_name(r, &ctx->name) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header: \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_ERROR; + } + } + + if (!ctx->index || ctx->literal) { + if (ngx_http_proxy_v2_validate_header_value(r, &ctx->value) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header: \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_ERROR; + } + } + + return NGX_OK; + } + + ctx->rest -= p - b->pos; + ctx->fragment_state = state; + b->pos = p; + + if (ctx->rest > ctx->padding) { + return NGX_AGAIN; + } + + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_proxy_v2_validate_header_name(ngx_http_request_t *r, ngx_str_t *s) +{ + u_char ch; + ngx_uint_t i; + + for (i = 0; i < s->len; i++) { + ch = s->data[i]; + + if (ch == ':' && i > 0) { + return NGX_ERROR; + } + + if (ch >= 'A' && ch <= 'Z') { + return NGX_ERROR; + } + + if (ch <= 0x20 || ch == 0x7f) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_validate_header_value(ngx_http_request_t *r, ngx_str_t *s) +{ + u_char ch; + ngx_uint_t i; + + for (i = 0; i < s->len; i++) { + ch = s->data[i]; + + if (ch == '\0' || ch == CR || ch == LF) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_rst_stream(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_error_2, + sw_error_3, + sw_error_4 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + if (ctx->rest != 4) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent rst stream frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy rst byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->error = (ngx_uint_t) ch << 24; + state = sw_error_2; + break; + + case sw_error_2: + ctx->error |= ch << 16; + state = sw_error_3; + break; + + case sw_error_3: + ctx->error |= ch << 8; + state = sw_error_4; + break; + + case sw_error_4: + ctx->error |= ch; + state = sw_start; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy error: %ui", ctx->error); + + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ctx->state = ngx_http_proxy_v2_st_start; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_goaway(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_last_stream_id_2, + sw_last_stream_id_3, + sw_last_stream_id_4, + sw_error, + sw_error_2, + sw_error_3, + sw_error_4, + sw_debug + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + + if (ctx->stream_id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent goaway frame " + "with non-zero stream id: %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->rest < 8) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent goaway frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy goaway byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->stream_id = (ch & 0x7f) << 24; + state = sw_last_stream_id_2; + break; + + case sw_last_stream_id_2: + ctx->stream_id |= ch << 16; + state = sw_last_stream_id_3; + break; + + case sw_last_stream_id_3: + ctx->stream_id |= ch << 8; + state = sw_last_stream_id_4; + break; + + case sw_last_stream_id_4: + ctx->stream_id |= ch; + state = sw_error; + break; + + case sw_error: + ctx->error = (ngx_uint_t) ch << 24; + state = sw_error_2; + break; + + case sw_error_2: + ctx->error |= ch << 16; + state = sw_error_3; + break; + + case sw_error_3: + ctx->error |= ch << 8; + state = sw_error_4; + break; + + case sw_error_4: + ctx->error |= ch; + state = sw_debug; + break; + + case sw_debug: + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy goaway: %ui, stream %ui", + ctx->error, ctx->stream_id); + + ctx->state = ngx_http_proxy_v2_st_start; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_window_update(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_size_2, + sw_size_3, + sw_size_4 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + if (ctx->rest != 4) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent window update frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy window update byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->window_update = (ch & 0x7f) << 24; + state = sw_size_2; + break; + + case sw_size_2: + ctx->window_update |= ch << 16; + state = sw_size_3; + break; + + case sw_size_3: + ctx->window_update |= ch << 8; + state = sw_size_4; + break; + + case sw_size_4: + ctx->window_update |= ch; + state = sw_start; + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ctx->state = ngx_http_proxy_v2_st_start; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy window update: %ui", ctx->window_update); + + if (ctx->stream_id) { + + if (ctx->window_update > (size_t) NGX_HTTP_V2_MAX_WINDOW + - ctx->send_window) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large window update"); + return NGX_ERROR; + } + + ctx->send_window += ctx->window_update; + + } else { + + if (ctx->window_update > NGX_HTTP_V2_MAX_WINDOW + - ctx->connection->send_window) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large window update"); + return NGX_ERROR; + } + + ctx->connection->send_window += ctx->window_update; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_settings(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + ssize_t window_update; + enum { + sw_start = 0, + sw_id, + sw_id_2, + sw_value, + sw_value_2, + sw_value_3, + sw_value_4 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + + if (ctx->stream_id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with non-zero stream id: %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy settings ack"); + + if (ctx->rest != 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with ack flag and non-zero length: %uz", + ctx->rest); + return NGX_ERROR; + } + + ctx->state = ngx_http_proxy_v2_st_start; + + return NGX_OK; + } + + if (ctx->rest % 6 != 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + + if (ctx->free == NULL && ctx->settings++ > 1000) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too many settings frames"); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy settings byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + case sw_id: + ctx->setting_id = ch << 8; + state = sw_id_2; + break; + + case sw_id_2: + ctx->setting_id |= ch; + state = sw_value; + break; + + case sw_value: + ctx->setting_value = (ngx_uint_t) ch << 24; + state = sw_value_2; + break; + + case sw_value_2: + ctx->setting_value |= ch << 16; + state = sw_value_3; + break; + + case sw_value_3: + ctx->setting_value |= ch << 8; + state = sw_value_4; + break; + + case sw_value_4: + ctx->setting_value |= ch; + state = sw_id; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy setting: %ui %ui", + ctx->setting_id, ctx->setting_value); + + /* + * The following settings are defined by the protocol: + * + * SETTINGS_HEADER_TABLE_SIZE, SETTINGS_ENABLE_PUSH, + * SETTINGS_MAX_CONCURRENT_STREAMS, SETTINGS_INITIAL_WINDOW_SIZE, + * SETTINGS_MAX_FRAME_SIZE, SETTINGS_MAX_HEADER_LIST_SIZE + * + * Only SETTINGS_INITIAL_WINDOW_SIZE seems to be needed in + * a simple client. + */ + + if (ctx->setting_id == 0x04) { + /* SETTINGS_INITIAL_WINDOW_SIZE */ + + if (ctx->setting_value > NGX_HTTP_V2_MAX_WINDOW) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with too large initial window size: %ui", + ctx->setting_value); + return NGX_ERROR; + } + + window_update = ctx->setting_value + - ctx->connection->init_window; + ctx->connection->init_window = ctx->setting_value; + + if (ctx->send_window > 0 + && window_update > (ssize_t) NGX_HTTP_V2_MAX_WINDOW + - ctx->send_window) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with too large initial window size: %ui", + ctx->setting_value); + return NGX_ERROR; + } + + ctx->send_window += window_update; + } + + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ctx->state = ngx_http_proxy_v2_st_start; + + return ngx_http_proxy_v2_send_settings_ack(r, ctx); +} + + +static ngx_int_t +ngx_http_proxy_v2_parse_ping(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_data_2, + sw_data_3, + sw_data_4, + sw_data_5, + sw_data_6, + sw_data_7, + sw_data_8 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + + if (ctx->stream_id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent ping frame " + "with non-zero stream id: %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->rest != 8) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent ping frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + + if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent ping frame with ack flag"); + return NGX_ERROR; + } + + if (ctx->free == NULL && ctx->pings++ > 1000) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too many ping frames"); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy ping byte: %02Xd s:%d", ch, state); +#endif + + if (state < sw_data_8) { + ctx->ping_data[state] = ch; + state++; + + } else { + ctx->ping_data[7] = ch; + state = sw_start; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy ping"); + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ctx->state = ngx_http_proxy_v2_st_start; + + return ngx_http_proxy_v2_send_ping_ack(r, ctx); +} + + +static ngx_int_t +ngx_http_proxy_v2_send_settings_ack(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx) +{ + ngx_chain_t *cl, **ll; + ngx_http_proxy_v2_frame_t *f; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy send settings ack"); + + for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { + ll = &cl->next; + } + + cl = ngx_http_proxy_v2_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + f = (ngx_http_proxy_v2_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_proxy_v2_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 0; + f->type = NGX_HTTP_V2_SETTINGS_FRAME; + f->flags = NGX_HTTP_V2_ACK_FLAG; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 0; + + *ll = cl; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_send_ping_ack(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx) +{ + ngx_chain_t *cl, **ll; + ngx_http_proxy_v2_frame_t *f; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy send ping ack"); + + for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { + ll = &cl->next; + } + + cl = ngx_http_proxy_v2_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + f = (ngx_http_proxy_v2_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_proxy_v2_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 8; + f->type = NGX_HTTP_V2_PING_FRAME; + f->flags = NGX_HTTP_V2_ACK_FLAG; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 0; + + cl->buf->last = ngx_copy(cl->buf->last, ctx->ping_data, 8); + + *ll = cl; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_v2_send_window_update(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx) +{ + size_t n; + ngx_chain_t *cl, **ll; + ngx_http_proxy_v2_frame_t *f; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy send window update: %uz %uz", + ctx->connection->recv_window, ctx->recv_window); + + for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { + ll = &cl->next; + } + + cl = ngx_http_proxy_v2_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + f = (ngx_http_proxy_v2_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_proxy_v2_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 4; + f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME; + f->flags = 0; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 0; + + n = NGX_HTTP_V2_MAX_WINDOW - ctx->connection->recv_window; + ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + *cl->buf->last++ = (u_char) ((n >> 24) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 16) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 8) & 0xff); + *cl->buf->last++ = (u_char) (n & 0xff); + + f = (ngx_http_proxy_v2_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_proxy_v2_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 4; + f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME; + f->flags = 0; + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + n = NGX_HTTP_V2_MAX_WINDOW - ctx->recv_window; + ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + *cl->buf->last++ = (u_char) ((n >> 24) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 16) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 8) & 0xff); + *cl->buf->last++ = (u_char) (n & 0xff); + + *ll = cl; + + return NGX_OK; +} + + +static ngx_chain_t * +ngx_http_proxy_v2_get_buf(ngx_http_request_t *r, ngx_http_proxy_v2_ctx_t *ctx) +{ + u_char *start; + ngx_buf_t *b; + ngx_chain_t *cl; + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return NULL; + } + + b = cl->buf; + start = b->start; + + if (start == NULL) { + + /* + * each buffer is large enough to hold two window update + * frames in a row + */ + + start = ngx_palloc(r->pool, 2 * sizeof(ngx_http_proxy_v2_frame_t) + 8); + if (start == NULL) { + return NULL; + } + + } + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->start = start; + b->pos = start; + b->last = start; + b->end = start + 2 * sizeof(ngx_http_proxy_v2_frame_t) + 8; + + b->tag = (ngx_buf_tag_t) &ngx_http_proxy_v2_body_output_filter; + b->temporary = 1; + b->flush = 1; + + return cl; +} + + +static ngx_http_proxy_v2_ctx_t * +ngx_http_proxy_v2_get_ctx(ngx_http_request_t *r) +{ + ngx_http_upstream_t *u; + ngx_http_proxy_v2_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_v2_module); + + if (ctx->connection == NULL) { + u = r->upstream; + + if (ngx_http_proxy_v2_get_connection_data(r, ctx, &u->peer) != NGX_OK) { + return NULL; + } + } + + return ctx; +} + + +static ngx_int_t +ngx_http_proxy_v2_get_connection_data(ngx_http_request_t *r, + ngx_http_proxy_v2_ctx_t *ctx, ngx_peer_connection_t *pc) +{ + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + + c = pc->connection; + + if (c == NULL) { + ctx->connection = ngx_palloc(r->pool, sizeof(ngx_http_proxy_v2_conn_t)); + if (ctx->connection == NULL) { + return NGX_ERROR; + } + + goto done; + } + + if (pc->cached) { + + /* + * for cached connections, connection data can be found + * in the cleanup handler + */ + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_proxy_v2_cleanup) { + ctx->connection = cln->data; + break; + } + } + + if (ctx->connection == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no connection data found for " + "keepalive http2 connection"); + return NGX_ERROR; + } + + ctx->send_window = ctx->connection->init_window; + ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + ctx->connection->last_stream_id += 2; + ctx->id = ctx->connection->last_stream_id; + + return NGX_OK; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_proxy_v2_conn_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_proxy_v2_cleanup; + ctx->connection = cln->data; + +done: + + ctx->connection->init_window = NGX_HTTP_V2_DEFAULT_WINDOW; + ctx->connection->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; + ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + ctx->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; + ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + ctx->id = 1; + ctx->connection->last_stream_id = 1; + + return NGX_OK; +} + + +static void +ngx_http_proxy_v2_cleanup(void *data) +{ +#if 0 + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http proxy cleanup"); +#endif + return; +} + + +static void +ngx_http_proxy_v2_abort_request(ngx_http_request_t *r) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "abort proxy http2 request"); + return; +} + + +static void +ngx_http_proxy_v2_finalize_request(ngx_http_request_t *r, ngx_int_t rc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize proxy http2 request"); + return; +} diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index ccd7b71..6637a70 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -117,6 +117,13 @@ static ngx_command_t ngx_http_ssl_commands[] = { 0, NULL }, + { ngx_string("ssl_ech_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, ech_files), + NULL }, + { ngx_string("ssl_password_file"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_ssl_password_file, @@ -412,6 +419,13 @@ static ngx_http_variable_t ngx_http_ssl_vars[] = { { ngx_string("ssl_alpn_protocol"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_alpn_protocol, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_ech_status"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_get_ech_status, NGX_HTTP_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_ech_outer_server_name"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_get_ech_outer_server_name, + NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_cert"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 }, @@ -678,6 +692,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) sscf->certificates = NGX_CONF_UNSET_PTR; sscf->certificate_keys = NGX_CONF_UNSET_PTR; sscf->certificate_cache = NGX_CONF_UNSET_PTR; + sscf->ech_files = NGX_CONF_UNSET_PTR; sscf->passwords = NGX_CONF_UNSET_PTR; sscf->conf_commands = NGX_CONF_UNSET_PTR; sscf->builtin_session_cache = NGX_CONF_UNSET; @@ -734,6 +749,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->certificate_cache, prev->certificate_cache, NULL); + ngx_conf_merge_ptr_value(conf->ech_files, prev->ech_files, NULL); + ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL); ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); @@ -812,7 +829,9 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { static ngx_ssl_client_hello_arg cb = { ngx_http_ssl_servername }; - ngx_ssl_set_client_hello_callback(conf->ssl.ctx, &cb); + if (ngx_ssl_set_client_hello_callback(&conf->ssl, &cb) != NGX_OK) { + return NGX_CONF_ERROR; + } if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, ngx_http_ssl_servername) @@ -928,6 +947,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (ngx_ssl_ech_files(cf, &conf->ssl, conf->ech_files) != NGX_OK) { + return NGX_CONF_ERROR; + } + if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index e50bdc9..44bfa39 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -49,6 +49,7 @@ typedef struct { ngx_str_t ciphers; + ngx_array_t *ech_files; ngx_array_t *passwords; ngx_array_t *conf_commands; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index c89dc29..2f3f80c 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -130,8 +130,8 @@ 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); +ngx_int_t ngx_http_validate_host(ngx_str_t *host, in_port_t *port, + 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); diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index ae921f9..4b0a6e3 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -383,21 +383,18 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case sw_host_end: + if (ch == ':') { + state = sw_port; + break; + } + r->host_end = p; if (r->method == NGX_HTTP_CONNECT) { - if (ch == ':') { - state = sw_port; - break; - } - return NGX_HTTP_PARSE_INVALID_REQUEST; } switch (ch) { - case ':': - state = sw_port; - break; case '/': r->uri_start = p; state = sw_after_slash_in_uri; @@ -465,14 +462,11 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case sw_port: if (ch >= '0' && ch <= '9') { - if (r->port >= 6553 && (r->port > 6553 || (ch - '0') > 5)) { - return NGX_HTTP_PARSE_INVALID_REQUEST; - } - - r->port = r->port * 10 + (ch - '0'); break; } + r->host_end = p; + if (r->method == NGX_HTTP_CONNECT) { if (ch == ' ') { state = sw_http_09; @@ -2260,9 +2254,6 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, case CR: state = sw_last_chunk_extension_almost_done; break; - case LF: - state = sw_trailer; - break; case ';': case ' ': case '\t': @@ -2279,9 +2270,6 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, case CR: state = sw_chunk_extension_almost_done; break; - case LF: - state = sw_chunk_data; - break; case ';': case ' ': case '\t': @@ -2299,7 +2287,7 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, state = sw_chunk_extension_almost_done; break; case LF: - state = sw_chunk_data; + goto invalid; } break; @@ -2319,9 +2307,6 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, case CR: state = sw_after_data_almost_done; break; - case LF: - state = sw_chunk_start; - break; default: goto invalid; } @@ -2340,7 +2325,7 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, state = sw_last_chunk_extension_almost_done; break; case LF: - state = sw_trailer; + goto invalid; } break; @@ -2357,7 +2342,7 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, state = sw_trailer_almost_done; break; case LF: - goto done; + goto invalid; default: state = sw_trailer_header; } @@ -2375,7 +2360,7 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, state = sw_trailer_header_almost_done; break; case LF: - state = sw_trailer; + goto invalid; } break; @@ -2401,35 +2386,45 @@ data: switch (state) { case sw_chunk_start: - ctx->length = 3 /* "0" LF LF */; + ctx->length = 5 /* "0" CRLF CRLF */; break; case sw_chunk_size: - ctx->length = 1 /* LF */ - + (ctx->size ? ctx->size + 4 /* LF "0" LF LF */ - : 1 /* LF */); + ctx->length = 2 /* CRLF */ + + (ctx->size ? ctx->size + 7 /* CRLF "0" CRLF CRLF */ + : 2 /* CRLF */); break; case sw_chunk_extension: + ctx->length = 2 /* CRLF */ + ctx->size + 7 /* CRLF "0" CRLF CRLF */; + break; case sw_chunk_extension_almost_done: - ctx->length = 1 /* LF */ + ctx->size + 4 /* LF "0" LF LF */; + ctx->length = 1 /* LF */ + ctx->size + 7 /* CRLF "0" CRLF CRLF */; break; case sw_chunk_data: - ctx->length = ctx->size + 4 /* LF "0" LF LF */; + ctx->length = ctx->size + 7 /* CRLF "0" CRLF CRLF */; break; case sw_after_data: + ctx->length = 7 /* CRLF "0" CRLF CRLF */; + break; case sw_after_data_almost_done: - ctx->length = 4 /* LF "0" LF LF */; + ctx->length = 6 /* LF "0" CRLF CRLF */; break; case sw_last_chunk_extension: + ctx->length = 4 /* CRLF CRLF */; + break; case sw_last_chunk_extension_almost_done: - ctx->length = 2 /* LF LF */; + ctx->length = 3 /* LF CRLF */; break; case sw_trailer: + ctx->length = 2 /* CRLF */; + break; case sw_trailer_almost_done: ctx->length = 1 /* LF */; break; case sw_trailer_header: + ctx->length = 4 /* CRLF CRLF */; + break; case sw_trailer_header_almost_done: - ctx->length = 2 /* LF LF */; + ctx->length = 3 /* LF CRLF */; break; } diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 533af45..41c1cda 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -931,7 +931,7 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } - rc = ngx_http_validate_host(&host, c->pool, 1); + rc = ngx_http_validate_host(&host, NULL, c->pool, 1); if (rc == NGX_ERROR) { goto error; @@ -1107,6 +1107,7 @@ ngx_http_process_request_line(ngx_event_t *rev) ssize_t n; ngx_int_t rc, rv; ngx_str_t host; + in_port_t port; ngx_connection_t *c; ngx_http_request_t *r; @@ -1169,7 +1170,7 @@ ngx_http_process_request_line(ngx_event_t *rev) host.len = r->host_end - r->host_start; host.data = r->host_start; - rc = ngx_http_validate_host(&host, r->pool, 0); + rc = ngx_http_validate_host(&host, &port, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1188,6 +1189,7 @@ ngx_http_process_request_line(ngx_event_t *rev) } r->headers_in.server = host; + r->port = port; } if (r->http_version < NGX_HTTP_VERSION_10) { @@ -1846,9 +1848,9 @@ static ngx_int_t ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - u_char *p; - ngx_int_t rc; - ngx_str_t host; + ngx_int_t rc; + ngx_str_t host; + in_port_t port; if (r->headers_in.host) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, @@ -1865,7 +1867,7 @@ ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, host = h->value; - rc = ngx_http_validate_host(&host, r->pool, 0); + rc = ngx_http_validate_host(&host, &port, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, @@ -1888,17 +1890,7 @@ ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, } r->headers_in.server = host; - - p = ngx_strlchr(h->value.data + host.len, - h->value.data + h->value.len, ':'); - - if (p) { - rc = ngx_atoi(p + 1, h->value.data + h->value.len - p - 1); - - if (rc > 0 && rc < 65536) { - r->port = rc; - } - } + r->port = port; return NGX_OK; } @@ -2182,74 +2174,176 @@ ngx_http_process_request(ngx_http_request_t *r) ngx_int_t -ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) +ngx_http_validate_host(ngx_str_t *host, in_port_t *portp, ngx_pool_t *pool, + ngx_uint_t alloc) { - u_char *h, ch; - size_t i, dot_pos, host_len; + u_char *h, ch; + size_t i, dot_pos, host_len; + ngx_int_t port; enum { - sw_usual = 0, - sw_literal, - sw_rest + sw_host_start = 0, + sw_host, + sw_host_ip_literal, + sw_host_end, + sw_port, } state; dot_pos = host->len; host_len = host->len; + port = 0; h = host->data; - state = sw_usual; + state = sw_host_start; for (i = 0; i < host->len; i++) { ch = h[i]; - switch (ch) { + switch (state) { - case '.': - if (dot_pos == i - 1) { - return NGX_DECLINED; - } - dot_pos = i; - break; + case sw_host_start: - case ':': - if (state == sw_usual) { - host_len = i; - state = sw_rest; - } - break; - - case '[': - if (i == 0) { - state = sw_literal; - } - break; - - case ']': - if (state == sw_literal) { - host_len = i + 1; - state = sw_rest; - } - break; - - default: - - if (ngx_path_separator(ch)) { - return NGX_DECLINED; + if (ch == '[') { + state = sw_host_ip_literal; + break; } - if (ch <= 0x20 || ch == 0x7f) { - return NGX_DECLINED; - } + state = sw_host; + + /* fall through */ + + case sw_host: if (ch >= 'A' && ch <= 'Z') { alloc = 1; + break; } + if (ch >= 'a' && ch <= 'z') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; + } + + switch (ch) { + case ':': + host_len = i; + state = sw_port; + break; + case '-': + break; + case '.': + if (dot_pos == i - 1) { + return NGX_DECLINED; + } + dot_pos = i; + break; + case '_': + case '~': + /* unreserved */ + break; + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + /* sub-delims */ + break; + case '%': + /* pct-encoded */ + break; + default: + return NGX_DECLINED; + } break; + + case sw_host_ip_literal: + + if (ch >= 'A' && ch <= 'Z') { + alloc = 1; + break; + } + + if (ch >= 'a' && ch <= 'z') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; + } + + switch (ch) { + case ':': + break; + case ']': + host_len = i + 1; + state = sw_host_end; + break; + case '-': + break; + case '.': + if (dot_pos == i - 1) { + return NGX_DECLINED; + } + dot_pos = i; + break; + case '_': + case '~': + /* unreserved */ + break; + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + /* sub-delims */ + break; + default: + return NGX_DECLINED; + } + break; + + case sw_host_end: + + if (ch == ':') { + state = sw_port; + break; + } + return NGX_DECLINED; + + case sw_port: + + if (ch >= '0' && ch <= '9') { + if (port >= 6553 && (port > 6553 || (ch - '0') > 5)) { + return NGX_DECLINED; + } + + port = port * 10 + (ch - '0'); + break; + } + return NGX_DECLINED; } } + if (state == sw_host_ip_literal) { + return NGX_DECLINED; + } + if (dot_pos == host_len - 1) { host_len--; } @@ -2269,6 +2363,10 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) host->len = host_len; + if (portp) { + *portp = port; + } + return NGX_OK; } diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 6616354..2e4b2b4 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -1771,6 +1771,23 @@ ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, } } +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + + if (u->ssl_alpn_protocol.len) { + if (SSL_set_alpn_protos(c->ssl->connection, u->ssl_alpn_protocol.data, + u->ssl_alpn_protocol.len) + != 0) + { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_set_alpn_protos() failed"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + +#endif + if (u->conf->ssl_session_reuse) { c->ssl->save_session = ngx_http_upstream_ssl_save_session; diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 64d70cf..3afe6e8 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -390,6 +390,7 @@ struct ngx_http_upstream_s { #if (NGX_HTTP_SSL || NGX_COMPAT) ngx_str_t ssl_name; + ngx_str_t ssl_alpn_protocol; #endif ngx_http_cleanup_pt *cleanup; diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 67f24bf..e1fd173 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3527,8 +3527,8 @@ ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) { - u_char *p; - ngx_int_t rc; + ngx_int_t rc; + in_port_t port; if (r->host_start) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, @@ -3539,7 +3539,7 @@ ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) r->host_start = value->data; r->host_end = value->data + value->len; - rc = ngx_http_validate_host(value, r->pool, 0); + rc = ngx_http_validate_host(value, &port, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, @@ -3561,16 +3561,7 @@ ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) } r->headers_in.server = *value; - - p = ngx_strlchr(r->host_start + value->len, r->host_end, ':'); - - if (p) { - rc = ngx_atoi(p + 1, r->host_end - p - 1); - - if (rc > 0 && rc < 65536) { - r->port = rc; - } - } + r->port = port; return NGX_OK; } @@ -4122,15 +4113,14 @@ ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, n = size; } - if (n > 0) { - rb->buf->last = ngx_cpymem(rb->buf->last, pos, n); - } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 request body recv %uz", n); - pos += n; - size -= n; + if (n > 0) { + rb->buf->last = ngx_cpymem(rb->buf->last, pos, n); + pos += n; + size -= n; + } if (size == 0 && last) { rb->rest = 0; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 77c55bf..6865e14 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -904,6 +904,7 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) u_char *p; ngx_int_t rc; ngx_str_t host; + in_port_t port; if (r->request_line.len) { return NGX_OK; @@ -961,7 +962,7 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) host.len = r->host_end - r->host_start; host.data = r->host_start; - rc = ngx_http_validate_host(&host, r->pool, 0); + rc = ngx_http_validate_host(&host, &port, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, @@ -979,16 +980,7 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) } r->headers_in.server = host; - - p = ngx_strlchr(r->host_start + host.len, r->host_end, ':'); - - if (p) { - rc = ngx_atoi(p + 1, r->host_end - p - 1); - - if (rc > 0 && rc < 65536) { - r->port = rc; - } - } + r->port = port; } if (ngx_list_init(&r->headers_in.headers, r->pool, 20, diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index 40951c2..a09c7c6 100644 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -474,72 +474,173 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s, ngx_int_t ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) { - u_char *h, ch; - size_t i, dot_pos, host_len; + u_char *h, ch; + size_t i, dot_pos, host_len; + ngx_int_t port; enum { - sw_usual = 0, - sw_literal, - sw_rest + sw_host_start = 0, + sw_host, + sw_host_ip_literal, + sw_host_end, + sw_port, } state; dot_pos = host->len; host_len = host->len; + port = 0; h = host->data; - state = sw_usual; + state = sw_host_start; for (i = 0; i < host->len; i++) { ch = h[i]; - switch (ch) { + switch (state) { - case '.': - if (dot_pos == i - 1) { - return NGX_DECLINED; - } - dot_pos = i; - break; + case sw_host_start: - case ':': - if (state == sw_usual) { - host_len = i; - state = sw_rest; - } - break; - - case '[': - if (i == 0) { - state = sw_literal; - } - break; - - case ']': - if (state == sw_literal) { - host_len = i + 1; - state = sw_rest; - } - break; - - default: - - if (ngx_path_separator(ch)) { - return NGX_DECLINED; + if (ch == '[') { + state = sw_host_ip_literal; + break; } - if (ch <= 0x20 || ch == 0x7f) { - return NGX_DECLINED; - } + state = sw_host; + + /* fall through */ + + case sw_host: if (ch >= 'A' && ch <= 'Z') { alloc = 1; + break; } + if (ch >= 'a' && ch <= 'z') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; + } + + switch (ch) { + case ':': + host_len = i; + state = sw_port; + break; + case '-': + break; + case '.': + if (dot_pos == i - 1) { + return NGX_DECLINED; + } + dot_pos = i; + break; + case '_': + case '~': + /* unreserved */ + break; + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + /* sub-delims */ + break; + case '%': + /* pct-encoded */ + break; + default: + return NGX_DECLINED; + } break; + + case sw_host_ip_literal: + + if (ch >= 'A' && ch <= 'Z') { + alloc = 1; + break; + } + + if (ch >= 'a' && ch <= 'z') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; + } + + switch (ch) { + case ':': + break; + case ']': + host_len = i + 1; + state = sw_host_end; + break; + case '-': + break; + case '.': + if (dot_pos == i - 1) { + return NGX_DECLINED; + } + dot_pos = i; + break; + case '_': + case '~': + /* unreserved */ + break; + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + /* sub-delims */ + break; + default: + return NGX_DECLINED; + } + break; + + case sw_host_end: + + if (ch == ':') { + state = sw_port; + break; + } + return NGX_DECLINED; + + case sw_port: + + if (ch >= '0' && ch <= '9') { + if (port >= 6553 && (port > 6553 || (ch - '0') > 5)) { + return NGX_DECLINED; + } + + port = port * 10 + (ch - '0'); + break; + } + return NGX_DECLINED; } } + if (state == sw_host_ip_literal) { + return NGX_DECLINED; + } + if (dot_pos == host_len - 1) { host_len--; } diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 90c0d04..555a6ad 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -126,6 +126,13 @@ static ngx_command_t ngx_stream_ssl_commands[] = { 0, NULL }, + { ngx_string("ssl_ech_file"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, ech_files), + NULL }, + { ngx_string("ssl_password_file"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_stream_ssl_password_file, @@ -372,6 +379,13 @@ static ngx_stream_variable_t ngx_stream_ssl_vars[] = { { ngx_string("ssl_alpn_protocol"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_alpn_protocol, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_ech_status"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_ech_status, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_ech_outer_server_name"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_ech_outer_server_name, + NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_cert"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_certificate, NGX_STREAM_VAR_CHANGEABLE, 0 }, @@ -888,6 +902,7 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf) sscf->certificates = NGX_CONF_UNSET_PTR; sscf->certificate_keys = NGX_CONF_UNSET_PTR; sscf->certificate_cache = NGX_CONF_UNSET_PTR; + sscf->ech_files = NGX_CONF_UNSET_PTR; sscf->passwords = NGX_CONF_UNSET_PTR; sscf->conf_commands = NGX_CONF_UNSET_PTR; sscf->prefer_server_ciphers = NGX_CONF_UNSET; @@ -943,6 +958,8 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->certificate_cache, prev->certificate_cache, NULL); + ngx_conf_merge_ptr_value(conf->ech_files, prev->ech_files, NULL); + ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL); ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); @@ -1008,7 +1025,9 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { static ngx_ssl_client_hello_arg cb = { ngx_stream_ssl_servername }; - ngx_ssl_set_client_hello_callback(conf->ssl.ctx, &cb); + if (ngx_ssl_set_client_hello_callback(&conf->ssl, &cb) != NGX_OK) { + return NGX_CONF_ERROR; + } SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, ngx_stream_ssl_servername); @@ -1118,6 +1137,10 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (ngx_ssl_ech_files(cf, &conf->ssl, conf->ech_files) != NGX_OK) { + return NGX_CONF_ERROR; + } + if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) { return NGX_CONF_ERROR; } diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h index 31f138c..6fdd8f8 100644 --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -49,6 +49,7 @@ typedef struct { ngx_str_t ciphers; + ngx_array_t *ech_files; ngx_array_t *passwords; ngx_array_t *conf_commands;