From 4deeb251b5fef241c40ff5b168125bd6059d865b Mon Sep 17 00:00:00 2001 From: Brian Neradt Date: Thu, 9 Sep 2021 16:30:02 +0000 Subject: [PATCH 1/3] Revert "Remove incompatible changes for 9.2.0 (#8316)" This reverts commit 4208ecb8719add76aa46ab2005ff9424531e2e73. Reverting the removal of the incompatible changes that we took out of master so that those incompatible changes will be in our 10-Dev branch. --- configs/records.config.default.in | 2 - doc/.tx/config | 6 +- doc/admin-guide/files/records.config.en.rst | 47 +-- doc/admin-guide/files/sni.yaml.en.rst | 3 - doc/admin-guide/performance/index.en.rst | 19 -- doc/admin-guide/plugins/lua.en.rst | 1 - .../api/functions/TSContScheduleEvery.en.rst | 58 ---- ...n.rst => TSContScheduleEveryOnPool.en.rst} | 28 +- .../api/functions/TSContScheduleOnPool.en.rst | 25 +- .../functions/TSContScheduleOnThread.en.rst | 17 +- .../functions/TSContThreadAffinitySet.en.rst | 4 +- .../functions/TSHttpOverridableConfig.en.rst | 2 - .../api/functions/TSHttpTxnAborted.en.rst | 6 +- .../api/types/TSOverridableConfigKey.en.rst | 1 - .../activating-continuations.en.rst | 2 +- .../plugins/continuations/index.en.rst | 2 +- .../writing-handler-functions.en.rst | 5 +- ...edule.en.po => TSContScheduleOnPool.en.po} | 10 +- .../functions/TSHttpOverridableConfig.en.po | 4 - .../activating-continuations.en.po | 4 +- .../plugins/continuations/index.en.po | 4 +- .../writing-handler-functions.en.po | 8 +- doc/release-notes/whats-new.en.rst | 6 + .../c-api/request_buffer/request_buffer.c | 2 +- include/ts/apidefs.h.in | 297 ++++++++++-------- include/ts/ts.h | 5 +- iocore/net/YamlSNIConfig.cc | 4 - iocore/net/YamlSNIConfig.h | 1 - lib/perl/lib/Apache/TS/AdminClient.pm | 2 - mgmt/RecordsConfig.cc | 14 +- plugins/esi/esi.cc | 3 +- plugins/lua/ts_lua_http.c | 4 +- plugins/lua/ts_lua_http_config.c | 6 - proxy/ProxySession.cc | 2 +- proxy/http/HttpConfig.cc | 21 +- proxy/http/HttpConfig.h | 6 +- proxy/http/HttpConnectionCount.cc | 59 +--- proxy/http/HttpConnectionCount.h | 15 +- proxy/http/HttpDebugNames.cc | 4 +- proxy/http/HttpSM.cc | 59 ++-- proxy/http/HttpTransact.cc | 9 +- src/shared/overridable_txn_vars.cc | 5 - src/traffic_server/InkAPI.cc | 79 +---- src/traffic_server/InkAPITest.cc | 7 +- .../cont_schedule/gold/schedule.gold | 4 - .../gold_tests/cont_schedule/schedule.test.py | 49 --- .../gold_tests/pluginTest/test_hooks/200.gold | 28 ++ .../pluginTest/test_hooks/body_buffer.test.py | 121 +++++++ .../timeout/tls_conn_timeout.test.py | 1 - tests/gold_tests/tls/tls_verify_base.test.py | 3 +- .../tls/tls_verify_override_base.test.py | 3 +- tests/tools/plugins/cont_schedule.cc | 87 +---- 52 files changed, 459 insertions(+), 705 deletions(-) delete mode 100644 doc/developer-guide/api/functions/TSContScheduleEvery.en.rst rename doc/developer-guide/api/functions/{TSContSchedule.en.rst => TSContScheduleEveryOnPool.en.rst} (56%) rename doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/{TSContSchedule.en.po => TSContScheduleOnPool.en.po} (81%) delete mode 100644 tests/gold_tests/cont_schedule/gold/schedule.gold delete mode 100644 tests/gold_tests/cont_schedule/schedule.test.py create mode 100644 tests/gold_tests/pluginTest/test_hooks/200.gold create mode 100644 tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py diff --git a/configs/records.config.default.in b/configs/records.config.default.in index 2f2b61779b6..dc22bf75308 100644 --- a/configs/records.config.default.in +++ b/configs/records.config.default.in @@ -35,7 +35,6 @@ CONFIG proxy.config.http.insert_response_via_str INT 0 # https://docs.trafficserver.apache.org/en/latest/admin-guide/files/parent.config.en.html ############################################################################## CONFIG proxy.config.http.parent_proxy.retry_time INT 300 -CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout INT 30 CONFIG proxy.config.http.forward.proxy_auth_to_parent INT 0 CONFIG proxy.config.http.uncacheable_requests_bypass_parent INT 1 @@ -60,7 +59,6 @@ CONFIG proxy.config.http.connect_attempts_max_retries INT 3 CONFIG proxy.config.http.connect_attempts_max_retries_dead_server INT 1 CONFIG proxy.config.http.connect_attempts_rr_retries INT 3 CONFIG proxy.config.http.connect_attempts_timeout INT 30 -CONFIG proxy.config.http.post_connect_attempts_timeout INT 1800 CONFIG proxy.config.http.down_server.cache_time INT 60 ############################################################################## diff --git a/doc/.tx/config b/doc/.tx/config index 317fba5638f..b3e1f737f4e 100644 --- a/doc/.tx/config +++ b/doc/.tx/config @@ -697,9 +697,9 @@ file_filter = locale//LC_MESSAGES/developer-guide/api/functions/TSContMute source_file = _build/locale/pot/developer-guide/api/functions/TSContMutexGet.en.pot source_lang = en -[apache-traffic-server-6x.developer-guide--api--functions--TSContSchedule_en] -file_filter = locale//LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po -source_file = _build/locale/pot/developer-guide/api/functions/TSContSchedule.en.pot +[apache-traffic-server-6x.developer-guide--api--functions--TSContScheduleOnPool_en] +file_filter = locale//LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po +source_file = _build/locale/pot/developer-guide/api/functions/TSContScheduleOnPool.en.pot source_lang = en [apache-traffic-server-6x.developer-guide--api--functions--TSDebug_en] diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index 62504b4b88a..114fd3886a6 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -1296,14 +1296,6 @@ Parent Proxy Configuration The total number of connection attempts allowed per parent for a specific transaction, if multiple parents are used. -.. ts:cv:: CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout INT 30 - :reloadable: - :overridable: - - The timeout value (in seconds) for parent cache connection attempts. - - See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts. - .. ts:cv:: CONFIG proxy.config.http.parent_proxy.mark_down_hostdb INT 0 :reloadable: :overridable: @@ -1573,15 +1565,9 @@ Origin Server Connect Attempts Set a limit for the number of concurrent connections to an upstream server group. A value of ``0`` disables checking. If a transaction attempts to connect to a group which already has the - maximum number of concurrent connections the transaction either rechecks after a delay or a 503 + maximum number of concurrent connections a 503 (``HTTP_STATUS_SERVICE_UNAVAILABLE``) error response is sent to the user agent. To configure - Number of transactions that can be delayed concurrently - See :ts:cv:`proxy.config.http.per_server.connection.queue_size`. - - How long to delay before rechecking - See :ts:cv:`proxy.config.http.per_server.connection.queue_delay`. - Upstream server group definition See :ts:cv:`proxy.config.http.per_server.connection.match`. @@ -1615,26 +1601,6 @@ Origin Server Connect Attempts This setting is independent of the :ts:cv:`setting for upstream session sharing matching `. -.. ts:cv:: CONFIG proxy.config.http.per_server.connection.queue_size INT 0 - :reloadable: - - Controls the number of transactions that can be waiting on an upstream server group. - - ``-1`` - Unlimited. - - ``0`` - Never wait. If the connection maximum has been reached immediately respond with an error. - - A positive number - If there are less than this many waiting transactions, delay this transaction and try again. Otherwise respond immediately with an error. - -.. ts:cv:: CONFIG proxy.config.http.per_server.connection.queue_delay INT 100 - :reloadable: - :units: milliseconds - - If a transaction is delayed due to too many connections in an upstream server group, delay this amount of time before checking again. - .. ts:cv:: CONFIG proxy.config.http.per_server.connection.alert_delay INT 60 :reloadable: :units: seconds @@ -1668,15 +1634,6 @@ Origin Server Connect Attempts See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts. -.. ts:cv:: CONFIG proxy.config.http.post_connect_attempts_timeout INT 1800 - :reloadable: - :overridable: - - The timeout value (in seconds) for an origin server connection when the client request is a ``POST`` or ``PUT`` - request. - - See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts. - .. ts:cv:: CONFIG proxy.config.http.post.check.content_length.enabled INT 1 Enables (``1``) or disables (``0``) checking the Content-Length: Header for a POST request. @@ -3773,7 +3730,7 @@ SSL Termination Client-Related Configuration ---------------------------- -.. ts:cv:: CONFIG proxy.config.ssl.client.verify.server.policy STRING PERMISSIVE +.. ts:cv:: CONFIG proxy.config.ssl.client.verify.server.policy STRING ENFORCED :reloadable: :overridable: diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst index 5577671ac86..2ee7f1994d7 100644 --- a/doc/admin-guide/files/sni.yaml.en.rst +++ b/doc/admin-guide/files/sni.yaml.en.rst @@ -121,9 +121,6 @@ client_sni_policy Outbound Policy of SNI on outbound connection. http2 Inbound Indicates whether the H2 protocol should be added to or removed from the protocol negotiation list. The valid values are :code:`on` or :code:`off`. -disable_h2 Inbound Deprecated for the more general h2 setting. Setting disable_h2 - to :code:`true` is the same as setting http2 to :code:`on`. - tunnel_route Inbound Destination as an FQDN and port, separated by a colon ``:``. Match group number can be specified by ``$N`` where N should refer to a specified group in the FQDN, ``tunnel_route: $1.domain``. diff --git a/doc/admin-guide/performance/index.en.rst b/doc/admin-guide/performance/index.en.rst index 639c2af7e7e..67305447c65 100644 --- a/doc/admin-guide/performance/index.en.rst +++ b/doc/admin-guide/performance/index.en.rst @@ -331,28 +331,9 @@ connection attempt until the origin fully establishes a connection (the connecti After the connection is established the value of :ts:cv:`proxy.config.http.transaction_no_activity_timeout_out` is used to established timeouts on the data over the connection. -In the case where you wish to have a different (generally longer) timeout for -``POST`` and ``PUT`` connections to an origin server, you may also adjust -:ts:cv:`proxy.config.http.post_connect_attempts_timeout` which applies only to -origin connections using those HTTP verbs. - :: CONFIG proxy.config.http.connect_attempts_timeout INT 30 - CONFIG proxy.config.http.post_connect_attempts_timeout INT 1800 - -Parent Proxy Timeout -~~~~~~~~~~~~~~~~~~~~ - -In hierarchical caching configurations, the :ts:cv:`proxy.config.http.parent_proxy.connect_attempts_timeout` -setting is used for all connection attempts to parent caches. It may be useful, -in cases where you wish to have |TS| fall back to an alternate parent cache -(in configurations where you have multiple parents for the same cache) more -quickly, to lower this timeout. - -:: - - CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout INT 30 Polling Timeout ~~~~~~~~~~~~~~~ diff --git a/doc/admin-guide/plugins/lua.en.rst b/doc/admin-guide/plugins/lua.en.rst index 6c598dabc43..28eef1b48a0 100644 --- a/doc/admin-guide/plugins/lua.en.rst +++ b/doc/admin-guide/plugins/lua.en.rst @@ -3941,7 +3941,6 @@ Http config constants TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT TS_LUA_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME - TS_LUA_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD TS_LUA_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS TS_LUA_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_STR diff --git a/doc/developer-guide/api/functions/TSContScheduleEvery.en.rst b/doc/developer-guide/api/functions/TSContScheduleEvery.en.rst deleted file mode 100644 index bc61392cac1..00000000000 --- a/doc/developer-guide/api/functions/TSContScheduleEvery.en.rst +++ /dev/null @@ -1,58 +0,0 @@ -.. Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed - with this work for additional information regarding copyright - ownership. The ASF licenses this file to you under the Apache - License, Version 2.0 (the "License"); you may not use this file - except in compliance with the License. You may obtain a copy of - the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied. See the License for the specific language governing - permissions and limitations under the License. - -.. include:: ../../../common.defs - -.. default-domain:: c - -TSContScheduleEvery -******************* - -Synopsis -======== - -.. code-block:: cpp - - #include - -.. function:: TSAction TSContScheduleEvery(TSCont contp, TSHRTime every) - -Description -=========== - -Schedules :arg:`contp` to periodically run every :arg:`delay` milliseconds in the future. -This is approximate. The delay will be at least :arg:`delay` but possibly more. -Resolutions finer than roughly 5 milliseconds will not be effective. :arg:`contp` is -required to have a mutex, which is provided to :func:`TSContCreate`. - -The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is -effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on -another thread this can be problematic to be correctly timed. The return value can be checked with -:func:`TSActionDone` to see if the continuation ran before the return, which is possible if -:arg:`timeout` is `0`. Returns ``nullptr`` if thread affinity was cleared. - -TSContSchedule() or TSContScheduleEvery() will default to set the thread affinity to the calling thread -when no affinity is already set for example, using :func:`TSContThreadAffinitySet` - -Note that the TSContSchedule() family of API shall only be called from an ATS EThread. -Calling it from raw non-EThreads can result in unpredictable behavior. - -See Also -======== - -:doc:`TSContSchedule.en` -:doc:`TSContScheduleOnPool.en` -:doc:`TSContScheduleOnThread.en` diff --git a/doc/developer-guide/api/functions/TSContSchedule.en.rst b/doc/developer-guide/api/functions/TSContScheduleEveryOnPool.en.rst similarity index 56% rename from doc/developer-guide/api/functions/TSContSchedule.en.rst rename to doc/developer-guide/api/functions/TSContScheduleEveryOnPool.en.rst index 4dbb9f6a090..01163955081 100644 --- a/doc/developer-guide/api/functions/TSContSchedule.en.rst +++ b/doc/developer-guide/api/functions/TSContScheduleEveryOnPool.en.rst @@ -18,8 +18,8 @@ .. default-domain:: c -TSContSchedule -************** +TSContScheduleEveryOnPool +************************* Synopsis ======== @@ -28,24 +28,25 @@ Synopsis #include -.. function:: TSAction TSContSchedule(TSCont contp, TSHRTime timeout) +.. function:: TSAction TSContScheduleEveryOnPool(TSCont contp, TSHRTime every) Description =========== -Schedules :arg:`contp` to run :arg:`delay` milliseconds in the future. This is approximate. The delay -will be at least :arg:`delay` but possibly more. Resolutions finer than roughly 5 milliseconds will -not be effective. :arg:`contp` is required to have a mutex, which is provided to +Schedules :arg:`contp` to run :arg:`every` milliseconds, on a random thread that belongs to +:arg:`tp`. The :arg:`every` is an approximation, meaning it will be at least :arg:`every` +milliseconds but possibly more. Resolutions finer than roughly 5 milliseconds will not be +effective. Note that :arg:`contp` is required to have a mutex, which is provided to :func:`TSContCreate`. -The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is -effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on -another thread this can be problematic to be correctly timed. The return value can be checked with -:func:`TSActionDone` to see if the continuation ran before the return, which is possible if -:arg:`timeout` is `0`. Returns ``nullptr`` if thread affinity was cleared. +The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This +is effective until the continuation :arg:`contp` is being dispatched. However, if it is +scheduled on another thread this can be problematic to be correctly timed. The return value +can be checked with :func:`TSActionDone` to see if the continuation ran before the return, +which is possible if :arg:`timeout` is `0`. -TSContSchedule() or TSContScheduleEvery() will default to set the thread affinity to the calling thread -when no affinity is already set for example, using :func:`TSContThreadAffinitySet` +If :arg:`contp` has no thread affinity set, the thread it is now scheduled on will be set +as its thread affinity thread. Note that the TSContSchedule() family of API shall only be called from an ATS EThread. Calling it from raw non-EThreads can result in unpredictable behavior. @@ -53,6 +54,5 @@ Calling it from raw non-EThreads can result in unpredictable behavior. See Also ======== -:doc:`TSContScheduleEvery.en` :doc:`TSContScheduleOnPool.en` :doc:`TSContScheduleOnThread.en` diff --git a/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst b/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst index 1517f87a0b2..3acf48473aa 100644 --- a/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst +++ b/doc/developer-guide/api/functions/TSContScheduleOnPool.en.rst @@ -33,11 +33,16 @@ Synopsis Description =========== -Mostly the same as :func:`TSContSchedule`. Schedules :arg:`contp` on a random thread that belongs to :arg:`tp`. -If thread type of the thread specified by thread affinity is the same as :arg:`tp`, the :arg:`contp` will -be scheduled on the thread specified by thread affinity. +Schedules :arg:`contp` to run :arg:`timeout` milliseconds in the future, on a random thread that +belongs to :arg:`tp`. The :arg:`timeout` is an approximation, meaning it will be at least +:arg:`timeout` milliseconds but possibly more. Resolutions finer than roughly 5 milliseconds will +not be effective. Note that :arg:`contp` is required to have a mutex, which is provided to +:func:`TSContCreate`. -The continuation is scheduled for a particular thread selected from a group of similar threads, as indicated by :arg:`tp`. +The continuation is scheduled for a particular thread selected from a group of similar threads, +as indicated by :arg:`tp`. If :arg:`contp` already has an thread affinity set, and the thread +type of thread affinity is the same as :arg:`tp`, then :arg:`contp` will be scheduled on the +thread specified by thread affinity. =========================== ======================================================================================= Pool Properties @@ -54,6 +59,15 @@ called and continuations that use them have the same restrictions. ``TS_THREAD_P are threads that exist to perform long or blocking actions, although sufficiently long operation can impact system performance by blocking other continuations on the threads. +The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is +effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on +another thread this can be problematic to be correctly timed. The return value can be checked with +:func:`TSActionDone` to see if the continuation ran before the return, which is possible if +:arg:`timeout` is `0`. + +If :arg:`contp` has no thread affinity set, the thread it is now scheduled on will be set +as its thread affinity thread. + Note that the TSContSchedule() family of API shall only be called from an ATS EThread. Calling it from raw non-EThreads can result in unpredictable behavior. @@ -124,6 +138,5 @@ the same continuation on two different threads of the same type. See Also ======== -:doc:`TSContSchedule.en` -:doc:`TSContScheduleEvery.en` +:doc:`TSContScheduleEveryOnPool.en` :doc:`TSContScheduleOnThread.en` diff --git a/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst b/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst index 0f91166b9c0..41148a9d3ad 100644 --- a/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst +++ b/doc/developer-guide/api/functions/TSContScheduleOnThread.en.rst @@ -33,7 +33,19 @@ Synopsis Description =========== -Mostly the same as :func:`TSContSchedule`. Schedules :arg:`contp` on :arg:`ethread`. +Schedules :arg:`contp` to run :arg:`timeout` milliseconds in the future, on the thread specified by +:arg:`ethread`. The :arg:`timeout` is an approximation, meaning it will be at least :arg:`timeout` +milliseconds but possibly more. Resolutions finer than roughly 5 milliseconds will not be effective. +Note that :arg:`contp` is required to have a mutex, which is provided to :func:`TSContCreate`. + +The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is +effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on +another thread this can be problematic to be correctly timed. The return value can be checked with +:func:`TSActionDone` to see if the continuation ran before the return, which is possible if +:arg:`timeout` is `0`. + +If :arg:`contp` has no thread affinity set, the thread it is now scheduled on will be set +as its thread affinity thread. Note that the TSContSchedule() family of API shall only be called from an ATS EThread. Calling it from raw non-EThreads can result in unpredictable behavior. @@ -41,6 +53,5 @@ Calling it from raw non-EThreads can result in unpredictable behavior. See Also ======== -:doc:`TSContSchedule.en` -:doc:`TSContScheduleEvery.en` :doc:`TSContScheduleOnPool.en` +:doc:`TSContScheduleEveryOnPool.en` diff --git a/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst b/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst index 10465e05e5a..64417de3136 100644 --- a/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst +++ b/doc/developer-guide/api/functions/TSContThreadAffinitySet.en.rst @@ -34,10 +34,10 @@ Description =========== Set the thread affinity of continuation :arg:`contp` to :arg:`ethread`. Future calls to -:func:`TSContSchedule`, and :func:`TSContScheduleOnPool` that has the same type as :arg:`ethread` +:func:`TSContScheduleOnPool`, and :func:`TSContScheduleOnPool` that has the same type as :arg:`ethread` will schedule the continuation on :arg:`ethread`, rather than an arbitrary thread of that type. -:func:`TSContSchedule` and :func:`TSContScheduleEvery` will default the affinity to calling thread +:func:`TSContScheduleOnPool` and :func:`TSContScheduleEveryOnPool` will default the affinity to calling thread when invoked without explicitly setting the thread affinity. Return Values diff --git a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst index 8ad3ae5324d..531993390b3 100644 --- a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst +++ b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst @@ -141,7 +141,6 @@ TSOverridableConfigKey Value Config :c:enumerator:`TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIFETIME` :ts:cv:`proxy.config.http.negative_revalidating_lifetime` :c:enumerator:`TS_CONFIG_HTTP_NORMALIZE_AE` :ts:cv:`proxy.config.http.normalize_ae` :c:enumerator:`TS_CONFIG_HTTP_NUMBER_OF_REDIRECTIONS` :ts:cv:`proxy.config.http.number_of_redirections` -:c:enumerator:`TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT` :ts:cv:`proxy.config.http.parent_proxy.connect_attempts_timeout` :c:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD` :ts:cv:`proxy.config.http.parent_proxy.fail_threshold` :c:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME` :ts:cv:`proxy.config.http.parent_proxy.retry_time` :c:enumerator:`TS_CONFIG_HTTP_PARENT_PROXY_TOTAL_CONNECT_ATTEMPTS` :ts:cv:`proxy.config.http.parent_proxy.total_connect_attempts` @@ -149,7 +148,6 @@ TSOverridableConfigKey Value Config :c:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH` :ts:cv:`proxy.config.http.per_server.connection.match` :c:enumerator:`TS_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX` :ts:cv:`proxy.config.http.per_server.connection.max` :c:enumerator:`TS_CONFIG_HTTP_POST_CHECK_CONTENT_LENGTH_ENABLED` :ts:cv:`proxy.config.http.post.check.content_length.enabled` -:c:enumerator:`TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT` :ts:cv:`proxy.config.http.post_connect_attempts_timeout` :c:enumerator:`TS_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY` :ts:cv:`proxy.config.http.redirect_use_orig_cache_key` :c:enumerator:`TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED` :ts:cv:`proxy.config.http.request_buffer_enabled` :c:enumerator:`TS_CONFIG_HTTP_REQUEST_HEADER_MAX_SIZE` :ts:cv:`proxy.config.http.request_header_max_size` diff --git a/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst b/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst index afa0658a4a7..e5cef8503fe 100644 --- a/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst +++ b/doc/developer-guide/api/functions/TSHttpTxnAborted.en.rst @@ -28,7 +28,7 @@ Synopsis #include -.. c:function:: TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp) +.. c:function:: TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp, bool *client_abort) Description ----------- @@ -37,6 +37,10 @@ Description transaction is aborted. This function should be used to determine whether a transaction has been aborted before attempting to cache the results. +Broadly, transaction aborts can be classified into either client side aborts or +server side. To distinguish between these, we have another boolean parameter +which gets set to TRUE in case of client side aborts. + Return values ------------- diff --git a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst index 8e1a919ddc5..7843fafd2e0 100644 --- a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst +++ b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst @@ -81,7 +81,6 @@ Enumeration Members .. c:enumerator:: TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT .. c:enumerator:: TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT .. c:enumerator:: TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME -.. c:enumerator:: TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD .. c:enumerator:: TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS .. c:enumerator:: TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT .. c:enumerator:: TS_CONFIG_HTTP_RESPONSE_SERVER_STR diff --git a/doc/developer-guide/plugins/continuations/activating-continuations.en.rst b/doc/developer-guide/plugins/continuations/activating-continuations.en.rst index 6b4135fd808..8ff24488e26 100644 --- a/doc/developer-guide/plugins/continuations/activating-continuations.en.rst +++ b/doc/developer-guide/plugins/continuations/activating-continuations.en.rst @@ -23,7 +23,7 @@ Activating Continuations ************************ Continuations are activated when they receive an event or by -``TSContSchedule`` (which schedules a continuation to receive an event). +``TSContScheduleOnPool`` (which schedules a continuation to receive an event). Continuations might receive an event because: - Your plugin calls ``TSContCall`` diff --git a/doc/developer-guide/plugins/continuations/index.en.rst b/doc/developer-guide/plugins/continuations/index.en.rst index 94bd13233ef..41722b1465d 100644 --- a/doc/developer-guide/plugins/continuations/index.en.rst +++ b/doc/developer-guide/plugins/continuations/index.en.rst @@ -54,7 +54,7 @@ one of the following: transactions uses ``TSContDataSet/Get`` - uses ``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, or - ``TSContSchedule`` APIs + ``TSContScheduleOnPool`` APIs Before being activated, a caller must grab the continuation's mutex. This requirement makes it possible for a continuation's handler function diff --git a/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst b/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst index f081ff7079c..7fec877fabe 100644 --- a/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst +++ b/doc/developer-guide/plugins/continuations/writing-handler-functions.en.rst @@ -95,7 +95,6 @@ Event Event Sender :macro:`TS_EVENT_CACHE_LOOKUP_COMPLETE` :macro:`TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK` :type:`TSHttpTxn` :macro:`TS_EVENT_IMMEDIATE` :func:`TSVConnClose` :func:`TSVIOReenable` - :func:`TSContSchedule` :func:`TSContScheduleOnPool` :func:`TSContScheduleOnThread` :macro:`TS_EVENT_IMMEDIATE` :macro:`TS_HTTP_REQUEST_TRANSFORM_HOOK` @@ -113,8 +112,7 @@ Event Event Sender :func:`TSHttpTxnServerIntercept` :func:`TSHttpTxnIntercept` :macro:`TS_EVENT_HOST_LOOKUP` :func:`TSHostLookup` :type:`TSHostLookupResult` -:macro:`TS_EVENT_TIMEOUT` :func:`TSContSchedule` - :func:`TSContScheduleOnPool` +:macro:`TS_EVENT_TIMEOUT` :func:`TSContScheduleOnPool` :func:`TSContScheduleOnThread` :macro:`TS_EVENT_ERROR` :macro:`TS_EVENT_VCONN_READ_READY` :func:`TSVConnRead` :type:`TSVIO` @@ -137,6 +135,5 @@ The continuation functions are listed below: - :func:`TSContDataSet` - :func:`TSContDestroy` - :func:`TSContMutexGet` -- :func:`TSContSchedule` - :func:`TSContScheduleOnPool` - :func:`TSContScheduleOnThread` diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po similarity index 81% rename from doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po rename to doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po index e35760b7bd3..d41276f6802 100644 --- a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContSchedule.en.po +++ b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSContScheduleOnPool.en.po @@ -29,18 +29,18 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.1.1\n" -#: ../../developer-guide/api/functions/TSContSchedule.en.rst:32 +#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:32 msgid "Description" msgstr "解説" -#: ../../developer-guide/api/functions/TSContSchedule.en.rst:25 +#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:25 msgid "Synopsis" msgstr "概要" -#: ../../developer-guide/api/functions/TSContSchedule.en.rst:22 -msgid "TSContSchedule" +#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:22 +msgid "TSContScheduleOnPool" msgstr "" -#: ../../developer-guide/api/functions/TSContSchedule.en.rst:27 +#: ../../developer-guide/api/functions/TSContScheduleOnPool.en.rst:27 msgid "`#include `" msgstr "`#include `" diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po index dc1ec751014..c6a61874b1a 100644 --- a/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po +++ b/doc/locale/ja/LC_MESSAGES/developer-guide/api/functions/TSHttpOverridableConfig.en.po @@ -290,10 +290,6 @@ msgstr "" msgid ":ts:cv:`proxy.config.http.down_server.cache_time`" msgstr "" -#: ../../../developer-guide/api/functions/TSHttpOverridableConfig.en.rst:119 -msgid ":ts:cv:`proxy.config.http.down_server.abort_threshold`" -msgstr "" - #: ../../../developer-guide/api/functions/TSHttpOverridableConfig.en.rst:120 msgid ":ts:cv:`proxy.config.http.cache.fuzz.time`" msgstr "" diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po index 5e8bbaabdd9..928db6110fd 100644 --- a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po +++ b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/activating-continuations.en.po @@ -48,10 +48,10 @@ msgstr "" #: ../../developer-guide/plugins/continuations/activating-continuations.en.rst:25 msgid "" "Continuations are activated when they receive an event or by " -"``TSContSchedule`` (which schedules a continuation to receive an event). " +"``TSContScheduleOnPool`` (which schedules a continuation to receive an event). " "Continuations might receive an event because:" msgstr "" -"継続は、イベントを受け取った際か ``TSContSchedule`` (イベントを受け取るため" +"継続は、イベントを受け取った際か ``TSContScheduleOnPool`` (イベントを受け取るため" "継続をスケジュールする関数)によってアクティベートされます。下記の理由により" "継続がイベントを受け取る可能性があります。 :" diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po index b2595d96af8..b51a9399be6 100644 --- a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po +++ b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/index.en.po @@ -220,8 +220,8 @@ msgstr "" #: ../../developer-guide/plugins/continuations/index.en.rst:56 msgid "" -"uses ``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, or ``TSContSchedule`` " +"uses ``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, or ``TSContScheduleOnPool`` " "APIs" msgstr "" -"``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, もしくは ``TSContSchedule`` " +"``TSCacheXXX``, ``TSNetXXX``, ``TSHostLookup``, もしくは ``TSContScheduleOnPool`` " "API を使用する。" diff --git a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po index bdc58ce1d1a..df4b7127f33 100644 --- a/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po +++ b/doc/locale/ja/LC_MESSAGES/developer-guide/plugins/continuations/writing-handler-functions.en.po @@ -229,8 +229,8 @@ msgid ":data:`TS_EVENT_IMMEDIATE`" msgstr ":data:`TS_EVENT_IMMEDIATE`" #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:90 -msgid ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContSchedule`" -msgstr ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContSchedule`" +msgid ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContScheduleOnPool`" +msgstr ":func:`TSVConnClose` :func:`TSVIOReenable` :func:`TSContScheduleOnPool`" #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:93 msgid ":data:`TS_HTTP_REQUEST_TRANSFORM_HOOK`" @@ -329,8 +329,8 @@ msgstr ":data:`TS_EVENT_TIMEOUT`" #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:108 #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:130 -msgid ":func:`TSContSchedule`" -msgstr ":func:`TSContSchedule`" +msgid ":func:`TSContScheduleOnPool`" +msgstr ":func:`TSContScheduleOnPool`" #: ../../../developer-guide/plugins/continuations/writing-handler-functions.en.rst:109 msgid ":data:`TS_EVENT_ERROR`" diff --git a/doc/release-notes/whats-new.en.rst b/doc/release-notes/whats-new.en.rst index 0500df49c43..eb641e68a11 100644 --- a/doc/release-notes/whats-new.en.rst +++ b/doc/release-notes/whats-new.en.rst @@ -35,6 +35,12 @@ New Features New or modified Configurations ------------------------------ +Combined Connect Timeouts +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The configuration settings :ts:cv: `proxy.config.http.parent_proxy.connect_attempts_timeout` and :ts:cv: `proxy.config.http.post_connect_attempts_timeout` have been removed. +All connect timeouts are controlled by :ts:cv: `proxy.config.http.connect_attempts_timeout`. + Logging and Metrics ------------------- diff --git a/example/plugins/c-api/request_buffer/request_buffer.c b/example/plugins/c-api/request_buffer/request_buffer.c index 69ab0c6269d..2214cda5243 100644 --- a/example/plugins/c-api/request_buffer/request_buffer.c +++ b/example/plugins/c-api/request_buffer/request_buffer.c @@ -67,7 +67,7 @@ request_buffer_plugin(TSCont contp, TSEvent event, void *edata) { TSDebug(PLUGIN_NAME, "request_buffer_plugin starting, event[%d]", event); TSHttpTxn txnp = (TSHttpTxn)(edata); - if (event == TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE) { + if (event == TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE) { int len = 0; char *body = request_body_get(txnp, &len); TSDebug(PLUGIN_NAME, "request_buffer_plugin gets the request body with length[%d]", len); diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index ee65fabf68b..9321dfe1794 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -264,6 +264,122 @@ typedef enum { TS_HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511 } TSHttpStatus; +/** + TSEvents are sent to continuations when they are called back. + The TSEvent provides the continuation's handler function with + information about the callback. Based on the event it receives, + the handler function can decide what to do. + */ +typedef enum { + TS_EVENT_NONE = 0, + TS_EVENT_IMMEDIATE = 1, + TS_EVENT_TIMEOUT = 2, + TS_EVENT_ERROR = 3, + TS_EVENT_CONTINUE = 4, + + TS_EVENT_VCONN_READ_READY = 100, + TS_EVENT_VCONN_WRITE_READY = 101, + TS_EVENT_VCONN_READ_COMPLETE = 102, + TS_EVENT_VCONN_WRITE_COMPLETE = 103, + TS_EVENT_VCONN_EOS = 104, + TS_EVENT_VCONN_INACTIVITY_TIMEOUT = 105, + TS_EVENT_VCONN_ACTIVE_TIMEOUT = 106, + TS_EVENT_VCONN_START = 107, + TS_EVENT_VCONN_CLOSE = 108, + TS_EVENT_VCONN_OUTBOUND_START = 109, + TS_EVENT_VCONN_OUTBOUND_CLOSE = 110, + TS_EVENT_VCONN_PRE_ACCEPT = TS_EVENT_VCONN_START, // Deprecated but still compatible + + TS_EVENT_NET_CONNECT = 200, + TS_EVENT_NET_CONNECT_FAILED = 201, + TS_EVENT_NET_ACCEPT = 202, + TS_EVENT_NET_ACCEPT_FAILED = 204, + + TS_EVENT_INTERNAL_206 = 206, + TS_EVENT_INTERNAL_207 = 207, + TS_EVENT_INTERNAL_208 = 208, + TS_EVENT_INTERNAL_209 = 209, + TS_EVENT_INTERNAL_210 = 210, + TS_EVENT_INTERNAL_211 = 211, + TS_EVENT_INTERNAL_212 = 212, + + TS_EVENT_HOST_LOOKUP = 500, + + TS_EVENT_CACHE_OPEN_READ = 1102, + TS_EVENT_CACHE_OPEN_READ_FAILED = 1103, + TS_EVENT_CACHE_OPEN_WRITE = 1108, + TS_EVENT_CACHE_OPEN_WRITE_FAILED = 1109, + TS_EVENT_CACHE_REMOVE = 1112, + TS_EVENT_CACHE_REMOVE_FAILED = 1113, + TS_EVENT_CACHE_SCAN = 1120, + TS_EVENT_CACHE_SCAN_FAILED = 1121, + TS_EVENT_CACHE_SCAN_OBJECT = 1122, + TS_EVENT_CACHE_SCAN_OPERATION_BLOCKED = 1123, + TS_EVENT_CACHE_SCAN_OPERATION_FAILED = 1124, + TS_EVENT_CACHE_SCAN_DONE = 1125, + TS_EVENT_CACHE_LOOKUP = 1126, + TS_EVENT_CACHE_READ = 1127, + TS_EVENT_CACHE_DELETE = 1128, + TS_EVENT_CACHE_WRITE = 1129, + TS_EVENT_CACHE_WRITE_HEADER = 1130, + TS_EVENT_CACHE_CLOSE = 1131, + TS_EVENT_CACHE_LOOKUP_READY = 1132, + TS_EVENT_CACHE_LOOKUP_COMPLETE = 1133, + TS_EVENT_CACHE_READ_READY = 1134, + TS_EVENT_CACHE_READ_COMPLETE = 1135, + + TS_EVENT_INTERNAL_1200 = 1200, + + TS_EVENT_SSL_SESSION_GET = 2000, + TS_EVENT_SSL_SESSION_NEW = 2001, + TS_EVENT_SSL_SESSION_REMOVE = 2002, + + TS_EVENT_AIO_DONE = 3900, + + TS_EVENT_HTTP_CONTINUE = 60000, + TS_EVENT_HTTP_ERROR = 60001, + TS_EVENT_HTTP_READ_REQUEST_HDR = 60002, + TS_EVENT_HTTP_OS_DNS = 60003, + TS_EVENT_HTTP_SEND_REQUEST_HDR = 60004, + TS_EVENT_HTTP_READ_CACHE_HDR = 60005, + TS_EVENT_HTTP_READ_RESPONSE_HDR = 60006, + TS_EVENT_HTTP_SEND_RESPONSE_HDR = 60007, + TS_EVENT_HTTP_REQUEST_TRANSFORM = 60008, + TS_EVENT_HTTP_RESPONSE_TRANSFORM = 60009, + TS_EVENT_HTTP_SELECT_ALT = 60010, + TS_EVENT_HTTP_TXN_START = 60011, + TS_EVENT_HTTP_TXN_CLOSE = 60012, + TS_EVENT_HTTP_SSN_START = 60013, + TS_EVENT_HTTP_SSN_CLOSE = 60014, + TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE = 60015, + TS_EVENT_HTTP_PRE_REMAP = 60016, + TS_EVENT_HTTP_POST_REMAP = 60017, + TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE = 60018, + TS_EVENT_HTTP_RESPONSE_CLIENT = 60019, + + TS_EVENT_LIFECYCLE_PORTS_INITIALIZED = 60100, + TS_EVENT_LIFECYCLE_PORTS_READY = 60101, + TS_EVENT_LIFECYCLE_CACHE_READY = 60102, + TS_EVENT_LIFECYCLE_SERVER_SSL_CTX_INITIALIZED = 60103, + TS_EVENT_LIFECYCLE_CLIENT_SSL_CTX_INITIALIZED = 60104, + TS_EVENT_LIFECYCLE_MSG = 60105, + TS_EVENT_LIFECYCLE_TASK_THREADS_READY = 60106, + TS_EVENT_LIFECYCLE_SHUTDOWN = 60107, + + TS_EVENT_INTERNAL_60200 = 60200, + TS_EVENT_INTERNAL_60201 = 60201, + TS_EVENT_INTERNAL_60202 = 60202, + TS_EVENT_SSL_CERT = 60203, + TS_EVENT_SSL_SERVERNAME = 60204, + TS_EVENT_SSL_VERIFY_SERVER = 60205, + TS_EVENT_SSL_VERIFY_CLIENT = 60206, + TS_EVENT_SSL_CLIENT_HELLO = 60207, + TS_EVENT_SSL_SECRET = 60208, + + TS_EVENT_MGMT_UPDATE = 60300 +} TSEvent; +#define TS_EVENT_HTTP_READ_REQUEST_PRE_REMAP TS_EVENT_HTTP_PRE_REMAP /* backwards compat */ + /** This set of enums represents the possible hooks where you can set up continuation callbacks. The functions used to register a @@ -316,28 +432,52 @@ typedef enum { TS_HTTP_LAST_HOOK _must_ be the last element. Only right place to insert a new element is just before TS_HTTP_LAST_HOOK. + + @note The TS_HTTP hooks below have to be in the same order as their + corresponding TS_EVENT counterparts. We use this in calculating the + corresponding event from a hook ID by summing + TS_EVENT_HTTP_READ_REQUEST_HDR with the hook ID (see the invoke call in + HttpSM::state_api_callout). For example, the following expression must be + true: + + TS_EVENT_HTTP_TXN_CLOSE == TS_EVENT_HTTP_READ_REQUEST_HDR + TS_HTTP_TXN_CLOSE_HOOK + */ typedef enum { - TS_HTTP_READ_REQUEST_HDR_HOOK, - TS_HTTP_OS_DNS_HOOK, - TS_HTTP_SEND_REQUEST_HDR_HOOK, - TS_HTTP_READ_CACHE_HDR_HOOK, - TS_HTTP_READ_RESPONSE_HDR_HOOK, - TS_HTTP_SEND_RESPONSE_HDR_HOOK, - TS_HTTP_REQUEST_TRANSFORM_HOOK, - TS_HTTP_RESPONSE_TRANSFORM_HOOK, - TS_HTTP_SELECT_ALT_HOOK, - TS_HTTP_TXN_START_HOOK, - TS_HTTP_TXN_CLOSE_HOOK, - TS_HTTP_SSN_START_HOOK, - TS_HTTP_SSN_CLOSE_HOOK, - TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, - TS_HTTP_PRE_REMAP_HOOK, - TS_HTTP_POST_REMAP_HOOK, - TS_HTTP_RESPONSE_CLIENT_HOOK, +/* + The following macro helps maintain the relationship between the TS_HTTP + hooks and their TS_EVENT_HTTP counterparts as described in the above + doxygen comment. + */ +#define DERIVE_HOOK_VALUE_FROM_EVENT(COMMON) TS_HTTP_##COMMON##_HOOK = TS_EVENT_HTTP_##COMMON - TS_EVENT_HTTP_READ_REQUEST_HDR + DERIVE_HOOK_VALUE_FROM_EVENT(READ_REQUEST_HDR), // TS_HTTP_READ_REQUEST_HDR_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(OS_DNS), // TS_HTTP_OS_DNS_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(SEND_REQUEST_HDR), // TS_HTTP_SEND_REQUEST_HDR_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(READ_CACHE_HDR), // TS_HTTP_READ_CACHE_HDR_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(READ_RESPONSE_HDR), // TS_HTTP_READ_RESPONSE_HDR_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(SEND_RESPONSE_HDR), // TS_HTTP_SEND_RESPONSE_HDR_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(REQUEST_TRANSFORM), // TS_HTTP_REQUEST_TRANSFORM_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(RESPONSE_TRANSFORM), // TS_HTTP_RESPONSE_TRANSFORM_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(SELECT_ALT), // TS_HTTP_SELECT_ALT_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(TXN_START), // TS_HTTP_TXN_START_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(TXN_CLOSE), // TS_HTTP_TXN_CLOSE_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(SSN_START), // TS_HTTP_SSN_START_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(SSN_CLOSE), // TS_HTTP_SSN_CLOSE_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(CACHE_LOOKUP_COMPLETE), // TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(PRE_REMAP), // TS_HTTP_PRE_REMAP_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(POST_REMAP), // TS_HTTP_POST_REMAP_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(REQUEST_BUFFER_READ_COMPLETE), // TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK + DERIVE_HOOK_VALUE_FROM_EVENT(RESPONSE_CLIENT), // TS_HTTP_RESPONSE_CLIENT_HOOK + +// NOTE: +// If adding any TS_HTTP hooks, be sure to understand the note above in the +// doxygen comment about the ordering of these enum values with respect to +// their corresponding TS_EVENT values. +#undef DERIVE_HOOK_VALUE_FROM_EVENT + // Putting the SSL hooks in the same enum space // So both sets of hooks can be set by the same Hook function - TS_SSL_FIRST_HOOK, + TS_SSL_FIRST_HOOK = 201, TS_VCONN_START_HOOK = TS_SSL_FIRST_HOOK, TS_VCONN_PRE_ACCEPT_HOOK = TS_VCONN_START_HOOK, // Deprecated but compatible for now. TS_VCONN_CLOSE_HOOK, @@ -352,7 +492,6 @@ typedef enum { TS_VCONN_OUTBOUND_START_HOOK, TS_VCONN_OUTBOUND_CLOSE_HOOK, TS_SSL_LAST_HOOK = TS_VCONN_OUTBOUND_CLOSE_HOOK, - TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK, TS_HTTP_LAST_HOOK } TSHttpHookID; @@ -442,122 +581,6 @@ typedef enum { TS_LIFECYCLE_LAST_HOOK } TSLifecycleHookID; -/** - TSEvents are sent to continuations when they are called back. - The TSEvent provides the continuation's handler function with - information about the callback. Based on the event it receives, - the handler function can decide what to do. - - */ -typedef enum { - TS_EVENT_NONE = 0, - TS_EVENT_IMMEDIATE = 1, - TS_EVENT_TIMEOUT = 2, - TS_EVENT_ERROR = 3, - TS_EVENT_CONTINUE = 4, - - TS_EVENT_VCONN_READ_READY = 100, - TS_EVENT_VCONN_WRITE_READY = 101, - TS_EVENT_VCONN_READ_COMPLETE = 102, - TS_EVENT_VCONN_WRITE_COMPLETE = 103, - TS_EVENT_VCONN_EOS = 104, - TS_EVENT_VCONN_INACTIVITY_TIMEOUT = 105, - TS_EVENT_VCONN_ACTIVE_TIMEOUT = 106, - TS_EVENT_VCONN_START = 107, - TS_EVENT_VCONN_CLOSE = 108, - TS_EVENT_VCONN_OUTBOUND_START = 109, - TS_EVENT_VCONN_OUTBOUND_CLOSE = 110, - TS_EVENT_VCONN_PRE_ACCEPT = TS_EVENT_VCONN_START, // Deprecated but still compatible - - TS_EVENT_NET_CONNECT = 200, - TS_EVENT_NET_CONNECT_FAILED = 201, - TS_EVENT_NET_ACCEPT = 202, - TS_EVENT_NET_ACCEPT_FAILED = 204, - - TS_EVENT_INTERNAL_206 = 206, - TS_EVENT_INTERNAL_207 = 207, - TS_EVENT_INTERNAL_208 = 208, - TS_EVENT_INTERNAL_209 = 209, - TS_EVENT_INTERNAL_210 = 210, - TS_EVENT_INTERNAL_211 = 211, - TS_EVENT_INTERNAL_212 = 212, - - TS_EVENT_HOST_LOOKUP = 500, - - TS_EVENT_CACHE_OPEN_READ = 1102, - TS_EVENT_CACHE_OPEN_READ_FAILED = 1103, - TS_EVENT_CACHE_OPEN_WRITE = 1108, - TS_EVENT_CACHE_OPEN_WRITE_FAILED = 1109, - TS_EVENT_CACHE_REMOVE = 1112, - TS_EVENT_CACHE_REMOVE_FAILED = 1113, - TS_EVENT_CACHE_SCAN = 1120, - TS_EVENT_CACHE_SCAN_FAILED = 1121, - TS_EVENT_CACHE_SCAN_OBJECT = 1122, - TS_EVENT_CACHE_SCAN_OPERATION_BLOCKED = 1123, - TS_EVENT_CACHE_SCAN_OPERATION_FAILED = 1124, - TS_EVENT_CACHE_SCAN_DONE = 1125, - TS_EVENT_CACHE_LOOKUP = 1126, - TS_EVENT_CACHE_READ = 1127, - TS_EVENT_CACHE_DELETE = 1128, - TS_EVENT_CACHE_WRITE = 1129, - TS_EVENT_CACHE_WRITE_HEADER = 1130, - TS_EVENT_CACHE_CLOSE = 1131, - TS_EVENT_CACHE_LOOKUP_READY = 1132, - TS_EVENT_CACHE_LOOKUP_COMPLETE = 1133, - TS_EVENT_CACHE_READ_READY = 1134, - TS_EVENT_CACHE_READ_COMPLETE = 1135, - - TS_EVENT_INTERNAL_1200 = 1200, - - TS_EVENT_SSL_SESSION_GET = 2000, - TS_EVENT_SSL_SESSION_NEW = 2001, - TS_EVENT_SSL_SESSION_REMOVE = 2002, - - TS_EVENT_AIO_DONE = 3900, - - TS_EVENT_HTTP_CONTINUE = 60000, - TS_EVENT_HTTP_ERROR = 60001, - TS_EVENT_HTTP_READ_REQUEST_HDR = 60002, - TS_EVENT_HTTP_OS_DNS = 60003, - TS_EVENT_HTTP_SEND_REQUEST_HDR = 60004, - TS_EVENT_HTTP_READ_CACHE_HDR = 60005, - TS_EVENT_HTTP_READ_RESPONSE_HDR = 60006, - TS_EVENT_HTTP_SEND_RESPONSE_HDR = 60007, - TS_EVENT_HTTP_REQUEST_TRANSFORM = 60008, - TS_EVENT_HTTP_RESPONSE_TRANSFORM = 60009, - TS_EVENT_HTTP_SELECT_ALT = 60010, - TS_EVENT_HTTP_TXN_START = 60011, - TS_EVENT_HTTP_TXN_CLOSE = 60012, - TS_EVENT_HTTP_SSN_START = 60013, - TS_EVENT_HTTP_SSN_CLOSE = 60014, - TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE = 60015, - TS_EVENT_HTTP_PRE_REMAP = 60016, - TS_EVENT_HTTP_POST_REMAP = 60017, - TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE = 60018, - - TS_EVENT_LIFECYCLE_PORTS_INITIALIZED = 60100, - TS_EVENT_LIFECYCLE_PORTS_READY = 60101, - TS_EVENT_LIFECYCLE_CACHE_READY = 60102, - TS_EVENT_LIFECYCLE_SERVER_SSL_CTX_INITIALIZED = 60103, - TS_EVENT_LIFECYCLE_CLIENT_SSL_CTX_INITIALIZED = 60104, - TS_EVENT_LIFECYCLE_MSG = 60105, - TS_EVENT_LIFECYCLE_TASK_THREADS_READY = 60106, - TS_EVENT_LIFECYCLE_SHUTDOWN = 60107, - - TS_EVENT_INTERNAL_60200 = 60200, - TS_EVENT_INTERNAL_60201 = 60201, - TS_EVENT_INTERNAL_60202 = 60202, - TS_EVENT_SSL_CERT = 60203, - TS_EVENT_SSL_SERVERNAME = 60204, - TS_EVENT_SSL_VERIFY_SERVER = 60205, - TS_EVENT_SSL_VERIFY_CLIENT = 60206, - TS_EVENT_SSL_CLIENT_HELLO = 60207, - TS_EVENT_SSL_SECRET = 60208, - - TS_EVENT_MGMT_UPDATE = 60300 -} TSEvent; -#define TS_EVENT_HTTP_READ_REQUEST_PRE_REMAP TS_EVENT_HTTP_PRE_REMAP /* backwards compat */ - typedef enum { TS_SRVSTATE_STATE_UNDEFINED = 0, TS_SRVSTATE_ACTIVE_TIMEOUT, @@ -788,10 +811,7 @@ typedef enum { TS_CONFIG_HTTP_CONNECT_ATTEMPTS_MAX_RETRIES_DEAD_SERVER, TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES, TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT, - TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT, TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME, - // Should be removed for 10.0 - TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD, TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS, TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT, TS_CONFIG_HTTP_RESPONSE_SERVER_STR, @@ -846,7 +866,6 @@ typedef enum { TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD, TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME, TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS, - TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT, TS_CONFIG_HTTP_NORMALIZE_AE, TS_CONFIG_HTTP_INSERT_FORWARDED, TS_CONFIG_HTTP_PROXY_PROTOCOL_OUT, diff --git a/include/ts/ts.h b/include/ts/ts.h index d2d216baae2..1dc9bec0d37 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -1246,10 +1246,8 @@ tsapi TSCont TSContCreate(TSEventFunc funcp, TSMutex mutexp); tsapi void TSContDestroy(TSCont contp); tsapi void TSContDataSet(TSCont contp, void *data); tsapi void *TSContDataGet(TSCont contp); -tsapi TSAction TSContSchedule(TSCont contp, TSHRTime timeout); tsapi TSAction TSContScheduleOnPool(TSCont contp, TSHRTime timeout, TSThreadPool tp); tsapi TSAction TSContScheduleOnThread(TSCont contp, TSHRTime timeout, TSEventThread ethread); -tsapi TSAction TSContScheduleEvery(TSCont contp, TSHRTime every /* millisecs */); tsapi TSAction TSContScheduleEveryOnPool(TSCont contp, TSHRTime every /* millisecs */, TSThreadPool tp); tsapi TSAction TSContScheduleEveryOnThread(TSCont contp, TSHRTime every /* millisecs */, TSEventThread ethread); tsapi TSReturnCode TSContThreadAffinitySet(TSCont contp, TSEventThread ethread); @@ -2387,10 +2385,11 @@ tsapi TSReturnCode TSAIOThreadNumSet(int thread_num); /** Check if transaction was aborted (due client/server errors etc.) + Client_abort is set as True, in case the abort was caused by the Client. @return 1 if transaction was aborted */ -tsapi TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp); +tsapi TSReturnCode TSHttpTxnAborted(TSHttpTxn txnp, bool *client_abort); tsapi TSVConn TSVConnCreate(TSEventFunc event_funcp, TSMutex mutexp); tsapi TSVConn TSVConnFdCreate(int fd); diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index 2142e84cedc..e38231f36e3 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -123,7 +123,6 @@ TsEnumDescriptor PROPERTIES_DESCRIPTOR = {{{"NONE", 0}, {"SIGNATURE", 0x1}, { TsEnumDescriptor TLS_PROTOCOLS_DESCRIPTOR = {{{"TLSv1", 0}, {"TLSv1_1", 1}, {"TLSv1_2", 2}, {"TLSv1_3", 3}}}; std::set valid_sni_config_keys = {TS_fqdn, - TS_disable_h2, TS_verify_client, TS_verify_client_ca_certs, TS_tunnel_route, @@ -160,9 +159,6 @@ template <> struct convert { } else { return false; // servername must be present } - if (node[TS_disable_h2]) { - item.offer_h2 = false; - } if (node[TS_http2]) { item.offer_h2 = node[TS_http2].as(); } diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index 5b10ff3f3ea..f1c4b6111ca 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -32,7 +32,6 @@ #define TSDECL(id) constexpr char TS_##id[] = #id TSDECL(fqdn); -TSDECL(disable_h2); TSDECL(verify_client); TSDECL(verify_client_ca_certs); TSDECL(tunnel_route); diff --git a/lib/perl/lib/Apache/TS/AdminClient.pm b/lib/perl/lib/Apache/TS/AdminClient.pm index 258c59232ed..35c7aa52285 100644 --- a/lib/perl/lib/Apache/TS/AdminClient.pm +++ b/lib/perl/lib/Apache/TS/AdminClient.pm @@ -474,13 +474,11 @@ The Apache Traffic Server Administration Manual will explain what these strings proxy.config.http.per_server.connection.max proxy.config.http.origin_min_keep_alive_connections proxy.config.http.parent_proxies - proxy.config.http.parent_proxy.connect_attempts_timeout proxy.config.http.parent_proxy.fail_threshold proxy.config.http.parent_proxy.file proxy.config.http.parent_proxy.per_parent_connect_attempts proxy.config.http.parent_proxy.retry_time proxy.config.http.parent_proxy.total_connect_attempts - proxy.config.http.post_connect_attempts_timeout proxy.config.http.post_copy_size proxy.config.http.push_method_enabled proxy.config.http.quick_filter.mask diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index e3429bbbbe6..60233fedc82 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -400,10 +400,6 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http.per_server.connection.alert_delay", RECD_INT, "60", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , - {RECT_CONFIG, "proxy.config.http.per_server.connection.queue_size", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} - , - {RECT_CONFIG, "proxy.config.http.per_server.connection.queue_delay", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} - , {RECT_CONFIG, "proxy.config.http.per_server.connection.min", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , {RECT_CONFIG, "proxy.config.http.attach_server_session_to_client", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} @@ -447,8 +443,6 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http.parent_proxy.per_parent_connect_attempts", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , - {RECT_CONFIG, "proxy.config.http.parent_proxy.connect_attempts_timeout", RECD_INT, "30", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} - , {RECT_CONFIG, "proxy.config.http.parent_proxy.mark_down_hostdb", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} , {RECT_CONFIG, "proxy.config.http.parent_proxy.self_detect", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} @@ -503,8 +497,6 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http.connect_attempts_timeout", RECD_INT, "30", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , - {RECT_CONFIG, "proxy.config.http.post_connect_attempts_timeout", RECD_INT, "1800", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} - , {RECT_CONFIG, "proxy.config.http.connect.dead.policy", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , {RECT_CONFIG, "proxy.config.http.down_server.cache_time", RECD_INT, "60", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} @@ -1138,7 +1130,7 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.ssl.CA.cert.path", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , - {RECT_CONFIG, "proxy.config.ssl.client.verify.server.policy", RECD_STRING, "PERMISSIVE", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + {RECT_CONFIG, "proxy.config.ssl.client.verify.server.policy", RECD_STRING, "ENFORCED", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , {RECT_CONFIG, "proxy.config.ssl.client.verify.server.properties", RECD_STRING, "ALL", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , @@ -1243,10 +1235,6 @@ static const RecordElement RecordsConfig[] = {RECT_CONFIG, "proxy.config.plugin.load_elevated", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_READ_ONLY} , - // Interim configuration setting for obeying keepalive requests on internal - // (PluginVC) sessions. See TS-4960 and friends. - {RECT_LOCAL, "proxy.config.http.keepalive_internal_vc", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}, - //############################################################################## //# //# Local Manager Specific Records File diff --git a/plugins/esi/esi.cc b/plugins/esi/esi.cc index 9d223b5f997..67b6fe9e5e0 100644 --- a/plugins/esi/esi.cc +++ b/plugins/esi/esi.cc @@ -578,7 +578,8 @@ removeCacheKey(TSHttpTxn txnp) static void cacheNodeList(ContData *cont_data) { - if (TSHttpTxnAborted(cont_data->txnp) == TS_SUCCESS) { + bool client_abort; + if (TSHttpTxnAborted(cont_data->txnp, &client_abort) == TS_SUCCESS) { TSDebug(cont_data->debug_tag, "[%s] Not caching node list as txn has been aborted", __FUNCTION__); return; } diff --git a/plugins/lua/ts_lua_http.c b/plugins/lua/ts_lua_http.c index 3cd4aec0f91..8fd5a0395fe 100644 --- a/plugins/lua/ts_lua_http.c +++ b/plugins/lua/ts_lua_http.c @@ -767,8 +767,8 @@ ts_lua_http_is_aborted(lua_State *L) ts_lua_http_ctx *http_ctx; GET_HTTP_CONTEXT(http_ctx, L); - - if (TSHttpTxnAborted(http_ctx->txnp)) { + bool client_abort = false; + if (TSHttpTxnAborted(http_ctx->txnp, &client_abort)) { lua_pushnumber(L, 1); } else { lua_pushnumber(L, 0); diff --git a/plugins/lua/ts_lua_http_config.c b/plugins/lua/ts_lua_http_config.c index 24420fe4851..c17eb8d321e 100644 --- a/plugins/lua/ts_lua_http_config.c +++ b/plugins/lua/ts_lua_http_config.c @@ -72,9 +72,7 @@ typedef enum { TS_LUA_CONFIG_HTTP_CONNECT_DEAD_POLICY = TS_CONFIG_HTTP_CONNECT_DEAD_POLICY, TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES = TS_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES, TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT = TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT, - TS_LUA_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT = TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT, TS_LUA_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME = TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME, - TS_LUA_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD = TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD, TS_LUA_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS = TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS, TS_LUA_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT = TS_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT, TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_STR = TS_CONFIG_HTTP_RESPONSE_SERVER_STR, @@ -129,7 +127,6 @@ typedef enum { TS_LUA_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD = TS_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD, TS_LUA_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME = TS_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME, TS_LUA_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS = TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS, - TS_LUA_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT = TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT, TS_LUA_CONFIG_HTTP_ALLOW_MULTI_RANGE = TS_CONFIG_HTTP_ALLOW_MULTI_RANGE, TS_LUA_CONFIG_HTTP_REQUEST_BUFFER_ENABLED = TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED, TS_LUA_CONFIG_HTTP_ALLOW_HALF_OPEN = TS_CONFIG_HTTP_ALLOW_HALF_OPEN, @@ -202,9 +199,7 @@ ts_lua_var_item ts_lua_http_config_vars[] = { TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CONNECT_DEAD_POLICY), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_RR_RETRIES), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT), - TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME), - TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_BACKGROUND_FILL_ACTIVE_TIMEOUT), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_RESPONSE_SERVER_STR), @@ -259,7 +254,6 @@ ts_lua_var_item ts_lua_http_config_vars[] = { TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PARENT_PROXY_FAIL_THRESHOLD), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PARENT_PROXY_RETRY_TIME), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS), - TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_ALLOW_MULTI_RANGE), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_REQUEST_BUFFER_ENABLED), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_ALLOW_HALF_OPEN), diff --git a/proxy/ProxySession.cc b/proxy/ProxySession.cc index f53d66b0daf..332b468ce9c 100644 --- a/proxy/ProxySession.cc +++ b/proxy/ProxySession.cc @@ -77,7 +77,7 @@ static const TSEvent eventmap[TS_HTTP_LAST_HOOK + 1] = { TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE, // TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK TS_EVENT_HTTP_PRE_REMAP, // TS_HTTP_PRE_REMAP_HOOK TS_EVENT_HTTP_POST_REMAP, // TS_HTTP_POST_REMAP_HOOK - TS_EVENT_NONE, // TS_HTTP_RESPONSE_CLIENT_HOOK + TS_EVENT_HTTP_RESPONSE_CLIENT, // TS_HTTP_RESPONSE_CLIENT_HOOK TS_EVENT_NONE, // TS_HTTP_LAST_HOOK }; diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc index 52674cbf049..79ba53262ce 100644 --- a/proxy/http/HttpConfig.cc +++ b/proxy/http/HttpConfig.cc @@ -1234,13 +1234,11 @@ HttpConfig::startup() HttpEstablishStaticConfigLongLong(c.oride.connect_attempts_rr_retries, "proxy.config.http.connect_attempts_rr_retries"); HttpEstablishStaticConfigLongLong(c.oride.connect_attempts_timeout, "proxy.config.http.connect_attempts_timeout"); - HttpEstablishStaticConfigLongLong(c.oride.post_connect_attempts_timeout, "proxy.config.http.post_connect_attempts_timeout"); HttpEstablishStaticConfigLongLong(c.oride.parent_connect_attempts, "proxy.config.http.parent_proxy.total_connect_attempts"); HttpEstablishStaticConfigLongLong(c.oride.parent_retry_time, "proxy.config.http.parent_proxy.retry_time"); HttpEstablishStaticConfigLongLong(c.oride.parent_fail_threshold, "proxy.config.http.parent_proxy.fail_threshold"); HttpEstablishStaticConfigLongLong(c.oride.per_parent_connect_attempts, "proxy.config.http.parent_proxy.per_parent_connect_attempts"); - HttpEstablishStaticConfigLongLong(c.oride.parent_connect_timeout, "proxy.config.http.parent_proxy.connect_attempts_timeout"); HttpEstablishStaticConfigByte(c.oride.parent_failures_update_hostdb, "proxy.config.http.parent_proxy.mark_down_hostdb"); HttpEstablishStaticConfigLongLong(c.oride.sock_recv_buffer_size_out, "proxy.config.net.sock_recv_buffer_size_out"); @@ -1314,8 +1312,6 @@ HttpConfig::startup() HttpEstablishStaticConfigByte(c.send_100_continue_response, "proxy.config.http.send_100_continue_response"); HttpEstablishStaticConfigByte(c.disallow_post_100_continue, "proxy.config.http.disallow_post_100_continue"); - HttpEstablishStaticConfigByte(c.keepalive_internal_vc, "proxy.config.http.keepalive_internal_vc"); - HttpEstablishStaticConfigByte(c.oride.cache_open_write_fail_action, "proxy.config.http.cache.open_write_fail_action"); HttpEstablishStaticConfigByte(c.oride.cache_when_to_revalidate, "proxy.config.http.cache.when_to_revalidate"); @@ -1434,17 +1430,9 @@ HttpConfig::reconfigure() params->disable_ssl_parenting = INT_TO_BOOL(m_master.disable_ssl_parenting); params->oride.forward_connect_method = INT_TO_BOOL(m_master.oride.forward_connect_method); - params->server_max_connections = m_master.server_max_connections; - params->max_websocket_connections = m_master.max_websocket_connections; - params->oride.outbound_conntrack = m_master.oride.outbound_conntrack; - // If queuing for outbound connection tracking is enabled without enabling max connections, it is meaningless, so we'll warn - if (params->outbound_conntrack.queue_size > 0 && - !(params->oride.outbound_conntrack.max > 0 || params->oride.outbound_conntrack.min > 0)) { - Warning("'%s' is set, but neither '%s' nor '%s' are " - "set, please correct your %s", - OutboundConnTrack::CONFIG_VAR_QUEUE_SIZE.data(), OutboundConnTrack::CONFIG_VAR_MAX.data(), - OutboundConnTrack::CONFIG_VAR_MIN.data(), ts::filename::RECORDS); - } + params->server_max_connections = m_master.server_max_connections; + params->max_websocket_connections = m_master.max_websocket_connections; + params->oride.outbound_conntrack = m_master.oride.outbound_conntrack; params->oride.attach_server_session_to_client = m_master.oride.attach_server_session_to_client; params->oride.max_proxy_cycles = m_master.oride.max_proxy_cycles; @@ -1523,12 +1511,10 @@ HttpConfig::reconfigure() params->oride.connect_attempts_rr_retries = m_master.oride.connect_attempts_rr_retries; params->oride.connect_attempts_timeout = m_master.oride.connect_attempts_timeout; params->oride.connect_dead_policy = m_master.oride.connect_dead_policy; - params->oride.post_connect_attempts_timeout = m_master.oride.post_connect_attempts_timeout; params->oride.parent_connect_attempts = m_master.oride.parent_connect_attempts; params->oride.parent_retry_time = m_master.oride.parent_retry_time; params->oride.parent_fail_threshold = m_master.oride.parent_fail_threshold; params->oride.per_parent_connect_attempts = m_master.oride.per_parent_connect_attempts; - params->oride.parent_connect_timeout = m_master.oride.parent_connect_timeout; params->oride.parent_failures_update_hostdb = m_master.oride.parent_failures_update_hostdb; params->oride.sock_recv_buffer_size_out = m_master.oride.sock_recv_buffer_size_out; @@ -1607,7 +1593,6 @@ HttpConfig::reconfigure() params->send_100_continue_response = INT_TO_BOOL(m_master.send_100_continue_response); params->disallow_post_100_continue = INT_TO_BOOL(m_master.disallow_post_100_continue); - params->keepalive_internal_vc = INT_TO_BOOL(m_master.keepalive_internal_vc); params->oride.cache_open_write_fail_action = m_master.oride.cache_open_write_fail_action; if (params->oride.cache_open_write_fail_action == CACHE_WL_FAIL_ACTION_READ_RETRY) { diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index f977ea5fecb..20b907907cd 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -663,7 +663,6 @@ struct OverridableHttpConfigParams { MgmtInt connect_attempts_max_retries_dead_server = 3; MgmtInt connect_attempts_rr_retries = 3; MgmtInt connect_attempts_timeout = 30; - MgmtInt post_connect_attempts_timeout = 1800; MgmtInt connect_dead_policy = 2; @@ -674,10 +673,8 @@ struct OverridableHttpConfigParams { MgmtInt parent_retry_time = 300; MgmtInt parent_fail_threshold = 10; MgmtInt per_parent_connect_attempts = 2; - MgmtInt parent_connect_timeout = 30; - MgmtInt down_server_timeout = 300; - MgmtInt client_abort_threshold = 1000; + MgmtInt down_server_timeout = 300; // open read failure retries. MgmtInt max_cache_open_read_retries = -1; @@ -826,7 +823,6 @@ struct HttpConfigParams : public ConfigInfo { MgmtByte send_100_continue_response = 0; MgmtByte disallow_post_100_continue = 0; - MgmtByte keepalive_internal_vc = 0; MgmtByte server_session_sharing_pool = TS_SERVER_SESSION_SHARING_POOL_THREAD; diff --git a/proxy/http/HttpConnectionCount.cc b/proxy/http/HttpConnectionCount.cc index cd64e8d38d5..15e6d1a70fa 100644 --- a/proxy/http/HttpConnectionCount.cc +++ b/proxy/http/HttpConnectionCount.cc @@ -104,30 +104,6 @@ Config_Update_Conntrack_Max(const char *name, RecDataT dtype, RecData data, void return false; } -bool -Config_Update_Conntrack_Queue_Size(const char *name, RecDataT dtype, RecData data, void *cookie) -{ - auto config = static_cast(cookie); - - if (RECD_INT == dtype) { - config->queue_size = data.rec_int; - return true; - } - return false; -} - -bool -Config_Update_Conntrack_Queue_Delay(const char *name, RecDataT dtype, RecData data, void *cookie) -{ - auto config = static_cast(cookie); - - if (RECD_INT == dtype && data.rec_int > 0) { - config->queue_delay = std::chrono::milliseconds(data.rec_int); - return true; - } - return false; -} - bool Config_Update_Conntrack_Match(const char *name, RecDataT dtype, RecData data, void *cookie) { @@ -171,8 +147,6 @@ OutboundConnTrack::config_init(GlobalConfig *global, TxnConfig *txn) Enable_Config_Var(CONFIG_VAR_MIN, &Config_Update_Conntrack_Min, txn); Enable_Config_Var(CONFIG_VAR_MAX, &Config_Update_Conntrack_Max, txn); Enable_Config_Var(CONFIG_VAR_MATCH, &Config_Update_Conntrack_Match, txn); - Enable_Config_Var(CONFIG_VAR_QUEUE_SIZE, &Config_Update_Conntrack_Queue_Size, global); - Enable_Config_Var(CONFIG_VAR_QUEUE_DELAY, &Config_Update_Conntrack_Queue_Delay, global); Enable_Config_Var(CONFIG_VAR_ALERT_DELAY, &Config_Update_Conntrack_Alert_Delay, global); } @@ -269,13 +243,13 @@ OutboundConnTrack::to_json_string() static const ts::BWFormat header_fmt{R"({{"count": {}, "list": [ )"}; static const ts::BWFormat item_fmt{ - R"( {{"type": "{}", "ip": "{}", "fqdn": "{}", "current": {}, "max": {}, "blocked": {}, "queued": {}, "alert": {}}}, + R"( {{"type": "{}", "ip": "{}", "fqdn": "{}", "current": {}, "max": {}, "blocked": {}, "alert": {}}}, )"}; static const std::string_view trailer{" \n]}"}; static const auto printer = [](ts::BufferWriter &w, Group const *g) -> ts::BufferWriter & { w.print(item_fmt, g->_match_type, g->_addr, g->_fqdn, g->_count.load(), g->_count_max.load(), g->_blocked.load(), - g->_rescheduled.load(), g->get_last_alert_epoch_time()); + g->get_last_alert_epoch_time()); return w; }; @@ -310,18 +284,17 @@ OutboundConnTrack::dump(FILE *f) self_type::get(groups); if (groups.size()) { - fprintf(f, "\nUpstream Connection Tracking\n%7s | %5s | %10s | %24s | %33s | %8s |\n", "Current", "Block", "Queue", "Address", - "Hostname Hash", "Match"); - fprintf(f, "------|-------|---------|--------------------------|-----------------------------------|----------|\n"); + fprintf(f, "\nUpstream Connection Tracking\n%7s | %5s | %24s | %33s | %8s |\n", "Current", "Block", "Address", "Hostname Hash", + "Match"); + fprintf(f, "------|-------|--------------------------|-----------------------------------|----------|\n"); for (Group const *g : groups) { ts::LocalBufferWriter<128> w; - w.print("{:7} | {:5} | {:5} | {:24} | {:33} | {:8} |\n", g->_count.load(), g->_blocked.load(), g->_rescheduled.load(), - g->_addr, g->_hash, g->_match_type); + w.print("{:7} | {:5} | {:24} | {:33} | {:8} |\n", g->_count.load(), g->_blocked.load(), g->_addr, g->_hash, g->_match_type); fwrite(w.data(), w.size(), 1, f); } - fprintf(f, "------|-------|-------|--------------------------|-----------------------------------|----------|\n"); + fprintf(f, "------|-------|--------------------------|-----------------------------------|----------|\n"); } } @@ -376,12 +349,11 @@ OutboundConnTrack::TxnState::Note_Unblocked(const TxnConfig *config, int count, { time_t lat; // last alert time (epoch seconds) - if ((_g->_blocked > 0 || _g->_rescheduled > 0) && _g->should_alert(&lat)) { - auto blocked = _g->_blocked.exchange(0); - auto rescheduled = _g->_rescheduled.exchange(0); + if (_g->_blocked > 0 && _g->should_alert(&lat)) { + auto blocked = _g->_blocked.exchange(0); ts::LocalBufferWriter<256> w; - w.print("upstream unblocked: [{}] count={} limit={} group=({}) blocked={} queued={} upstream={}\0", - ts::bwf::Date(lat, "%b %d %H:%M:%S"sv), count, config->max, *_g, blocked, rescheduled, addr); + w.print("upstream unblocked: [{}] count={} limit={} group=({}) blocked={} upstream={}\0", + ts::bwf::Date(lat, "%b %d %H:%M:%S"sv), count, config->max, *_g, blocked, addr); Debug(DEBUG_TAG, "%s", w.data()); Note("%s", w.data()); } @@ -391,14 +363,13 @@ void OutboundConnTrack::TxnState::Warn_Blocked(const TxnConfig *config, int64_t sm_id, int count, sockaddr const *addr, char const *debug_tag) { - bool alert_p = _g->should_alert(); - auto blocked = alert_p ? _g->_blocked.exchange(0) : _g->_blocked.load(); - auto rescheduled = alert_p ? _g->_rescheduled.exchange(0) : _g->_rescheduled.load(); + bool alert_p = _g->should_alert(); + auto blocked = alert_p ? _g->_blocked.exchange(0) : _g->_blocked.load(); if (alert_p || debug_tag) { ts::LocalBufferWriter<256> w; - w.print("[{}] too many connections: count={} limit={} group=({}) blocked={} queued={} upstream={}\0", sm_id, count, config->max, - *_g, blocked, rescheduled, addr); + w.print("[{}] too many connections: count={} limit={} group=({}) blocked={} upstream={}\0", sm_id, count, config->max, *_g, + blocked, addr); if (debug_tag) { Debug(debug_tag, "%s", w.data()); diff --git a/proxy/http/HttpConnectionCount.h b/proxy/http/HttpConnectionCount.h index 494d2ca3be7..19a3075dc85 100644 --- a/proxy/http/HttpConnectionCount.h +++ b/proxy/http/HttpConnectionCount.h @@ -77,9 +77,7 @@ class OutboundConnTrack /** Static configuration values. */ struct GlobalConfig { - int queue_size{0}; ///< Maximum delayed transactions. - std::chrono::milliseconds queue_delay{100}; ///< Reschedule / queue delay in ms. - std::chrono::seconds alert_delay{60}; ///< Alert delay in seconds. + std::chrono::seconds alert_delay{60}; ///< Alert delay in seconds. }; // The names of the configuration values. @@ -88,8 +86,6 @@ class OutboundConnTrack static constexpr std::string_view CONFIG_VAR_MAX{"proxy.config.http.per_server.connection.max"_sv}; static constexpr std::string_view CONFIG_VAR_MIN{"proxy.config.http.per_server.connection.min"_sv}; static constexpr std::string_view CONFIG_VAR_MATCH{"proxy.config.http.per_server.connection.match"_sv}; - static constexpr std::string_view CONFIG_VAR_QUEUE_SIZE{"proxy.config.http.per_server.connection.queue_size"_sv}; - static constexpr std::string_view CONFIG_VAR_QUEUE_DELAY{"proxy.config.http.per_server.connection.queue_delay"_sv}; static constexpr std::string_view CONFIG_VAR_ALERT_DELAY{"proxy.config.http.per_server.connection.alert_delay"_sv}; /// A record for the outbound connection count. @@ -122,7 +118,6 @@ class OutboundConnTrack std::atomic _count{0}; ///< Number of outbound connections. std::atomic _count_max{0}; ///< largest observed @a count value. std::atomic _blocked{0}; ///< Number of outbound connections blocked since last alert. - std::atomic _rescheduled{0}; ///< # of connection reschedules. std::atomic _in_queue{0}; ///< # of connections queued, waiting for a connection. std::atomic _last_alert{0}; ///< Absolute time of the last alert. @@ -168,8 +163,6 @@ class OutboundConnTrack void dequeue(); /// Note blocking a transaction. void blocked(); - /// Note a rescheduling - void rescheduled(); /// Clear all reservations. void clear(); /// Drop the reservation - assume it will be cleaned up elsewhere. @@ -392,12 +385,6 @@ OutboundConnTrack::TxnState::blocked() ++_g->_blocked; } -inline void -OutboundConnTrack::TxnState::rescheduled() -{ - ++_g->_rescheduled; -} - /* === Linkage === */ inline auto OutboundConnTrack::Linkage::next_ptr(value_type *value) -> value_type *& diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc index 2eb03f8e8f9..3138913308c 100644 --- a/proxy/http/HttpDebugNames.cc +++ b/proxy/http/HttpDebugNames.cc @@ -338,8 +338,8 @@ HttpDebugNames::get_event_name(int event) return "TS_EVENT_VCONN_CLOSE"; case TS_EVENT_LIFECYCLE_MSG: return "TS_EVENT_LIFECYCLE_MSG"; - case TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE: - return "TS_EVENT_HTTP_REQUEST_BUFFER_COMPLETE"; + case TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE: + return "TS_EVENT_HTTP_REQUEST_BUFFER_READ_COMPLETE"; case TS_EVENT_MGMT_UPDATE: return "TS_EVENT_MGMT_UPDATE"; case TS_EVENT_INTERNAL_60200: diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index c9a6c255725..81ed57ec6bc 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -346,15 +346,7 @@ HttpSM::get_server_connect_timeout() if (t_state.api_txn_connect_timeout_value != -1) { retval = HRTIME_MSECONDS(t_state.api_txn_connect_timeout_value); } else { - int connect_timeout; - if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) { - connect_timeout = t_state.txn_conf->post_connect_attempts_timeout; - } else if (t_state.current.server == &t_state.parent_info) { - connect_timeout = t_state.txn_conf->parent_connect_timeout; - } else { - connect_timeout = t_state.txn_conf->connect_attempts_timeout; - } - retval = HRTIME_SECONDS(connect_timeout); + retval = HRTIME_SECONDS(t_state.txn_conf->connect_attempts_timeout); } return retval; } @@ -1670,7 +1662,7 @@ plugins required to work with sni_routing. // void HttpSM::handle_api_return() // // Figures out what to do after calling api callouts -// have finished. This messy and I would like +// have finished. This is messy and I would like // to come up with a cleaner way to handle the api // return. The way we are doing things also makes a // mess of set_next_state() @@ -3736,7 +3728,14 @@ HttpSM::tunnel_handler_post_server(int event, HttpTunnelConsumer *c) { STATE_ENTER(&HttpSM::tunnel_handler_post_server, event); - server_request_body_bytes = c->bytes_written; + // If is_using_post_buffer has been used, then this handler gets called + // twice, once with the buffered request body bytes and a second time with + // the (now) zero length user agent buffer. See wait_for_full_body where + // these bytes are read. Don't clobber the server_request_body_bytes with + // zero on that second read. + if (server_request_body_bytes == 0) { + server_request_body_bytes = c->bytes_written; + } switch (event) { case VC_EVENT_EOS: @@ -5228,31 +5227,8 @@ HttpSM::do_http_server_open(bool raw) ink_assert(pending_action.is_empty()); // in case of reschedule must not have already pending. - // If the queue is disabled, reschedule. - if (t_state.http_config_param->outbound_conntrack.queue_size < 0) { - ct_state.enqueue(); - ct_state.rescheduled(); - pending_action = - eventProcessor.schedule_in(this, HRTIME_MSECONDS(t_state.http_config_param->outbound_conntrack.queue_delay.count())); - } else if (t_state.http_config_param->outbound_conntrack.queue_size > 0) { // queue enabled, check for a slot - auto wcount = ct_state.enqueue(); - if (wcount < t_state.http_config_param->outbound_conntrack.queue_size) { - ct_state.rescheduled(); - SMDebug("http", "%s", lbw().print("[{}] queued for {}\0", sm_id, t_state.current.server->dst_addr).data()); - pending_action = - eventProcessor.schedule_in(this, HRTIME_MSECONDS(t_state.http_config_param->outbound_conntrack.queue_delay.count())); - } else { // the queue is full - ct_state.dequeue(); // release the queue slot - ct_state.blocked(); // note the blockage. - HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat); - send_origin_throttled_response(); - } - } else { // queue size is 0, always block. - ct_state.blocked(); - HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat); - send_origin_throttled_response(); - } - + ct_state.blocked(); + HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat); ct_state.Warn_Blocked(&t_state.txn_conf->outbound_conntrack, sm_id, ccount - 1, &t_state.current.server->dst_addr.sa, debug_on && is_debug_tag_set("http") ? "http" : nullptr); send_origin_throttled_response(); @@ -5956,10 +5932,17 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) // Next order of business if copy the remaining data from the // header buffer into new buffer - client_request_body_bytes = + int64_t num_body_bytes = post_buffer->write(ua_txn->get_remote_reader(), chunked ? ua_txn->get_remote_reader()->read_avail() : post_bytes); - ua_txn->get_remote_reader()->consume(client_request_body_bytes); + // If is_using_post_buffer has been used, then client_request_body_bytes + // will have already been set in wait_for_full_body and there will be + // zero bytes in this user agent buffer. We don't want to clobber + // client_request_body_bytes with a zero value here in those cases. + if (client_request_body_bytes == 0) { + client_request_body_bytes = num_body_bytes; + } + ua_txn->get_remote_reader()->consume(num_body_bytes); p = tunnel.add_producer(ua_entry->vc, post_bytes - transfered_bytes, buf_start, &HttpSM::tunnel_handler_post_ua, HT_HTTP_CLIENT, "user agent post"); } diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index a7866cc79d9..2eb1c89f4a5 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -5760,15 +5760,8 @@ HttpTransact::initialize_state_variables_from_request(State *s, HTTPHdr *obsolet } // If this is an internal request, never keep alive - if (!s->txn_conf->keep_alive_enabled_in) { + if (!s->txn_conf->keep_alive_enabled_in || (vc && vc->get_is_internal_request())) { s->client_info.keep_alive = HTTP_NO_KEEPALIVE; - } else if (vc && vc->get_is_internal_request()) { - // Following the trail of JIRAs back from TS-4960, there can be issues with - // EOS event delivery when using keepalive on internal PluginVC session. As - // an interim measure, if proxy.config.http.keepalive_internal_vc is set, - // we will obey the incoming transaction's keepalive request. - s->client_info.keep_alive = - s->http_config_param->keepalive_internal_vc ? incoming_request->keep_alive_get() : HTTP_NO_KEEPALIVE; } else { s->client_info.keep_alive = incoming_request->keep_alive_get(); } diff --git a/src/shared/overridable_txn_vars.cc b/src/shared/overridable_txn_vars.cc index 7c2f39847c6..ac8baa5f8bf 100644 --- a/src/shared/overridable_txn_vars.cc +++ b/src/shared/overridable_txn_vars.cc @@ -89,8 +89,6 @@ const std::unordered_mapmdata; } -TSAction -TSContSchedule(TSCont contp, TSHRTime timeout) -{ - sdk_assert(sdk_sanity_check_iocore_structure(contp) == TS_SUCCESS); - - /* ensure we are on a EThread */ - sdk_assert(sdk_sanity_check_null_ptr((void *)this_ethread()) == TS_SUCCESS); - - FORCE_PLUGIN_SCOPED_MUTEX(contp); - - INKContInternal *i = reinterpret_cast(contp); - - if (ink_atomic_increment(static_cast(&i->m_event_count), 1) < 0) { - ink_assert(!"not reached"); - } - - EThread *eth = i->getThreadAffinity(); - if (eth == nullptr) { - eth = this_ethread(); - i->setThreadAffinity(eth); - } - - TSAction action; - if (timeout == 0) { - action = reinterpret_cast(eth->schedule_imm(i)); - } else { - action = reinterpret_cast(eth->schedule_in(i, HRTIME_MSECONDS(timeout))); - } - - /* This is a hack. Should be handled in ink_types */ - action = (TSAction)((uintptr_t)action | 0x1); - return action; -} - TSAction TSContScheduleOnPool(TSCont contp, TSHRTime timeout, TSThreadPool tp) { @@ -4753,35 +4719,6 @@ TSContScheduleOnThread(TSCont contp, TSHRTime timeout, TSEventThread ethread) return action; } -TSAction -TSContScheduleEvery(TSCont contp, TSHRTime every /* millisecs */) -{ - sdk_assert(sdk_sanity_check_iocore_structure(contp) == TS_SUCCESS); - - /* ensure we are on a EThread */ - sdk_assert(sdk_sanity_check_null_ptr((void *)this_ethread()) == TS_SUCCESS); - - FORCE_PLUGIN_SCOPED_MUTEX(contp); - - INKContInternal *i = reinterpret_cast(contp); - - if (ink_atomic_increment(static_cast(&i->m_event_count), 1) < 0) { - ink_assert(!"not reached"); - } - - EThread *eth = i->getThreadAffinity(); - if (eth == nullptr) { - eth = this_ethread(); - i->setThreadAffinity(eth); - } - - TSAction action = reinterpret_cast(eth->schedule_every(i, HRTIME_MSECONDS(every))); - - /* This is a hack. Should be handled in ink_types */ - action = (TSAction)((uintptr_t)action | 0x1); - return action; -} - TSAction TSContScheduleEveryOnPool(TSCont contp, TSHRTime every, TSThreadPool tp) { @@ -5739,16 +5676,19 @@ TSHttpTxnShutDown(TSHttpTxn txnp, TSEvent event) } TSReturnCode -TSHttpTxnAborted(TSHttpTxn txnp) +TSHttpTxnAborted(TSHttpTxn txnp, bool *client_abort) { sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); + sdk_assert(client_abort != nullptr); - HttpSM *sm = (HttpSM *)txnp; + *client_abort = false; + HttpSM *sm = (HttpSM *)txnp; switch (sm->t_state.squid_codes.log_code) { case SQUID_LOG_ERR_CLIENT_ABORT: case SQUID_LOG_ERR_CLIENT_READ_ERROR: case SQUID_LOG_TCP_SWAPFAIL: // check for client abort and cache read error + *client_abort = true; return TS_SUCCESS; default: break; @@ -8736,15 +8676,9 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr case TS_CONFIG_HTTP_CONNECT_ATTEMPTS_TIMEOUT: ret = _memberp_to_generic(&overridableHttpConfig->connect_attempts_timeout, conv); break; - case TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT: - ret = _memberp_to_generic(&overridableHttpConfig->post_connect_attempts_timeout, conv); - break; case TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME: ret = _memberp_to_generic(&overridableHttpConfig->down_server_timeout, conv); break; - case TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD: - ret = _memberp_to_generic(&overridableHttpConfig->client_abort_threshold, conv); - break; case TS_CONFIG_HTTP_DOC_IN_CACHE_SKIP_DNS: ret = _memberp_to_generic(&overridableHttpConfig->doc_in_cache_skip_dns, conv); break; @@ -8913,9 +8847,6 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr case TS_CONFIG_HTTP_PER_PARENT_CONNECT_ATTEMPTS: ret = _memberp_to_generic(&overridableHttpConfig->per_parent_connect_attempts, conv); break; - case TS_CONFIG_HTTP_PARENT_CONNECT_ATTEMPT_TIMEOUT: - ret = _memberp_to_generic(&overridableHttpConfig->parent_connect_timeout, conv); - break; case TS_CONFIG_HTTP_ALLOW_MULTI_RANGE: ret = _memberp_to_generic(&overridableHttpConfig->allow_multi_range, conv); break; diff --git a/src/traffic_server/InkAPITest.cc b/src/traffic_server/InkAPITest.cc index 783c1c7fc1c..a8f27d21dfe 100644 --- a/src/traffic_server/InkAPITest.cc +++ b/src/traffic_server/InkAPITest.cc @@ -6636,8 +6636,9 @@ enum ORIG_TSHttpHookID { ORIG_TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, ORIG_TS_HTTP_PRE_REMAP_HOOK, ORIG_TS_HTTP_POST_REMAP_HOOK, + ORIG_TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK, ORIG_TS_HTTP_RESPONSE_CLIENT_HOOK, - ORIG_TS_SSL_FIRST_HOOK, + ORIG_TS_SSL_FIRST_HOOK = 201, ORIG_TS_VCONN_START_HOOK = ORIG_TS_SSL_FIRST_HOOK, ORIG_TS_VCONN_CLOSE_HOOK, ORIG_TS_SSL_CLIENT_HELLO_HOOK, @@ -6649,7 +6650,6 @@ enum ORIG_TSHttpHookID { ORIG_TS_VCONN_OUTBOUND_START_HOOK, ORIG_TS_VCONN_OUTBOUND_CLOSE_HOOK, ORIG_TS_SSL_LAST_HOOK = ORIG_TS_VCONN_OUTBOUND_CLOSE_HOOK, - ORIG_TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK, ORIG_TS_HTTP_LAST_HOOK }; @@ -8624,9 +8624,7 @@ std::array SDK_Overridable_Configs = { "proxy.config.http.connect_attempts_max_retries_dead_server", "proxy.config.http.connect_attempts_rr_retries", "proxy.config.http.connect_attempts_timeout", - "proxy.config.http.post_connect_attempts_timeout", "proxy.config.http.down_server.cache_time", - "proxy.config.http.down_server.abort_threshold", "proxy.config.http.doc_in_cache_skip_dns", "proxy.config.http.background_fill_active_timeout", "proxy.config.http.response_server_str", @@ -8680,7 +8678,6 @@ std::array SDK_Overridable_Configs = { "proxy.config.http.parent_proxy.fail_threshold", "proxy.config.http.parent_proxy.retry_time", "proxy.config.http.parent_proxy.per_parent_connect_attempts", - "proxy.config.http.parent_proxy.connect_attempts_timeout", "proxy.config.http.normalize_ae", "proxy.config.http.insert_forwarded", "proxy.config.http.proxy_protocol_out", diff --git a/tests/gold_tests/cont_schedule/gold/schedule.gold b/tests/gold_tests/cont_schedule/gold/schedule.gold deleted file mode 100644 index 7b5d1f6a2b2..00000000000 --- a/tests/gold_tests/cont_schedule/gold/schedule.gold +++ /dev/null @@ -1,4 +0,0 @@ -`` -``(TSContSchedule_test.check) pass [should be the same thread] -``(TSContSchedule_test.check) pass [should not be the same thread] -`` diff --git a/tests/gold_tests/cont_schedule/schedule.test.py b/tests/gold_tests/cont_schedule/schedule.test.py deleted file mode 100644 index d5e58ecafad..00000000000 --- a/tests/gold_tests/cont_schedule/schedule.test.py +++ /dev/null @@ -1,49 +0,0 @@ -''' -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import os - -Test.Summary = 'Test TSContSchedule API' -Test.ContinueOnFail = True - -# Define default ATS -ts = Test.MakeATSProcess('ts') - -Test.testName = 'Test TSContSchedule API' - -ts.Disk.records_config.update({ - 'proxy.config.exec_thread.autoconfig': 0, - 'proxy.config.exec_thread.autoconfig.scale': 1.5, - 'proxy.config.exec_thread.limit': 32, - 'proxy.config.accept_threads': 1, - 'proxy.config.task_threads': 2, - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'TSContSchedule_test' -}) - -# Load plugin -Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'cont_schedule.so'), ts, 'thread') - -# www.example.com Host -tr = Test.AddTestRun() -tr.Processes.Default.Command = 'printf "Test TSContSchedule API"' -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.StartBefore(ts) -ts.Streams.All = "gold/schedule.gold" -ts.Streams.All += Testers.ExcludesExpression('fail', 'should not contain "fail"') diff --git a/tests/gold_tests/pluginTest/test_hooks/200.gold b/tests/gold_tests/pluginTest/test_hooks/200.gold new file mode 100644 index 00000000000..a8875735ebb --- /dev/null +++ b/tests/gold_tests/pluginTest/test_hooks/200.gold @@ -0,0 +1,28 @@ +`` +> POST /contentlength HTTP/1.1`` +> Host:`` +> User-Agent: curl/`` +> Accept: */*`` +> Content-Length: 22`` +> Content-Type: application/`` +`` +< HTTP/1.1 200 OK`` +< Server: ATS/`` +< Content-Length: 23`` +< Date:`` +< Age:`` +`` +> POST /chunked`` +> Host:`` +> User-Agent: curl/`` +> Accept: */*`` +> Transfer-Encoding: chunked`` +> Content-Type: application/`` +`` +< HTTP/1.1 200 OK`` +< Server: ATS/`` +< Date: `` +< Age: 0`` +< Transfer-Encoding: chunked`` +< Connection: keep-alive`` +`` diff --git a/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py new file mode 100644 index 00000000000..674274fb0a8 --- /dev/null +++ b/tests/gold_tests/pluginTest/test_hooks/body_buffer.test.py @@ -0,0 +1,121 @@ +''' +Verify HTTP body buffering. +''' +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + + +def int_to_hex_string(int_value): + ''' + Convert the given int value to a hex string with no '0x' prefix. + + >>> int_to_hex_string(0) + '0' + >>> int_to_hex_string(1) + '1' + >>> int_to_hex_string(10) + 'a' + >>> int_to_hex_string(16) + '10' + >>> int_to_hex_string(17) + 'f1' + ''' + if not isinstance(int_value, int): + raise ValueError("Input should be an int type.") + return hex(int_value).split('x')[1] + + +class BodyBufferTest: + def __init__(cls, description): + Test.Summary = description + cls._origin_max_connections = 3 + cls.setupOriginServer() + cls.setupTS() + + def setupOriginServer(self): + self._server = Test.MakeOriginServer("server") + self.content_length_request_body = "content-length request" + self.content_length_size = len(self.content_length_request_body) + request_header = {"headers": "POST /contentlength HTTP/1.1\r\n" + "Host: www.example.com\r\n" + f"Content-Length: {self.content_length_size}\r\n\r\n", + "timestamp": "1469733493.993", + "body": self.content_length_request_body} + content_length_response_body = "content-length response" + content_length_response_size = len(content_length_response_body) + response_header = {"headers": "HTTP/1.1 200 OK\r\n" + "Server: microserver\r\n" + f"Content-Length: {content_length_response_size}\r\n\r\n" + "Connection: close\r\n\r\n", + "timestamp": "1469733493.993", + "body": content_length_response_body} + self._server.addResponse("sessionlog.json", request_header, response_header) + + self.chunked_request_body = "chunked request" + hex_size = int_to_hex_string(len(self.chunked_request_body)) + self.encoded_chunked_request = f"{hex_size}\r\n{self.chunked_request_body}\r\n0\r\n\r\n" + self.encoded_chunked_size = len(self.content_length_request_body) + request_header2 = {"headers": "POST /chunked HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n\r\n", + "timestamp": "1469733493.993", + "body": self.encoded_chunked_request} + self.chunked_response_body = "chunked response" + hex_size = int_to_hex_string(len(self.chunked_response_body)) + self.encoded_chunked_response = f"{hex_size}\r\n{self.chunked_response_body}\r\n0\r\n\r\n" + response_header2 = {"headers": "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Server: microserver\r\n" + "Connection: close\r\n\r\n", + "timestamp": "1469733493.993", + "body": self.encoded_chunked_response} + self._server.addResponse("sessionlog.json", request_header2, response_header2) + + def setupTS(self): + self._ts = Test.MakeATSProcess("ts", select_ports=False) + self._ts.Disk.remap_config.AddLine( + f'map / http://127.0.0.1:{self._server.Variables.Port}' + ) + Test.PrepareInstalledPlugin('request_buffer.so', self._ts) + self._ts.Disk.records_config.update({ + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'request_buffer', + }) + + self._ts.Streams.stderr = Testers.ContainsExpression( + rf"request_buffer_plugin gets the request body with length\[{self.content_length_size}\]", + "Verify that the plugin parsed the content-length request body data.") + self._ts.Streams.stderr += Testers.ContainsExpression( + rf"request_buffer_plugin gets the request body with length\[{self.encoded_chunked_size}\]", + "Verify that the plugin parsed the chunked request body.") + + def run(self): + tr = Test.AddTestRun() + # Send both a Content-Length request and a chunked-encoded request. + tr.Processes.Default.Command = ( + f'curl -v http://127.0.0.1:{self._ts.Variables.port}/contentlength -d "{self.content_length_request_body}" --next ' + f'-v http://127.0.0.1:{self._ts.Variables.port}/chunked -H "Transfer-Encoding: chunked" -d "{self.chunked_request_body}"') + tr.Processes.Default.ReturnCode = 0 + tr.Processes.Default.StartBefore(self._server) + tr.Processes.Default.StartBefore(Test.Processes.ts) + tr.Processes.Default.Streams.stderr = "200.gold" + + +bodyBufferTest = BodyBufferTest("Test request body buffering.") +bodyBufferTest.run() diff --git a/tests/gold_tests/timeout/tls_conn_timeout.test.py b/tests/gold_tests/timeout/tls_conn_timeout.test.py index 5a635b1a8dc..86da7ecc4ee 100644 --- a/tests/gold_tests/timeout/tls_conn_timeout.test.py +++ b/tests/gold_tests/timeout/tls_conn_timeout.test.py @@ -43,7 +43,6 @@ ts.Disk.records_config.update({ 'proxy.config.url_remap.remap_required': 1, 'proxy.config.http.connect_attempts_timeout': 1, - 'proxy.config.http.post_connect_attempts_timeout': 1, 'proxy.config.http.connect_attempts_max_retries': 1, 'proxy.config.http.transaction_no_activity_timeout_out': 4, 'proxy.config.diags.debug.enabled': 0, diff --git a/tests/gold_tests/tls/tls_verify_base.test.py b/tests/gold_tests/tls/tls_verify_base.test.py index bcb03183768..884f905e7b1 100644 --- a/tests/gold_tests/tls/tls_verify_base.test.py +++ b/tests/gold_tests/tls/tls_verify_base.test.py @@ -76,7 +76,8 @@ 'proxy.config.ssl.client.CA.cert.filename': 'signer.pem', 'proxy.config.url_remap.pristine_host_hdr': 1, 'proxy.config.exec_thread.autoconfig.scale': 1.0, - 'proxy.config.ssl.client.sni_policy': 'host' + 'proxy.config.ssl.client.sni_policy': 'host', + 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE' }) ts.Disk.sni_yaml.AddLines([ diff --git a/tests/gold_tests/tls/tls_verify_override_base.test.py b/tests/gold_tests/tls/tls_verify_override_base.test.py index 0495ec54b78..1274185ea92 100644 --- a/tests/gold_tests/tls/tls_verify_override_base.test.py +++ b/tests/gold_tests/tls/tls_verify_override_base.test.py @@ -113,7 +113,8 @@ 'proxy.config.url_remap.pristine_host_hdr': 1, 'proxy.config.exec_thread.autoconfig.scale': 1.0, 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), - 'proxy.config.dns.resolv_conf': 'NULL' + 'proxy.config.dns.resolv_conf': 'NULL', + 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE' }) dns.addRecords(records={"foo.com.": ["127.0.0.1"]}) diff --git a/tests/tools/plugins/cont_schedule.cc b/tests/tools/plugins/cont_schedule.cc index 4b8aa31f628..7706d895564 100644 --- a/tests/tools/plugins/cont_schedule.cc +++ b/tests/tools/plugins/cont_schedule.cc @@ -42,79 +42,12 @@ static TSEventThread thread_2 = nullptr; static TSCont contp_1 = nullptr; static TSCont contp_2 = nullptr; -static int TSContSchedule_handler_1(TSCont contp, TSEvent event, void *edata); -static int TSContSchedule_handler_2(TSCont contp, TSEvent event, void *edata); static int TSContScheduleOnPool_handler_1(TSCont contp, TSEvent event, void *edata); static int TSContScheduleOnPool_handler_2(TSCont contp, TSEvent event, void *edata); static int TSContScheduleOnThread_handler_1(TSCont contp, TSEvent event, void *edata); static int TSContScheduleOnThread_handler_2(TSCont contp, TSEvent event, void *edata); static int TSContThreadAffinity_handler(TSCont contp, TSEvent event, void *edata); -static int -TSContSchedule_handler_1(TSCont contp, TSEvent event, void *edata) -{ - TSDebug(DEBUG_TAG_HDL, "TSContSchedule handler 1 thread [%p]", TSThreadSelf()); - if (thread_1 == nullptr) { - // First time entering this handler, before everything else starts. - thread_1 = TSEventThreadSelf(); - - // Set the affinity of contp_2 to thread_1, and schedule it twice. - // Since it's on the same thread, we don't need a delay. - TSDebug(DEBUG_TAG_HDL, "[%s] scheduling continuation", plugin_name); - TSContThreadAffinitySet(contp_2, thread_1); - TSContSchedule(contp_2, 0); - TSContSchedule(contp_2, 0); - } else if (thread_2 == nullptr) { - TSDebug(DEBUG_TAG_CHK, "fail [schedule delay not applied]"); - } else { - // Second time in here, should be after the two scheduled handler_2 runs. - // Since handler_1 has no affinity set, we should be on a different thread now. - // Also, thread_2 should be the same as thread_1, since thread_1 was set as - // affinity for handler_2. - if (thread_2 != TSEventThreadSelf() && thread_2 == thread_1) { - TSDebug(DEBUG_TAG_CHK, "pass [should not be the same thread]"); - } else { - TSDebug(DEBUG_TAG_CHK, "fail [on the same thread]"); - } - } - return 0; -} - -static int -TSContSchedule_handler_2(TSCont contp, TSEvent event, void *edata) -{ - TSDebug(DEBUG_TAG_HDL, "TSContSchedule handler 2 thread [%p]", TSThreadSelf()); - if (thread_2 == nullptr) { - // First time in this handler, should get here after handler_1, - // and also record the thread id. - thread_2 = TSEventThreadSelf(); - } else if (thread_2 == TSEventThreadSelf()) { - // Second time in here, since the affinity is set to thread_1, we should be - // on the same thread as last time. - TSDebug(DEBUG_TAG_CHK, "pass [should be the same thread]"); - } else { - TSDebug(DEBUG_TAG_CHK, "fail [not the same thread]"); - } - return 0; -} - -void -TSContSchedule_test() -{ - contp_1 = TSContCreate(TSContSchedule_handler_1, TSMutexCreate()); - contp_2 = TSContCreate(TSContSchedule_handler_2, TSMutexCreate()); - - if (contp_1 == nullptr || contp_2 == nullptr) { - TSDebug(DEBUG_TAG_SCHD, "[%s] could not create continuation", plugin_name); - abort(); - } else { - TSDebug(DEBUG_TAG_SCHD, "[%s] scheduling continuation", plugin_name); - TSContScheduleOnPool(contp_1, 0, TS_THREAD_POOL_NET); - TSContThreadAffinityClear(contp_1); - TSContScheduleOnPool(contp_1, 200, TS_THREAD_POOL_NET); - } -} - static int TSContScheduleOnPool_handler_1(TSCont contp, TSEvent event, void *edata) { @@ -180,7 +113,7 @@ TSContScheduleOnPool_test() static int TSContScheduleOnThread_handler_1(TSCont contp, TSEvent event, void *edata) { - // Mostly same as TSContSchedule_handler_1, no need to set affinity + // Mostly same as TSContScheduleOnPool_handler_1, no need to set affinity // since we are scheduling directly on to a thread. TSDebug(DEBUG_TAG_HDL, "TSContScheduleOnThread handler 1 thread [%p]", TSThreadSelf()); if (thread_1 == nullptr) { @@ -280,15 +213,12 @@ LifecycleHookTracer(TSCont contp, TSEvent event, void *edata) if (event == TS_EVENT_LIFECYCLE_TASK_THREADS_READY) { switch (test_flag) { case 1: - TSContSchedule_test(); - break; - case 2: TSContScheduleOnPool_test(); break; - case 3: + case 2: TSContScheduleOnThread_test(); break; - case 4: + case 3: TSContThreadAffinity_test(); break; default: @@ -301,20 +231,17 @@ LifecycleHookTracer(TSCont contp, TSEvent event, void *edata) void TSPluginInit(int argc, const char *argv[]) { - if (argc == 1) { - TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContSchedule"); - test_flag = 1; - } else if (argc == 2) { + if (argc == 2) { int len = strlen(argv[1]); if (len == 4 && strncmp(argv[1], "pool", 4) == 0) { TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContScheduleOnPool"); - test_flag = 2; + test_flag = 1; } else if (len == 6 && strncmp(argv[1], "thread", 6) == 0) { TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContScheduleOnThread"); - test_flag = 3; + test_flag = 2; } else if (len == 8 && strncmp(argv[1], "affinity", 8) == 0) { TSDebug(DEBUG_TAG_INIT, "initializing plugin for testing TSContThreadAffinity"); - test_flag = 4; + test_flag = 3; } else { goto Lerror; } From b13ddc33d414d11ce7ee5bd00feefd402295f8f0 Mon Sep 17 00:00:00 2001 From: Susan Hinrichs Date: Wed, 16 Dec 2020 17:02:29 +0000 Subject: [PATCH 2/3] Http2 to origin. --- doc/admin-guide/files/records.config.en.rst | 7 + .../statistics/core/http-connection.en.rst | 15 + .../statistics/core/http-transaction.en.rst | 10 + include/ts/apidefs.h.in | 1 + include/tscore/ink_defs.h | 2 + iocore/eventsystem/I_EThread.h | 2 + iocore/eventsystem/I_Thread.h | 1 + iocore/net/ALPNSupport.cc | 33 + iocore/net/I_NetVConnection.h | 3 + iocore/net/P_ALPNSupport.h | 1 + iocore/net/P_SSLConfig.h | 3 + iocore/net/SSLConfig.cc | 10 + iocore/net/SSLNetVConnection.cc | 20 + iocore/net/UnixNetVConnection.cc | 4 + mgmt/RecordsConfig.cc | 4 + plugins/lua/ts_lua_http_config.c | 2 + proxy/PoolableSession.h | 7 + proxy/ProxySession.cc | 20 + proxy/ProxySession.h | 2 + proxy/ProxyTransaction.cc | 28 + proxy/ProxyTransaction.h | 5 + proxy/hdrs/HdrToken.cc | 5 +- proxy/http/Http1ServerSession.cc | 4 + proxy/http/HttpConfig.h | 1 + proxy/http/HttpProxyServerMain.cc | 6 + proxy/http/HttpSM.cc | 899 ++++++++++++++---- proxy/http/HttpSM.h | 35 +- proxy/http/HttpSessionManager.cc | 17 +- proxy/http/HttpSessionManager.h | 6 +- proxy/http/HttpTunnel.cc | 28 +- proxy/http2/HTTP2.cc | 96 +- proxy/http2/HTTP2.h | 21 +- proxy/http2/Http2ClientSession.cc | 32 +- proxy/http2/Http2ClientSession.h | 5 +- proxy/http2/Http2CommonSession.cc | 73 +- proxy/http2/Http2CommonSession.h | 8 + proxy/http2/Http2ConnectionState.cc | 338 +++++-- proxy/http2/Http2ConnectionState.h | 15 +- proxy/http2/Http2ServerSession.cc | 428 +++++++++ proxy/http2/Http2ServerSession.h | 94 ++ proxy/http2/Http2Stream.cc | 426 ++++++--- proxy/http2/Http2Stream.h | 118 ++- proxy/http2/Makefile.am | 4 +- proxy/http2/unit_tests/test_HTTP2.cc | 2 +- proxy/logging/LogAccess.cc | 2 +- src/shared/overridable_txn_vars.cc | 3 +- src/traffic_server/InkAPI.cc | 18 +- src/traffic_server/InkAPITest.cc | 3 +- tests/gold_tests/h2/gold/nghttp_0_stdout.gold | 2 - tests/gold_tests/h2/gold/nghttp_1_stdout.gold | 2 +- tests/gold_tests/h2/h2origin.test.py | 94 ++ .../h2/h2origin_single_thread.test.py | 90 ++ tests/gold_tests/h2/http2.test.py | 4 + tests/gold_tests/h2/httpbin.test.py | 2 +- .../h2/replay/h1-client-h2-origin.yaml | 596 ++++++++++++ tests/gold_tests/h2/replay/h2-origin.yaml | 624 ++++++++++++ .../timeout/tls_conn_timeout.test.py | 4 +- 57 files changed, 3709 insertions(+), 576 deletions(-) create mode 100644 proxy/http2/Http2ServerSession.cc create mode 100644 proxy/http2/Http2ServerSession.h create mode 100644 tests/gold_tests/h2/h2origin.test.py create mode 100644 tests/gold_tests/h2/h2origin_single_thread.test.py create mode 100644 tests/gold_tests/h2/replay/h1-client-h2-origin.yaml create mode 100644 tests/gold_tests/h2/replay/h2-origin.yaml diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index 114fd3886a6..dbd5adb30df 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -3859,6 +3859,13 @@ Client-Related Configuration Enables (``1``) or disables (``0``) TLSv1_3 in the ATS client context. If not specified, enabled by default +.. ts:cv:: CONFIG proxy.config.ssl.client.alpn_protocol STRING "" + + Set the alpn string that ATS will send to origin during new connections. By default no ALPN string will be set. + To enable HTTP/2 communication to the origin, set this to "h2,http1.1". + + :overridable: + .. ts:cv:: CONFIG proxy.config.ssl.async.handshake.enabled INT 0 Enables the use of OpenSSL async job during the TLS handshake. Traffic diff --git a/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst b/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst index 37cdcb06f77..4835960c64b 100644 --- a/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst +++ b/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst @@ -168,6 +168,21 @@ HTTP/2 Represents the current number of HTTP/2 active connections from client to the |TS|. +.. ts:stat:: global proxy.process.http2.total_server_connections integer + :type: counter + + Represents the total number of HTTP/2 connections from |TS| to the origin. + +.. ts:stat:: global proxy.process.http2.current_server_connections integer + :type: gauge + + Represents the current number of HTTP/2 connections from |TS| to the origin. + +.. ts:stat:: global proxy.process.http2.current_active_server_connections integer + :type: gauge + + Represents the current number of HTTP/2 active connections from |TS| to the origin. + .. ts:stat:: global proxy.process.http2.connection_errors integer :type: counter diff --git a/doc/admin-guide/monitoring/statistics/core/http-transaction.en.rst b/doc/admin-guide/monitoring/statistics/core/http-transaction.en.rst index 07a6e60a0d8..9dd730c2be4 100644 --- a/doc/admin-guide/monitoring/statistics/core/http-transaction.en.rst +++ b/doc/admin-guide/monitoring/statistics/core/http-transaction.en.rst @@ -165,6 +165,16 @@ HTTP/2 Represents the current number of HTTP/2 streams from client to the |TS|. +.. ts:stat:: global proxy.process.http2.total_server_streams integer + :type: counter + + Represents the total number of HTTP/2 streams from |TS| to the origin. + +.. ts:stat:: global proxy.process.http2.current_server_streams integer + :type: gauge + + Represents the current number of HTTP/2 streams from |TS| to the origin. + .. ts:stat:: global proxy.process.http2.total_transactions_time integer :type: counter :units: seconds diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 9321dfe1794..8d3cc177711 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -884,6 +884,7 @@ typedef enum { TS_CONFIG_HTTP_CONNECT_DEAD_POLICY, TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX, TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK, + TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS, TS_CONFIG_LAST_ENTRY } TSOverridableConfigKey; diff --git a/include/tscore/ink_defs.h b/include/tscore/ink_defs.h index 35141368371..dc40bc812d3 100644 --- a/include/tscore/ink_defs.h +++ b/include/tscore/ink_defs.h @@ -110,6 +110,8 @@ countof(const T (&)[N]) #define MAP_SHARED_MAP_NORESERVE (MAP_SHARED) #endif +#define MAX_ALPN_STRING 30 + /* Variables */ extern int debug_level; diff --git a/iocore/eventsystem/I_EThread.h b/iocore/eventsystem/I_EThread.h index 49d74d896a3..3b15794713d 100644 --- a/iocore/eventsystem/I_EThread.h +++ b/iocore/eventsystem/I_EThread.h @@ -45,6 +45,7 @@ struct EventIO; class ServerSessionPool; class Event; class Continuation; +class ConnectingPool; enum ThreadType { REGULAR = 0, @@ -349,6 +350,7 @@ class EThread : public Thread Event *start_event = nullptr; ServerSessionPool *server_session_pool = nullptr; + ConnectingPool *connecting_pool = nullptr; /** Default handler used until it is overridden. diff --git a/iocore/eventsystem/I_Thread.h b/iocore/eventsystem/I_Thread.h index 8037d2a10c4..a7e3e0315fe 100644 --- a/iocore/eventsystem/I_Thread.h +++ b/iocore/eventsystem/I_Thread.h @@ -121,6 +121,7 @@ class Thread ProxyAllocator quicNetVCAllocator; ProxyAllocator http1ClientSessionAllocator; ProxyAllocator http2ClientSessionAllocator; + ProxyAllocator http2ServerSessionAllocator; ProxyAllocator http2StreamAllocator; ProxyAllocator httpSMAllocator; ProxyAllocator quicClientSessionAllocator; diff --git a/iocore/net/ALPNSupport.cc b/iocore/net/ALPNSupport.cc index 69b8b1c24d7..3d5832889e8 100644 --- a/iocore/net/ALPNSupport.cc +++ b/iocore/net/ALPNSupport.cc @@ -145,3 +145,36 @@ ALPNSupport::registerNextProtocolSet(SSLNextProtocolSet *s, const SessionProtoco this->npnSet = s; npnSet->create_npn_advertisement(protoenabled, &npn, &npnsz); } + +bool +ALPNSupport::process_alpn_protocols(const std::string_view protocols, unsigned char *client_alpn_protocols, int &alpn_array_len) +{ + // Count up the number of separators + int start = 0; + size_t offset = protocols.find(',', start); + int index = 0; + bool retval = true; + while (offset != protocols.npos) { + if ((index + 1 + static_cast(offset) - start) > alpn_array_len) { + retval = false; + break; + } + client_alpn_protocols[index++] = offset - start; + memcpy(client_alpn_protocols + index, protocols.data() + start, offset - start); + index += offset - start; + start = offset + 1; + offset = protocols.find(',', start); + } + // Copy in the last string + offset = protocols.length(); + if ((index + 1 + static_cast(offset) - start) > alpn_array_len) { + retval = false; + alpn_array_len = 0; + } else { + client_alpn_protocols[index++] = offset - start; + memcpy(client_alpn_protocols + index, protocols.data() + start, offset - start); + index += offset - start; + alpn_array_len = index; + } + return retval; +} diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h index 6ad2a0189dc..57fffb2fd50 100644 --- a/iocore/net/I_NetVConnection.h +++ b/iocore/net/I_NetVConnection.h @@ -225,6 +225,9 @@ struct NetVCOptions { bool tls_upstream = false; + unsigned char alpn_protocols_array[MAX_ALPN_STRING]; + int alpn_protocols_array_size = 0; + /// Reset all values to defaults. /** diff --git a/iocore/net/P_ALPNSupport.h b/iocore/net/P_ALPNSupport.h index fe398001eaf..c97aa226d79 100644 --- a/iocore/net/P_ALPNSupport.h +++ b/iocore/net/P_ALPNSupport.h @@ -45,6 +45,7 @@ class ALPNSupport void enableProtocol(int idx); void clear(); bool setSelectedProtocol(const unsigned char *proto, unsigned int len); + static bool process_alpn_protocols(const std::string_view protocols, unsigned char *alpn_array, int &alpn_array_len); int advertise_next_protocol(SSL *ssl, const unsigned char **out, unsigned *outlen); int select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned inlen); diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h index 262df7c9c58..56a7da2784d 100644 --- a/iocore/net/P_SSLConfig.h +++ b/iocore/net/P_SSLConfig.h @@ -101,6 +101,9 @@ struct SSLConfigParams : public ConfigInfo { long ssl_ctx_options; long ssl_client_ctx_options; + unsigned char alpn_protocols_array[MAX_ALPN_STRING]; + int alpn_protocols_array_size = 0; + char *server_tls13_cipher_suites; char *client_tls13_cipher_suites; char *server_groups_list; diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc index cecbb4592ed..a9e454bd8a3 100644 --- a/iocore/net/SSLConfig.cc +++ b/iocore/net/SSLConfig.cc @@ -255,6 +255,16 @@ SSLConfigParams::initialize() } #endif + // Read in the protocol string for ALPN to origin + char *clientALPNProtocols; + REC_ReadConfigStringAlloc(clientALPNProtocols, "proxy.config.ssl.client.alpn_protocols"); + + // Assume the protocols are comma delimited + if (clientALPNProtocols) { + this->alpn_protocols_array_size = MAX_ALPN_STRING; + ALPNSupport::process_alpn_protocols(clientALPNProtocols, this->alpn_protocols_array, this->alpn_protocols_array_size); + } + #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE REC_ReadConfigInteger(option, "proxy.config.ssl.server.honor_cipher_order"); if (option) { diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index da1b473f1b7..c40ce39ae8b 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -1142,6 +1142,16 @@ SSLNetVConnection::sslStartHandShake(int event, int &err) return EVENT_ERROR; } + // If it is negative, we are conscious not setting alpn (e.g. for private server sessions) + if (options.alpn_protocols_array_size >= 0) { + if (options.alpn_protocols_array_size > 0) { + SSL_set_alpn_protos(this->ssl, options.alpn_protocols_array, options.alpn_protocols_array_size); + } else if (params->alpn_protocols_array_size > 0) { + // Set the ALPN protocols we are requesting. + SSL_set_alpn_protos(this->ssl, params->alpn_protocols_array, params->alpn_protocols_array_size); + } + } + SSL_set_verify(this->ssl, SSL_VERIFY_PEER, verify_callback); // SNI @@ -1502,6 +1512,16 @@ SSLNetVConnection::sslClientHandShakeEvent(int &err) X509_free(cert); } } + { + const unsigned char *proto; + unsigned int len = 0; + // Make note of the negotiated protocol + SSL_get0_alpn_selected(ssl, &proto, &len); + if (len == 0) { + SSL_get0_next_proto_negotiated(ssl, &proto, &len); + } + this->set_negotiated_protocol_id({reinterpret_cast(proto), static_cast(len)}); + } // if the handshake is complete and write is enabled reschedule the write if (closed == 0 && write.enabled) { diff --git a/iocore/net/UnixNetVConnection.cc b/iocore/net/UnixNetVConnection.cc index 0bb3ecf5816..d193d92ecea 100644 --- a/iocore/net/UnixNetVConnection.cc +++ b/iocore/net/UnixNetVConnection.cc @@ -361,6 +361,7 @@ write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread) { NetState *s = &vc->write; ProxyMutex *mutex = thread->mutex.get(); + Continuation *c = vc->write.vio.cont; MUTEX_TRY_LOCK(lock, s->vio.mutex, thread); @@ -445,6 +446,9 @@ write_to_net_io(NetHandler *nh, UnixNetVConnection *vc, EThread *thread) if (towrite != ntodo && buf.writer()->write_avail()) { if (write_signal_and_update(VC_EVENT_WRITE_READY, vc) != EVENT_CONT) { return; + } else if (c != s->vio.cont) { /* The write vio was updated in the handler */ + write_reschedule(nh, vc); + return; } ntodo = s->vio.ntodo(); diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index 60233fedc82..7d8dcb2afa8 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -1128,6 +1128,8 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.ssl.CA.cert.filename", RECD_STRING, nullptr, RECU_RESTART_TS, RR_NULL, RECC_STR, "^[^[:space:]]*$", RECA_NULL} , + {RECT_CONFIG, "proxy.config.ssl.client.alpn_protocols", RECD_STRING, nullptr, RECU_RESTART_TS, RR_NULL, RECC_STR, "^[^[:space:]]*$", RECA_NULL} + , {RECT_CONFIG, "proxy.config.ssl.CA.cert.path", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , {RECT_CONFIG, "proxy.config.ssl.client.verify.server.policy", RECD_STRING, "ENFORCED", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} @@ -1328,6 +1330,8 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http2.no_activity_timeout_in", RECD_INT, "120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , + {RECT_CONFIG, "proxy.config.http2.no_activity_timeout_out", RECD_INT, "120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , {RECT_CONFIG, "proxy.config.http2.active_timeout_in", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , {RECT_CONFIG, "proxy.config.http2.push_diary_size", RECD_INT, "256", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} diff --git a/plugins/lua/ts_lua_http_config.c b/plugins/lua/ts_lua_http_config.c index c17eb8d321e..feeb958ac1d 100644 --- a/plugins/lua/ts_lua_http_config.c +++ b/plugins/lua/ts_lua_http_config.c @@ -138,6 +138,7 @@ typedef enum { TS_LUA_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE = TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE, TS_LUA_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX = TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX, TS_LUA_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK = TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK, + TS_LUA_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS = TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS, TS_LUA_CONFIG_LAST_ENTRY = TS_CONFIG_LAST_ENTRY, } TSLuaOverridableConfigKey; @@ -263,6 +264,7 @@ ts_lua_var_item ts_lua_http_config_vars[] = { TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME), TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME), TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE), + TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS), TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_HTTP_SERVER_MIN_KEEP_ALIVE_CONNS), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX), TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH), diff --git a/proxy/PoolableSession.h b/proxy/PoolableSession.h index d98f93c2540..114479fb373 100644 --- a/proxy/PoolableSession.h +++ b/proxy/PoolableSession.h @@ -85,6 +85,7 @@ class PoolableSession : public ProxySession bool is_private() const; virtual void set_netvc(NetVConnection *newvc); + virtual bool is_multiplexing() const; // Used to determine whether the session is for parent proxy // it is session to origin server @@ -237,3 +238,9 @@ PoolableSession::attach_hostname(const char *hostname) CryptoContext().hash_immediate(hostname_hash, (unsigned char *)hostname, strlen(hostname)); } } + +inline bool +PoolableSession::is_multiplexing() const +{ + return false; +} diff --git a/proxy/ProxySession.cc b/proxy/ProxySession.cc index 332b468ce9c..d11dae04d66 100644 --- a/proxy/ProxySession.cc +++ b/proxy/ProxySession.cc @@ -26,6 +26,8 @@ #include "ProxySession.h" #include "P_SSLNetVConnection.h" +std::map> ProtocolSessionCreateMap; + ProxySession::ProxySession() : VConnection(nullptr) {} ProxySession::ProxySession(NetVConnection *vc) : VConnection(nullptr), _vc(vc) {} @@ -312,3 +314,21 @@ ProxySession::support_sni() const { return _vc ? _vc->support_sni() : false; } + +PoolableSession * +ProxySession::protocol_creation(NetVConnection *netvc) +{ + // Figure out what protocol was negotiated + int proto_index = SessionProtocolNameRegistry::INVALID; + SSLNetVConnection *sslnetvc = dynamic_cast(netvc); + if (sslnetvc) { + proto_index = sslnetvc->get_negotiated_protocol_id(); + } + // No ALPN occurred. Assume it was HTTP/1.x and hope for the best + if (proto_index == SessionProtocolNameRegistry::INVALID) { + proto_index = TS_ALPN_PROTOCOL_INDEX_HTTP_1_1; + } + auto iter = ProtocolSessionCreateMap.find(proto_index); + ink_release_assert(iter != ProtocolSessionCreateMap.end()); + return iter->second(); +} diff --git a/proxy/ProxySession.h b/proxy/ProxySession.h index 4162813db4d..74060050a7d 100644 --- a/proxy/ProxySession.h +++ b/proxy/ProxySession.h @@ -162,6 +162,8 @@ class ProxySession : public VConnection, public PluginUserArgs return nullptr; } + static PoolableSession *protocol_creation(NetVConnection *netvc); + //////////////////// // Members diff --git a/proxy/ProxyTransaction.cc b/proxy/ProxyTransaction.cc index cb80b3cc2cc..a4d5cb6d83a 100644 --- a/proxy/ProxyTransaction.cc +++ b/proxy/ProxyTransaction.cc @@ -235,6 +235,34 @@ ProxyTransaction::get_version(HTTPHdr &hdr) const return hdr.version_get(); } +bool +ProxyTransaction::is_read_closed() const +{ + return false; +} + +bool +ProxyTransaction::expect_send_trailer() const +{ + return false; +} + +void +ProxyTransaction::set_expect_send_trailer() +{ +} + +bool +ProxyTransaction::expect_receive_trailer() const +{ + return false; +} + +void +ProxyTransaction::set_expect_receive_trailer() +{ +} + bool ProxyTransaction::allow_half_open() const { diff --git a/proxy/ProxyTransaction.h b/proxy/ProxyTransaction.h index 261af6829bd..cf6c3a2fa7c 100644 --- a/proxy/ProxyTransaction.h +++ b/proxy/ProxyTransaction.h @@ -49,6 +49,11 @@ class ProxyTransaction : public VConnection virtual void set_inactivity_timeout(ink_hrtime timeout_in); virtual void cancel_inactivity_timeout(); virtual void cancel_active_timeout(); + virtual bool is_read_closed() const; + virtual bool expect_send_trailer() const; + virtual void set_expect_send_trailer(); + virtual bool expect_receive_trailer() const; + virtual void set_expect_receive_trailer(); // Implement VConnection interface. VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = nullptr) override; diff --git a/proxy/hdrs/HdrToken.cc b/proxy/hdrs/HdrToken.cc index a4e161c4527..8702f863f1a 100644 --- a/proxy/hdrs/HdrToken.cc +++ b/proxy/hdrs/HdrToken.cc @@ -227,7 +227,10 @@ static HdrTokenFieldInfo _hdrtoken_strs_field_initializers[] = { {"Strict-Transport-Security", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HTIF_MULTVALS)}, {"Subject", MIME_SLOTID_NONE, MIME_PRESENCE_SUBJECT, HTIF_NONE}, {"Summary", MIME_SLOTID_NONE, MIME_PRESENCE_SUMMARY, HTIF_NONE}, - {"TE", MIME_SLOTID_TE, MIME_PRESENCE_TE, (HTIF_COMMAS | HTIF_MULTVALS | HTIF_HOPBYHOP)}, + // Need to figure out why this cannot be handled as hop by hop. If it is hop-by-hop + // the information is not propagated for gRPC + //{"TE", MIME_SLOTID_TE, MIME_PRESENCE_TE, (HTIF_COMMAS | HTIF_MULTVALS | HTIF_HOPBYHOP)}, + {"TE", MIME_SLOTID_TE, MIME_PRESENCE_TE, (HTIF_COMMAS | HTIF_MULTVALS)}, {"Transfer-Encoding", MIME_SLOTID_TRANSFER_ENCODING, MIME_PRESENCE_TRANSFER_ENCODING, (HTIF_COMMAS | HTIF_MULTVALS | HTIF_HOPBYHOP)}, {"Upgrade", MIME_SLOTID_NONE, MIME_PRESENCE_UPGRADE, (HTIF_COMMAS | HTIF_MULTVALS | HTIF_HOPBYHOP)}, diff --git a/proxy/http/Http1ServerSession.cc b/proxy/http/Http1ServerSession.cc index e0a9c82926f..5d64a372a4d 100644 --- a/proxy/http/Http1ServerSession.cc +++ b/proxy/http/Http1ServerSession.cc @@ -256,3 +256,7 @@ Http1ServerSession::new_transaction() trans.set_reader(this->get_remote_reader()); return &trans; } + +std::function Create_h1_server_session = []() -> PoolableSession * { + return httpServerSessionAllocator.alloc(); +}; diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index 20b907907cd..b5a4bf6f85f 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -720,6 +720,7 @@ struct OverridableHttpConfigParams { char *ssl_client_cert_filename = nullptr; char *ssl_client_private_key_filename = nullptr; char *ssl_client_ca_cert_filename = nullptr; + char *ssl_client_alpn_protocols = nullptr; // Host Resolution order HostResData host_res_data; diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc index 1a38a8bd259..597c8318749 100644 --- a/proxy/http/HttpProxyServerMain.cc +++ b/proxy/http/HttpProxyServerMain.cc @@ -49,6 +49,9 @@ HttpSessionAccept *plugin_http_accept = nullptr; HttpSessionAccept *plugin_http_transparent_accept = nullptr; +extern std::function Create_h1_server_session; +extern std::function Create_h2_server_session; +extern std::map> ProtocolSessionCreateMap; static SLL ssl_plugin_acceptors; static Ptr ssl_plugin_mutex; @@ -216,6 +219,9 @@ MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned if (port.m_session_protocol_preference.intersects(HTTP2_PROTOCOL_SET)) { probe->registerEndpoint(ProtocolProbeSessionAccept::PROTO_HTTP2, new Http2SessionAccept(accept_opt)); } + ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_1_0, Create_h1_server_session}); + ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_1_1, Create_h1_server_session}); + ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_2_0, Create_h2_server_session}); if (port.isSSL()) { SSLNextProtocolAccept *ssl = new SSLNextProtocolAccept(probe, port.m_transparent_passthrough); diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index 81ed57ec6bc..5a28974a901 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -28,6 +28,7 @@ #include "HttpTransactHeaders.h" #include "ProxyConfig.h" #include "Http1ServerSession.h" +#include "Http2ServerSession.h" #include "HttpDebugNames.h" #include "HttpSessionManager.h" #include "P_Cache.h" @@ -226,7 +227,6 @@ HttpVCTable::find_entry(VIO *vio) void HttpVCTable::remove_entry(HttpVCTableEntry *e) { - ink_assert(e->vc == nullptr || e->in_tunnel); e->vc = nullptr; e->eos = false; if (e->read_buffer) { @@ -258,18 +258,6 @@ HttpVCTable::cleanup_entry(HttpVCTableEntry *e) { ink_assert(e->vc); if (e->in_tunnel == false) { - // Update stats - switch (e->vc_type) { - case HTTP_UA_VC: - // proxy.process.http.current_client_transactions is decremented in HttpSM::destroy - break; - default: - // This covers: - // HTTP_UNKNOWN, HTTP_SERVER_VC, HTTP_TRANSFORM_VC, HTTP_CACHE_READ_VC, - // HTTP_CACHE_WRITE_VC, HTTP_RAW_SERVER_VC - break; - } - if (e->vc_type == HTTP_SERVER_VC) { HTTP_INCREMENT_DYN_STAT(http_origin_shutdown_cleanup_entry); } @@ -289,6 +277,160 @@ HttpVCTable::cleanup_all() } } +class ConnectingEntry : public Continuation +{ +public: + std::set _connect_sms; + NetVConnection *_netvc = nullptr; + IOBufferReader *_netvc_reader = nullptr; + MIOBuffer *_netvc_read_buffer = nullptr; + std::string sni; + std::string cert_name; + IpEndpoint _ipaddr; + std::string hostname; + Action *_pending_action = nullptr; + NetVCOptions opt; + + void remove_entry(); + int state_http_server_open(int event, void *data); + static PoolableSession *create_server_session(HttpSM *root_sm, NetVConnection *netvc, MIOBuffer *netvc_read_buffer, + IOBufferReader *netvc_reader); +}; + +struct IpHelper { + size_t + operator()(IpEndpoint const &arg) const + { + return IpAddr{&arg.sa}.hash(); + } + bool + operator()(IpEndpoint const &arg1, IpEndpoint const &arg2) const + { + return ats_ip_addr_port_eq(&arg1.sa, &arg2.sa); + } +}; +using ConnectingIpPool = std::unordered_multimap; + +class ConnectingPool +{ +public: + ConnectingPool() {} + ConnectingIpPool m_ip_pool; +}; + +void +initialize_thread_for_connecting_pools(EThread *thread) +{ + if (thread->connecting_pool == nullptr) { + thread->connecting_pool = new ConnectingPool(); + } +} + +int +ConnectingEntry::state_http_server_open(int event, void *data) +{ + Debug("http_connect", "entered inside ConnectingEntry::state_http_server_open"); + + switch (event) { + case NET_EVENT_OPEN: { + _netvc = static_cast(data); + UnixNetVConnection *vc = static_cast(_netvc); + ink_release_assert(_pending_action == nullptr || _pending_action->continuation == vc->get_action()->continuation); + _pending_action = nullptr; + Debug("http_connect", "ConnectingEntrysetting handler for TCP handshake"); + // Just want to get a write-ready event so we know that the TCP handshake is complete. + // The buffer we create will be handed over to the eventually created server session + _netvc_read_buffer = new_MIOBuffer(HTTP_SERVER_RESP_HDR_BUFFER_INDEX); + _netvc_reader = _netvc_read_buffer->alloc_reader(); + _netvc->do_io_write(this, 1, _netvc_reader); + ink_release_assert(!_connect_sms.empty()); + if (!_connect_sms.empty()) { + HttpSM *prime_connect_sm = *(_connect_sms.begin()); + _netvc->set_inactivity_timeout(prime_connect_sm->get_server_connect_timeout()); + } + ink_release_assert(_pending_action == nullptr); + return 0; + } + case VC_EVENT_READ_COMPLETE: + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: { + Debug("http_connect", "Kick off %" PRId64 " state machines waiting for origin", _connect_sms.size()); + this->remove_entry(); + _netvc->do_io_write(nullptr, 0, nullptr); + if (!_connect_sms.empty()) { + auto prime_iter = _connect_sms.rbegin(); + ink_release_assert(prime_iter != _connect_sms.rend()); + PoolableSession *new_session = (*prime_iter)->create_server_session(_netvc, _netvc_read_buffer, _netvc_reader); + _netvc = nullptr; + + // Did we end up with a multiplexing session? + int count = 0; + if (new_session->is_multiplexing()) { + // Hand off to all queued up ConnectSM's. + while (!_connect_sms.empty()) { + Debug("http_connect", "ConnectingEntry Pass along CONNECT_EVENT_TXN %d", count++); + auto entry = _connect_sms.begin(); + + SCOPED_MUTEX_LOCK(lock, (*entry)->mutex, this_ethread()); + (*entry)->handleEvent(CONNECT_EVENT_TXN, new_session); + _connect_sms.erase(entry); + } + } else { + // Hand off to one and tell all of the others to connect directly + Debug("http_connect", "ConnectingEntry send CONNECT_EVENT_TXN to first %d", count++); + { + SCOPED_MUTEX_LOCK(lock, (*prime_iter)->mutex, this_ethread()); + (*prime_iter)->handleEvent(CONNECT_EVENT_TXN, new_session); + _connect_sms.erase((++prime_iter).base()); + } + while (!_connect_sms.empty()) { + auto entry = _connect_sms.begin(); + Debug("http_connect", "ConnectingEntry Pass along CONNECT_EVENT_DIRECT %d", count++); + SCOPED_MUTEX_LOCK(lock, (*entry)->mutex, this_ethread()); + (*entry)->handleEvent(CONNECT_EVENT_DIRECT, nullptr); + _connect_sms.erase(entry); + } + } + } else { + ink_release_assert(!"There should be some sms on the connect_entry"); + } + delete this; + + // ConnectingEntry should remove itself from the tables and delete itself + return 0; + } + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + case NET_EVENT_OPEN_FAILED: { + ink_release_assert(_netvc != nullptr); + Debug("http_connect", "Stop %" PRId64 " state machines waiting for failed origin", _connect_sms.size()); + this->remove_entry(); + int vc_provided_cert = _netvc->provided_cert(); + int lerrno = _netvc->lerrno; + _netvc->do_io_close(); + while (!_connect_sms.empty()) { + auto entry = _connect_sms.begin(); + SCOPED_MUTEX_LOCK(lock, (*entry)->mutex, this_ethread()); + (*entry)->t_state.set_connect_fail(lerrno); + (*entry)->server_connection_provided_cert = vc_provided_cert; + (*entry)->handleEvent(event, data); + _connect_sms.erase(entry); + } + // ConnectingEntry should remove itself from the tables and delete itself + delete this; + + return 0; + } + default: + Error("[ConnectingEntry::state_http_server_open] Unknown event: %d", event); + ink_release_assert(0); + return 0; + } + + return 0; +} + #define SMDebug(tag, ...) SpecificDebug(debug_on, tag, __VA_ARGS__) #define REMEMBER(e, r) \ @@ -387,6 +529,8 @@ HttpSM::init(bool from_early_data) magic = HTTP_SM_MAGIC_ALIVE; + server_txn = nullptr; + // Unique state machine identifier sm_id = next_sm_id++; t_state.state_machine_id = sm_id; @@ -617,9 +761,7 @@ HttpSM::attach_client_session(ProxyTransaction *client_vc) // this hook maybe asynchronous, we need to disable IO on // client but set the continuation to be the state machine // so if we get an timeout events the sm handles them - // hold onto enabling read until setup_client_read_request_header - ua_entry->read_vio = client_vc->do_io_read(this, 0, nullptr); - ua_entry->write_vio = client_vc->do_io_write(this, 0, nullptr); + ua_entry->read_vio = client_vc->do_io_read(this, 0, ua_txn->get_remote_reader()->mbuf); ///////////////////////// // set up timeouts // @@ -805,7 +947,8 @@ HttpSM::state_read_client_request_header(int event, void *data) ua_raw_buffer_reader = nullptr; } http_parser_clear(&http_parser); - ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; + ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; + ua_txn->cancel_inactivity_timeout(); milestones[TS_MILESTONE_UA_READ_HEADER_DONE] = Thread::get_hrtime(); } @@ -992,20 +1135,25 @@ HttpSM::state_watch_for_client_abort(int event, void *data) * client. */ case VC_EVENT_EOS: { - // We got an early EOS. If the tunnal has cache writer, don't kill it for background fill. - NetVConnection *netvc = ua_txn->get_netvc(); - if (ua_txn->allow_half_open() || tunnel.has_consumer_besides_client()) { - if (netvc) { - netvc->do_io_shutdown(IO_SHUTDOWN_READ); + // We got an early EOS. + if (!terminate_sm) { // Not done already + NetVConnection *netvc = ua_txn->get_netvc(); + if (ua_txn->allow_half_open() || tunnel.has_consumer_besides_client()) { + if (netvc) { + netvc->do_io_shutdown(IO_SHUTDOWN_READ); + } + } else { + ua_txn->do_io_close(); + vc_table.cleanup_entry(ua_entry); + ink_release_assert(vc_table.find_entry(ua_txn) == nullptr); + ua_entry = nullptr; + tunnel.kill_tunnel(); + terminate_sm = true; // Just die already, the requester is gone + set_ua_abort(HttpTransact::ABORTED, event); + } + if (ua_entry) { + ua_entry->eos = true; } - ua_entry->eos = true; - } else { - ua_txn->do_io_close(); - vc_table.cleanup_entry(ua_entry); - ua_entry = nullptr; - tunnel.kill_tunnel(); - terminate_sm = true; // Just die already, the requester is gone - set_ua_abort(HttpTransact::ABORTED, event); } break; } @@ -1220,14 +1368,6 @@ HttpSM::state_raw_http_server_open(int event, void *data) pending_action = nullptr; switch (event) { - case EVENT_INTERVAL: - // If we get EVENT_INTERNAL it means that we moved the transaction - // to a different thread in do_http_server_open. Since we didn't - // do any of the actual work in do_http_server_open, we have to - // go back and do it now. - do_http_server_open(true); - return 0; - case NET_EVENT_OPEN: // Record the VC in our table @@ -1544,7 +1684,7 @@ plugins required to work with sni_routing. api_timer = -Thread::get_hrtime_updated(); HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_api_callout); ink_release_assert(pending_action.is_empty()); - pending_action = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(10)); + pending_action = this_ethread()->schedule_in(this, HRTIME_MSECONDS(10)); return -1; } @@ -1620,6 +1760,10 @@ plugins required to work with sni_routing. } break; + // Eat the EOS while we are waiting for any locks to complete the transaction + case VC_EVENT_EOS: + return 0; + default: ink_assert(false); terminate_sm = true; @@ -1813,32 +1957,29 @@ HttpSM::handle_api_return() } PoolableSession * -HttpSM::create_server_session(NetVConnection *netvc) +HttpSM::create_server_session(NetVConnection *netvc, MIOBuffer *netvc_read_buffer, IOBufferReader *netvc_reader) { - HttpTransact::State &s = this->t_state; - PoolableSession *retval = httpServerSessionAllocator.alloc(); + PoolableSession *retval = ProxySession::protocol_creation(netvc); - retval->sharing_pool = static_cast(s.http_config_param->server_session_sharing_pool); - retval->sharing_match = static_cast(s.txn_conf->server_session_sharing_match); - MIOBuffer *netvc_read_buffer = new_MIOBuffer(HTTP_SERVER_RESP_HDR_BUFFER_INDEX); - IOBufferReader *netvc_reader = netvc_read_buffer->alloc_reader(); + retval->sharing_pool = static_cast(t_state.http_config_param->server_session_sharing_pool); + retval->sharing_match = static_cast(t_state.txn_conf->server_session_sharing_match); retval->new_connection(netvc, netvc_read_buffer, netvc_reader); + retval->attach_hostname(t_state.current.server->name); - retval->attach_hostname(s.current.server->name); - - ATS_PROBE1(new_origin_server_connection, s.current.server->name); + ATS_PROBE1(new_origin_server_connection, t_state.current.server->name); retval->set_active(); if (netvc) { - ats_ip_copy(&s.server_info.src_addr, netvc->get_local_addr()); + ats_ip_copy(&t_state.server_info.src_addr, netvc->get_local_addr()); } // If origin_max_connections or origin_min_keep_alive_connections is set then we are metering // the max and or min number of connections per host. Transfer responsibility for this to the // session object. - if (s.outbound_conn_track_state.is_active()) { - Debug("http_connect", "[%" PRId64 "] max number of outbound connections: %d", this->sm_id, s.txn_conf->outbound_conntrack.max); - retval->enable_outbound_connection_tracking(s.outbound_conn_track_state.drop()); + if (t_state.outbound_conn_track_state.is_active()) { + Debug("http_connect", "[%" PRId64 "] max number of outbound connections: %d", this->sm_id, + t_state.txn_conf->outbound_conntrack.max); + retval->enable_outbound_connection_tracking(t_state.outbound_conn_track_state.drop()); } return retval; } @@ -1846,14 +1987,26 @@ HttpSM::create_server_session(NetVConnection *netvc) bool HttpSM::create_server_txn(PoolableSession *new_session) { + ink_assert(new_session != nullptr); bool retval = false; - server_txn = new_session->new_transaction(); - if (server_txn != nullptr) { + + server_txn = new_session->new_transaction(); + if (server_txn) { + retval = true; server_txn->attach_transaction(this); + if (t_state.current.request_to == HttpTransact::PARENT_PROXY) { + new_session->to_parent_proxy = true; + HTTP_INCREMENT_DYN_STAT(http_current_parent_proxy_connections_stat); + HTTP_INCREMENT_DYN_STAT(http_total_parent_proxy_connections_stat); + } else { + new_session->to_parent_proxy = false; + } server_txn->do_io_write(this, 0, nullptr); attach_server_session(); - retval = true; } + _netvc = nullptr; + _netvc_read_buffer = nullptr; + _netvc_reader = nullptr; return retval; } @@ -1876,80 +2029,76 @@ HttpSM::state_http_server_open(int event, void *data) switch (event) { case NET_EVENT_OPEN: { - NetVConnection *netvc = static_cast(data); - UnixNetVConnection *vc = static_cast(data); - PoolableSession *new_session = this->create_server_session(netvc); - if (t_state.current.request_to == HttpTransact::PARENT_PROXY) { - new_session->to_parent_proxy = true; - HTTP_INCREMENT_DYN_STAT(http_current_parent_proxy_connections_stat); - HTTP_INCREMENT_DYN_STAT(http_total_parent_proxy_connections_stat); - } else { - new_session->to_parent_proxy = false; - } - this->create_server_txn(new_session); - // Since the UnixNetVConnection::action_ or SocksEntry::action_ may be returned from netProcessor.connect_re, and the - // SocksEntry::action_ will be copied into UnixNetVConnection::action_ before call back NET_EVENT_OPEN from SocksEntry::free(), - // so we just compare the Continuation between pending_action and VC's action_. + // SocksEntry::action_ will be copied into UnixNetVConnection::action_ before call back NET_EVENT_OPEN from + // SocksEntry::free(), so we just compare the Continuation between pending_action and VC's action_. + _netvc = static_cast(data); + _netvc_read_buffer = new_MIOBuffer(HTTP_SERVER_RESP_HDR_BUFFER_INDEX); + _netvc_reader = _netvc_read_buffer->alloc_reader(); + UnixNetVConnection *vc = static_cast(_netvc); ink_release_assert(pending_action.is_empty() || pending_action.get_continuation() == vc->get_action()->continuation); pending_action = nullptr; if (this->plugin_tunnel_type == HTTP_NO_PLUGIN_TUNNEL) { - SMDebug("http", "[%" PRId64 "] setting handler for TCP handshake", sm_id); + SMDebug("http_connect", "[%" PRId64 "] setting handler for TCP handshake timeout %" PRId64, this->sm_id, + this->get_server_connect_timeout()); // Just want to get a write-ready event so we know that the TCP handshake is complete. - server_entry->vc_handler = &HttpSM::state_http_server_open; - - int64_t nbytes = 1; - if (t_state.txn_conf->proxy_protocol_out >= 0) { - nbytes = do_outbound_proxy_protocol(server_txn->get_remote_reader()->mbuf, vc, ua_txn->get_netvc(), - t_state.txn_conf->proxy_protocol_out); - } - - server_entry->write_vio = server_txn->do_io_write(this, nbytes, server_txn->get_remote_reader()); + // The buffer we create will be handed over to the eventually created server session + _netvc->do_io_write(this, 1, _netvc_reader); + _netvc->set_inactivity_timeout(this->get_server_connect_timeout()); // Pre-emptively set a server connect failure that will be cleared once a WRITE_READY is received from origin or // bytes are received back t_state.set_connect_fail(EIO); } else { // in the case of an intercept plugin don't to the connect timeout change - SMDebug("http", "[%" PRId64 "] not setting handler for TCP handshake", sm_id); + Debug("http_connect", "[%" PRId64 "] not setting handler for TCP handshake", this->sm_id); + this->create_server_txn(this->create_server_session(_netvc, _netvc_read_buffer, _netvc_reader)); handle_http_server_open(); } - + ink_assert(pending_action.is_empty()); return 0; } + case CONNECT_EVENT_DIRECT: + // Try it again, but direct this time + do_http_server_open(false, true); + break; + case CONNECT_EVENT_TXN: + Debug("http", "[%" PRId64 "] TCP Handshake complete via CONNECT_EVENT_TXN", sm_id); + if (this->create_server_txn(static_cast(data))) { + write_outbound_proxy_protocol(); + handle_http_server_open(); + } else { // Failed to create transaction. Maybe too many active transactions already + // Try again (probably need a bounding counter here) + do_http_server_open(false); + } + return 0; case VC_EVENT_READ_COMPLETE: case VC_EVENT_WRITE_READY: case VC_EVENT_WRITE_COMPLETE: // Update the time out to the regular connection timeout. SMDebug("http_ss", "[%" PRId64 "] TCP Handshake complete", sm_id); - server_entry->vc_handler = &HttpSM::state_send_server_request_header; - - // Reset the timeout to the non-connect timeout - server_txn->set_inactivity_timeout(get_server_inactivity_timeout()); + this->create_server_txn(this->create_server_session(_netvc, _netvc_read_buffer, _netvc_reader)); + write_outbound_proxy_protocol(); t_state.current.server->clear_connect_fail(); handle_http_server_open(); return 0; - case EVENT_INTERVAL: // Delayed call from another thread - if (server_txn == nullptr) { - do_http_server_open(); - } - break; case VC_EVENT_INACTIVITY_TIMEOUT: case VC_EVENT_ACTIVE_TIMEOUT: t_state.set_connect_fail(ETIMEDOUT); /* fallthrough */ case VC_EVENT_ERROR: case NET_EVENT_OPEN_FAILED: { - if (server_txn) { - NetVConnection *vc = server_txn->get_netvc(); - if (vc) { - t_state.set_connect_fail(vc->lerrno); - server_connection_provided_cert = vc->provided_cert(); - } - } - t_state.current.state = HttpTransact::CONNECTION_ERROR; t_state.outbound_conn_track_state.clear(); + if (_netvc != nullptr) { + if (event == VC_EVENT_ERROR || event == NET_EVENT_OPEN_FAILED) { + t_state.set_connect_fail(_netvc->lerrno); + } + this->server_connection_provided_cert = _netvc->provided_cert(); + _netvc->do_io_write(nullptr, 0, nullptr); + _netvc->do_io_close(); + _netvc = nullptr; + } /* If we get this error in transparent mode, then we simply can't bind to the 4-tuple to make the connection. There's no hope of retries succeeding in the near future. The best option is to just shut down the connection without further comment. The @@ -2243,6 +2392,103 @@ HttpSM::state_send_server_request_header(int event, void *data) return 0; } +bool +HttpSM::origin_multiplexed() const +{ + return (t_state.host_db_info.app.http_data.http_version == HTTP_2_0 || + t_state.host_db_info.app.http_data.http_version == HTTP_INVALID); +} + +void +ConnectingEntry::remove_entry() +{ + EThread *ethread = this_ethread(); + auto ip_iter = ethread->connecting_pool->m_ip_pool.find(this->_ipaddr); + while (ip_iter != ethread->connecting_pool->m_ip_pool.end() && this->_ipaddr == ip_iter->first) { + if (ip_iter->second == this) { + ethread->connecting_pool->m_ip_pool.erase(ip_iter); + break; + } + ++ip_iter; + } +} + +void +HttpSM::cancel_pending_server_connection() +{ + EThread *ethread = this_ethread(); + if (nullptr == ethread->connecting_pool) { + return; // No pending requests + } + if (t_state.current.server) { + IpEndpoint ip; + ip.assign(&this->t_state.current.server->dst_addr.sa); + auto ip_iter = ethread->connecting_pool->m_ip_pool.find(ip); + while (ip_iter != ethread->connecting_pool->m_ip_pool.end() && ip_iter->first == ip) { + ConnectingEntry *connecting_entry = ip_iter->second; + // Found a match + // Look for our sm in the queue + auto entry = connecting_entry->_connect_sms.find(this); + if (entry != connecting_entry->_connect_sms.end()) { + connecting_entry->_connect_sms.erase(entry); + if (connecting_entry->_connect_sms.empty()) { + if (connecting_entry->_netvc) { + connecting_entry->_netvc->do_io_write(nullptr, 0, nullptr); + connecting_entry->_netvc->do_io_close(); + } + ethread->connecting_pool->m_ip_pool.erase(ip_iter); + delete connecting_entry; + break; + } else { + // Leave the shared entry remaining alone + } + } + ++ip_iter; + } + } +} + +// Returns true if there was a matching entry that we +// queued this request on +bool +HttpSM::add_to_existing_request() +{ + HttpTransact::State &s = this->t_state; + bool retval = false; + EThread *ethread = this_ethread(); + + if (this->plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL) { + return false; + } + + if (nullptr == ethread->connecting_pool) { + initialize_thread_for_connecting_pools(ethread); + } + auto my_nh = ((UnixNetVConnection *)(this)->ua_txn->get_netvc())->nh; + ink_release_assert(my_nh == nullptr /* PluginVC */ || my_nh == get_NetHandler(this_ethread())); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_http_server_open); + + IpEndpoint ip; + ip.assign(&s.current.server->dst_addr.sa); + auto ip_iter = ethread->connecting_pool->m_ip_pool.find(ip); + std::string_view proposed_sni = this->get_outbound_sni(); + std::string_view proposed_cert = this->get_outbound_cert(); + std::string_view proposed_hostname = this->t_state.current.server->name; + while (!retval && ip_iter != ethread->connecting_pool->m_ip_pool.end() && ip_iter->first == ip) { + // Check that entry matches sni, hostname, and cert + if (proposed_hostname == ip_iter->second->hostname && proposed_sni == ip_iter->second->sni && + proposed_cert == ip_iter->second->cert_name && ip_iter->second->_connect_sms.size() < 50) { + ip_iter->second->_connect_sms.insert(this); + Debug("http_connect", "Add entry to connection queue. size=%" PRId64, ip_iter->second->_connect_sms.size()); + retval = true; + break; + } + ++ip_iter; + } + return retval; +} + void HttpSM::process_srv_info(HostDBInfo *r) { @@ -2382,11 +2628,6 @@ int HttpSM::state_hostdb_lookup(int event, void *data) { STATE_ENTER(&HttpSM::state_hostdb_lookup, event); - // ink_assert (m_origin_server_vc == 0); - // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed into - // REQ_FLAVOR_REVPROXY - ink_assert(t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || - t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || ua_entry->vc != nullptr); switch (event) { case EVENT_HOST_DB_LOOKUP: @@ -2417,7 +2658,6 @@ HttpSM::state_hostdb_lookup(int event, void *data) default: ink_assert(!"Unexpected event"); } - return 0; } @@ -2923,6 +3163,50 @@ HttpSM::tunnel_handler_post(int event, void *data) return 0; } +int +HttpSM::tunnel_handler_trailer(int event, void *data) +{ + STATE_ENTER(&HttpSM::tunnel_handler_trailer, event); + + switch (event) { + case HTTP_TUNNEL_EVENT_DONE: // Response tunnel done. + break; + + default: + // If the response tunnel did not succeed, just clean up as in the default case + return tunnel_handler(event, data); + } + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + + // Set up a new tunnel to transport the trailing header to the UA + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + MIOBuffer *trailer_buffer = new_MIOBuffer(HTTP_HEADER_BUFFER_SIZE_INDEX); + IOBufferReader *buf_start = trailer_buffer->alloc_reader(); + + size_t nbytes = INT64_MAX; + int start_bytes = trailer_buffer->write(server_txn->get_remote_reader(), server_txn->get_remote_reader()->read_avail()); + server_txn->get_remote_reader()->consume(start_bytes); + // The server has already sent all it has + if (server_txn->is_read_closed()) { + nbytes = start_bytes; + } + // Signal the ua_txn to get ready for a trailer + ua_txn->set_expect_send_trailer(); + tunnel.reset(); + HttpTunnelProducer *p = tunnel.add_producer(server_entry->vc, nbytes, buf_start, &HttpSM::tunnel_handler_trailer_server, + HT_HTTP_SERVER, "http server trailer"); + tunnel.add_consumer(ua_entry->vc, server_entry->vc, &HttpSM::tunnel_handler_trailer_ua, HT_HTTP_CLIENT, "user agent trailer"); + + ua_entry->in_tunnel = true; + server_entry->in_tunnel = true; + + tunnel.tunnel_run(p); + + return 0; +} + int HttpSM::tunnel_handler_cache_fill(int event, void *data) { @@ -2933,12 +3217,31 @@ HttpSM::tunnel_handler_cache_fill(int event, void *data) ink_release_assert(cache_sm.cache_write_vc); - tunnel.deallocate_buffers(); - this->postbuf_clear(); - tunnel.reset(); + int64_t alloc_index = find_server_buffer_size(); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); - setup_server_transfer_to_cache_only(); - tunnel.tunnel_run(); + TunnelChunkingAction_t action = + (t_state.current.server && t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) ? + TCA_DECHUNK_CONTENT : + TCA_PASSTHRU_DECHUNKED_CONTENT; + + int64_t nbytes = server_transfer_init(buf, 0); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + server_entry->vc = server_txn; + HttpTunnelProducer *p = + tunnel.add_producer(server_entry->vc, nbytes, buf_start, &HttpSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); + + tunnel.set_producer_chunking_action(p, 0, action); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + + setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, 0, "cache write"); + + server_entry->in_tunnel = true; + // Kick off the new producer + tunnel.tunnel_run(p); return 0; } @@ -3193,6 +3496,13 @@ HttpSM::tunnel_handler_server(int event, HttpTunnelProducer *p) tunnel.local_finish_all(p); } } + if (server_txn->expect_receive_trailer()) { + SMDebug("http", "[%" PRId64 "] wait for that trailing header", sm_id); + // Swap out the default hander to set up the new tunnel for the trailer exchange. + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler_trailer); + tunnel.local_finish_all(p); + return 0; + } break; case HTTP_TUNNEL_EVENT_CONSUMER_DETACH: @@ -3268,6 +3578,84 @@ HttpSM::tunnel_handler_server(int event, HttpTunnelProducer *p) return 0; } +int +HttpSM::tunnel_handler_trailer_server(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&HttpSM::tunnel_handler_trailer_server, event); + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + t_state.squid_codes.log_code = SQUID_LOG_ERR_READ_TIMEOUT; + t_state.squid_codes.hier_code = SQUID_HIER_TIMEOUT_DIRECT; + /* fallthru */ + + case VC_EVENT_EOS: + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + t_state.current.server->state = HttpTransact::INACTIVE_TIMEOUT; + break; + case VC_EVENT_ACTIVE_TIMEOUT: + t_state.current.server->state = HttpTransact::ACTIVE_TIMEOUT; + break; + case VC_EVENT_ERROR: + t_state.current.server->state = HttpTransact::CONNECTION_ERROR; + break; + case VC_EVENT_EOS: + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + break; + } + + ink_assert(p->vc_type == HT_HTTP_SERVER); + + SMDebug("http", "[%" PRId64 "] [HttpSM::tunnel_handler_server] aborting HTTP tunnel due to server truncation", sm_id); + tunnel.chain_abort_all(p); + + t_state.current.server->abort = HttpTransact::ABORTED; + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + t_state.squid_codes.log_code = SQUID_LOG_ERR_READ_ERROR; + break; + + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + case VC_EVENT_READ_COMPLETE: + // + // The transfer completed successfully + p->read_success = true; + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + t_state.current.server->abort = HttpTransact::DIDNOT_ABORT; + break; + + case HTTP_TUNNEL_EVENT_CONSUMER_DETACH: + case VC_EVENT_READ_READY: + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + // We handled the event. Now either shutdown server transaction + ink_assert(server_entry->vc == p->vc); + ink_assert(p->vc_type == HT_HTTP_SERVER); + ink_assert(p->vc == server_txn); + + // The server session has been released. Clean all pointer + // Calling remove_entry instead of server_entry because we don't + // want to close the server VC at this point + vc_table.remove_entry(server_entry); + + p->vc->do_io_close(); + p->read_vio = nullptr; + + server_entry = nullptr; + + return 0; +} + // int HttpSM::tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer* c) // // Used for tunneling the 100 continue response. The tunnel @@ -3289,6 +3677,7 @@ HttpSM::tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer *c) case VC_EVENT_ACTIVE_TIMEOUT: case VC_EVENT_ERROR: set_ua_abort(HttpTransact::ABORTED, event); + vc_table.remove_entry(ua_entry); c->vc->do_io_close(); break; case VC_EVENT_WRITE_COMPLETE: @@ -3387,13 +3776,13 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer *c) HTTP_INCREMENT_DYN_STAT(http_background_fill_current_count_stat); HTTP_INCREMENT_DYN_STAT(http_background_fill_total_count_stat); - ink_assert(server_entry->vc == server_txn); ink_assert(c->is_downstream_from(server_txn)); server_txn->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->background_fill_active_timeout)); } // Even with the background fill, the client side should go down c->write_vio = nullptr; + vc_table.remove_entry(ua_entry); c->vc->do_io_close(EHTTP_ERROR); c->alive = false; @@ -3462,8 +3851,9 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer *c) break; } - ink_assert(ua_entry->vc == c->vc); - if (close_connection) { + if (event == VC_EVENT_WRITE_COMPLETE && server_txn && server_txn->expect_receive_trailer()) { + // Don't shutdown if we are still expecting a trailer + } else if (close_connection) { // If the client could be pipelining or is doing a POST, we need to // set the ua_txn into half close mode @@ -3475,6 +3865,7 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer *c) } vc_table.remove_entry(this->ua_entry); + ink_release_assert(vc_table.find_entry(ua_txn) == nullptr); ua_txn->do_io_close(); } else { ink_assert(ua_txn->get_remote_reader() != nullptr); @@ -3485,6 +3876,66 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer *c) return 0; } +int +HttpSM::tunnel_handler_trailer_ua(int event, HttpTunnelConsumer *c) +{ + HttpTunnelProducer *p = nullptr; + HttpTunnelConsumer *selfc = nullptr; + + STATE_ENTER(&HttpSM::tunnel_handler_trailer_ua, event); + ink_assert(c->vc == ua_txn); + milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime(); + + switch (event) { + case VC_EVENT_EOS: + ua_entry->eos = true; + + // FALL-THROUGH + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + + // The user agent died or aborted. Check to + // see if we should setup a background fill + set_ua_abort(HttpTransact::ABORTED, event); + + // Should not be processing trailer headers in the background fill case + ink_assert(!is_bg_fill_necessary(c)); + p = c->producer; + tunnel.chain_abort_all(c->producer); + selfc = p->self_consumer; + if (selfc) { + // This is the case where there is a transformation between ua and os + p = selfc->producer; + // if producer is the cache or OS, close the producer. + // Otherwise in case of large docs, producer iobuffer gets filled up, + // waiting for a consumer to consume data and the connection is never closed. + if (p->alive && ((p->vc_type == HT_CACHE_READ) || (p->vc_type == HT_HTTP_SERVER))) { + tunnel.chain_abort_all(p); + } + } + break; + + case VC_EVENT_WRITE_COMPLETE: + c->write_success = true; + t_state.client_info.abort = HttpTransact::DIDNOT_ABORT; + break; + case VC_EVENT_WRITE_READY: + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + ink_assert(ua_entry->vc == c->vc); + vc_table.remove_entry(this->ua_entry); + ua_txn->do_io_close(); + ink_release_assert(vc_table.find_entry(ua_txn) == nullptr); + return 0; +} + int HttpSM::tunnel_handler_ua_push(int event, HttpTunnelProducer *p) { @@ -3737,6 +4188,18 @@ HttpSM::tunnel_handler_post_server(int event, HttpTunnelConsumer *c) server_request_body_bytes = c->bytes_written; } + // We may be reading from a transform. In that case, we + // want to close the transform + if (c->producer->vc_type == HT_TRANSFORM) { + if (c->producer->handler_state == HTTP_SM_TRANSFORM_OPEN) { + ink_assert(c->producer->vc == post_transform_info.vc); + c->producer->vc->do_io_close(); + c->producer->alive = false; + c->producer->self_consumer->alive = false; + ink_release_assert(vc_table.find_entry(c->producer->vc) == nullptr); + } + } + switch (event) { case VC_EVENT_EOS: case VC_EVENT_ERROR: @@ -4966,7 +5429,7 @@ HttpSM::get_outbound_sni() const // ////////////////////////////////////////////////////////////////////////// void -HttpSM::do_http_server_open(bool raw) +HttpSM::do_http_server_open(bool raw, bool only_direct) { int ip_family = t_state.current.server->dst_addr.sa.sa_family; auto fam_name = ats_ip_family_name(ip_family); @@ -5111,6 +5574,7 @@ HttpSM::do_http_server_open(bool raw) (t_state.txn_conf->keep_alive_post_out == 1 || t_state.hdr_info.request_content_length <= 0) && !is_private() && ua_txn != nullptr) { HSMresult_t shared_result; + SMDebug("http_ss", "Try to acquire_session for %s", t_state.current.server->name); shared_result = httpSessionManager.acquire_session(this, // state machine &t_state.current.server->dst_addr.sa, // ip + port t_state.current.server->name, // hostname @@ -5190,6 +5654,18 @@ HttpSM::do_http_server_open(bool raw) ink_release_assert(ua_txn == nullptr); } } + + bool multiplexed_origin = !only_direct && !raw && this->origin_multiplexed() && !is_private(); + if (multiplexed_origin) { + Debug("http_ss", "Check for existing connect request"); + if (this->add_to_existing_request()) { + Debug("http_ss", "Queue behind existing request"); + // We are queued up behind an existing connect request + // Go away and wait. + return; + } + } + // Check to see if we have reached the max number of connections. // Atomically read the current number of connections and check to see // if we have gone above the max allowed. @@ -5313,7 +5789,38 @@ HttpSM::do_http_server_open(bool raw) opt.set_ssl_client_cert_name(t_state.txn_conf->ssl_client_cert_filename); opt.ssl_client_private_key_name = t_state.txn_conf->ssl_client_private_key_filename; opt.ssl_client_ca_cert_name = t_state.txn_conf->ssl_client_ca_cert_filename; - + if (is_private()) { + // If the connection to origin is private, don't try the negotiate the higher overhead H2 + opt.alpn_protocols_array_size = -1; + Debug("connect", "Clear alpn for private session"); + } else if (t_state.txn_conf->ssl_client_alpn_protocols != nullptr) { + opt.alpn_protocols_array_size = MAX_ALPN_STRING; + Debug("connect", "Override alpn %s", t_state.txn_conf->ssl_client_alpn_protocols); + ALPNSupport::process_alpn_protocols(t_state.txn_conf->ssl_client_alpn_protocols, opt.alpn_protocols_array, + opt.alpn_protocols_array_size); + } + + ConnectingEntry *new_entry = nullptr; + if (multiplexed_origin) { + EThread *ethread = this_ethread(); + if (nullptr != ethread->connecting_pool) { + Debug("http_ss", "Queue multiplexed request"); + new_entry = new ConnectingEntry(); + new_entry->mutex = this->mutex; + new_entry->handler = (ContinuationHandler)&ConnectingEntry::state_http_server_open; + new_entry->_ipaddr.assign(&t_state.current.server->dst_addr.sa); + new_entry->hostname = t_state.current.server->name; + new_entry->sni = this->get_outbound_sni(); + new_entry->cert_name = this->get_outbound_cert(); + new_entry->_connect_sms.insert(this); + ethread->connecting_pool->m_ip_pool.insert(std::make_pair(new_entry->_ipaddr, new_entry)); + } + } + + Continuation *cont = new_entry; + if (!cont) { + cont = this; + } if (tls_upstream) { SMDebug("http", "calling sslNetProcessor.connect_re"); @@ -5334,12 +5841,12 @@ HttpSM::do_http_server_open(bool raw) opt.set_ssl_servername(t_state.server_info.name); } - pending_action = sslNetProcessor.connect_re(this, // state machine + pending_action = sslNetProcessor.connect_re(cont, // state machine or ConnectingEntry &t_state.current.server->dst_addr.sa, // addr + port &opt); } else { SMDebug("http", "calling netProcessor.connect_re"); - pending_action = netProcessor.connect_re(this, // state machine + pending_action = netProcessor.connect_re(cont, // state machine or ConnectingEntry &t_state.current.server->dst_addr.sa, // addr + port &opt); } @@ -5471,7 +5978,6 @@ HttpSM::mark_host_failure(HostDBInfo *info, time_t time_down) .extend(1) .write('\0') .data()); - if (url_str) { t_state.arena.str_free(url_str); } @@ -5674,16 +6180,17 @@ HttpSM::handle_http_server_open() vc->apply_options(); } } - } - server_txn->set_inactivity_timeout(get_server_inactivity_timeout()); + server_txn->set_inactivity_timeout(get_server_inactivity_timeout()); - int method = t_state.hdr_info.server_request.method_get_wksidx(); - if (method != HTTP_WKSIDX_TRACE && - (t_state.hdr_info.request_content_length > 0 || t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING) && - do_post_transform_open()) { - do_setup_post_tunnel(HTTP_TRANSFORM_VC); // Seems like we should be sending the request along this way too - } else if (server_txn != nullptr) { - setup_server_send_request_api(); + int method = t_state.hdr_info.server_request.method_get_wksidx(); + if (method != HTTP_WKSIDX_TRACE && + server_txn->has_request_body(t_state.hdr_info.response_content_length, + t_state.server_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING) && + do_post_transform_open()) { + do_setup_post_tunnel(HTTP_TRANSFORM_VC); + } else { + setup_server_send_request_api(); + } } } @@ -5889,6 +6396,7 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) bool chunked = t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING || t_state.hdr_info.request_content_length == HTTP_UNDEFINED_CL; bool post_redirect = false; + int64_t post_bytes; HttpTunnelProducer *p = nullptr; // YTS Team, yamsat Plugin @@ -5902,8 +6410,8 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) IOBufferReader *postdata_producer_reader = postdata_producer_buffer->alloc_reader(); postdata_producer_buffer->write(this->_postbuf.postdata_copy_buffer_start); - int64_t post_bytes = postdata_producer_reader->read_avail(); - transfered_bytes = post_bytes; + post_bytes = postdata_producer_reader->read_avail(); + transfered_bytes = post_bytes; p = tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, post_bytes, postdata_producer_reader, (HttpProducerHandler) nullptr, HT_STATIC, "redirect static agent post"); } else { @@ -5920,7 +6428,7 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) } MIOBuffer *post_buffer = new_MIOBuffer(alloc_index); IOBufferReader *buf_start = post_buffer->alloc_reader(); - int64_t post_bytes = chunked ? INT64_MAX : t_state.hdr_info.request_content_length; + post_bytes = chunked ? INT64_MAX : t_state.hdr_info.request_content_length; if (enable_redirection) { this->_postbuf.init(post_buffer->clone_reader(buf_start)); @@ -5943,6 +6451,10 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) client_request_body_bytes = num_body_bytes; } ua_txn->get_remote_reader()->consume(num_body_bytes); + // The user agent has already sent all it has + if (ua_txn->is_read_closed()) { + post_bytes = client_request_body_bytes; + } p = tunnel.add_producer(ua_entry->vc, post_bytes - transfered_bytes, buf_start, &HttpSM::tunnel_handler_post_ua, HT_HTTP_CLIENT, "user agent post"); } @@ -5976,14 +6488,22 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) break; } - // The user agent may support chunked (HTTP/1.1) or not (HTTP/2) - // In either case, the server will support chunked (HTTP/1.1) + // The user agent and origin may support chunked (HTTP/1.1) or not (HTTP/2) if (chunked) { if (ua_txn->is_chunked_encoding_supported()) { - tunnel.set_producer_chunking_action(p, 0, TCA_PASSTHRU_CHUNKED_CONTENT); + if (server_txn->is_chunked_encoding_supported()) { + tunnel.set_producer_chunking_action(p, 0, TCA_PASSTHRU_CHUNKED_CONTENT); + } else { + tunnel.set_producer_chunking_action(p, 0, TCA_DECHUNK_CONTENT); + tunnel.set_producer_chunking_size(p, 0); + } } else { - tunnel.set_producer_chunking_action(p, 0, TCA_CHUNK_CONTENT); - tunnel.set_producer_chunking_size(p, 0); + if (server_txn->is_chunked_encoding_supported()) { + tunnel.set_producer_chunking_action(p, 0, TCA_CHUNK_CONTENT); + tunnel.set_producer_chunking_size(p, 0); + } else { + tunnel.set_producer_chunking_action(p, 0, TCA_PASSTHRU_DECHUNKED_CONTENT); + } } } @@ -6150,10 +6670,21 @@ HttpSM::write_header_into_buffer(HTTPHdr *h, MIOBuffer *b) return dumpoffset; } +void +HttpSM::write_outbound_proxy_protocol() +{ + int64_t nbytes = 1; + if (t_state.txn_conf->proxy_protocol_out >= 0) { + nbytes = do_outbound_proxy_protocol(server_txn->get_remote_reader()->mbuf, server_txn->get_netvc(), ua_txn->get_netvc(), + t_state.txn_conf->proxy_protocol_out); + } + server_entry->write_vio = server_txn->do_io_write(this, nbytes, server_txn->get_remote_reader()); +} + void HttpSM::attach_server_session() { - hsm_release_assert(server_entry == nullptr); + hsm_release_assert(this->server_entry == nullptr); // In the h1 only origin version, the transact_count was updated after making this assignment. // The SSN-TXN-COUNT option in header rewrite relies on this fact, so we decrement here so the // plugin API interface is consistent as we move to more protocols to origin @@ -6232,12 +6763,14 @@ HttpSM::attach_server_session() // Do we need Transfer_Encoding? if (ua_txn->has_request_body(t_state.hdr_info.request_content_length, t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) { - // See if we need to insert a chunked header - if (!t_state.hdr_info.server_request.presence(MIME_PRESENCE_CONTENT_LENGTH)) { - // Stuff in a TE setting so we treat this as chunked, sort of. - t_state.server_info.transfer_encoding = HttpTransact::CHUNKED_ENCODING; - t_state.hdr_info.server_request.value_append(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING, HTTP_VALUE_CHUNKED, - HTTP_LEN_CHUNKED, true); + if (server_txn->is_chunked_encoding_supported()) { + // See if we need to insert a chunked header + if (!t_state.hdr_info.server_request.presence(MIME_PRESENCE_CONTENT_LENGTH)) { + // Stuff in a TE setting so we treat this as chunked, sort of. + t_state.server_info.transfer_encoding = HttpTransact::CHUNKED_ENCODING; + t_state.hdr_info.server_request.value_append(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING, HTTP_VALUE_CHUNKED, + HTTP_LEN_CHUNKED, true); + } } } @@ -6327,10 +6860,6 @@ HttpSM::setup_server_read_response_header() server_response_hdr_bytes = 0; milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = 0; - // We already done the READ when we setup the connection to - // read the request header - ink_assert(server_entry->read_vio); - // The tunnel from OS to UA is now setup. Ready to read the response server_entry->read_vio = server_txn->do_io_read(this, INT64_MAX, server_txn->get_remote_reader()->mbuf); @@ -6769,36 +7298,6 @@ HttpSM::setup_transfer_from_transform_to_cache_only() return p; } -void -HttpSM::setup_server_transfer_to_cache_only() -{ - TunnelChunkingAction_t action; - int64_t alloc_index; - int64_t nbytes; - - alloc_index = find_server_buffer_size(); - MIOBuffer *buf = new_MIOBuffer(alloc_index); - IOBufferReader *buf_start = buf->alloc_reader(); - - action = (t_state.current.server && t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) ? - TCA_DECHUNK_CONTENT : - TCA_PASSTHRU_DECHUNKED_CONTENT; - - nbytes = server_transfer_init(buf, 0); - - HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); - - HttpTunnelProducer *p = - tunnel.add_producer(server_entry->vc, nbytes, buf_start, &HttpSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); - - tunnel.set_producer_chunking_action(p, 0, action); - tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); - - setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, 0, "cache write"); - - server_entry->in_tunnel = true; -} - HttpTunnelProducer * HttpSM::setup_server_transfer() { @@ -6864,28 +7363,6 @@ HttpSM::setup_server_transfer() this->setup_plugin_agents(p, client_response_hdr_bytes); - // If the incoming server response is chunked and the client does not - // expect a chunked response, then dechunk it. Otherwise, if the - // incoming response is not chunked and the client expects a chunked - // response, then chunk it. - /* - // this block is moved up so that we know if we need to remove - // Content-Length field from response header before writing the - // response header into buffer bz50730 - TunnelChunkingAction_t action; - if (t_state.client_info.receive_chunked_response == false) { - if (t_state.current.server->transfer_encoding == - HttpTransact::CHUNKED_ENCODING) - action = TCA_DECHUNK_CONTENT; - else action = TCA_PASSTHRU_DECHUNKED_CONTENT; - } - else { - if (t_state.current.server->transfer_encoding != - HttpTransact::CHUNKED_ENCODING) - action = TCA_CHUNK_CONTENT; - else action = TCA_PASSTHRU_CHUNKED_CONTENT; - } - */ tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, action); tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); return p; @@ -7076,12 +7553,17 @@ HttpSM::kill_this() transform_cache_sm.end_both(); vc_table.cleanup_all(); - // tunnel.deallocate_buffers(); - // Why don't we just kill the tunnel? Might still be - // active if the state machine is going down hard, - // and we should clean it up. + // Clean up the tunnel resources. Take + // it down if it is still active tunnel.kill_tunnel(); + if (_netvc) { + _netvc->do_io_close(); + free_MIOBuffer(_netvc_read_buffer); + } else if (server_txn == nullptr) { + this->cancel_pending_server_connection(); + } + // It possible that a plugin added transform hook // but the hook never executed due to a client abort // In that case, we need to manually close all the @@ -7157,6 +7639,8 @@ HttpSM::kill_this() } if (ua_txn) { ua_txn->transaction_done(); + } else { + ink_release_assert(!"Cannot transact done"); } // In the async state, the plugin could have been @@ -7502,7 +7986,6 @@ HttpSM::set_next_state() case HttpTransact::SM_ACTION_DNS_LOOKUP: { sockaddr const *addr; - if (t_state.api_server_addr_set) { /* If the API has set the server address before the OS DNS lookup * then we can skip the lookup @@ -7588,8 +8071,7 @@ HttpSM::set_next_state() } ink_assert(t_state.dns_info.looking_up != HttpTransact::UNDEFINED_LOOKUP); - do_hostdb_lookup(); - break; + return do_hostdb_lookup(); } case HttpTransact::SM_ACTION_DNS_REVERSE_LOOKUP: { @@ -8189,6 +8671,9 @@ HttpSM::get_http_schedule(int event, void * /* data ATS_UNUSED */) return 0; } +/* + * Used from an InkAPI + */ bool HttpSM::set_server_session_private(bool private_session) { @@ -8199,8 +8684,8 @@ HttpSM::set_server_session_private(bool private_session) return false; } -inline bool -HttpSM::is_private() +bool +HttpSM::is_private() const { bool res = false; if (will_be_private_ss) { diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h index ac7c74512eb..9e2bfd9e617 100644 --- a/proxy/http/HttpSM.h +++ b/proxy/http/HttpSM.h @@ -48,6 +48,9 @@ #define HTTP_API_CONTINUE (INK_API_EVENT_EVENTS_START + 0) #define HTTP_API_ERROR (INK_API_EVENT_EVENTS_START + 1) +#define CONNECT_EVENT_TXN (HTTP_NET_CONNECTION_EVENT_EVENTS_START) + 0 +#define CONNECT_EVENT_DIRECT (HTTP_NET_CONNECTION_EVENT_EVENTS_START) + 1 + // The default size for http header buffers when we don't // need to include extra space for the document static size_t const HTTP_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; @@ -59,7 +62,7 @@ static size_t const HTTP_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ // the larger buffer size static size_t const HTTP_SERVER_RESP_HDR_BUFFER_INDEX = BUFFER_SIZE_INDEX_8K; -class Http1ServerSession; +class PoolableSession; class AuthHttpAdapter; class HttpSM; @@ -269,21 +272,24 @@ class HttpSM : public Continuation, public PluginUserArgs // holding the lock for the server session void attach_server_session(); - PoolableSession *create_server_session(NetVConnection *netvc); + PoolableSession *create_server_session(NetVConnection *netvc, MIOBuffer *netvc_read_buffer, IOBufferReader *netvc_reader); bool create_server_txn(PoolableSession *new_session); HTTPVersion get_server_version(HTTPHdr &hdr) const; + // Write out the proxy_protocol information on a new outbound connection + void write_outbound_proxy_protocol(); + ProxyTransaction * - get_ua_txn() + get_server_txn() { - return ua_txn; + return server_txn; } ProxyTransaction * - get_server_txn() + get_ua_txn() { - return server_txn; + return ua_txn; } // Called by transact. Updates are fire and forget @@ -316,6 +322,8 @@ class HttpSM : public Continuation, public PluginUserArgs // A NULL 'r' argument indicates the hostdb lookup failed void process_hostdb_info(HostDBInfo *r); void process_srv_info(HostDBInfo *r); + bool origin_multiplexed() const; + bool add_to_existing_request(); // Called by transact. Synchronous. VConnection *do_transform_open(); @@ -349,7 +357,7 @@ class HttpSM : public Continuation, public PluginUserArgs void txn_hook_add(TSHttpHookID id, INKContInternal *cont); APIHook *txn_hook_get(TSHttpHookID id); - bool is_private(); + bool is_private() const; bool is_redirect_required(); /// Get the protocol stack for the inbound (client, user agent) connection. @@ -454,6 +462,7 @@ class HttpSM : public Continuation, public PluginUserArgs int tunnel_handler(int event, void *data); int tunnel_handler_push(int event, void *data); int tunnel_handler_post(int event, void *data); + int tunnel_handler_trailer(int event, void *data); // YTS Team, yamsat Plugin int tunnel_handler_for_partial_post(int event, void *data); @@ -502,6 +511,8 @@ class HttpSM : public Continuation, public PluginUserArgs int tunnel_handler_cache_read(int event, HttpTunnelProducer *p); int tunnel_handler_post_ua(int event, HttpTunnelProducer *c); int tunnel_handler_post_server(int event, HttpTunnelConsumer *c); + int tunnel_handler_trailer_ua(int event, HttpTunnelConsumer *c); + int tunnel_handler_trailer_server(int event, HttpTunnelProducer *c); int tunnel_handler_ssl_producer(int event, HttpTunnelProducer *p); int tunnel_handler_ssl_consumer(int event, HttpTunnelConsumer *p); int tunnel_handler_transform_write(int event, HttpTunnelConsumer *c); @@ -511,7 +522,7 @@ class HttpSM : public Continuation, public PluginUserArgs void do_hostdb_lookup(); void do_hostdb_reverse_lookup(); void do_cache_lookup_and_read(); - void do_http_server_open(bool raw = false); + void do_http_server_open(bool raw = false, bool only_direct = false); void send_origin_throttled_response(); void do_setup_post_tunnel(HttpVC_t to_vc_type); void do_cache_prepare_write(); @@ -545,7 +556,6 @@ class HttpSM : public Continuation, public PluginUserArgs void setup_server_send_request(); void setup_server_send_request_api(); HttpTunnelProducer *setup_server_transfer(); - void setup_server_transfer_to_cache_only(); HttpTunnelProducer *setup_cache_read_transfer(); void setup_internal_transfer(HttpSMHandler handler); void setup_error_transfer(); @@ -698,10 +708,15 @@ class HttpSM : public Continuation, public PluginUserArgs void rewind_state_machine(); private: + void cancel_pending_server_connection(); + PostDataBuffers _postbuf; int _client_connection_id = -1, _client_transaction_id = -1; int _client_transaction_priority_weight = -1, _client_transaction_priority_dependence = -1; - bool _from_early_data = false; + bool _from_early_data = false; + NetVConnection *_netvc = nullptr; + IOBufferReader *_netvc_reader = nullptr; + MIOBuffer *_netvc_read_buffer = nullptr; }; // Function to get the cache_sm object - YTS Team, yamsat diff --git a/proxy/http/HttpSessionManager.cc b/proxy/http/HttpSessionManager.cc index d20a1e9e7fe..68cbfa7ceac 100644 --- a/proxy/http/HttpSessionManager.cc +++ b/proxy/http/HttpSessionManager.cc @@ -165,7 +165,9 @@ ServerSessionPool::acquireSession(sockaddr const *addr, CryptoHash const &hostna } if (zret == HSM_DONE) { to_return = first; - this->removeSession(to_return); + if (!to_return->is_multiplexing()) { + this->removeSession(to_return); + } } else if (first != m_fqdn_pool.end()) { Debug("http_ss", "Failed find entry due to name mismatch %s", sm->t_state.current.server->name); } @@ -190,7 +192,9 @@ ServerSessionPool::acquireSession(sockaddr const *addr, CryptoHash const &hostna } if (zret == HSM_DONE) { to_return = first; - this->removeSession(to_return); + if (!to_return->is_multiplexing()) { + this->removeSession(to_return); + } } } return zret; @@ -501,6 +505,15 @@ ServerSessionPool::removeSession(PoolableSession *to_remove) } } +void +ServerSessionPool::testSession(PoolableSession *ss) +{ + auto fqdn_iter = m_fqdn_pool.find(ss); + ink_release_assert(fqdn_iter == m_fqdn_pool.end()); + auto ip_iter = m_ip_pool.find(ss); + ink_release_assert(ip_iter == m_ip_pool.end()); +} + void ServerSessionPool::addSession(PoolableSession *ss) { diff --git a/proxy/http/HttpSessionManager.h b/proxy/http/HttpSessionManager.h index 8ce9a89baab..ce952b49bb4 100644 --- a/proxy/http/HttpSessionManager.h +++ b/proxy/http/HttpSessionManager.h @@ -67,6 +67,9 @@ class ServerSessionPool : public Continuation static bool validate_host_sni(HttpSM *sm, NetVConnection *netvc); static bool validate_sni(HttpSM *sm, NetVConnection *netvc); static bool validate_cert(HttpSM *sm, NetVConnection *netvc); + void removeSession(PoolableSession *ssn); + void addSession(PoolableSession *ssn); + void testSession(PoolableSession *ssn); int count() const { @@ -74,9 +77,6 @@ class ServerSessionPool : public Continuation } private: - void removeSession(PoolableSession *ssn); - void addSession(PoolableSession *ssn); - using IPTable = IntrusiveHashMap; using FQDNTable = IntrusiveHashMap; diff --git a/proxy/http/HttpTunnel.cc b/proxy/http/HttpTunnel.cc index 3f7a3b53e47..a3ce2c01dee 100644 --- a/proxy/http/HttpTunnel.cc +++ b/proxy/http/HttpTunnel.cc @@ -715,6 +715,7 @@ HttpTunnel::chain(HttpTunnelConsumer *c, HttpTunnelProducer *p) void HttpTunnel::tunnel_run(HttpTunnelProducer *p_arg) { + ++reentrancy_count; Debug("http_tunnel", "tunnel_run started, p_arg is %s", p_arg ? "provided" : "NULL"); if (p_arg) { producer_run(p_arg); @@ -730,6 +731,7 @@ HttpTunnel::tunnel_run(HttpTunnelProducer *p_arg) } } } + --reentrancy_count; // It is possible that there was nothing to do // due to a all transfers being zero length @@ -974,6 +976,7 @@ HttpTunnel::producer_run(HttpTunnelProducer *p) p->handler_state = HTTP_SM_POST_SUCCESS; } } + Debug("http_tunnel", "Start write vio %ld bytes", c_write); // Start the writes now that we know we will consume all the initial data c->write_vio = c->vc->do_io_write(this, c_write, c->buffer_reader); ink_assert(c_write > 0); @@ -998,9 +1001,17 @@ HttpTunnel::producer_run(HttpTunnelProducer *p) if (read_start_pos > 0) { p->read_vio = ((CacheVC *)p->vc)->do_io_pread(this, producer_n, p->read_buffer, read_start_pos); } else { + Debug("http_tunnel", "Start read vio %ld bytes", producer_n); p->read_vio = p->vc->do_io_read(this, producer_n, p->read_buffer); } } + } else { + // If the producer is not alive (precomplete) make sure to kick the consumers + for (c = p->consumer_list.head; c; c = c->link.next) { + if (c->alive && c->write_vio) { + c->write_vio->reenable(); + } + } } // Now that the tunnel has started, we must remove producer's reader so @@ -1126,14 +1137,6 @@ HttpTunnel::producer_handler(int event, HttpTunnelProducer *p) // Handle chunking/dechunking/chunked-passthrough if necessary. if (p->do_chunking) { event = producer_handler_dechunked(event, p); - - // If we were in PRECOMPLETE when this function was called - // and we are doing chunking, then we just wrote the last - // chunk in the the function call above. We are done with the - // tunnel. - if (event == HTTP_TUNNEL_EVENT_PRECOMPLETE) { - event = VC_EVENT_EOS; - } } else if (p->do_dechunking || p->do_chunked_passthru) { event = producer_handler_chunked(event, p); } else { @@ -1172,6 +1175,7 @@ HttpTunnel::producer_handler(int event, HttpTunnelProducer *p) // Data read from producer, reenable consumers for (c = p->consumer_list.head; c; c = c->link.next) { if (c->alive && c->write_vio) { + Debug("http_redirect", "Read ready alive"); c->write_vio->reenable(); } } @@ -1181,6 +1185,8 @@ HttpTunnel::producer_handler(int event, HttpTunnelProducer *p) // If the write completes on the stack (as it can for http2), then // consumer could have called back by this point. Must treat this as // a regular read complete (falling through to the following cases). + p->bytes_read = p->init_bytes_done; + [[fallthrough]]; case VC_EVENT_READ_COMPLETE: case VC_EVENT_EOS: @@ -1194,7 +1200,6 @@ HttpTunnel::producer_handler(int event, HttpTunnelProducer *p) // the message length being a property of the encoding) // In that case, we won't have done a do_io so there // will not be vio - p->bytes_read = 0; } // callback the SM to notify of completion @@ -1209,7 +1214,7 @@ HttpTunnel::producer_handler(int event, HttpTunnelProducer *p) sm_callback = true; p->update_state_if_not_set(HTTP_SM_POST_SUCCESS); - // Data read from producer, reenable consumers + // Kick off the consumers if appropriate for (c = p->consumer_list.head; c; c = c->link.next) { if (c->alive && c->write_vio) { c->write_vio->reenable(); @@ -1347,6 +1352,9 @@ HttpTunnel::consumer_handler(int event, HttpTunnelConsumer *c) case VC_EVENT_INACTIVITY_TIMEOUT: ink_assert(c->alive); ink_assert(c->buffer_reader); + if (c->write_vio) { + c->write_vio->reenable(); + } c->alive = false; c->bytes_written = c->write_vio ? c->write_vio->ndone : 0; diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index 648609cbb80..3a5a7fd14f4 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -61,11 +61,16 @@ Http2HeaderName http2_connection_specific_headers[5] = {}; // Statistics RecRawStatBlock *http2_rsb; static const char *const HTTP2_STAT_CURRENT_CLIENT_CONNECTION_NAME = "proxy.process.http2.current_client_connections"; +static const char *const HTTP2_STAT_CURRENT_SERVER_CONNECTION_NAME = "proxy.process.http2.current_server_connections"; static const char *const HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_NAME = "proxy.process.http2.current_active_client_connections"; +static const char *const HTTP2_STAT_CURRENT_ACTIVE_SERVER_CONNECTION_NAME = "proxy.process.http2.current_active_server_connections"; static const char *const HTTP2_STAT_CURRENT_CLIENT_STREAM_NAME = "proxy.process.http2.current_client_streams"; +static const char *const HTTP2_STAT_CURRENT_SERVER_STREAM_NAME = "proxy.process.http2.current_server_streams"; static const char *const HTTP2_STAT_TOTAL_CLIENT_STREAM_NAME = "proxy.process.http2.total_client_streams"; +static const char *const HTTP2_STAT_TOTAL_SERVER_STREAM_NAME = "proxy.process.http2.total_server_streams"; static const char *const HTTP2_STAT_TOTAL_TRANSACTIONS_TIME_NAME = "proxy.process.http2.total_transactions_time"; static const char *const HTTP2_STAT_TOTAL_CLIENT_CONNECTION_NAME = "proxy.process.http2.total_client_connections"; +static const char *const HTTP2_STAT_TOTAL_SERVER_CONNECTION_NAME = "proxy.process.http2.total_server_connections"; static const char *const HTTP2_STAT_CONNECTION_ERRORS_NAME = "proxy.process.http2.connection_errors"; static const char *const HTTP2_STAT_STREAM_ERRORS_NAME = "proxy.process.http2.stream_errors"; static const char *const HTTP2_STAT_SESSION_DIE_DEFAULT_NAME = "proxy.process.http2.session_die_default"; @@ -442,10 +447,16 @@ http2_convert_header_from_2_to_1_1(HTTPHdr *headers) if (MIMEField *field = headers->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY); field != nullptr && field->value_is_valid()) { int authority_len; - const char *authority = field->value_get(&authority_len); + // Set the host header field + MIMEField *host = headers->field_find(MIME_FIELD_HOST, MIME_LEN_HOST); + if (host == nullptr) { + host = headers->field_create(MIME_FIELD_HOST, MIME_LEN_HOST); + headers->field_attach(host); + } + const char *authority = field->value_get(&authority_len); url_host_set(headers->m_heap, headers->m_http->u.req.m_url_impl, authority, authority_len, true); - + host->value_set(headers->m_heap, headers->m_mime, authority, authority_len); headers->field_delete(field); } else { return PARSE_RESULT_ERROR; @@ -595,6 +606,9 @@ http2_convert_header_from_1_1_to_2(HTTPHdr *headers) } else { field->value_set(headers->m_heap, headers->m_mime, value, value_len); } + // Remove the host header field, redundant to the authority field + // For istio/envoy, having both was causing 404 responses + headers->field_delete(MIME_FIELD_HOST, MIME_LEN_HOST); } else { ink_abort("initialize HTTP/2 pseudo-headers"); return PARSE_RESULT_ERROR; @@ -604,13 +618,27 @@ http2_convert_header_from_1_1_to_2(HTTPHdr *headers) if (MIMEField *field = headers->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH); field != nullptr) { int value_len; const char *value = headers->path_get(&value_len); + int param_len; + const char *param = headers->params_get(¶m_len); + int query_len; + const char *query = headers->query_get(&query_len); + int path_len = value_len + 1; - ts::LocalBuffer buf(value_len + 1); + ts::LocalBuffer buf(value_len + 1 + 1 + 1 + query_len + param_len); char *path = buf.data(); path[0] = '/'; memcpy(path + 1, value, value_len); - - field->value_set(headers->m_heap, headers->m_mime, path, value_len + 1); + if (param_len > 0) { + path[path_len] = ';'; + memcpy(path + path_len + 1, param, param_len); + path_len += 1 + param_len; + } + if (query_len > 0) { + path[path_len] = '?'; + memcpy(path + path_len + 1, query, query_len); + path_len += 1 + query_len; + } + field->value_set(headers->m_heap, headers->m_mime, path, path_len); } else { ink_abort("initialize HTTP/2 pseudo-headers"); return PARSE_RESULT_ERROR; @@ -678,12 +706,11 @@ http2_encode_header_blocks(HTTPHdr *in, uint8_t *out, uint32_t out_len, uint32_t */ Http2ErrorCode http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_t buf_len, uint32_t *len_read, HpackHandle &handle, - bool &trailing_header, uint32_t maximum_table_size) + bool is_trailing_header, uint32_t maximum_table_size, bool is_outbound) { const MIMEField *field; const char *value; int len; - bool is_trailing_header = trailing_header; int64_t result = hpack_decode_header_block(handle, hdr, buf_start, buf_len, Http2::max_header_list_size, maximum_table_size); if (result < 0) { @@ -700,7 +727,7 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ } MIMEFieldIter iter; - unsigned int expected_pseudo_header_count = 4; + unsigned int expected_pseudo_header_count = is_outbound ? 1 : 4; unsigned int pseudo_header_count = 0; if (is_trailing_header) { @@ -728,7 +755,7 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ if (hdr->field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION) != nullptr || hdr->field_find(MIME_FIELD_KEEP_ALIVE, MIME_LEN_KEEP_ALIVE) != nullptr || hdr->field_find(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION) != nullptr || - hdr->field_find(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING) != nullptr || + // hdr->field_find(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING) != nullptr || hdr->field_find(MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE) != nullptr) { return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; } @@ -742,13 +769,6 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ } } - // turn on that we have a trailer header - const char trailer_name[] = "trailer"; - field = hdr->field_find(trailer_name, sizeof(trailer_name) - 1); - if (field) { - trailing_header = true; - } - // when The TE header field is received, it MUST NOT contain any // value other than "trailers". field = hdr->field_find(MIME_FIELD_TE, MIME_LEN_TE); @@ -761,18 +781,29 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ if (!is_trailing_header) { // Check pseudo headers - if (hdr->fields_count() >= 4) { - if (hdr->field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME) == nullptr || - hdr->field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD) == nullptr || - hdr->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH) == nullptr || - hdr->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY) == nullptr || - hdr->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS) != nullptr) { - // Decoded header field is invalid + if (is_outbound) { + if (hdr->fields_count() >= 1) { + if (hdr->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS) == nullptr) { + return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; + } + } else { + // Pseudo headers is insufficient return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; } } else { - // Pseudo headers is insufficient - return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; + if (hdr->fields_count() >= 4) { + if (hdr->field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME) == nullptr || + hdr->field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD) == nullptr || + hdr->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH) == nullptr || + hdr->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY) == nullptr || + hdr->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS) != nullptr) { + // Decoded header field is invalid + return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; + } + } else { + // Pseudo headers is insufficient + return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; + } } } @@ -791,6 +822,7 @@ uint32_t Http2::header_table_size = 4096; uint32_t Http2::max_header_list_size = 4294967295; uint32_t Http2::accept_no_activity_timeout = 120; uint32_t Http2::no_activity_timeout_in = 120; +uint32_t Http2::no_activity_timeout_out = 120; uint32_t Http2::active_timeout_in = 0; uint32_t Http2::push_diary_size = 256; uint32_t Http2::zombie_timeout_in = 0; @@ -821,6 +853,7 @@ Http2::init() REC_EstablishStaticConfigInt32U(max_header_list_size, "proxy.config.http2.max_header_list_size"); REC_EstablishStaticConfigInt32U(accept_no_activity_timeout, "proxy.config.http2.accept_no_activity_timeout"); REC_EstablishStaticConfigInt32U(no_activity_timeout_in, "proxy.config.http2.no_activity_timeout_in"); + REC_EstablishStaticConfigInt32U(no_activity_timeout_out, "proxy.config.http2.no_activity_timeout_out"); REC_EstablishStaticConfigInt32U(active_timeout_in, "proxy.config.http2.active_timeout_in"); REC_EstablishStaticConfigInt32U(push_diary_size, "proxy.config.http2.push_diary_size"); REC_EstablishStaticConfigInt32U(zombie_timeout_in, "proxy.config.http2.zombie_debug_timeout_in"); @@ -857,18 +890,31 @@ Http2::init() RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_CLIENT_CONNECTION_NAME, RECD_INT, RECP_NON_PERSISTENT, static_cast(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT), RecRawStatSyncSum); HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT); + RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_SERVER_CONNECTION_NAME, RECD_INT, RECP_NON_PERSISTENT, + static_cast(HTTP2_STAT_CURRENT_SERVER_SESSION_COUNT), RecRawStatSyncSum); + HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_SERVER_SESSION_COUNT); RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_NAME, RECD_INT, RECP_NON_PERSISTENT, static_cast(HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_COUNT), RecRawStatSyncSum); HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_COUNT); + RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_ACTIVE_SERVER_CONNECTION_NAME, RECD_INT, RECP_NON_PERSISTENT, + static_cast(HTTP2_STAT_CURRENT_ACTIVE_SERVER_CONNECTION_COUNT), RecRawStatSyncSum); + HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_ACTIVE_SERVER_CONNECTION_COUNT); RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_CLIENT_STREAM_NAME, RECD_INT, RECP_NON_PERSISTENT, static_cast(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT), RecRawStatSyncSum); HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT); + RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CURRENT_SERVER_STREAM_NAME, RECD_INT, RECP_NON_PERSISTENT, + static_cast(HTTP2_STAT_CURRENT_SERVER_STREAM_COUNT), RecRawStatSyncSum); + HTTP2_CLEAR_DYN_STAT(HTTP2_STAT_CURRENT_SERVER_STREAM_COUNT); RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_CLIENT_STREAM_NAME, RECD_INT, RECP_PERSISTENT, static_cast(HTTP2_STAT_TOTAL_CLIENT_STREAM_COUNT), RecRawStatSyncCount); + RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_SERVER_STREAM_NAME, RECD_INT, RECP_PERSISTENT, + static_cast(HTTP2_STAT_TOTAL_SERVER_STREAM_COUNT), RecRawStatSyncCount); RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_TRANSACTIONS_TIME_NAME, RECD_INT, RECP_PERSISTENT, static_cast(HTTP2_STAT_TOTAL_TRANSACTIONS_TIME), RecRawStatSyncSum); RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_CLIENT_CONNECTION_NAME, RECD_INT, RECP_PERSISTENT, static_cast(HTTP2_STAT_TOTAL_CLIENT_CONNECTION_COUNT), RecRawStatSyncSum); + RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_TOTAL_SERVER_CONNECTION_NAME, RECD_INT, RECP_PERSISTENT, + static_cast(HTTP2_STAT_TOTAL_SERVER_CONNECTION_COUNT), RecRawStatSyncSum); RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_CONNECTION_ERRORS_NAME, RECD_INT, RECP_PERSISTENT, static_cast(HTTP2_STAT_CONNECTION_ERRORS_COUNT), RecRawStatSyncSum); RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_STREAM_ERRORS_NAME, RECD_INT, RECP_PERSISTENT, diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h index d2eed22ffe3..651046e0b47 100644 --- a/proxy/http2/HTTP2.h +++ b/proxy/http2/HTTP2.h @@ -84,12 +84,17 @@ const uint8_t HTTP2_PRIORITY_DEFAULT_WEIGHT = 15; // Statistics enum { - HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT, // Current # of HTTP2 connections - HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_COUNT, // Current # of active HTTP2 connections - HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, // Current # of active HTTP2 streams + HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT, // Current # of inbound HTTP2 connections + HTTP2_STAT_CURRENT_SERVER_SESSION_COUNT, // Current # of outbound HTTP2 connections + HTTP2_STAT_CURRENT_ACTIVE_CLIENT_CONNECTION_COUNT, // Current # of active inbound HTTP2 connections + HTTP2_STAT_CURRENT_ACTIVE_SERVER_CONNECTION_COUNT, // Current # of active outbound HTTP2 connections + HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, // Current # of active inbound HTTP2 streams + HTTP2_STAT_CURRENT_SERVER_STREAM_COUNT, // Current # of active outboundHTTP2 streams HTTP2_STAT_TOTAL_CLIENT_STREAM_COUNT, + HTTP2_STAT_TOTAL_SERVER_STREAM_COUNT, HTTP2_STAT_TOTAL_TRANSACTIONS_TIME, // Total stream time and streams - HTTP2_STAT_TOTAL_CLIENT_CONNECTION_COUNT, // Total connections running http2 + HTTP2_STAT_TOTAL_CLIENT_CONNECTION_COUNT, // Total inbound connections running http2 + HTTP2_STAT_TOTAL_SERVER_CONNECTION_COUNT, // Total outbound connections running http2 HTTP2_STAT_STREAM_ERRORS_COUNT, HTTP2_STAT_CONNECTION_ERRORS_COUNT, HTTP2_STAT_SESSION_DIE_DEFAULT, @@ -245,8 +250,8 @@ enum Http2SettingsIdentifier { HTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 4, HTTP2_SETTINGS_MAX_FRAME_SIZE = 5, HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 6, - - HTTP2_SETTINGS_MAX + HTTP2_SETTINGS_MAX, // Really just the max of the "densely numbered" core id's + HTTP2_SETTINGS_GRPC_ALLOW_TRUE_BINARY_METADATA = 0xfe03, }; // [RFC 7540] 4.1. Frame Format @@ -362,7 +367,8 @@ bool http2_parse_goaway(IOVec, Http2Goaway &); bool http2_parse_window_update(IOVec, uint32_t &); -Http2ErrorCode http2_decode_header_blocks(HTTPHdr *, const uint8_t *, const uint32_t, uint32_t *, HpackHandle &, bool &, uint32_t); +Http2ErrorCode http2_decode_header_blocks(HTTPHdr *, const uint8_t *, const uint32_t, uint32_t *, HpackHandle &, bool, uint32_t, + bool is_outbound = false); Http2ErrorCode http2_encode_header_blocks(HTTPHdr *, uint8_t *, uint32_t, uint32_t *, HpackHandle &, int32_t); @@ -390,6 +396,7 @@ class Http2 static uint32_t max_header_list_size; static uint32_t accept_no_activity_timeout; static uint32_t no_activity_timeout_in; + static uint32_t no_activity_timeout_out; static uint32_t active_timeout_in; static uint32_t push_diary_size; static uint32_t zombie_timeout_in; diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc index ea0c9630d67..84e17d25708 100644 --- a/proxy/http2/Http2ClientSession.cc +++ b/proxy/http2/Http2ClientSession.cc @@ -45,6 +45,10 @@ Http2ClientSession::destroy() in_destroy = true; REMEMBER(NO_EVENT, this->recursion) Http2SsnDebug("session destroy"); + if (_vc) { + _vc->do_io_close(); + _vc = nullptr; + } // Let everyone know we are going down do_api_callout(TS_HTTP_SSN_CLOSE_HOOK); } @@ -53,14 +57,10 @@ Http2ClientSession::destroy() void Http2ClientSession::free() { - if (_vc) { - _vc->do_io_close(); - _vc = nullptr; - } auto mutex_thread = this->mutex->thread_holding; if (Http2CommonSession::common_free(this)) { HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_SESSION_COUNT, mutex_thread); - THREAD_FREE(this, http2ClientSessionAllocator, this_ethread()); + THREAD_FREE(this, http2ClientSessionAllocator, mutex_thread); } } @@ -97,7 +97,6 @@ Http2ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB _vc->set_inactivity_timeout(HRTIME_SECONDS(Http2::accept_no_activity_timeout)); this->schedule_event = nullptr; this->mutex = new_vc->mutex; - this->in_destroy = false; this->connection_state.mutex = this->mutex; @@ -112,7 +111,7 @@ Http2ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB this->_vc->set_tcp_congestion_control(CLIENT_SIDE); this->read_buffer = iobuf ? iobuf : new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX); - this->read_buffer->water_mark = connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE); + this->read_buffer->water_mark = connection_state.local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE); this->_read_buffer_reader = reader ? reader : this->read_buffer->alloc_reader(); // This block size is the buffer size that we pass to SSLWriteBuffer @@ -268,17 +267,17 @@ Http2ClientSession::get_transact_count() const return connection_state.get_stream_requests(); } -void -Http2ClientSession::release(ProxyTransaction *trans) -{ -} - const char * Http2ClientSession::get_protocol_string() const { return "http/2"; } +void +Http2ClientSession::release(ProxyTransaction *trans) +{ +} + int Http2ClientSession::populate_protocol(std::string_view *result, int size) const { @@ -311,6 +310,15 @@ Http2ClientSession::get_proxy_session() return this; } +void +Http2ClientSession::set_no_activity_timeout() +{ + // Only set if not previously set + if (this->_vc->get_inactivity_timeout() == 0) { + this->set_inactivity_timeout(HRTIME_SECONDS(Http2::no_activity_timeout_in)); + } +} + HTTPVersion Http2ClientSession::get_version(HTTPHdr &hdr) const { diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h index cd4cf0df503..dc1336face0 100644 --- a/proxy/http2/Http2ClientSession.h +++ b/proxy/http2/Http2ClientSession.h @@ -33,8 +33,7 @@ class Http2ClientSession : public ProxySession, public Http2CommonSession { public: - using super = ProxySession; ///< Parent type. - using SessionHandler = int (Http2ClientSession::*)(int, void *); + using super = ProxySession; ///< Parent type. Http2ClientSession(); @@ -63,6 +62,8 @@ class Http2ClientSession : public ProxySession, public Http2CommonSession void increment_current_active_connections_stat() override; void decrement_current_active_connections_stat() override; + void set_no_activity_timeout() override; + ProxySession *get_proxy_session() override; // noncopyable diff --git a/proxy/http2/Http2CommonSession.cc b/proxy/http2/Http2CommonSession.cc index 55974b5b0fc..729657457d3 100644 --- a/proxy/http2/Http2CommonSession.cc +++ b/proxy/http2/Http2CommonSession.cc @@ -91,6 +91,7 @@ Http2CommonSession::common_free(ProxySession *ssn) ink_hrtime_to_msec(this->_milestones[Http2SsnMilestone::OPEN]), this->_milestones.difference_sec(Http2SsnMilestone::OPEN, Http2SsnMilestone::CLOSE)); } + // Update stats on how we died. May want to eliminate this. Was useful for // tracking down which cases we were having problems cleaning up. But for general // use probably not worth the effort @@ -147,6 +148,20 @@ Http2CommonSession::set_half_close_local_flag(bool flag) half_close_local = flag; } +void +Http2CommonSession::add_url_to_pushed_table(const char *url, int url_len) +{ + // Delay std::unordered_set allocation until when it used + if (_h2_pushed_urls == nullptr) { + this->_h2_pushed_urls = new std::unordered_set(); + this->_h2_pushed_urls->reserve(Http2::push_diary_size); + } + + if (_h2_pushed_urls->size() < Http2::push_diary_size) { + _h2_pushed_urls->emplace(url); + } +} + int64_t Http2CommonSession::xmit(const Http2TxFrame &frame, bool flush) { @@ -172,12 +187,30 @@ void Http2CommonSession::flush() { if (this->_pending_sending_data_size > 0) { + total_write_len += this->_pending_sending_data_size; this->_pending_sending_data_size = 0; this->_write_buffer_last_flush = Thread::get_hrtime(); write_reenable(); } } +void +Http2CommonSession::write_reenable() +{ + if (write_vio) { + // Grab the lock for the write_vio. Holding the lock is + // checked eventually via the reenable logic + SCOPED_MUTEX_LOCK(lock, write_vio->mutex, this_ethread()); + write_vio->reenable(); + } +} + +int64_t +Http2CommonSession::write_avail() +{ + return this->write_buffer->write_avail(); +} + int Http2CommonSession::state_read_connection_preface(int event, void *edata) { @@ -268,13 +301,13 @@ Http2CommonSession::do_start_frame_read(Http2ErrorCode &ret_error) this->_read_buffer_reader->consume(nbytes); - if (!http2_frame_header_is_valid(this->current_hdr, this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE))) { + if (!http2_frame_header_is_valid(this->current_hdr, this->connection_state.local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE))) { ret_error = Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; return -1; } // If we know up front that the payload is too long, nuke this connection. - if (this->current_hdr.length > this->connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)) { + if (this->current_hdr.length > this->connection_state.local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)) { ret_error = Http2ErrorCode::HTTP2_ERROR_FRAME_SIZE_ERROR; return -1; } @@ -341,11 +374,15 @@ Http2CommonSession::do_complete_frame_read() int Http2CommonSession::do_process_frame_read(int event, VIO *vio, bool inside_frame) { + Http2SsnDebug("do_process_frame_read %" PRId64 " bytes ready", this->_read_buffer_reader->read_avail()); + if (inside_frame) { do_complete_frame_read(); } while (this->_read_buffer_reader->read_avail() >= static_cast(HTTP2_FRAME_HEADER_LEN)) { + Http2SsnDebug("do_process_frame_read try to read frame %" PRId64 " bytes ready", this->_read_buffer_reader->read_avail()); + // Cancel reading if there was an error or connection is closed if (connection_state.tx_error_code.code != static_cast(Http2ErrorCode::HTTP2_ERROR_NO_ERROR) || connection_state.is_state_closed()) { @@ -354,10 +391,11 @@ Http2CommonSession::do_process_frame_read(int event, VIO *vio, bool inside_frame } Http2ErrorCode err = Http2ErrorCode::HTTP2_ERROR_NO_ERROR; - if (this->connection_state.get_stream_error_rate() > std::min(1.0, Http2::stream_error_rate_threshold * 2.0)) { + if (this->connection_state.get_stream_error_rate() > std::min(1.0, Http2::stream_error_rate_threshold * 2.0) && + !this->is_outbound()) { ip_port_text_buffer ipb; const char *client_ip = ats_ip_ntop(this->get_proxy_session()->get_remote_addr(), ipb, sizeof(ipb)); - SiteThrottledWarning("HTTP/2 session error client_ip=%s session_id=%" PRId64 + SiteThrottledWarning("HTTP/2 session error peer_ip=%s session_id=%" PRId64 " closing a connection, because its stream error rate (%f) exceeded the threshold (%f)", client_ip, this->get_connection_id(), this->connection_state.get_stream_error_rate(), Http2::stream_error_rate_threshold); @@ -403,32 +441,17 @@ Http2CommonSession::do_process_frame_read(int event, VIO *vio, bool inside_frame bool Http2CommonSession::_should_do_something_else() { - // Do something else every 128 incoming frames if connection state isn't closed - return (this->_n_frame_read & 0x7F) == 0 && !connection_state.is_state_closed(); -} - -int64_t -Http2CommonSession::write_avail() -{ - return this->write_buffer->write_avail(); + // Do something else every 128 incoming frames + return (this->_n_frame_read & 0x7F) == 0; } void -Http2CommonSession::write_reenable() +Http2CommonSession::add_session() { - write_vio->reenable(); } -void -Http2CommonSession::add_url_to_pushed_table(const char *url, int url_len) +bool +Http2CommonSession::is_outbound() const { - // Delay std::unordered_set allocation until when it used - if (_h2_pushed_urls == nullptr) { - this->_h2_pushed_urls = new std::unordered_set(); - this->_h2_pushed_urls->reserve(Http2::push_diary_size); - } - - if (_h2_pushed_urls->size() < Http2::push_diary_size) { - _h2_pushed_urls->emplace(url); - } + return false; } diff --git a/proxy/http2/Http2CommonSession.h b/proxy/http2/Http2CommonSession.h index 0961054fc21..9da14cf803c 100644 --- a/proxy/http2/Http2CommonSession.h +++ b/proxy/http2/Http2CommonSession.h @@ -108,6 +108,10 @@ class Http2CommonSession // Variables Http2ConnectionState connection_state; + virtual void add_session(); + virtual bool is_outbound() const; + virtual void set_no_activity_timeout() = 0; + protected: // SessionHandler(s) - state of reading frame int state_read_connection_preface(int, void *); @@ -133,6 +137,9 @@ class Http2CommonSession MIOBuffer *write_buffer = nullptr; IOBufferReader *_write_buffer_reader = nullptr; + + int64_t total_write_len = 0; + Http2FrameHeader current_hdr = {0, 0, 0, 0}; uint32_t _write_size_threshold = 0; uint32_t _write_time_threshold = 100; @@ -224,3 +231,4 @@ Http2CommonSession::do_clear_session_active() { get_proxy_session()->clear_session_active(); } + diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index 75e85889cae..9f475be554f 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -1,6 +1,6 @@ /** @file - Http2ConnectionState. + Http2ConnectionState.cc @section license License @@ -24,6 +24,7 @@ #include "P_Net.h" #include "Http2ConnectionState.h" #include "Http2ClientSession.h" +#include "Http2ServerSession.h" #include "Http2Stream.h" #include "Http2Frame.h" #include "Http2DebugNames.h" @@ -42,12 +43,10 @@ } \ } -#define Http2ConDebug(session, fmt, ...) \ - SsnDebug(session->get_proxy_session(), "http2_con", "[%" PRId64 "] " fmt, session->get_connection_id(), ##__VA_ARGS__); +#define Http2ConDebug(session, fmt, ...) Debug("http2_con", "[%" PRId64 "] " fmt, session->get_connection_id(), ##__VA_ARGS__); -#define Http2StreamDebug(session, stream_id, fmt, ...) \ - SsnDebug(session->get_proxy_session(), "http2_con", "[%" PRId64 "] [%u] " fmt, session->get_connection_id(), stream_id, \ - ##__VA_ARGS__); +#define Http2StreamDebug(session, stream_id, fmt, ...) \ + Debug("http2_con", "[%" PRId64 "] [%u] " fmt, session->get_connection_id(), stream_id, ##__VA_ARGS__); using http2_frame_dispatch = Http2Error (*)(Http2ConnectionState &, const Http2Frame &); @@ -105,13 +104,23 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) if (stream == nullptr) { if (cstate.is_valid_streamid(id)) { // This error occurs fairly often, and is probably innocuous (SM initiates the shutdown) - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED, nullptr); + cstate.send_rst_stream_frame(id, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); + if (cstate.session->is_outbound()) { + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + } else { + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED, nullptr); + } } else { return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, "recv data stream freed with invalid id"); } } + if (stream->get_state() == Http2StreamState::HTTP2_STREAM_STATE_CLOSED) { + cstate.send_rst_stream_frame(id, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED); + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + } + // If a DATA frame is received whose stream is not in "open" or "half closed // (local)" state, // the recipient MUST respond with a stream error of type STREAM_CLOSED. @@ -147,14 +156,19 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // Pure END_STREAM if (payload_length == 0) { - stream->signal_read_event(VC_EVENT_READ_COMPLETE); + if (stream->read_enabled()) { + stream->signal_read_event(VC_EVENT_READ_COMPLETE); + } return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); } } else { - // If payload length is 0 without END_STREAM flag, do nothing - if (payload_length == 0) { - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); - } + // Any headers that show up after we received data are by definition trailing headers + stream->set_trailing_header_is_possible(); + } + + // If payload length is 0 without END_STREAM flag, do nothing + if (payload_length == 0 && !stream->recv_end_stream) { + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); } // Check whether Window Size is acceptable @@ -172,7 +186,7 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) stream->decrement_server_rwnd(payload_length); if (is_debug_tag_set("http2_con")) { - uint32_t rwnd = cstate.server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE); + uint32_t rwnd = cstate.local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE); Http2StreamDebug(cstate.session, id, "Received DATA frame: rwnd con=%zd/%" PRId32 " stream=%zd/%" PRId32, cstate.server_rwnd(), rwnd, stream->server_rwnd(), rwnd); } @@ -180,7 +194,7 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) const uint32_t unpadded_length = payload_length - pad_length; MIOBuffer *writer = stream->read_vio_writer(); if (writer == nullptr) { - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR); + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR, "no writer"); } // If we call write() multiple times, we must keep the same reader, so we can @@ -200,14 +214,19 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) unsigned int num_written = writer->write(myreader, read_len); if (num_written != read_len) { myreader->writer()->dealloc_reader(myreader); - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR); + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR, "Write mismatch"); } myreader->consume(num_written); + stream->read_update(num_written); } myreader->writer()->dealloc_reader(myreader); if (frame.header().flags & HTTP2_FLAGS_DATA_END_STREAM) { // TODO: set total written size to read_vio.nbytes + stream->read_done(); + } + + if (frame.header().flags & HTTP2_FLAGS_DATA_END_STREAM) { stream->signal_read_event(VC_EVENT_READ_COMPLETE); } else { stream->signal_read_event(VC_EVENT_READ_READY); @@ -238,28 +257,47 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) "recv headers bad client id"); } - Http2Stream *stream = nullptr; - bool new_stream = false; + Http2Stream *stream = nullptr; + bool new_stream = false; + bool reset_header_after_decoding = false; + bool free_stream_after_decoding = false; if (cstate.is_valid_streamid(stream_id)) { stream = cstate.find_stream(stream_id); - if (stream == nullptr) { - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED, - "recv headers cannot find existing stream_id"); - } else if (stream->get_state() == Http2StreamState::HTTP2_STREAM_STATE_CLOSED) { + if (!cstate.session->is_outbound() && (stream == nullptr || !stream->trailing_header_is_possible())) { return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED, - "recv_header to closed stream"); - } else if (!stream->has_trailing_header()) { - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, "stream not expecting trailer header"); + } else if (stream == nullptr || stream->get_state() == Http2StreamState::HTTP2_STREAM_STATE_CLOSED) { + if (cstate.session->is_outbound()) { + reset_header_after_decoding = true; + // return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + // return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED, + // "recv_header to closed stream"); + } else { + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_STREAM_CLOSED, + "recv_header to closed stream"); + } } - } else { - // Create new stream - Http2Error error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); - stream = cstate.create_stream(stream_id, error); - new_stream = true; - if (!stream) { - return error; + } + + if (!http2_is_client_streamid(stream_id)) { + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, + "recv headers bad client id"); + } + + if (!stream) { + if (reset_header_after_decoding) { + free_stream_after_decoding = true; + stream = THREAD_ALLOC_INIT(http2StreamAllocator, this_ethread(), cstate.session->get_proxy_session(), stream_id, + cstate.peer_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE), true); + } else { + // Create new stream + Http2Error error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + stream = cstate.create_stream(stream_id, error); + new_stream = true; + if (!stream) { + return error; + } } } @@ -351,27 +389,36 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { // NOTE: If there are END_HEADERS flag, decode stored Header Blocks. - if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, frame.header().flags) && stream->has_trailing_header() == false) { + if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, frame.header().flags)) { return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, "recv headers end headers and not trailing header"); } - bool empty_request = false; - if (stream->has_trailing_header()) { + if (stream->trailing_header_is_possible()) { if (!(frame.header().flags & HTTP2_FLAGS_HEADERS_END_STREAM)) { return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, "recv headers tailing header without endstream"); } - // If the flag has already been set before decoding header blocks, this is the trailing header. - // Set a flag to avoid initializing fetcher for now. - // Decoding header blocks is still needed to maintain a HPACK dynamic table. - // TODO: TS-3812 - empty_request = true; } - stream->mark_milestone(Http2StreamMilestone::START_DECODE_HEADERS); + if (stream->trailing_header_is_possible()) { + stream->reset_recv_headers(); + } else { + stream->mark_milestone(Http2StreamMilestone::START_DECODE_HEADERS); + } Http2ErrorCode result = - stream->decode_header_blocks(*cstate.local_hpack_handle, cstate.server_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); + stream->decode_header_blocks(*cstate.local_hpack_handle, cstate.local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); + + // If this was an outbound connection and the state was already closed, just clear the + // headers after processing. We just processed the heaer blocks to keep the dynamic table in + // sync with peer to avoid future HPACK compression errors + if (reset_header_after_decoding) { + stream->reset_recv_headers(); + if (free_stream_after_decoding) { + THREAD_FREE(stream, http2StreamAllocator, this_ethread()); + } + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + } if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { if (result == Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR) { @@ -387,15 +434,22 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) } // Set up the State Machine - if (!empty_request) { + if (!stream->is_outbound_connection() && !stream->trailing_header_is_possible()) { SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread()); stream->mark_milestone(Http2StreamMilestone::START_TXN); stream->new_transaction(frame.is_from_early_data()); // Send request header to SM stream->send_request(cstate); } else { - // Signal VC_EVENT_READ_COMPLETE because received trailing header fields with END_STREAM flag - stream->signal_read_event(VC_EVENT_READ_COMPLETE); + // If this is a trailer, first signal to the SM that the body is done + if (stream->trailing_header_is_possible()) { + stream->set_expect_receive_trailer(); + // Propagate the trailer header + stream->send_request(cstate); + } else { + // Propagate the response + stream->send_request(cstate); + } } } else { // NOTE: Expect CONTINUATION Frame. Do NOT change state of stream or decode @@ -569,7 +623,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) Warning("Setting frame for zombied session %" PRId64, cstate.session->get_connection_id()); } - // Update SETTIGNS frame count per minute + // Update SETTINGS frame count per minute cstate.increment_received_settings_frame_count(); // Close this connection if its SETTINGS frame count exceeds a limit if (cstate.get_received_settings_frame_count() > Http2::max_settings_frames_per_minute) { @@ -645,7 +699,7 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) cstate.update_initial_rwnd(param.value); } - cstate.client_settings.set(static_cast(param.id), param.value); + cstate.peer_settings.set(static_cast(param.id), param.value); ++n_settings; } @@ -663,8 +717,8 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // [RFC 7540] 6.5. Once all values have been applied, the recipient MUST // immediately emit a SETTINGS frame with the ACK flag set. Http2SettingsFrame ack_frame(0, HTTP2_FLAGS_SETTINGS_ACK); + Http2StreamDebug(cstate.session, stream_id, "Send Setting ACK"); cstate.session->xmit(ack_frame); - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); } @@ -757,6 +811,14 @@ rcv_goaway_frame(Http2ConnectionState &cstate, const Http2Frame &frame) static_cast(goaway.error_code)); cstate.rx_error_code = {ProxyErrorClass::SSN, static_cast(goaway.error_code)}; + + if (static_cast(goaway.error_code) != 0) { + ip_port_text_buffer ipb; + const char *client_ip = ats_ip_ntop(cstate.session->get_proxy_session()->get_remote_addr(), ipb, sizeof(ipb)); + Warning("HTTP/2 rcv GOAWAY error code=0x%02x %s_ip=%s session_id=%" PRId64, static_cast(goaway.error_code), + cstate.session->is_outbound() ? "server" : "client", client_ip, cstate.session->get_connection_id()); + } + cstate.session->get_proxy_session()->do_io_close(); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); @@ -845,7 +907,7 @@ rcv_window_update_frame(Http2ConnectionState &cstate, const Http2Frame &frame) auto error = stream->increment_client_rwnd(size); if (error != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, error); + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, error, "Bad stream rwnd"); } ssize_t wnd = std::min(cstate.client_rwnd(), stream->client_rwnd()); @@ -929,7 +991,7 @@ rcv_continuation_frame(Http2ConnectionState &cstate, const Http2Frame &frame) } Http2ErrorCode result = - stream->decode_header_blocks(*cstate.local_hpack_handle, cstate.server_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); + stream->decode_header_blocks(*cstate.local_hpack_handle, cstate.local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { if (result == Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR) { @@ -1061,9 +1123,10 @@ Http2ConnectionState::init(Http2CommonSession *ssn) The client connection preface is HTTP2_CONNECTION_PREFACE. The server connection preface consists of a potentially empty SETTINGS frame. - Details in [RFC 7540] 3.5. HTTP/2 Connection Preface - - TODO: send client connection preface if the connection is outbound + [RFC 7540] 3.5. HTTP/2 Connection Preface. Upon establishment of a TCP connection and + determination that HTTP/2 will be used by both peers, each endpoint MUST + send a connection preface as a final confirmation ... The server connection + preface consists of a potentially empty SETTINGS frame. */ void Http2ConnectionState::send_connection_preface() @@ -1076,8 +1139,8 @@ Http2ConnectionState::send_connection_preface() send_settings_frame(configured_settings); - if (server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) > HTTP2_INITIAL_WINDOW_SIZE) { - send_window_update_frame(0, server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - HTTP2_INITIAL_WINDOW_SIZE); + if (local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) > HTTP2_INITIAL_WINDOW_SIZE) { + send_window_update_frame(0, local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - HTTP2_INITIAL_WINDOW_SIZE); } } @@ -1160,7 +1223,7 @@ Http2ConnectionState::rcv_frame(const Http2Frame *frame) if (error.cls == Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION) { if (error.msg) { Error("HTTP/2 connection error code=0x%02x client_ip=%s session_id=%" PRId64 " stream_id=%u %s", - static_cast(error.code), client_ip, session->get_connection_id(), stream_id, error.msg); + static_cast(error.code), client_ip, session->get_proxy_session()->connection_id(), stream_id, error.msg); } this->send_goaway_frame(this->latest_streamid_in, error.code); this->session->set_half_close_local_flag(true); @@ -1173,7 +1236,7 @@ Http2ConnectionState::rcv_frame(const Http2Frame *frame) } else if (error.cls == Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM) { if (error.msg) { Error("HTTP/2 stream error code=0x%02x client_ip=%s session_id=%" PRId64 " stream_id=%u %s", static_cast(error.code), - client_ip, session->get_connection_id(), stream_id, error.msg); + client_ip, session->get_proxy_session()->connection_id(), stream_id, error.msg); } this->send_rst_stream_frame(stream_id, error.code); } @@ -1258,7 +1321,6 @@ Http2ConnectionState::main_event_handler(int event, void *edata) } } } - return 0; } @@ -1278,8 +1340,14 @@ Http2ConnectionState::state_closed(int event, void *edata) return 0; } +bool +Http2ConnectionState::is_peer_concurrent_stream_max() const +{ + return client_streams_in_count >= peer_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); +} + Http2Stream * -Http2ConnectionState::create_stream(Http2StreamId new_id, Http2Error &error) +Http2ConnectionState::create_stream(Http2StreamId new_id, Http2Error &error, bool initiating_connection) { // first check if we've hit the active connection limit if (!session->get_netvc()->add_to_active_queue()) { @@ -1320,22 +1388,34 @@ Http2ConnectionState::create_stream(Http2StreamId new_id, Http2Error &error) // Endpoints MUST NOT exceed the limit set by their peer. An endpoint // that receives a HEADERS frame that causes their advertised concurrent // stream limit to be exceeded MUST treat this as a stream error. + int check_max_concurrent_limit; + int check_count; if (client_streamid) { - if (client_streams_in_count >= server_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)) { - error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_REFUSED_STREAM, - "recv headers creating inbound stream beyond max_concurrent limit"); - return nullptr; - } - } else { - if (client_streams_out_count >= client_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)) { - error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_REFUSED_STREAM, - "recv headers creating outbound stream beyond max_concurrent limit"); - return nullptr; - } + check_count = client_streams_in_count; + // If this is an outbound client stream, must check against the peer's max_concurrent + if (session->is_outbound()) { + check_max_concurrent_limit = peer_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + } else { // Inbound client streamm check against our own max_connecurent limits + check_max_concurrent_limit = local_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + } + } else { // Not a client stream (i.e. a push) + check_count = client_streams_out_count; + // If this is an outbound non-client stream, must check against the local max_concurrent + if (session->is_outbound()) { + check_max_concurrent_limit = local_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + } else { // Inbound non-client streamm check against the peer's max_connecurent limits + check_max_concurrent_limit = peer_settings.get(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + } + } + // If we haven't got the peers settings yet, just hope for the best + if (check_max_concurrent_limit >= 0 && check_count >= check_max_concurrent_limit) { + error = Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_REFUSED_STREAM, + "recv headers creating stream beyond max_concurrent limit"); + return nullptr; } Http2Stream *new_stream = THREAD_ALLOC_INIT(http2StreamAllocator, this_ethread(), session->get_proxy_session(), new_id, - client_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); + peer_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE), initiating_connection); ink_assert(nullptr != new_stream); ink_assert(!stream_list.in(new_stream)); @@ -1361,6 +1441,9 @@ Http2ConnectionState::create_stream(Http2StreamId new_id, Http2Error &error) new_stream->is_first_transaction_flag = get_stream_requests() == 0; increment_stream_requests(); + // Clear the session timeout. Let the transaction timeouts reign + session->get_proxy_session()->cancel_inactivity_timeout(); + return new_stream; } @@ -1376,6 +1459,17 @@ Http2ConnectionState::find_stream(Http2StreamId id) const return nullptr; } +void +Http2ConnectionState::start_streams() +{ + Http2Stream *s = stream_list.head; + while (s) { + Http2Stream *next = static_cast(s->link.next); + s->reenable_write(); + s = next; + } +} + void Http2ConnectionState::restart_streams() { @@ -1418,8 +1512,8 @@ Http2ConnectionState::restart_streams() void Http2ConnectionState::restart_receiving(Http2Stream *stream) { - uint32_t initial_rwnd = this->server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE); - uint32_t min_rwnd = std::min(initial_rwnd, this->server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)); + uint32_t initial_rwnd = this->local_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE); + uint32_t min_rwnd = std::min(initial_rwnd, this->local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)); // Connection level WINDOW UPDATE if (this->server_rwnd() < min_rwnd) { @@ -1510,6 +1604,9 @@ Http2ConnectionState::delete_stream(Http2Stream *stream) if (http2_is_client_streamid(stream->get_id())) { ink_assert(client_streams_in_count > 0); --client_streams_in_count; + if (!fini_received && !is_peer_concurrent_stream_max()) { + session->add_session(); + } } else { ink_assert(client_streams_out_count > 0); --client_streams_out_count; @@ -1543,10 +1640,14 @@ Http2ConnectionState::release_stream() // Can't do this because we just destroyed right here ^, // or we can use a local variable to do it. // session = nullptr; + } else if (shutdown_state == HTTP2_SHUTDOWN_IN_PROGRESS && fini_event == nullptr) { + session->do_clear_session_active(); + fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI); } else if (session->get_proxy_session()->is_active()) { // If the number of clients is 0, HTTP2_SESSION_EVENT_FINI is not received or sent, and session is active, // then mark the connection as inactive session->do_clear_session_active(); + session->set_no_activity_timeout(); UnixNetVConnection *vc = static_cast(session->get_netvc()); if (vc && vc->active_timeout_in == 0) { // With heavy traffic, session could be destroyed. Do not touch session after this. @@ -1567,7 +1668,7 @@ Http2ConnectionState::update_initial_rwnd(Http2WindowSize new_size) // Update stream level window sizes for (Http2Stream *s = stream_list.head; s; s = static_cast(s->link.next)) { SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread()); - s->update_initial_rwnd(new_size - (client_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - s->client_rwnd())); + s->update_initial_rwnd(new_size - (peer_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - s->client_rwnd())); } } @@ -1644,7 +1745,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len payload_length = 0; uint8_t flags = 0x00; - IOBufferReader *resp_reader = stream->response_get_data_reader(); + IOBufferReader *resp_reader = stream->send_get_data_reader(); SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread()); @@ -1687,7 +1788,8 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len return Http2SendDataFrameResult::NO_PAYLOAD; } - if (stream->is_write_vio_done()) { + if (stream->is_write_vio_done() && !resp_reader->is_read_avail_more_than(payload_length)) { + Http2StreamDebug(this->session, stream->get_id(), "End of Data Frame"); flags |= HTTP2_FLAGS_DATA_END_STREAM; } @@ -1696,8 +1798,8 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len stream->decrement_client_rwnd(payload_length); // Create frame - Http2StreamDebug(session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd", - _client_rwnd, stream->client_rwnd(), payload_length); + Http2StreamDebug(session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd flags: 0x%x", + _client_rwnd, stream->client_rwnd(), payload_length, flags); Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length); this->session->xmit(data, flags & HTTP2_FLAGS_DATA_END_STREAM); @@ -1726,18 +1828,31 @@ Http2ConnectionState::send_data_frames(Http2Stream *stream) return; } + if (zombie_event != nullptr) { + zombie_event->cancel(); + zombie_event = nullptr; + } + size_t len = 0; Http2SendDataFrameResult result = Http2SendDataFrameResult::NO_ERROR; - while (result == Http2SendDataFrameResult::NO_ERROR) { - result = send_a_data_frame(stream, len); + bool more_data = true; + IOBufferReader *resp_reader = stream->send_get_data_reader(); + while (more_data && result == Http2SendDataFrameResult::NO_ERROR) { + result = send_a_data_frame(stream, len); + more_data = resp_reader->is_read_avail_more_than(0); - if (result == Http2SendDataFrameResult::DONE) { + if (result == Http2SendDataFrameResult::DONE && !stream->is_outbound_connection()) { // Delete a stream immediately // TODO its should not be deleted for a several time to handling // RST_STREAM and WINDOW_UPDATE. // See 'closed' state written at [RFC 7540] 5.1. Http2StreamDebug(this->session, stream->get_id(), "Shutdown stream"); + stream->signal_write_event(VC_EVENT_WRITE_COMPLETE); stream->initiating_close(); + break; + } else if (stream->is_outbound_connection() && stream->is_write_vio_done()) { + stream->signal_write_event(VC_EVENT_WRITE_COMPLETE); + break; } } @@ -1753,16 +1868,20 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) Http2StreamDebug(session, stream->get_id(), "Send HEADERS frame"); - HTTPHdr *resp_hdr = &stream->response_header; - http2_convert_header_from_1_1_to_2(resp_hdr); + HTTPHdr *send_hdr = stream->get_send_header(); + if (stream->expect_send_trailer()) { + // Which is a no-op conversion + } else { + http2_convert_header_from_1_1_to_2(send_hdr); + } - uint32_t buf_len = resp_hdr->length_get() * 2; // Make it double just in case + uint32_t buf_len = send_hdr->length_get() * 2; // Make it double just in case ts::LocalBuffer local_buffer(buf_len); uint8_t *buf = local_buffer.data(); stream->mark_milestone(Http2StreamMilestone::START_ENCODE_HEADERS); - Http2ErrorCode result = http2_encode_header_blocks(resp_hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle), - client_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); + Http2ErrorCode result = http2_encode_header_blocks(send_hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle), + peer_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { return; } @@ -1771,11 +1890,24 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) if (header_blocks_size <= static_cast(BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]))) { payload_length = header_blocks_size; flags |= HTTP2_FLAGS_HEADERS_END_HEADERS; - if ((resp_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH) && resp_hdr->get_content_length() == 0) || - (!resp_hdr->expect_final_response() && stream->is_write_vio_done())) { - Http2StreamDebug(session, stream->get_id(), "END_STREAM"); - flags |= HTTP2_FLAGS_HEADERS_END_STREAM; - stream->send_end_stream = true; + if (stream->is_outbound_connection()) { // Will be sending a request_header + int method = send_hdr->method_get_wksidx(); + if (!(method == HTTP_WKSIDX_POST || method == HTTP_WKSIDX_PUSH || method == HTTP_WKSIDX_PUT) && + !send_hdr->presence(MIME_PRESENCE_TRANSFER_ENCODING) && + ((send_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH) && send_hdr->get_content_length() == 0) || + !send_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH))) { + // TODO deal with the chunked encoding case + Http2StreamDebug(session, stream->get_id(), "request END_STREAM"); + flags |= HTTP2_FLAGS_HEADERS_END_STREAM; + stream->send_end_stream = true; + } + } else { + if ((send_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH) && send_hdr->get_content_length() == 0) || + (!send_hdr->expect_final_response() && stream->is_write_vio_done())) { + Http2StreamDebug(session, stream->get_id(), "response END_STREAM"); + flags |= HTTP2_FLAGS_HEADERS_END_STREAM; + stream->send_end_stream = true; + } } stream->mark_milestone(Http2StreamMilestone::START_TX_HEADERS_FRAMES); } else { @@ -1793,6 +1925,7 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) return; } + Http2StreamDebug(session, stream->get_id(), "Send HEADERS frame flags: 0x%x length: %d", flags, payload_length); Http2HeadersFrame headers(stream->get_id(), flags, buf, payload_length); this->session->xmit(headers); uint64_t sent = payload_length; @@ -1821,7 +1954,7 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con int payload_length = 0; uint8_t flags = 0x00; - if (client_settings.get(HTTP2_SETTINGS_ENABLE_PUSH) == 0) { + if (peer_settings.get(HTTP2_SETTINGS_ENABLE_PUSH) == 0) { return false; } @@ -1853,7 +1986,7 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con uint8_t *buf = local_buffer.data(); Http2ErrorCode result = http2_encode_header_blocks(&hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle), - client_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); + peer_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE)); if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { return false; } @@ -1912,7 +2045,7 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con } } stream->change_state(HTTP2_FRAME_TYPE_PUSH_PROMISE, HTTP2_FLAGS_PUSH_PROMISE_END_HEADERS); - stream->set_request_headers(hdr); + stream->set_recv_headers(hdr); stream->new_transaction(); stream->recv_end_stream = true; // No more data with the request stream->send_request(*this); @@ -1964,18 +2097,18 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set unsigned settings_value = new_settings.get(id); // Send only difference - if (settings_value != server_settings.get(id)) { + if (settings_value != local_settings.get(id)) { Http2StreamDebug(session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(id), settings_value); params[params_size++] = {static_cast(id), settings_value}; // Update current settings - server_settings.set(id, new_settings.get(id)); + local_settings.set(id, new_settings.get(id)); } } Http2SettingsFrame settings(stream_id, HTTP2_FRAME_NO_FLAG, params, params_size); - this->session->xmit(settings); + this->session->xmit(settings, true); } void @@ -2115,12 +2248,13 @@ Http2ConnectionState::increment_client_rwnd(size_t amount) this->_recent_rwnd_increment[this->_recent_rwnd_increment_index] = amount; ++this->_recent_rwnd_increment_index; this->_recent_rwnd_increment_index %= this->_recent_rwnd_increment.size(); - double sum = std::accumulate(this->_recent_rwnd_increment.begin(), this->_recent_rwnd_increment.end(), 0.0); - double avg = sum / this->_recent_rwnd_increment.size(); - if (avg < Http2::min_avg_window_update) { - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE, this_ethread()); - return Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM; - } + // SKH Causing problems with gRPC processing. Python example resulted in amount 8 + // double sum = std::accumulate(this->_recent_rwnd_increment.begin(), this->_recent_rwnd_increment.end(), 0.0); + // double avg = sum / this->_recent_rwnd_increment.size(); + // if (avg < Http2::min_avg_window_update) { + // HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE, this_ethread()); + // return Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM; + //} return Http2ErrorCode::HTTP2_ERROR_NO_ERROR; } diff --git a/proxy/http2/Http2ConnectionState.h b/proxy/http2/Http2ConnectionState.h index caf4a957aef..3ffada5386a 100644 --- a/proxy/http2/Http2ConnectionState.h +++ b/proxy/http2/Http2ConnectionState.h @@ -1,6 +1,6 @@ /** @file - Http2ConnectionState. + Http2ConnectionState.h @section license License @@ -33,8 +33,8 @@ #include "Http2DependencyTree.h" #include "Http2FrequencyCounter.h" -class Http2CommonSession; class Http2Frame; +class Http2CommonSession; enum class Http2SendDataFrameResult { NO_ERROR = 0, @@ -87,8 +87,8 @@ class Http2ConnectionState : public Continuation ActivityCop _cop; // Settings. - Http2ConnectionSettings server_settings; - Http2ConnectionSettings client_settings; + Http2ConnectionSettings local_settings; + Http2ConnectionSettings peer_settings; void init(Http2CommonSession *ssn); void send_connection_preface(); @@ -100,9 +100,10 @@ class Http2ConnectionState : public Continuation int state_closed(int, void *); // Stream control interfaces - Http2Stream *create_stream(Http2StreamId new_id, Http2Error &error); + Http2Stream *create_stream(Http2StreamId new_id, Http2Error &error, bool initiating_connection = false); Http2Stream *find_stream(Http2StreamId id) const; void restart_streams(); + void start_streams(); bool delete_stream(Http2Stream *stream); void release_stream(); void cleanup_streams(); @@ -113,6 +114,7 @@ class Http2ConnectionState : public Continuation Http2StreamId get_latest_stream_id_out() const; int get_stream_requests() const; void increment_stream_requests(); + bool is_peer_concurrent_stream_max() const; // Continuated header decoding Http2StreamId get_continued_stream_id() const; @@ -163,6 +165,9 @@ class Http2ConnectionState : public Continuation Http2ErrorCode increment_server_rwnd(size_t amount); Http2ErrorCode decrement_server_rwnd(size_t amount); + bool no_streams() const; + bool single_stream() const; + private: unsigned _adjust_concurrent_stream(); diff --git a/proxy/http2/Http2ServerSession.cc b/proxy/http2/Http2ServerSession.cc new file mode 100644 index 00000000000..dd5258b2282 --- /dev/null +++ b/proxy/http2/Http2ServerSession.cc @@ -0,0 +1,428 @@ +/** @file + + Http2ServerSession. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "Http2ServerSession.h" +#include "HttpDebugNames.h" +#include "tscore/ink_base64.h" +#include "Http2CommonSessionInternal.h" +#include "HttpSessionManager.h" +#include "P_SSLNetVConnection.h" + +ClassAllocator http2ServerSessionAllocator("http2ServerSessionAllocator"); + +static int +send_connection_event(Continuation *cont, int event, void *edata) +{ + SCOPED_MUTEX_LOCK(lock, cont->mutex, this_ethread()); + return cont->handleEvent(event, edata); +} + +Http2ServerSession::Http2ServerSession() = default; + +void +Http2ServerSession::destroy() +{ + if (!in_destroy) { + in_destroy = true; + write_vio = nullptr; + this->remove_session(); + this->release_outbound_connection_tracking(); + REMEMBER(NO_EVENT, this->recursion) + Http2SsnDebug("session destroy"); + if (_vc) { + _vc->do_io_close(); + _vc = nullptr; + } + free(); + } +} + +void +Http2ServerSession::free() +{ + auto mutex_thread = this->mutex->thread_holding; + if (Http2CommonSession::common_free(this)) { + HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_SERVER_SESSION_COUNT, mutex_thread); + THREAD_FREE(this, http2ServerSessionAllocator, mutex_thread); + } +} + +void +Http2ServerSession::start() +{ + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + + SET_HANDLER(&Http2ServerSession::main_event_handler); + HTTP2_SET_SESSION_HANDLER(&Http2ServerSession::state_start_frame_read); + + VIO *read_vio = this->do_io_read(this, INT64_MAX, this->read_buffer); + write_vio = this->do_io_write(this, INT64_MAX, this->_write_buffer_reader); + + this->connection_state.init(this); + + // 3.5 HTTP/2 Connection Preface. Upon establishment of a TCP connection and + // determination that HTTP/2 will be used by both peers, each endpoint MUST + // send a connection preface as a final confirmation ... + // This is the preface string sent by the client + this->write_buffer->write(HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN); + total_write_len += HTTP2_CONNECTION_PREFACE_LEN; + // write_vio->nbytes = total_write_len; + write_reenable(); + Http2SsnDebug("Sent Connection Preface"); + + this->connection_state.send_connection_preface(); + + this->handleEvent(VC_EVENT_READ_READY, read_vio); +} + +void +Http2ServerSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reader) +{ + ink_assert(new_vc->mutex->thread_holding == this_ethread()); + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_SERVER_SESSION_COUNT, new_vc->mutex->thread_holding); + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_TOTAL_SERVER_CONNECTION_COUNT, new_vc->mutex->thread_holding); + this->_milestones.mark(Http2SsnMilestone::OPEN); + + // Unique client session identifier. + this->con_id = ProxySession::next_connection_id(); + this->_vc = new_vc; + _vc->set_inactivity_timeout(HRTIME_SECONDS(Http2::accept_no_activity_timeout)); + this->schedule_event = nullptr; + this->mutex = new_vc->mutex; + + this->connection_state.mutex = this->mutex; + + SSLNetVConnection *ssl_vc = dynamic_cast(new_vc); + if (ssl_vc != nullptr) { + this->read_from_early_data = ssl_vc->read_from_early_data; + Debug("ssl_early_data", "read_from_early_data = %" PRId64, this->read_from_early_data); + } + + Http2SsnDebug("session born, netvc %p", this->_vc); + + this->_vc->set_tcp_congestion_control(CLIENT_SIDE); + + this->read_buffer = iobuf ? iobuf : new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX); + this->read_buffer->water_mark = connection_state.local_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE); + this->_read_buffer_reader = reader ? reader : this->read_buffer->alloc_reader(); + + // Set write buffer size to max size of TLS record (16KB) + // This block size is the buffer size that we pass to SSLWriteBuffer + auto buffer_block_size_index = iobuffer_size_to_index(Http2::write_buffer_block_size, MAX_BUFFER_SIZE_INDEX); + this->write_buffer = new_MIOBuffer(buffer_block_size_index); + this->_write_buffer_reader = this->write_buffer->alloc_reader(); + this->_write_size_threshold = index_to_buffer_size(buffer_block_size_index) * Http2::write_size_threshold; + + this->_handle_if_ssl(new_vc); + + do_api_callout(TS_HTTP_SSN_START_HOOK); + + this->add_session(); +} + +// implement that. After we send a GOAWAY, there +// are scenarios where we would like to complete the outstanding streams. + +void +Http2ServerSession::do_io_close(int alerrno) +{ + REMEMBER(NO_EVENT, this->recursion) + Http2SsnDebug("session closed"); + + this->remove_session(); + + ink_assert(this->mutex->thread_holding == this_ethread()); + send_connection_event(&this->connection_state, HTTP2_SESSION_EVENT_FINI, this); + + { + SCOPED_MUTEX_LOCK(lock, this->connection_state.mutex, this_ethread()); + this->connection_state.release_stream(); + } + + // Destroy will be called from connection_state.release_stream() once the number of active streams goes to 0 +} + +int +Http2ServerSession::main_event_handler(int event, void *edata) +{ + ink_assert(this->mutex->thread_holding == this_ethread()); + int retval; + + recursion++; + + Event *e = static_cast(edata); + if (e == schedule_event) { + schedule_event = nullptr; + } + + Http2SsnDebug("main_event_handler=%d edata=%p", event, edata); + + switch (event) { + case VC_EVENT_READ_COMPLETE: + case VC_EVENT_READ_READY: { + bool is_zombie = connection_state.get_zombie_event() != nullptr; + retval = (this->*session_handler)(event, edata); + if (is_zombie && connection_state.get_zombie_event() != nullptr) { + Warning("Processed read event for zombie session %" PRId64, connection_id()); + } + break; + } + + case HTTP2_SESSION_EVENT_REENABLE: + // VIO will be reenableed in this handler + retval = (this->*session_handler)(VC_EVENT_READ_READY, static_cast(e->cookie)); + // Clear the event after calling session_handler to not reschedule REENABLE in it + this->_reenable_event = nullptr; + break; + + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + this->set_dying_event(event); + this->do_io_close(); + retval = 0; + break; + + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + this->connection_state.restart_streams(); + if ((Thread::get_hrtime() >= this->_write_buffer_last_flush + HRTIME_MSECONDS(this->_write_time_threshold))) { + this->flush(); + } + + retval = 0; + break; + + case HTTP2_SESSION_EVENT_XMIT: + default: + Http2SsnDebug("unexpected event=%d edata=%p", event, edata); + ink_release_assert(0); + retval = 0; + break; + } + + if (!this->is_draining() && this->connection_state.get_shutdown_reason() == Http2ErrorCode::HTTP2_ERROR_MAX) { + this->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NONE); + } + + if (this->connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NONE) { + if (this->is_draining()) { // For a case we already checked Connection header and it didn't exist + Http2SsnDebug("Preparing for graceful shutdown because of draining state"); + this->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED); + } /*else if (this->connection_state.get_stream_error_rate() > + Http2::stream_error_rate_threshold) { // For a case many stream errors happened + ip_port_text_buffer ipb; + const char *client_ip = ats_ip_ntop(get_remote_addr(), ipb, sizeof(ipb)); + SiteThrottledWarning("HTTP/2 session error origin_ip=%s session_id=%" PRId64 + " closing a connection, because its stream error rate (%f) exceeded the threshold (%f)", + client_ip, connection_id(), this->connection_state.get_stream_error_rate(), Http2::stream_error_rate_threshold); + Http2SsnDebug("Preparing for graceful shutdown because of a high stream error rate"); + cause_of_death = Http2SessionCod::HIGH_ERROR_RATE; + this->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM); + } */ + } + + if (this->connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NOT_INITIATED) { + send_connection_event(&this->connection_state, HTTP2_SESSION_EVENT_SHUTDOWN_INIT, this); + } + + recursion--; + if (!connection_state.is_recursing() && this->recursion == 0 && kill_me) { + this->free(); + } + return retval; +} + +void +Http2ServerSession::increment_current_active_connections_stat() +{ + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_ACTIVE_SERVER_CONNECTION_COUNT, this_ethread()); +} + +void +Http2ServerSession::decrement_current_active_connections_stat() +{ + HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_ACTIVE_SERVER_CONNECTION_COUNT, this_ethread()); +} + +sockaddr const * +Http2ServerSession::get_remote_addr() const +{ + return _vc ? _vc->get_remote_addr() : &cached_client_addr.sa; +} + +sockaddr const * +Http2ServerSession::get_local_addr() +{ + return _vc ? _vc->get_local_addr() : &cached_local_addr.sa; +} + +int +Http2ServerSession::get_transact_count() const +{ + return connection_state.get_stream_requests(); +} + +const char * +Http2ServerSession::get_protocol_string() const +{ + return "http/2"; +} + +void +Http2ServerSession::release(ProxyTransaction *trans) +{ +} + +int +Http2ServerSession::populate_protocol(std::string_view *result, int size) const +{ + int retval = 0; + if (size > retval) { + result[retval++] = IP_PROTO_TAG_HTTP_2_0; + if (size > retval) { + retval += super::populate_protocol(result + retval, size - retval); + } + } + return retval; +} + +const char * +Http2ServerSession::protocol_contains(std::string_view prefix) const +{ + const char *retval = nullptr; + + if (prefix.size() <= IP_PROTO_TAG_HTTP_2_0.size() && strncmp(IP_PROTO_TAG_HTTP_2_0.data(), prefix.data(), prefix.size()) == 0) { + retval = IP_PROTO_TAG_HTTP_2_0.data(); + } else { + retval = super::protocol_contains(prefix); + } + return retval; +} + +ProxySession * +Http2ServerSession::get_proxy_session() +{ + return this; +} + +ProxyTransaction * +Http2ServerSession::new_transaction() +{ + // In == client side + Http2StreamId latest_id = connection_state.get_latest_stream_id_in(); + Http2StreamId stream_id = (latest_id == 0) ? 3 : latest_id + 2; + this->set_session_active(); + + // Create a new stream/transaction + Http2Error error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + Http2Stream *stream = connection_state.create_stream(stream_id, error, true); + + if (connection_state.is_peer_concurrent_stream_max()) { + Warning("Remove SSN %" PRId64, con_id); + remove_session(); + } + + return stream; +} + +void +Http2ServerSession::add_session() +{ + if (this->in_session_table) { + return; + } + Http2SsnDebug("Add session to pool"); + EThread *ethread = this_ethread(); + ServerSessionPool *pool = ethread->server_session_pool; + MUTEX_TRY_LOCK(lock, pool->mutex, ethread); + if (lock.is_locked()) { + pool->addSession(this); + this->in_session_table = true; + } +} + +void +Http2ServerSession::remove_session() +{ + if (!this->in_session_table) { + return; + } + Http2SsnDebug("Remove session from pool"); + EThread *ethread = this_ethread(); + ServerSessionPool *pool = ethread->server_session_pool; + MUTEX_TRY_LOCK(lock, pool->mutex, ethread); + if (lock.is_locked()) { + pool->removeSession(this); + in_session_table = false; + } else { + ink_release_assert(!"How did we not get the pool lock?"); + } +} + +bool +Http2ServerSession::is_multiplexing() const +{ + return true; +} + +bool +Http2ServerSession::is_outbound() const +{ + return true; +} + +void +Http2ServerSession::set_netvc(NetVConnection *netvc) +{ + super::set_netvc(netvc); + if (netvc == nullptr) { + write_vio = nullptr; + } +} + +void +Http2ServerSession::set_no_activity_timeout() +{ + // Only set if not previously set + if (this->_vc->get_inactivity_timeout() == 0) { + this->set_inactivity_timeout(HRTIME_SECONDS(Http2::no_activity_timeout_out)); + } +} + +HTTPVersion +Http2ServerSession::get_version(HTTPHdr &hdr) const +{ + return HTTP_2_0; +} + +IOBufferReader * +Http2ServerSession::get_remote_reader() +{ + return _read_buffer_reader; +} + +std::function Create_h2_server_session = []() -> PoolableSession * { + return http2ServerSessionAllocator.alloc(); +}; diff --git a/proxy/http2/Http2ServerSession.h b/proxy/http2/Http2ServerSession.h new file mode 100644 index 00000000000..6bd4e330ff0 --- /dev/null +++ b/proxy/http2/Http2ServerSession.h @@ -0,0 +1,94 @@ +/** @file + + Http2ServerSession. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include "Plugin.h" +#include "Http2CommonSession.h" +#include +#include "tscore/ink_inet.h" +#include "tscore/History.h" +#include "Milestones.h" +#include "PoolableSession.h" + +class Http2ServerSession : public PoolableSession, public Http2CommonSession +{ +public: + using super = PoolableSession; ///< Parent type. + using SessionHandler = int (Http2ServerSession::*)(int, void *); + + Http2ServerSession(); + + ///////////////////// + // Methods + + // Implement VConnection interface + void do_io_close(int lerrno = -1) override; + + // Implement ProxySession interface + void new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reader) override; + void start() override; + void destroy() override; + void release(ProxyTransaction *trans) override; + void free() override; + ProxyTransaction *new_transaction() override; + + void add_session() override; + void remove_session(); + + //////////////////// + // Accessors + sockaddr const *get_remote_addr() const override; + sockaddr const *get_local_addr() override; + int get_transact_count() const override; + const char *get_protocol_string() const override; + int populate_protocol(std::string_view *result, int size) const override; + const char *protocol_contains(std::string_view prefix) const override; + HTTPVersion get_version(HTTPHdr &hdr) const override; + void increment_current_active_connections_stat() override; + void decrement_current_active_connections_stat() override; + IOBufferReader *get_remote_reader() override; + + ProxySession *get_proxy_session() override; + + // noncopyable + Http2ServerSession(Http2ServerSession &) = delete; + Http2ServerSession &operator=(const Http2ServerSession &) = delete; + + bool is_multiplexing() const override; + bool is_outbound() const override; + + void set_netvc(NetVConnection *netvc) override; + + void set_no_activity_timeout() override; + +private: + int main_event_handler(int, void *); + + IpEndpoint cached_client_addr; + IpEndpoint cached_local_addr; + + bool in_session_table = false; +}; + +extern ClassAllocator http2ServerSessionAllocator; diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index 87747e4d900..ead7cab7646 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -25,6 +25,7 @@ #include "HTTP2.h" #include "Http2ClientSession.h" +#include "Http2ServerSession.h" #include "../http/HttpSM.h" #include @@ -39,25 +40,32 @@ ClassAllocator http2StreamAllocator("http2StreamAllocator"); -Http2Stream::Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_rwnd) +Http2Stream::Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_rwnd, bool outbound_connection) : super(session), _id(sid), _client_rwnd(initial_rwnd) { SET_HANDLER(&Http2Stream::main_event_handler); this->mark_milestone(Http2StreamMilestone::OPEN); - this->_sm = nullptr; - this->_id = sid; - this->_thread = this_ethread(); - this->_client_rwnd = initial_rwnd; - this->_server_rwnd = Http2::initial_window_size; + this->_sm = nullptr; + this->_id = sid; + this->_thread = this_ethread(); + this->_state = Http2StreamState::HTTP2_STREAM_STATE_IDLE; + this->_outbound_flag = outbound_connection; + this->_client_rwnd = initial_rwnd; + this->_server_rwnd = Http2::initial_window_size; - this->_reader = this->_request_buffer.alloc_reader(); + this->_reader = this->_recv_buffer.alloc_reader(); - _req_header.create(HTTP_TYPE_REQUEST); - response_header.create(HTTP_TYPE_RESPONSE); - // TODO: init _req_header instead of response_header if this Http2Stream is outgoing - http2_init_pseudo_headers(response_header); + if (this->is_outbound_connection()) { // Flip the sense of the expected headers. Fix naming later + _recv_header.create(HTTP_TYPE_RESPONSE); + _send_header.create(HTTP_TYPE_REQUEST); + } else { + _recv_header.create(HTTP_TYPE_REQUEST); + _send_header.create(HTTP_TYPE_RESPONSE); + } + + http2_init_pseudo_headers(_send_header); http_parser_init(&http_parser); } @@ -66,6 +74,14 @@ Http2Stream::~Http2Stream() { REMEMBER(NO_EVENT, this->reentrancy_count); Http2StreamDebug("Destroy stream, sent %" PRIu64 " bytes", this->bytes_sent); + + // In the case of a temporary stream used to parse the header to keep the HPACK + // up to date, there may not be a mutex. Nothing was set up, so nothing to + // clean up in the destructor + if (this->mutex == nullptr) { + return; + } + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); // Clean up after yourself if this was an EOS ink_release_assert(this->closed); @@ -77,16 +93,16 @@ Http2Stream::~Http2Stream() if (_proxy_ssn) { cid = _proxy_ssn->connection_id(); - Http2ClientSession *h2_proxy_ssn = static_cast(_proxy_ssn); - SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->mutex, this_ethread()); + SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); + Http2ConnectionState &connection_state = this->get_connection_state(); // Make sure the stream is removed from the stream list and priority tree // In many cases, this has been called earlier, so this call is a no-op - h2_proxy_ssn->connection_state.delete_stream(this); + connection_state.delete_stream(this); - h2_proxy_ssn->connection_state.decrement_stream_count(); + connection_state.decrement_stream_count(); // Update session's stream counts, so it accurately goes into keep-alive state - h2_proxy_ssn->connection_state.release_stream(); + connection_state.release_stream(); // Do not access `_proxy_ssn` in below. It might be freed by `release_stream`. } @@ -119,11 +135,11 @@ Http2Stream::~Http2Stream() this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::CLOSE)); } - _req_header.destroy(); - response_header.destroy(); + _recv_header.destroy(); + _send_header.destroy(); // Drop references to all buffer data - this->_request_buffer.clear(); + this->_recv_buffer.clear(); // Free the mutexes in the VIO read_vio.mutex.clear(); @@ -190,6 +206,8 @@ Http2Stream::main_event_handler(int event, void *edata) } break; case VC_EVENT_READ_COMPLETE: + read_vio.nbytes = read_vio.ndone; + /* fall through */ case VC_EVENT_READ_READY: _timeout.update_inactivity(); if (e->cookie == &read_vio) { @@ -203,10 +221,14 @@ Http2Stream::main_event_handler(int event, void *edata) case VC_EVENT_EOS: if (e->cookie == &read_vio) { SCOPED_MUTEX_LOCK(lock, read_vio.mutex, this_ethread()); - read_vio.cont->handleEvent(VC_EVENT_EOS, &read_vio); + if (read_vio.cont) { + read_vio.cont->handleEvent(VC_EVENT_EOS, &read_vio); + } } else if (e->cookie == &write_vio) { SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); - write_vio.cont->handleEvent(VC_EVENT_EOS, &write_vio); + if (write_vio.cont) { + write_vio.cont->handleEvent(VC_EVENT_EOS, &write_vio); + } } break; } @@ -220,18 +242,29 @@ Http2Stream::main_event_handler(int event, void *edata) Http2ErrorCode Http2Stream::decode_header_blocks(HpackHandle &hpack_handle, uint32_t maximum_table_size) { - return http2_decode_header_blocks(&_req_header, (const uint8_t *)header_blocks, header_blocks_length, nullptr, hpack_handle, - trailing_header, maximum_table_size); + return http2_decode_header_blocks(&_recv_header, (const uint8_t *)header_blocks, header_blocks_length, nullptr, hpack_handle, + _possible_trailing_header, maximum_table_size, _outbound_flag); } void Http2Stream::send_request(Http2ConnectionState &cstate) { - ink_release_assert(this->_sm != nullptr); - this->_http_sm_id = this->_sm->sm_id; + if (closed) { + return; + } + REMEMBER(NO_EVENT, this->reentrancy_count); // Convert header to HTTP/1.1 format - http2_convert_header_from_2_to_1_1(&_req_header); + http2_convert_header_from_2_to_1_1(&_recv_header); + + if (this->expect_send_trailer()) { + // Send read complete to terminate previous data tunnel + this->read_vio.nbytes = this->read_vio.ndone; + this->signal_read_event(VC_EVENT_READ_COMPLETE); + } + + ink_release_assert(this->_sm != nullptr); + this->_http_sm_id = this->_sm->sm_id; // Write header to a buffer. Borrowing logic from HttpSM::write_header_into_buffer. // Seems like a function like this ought to be in HTTPHdr directly @@ -241,16 +274,16 @@ Http2Stream::send_request(Http2ConnectionState &cstate) do { bufindex = 0; tmp = dumpoffset; - IOBufferBlock *block = this->_request_buffer.get_current_block(); + IOBufferBlock *block = this->_recv_buffer.get_current_block(); if (!block) { - this->_request_buffer.add_block(); - block = this->_request_buffer.get_current_block(); + this->_recv_buffer.add_block(); + block = this->_recv_buffer.get_current_block(); } - done = _req_header.print(block->start(), block->write_avail(), &bufindex, &tmp); + done = _recv_header.print(block->end(), block->write_avail(), &bufindex, &tmp); dumpoffset += bufindex; - this->_request_buffer.fill(bufindex); + this->_recv_buffer.fill(bufindex); if (!done) { - this->_request_buffer.add_block(); + this->_recv_buffer.add_block(); } } while (!done); @@ -259,16 +292,18 @@ Http2Stream::send_request(Http2ConnectionState &cstate) return; } - // Is the _sm ready to process the header? - if (this->read_vio.nbytes > 0) { - if (this->recv_end_stream) { - this->read_vio.nbytes = bufindex; - this->signal_read_event(VC_EVENT_READ_COMPLETE); + if (this->recv_end_stream) { + this->read_vio.nbytes = bufindex; + this->read_vio.ndone = bufindex; + if (_outbound_flag) { + this->signal_read_event(VC_EVENT_EOS); } else { - // End of header but not end of stream, must have some body frames coming - this->has_body = true; - this->signal_read_event(VC_EVENT_READ_READY); + this->signal_read_event(VC_EVENT_READ_COMPLETE); } + } else { + // End of header but not end of stream, must have some body frames coming + this->has_body = true; + this->signal_read_event(VC_EVENT_READ_READY); } } @@ -305,7 +340,11 @@ Http2Stream::change_state(uint8_t type, uint8_t flags) _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; } else if (type == HTTP2_FRAME_TYPE_HEADERS || type == HTTP2_FRAME_TYPE_DATA) { if (recv_end_stream) { - _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + if (send_end_stream) { + _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; + } else { + _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } } else if (send_end_stream) { _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; } else { @@ -340,8 +379,8 @@ Http2Stream::change_state(uint8_t type, uint8_t flags) _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; } else { // Error, set state closed - _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; - return false; + //_state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; + // return false; } break; @@ -356,8 +395,8 @@ Http2Stream::change_state(uint8_t type, uint8_t flags) return true; } else { // Error, set state closed - _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; - return false; + //_state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; + // return false; } break; @@ -409,10 +448,11 @@ Http2Stream::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *abuffe write_vio.ndone = 0; write_vio.vc_server = this; write_vio.op = VIO::WRITE; + _send_reader = abuffer; - if (c != nullptr && nbytes > 0 && this->is_client_state_writeable()) { + if (c != nullptr && nbytes > 0 && this->is_state_writeable()) { update_write_request(false); - } else if (!this->is_client_state_writeable()) { + } else if (!this->is_state_writeable()) { // Cannot start a write on a closed stream return nullptr; } @@ -429,12 +469,16 @@ Http2Stream::do_io_close(int /* flags */) REMEMBER(NO_EVENT, this->reentrancy_count); Http2StreamDebug("do_io_close"); + if (this->is_state_writeable()) { // Let the other end know we are going away + this->get_connection_state().send_rst_stream_frame(_id, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); + } + // When we get here, the SM has initiated the shutdown. Either it received a WRITE_COMPLETE, or it is shutting down. Any // remaining IO operations back to client should be abandoned. The SM-side buffers backing these operations will be deleted // by the time this is called from transaction_done. closed = true; - if (_proxy_ssn && this->is_client_state_writeable()) { + if (_proxy_ssn && this->is_state_writeable()) { // Make sure any trailing end of stream frames are sent // We will be removed at send_data_frames or closing connection phase Http2ClientSession *h2_proxy_ssn = static_cast(this->_proxy_ssn); @@ -445,7 +489,7 @@ Http2Stream::do_io_close(int /* flags */) _clear_timers(); clear_io_events(); - // Wait until transaction_done is called from HttpSM to signal that the TXN_CLOSE hook has been executed + // Otherwise, Wait until transaction_done is called from HttpSM to signal that the TXN_CLOSE hook has been executed } } @@ -461,7 +505,8 @@ Http2Stream::transaction_done() if (!closed) { do_io_close(); // Make sure we've been closed. If we didn't close the _proxy_ssn session better still be open } - ink_release_assert(closed || !static_cast(_proxy_ssn)->connection_state.is_state_closed()); + Http2ConnectionState &state = this->get_connection_state(); + ink_release_assert(closed || !state.is_state_closed()); _sm = nullptr; if (closed) { @@ -476,11 +521,11 @@ Http2Stream::transaction_done() void Http2Stream::terminate_if_possible() { - if (terminate_stream && reentrancy_count == 0) { + // if (terminate_stream && reentrancy_count == 0) { + if (reentrancy_count == 0 && closed && terminate_stream) { REMEMBER(NO_EVENT, this->reentrancy_count); - Http2ClientSession *h2_proxy_ssn = static_cast(this->_proxy_ssn); - SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->mutex, this_ethread()); + SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); THREAD_FREE(this, http2StreamAllocator, this_ethread()); } } @@ -492,7 +537,12 @@ Http2Stream::initiating_close() if (!closed) { SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); REMEMBER(NO_EVENT, this->reentrancy_count); - Http2StreamDebug("initiating_close"); + Http2StreamDebug("initiating_close client_window=%" PRId64 " session_window=%" PRId64, _client_rwnd, + this->get_connection_state().client_rwnd()); + + if (this->is_state_writeable()) { // Let the other end know we are going away + this->get_connection_state().send_rst_stream_frame(_id, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); + } // Set the state of the connection to closed // TODO - these states should be combined @@ -513,30 +563,32 @@ Http2Stream::initiating_close() // We are sending signals rather than calling the handlers directly to avoid the case where // the HttpTunnel handler causes the HttpSM to be deleted on the stack. bool sent_write_complete = false; + bool sent_eos = false; if (_sm) { // Push out any last IO events - if (write_vio.cont) { + if (write_vio.cont && (write_vio.nbytes > write_vio.ndone || write_vio.nbytes == 0)) { SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); - // Are we done? - if (write_vio.nbytes > 0 && write_vio.nbytes == write_vio.ndone) { - Http2StreamDebug("handle write from destroy (event=%d)", VC_EVENT_WRITE_COMPLETE); - write_event = send_tracked_event(write_event, VC_EVENT_WRITE_COMPLETE, &write_vio); - } else { - write_event = send_tracked_event(write_event, VC_EVENT_EOS, &write_vio); - Http2StreamDebug("handle write from destroy (event=%d)", VC_EVENT_EOS); - } + write_event = send_tracked_event(write_event, VC_EVENT_EOS, &write_vio); + Http2StreamDebug("handle write from destroy (event=%d)", VC_EVENT_EOS); + sent_eos = true; + } else if (write_vio.cont && write_vio.nbytes == write_vio.ndone) { + SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); + write_event = send_tracked_event(write_event, VC_EVENT_WRITE_COMPLETE, &write_vio); + Http2StreamDebug("handle write from destroy (event=%d)", VC_EVENT_EOS); sent_write_complete = true; } - } - // Send EOS to let SM know that we aren't sticking around - if (_sm && read_vio.cont) { - // Only bother with the EOS if we haven't sent the write complete - if (!sent_write_complete) { + // Send EOS to let SM know that we aren't sticking around + if (_sm && read_vio.cont && !sent_eos) { SCOPED_MUTEX_LOCK(lock, read_vio.mutex, this_ethread()); Http2StreamDebug("send EOS to read cont"); read_event = send_tracked_event(read_event, VC_EVENT_EOS, &read_vio); + } else if (!sent_write_complete && !sent_eos) { + Http2StreamDebug("send EOS to SM"); + // Just send EOS to the _sm + _sm->handleEvent(VC_EVENT_EOS, nullptr); } - } else if (!sent_write_complete) { + } else if (!terminate_stream) { + Http2StreamDebug("No SM to signal"); // Transaction is already gone or not started. Kill yourself terminate_stream = true; terminate_if_possible(); @@ -577,7 +629,7 @@ Http2Stream::update_read_request(bool call_update) ink_release_assert(this->_thread == this_ethread()); SCOPED_MUTEX_LOCK(lock, read_vio.mutex, this_ethread()); - if (read_vio.nbytes == 0) { + if (read_vio.nbytes == 0 || read_vio.is_disabled()) { return; } @@ -604,11 +656,12 @@ Http2Stream::update_read_request(bool call_update) void Http2Stream::restart_sending() { - if (!this->response_header_done) { + if (!this->parsing_header_done) { + this->update_write_request(true); return; } - IOBufferReader *reader = this->response_get_data_reader(); + IOBufferReader *reader = this->send_get_data_reader(); if (reader && !reader->is_read_avail_more_than(0)) { return; } @@ -617,14 +670,14 @@ Http2Stream::restart_sending() return; } - this->send_response_body(true); + this->send_body(true); } void Http2Stream::update_write_request(bool call_update) { - if (!this->is_client_state_writeable() || closed || _proxy_ssn == nullptr || write_vio.mutex == nullptr || - write_vio.get_reader() == nullptr) { + if (!this->is_state_writeable() || closed || _proxy_ssn == nullptr || write_vio.mutex == nullptr || + write_vio.get_reader() == nullptr || this->_send_reader == nullptr) { return; } @@ -634,73 +687,92 @@ Http2Stream::update_write_request(bool call_update) } ink_release_assert(this->_thread == this_ethread()); - Http2ClientSession *h2_proxy_ssn = static_cast(this->_proxy_ssn); + Http2StreamDebug("update_write_request parse_done=%d", parsing_header_done); + + Http2ConnectionState &connection_state = this->get_connection_state(); SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); IOBufferReader *vio_reader = write_vio.get_reader(); - if (write_vio.ntodo() == 0 || !vio_reader->is_read_avail_more_than(0)) { + if (write_vio.ntodo() > 0 && (!vio_reader->is_read_avail_more_than(0) || + // If there is no window left, just give up now too + std::min(_client_rwnd, this->get_connection_state().client_rwnd()) == 0)) { + Http2StreamDebug("update_write_request give up without doing anything ntodo=%" PRId64 " is_read_avail=%d client_window=%" PRId64 + " session_window=%" PRId64, + write_vio.ntodo(), vio_reader->is_read_avail_more_than(0), _client_rwnd, + this->get_connection_state().client_rwnd()); return; } // Process the new data - if (!this->response_header_done) { - // Still parsing the response_header + if (!this->parsing_header_done) { + // Still parsing the request or response header int bytes_used = 0; - int state = this->response_header.parse_resp(&http_parser, vio_reader, &bytes_used, false); - // HTTPHdr::parse_resp() consumed the vio_reader in above (consumed size is `bytes_used`) + int state; + if (this->is_outbound_connection()) { + state = this->_send_header.parse_req(&http_parser, this->_send_reader, &bytes_used, false); + } else { + state = this->_send_header.parse_resp(&http_parser, this->_send_reader, &bytes_used, false); + } + // HTTPHdr::parse_resp() consumed the send_reader in above write_vio.ndone += bytes_used; switch (state) { case PARSE_RESULT_DONE: { - this->response_header_done = true; + this->parsing_header_done = true; + Http2StreamDebug("update_write_request parsing done, read %d bytes", bytes_used); // Schedule session shutdown if response header has "Connection: close" - MIMEField *field = this->response_header.field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION); + MIMEField *field = this->_send_header.field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION); if (field) { int len; const char *value = field->value_get(&len); if (memcmp(HTTP_VALUE_CLOSE, value, HTTP_LEN_CLOSE) == 0) { - SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->mutex, this_ethread()); - if (h2_proxy_ssn->connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NONE) { - h2_proxy_ssn->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); + SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); + if (connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NONE) { + connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); } } } { - SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->mutex, this_ethread()); + SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); // Send the response header back - h2_proxy_ssn->connection_state.send_headers_frame(this); + connection_state.send_headers_frame(this); } // Roll back states of response header to read final response - if (this->response_header.expect_final_response()) { - this->response_header_done = false; - response_header.destroy(); - response_header.create(HTTP_TYPE_RESPONSE); - http2_init_pseudo_headers(response_header); + if (!this->is_outbound_connection() && this->_send_header.expect_final_response()) { + this->parsing_header_done = false; + } + if (this->is_outbound_connection() || this->_send_header.expect_final_response()) { + _send_header.destroy(); + _send_header.create(this->is_outbound_connection() ? HTTP_TYPE_REQUEST : HTTP_TYPE_RESPONSE); + http2_init_pseudo_headers(_send_header); http_parser_clear(&http_parser); http_parser_init(&http_parser); } - + bool final_write = this->write_vio.ntodo() == 0; this->signal_write_event(call_update); - if (vio_reader->is_read_avail_more_than(0)) { + if (!final_write && this->_send_reader->is_read_avail_more_than(0)) { + Http2StreamDebug("update_write_request done parsing, still more to send"); this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES); - this->send_response_body(call_update); + this->send_body(call_update); } break; } case PARSE_RESULT_CONT: // Let it ride for next time + Http2StreamDebug("update_write_request still parsing, read %d bytes", bytes_used); break; default: + Http2StreamDebug("update_write_request state %d, read %d bytes", state, bytes_used); break; } } else { this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES); - this->send_response_body(call_update); + this->send_body(call_update); } return; @@ -709,10 +781,12 @@ Http2Stream::update_write_request(bool call_update) void Http2Stream::signal_read_event(int event) { - if (this->read_vio.cont == nullptr || this->read_vio.cont->mutex == nullptr || this->read_vio.op == VIO::NONE) { + if (this->terminate_stream || this->closed || this->read_vio.cont == nullptr || this->read_vio.cont->mutex == nullptr || + this->read_vio.op == VIO::NONE) { return; } + reentrancy_count++; MUTEX_TRY_LOCK(lock, read_vio.cont->mutex, this_ethread()); if (lock.is_locked()) { _timeout.update_inactivity(); @@ -723,6 +797,9 @@ Http2Stream::signal_read_event(int event) } this->_read_vio_event = this_ethread()->schedule_in(this, retry_delay, event, &read_vio); } + reentrancy_count--; + // Clean stream up if the terminate flag is set and we are at the bottom of the handler stack + terminate_if_possible(); } void @@ -730,10 +807,11 @@ Http2Stream::signal_write_event(int event) { // Don't signal a write event if in fact nothing was written if (this->write_vio.cont == nullptr || this->write_vio.cont->mutex == nullptr || this->write_vio.op == VIO::NONE || - this->write_vio.nbytes == 0) { + this->terminate_stream) { return; } + reentrancy_count++; MUTEX_TRY_LOCK(lock, write_vio.cont->mutex, this_ethread()); if (lock.is_locked()) { _timeout.update_inactivity(); @@ -744,21 +822,29 @@ Http2Stream::signal_write_event(int event) } this->_write_vio_event = this_ethread()->schedule_in(this, retry_delay, event, &write_vio); } + reentrancy_count--; + // Clean stream up if the terminate flag is set and we are at the bottom of the handler stack + terminate_if_possible(); } void Http2Stream::signal_write_event(bool call_update) { - if (this->write_vio.cont == nullptr || this->write_vio.op == VIO::NONE) { - return; - } - - if (this->write_vio.get_writer()->write_avail() == 0) { + if (this->write_vio.cont == nullptr || this->write_vio.op == VIO::NONE || this->terminate_stream) { return; } int send_event = this->write_vio.ntodo() == 0 ? VC_EVENT_WRITE_COMPLETE : VC_EVENT_WRITE_READY; + if (this->write_vio.ntodo() > 0 && this->write_vio.get_writer()->write_avail() == 0) { + if (!this->is_state_writeable() && this->_outbound_flag) { + send_event = VC_EVENT_EOS; + } else { + return; + } + } + reentrancy_count++; + if (call_update) { // Coming from reenable. Safe to call the handler directly if (write_vio.cont && this->_sm) { @@ -768,34 +854,52 @@ Http2Stream::signal_write_event(bool call_update) // Called from do_io_write. Might still be setting up state. Send an event to let the dust settle write_event = send_tracked_event(write_event, send_event, &write_vio); } + reentrancy_count--; + // Clean stream up if the terminate flag is set and we are at the bottom of the handler stack + terminate_if_possible(); } bool Http2Stream::push_promise(URL &url, const MIMEField *accept_encoding) { - Http2ClientSession *h2_proxy_ssn = static_cast(this->_proxy_ssn); - SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->mutex, this_ethread()); - return h2_proxy_ssn->connection_state.send_push_promise_frame(this, url, accept_encoding); + SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); + return this->get_connection_state().send_push_promise_frame(this, url, accept_encoding); } void -Http2Stream::send_response_body(bool call_update) +Http2Stream::send_body(bool call_update) { - Http2ClientSession *h2_proxy_ssn = static_cast(this->_proxy_ssn); + Http2ConnectionState &connection_state = this->get_connection_state(); _timeout.update_inactivity(); + reentrancy_count++; + + SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); if (Http2::stream_priority_enabled) { - SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->mutex, this_ethread()); - h2_proxy_ssn->connection_state.schedule_stream(this); + connection_state.schedule_stream(this); // signal_write_event() will be called from `Http2ConnectionState::send_data_frames_depends_on_priority()` // when write_vio is consumed } else { - SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->mutex, this_ethread()); - h2_proxy_ssn->connection_state.send_data_frames(this); + connection_state.send_data_frames(this); + if (write_vio.ntodo() == 0) { // Clear the header processing for possible future trailing header + this->parsing_header_done = 0; + } this->signal_write_event(call_update); // XXX The call to signal_write_event can destroy/free the Http2Stream. // Don't modify the Http2Stream after calling this method. } + + reentrancy_count--; + terminate_if_possible(); +} + +void +Http2Stream::reenable_write() +{ + if (this->_proxy_ssn) { + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + update_write_request(true); + } } void @@ -806,22 +910,20 @@ Http2Stream::reenable(VIO *vio) SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); update_write_request(true); } else if (vio->op == VIO::READ) { - Http2ClientSession *h2_proxy_ssn = static_cast(this->_proxy_ssn); - { - SCOPED_MUTEX_LOCK(ssn_lock, h2_proxy_ssn->mutex, this_ethread()); - h2_proxy_ssn->connection_state.restart_receiving(this); - } + SCOPED_MUTEX_LOCK(ssn_lock, _proxy_ssn->mutex, this_ethread()); + Http2ConnectionState &connection_state = this->get_connection_state(); + connection_state.restart_receiving(this); - SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); - update_read_request(true); + // SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + // update_read_request(true); } } } IOBufferReader * -Http2Stream::response_get_data_reader() const +Http2Stream::send_get_data_reader() const { - return write_vio.get_reader(); + return this->_send_reader; } void @@ -899,14 +1001,23 @@ Http2Stream::release() void Http2Stream::increment_transactions_stat() { - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, _thread); - HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_TOTAL_CLIENT_STREAM_COUNT, _thread); + if (_outbound_flag) { + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_SERVER_STREAM_COUNT, _thread); + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_TOTAL_SERVER_STREAM_COUNT, _thread); + } else { + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, _thread); + HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_TOTAL_CLIENT_STREAM_COUNT, _thread); + } } void Http2Stream::decrement_transactions_stat() { - HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, _thread); + if (_outbound_flag) { + HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_SERVER_STREAM_COUNT, _thread); + } else { + HTTP2_DECREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, _thread); + } } ssize_t @@ -1012,3 +1123,68 @@ Http2Stream::has_request_body(int64_t content_length, bool is_chunked_set) const { return has_body; } + +Http2ConnectionState & +Http2Stream::get_connection_state() +{ + if (_outbound_flag) { + Http2ServerSession *session = static_cast(_proxy_ssn); + return session->connection_state; + } else { + Http2ClientSession *session = static_cast(_proxy_ssn); + return session->connection_state; + } +} + +bool +Http2Stream::is_read_closed() const +{ + return this->recv_end_stream; +} + +bool +Http2Stream::expect_send_trailer() const +{ + return this->_expect_send_trailer; +} + +void +Http2Stream::set_expect_send_trailer() +{ + _expect_send_trailer = true; + parsing_header_done = false; + reset_send_headers(); +} +bool +Http2Stream::expect_receive_trailer() const +{ + return this->_expect_receive_trailer; +} + +void +Http2Stream::set_expect_receive_trailer() +{ + _expect_receive_trailer = true; +} + +void +Http2Stream::set_rx_error_code(ProxyError e) +{ + if (!this->is_outbound_connection() && this->_sm) { + this->_sm->t_state.client_info.rx_error_code = e; + } +} + +void +Http2Stream::set_tx_error_code(ProxyError e) +{ + if (!this->is_outbound_connection() && this->_sm) { + this->_sm->t_state.client_info.tx_error_code = e; + } +} + +HTTPVersion +Http2Stream::get_version(HTTPHdr &hdr) const +{ + return HTTP_2_0; +} diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h index 03d9decea09..c9947cfb811 100644 --- a/proxy/http2/Http2Stream.h +++ b/proxy/http2/Http2Stream.h @@ -55,13 +55,14 @@ class Http2Stream : public ProxyTransaction using super = ProxyTransaction; ///< Parent type. Http2Stream() {} // Just to satisfy ClassAllocator - Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_rwnd); + Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_rwnd, bool outbound_connection = false); ~Http2Stream(); int main_event_handler(int event, void *edata); void release() override; void reenable(VIO *vio) override; + void reenable_write(); void transaction_done() override; void do_io_shutdown(ShutdownHowTo_t) override {} @@ -69,9 +70,19 @@ class Http2Stream : public ProxyTransaction VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *abuffer, bool owner = false) override; void do_io_close(int lerrno = -1) override; + bool expect_send_trailer() const override; + void set_expect_send_trailer() override; + bool expect_receive_trailer() const override; + void set_expect_receive_trailer() override; + Http2ErrorCode decode_header_blocks(HpackHandle &hpack_handle, uint32_t maximum_table_size); void send_request(Http2ConnectionState &cstate); void initiating_close(); + bool + is_outbound_connection() const + { + return _outbound_flag; + } void terminate_if_possible(); void update_read_request(bool send_update); void update_write_request(bool send_update); @@ -106,14 +117,27 @@ class Http2Stream : public ProxyTransaction int get_transaction_id() const override; int get_transaction_priority_weight() const override; int get_transaction_priority_dependence() const override; + bool is_read_closed() const override; + + HTTPHdr * + get_send_header() + { + return &_send_header; + } + + void read_update(int count); + void read_done(); void clear_io_events(); - bool is_client_state_writeable() const; + bool is_state_writeable() const; bool is_closed() const; - IOBufferReader *response_get_data_reader() const; + IOBufferReader *send_get_data_reader() const; + void set_rx_error_code(ProxyError e) override; + void set_tx_error_code(ProxyError e) override; bool has_request_body(int64_t content_length, bool is_chunked_set) const override; + HTTPVersion get_version(HTTPHdr &hdr) const override; void mark_milestone(Http2StreamMilestone type); @@ -125,29 +149,36 @@ class Http2Stream : public ProxyTransaction Http2StreamState get_state() const; bool change_state(uint8_t type, uint8_t flags); void update_initial_rwnd(Http2WindowSize new_size); - bool has_trailing_header() const; - void set_request_headers(HTTPHdr &h2_headers); + bool trailing_header_is_possible() const; + void set_trailing_header_is_possible(); + void set_recv_headers(HTTPHdr &h2_headers); + void reset_recv_headers(); + void reset_send_headers(); MIOBuffer *read_vio_writer() const; int64_t read_vio_read_avail(); + bool read_enabled() const; ////////////////// // Variables uint8_t *header_blocks = nullptr; - uint32_t header_blocks_length = 0; // total length of header blocks (not include Padding or other fields) + uint32_t header_blocks_length = 0; // total length of header blocks (not include + // Padding or other fields) bool recv_end_stream = false; bool send_end_stream = false; - bool response_header_done = false; + bool parsing_header_done = false; bool is_first_transaction_flag = false; - HTTPHdr response_header; + HTTPHdr _send_header; + IOBufferReader *_send_reader = nullptr; Http2DependencyTree::Node *priority_node = nullptr; + Http2ConnectionState &get_connection_state(); + private: - bool response_is_data_available() const; Event *send_tracked_event(Event *event, int send_event, VIO *vio); - void send_response_body(bool call_update); + void send_body(bool call_update); void _clear_timers(); /** @@ -164,17 +195,21 @@ class Http2Stream : public ProxyTransaction Http2StreamState _state = Http2StreamState::HTTP2_STREAM_STATE_IDLE; int64_t _http_sm_id = -1; - HTTPHdr _req_header; - MIOBuffer _request_buffer = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; - int64_t read_vio_nbytes; + HTTPHdr _recv_header; + MIOBuffer _recv_buffer = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; VIO read_vio; VIO write_vio; History _history; Milestones(Http2StreamMilestone::LAST_ENTRY)> _milestones; - bool trailing_header = false; - bool has_body = false; + bool _possible_trailing_header = false; + bool _expect_send_trailer = false; + bool _expect_receive_trailer = false; + + bool has_body = false; + + bool _outbound_flag = false; // A brief discussion of similar flags and state variables: _state, closed, terminate_stream // @@ -264,15 +299,35 @@ Http2Stream::update_initial_rwnd(Http2WindowSize new_size) } inline bool -Http2Stream::has_trailing_header() const +Http2Stream::trailing_header_is_possible() const +{ + return _possible_trailing_header; +} + +inline void +Http2Stream::set_trailing_header_is_possible() +{ + _possible_trailing_header = true; +} + +inline void +Http2Stream::set_recv_headers(HTTPHdr &h2_headers) +{ + _recv_header.copy(&h2_headers); +} + +inline void +Http2Stream::reset_recv_headers() { - return trailing_header; + this->_recv_header.destroy(); + this->_recv_header.create(HTTP_TYPE_RESPONSE); } inline void -Http2Stream::set_request_headers(HTTPHdr &h2_headers) +Http2Stream::reset_send_headers() { - _req_header.copy(&h2_headers); + this->_send_header.destroy(); + this->_send_header.create(HTTP_TYPE_RESPONSE); } // Check entire DATA payload length if content-length: header is exist @@ -285,15 +340,16 @@ Http2Stream::increment_data_length(uint64_t length) inline bool Http2Stream::payload_length_is_valid() const { - uint32_t content_length = _req_header.get_content_length(); + uint32_t content_length = _recv_header.get_content_length(); return content_length == 0 || content_length == data_length; } inline bool -Http2Stream::is_client_state_writeable() const +Http2Stream::is_state_writeable() const { return _state == Http2StreamState::HTTP2_STREAM_STATE_OPEN || _state == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE || - _state == Http2StreamState::HTTP2_STREAM_STATE_RESERVED_LOCAL; + _state == Http2StreamState::HTTP2_STREAM_STATE_RESERVED_LOCAL || + (_outbound_flag && _state == Http2StreamState::HTTP2_STREAM_STATE_IDLE); } inline bool @@ -314,9 +370,27 @@ Http2Stream::read_vio_writer() const return this->read_vio.get_writer(); } +inline bool +Http2Stream::read_enabled() const +{ + return !this->read_vio.is_disabled(); +} + inline void Http2Stream::_clear_timers() { _timeout.cancel_active_timeout(); _timeout.cancel_inactive_timeout(); } + +inline void +Http2Stream::read_update(int count) +{ + read_vio.ndone += count; +} + +inline void +Http2Stream::read_done() +{ + read_vio.nbytes = read_vio.ndone; +} diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am index 301d3119c24..932daed4b2d 100644 --- a/proxy/http2/Makefile.am +++ b/proxy/http2/Makefile.am @@ -42,8 +42,10 @@ libhttp2_a_SOURCES = \ Http2Frame.h \ Http2ClientSession.cc \ Http2ClientSession.h \ - Http2CommonSession.cc \ Http2CommonSession.h \ + Http2CommonSession.cc \ + Http2ServerSession.cc \ + Http2ServerSession.h \ Http2ConnectionState.cc \ Http2ConnectionState.h \ Http2DebugNames.cc \ diff --git a/proxy/http2/unit_tests/test_HTTP2.cc b/proxy/http2/unit_tests/test_HTTP2.cc index 21a772ae9f7..3febe204efb 100644 --- a/proxy/http2/unit_tests/test_HTTP2.cc +++ b/proxy/http2/unit_tests/test_HTTP2.cc @@ -108,8 +108,8 @@ TEST_CASE("Convert HTTPHdr", "[HTTP2]") // check CHECK_THAT(buf, Catch::StartsWith("GET https://trafficserver.apache.org/index.html HTTP/1.1\r\n" - "Host: trafficserver.apache.org\r\n" "User-Agent: foobar\r\n" + "Host: trafficserver.apache.org\r\n" "\r\n")); } diff --git a/proxy/logging/LogAccess.cc b/proxy/logging/LogAccess.cc index da82c04ced3..fae5a589aba 100644 --- a/proxy/logging/LogAccess.cc +++ b/proxy/logging/LogAccess.cc @@ -1276,7 +1276,7 @@ LogAccess::marshal_client_provided_cert(char *buf) { int provided_cert = 0; if (m_http_sm) { - auto txn = m_http_sm->get_ua_txn(); + auto txn = m_http_sm->ua_txn; if (txn) { auto ssn = txn->get_proxy_ssn(); if (ssn) { diff --git a/src/shared/overridable_txn_vars.cc b/src/shared/overridable_txn_vars.cc index ac8baa5f8bf..f99ca665ed2 100644 --- a/src/shared/overridable_txn_vars.cc +++ b/src/shared/overridable_txn_vars.cc @@ -160,4 +160,5 @@ const std::unordered_map(ssnp); if (ss != nullptr) { - vconn = reinterpret_cast(ss->get_netvc()); + return reinterpret_cast(ss->get_netvc()); } - return vconn; + return nullptr; } TSVConn @@ -7888,9 +7887,8 @@ TSHttpTxnServerFdGet(TSHttpTxn txnp, int *fdp) sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); sdk_assert(sdk_sanity_check_null_ptr((void *)fdp) == TS_SUCCESS); - HttpSM *sm = reinterpret_cast(txnp); - *fdp = -1; - + HttpSM *sm = reinterpret_cast(txnp); + *fdp = -1; TSReturnCode retval = TS_ERROR; ProxyTransaction *ss = sm->get_server_txn(); if (ss != nullptr) { @@ -8821,6 +8819,7 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr case TS_CONFIG_SSL_CERT_FILEPATH: case TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME: case TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME: + case TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS: // String, must be handled elsewhere break; case TS_CONFIG_PARENT_FAILURES_UPDATE_HOSTDB: @@ -9059,6 +9058,11 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char s->t_state.my_txn_conf().ssl_client_ca_cert_filename = const_cast(value); } break; + case TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS: + if (value && length > 0) { + s->t_state.my_txn_conf().ssl_client_alpn_protocols = const_cast(value); + } + break; case TS_CONFIG_SSL_CERT_FILEPATH: /* noop */ break; diff --git a/src/traffic_server/InkAPITest.cc b/src/traffic_server/InkAPITest.cc index a8f27d21dfe..fcc1721afec 100644 --- a/src/traffic_server/InkAPITest.cc +++ b/src/traffic_server/InkAPITest.cc @@ -8695,7 +8695,8 @@ std::array SDK_Overridable_Configs = { "proxy.config.hostdb.ip_resolve", "proxy.config.http.connect.dead.policy", "proxy.config.plugin.vc.default_buffer_index", - "proxy.config.plugin.vc.default_buffer_water_mark"}}; + "proxy.config.plugin.vc.default_buffer_water_mark", + "proxy.config.ssl.client.alpn_protocols"}}; extern ClassAllocator httpSMAllocator; diff --git a/tests/gold_tests/h2/gold/nghttp_0_stdout.gold b/tests/gold_tests/h2/gold/nghttp_0_stdout.gold index e8e9acabd41..f19a43516d3 100644 --- a/tests/gold_tests/h2/gold/nghttp_0_stdout.gold +++ b/tests/gold_tests/h2/gold/nghttp_0_stdout.gold @@ -12,5 +12,3 @@ `` [``] recv (stream_id=1) :status: 200 `` -``; END_STREAM -`` diff --git a/tests/gold_tests/h2/gold/nghttp_1_stdout.gold b/tests/gold_tests/h2/gold/nghttp_1_stdout.gold index c1045323b0e..c1cd10559be 100644 --- a/tests/gold_tests/h2/gold/nghttp_1_stdout.gold +++ b/tests/gold_tests/h2/gold/nghttp_1_stdout.gold @@ -7,6 +7,6 @@ [``] recv GOAWAY frame (last_stream_id=1, error_code=NO_ERROR(0x00), opaque_data(0)=[]) `` -[``] recv DATA frame +[``] recv DATA frame ; END_STREAM `` diff --git a/tests/gold_tests/h2/h2origin.test.py b/tests/gold_tests/h2/h2origin.test.py new file mode 100644 index 00000000000..f0e02db026d --- /dev/null +++ b/tests/gold_tests/h2/h2origin.test.py @@ -0,0 +1,94 @@ +''' +Test communication to origin with H2 +''' +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Test.Summary = ''' +Test communication to origin with H2 +''' + +Test.ContinueOnFail = True + +# +# Communicate to origin with HTTP/2 +# +ts = Test.MakeATSProcess("ts", enable_tls="true") + +# add ssl materials like key, certificates for the server +ts.addDefaultSSLFiles() +replay_file = "replay/" +server = Test.MakeVerifierServerProcess("h2-origin", replay_file) +ts.Disk.records_config.update({ + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'http', + 'proxy.config.exec_thread.autoconfig': 0, + # Allow for more parallelism + 'proxy.config.exec_thread.limit': 4, + 'proxy.config.ssl.client.alpn_protocols': 'h2,http/1.1', + # Sticking with thread pool because global pool does not work with h2 + 'proxy.config.http.server_session_sharing.pool': 'thread', + 'proxy.config.http.server_session_sharing.match': 'ip,sni,cert', + 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', +}) + +ts.Disk.remap_config.AddLine( + 'map / https://127.0.0.1:{0}'.format(server.Variables.https_port) +) +ts.Disk.ssl_multicert_config.AddLine( + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key' +) + +ts.Disk.logging_yaml.AddLines( + ''' +logging: + formats: + - name: testformat + format: '% % % % %<{uuid}cqh> % % % % % %' + logs: + - mode: ascii + format: testformat + filename: squid +'''.split("\n") +) + +tr = Test.AddTestRun("Test traffic to origin using HTTP/2") +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(ts) +tr.AddVerifierClientProcess("client", replay_file, http_ports=[ts.Variables.port], https_ports=[ts.Variables.ssl_port]) +tr.StillRunningAfter = ts +tr.TimeOut = 60 + +# Just a check to flush out the traffic log until we have a clean shutdown for traffic_server +tr = Test.AddTestRun("Wait for the access log to write out") +tr.DelayStart = 10 +tr.StillRunningAfter = ts +tr.StillRunningAfter = server +tr.Processes.Default.Command = 'ls' +tr.Processes.Default.ReturnCode = 0 + +# UUIDs 1-4 should be http/1.1 clients and H2 origin +# UUIDs 5-9 should be http/2 clients and H2 origins +ts.Disk.squid_log.Content = Testers.ContainsExpression(" [1-4] http/1.1 http/2", "cases 1-4 request http/1.1") +ts.Disk.squid_log.Content += Testers.ExcludesExpression(" [1-4] http/2 http/2", "cases 1-4 request http/1.1") +ts.Disk.squid_log.Content = Testers.ContainsExpression(" 1[1-4] http/1.1 http/2", "cases 12-14 request http/1.1") +ts.Disk.squid_log.Content += Testers.ExcludesExpression(" 1[2-4] http/2 http/2", "cases 12-14 request http/1.1") +ts.Disk.squid_log.Content += Testers.ContainsExpression(" [5-9] http/2 http/2", "cases 5-11 request http/2") +ts.Disk.squid_log.Content += Testers.ExcludesExpression(" [5-9] http/1.1 http/2", "cases 5-11 request http/2") +ts.Disk.squid_log.Content += Testers.ContainsExpression(" 1[0-1] http/2 http/2", "cases 5-11 request http/2") +ts.Disk.squid_log.Content += Testers.ExcludesExpression(" 1[0-1] http/1.1 http/2", "cases 5-11 request http/2") diff --git a/tests/gold_tests/h2/h2origin_single_thread.test.py b/tests/gold_tests/h2/h2origin_single_thread.test.py new file mode 100644 index 00000000000..2254702c308 --- /dev/null +++ b/tests/gold_tests/h2/h2origin_single_thread.test.py @@ -0,0 +1,90 @@ +''' +Test communication to origin with H2 +''' +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Test.Summary = ''' +Test communication to origin with H2 +''' + +Test.ContinueOnFail = True + +# +# Communicate to origin with HTTP/2 +# +ts = Test.MakeATSProcess("ts", enable_tls="true") + +# add ssl materials like key, certificates for the server +ts.addDefaultSSLFiles() +replay_file = "replay" +server = Test.MakeVerifierServerProcess("h2-origin", replay_file) +ts.Disk.records_config.update({ + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'http', + 'proxy.config.exec_thread.autoconfig': 0, + # Limiting ourselves to 1 thread to exercise origin reuse + 'proxy.config.exec_thread.limit': 1, + 'proxy.config.ssl.client.alpn_protocols': 'h2,http1.1', + # Sticking with hybrid pool because global pool does not work with h2 + 'proxy.config.http.server_session_sharing.pool': 'hybrid', + 'proxy.config.http.server_session_sharing.match': 'hostonly', + 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', +}) + +ts.Disk.remap_config.AddLine( + 'map / https://127.0.0.1:{0}'.format(server.Variables.https_port) +) +ts.Disk.ssl_multicert_config.AddLine( + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key' +) + +ts.Disk.logging_yaml.AddLines( + ''' +logging: + formats: + - name: testformat + format: '% % % % %<{uuid}cqh> % % % % % %' + logs: + - mode: ascii + format: testformat + filename: squid +'''.split("\n") +) + +tr = Test.AddTestRun("Test traffic to origin using HTTP/2") +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(ts) +tr.AddVerifierClientProcess("client", replay_file, http_ports=[ts.Variables.port], https_ports=[ts.Variables.ssl_port]) +tr.StillRunningAfter = ts + +# Just a check to flush out the traffic log until we have a clean shutdown for traffic_server +tr = Test.AddTestRun("Wait for the access log to write out") +tr.DelayStart = 10 +tr.StillRunningAfter = ts +tr.StillRunningAfter = server +tr.Processes.Default.Command = 'ls' +tr.Processes.Default.ReturnCode = 0 + +# UUIDs 1-4 should be http/1.1 clients and H2 origin +# UUIDs 5-9 should be http/2 clients and H2 origins +ts.Disk.squid_log.Content = Testers.ContainsExpression(" [1-4] http/1.1 http/2", "cases 1-4 request http/1.1") +ts.Disk.squid_log.Content += Testers.ExcludesExpression(" [1-4] http/2 http/2", "cases 1-4 request http/1.1") +ts.Disk.squid_log.Content += Testers.ContainsExpression(" [5-9] http/2 http/2", "cases 5-9 request http/2") +ts.Disk.squid_log.Content += Testers.ExcludesExpression(" [5-9] http/1.1 http/2", "cases 5-9 request http/2") +ts.Disk.squid_log.Content += Testers.ContainsExpression(" http/2 1 1 1 [2-9]", "At least one case of origin reuse") diff --git a/tests/gold_tests/h2/http2.test.py b/tests/gold_tests/h2/http2.test.py index e6d2801ae35..b0b2e8caae8 100644 --- a/tests/gold_tests/h2/http2.test.py +++ b/tests/gold_tests/h2/http2.test.py @@ -189,6 +189,7 @@ post_body, ts.Variables.ssl_port) tr.Processes.Default.ReturnCode = 0 tr.Processes.Default.Streams.All = "gold/post_chunked.gold" +tr.TimeOut = 60 tr.StillRunningAfter = server # Test Case 7: Post with big chunked body @@ -199,6 +200,7 @@ ts.Variables.ssl_port) tr.Processes.Default.ReturnCode = 0 tr.Processes.Default.Streams.All = "gold/post_chunked.gold" +tr.TimeOut = 60 tr.StillRunningAfter = server # Test Case 8: Huge response header @@ -208,6 +210,7 @@ tr.Processes.Default.Streams.stdout = "gold/http2_8_stdout.gold" # Different versions of curl will have different cases for HTTP/2 field names. tr.Processes.Default.Streams.stderr = Testers.GoldFile("gold/http2_8_stderr.gold", case_insensitive=True) +tr.TimeOut = 60 tr.StillRunningAfter = server # Test Case 9: Header Only Response - e.g. 204 @@ -217,4 +220,5 @@ tr.Processes.Default.Streams.stdout = "gold/http2_9_stdout.gold" # Different versions of curl will have different cases for HTTP/2 field names. tr.Processes.Default.Streams.stderr = Testers.GoldFile("gold/http2_9_stderr.gold", case_insensitive=True) +tr.TimeOut = 60 tr.StillRunningAfter = server diff --git a/tests/gold_tests/h2/httpbin.test.py b/tests/gold_tests/h2/httpbin.test.py index ff8eb95a712..7300b4faaaf 100644 --- a/tests/gold_tests/h2/httpbin.test.py +++ b/tests/gold_tests/h2/httpbin.test.py @@ -56,7 +56,7 @@ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http2', + 'proxy.config.diags.debug.tags': 'http', }) ts.Disk.logging_yaml.AddLines( diff --git a/tests/gold_tests/h2/replay/h1-client-h2-origin.yaml b/tests/gold_tests/h2/replay/h1-client-h2-origin.yaml new file mode 100644 index 00000000000..93fa1cff5df --- /dev/null +++ b/tests/gold_tests/h2/replay/h1-client-h2-origin.yaml @@ -0,0 +1,596 @@ +meta: + version: '1.0' + +sessions: + - protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.3 + sni: data.brian.example.com + proxy-verify-mode: 0 + proxy-provided-cert: true + - name: tcp + - name: ip + version: '4' + + transactions: + # + # Test 1: Zero length response. + # + - all: { headers: { fields: [[ uuid, 1 ]]}} + + client-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: GET + url: /some/path + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: GET + url: /some/path + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + + # + # Test 2: Non-zero length response. + # + - all: { headers: { fields: [[ uuid, 2 ]]}} + + client-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: GET + url: /some/path2 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: GET + url: /some/path2 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + proxy-response: + version: '1.1' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + - protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.3 + sni: data.brian.example.com + proxy-verify-mode: 0 + proxy-provided-cert: true + - name: tcp + - name: ip + version: '4' + + transactions: + # + # Test 3: 8 byte post with a 404 response. + # + - all: { headers: { fields: [[ uuid, 3 ]]}} + + client-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: POST + url: /some/path3 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 8 ] + content: + encoding: plain + size: 8 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path3 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 8 ] + content: + encoding: plain + size: 8 + + server-response: + version: '2' + status: 404 + reason: "Not Found" + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + proxy-response: + version: '2' + status: 404 + reason: "Not Found" + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + # + # Test 4: 32 byte POST with a 200 response. + # + - all: { headers: { fields: [[ uuid, 4 ]]}} + + client-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + # + # Test 5: 3200 byte POST with a 200 response. + # + - all: { headers: { fields: [[ uuid, 12 ]]}} + + client-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + proxy-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 1600 ] + content: + encoding: plain + size: 1600 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 1600 ] + content: + encoding: plain + size: 1600 + + # + # Test 6: large post body small response + # + - all: { headers: { fields: [[ uuid, 13 ]]}} + + client-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + proxy-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + proxy-response: + version: '1.1' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + # + # Test 7: small post body large response + # + - all: { headers: { fields: [[ uuid, 14 ]]}} + + client-request: + protocol: + - name: http + version: '1.1' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '1.1' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + proxy-response: + version: '1.1' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 diff --git a/tests/gold_tests/h2/replay/h2-origin.yaml b/tests/gold_tests/h2/replay/h2-origin.yaml new file mode 100644 index 00000000000..65b133375a4 --- /dev/null +++ b/tests/gold_tests/h2/replay/h2-origin.yaml @@ -0,0 +1,624 @@ +meta: + version: '1.0' + +sessions: + - protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.3 + sni: data.brian.example.com + proxy-verify-mode: 0 + proxy-provided-cert: true + - name: tcp + - name: ip + version: '4' + + transactions: + # + # Test 1: Zero length response. + # + - all: { headers: { fields: [[ uuid, 5 ]]}} + + client-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: GET + url: /some/path;arg=1;arg=2?foo + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + content: + encoding: plain + size: 0 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: GET + url: + - [ path, { value: /some/path;arg=1;arg=2, as: equal } ] + - [ query, { value: foo, as: equal } ] + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + + # + # Test 2: Non-zero length response. + # + - all: { headers: { fields: [[ uuid, 6 ]]}} + + client-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: GET + url: /some/path2 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: GET + url: + - [ path, { value: /some/path2, as: equal } ] + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + - protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.3 + sni: data.brian.example.com + proxy-verify-mode: 0 + proxy-provided-cert: true + - name: tcp + - name: ip + version: '4' + + transactions: + # + # Test 3: 8 byte post with a 404 response. + # + - all: { headers: { fields: [[ uuid, 7 ]]}} + + client-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path3?foo=bar + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 8 ] + content: + encoding: plain + size: 8 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: + - [ path, { value: /some/path3, as: equal }] + - [ query, { value: foo=bar, as: equal }] + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 8 ] + content: + encoding: plain + size: 8 + + server-response: + version: '2' + status: 404 + reason: "Not Found" + headers: + encoding: esc_json + fields: + - [ bob, 0 ] + content: + encoding: plain + size: 0 + + proxy-response: + version: '2' + status: 404 + reason: "Not Found" + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + proxy-response: + version: '2' + status: 404 + reason: "Not Found" + headers: + encoding: esc_json + fields: + - [ Content-Length, 0 ] + content: + encoding: plain + size: 0 + + # + # Test 4: 32 byte POST with a 200 response. + # + - all: { headers: { fields: [[ uuid, 8 ]]}} + + client-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + # + # Test 5: 3200 byte POST with a 200 response. + # + - all: { headers: { fields: [[ uuid, 9 ]]}} + + client-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ bob, 1600 ] + content: + encoding: plain + size: 1600 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 1600 ] + content: + encoding: plain + size: 1600 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 1600 ] + content: + encoding: plain + size: 1600 + + # + # Test 6: large post body small response + # + - all: { headers: { fields: [[ uuid, 10 ]]}} + + client-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + content: + encoding: plain + size: 3200 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 16 ] + content: + encoding: plain + size: 16 + + # + # Test 7: small post body large response + # + - all: { headers: { fields: [[ uuid, 11 ]]}} + + client-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + proxy-request: + protocol: + - name: http + version: '2' + - name: tls + version: TLSv1.2 + sni: data.brian.example.com + proxy-verify-mode: 1 + proxy-provided-cert: false + - name: tcp + - name: ip + version: '4' + + version: '2' + scheme: https + method: POST + url: /some/path4 + headers: + encoding: esc_json + fields: + - [ Host, data.brian.example.com ] + - [ Content-Length, 32 ] + content: + encoding: plain + size: 32 + + server-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 + + proxy-response: + version: '2' + status: 200 + reason: OK + headers: + encoding: esc_json + fields: + - [ Content-Length, 3200 ] + content: + encoding: plain + size: 3200 diff --git a/tests/gold_tests/timeout/tls_conn_timeout.test.py b/tests/gold_tests/timeout/tls_conn_timeout.test.py index 86da7ecc4ee..8187220c6cf 100644 --- a/tests/gold_tests/timeout/tls_conn_timeout.test.py +++ b/tests/gold_tests/timeout/tls_conn_timeout.test.py @@ -69,7 +69,7 @@ tr.Processes.Default.Command = 'curl -H"Connection:close" -d "bob" -i http://127.0.0.1:{0}/connect_blocked --tlsv1.2'.format( ts.Variables.port) tr.Processes.Default.Streams.All = Testers.ContainsExpression( - "HTTP/1.1 502 connect failed", "Connect failed") + "HTTP/1.1 502 Connection timed out", "Connect failed") tr.Processes.Default.ReturnCode = 0 tr.StillRunningAfter = delay_post_connect tr.StillRunningAfter = Test.Processes.ts @@ -93,7 +93,7 @@ tr.Processes.Default.Command = 'curl -H"Connection:close" -i http://127.0.0.1:{0}/get_connect_blocked --tlsv1.2'.format( ts.Variables.port) tr.Processes.Default.Streams.All = Testers.ContainsExpression( - "HTTP/1.1 502 connect failed", "Connect failed") + "HTTP/1.1 502 Connection timed out", "Connect failed") tr.Processes.Default.ReturnCode = 0 tr.StillRunningAfter = delay_get_connect From d40fb86396d1a6dac27595a6375f569aefcad160 Mon Sep 17 00:00:00 2001 From: Susan Hinrichs Date: Sun, 24 Oct 2021 16:14:10 -0500 Subject: [PATCH 3/3] Fix documentation typo --- doc/admin-guide/files/records.config.en.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index dbd5adb30df..9e1a03a3e94 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -3859,7 +3859,7 @@ Client-Related Configuration Enables (``1``) or disables (``0``) TLSv1_3 in the ATS client context. If not specified, enabled by default -.. ts:cv:: CONFIG proxy.config.ssl.client.alpn_protocol STRING "" +.. ts:cv:: CONFIG proxy.config.ssl.client.alpn_protocols STRING "" Set the alpn string that ATS will send to origin during new connections. By default no ALPN string will be set. To enable HTTP/2 communication to the origin, set this to "h2,http1.1".