This is an automated email from the ASF dual-hosted git repository.
bcall pushed a commit to branch 9.2.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/9.2.x by this push:
new a49e575340 APIs to get the h2 error codes and a plugin to use them
(#10571)
a49e575340 is described below
commit a49e5753405e20b553f16d976db99a5ccce4ffbb
Author: Bryan Call <[email protected]>
AuthorDate: Mon Oct 9 09:23:56 2023 -0700
APIs to get the h2 error codes and a plugin to use them (#10571)
---
doc/admin-guide/plugins/block_errors.en.rst | 73 +++++
doc/admin-guide/plugins/index.en.rst | 4 +
include/ts/ts.h | 36 +++
include/tscore/ink_inet.h | 26 ++
plugins/Makefile.am | 1 +
plugins/experimental/block_errors/CMakeLists.txt | 22 ++
plugins/experimental/block_errors/Makefile.inc | 20 ++
plugins/experimental/block_errors/block_errors.cc | 309 ++++++++++++++++++++++
proxy/ProxyTransaction.cc | 5 +-
src/traffic_server/InkAPI.cc | 50 ++++
10 files changed, 545 insertions(+), 1 deletion(-)
diff --git a/doc/admin-guide/plugins/block_errors.en.rst
b/doc/admin-guide/plugins/block_errors.en.rst
new file mode 100644
index 0000000000..c08d597726
--- /dev/null
+++ b/doc/admin-guide/plugins/block_errors.en.rst
@@ -0,0 +1,73 @@
+.. 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
+
+.. _admin-plugins-block_errors:
+
+Block Errors Plugin
+*******************
+
+Description
+===========
+The `block_errors` plugin blocks connections or downgrades the protocol from
HTTP/2 to HTTP/1.1 for clients that have too many HTTP/2 errors on the server.
+
+The plugin tracks users based on their IP address and blocks them for a
configurable amount of time. `block_errors` can be configured to either block
or downgrade the protocol, only use HTTP/1.1, for any new connections.
+The existing connection that experience errors and is over the error limit
will be closed. The plugin also supports on the fly configuration changes
using the `traffic_ctl` command.
+
+
+Configuration
+=============
+
+To enable the `block_errors` plugin, insert the following line in
:file:`plugin.config`:
+
+ block_errors.so
+
+Additional configuration options are available and can be set in
:file:`plugin.config`:
+
+ block_errors.so <error limit> <timeout> <shutdown> <enable>
+
+- ``error limit``: The number of errors allowed before blocking the client.
Default: 1000 (per minute)
+- ``timeout``: The time in minutes to block the client. Default: 4 (minutes)
+- ``shutdown``: Shutdown (1) or downgrade (0) the protocol for new
connections. Default: 0 (downgrade to HTTP/1.1)
+- ``enable``: Enable (1) or disable (0) the plugin. Default: 1 (enabled)
+
+Example Configuration
+=====================
+
+ block_errors.so 1000 4 0 1
+
+Run Time Configuration
+======================
+The plugin can be configured at run time using the `traffic_ctl` command. The
following commands are available:
+
+- ``block_errors.error_limit``: Set the error limit. Takes a single argument,
the number of errors allowed before blocking the client.
+- ``block_errors.timeout``: Set the block timeout. Takes a single argument,
the number of minutes to block the client.
+- ``block_errors.shutdown``: Set the shutdown mode. Takes a single argument,
0 to downgrade to HTTP/1.1, 1 to close the connection.
+- ``block_errors.enable``: Enable or disable the plugin. Takes a single
argument, 0 to disable, 1 to enable.
+
+Example Run Time Configuration
+==============================
+
+ traffic_ctl plugin msg block_errors.error_limit 10000
+
+ traffic_ctl plugin msg block_errors.timeout 10
+
+ traffic_ctl plugin msg block_errors.shutdown 1
+
+ traffic_ctl plugin msg block_errors.enable 1
diff --git a/doc/admin-guide/plugins/index.en.rst
b/doc/admin-guide/plugins/index.en.rst
index 986a956913..b0218bab9d 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -151,6 +151,7 @@ directory of the |TS| source tree. Experimental plugins can
be compiled by passi
:hidden:
Access Control <access_control.en>
+ Block Errors <block_errors.en>
Cache Fill <cache_fill.en>
Certifier <certifier.en>
Cert Reporting Tool <cert_reporting_tool.en>
@@ -187,6 +188,9 @@ directory of the |TS| source tree. Experimental plugins can
be compiled by passi
:doc:`Access Control <access_control.en>`
Access control plugin that handles various access control use-cases.
+:doc:`Block Errors <block_errors.en>`
+ Blocks or downgrades new connections when the server receives too many
errors from an IP address.
+
:doc:`Certifier <certifier.en>`
Manages and/or generates certificates for incoming HTTPS requests.
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 7c56b57d9d..85c07708c4 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -2663,6 +2663,42 @@ tsapi void TSHttpTxnResponseActionGet(TSHttpTxn txnp,
TSResponseAction *action);
*/
tsapi TSIOBufferReader TSHttpTxnPostBufferReaderGet(TSHttpTxn txnp);
+/**
+ * @brief Get the client error received from the transaction
+ *
+ * @param txnp The transaction where the error code is stored
+ * @param error_class Either session/connection or stream/transaction error
+ * @param error_code Error code received from the client
+ */
+void TSHttpTxnClientReceivedErrorGet(TSHttpTxn txnp, uint32_t *error_class,
uint64_t *error_code);
+
+/**
+ * @brief Get the client error sent from the transaction
+ *
+ * @param txnp The transaction where the error code is stored
+ * @param error_class Either session/connection or stream/transaction error
+ * @param error_code Error code sent to the client
+ */
+void TSHttpTxnClientSentErrorGet(TSHttpTxn txnp, uint32_t *error_class,
uint64_t *error_code);
+
+/**
+ * @brief Get the server error received from the transaction
+ *
+ * @param txnp The transaction where the error code is stored
+ * @param error_class Either session/connection or stream/transaction error
+ * @param error_code Error code sent from the server
+ */
+void TSHttpTxnServerReceivedErrorGet(TSHttpTxn txnp, uint32_t *error_class,
uint64_t *error_code);
+
+/**
+ * @brief Get the server error sent from the transaction
+ *
+ * @param txnp The transaction where the error code is stored
+ * @param error_class Either session/connection or stream/transaction error
+ * @param error_code Error code sent to the server
+ */
+void TSHttpTxnServerSentErrorGet(TSHttpTxn txnp, uint32_t *error_class,
uint64_t *error_code);
+
/**
* Initiate an HTTP/2 Server Push preload request.
* Use this api to register a URL that you want to preload with HTTP/2 Server
Push.
diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h
index 332fa03ed2..a593063813 100644
--- a/include/tscore/ink_inet.h
+++ b/include/tscore/ink_inet.h
@@ -1268,6 +1268,7 @@ struct IpAddr {
- Else: 0.
*/
uint32_t hash() const;
+ uint64_t hash64() const;
/** The hashing function embedded in a functor.
@see hash
@@ -1476,6 +1477,18 @@ IpAddr::hash() const
return zret;
}
+inline uint64_t
+IpAddr::hash64() const
+{
+ uint64_t zret = 0;
+ if (this->isIp4()) {
+ zret = ntohl(_addr._ip4);
+ } else if (this->isIp6()) {
+ zret = _addr._u64[0] ^ _addr._u64[1];
+ }
+ return zret;
+}
+
/// Write IP @a addr to storage @a dst.
/// @return @s dst.
sockaddr *ats_ip_set(sockaddr *dst, ///< Destination storage.
@@ -1611,3 +1624,16 @@ namespace bwf
detail::MemDump Hex_Dump(IpEndpoint const &addr);
} // namespace bwf
} // namespace ts
+
+namespace std
+{
+/// Standard hash support for @a IPAddr.
+template <> struct hash<IpAddr> {
+ size_t
+ operator()(IpAddr const &addr) const
+ {
+ return addr.hash64();
+ }
+};
+
+} // namespace std
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 035b8ce0dc..1681c92368 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -61,6 +61,7 @@ if BUILD_EXPERIMENTAL_PLUGINS
include experimental/access_control/Makefile.inc
include experimental/acme/Makefile.inc
+include experimental/block_errors/Makefile.inc
include experimental/cache_fill/Makefile.inc
include experimental/cert_reporting_tool/Makefile.inc
include experimental/collapsed_forwarding/Makefile.inc
diff --git a/plugins/experimental/block_errors/CMakeLists.txt
b/plugins/experimental/block_errors/CMakeLists.txt
new file mode 100644
index 0000000000..ea67625ee6
--- /dev/null
+++ b/plugins/experimental/block_errors/CMakeLists.txt
@@ -0,0 +1,22 @@
+#######################
+#
+# 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.
+#
+#######################
+
+project(block_errors)
+
+add_atsplugin(block_errors
+ block_errors.cc
+)
diff --git a/plugins/experimental/block_errors/Makefile.inc
b/plugins/experimental/block_errors/Makefile.inc
new file mode 100644
index 0000000000..3659b6a1ee
--- /dev/null
+++ b/plugins/experimental/block_errors/Makefile.inc
@@ -0,0 +1,20 @@
+# 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.
+
+pkglib_LTLIBRARIES += experimental/block_errors/block_errors.la
+
+experimental_block_errors_block_errors_la_SOURCES = \
+ experimental/block_errors/block_errors.cc
diff --git a/plugins/experimental/block_errors/block_errors.cc
b/plugins/experimental/block_errors/block_errors.cc
new file mode 100644
index 0000000000..c28dd3d8ea
--- /dev/null
+++ b/plugins/experimental/block_errors/block_errors.cc
@@ -0,0 +1,309 @@
+/*
+ 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 <ts/ts.h>
+#include <unordered_map>
+#include <limits>
+#include <tscore/ink_inet.h>
+#include <tscore/BufferWriter.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <shared_mutex>
+#include <cinttypes>
+#include <string_view>
+#include <mutex>
+
+#define PLUGIN_NAME "block_errors"
+#define PLUGIN_NAME_CLEAN "block_clean"
+static uint32_t RESET_LIMIT = 1000;
+static uint32_t TIMEOUT_CYCLES = 4;
+static int StatCountBlocks = -1;
+static bool shutdown_connection = false;
+static bool enabled = true;
+
+//-------------------------------------------------------------------------
+static int
+msg_hook(TSCont *contp, TSEvent event, void *edata)
+{
+ TSPluginMsg *msg = static_cast<TSPluginMsg *>(edata);
+ std::string_view tag(static_cast<const char *>(msg->tag));
+ std::string_view data(static_cast<const char *>(msg->data));
+
+ TSDebug(PLUGIN_NAME, "msg_hook: tag=%s data=%s", tag.data(), data.data());
+
+ if (tag == "block_errors.enabled") {
+ enabled = static_cast<bool>(atoi(data.data()));
+ } else if (tag == "block_errors.limit") {
+ RESET_LIMIT = atoi(data.data());
+ } else if (tag == "block_errors.cycles") {
+ TIMEOUT_CYCLES = atoi(data.data());
+ } else if (tag == "block_errors.shutdown") {
+ shutdown_connection = static_cast<bool>(atoi(data.data()));
+ } else {
+ TSDebug(PLUGIN_NAME, "msg_hook: unknown message tag '%s'", tag.data());
+ TSError("block_errors: unknown message tag '%s'", tag.data());
+ }
+
+ TSDebug(PLUGIN_NAME, "reset limit: %d per minute, timeout limit: %d minutes,
shutdown connection: %d enabled: %d", RESET_LIMIT,
+ TIMEOUT_CYCLES, shutdown_connection, enabled);
+
+ return 0;
+}
+
+//-------------------------------------------------------------------------
+// convert a sockaddr to a string
+std::string &
+ipaddr_to_string(const IpAddr &ip, std::string &address)
+{
+ ts::LocalBufferWriter<128> writer;
+ writer.print("{}", ip);
+ address = writer.view();
+
+ return address;
+}
+
+//-------------------------------------------------------------------------
+struct IPTableItem {
+ uint32_t _count = 1;
+ uint32_t _cycles = 0;
+};
+
+//-------------------------------------------------------------------------
+class IPTable
+{
+public:
+ IPTable() = default;
+
+ uint32_t
+ increment(IpAddr const &ip)
+ {
+ std::unique_lock lock(_mutex);
+ auto item = _table.find(ip);
+ if (item == _table.end()) {
+ _table.insert(std::make_pair(ip, IPTableItem()));
+ return 1;
+ } else {
+ ++item->second._count;
+ uint32_t tmp_count = item->second._count;
+ return tmp_count;
+ }
+ }
+
+ uint32_t
+ getCount(IpAddr const &ip)
+ {
+ std::shared_lock lock(_mutex);
+ auto item = _table.find(ip);
+ if (item == _table.end()) {
+ return 0;
+ } else {
+ uint32_t tmp_count = item->second._count;
+ return tmp_count;
+ }
+ }
+
+ void
+ clean()
+ {
+ std::string address;
+ std::unique_lock lock(_mutex);
+ for (auto item = _table.begin(); item != _table.end();) {
+ if (item->second._count <= RESET_LIMIT || item->second._cycles >=
TIMEOUT_CYCLES) {
+ // remove the item if the count is below the limit or the timeout has
expired
+ TSDebug(PLUGIN_NAME_CLEAN, "ip=%s count=%d removing",
ipaddr_to_string(item->first, address).c_str(), item->second._count);
+ item = _table.erase(item);
+ } else {
+ // increment the timeout cycles if the count is above the limit
+ if (item->second._cycles == 0) {
+ // log only once per ip address per timeout period
+ TSError("block_errors: blocking or downgrading ip=%s for %d minutes,
reset count=%d",
+ ipaddr_to_string(item->first, address).c_str(),
TIMEOUT_CYCLES, item->second._count);
+ TSStatIntIncrement(StatCountBlocks, 1);
+ }
+ ++item->second._cycles;
+ TSDebug(PLUGIN_NAME_CLEAN, "ip=%s count=%d incrementing cycles=%d",
ipaddr_to_string(item->first, address).c_str(),
+ item->second._count, item->second._cycles);
+ ++item;
+ }
+ }
+ }
+
+private:
+ std::unordered_map<IpAddr, IPTableItem> _table;
+ std::shared_mutex _mutex;
+};
+
+IPTable ip_table;
+
+//-------------------------------------------------------------------------
+static int
+handle_start_hook(TSCont *contp, TSEvent event, void *edata)
+{
+ TSDebug(PLUGIN_NAME, "handle_start_hook");
+ auto vconn = static_cast<TSVConn>(edata);
+
+ if (enabled == false) {
+ TSDebug(PLUGIN_NAME, "plugin disabled");
+ TSVConnReenable(vconn);
+ return 0;
+ }
+
+ // only handle ssl connections
+ if (TSVConnIsSsl(vconn) == 0) {
+ TSDebug(PLUGIN_NAME, "not a ssl connection");
+ TSVConnReenable(vconn);
+ return 0;
+ }
+
+ // get the ip address
+ const sockaddr *addr = TSNetVConnRemoteAddrGet(vconn);
+ IpAddr ipaddr(addr);
+
+ // get the count for the ip address
+ uint32_t count = ip_table.getCount(ipaddr);
+ TSDebug(PLUGIN_NAME, "count=%d", count);
+
+ // if the count is over the limit, shutdown or downgrade the connection
+ if (count > RESET_LIMIT) {
+ std::string address;
+ if (shutdown_connection == true) {
+ // shutdown the connection
+ TSDebug(PLUGIN_NAME, "ip=%s count=%d is over the limit, shutdown
connection on start",
+ ipaddr_to_string(ipaddr, address).c_str(), count);
+ int fd = TSVConnFdGet(vconn);
+ shutdown(fd, SHUT_RDWR);
+ char buffer[4096];
+ while (read(fd, buffer, sizeof(buffer)) > 0) {
+ // drain the connection
+ }
+ } else {
+ // downgrade the connection
+ TSDebug(PLUGIN_NAME, "ip=%s count=%d is over the limit, downgrading
connection", ipaddr_to_string(ipaddr, address).c_str(),
+ count);
+ TSVConnProtocolDisable(vconn, TS_ALPN_PROTOCOL_HTTP_2_0);
+ }
+ }
+
+ TSVConnReenable(vconn);
+ return 0;
+}
+
+//-------------------------------------------------------------------------
+struct Errors {
+ uint32_t cls = 0; // class of error
+ uint64_t code = 0; // error code
+};
+
+//-------------------------------------------------------------------------
+static int
+handle_close_hook(TSCont *contp, TSEvent event, void *edata)
+{
+ TSDebug(PLUGIN_NAME, "handle_close_hook");
+ auto txnp = static_cast<TSHttpTxn>(edata);
+
+ if (enabled == false) {
+ TSDebug(PLUGIN_NAME, "plugin disabled");
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+ }
+
+ // get the errors from the state machine
+ Errors transaction;
+ Errors session;
+ TSHttpTxnClientReceivedErrorGet(txnp, &transaction.cls, &transaction.code);
+ TSHttpTxnClientSentErrorGet(txnp, &session.cls, &session.code);
+
+ // debug if we have an error
+ if (transaction.cls != 0 || session.cls != 0 || transaction.code != 0 ||
session.code != 0) {
+ TSDebug(PLUGIN_NAME, "transaction error class=%d code=%" PRIu64 " session
error class=%d code=%" PRIu64, transaction.cls,
+ transaction.code, session.cls, session.code);
+ }
+
+ // count the error if there is a transaction error CANCEL or a session error
ENHANCE_YOUR_CALM
+ // https://www.rfc-editor.org/rfc/rfc9113.html#name-error-codes
+ if ((transaction.cls == 2 && transaction.code == 8) || (session.cls == 1 &&
session.code == 11)) {
+ TSHttpSsn ssn = TSHttpTxnSsnGet(txnp);
+ TSVConn vconn = TSHttpSsnClientVConnGet(ssn);
+ const sockaddr *addr = TSNetVConnRemoteAddrGet(vconn);
+ IpAddr ipaddr(addr);
+ uint32_t count = ip_table.increment(ipaddr);
+ if (count > RESET_LIMIT) {
+ std::string address;
+ TSDebug(PLUGIN_NAME, "ip=%s count=%d is over the limit, shutdown
connection on close",
+ ipaddr_to_string(ipaddr, address).c_str(), count);
+ int fd = TSVConnFdGet(vconn);
+ shutdown(fd, SHUT_RDWR);
+ }
+ }
+
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+}
+
+//-------------------------------------------------------------------------
+static int
+clean_table(TSCont *contp, TSEvent event, void *edata)
+{
+ ip_table.clean();
+ return 0;
+}
+
+//-------------------------------------------------------------------------
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSDebug(PLUGIN_NAME, "TSPluginInit");
+
+ // register the plugin
+ TSPluginRegistrationInfo info;
+ info.plugin_name = "block_errors";
+ info.vendor_name = "Apache Software Foundation";
+ info.support_email = "[email protected]";
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("Plugin registration failed");
+ }
+
+ // set the reset and timeout values
+ if (argc == 5) {
+ RESET_LIMIT = atoi(argv[1]);
+ TIMEOUT_CYCLES = atoi(argv[2]);
+ shutdown_connection = static_cast<bool>(atoi(argv[3]));
+ enabled = static_cast<bool>(atoi(argv[4]));
+ } else if (argc > 1 && argc < 5) {
+ TSDebug(PLUGIN_NAME,
+ "block_errors: invalid number of arguments, using the defaults -
usage: block_errors.so <reset limit> <timeout "
+ "cycles> <shutdown connection> <enabled>");
+ TSError("block_errors: invalid number of arguments, using the defaults -
usage: block_errors.so <reset limit> <timeout cycles> "
+ "<shutdown connection> <enabled>");
+ }
+
+ TSDebug(PLUGIN_NAME, "reset limit: %d per minute, timeout limit: %d minutes,
shutdown connection: %d enabled: %d", RESET_LIMIT,
+ TIMEOUT_CYCLES, shutdown_connection, enabled);
+
+ // create a stat counter
+ StatCountBlocks = TSStatCreate("block_errors.count", TS_RECORDDATATYPE_INT,
TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
+
+ // register the hooks
+ TSHttpHookAdd(TS_VCONN_START_HOOK,
TSContCreate(reinterpret_cast<TSEventFunc>(handle_start_hook), nullptr));
+ TSHttpHookAdd(TS_HTTP_TXN_CLOSE_HOOK,
TSContCreate(reinterpret_cast<TSEventFunc>(handle_close_hook), nullptr));
+ TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK,
TSContCreate(reinterpret_cast<TSEventFunc>(msg_hook), nullptr));
+
+ // schedule cleanup on task thread every 60 seconds
+ TSContScheduleEveryOnPool(TSContCreate((TSEventFunc)clean_table,
TSMutexCreate()), 60 * 1000, TS_THREAD_POOL_TASK);
+}
diff --git a/proxy/ProxyTransaction.cc b/proxy/ProxyTransaction.cc
index cb80b3cc2c..c56ddb348d 100644
--- a/proxy/ProxyTransaction.cc
+++ b/proxy/ProxyTransaction.cc
@@ -81,7 +81,10 @@ void
ProxyTransaction::set_tx_error_code(ProxyError e)
{
if (this->_sm) {
- this->_sm->t_state.client_info.tx_error_code = e;
+ if (!(this->_sm->t_state.client_info.tx_error_code.cls ==
ProxyErrorClass::SSN && e.cls == ProxyErrorClass::TXN)) {
+ // if there is not an error already set for the session set the
transaction level error
+ this->_sm->t_state.client_info.tx_error_code = e;
+ }
}
}
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index 7cd59605f3..6e3f68fb12 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -8432,6 +8432,56 @@ TSHttpTxnIsInternal(TSHttpTxn txnp)
return TSHttpSsnIsInternal(TSHttpTxnSsnGet(txnp));
}
+static void
+txn_error_get(TSHttpTxn txnp, bool client, bool sent, uint32_t &error_class,
uint64_t &error_code)
+{
+ sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
+ HttpSM *sm =
reinterpret_cast<HttpSM *>(txnp);
+ HttpTransact::ConnectionAttributes *connection_attributes = nullptr;
+
+ if (client == true) {
+ // client
+ connection_attributes = &sm->t_state.client_info;
+ } else {
+ // server
+ connection_attributes = &sm->t_state.server_info;
+ }
+
+ if (sent == true) {
+ // sent
+ error_code = connection_attributes->tx_error_code.code;
+ error_class =
static_cast<uint32_t>(connection_attributes->tx_error_code.cls);
+ } else {
+ // received
+ error_code = connection_attributes->rx_error_code.code;
+ error_class =
static_cast<uint32_t>(connection_attributes->rx_error_code.cls);
+ }
+}
+
+void
+TSHttpTxnClientReceivedErrorGet(TSHttpTxn txnp, uint32_t *error_class,
uint64_t *error_code)
+{
+ txn_error_get(txnp, true, false, *error_class, *error_code);
+}
+
+void
+TSHttpTxnClientSentErrorGet(TSHttpTxn txnp, uint32_t *error_class, uint64_t
*error_code)
+{
+ txn_error_get(txnp, true, true, *error_class, *error_code);
+}
+
+void
+TSHttpTxnServerReceivedErrorGet(TSHttpTxn txnp, uint32_t *error_class,
uint64_t *error_code)
+{
+ txn_error_get(txnp, false, false, *error_class, *error_code);
+}
+
+void
+TSHttpTxnServerSentErrorGet(TSHttpTxn txnp, uint32_t *error_class, uint64_t
*error_code)
+{
+ txn_error_get(txnp, false, true, *error_class, *error_code);
+}
+
TSReturnCode
TSHttpTxnServerPush(TSHttpTxn txnp, const char *url, int url_len)
{