diff --git a/.gitignore b/.gitignore index 4a4a40759e0..a426f952ade 100644 --- a/.gitignore +++ b/.gitignore @@ -170,7 +170,6 @@ rc/trafficserver.service .libs/ .svn/ -.vscode/ target tsxs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..9757af98a9c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.associations": { + "*.cript": "cpp", + "*.test.py": "python", + "*.test.ext": "python" + } +} diff --git a/cmake/ExperimentalPlugins.cmake b/cmake/ExperimentalPlugins.cmake index e8ea6141347..487e0bfed16 100644 --- a/cmake/ExperimentalPlugins.cmake +++ b/cmake/ExperimentalPlugins.cmake @@ -31,6 +31,7 @@ auto_option(ACCESS_CONTROL FEATURE_VAR BUILD_ACCESS_CONTROL DEFAULT ${_DEFAULT}) auto_option(BLOCK_ERRORS FEATURE_VAR BUILD_BLOCK_ERRORS DEFAULT ${_DEFAULT}) auto_option(CACHE_FILL FEATURE_VAR BUILD_CACHE_FILL DEFAULT ${_DEFAULT}) auto_option(CERT_REPORTING_TOOL FEATURE_VAR BUILD_CERT_REPORTING_TOOL DEFAULT ${_DEFAULT}) +auto_option(CONNECTION_EXEMPT_LIST FEATURE_VAR BUILD_CONNECTION_EXEMPT_LIST DEFAULT ${_DEFAULT}) auto_option(COOKIE_REMAP FEATURE_VAR BUILD_COOKIE_REMAP DEFAULT ${_DEFAULT}) auto_option(CUSTOM_REDIRECT FEATURE_VAR BUILD_CUSTOM_REDIRECT DEFAULT ${_DEFAULT}) auto_option(FQ_PACING FEATURE_VAR BUILD_FQ_PACING DEFAULT ${_DEFAULT}) diff --git a/cmake/add_cript.cmake b/cmake/add_cript.cmake new file mode 100644 index 00000000000..4a145370731 --- /dev/null +++ b/cmake/add_cript.cmake @@ -0,0 +1,35 @@ +####################### +# +# 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. +# +####################### + +# Function to build pre-compiled cript scripts +function(add_cript name source_file) + # Check if ENABLE_CRIPTS is ON, if not, skip + if(NOT ENABLE_CRIPTS) + message(STATUS "Skipping cript ${name} - ENABLE_CRIPTS is OFF") + return() + endif() + + # Use the standard ATS plugin macro and link with cripts + add_atsplugin(${name} ${source_file}) + target_link_libraries(${name} PRIVATE ts::cripts) + + # Tell CMake that .cript files are C++ files + set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX) + set_source_files_properties(${source_file} PROPERTIES LANGUAGE CXX) + + verify_remap_plugin(${name}) +endfunction() diff --git a/doc/Doxyfile b/doc/Doxyfile index b8022942e60..aa1e06da9c8 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -768,7 +768,7 @@ INPUT_ENCODING = UTF-8 # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. -FILE_PATTERNS = *.c *.cc *.h *.h.in *.i +FILE_PATTERNS = *.c *.cc *.h *.h.in *.i *.cript # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. diff --git a/doc/admin-guide/files/records.yaml.en.rst b/doc/admin-guide/files/records.yaml.en.rst index 920ec5d7d40..c3e001565d0 100644 --- a/doc/admin-guide/files/records.yaml.en.rst +++ b/doc/admin-guide/files/records.yaml.en.rst @@ -524,6 +524,37 @@ Network below this limit. A value of 0 disables the per client concurrent connection limit. + See :ts:cv:`proxy.config.http.per_client.connection.exempt_list` for a way to + allow (not count) certain client IP addresses when applying this limit. + +.. ts:cv:: CONFIG proxy.config.http.per_client.connection.exempt_list STRING NULL + + A comma-separated list of IP addresses or CIDR ranges to exempt when + counting incoming client connections for per client connection + throttling. Incoming addresses in this specified set will not count + against :ts:cv:`proxy.config.net.per_client.max_connections_in` and + thus will not be blocked by that configuration. This may be useful, + for example, to allow any number of incoming connections from within + an organization's network without blocking them due to the per client + connection max feature. + + This configuration takes a comma-separated list of IP addresses, CIDR + networks, or ranges separated by a dash. + + ============================== =========================================================== + Example Effect + ============================== =========================================================== + ``10.0.2.123`` Exempt a single IP Address. + ``10.0.3.1-10.0.3.254`` Exempt a range of IP address. + ``10.0.4.0/24`` Exempt a range of IP address specified by CIDR notation. + ``10.0.2.123,172.16.0.0/20`` Exempt multiple addresses/ranges. + ============================== =========================================================== + + Here is an example configuration value:: + + 10.0.2.123,172.16.0.0/20,192.168.1.0/24 + + .. ts:cv:: CONFIG proxy.config.http.per_client.connection.alert_delay INT 60 :reloadable: :units: seconds @@ -2041,7 +2072,7 @@ Proxy User Variables by a dash or by using CIDR notation. ======================= =========================================================== - Example Effect + Example Effect ======================= =========================================================== ``10.0.2.123`` A single IP Address. ``10.0.3.1-10.0.3.254`` A range of IP address. diff --git a/doc/admin-guide/plugins/connection_exempt_list.en.rst b/doc/admin-guide/plugins/connection_exempt_list.en.rst new file mode 100644 index 00000000000..cb1f93372e0 --- /dev/null +++ b/doc/admin-guide/plugins/connection_exempt_list.en.rst @@ -0,0 +1,103 @@ +.. include:: ../../common.defs + +.. _admin-plugins-connection-exempt-list: + +Connection Exempt List Plugin +****************************** + +.. 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. + +Description +=========== + +:ts:cv:`proxy.config.http.per_client.connection.exempt_list` allows +administrators to set exemptions to the per-client connection limit. However, +for large networks, managing this as a comma-separated string in +:file:`records.yaml` can be cumbersome. This plugin allows administrators to set +the exemption list :ts:cv:`proxy.config.http.per_client.connection.exempt_list` +value via an external YAML file. + +Plugin Configuration +==================== + +The plugin is configured as a global plugin and requires a path to a YAML +configuration file. Load the plugin by adding a line to the +:file:`plugin.config`: + +.. code-block:: text + + connection_exempt_list.so /path/to/exempt_list.yaml + +Configuration File Format +========================== + +The exempt list configuration file must be in YAML format with the following +simple structure: + +.. code-block:: yaml + + exempt_list: + - 127.0.0.1 + - ::1 + - 192.168.1.0/24 + - 10.0.0.0/8 + +The configuration file supports the same range formats as +:ts:cv:`proxy.config.http.per_client.connection.exempt_list`. + +* Individual IPv4 addresses (e.g., ``192.168.1.100``) +* Individual IPv6 addresses (e.g., ``::1``, ``2001:db8::1``) +* IPv4 CIDR ranges (e.g., ``192.168.0.0/16``) +* Ranges as a dash-separated string (e.g., ``10.0.0.0-10.0.0.255``) + +Example Usage +============= + +1. Create an exempt list configuration file (e.g., +``/opt/ats/etc/trafficserver/exempt_localhost.yaml``): + + .. code-block:: yaml + + exempt_list: + - 127.0.0.1 + - ::1 + +2. Enable the plugin in :file:`plugin.config`: + + .. code-block:: text + + connection_exempt_list.so /opt/ats/etc/trafficserver/exempt_localhost.yaml + +3. Configure per-client connection limits in :file:`records.yaml`: + + .. code-block:: yaml + + records: + net: + per_client: + max_connections_in: 300 + +4. Start |TS|. The plugin will load the exempt list and not apply the per-client + connection limit to the exempted IP addresses and ranges. + +See Also +======== + +* :ts:cv:`proxy.config.net.per_client.max_connections_in` +* :ts:cv:`proxy.config.http.per_client.connection.exempt_list` +* :doc:`../files/plugin.config.en` diff --git a/doc/admin-guide/plugins/index.en.rst b/doc/admin-guide/plugins/index.en.rst index d92a4b1da09..23d79f490bf 100644 --- a/doc/admin-guide/plugins/index.en.rst +++ b/doc/admin-guide/plugins/index.en.rst @@ -171,6 +171,7 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi Cache Fill Certifier Cert Reporting Tool + Connection Exempt List Cookie Remap GeoIP ACL FQ Pacing @@ -209,6 +210,10 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi :doc:`Cert Reporting Tool ` Examines and logs information on loaded certificates. +:doc:`Connection Exempt List ` + Provides a way for administrators to set + :ts:cv:`proxy.config.http.per_client.connection.exempt_list` via a YAML file. + :doc:`Cookie Remap ` Makes decisions on destinations based on cookies. diff --git a/doc/developer-guide/api/functions/TSConnectionLimitExemptList.en.rst b/doc/developer-guide/api/functions/TSConnectionLimitExemptList.en.rst new file mode 100644 index 00000000000..684695aa39e --- /dev/null +++ b/doc/developer-guide/api/functions/TSConnectionLimitExemptList.en.rst @@ -0,0 +1,125 @@ +.. 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. + +.. default-domain:: cpp + +TSConnectionLimitExemptList +=========================== + +Synopsis +-------- + +.. code-block:: cpp + + #include + +.. function:: TSReturnCode TSConnectionLimitExemptListAdd(std::string_view ip_ranges) +.. function:: TSReturnCode TSConnectionLimitExemptListRemove(std::string_view ip_ranges) +.. function:: void TSConnectionLimitExemptListClear() + +Description +----------- + +These functions manage the per-client connection limit exempt list, which contains IP addresses +and ranges that are exempt from the connection limits enforced by +:ts:cv:`proxy.config.net.per_client.max_connections_in`. + +:func:`TSConnectionLimitExemptListAdd` adds one or more IP addresses or CIDR ranges specified in +:arg:`ip_ranges` to the existing exempt list. The :arg:`ip_ranges` parameter can be a single +IP address or CIDR range, or a comma-separated string of multiple ranges (e.g., +"192.168.1.10,10.0.0.0/8,172.16.0.0/12"). The ranges are added without removing any existing +entries. Returns :enumerator:`TS_SUCCESS` if all ranges were successfully added, :enumerator:`TS_ERROR` if +any of the IP ranges are invalid or if the operation fails. + +:func:`TSConnectionLimitExemptListRemove` removes one or more IP addresses or CIDR ranges specified in +:arg:`ip_ranges` from the existing exempt list. The :arg:`ip_ranges` parameter can be a single +IP address or CIDR range, or a comma-separated string of multiple ranges. If a range is not present +in the list, it is silently ignored. Returns :enumerator:`TS_SUCCESS` if all ranges were successfully +processed, :enumerator:`TS_ERROR` if any of the IP ranges are invalid or if the operation fails. + +:func:`TSConnectionLimitExemptListClear` removes all entries from the per-client connection +limit exempt list. After calling this function, all clients will be subject to connection +limits. This function does not return a value and never fails. + +All functions are thread-safe and can be called from any plugin context. Changes made through +these functions will override any configuration set via +:ts:cv:`proxy.config.http.per_client.connection.exempt_list`. + +Return Values +------------- + +:func:`TSConnectionLimitExemptListAdd` and :func:`TSConnectionLimitExemptListRemove` return +:enumerator:`TS_SUCCESS` if the operation completed successfully, or :enumerator:`TS_ERROR` if the +operation failed due to invalid input or system errors. + +Examples +-------- + +.. code-block:: cpp + + #include + #include + #include + + void load_exempt_list_from_file(const char *filename) { + std::ifstream file(filename); + if (!file.is_open()) { + TSError("Failed to open exempt list file: %s", filename); + return; + } + + // Clear existing exempt list before loading from file + TSConnectionLimitExemptListClear(); + + std::string line; + int line_num = 0; + while (std::getline(file, line)) { + line_num++; + + // Skip empty lines and comments + if (line.empty() || line[0] == '#') { + continue; + } + + // Add each IP range to the exempt list + TSReturnCode result = TSConnectionLimitExemptListAdd(line.c_str()); + if (result != TS_SUCCESS) { + TSError("Failed to add IP range '%s' from line %d in %s", line.c_str(), line_num, filename); + } else { + TSDebug("exempt_list", "Added IP range: %s", line.c_str()); + } + } + file.close(); + } + + void TSPluginInit(int argc, const char *argv[]) { + const char *exempt_file = "exempt_ips.txt"; + + // Check if custom file specified in plugin arguments + if (argc > 1) { + exempt_file = argv[1]; + } + + // Load exempt list from file + load_exempt_list_from_file(exempt_file); + } + + +See Also +-------- + +:ts:cv:`proxy.config.net.per_client.max_connections_in`, +:ts:cv:`proxy.config.http.per_client.connection.exempt_list` diff --git a/include/cripts/Epilogue.hpp b/include/cripts/Epilogue.hpp index c7299828985..6537609167c 100644 --- a/include/cripts/Epilogue.hpp +++ b/include/cripts/Epilogue.hpp @@ -752,10 +752,12 @@ TSPluginInit(int argc, const char *argv[]) inst->NeedCallback(enabled_txn_hooks); TSContDataSet(contp, context); TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, contp); // This acts similarly to the DoRemap callback + } else if (needs_glb_init) { + CDebug("[%s] - No global hooks, but there is a global init callback", info.plugin_name); } else { + TSError("[%s] - No global hooks, no global init callback", info.plugin_name); delete context; delete inst; - TSError("[%s] - No global hooks enabled", info.plugin_name); } } diff --git a/include/iocore/net/ConnectionTracker.h b/include/iocore/net/ConnectionTracker.h index 86784b2f38e..5236917e3b0 100644 --- a/include/iocore/net/ConnectionTracker.h +++ b/include/iocore/net/ConnectionTracker.h @@ -82,16 +82,22 @@ class ConnectionTracker /** Static configuration values. */ struct GlobalConfig { + GlobalConfig() = default; + GlobalConfig(GlobalConfig const &); + GlobalConfig &operator=(GlobalConfig const &); + std::chrono::seconds client_alert_delay{60}; ///< Alert delay in seconds. std::chrono::seconds server_alert_delay{60}; ///< Alert delay in seconds. bool metric_enabled{false}; ///< Enabling per server metrics. std::string metric_prefix; ///< Per server metric prefix. + swoc::IPRangeSet client_exempt_list; ///< The set of IP addresses to not block due client connection counting. }; // The names of the configuration values. // Unfortunately these are not used in RecordsConfig.cc so that must be made consistent by hand. // Note: These need to be @c constexpr or there are static initialization ordering risks. static constexpr std::string_view CONFIG_CLIENT_VAR_ALERT_DELAY{"proxy.config.http.per_client.connection.alert_delay"}; + static constexpr std::string_view CONFIG_CLIENT_VAR_EXEMPT_LIST{"proxy.config.http.per_client.connection.exempt_list"}; static constexpr std::string_view CONFIG_SERVER_VAR_MAX{"proxy.config.http.per_server.connection.max"}; static constexpr std::string_view CONFIG_SERVER_VAR_MIN{"proxy.config.http.per_server.connection.min"}; static constexpr std::string_view CONFIG_SERVER_VAR_MATCH{"proxy.config.http.per_server.connection.match"}; @@ -172,11 +178,18 @@ class ConnectionTracker std::shared_ptr _g; ///< Active group for this transaction. bool _reserved_p{false}; ///< Set if a connection slot has been reserved. bool _queued_p{false}; ///< Set if the connection is delayed / queued. + bool _exempt_p{false}; ///< Set if the peer is in the connection exempt list. /// Check if tracking is active. - bool is_active(); + bool is_active() const; + + /// Whether this group is in the connection max exempt list. + /// @return @c true if this group should not be blocked due to + /// proxy.config.net.per_client.max_connections_in. + bool is_exempt() const; /// Reserve a connection. + /// @return the number of tracked connections. int reserve(); /// Release a connection reservation. void release(); @@ -272,6 +285,42 @@ class ConnectionTracker */ static void config_init(GlobalConfig *global, TxnConfig *txn, RecConfigUpdateCb const &config_cb); + /** Set the client connection exempt list programmatically. + * + * This allows plugins to override the per-client connection exempt list with their own + * IPRangeSet. This will replace the existing exempt list entirely. + * + * @param ip_ranges The IPRangeSet containing the addresses that should be exempt from per-client connection limits. + * @return true if the exempt list was successfully updated, false otherwise. + */ + static bool set_client_exempt_list(swoc::IPRangeSet const &ip_ranges); + + /** Add an IP range to the client connection exempt list. + * + * This allows plugins to add an additional IP range to the existing per-client connection exempt list. + * The new range will be added to any existing ranges in the list. + * + * @param ip_range The IPRange containing the addresses to add to the exempt list. + * @return true if the range was successfully added, false otherwise. + */ + static bool add_client_exempt_range(swoc::IPRange const &ip_range); + + /** Remove an IP range from the client connection exempt list. + * + * This allows plugins to remove an IP range from the existing per-client connection exempt list. + * If the range is not present in the list, the operation succeeds without error. + * + * @param ip_range The IPRange containing the addresses to remove from the exempt list. + * @return true if the operation completed successfully, false otherwise. + */ + static bool remove_client_exempt_range(swoc::IPRange const &ip_range); + + /** Clear all IP ranges from the client connection exempt list. + * + * This allows plugins to remove all entries from the per-client connection exempt list. + */ + static void clear_client_exempt_list(); + /// Debug control used for debugging output. static inline DbgCtl dbg_ctl{"conn_track"}; @@ -382,11 +431,17 @@ ConnectionTracker::Group::metric_name(const Key &key, std::string_view fqdn, std } inline bool -ConnectionTracker::TxnState::is_active() +ConnectionTracker::TxnState::is_active() const { return nullptr != _g; } +inline bool +ConnectionTracker::TxnState::is_exempt() const +{ + return _exempt_p; +} + inline int ConnectionTracker::TxnState::reserve() { diff --git a/include/ts/ts.h b/include/ts/ts.h index 7bfab149827..8e990a7625f 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -2897,6 +2897,36 @@ TSReturnCode TSHostStatusGet(const char *hostname, const size_t hostname_len, TS void TSHostStatusSet(const char *hostname, const size_t hostname_len, TSHostStatus status, const unsigned int down_time, const unsigned int reason); +/* + * Add one or more IP addresses or CIDR ranges to the per-client connection limit exempt list. + * This function allows plugins to programmatically add to the list of IP addresses + * that should be exempt from per-client connection limits (see + * proxy.config.net.per_client.max_connections_in). + * + * @param ip_ranges The IP address or CIDR range to exempt, or a comma-separated list of ranges. + * @return TS_SUCCESS if the exempt list was successfully updated, TS_ERROR otherwise. + */ +TSReturnCode TSConnectionLimitExemptListAdd(std::string_view ip_ranges); + +/* + * Remove one or more IP addresses or CIDR ranges from the per-client connection limit exempt list. + * This function allows plugins to programmatically remove from the list of IP addresses + * that should be exempt from per-client connection limits (see + * proxy.config.net.per_client.max_connections_in). + * + * @param ip_ranges The IP address or CIDR range to remove, or a comma-separated list of ranges. + * @return TS_SUCCESS if the exempt list was successfully updated, TS_ERROR otherwise. + */ +TSReturnCode TSConnectionLimitExemptListRemove(std::string_view ip_ranges); + +/* + * Clear the per-client connection limit exempt list. + * This function allows plugins to programmatically clear the list of IP addresses + * that should be exempt from per-client connection limits (see + * proxy.config.net.per_client.max_connections_in). + */ +void TSConnectionLimitExemptListClear(); + /* * Set or get various HTTP Transaction control settings. */ diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e4f4f7af3fc..d61fa837f2c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -16,6 +16,7 @@ ####################### include(add_atsplugin) +include(add_cript) # The experimental plugins are handled in cmake/ExperimentalPlugins.cmake. diff --git a/plugins/experimental/CMakeLists.txt b/plugins/experimental/CMakeLists.txt index 20a54f4705c..33e9b29faad 100644 --- a/plugins/experimental/CMakeLists.txt +++ b/plugins/experimental/CMakeLists.txt @@ -29,6 +29,9 @@ endif() if(BUILD_CERT_REPORTING_TOOL) add_subdirectory(cert_reporting_tool) endif() +if(BUILD_CONNECTION_EXEMPT_LIST) + add_subdirectory(connection_exempt_list) +endif() if(BUILD_COOKIE_REMAP) add_subdirectory(cookie_remap) endif() diff --git a/plugins/experimental/connection_exempt_list/CMakeLists.txt b/plugins/experimental/connection_exempt_list/CMakeLists.txt new file mode 100644 index 00000000000..32560531c88 --- /dev/null +++ b/plugins/experimental/connection_exempt_list/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. +# +####################### + +# Include the add_cript macro +include(add_cript) + +# Build the connection_exempt_list cript as a pre-compiled plugin +add_cript(connection_exempt_list connection_exempt_list.cript) diff --git a/plugins/experimental/connection_exempt_list/connection_exempt_list.cript b/plugins/experimental/connection_exempt_list/connection_exempt_list.cript new file mode 100644 index 00000000000..b94edace2ff --- /dev/null +++ b/plugins/experimental/connection_exempt_list/connection_exempt_list.cript @@ -0,0 +1,86 @@ +/* + 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 +#include + +glb_init() +{ + CDebug("Initializing per-client connection exempt list plugin"); + if (instance.Size() != 1) { + TSError("Expected a single exempt list filepath as argument, %lu provided.", instance.Size()); + return; + } + // First argument (after plugin name) should be the filename + auto filename = AsString(instance.data[0]); + + CDebug("Loading exempt list from file: {}", filename); + + // Clear any existing exempt list + TSConnectionLimitExemptListClear(); + + try { + // Load the YAML file + YAML::Node config = YAML::LoadFile(filename.c_str()); + integer added_count = 0; + + // Check if the root node contains the exempt_list key + if (!config["exempt_list"]) { + TSError("YAML file '%s' does not contain an 'exempt_list' key", filename.c_str()); + return; + } + + const YAML::Node &exempt_list = config["exempt_list"]; + + // Verify that exempt_list is a sequence/array + if (!exempt_list.IsSequence()) { + TSError("'exempt_list' in YAML file '%s' is not a sequence/array", filename.c_str()); + return; + } + + // Iterate through each IP range in the exempt list + for (const YAML::Node &item : exempt_list) { + if (!item.IsScalar()) { + TSError("Non-scalar item found in exempt_list, skipping"); + continue; + } + + std::string ip_range = item.as(); + + // Add IP range to exempt list + TSReturnCode result = TSConnectionLimitExemptListAdd(ip_range); + if (result == TS_SUCCESS) { + added_count++; + CDebug("Added IP range: {}", ip_range); + } else { + TSError("Failed to add IP range '%s'", ip_range.c_str()); + } + } + + CDebug("Successfully processed YAML file, added {} IP ranges to exempt list", added_count); + + } catch (const YAML::Exception &e) { + TSError("YAML parsing error for file '%s': %s", filename.c_str(), e.what()); + } catch (const std::exception &e) { + TSError("Failed to process exempt list file '%s': %s", filename.c_str(), e.what()); + } catch (...) { + TSError("Unknown error while processing exempt list file '%s'", filename.c_str()); + } +} + +#include diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 6c6f34b37e9..4b4f5ecb381 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -89,6 +89,7 @@ #include "mgmt/rpc/jsonrpc/JsonRPC.h" #include +#include #include "ts/ts.h" /**************************************************************** @@ -9167,3 +9168,43 @@ TSHttpTxnTypeGet(TSHttpTxn txnp) } return retval; } + +TSReturnCode +TSConnectionLimitExemptListAdd(std::string_view ip_ranges) +{ + swoc::TextView ip_ranges_tv{ip_ranges}; + while (auto ip_range_tv = ip_ranges_tv.take_prefix_at(',')) { + swoc::IPRange ip_range; + if (!ip_range.load(ip_range_tv)) { + return TS_ERROR; + } + bool success = ConnectionTracker::add_client_exempt_range(ip_range); + if (!success) { + return TS_ERROR; + } + } + return TS_SUCCESS; +} + +TSReturnCode +TSConnectionLimitExemptListRemove(std::string_view ip_ranges) +{ + swoc::TextView ip_ranges_tv{ip_ranges}; + while (auto ip_range_tv = ip_ranges_tv.take_prefix_at(',')) { + swoc::IPRange ip_range; + if (!ip_range.load(ip_range_tv)) { + return TS_ERROR; + } + bool success = ConnectionTracker::remove_client_exempt_range(ip_range); + if (!success) { + return TS_ERROR; + } + } + return TS_SUCCESS; +} + +void +TSConnectionLimitExemptListClear() +{ + ConnectionTracker::clear_client_exempt_list(); +} diff --git a/src/cripts/Instance.cc b/src/cripts/Instance.cc index 598e992d177..ebc1f3a34b6 100644 --- a/src/cripts/Instance.cc +++ b/src/cripts/Instance.cc @@ -37,6 +37,15 @@ Instance::_initialize(int argc, const char *argv[], const char *filename, bool r data[i - 2] = s; } _size = argc - 2; + } else { + // Global plugins don't have the from/to url values. + for (int i = 1; i < argc && i <= 16; i++) { + auto s = cripts::string(argv[i]); + + s.trim("\"\'"); + data[i - 1] = s; + } + _size = argc - 1; } // Set the debug tag for this plugin, slightly annoying that we have to calculate diff --git a/src/iocore/net/ConnectionTracker.cc b/src/iocore/net/ConnectionTracker.cc index 4a2e65eaf3d..90f6c380dcf 100644 --- a/src/iocore/net/ConnectionTracker.cc +++ b/src/iocore/net/ConnectionTracker.cc @@ -24,6 +24,7 @@ #include "P_Net.h" // For Metrics. #include "iocore/net/ConnectionTracker.h" #include "records/RecCore.h" +#include "swoc/IPAddr.h" using namespace std::literals; @@ -211,7 +212,72 @@ Groups_To_JSON(std::vector> cons return text; } -} // namespace +bool +Config_Update_Conntrack_Client_Exempt_List(const char * /* name ATS_UNUSED */, RecDataT dtype, RecData data, void *cookie) +{ + if (RECD_STRING != dtype) { + Warning("Invalid type for '%s' - must be 'STRING'", ConnectionTracker::CONFIG_CLIENT_VAR_EXEMPT_LIST.data()); + return false; + } + auto *config = static_cast(cookie); + if (data.rec_string == nullptr) { + // There is no exempt list configured. Ensure that our exempt list is empty. + config->client_exempt_list.clear(); + return true; + } + std::string_view exempt_list_string{data.rec_string}; + ink_release_assert(config != nullptr); + + // Clear the existing exempt list. + config->client_exempt_list.clear(); + + // Parse the comma-separated list of IP ranges. + swoc::TextView ranges{exempt_list_string}; + while (!ranges.empty()) { + swoc::TextView range_sv = ranges.take_prefix_at(','); + range_sv.trim_if(&isspace); + + if (!range_sv.empty()) { + swoc::IPRange range; + if (!range.load(range_sv)) { + Warning("%s: '%.*s' is not a valid IP range in configuration '%s'", ConnectionTracker::CONFIG_CLIENT_VAR_EXEMPT_LIST.data(), + static_cast(range_sv.size()), range_sv.data(), ConnectionTracker::CONFIG_CLIENT_VAR_EXEMPT_LIST.data()); + return false; + } + config->client_exempt_list.mark(range); + } + } + + return true; +} + +} // anonymous namespace + +ConnectionTracker::GlobalConfig::GlobalConfig(GlobalConfig const &other) +{ + this->client_alert_delay = other.client_alert_delay; + this->server_alert_delay = other.server_alert_delay; + this->metric_enabled = other.metric_enabled; + this->metric_prefix = other.metric_prefix; + this->client_exempt_list.clear(); + for (auto const &ip_range : other.client_exempt_list) { + this->client_exempt_list.mark(ip_range); + } +} + +ConnectionTracker::GlobalConfig & +ConnectionTracker::GlobalConfig::operator=(GlobalConfig const &other) +{ + this->client_alert_delay = other.client_alert_delay; + this->server_alert_delay = other.server_alert_delay; + this->metric_enabled = other.metric_enabled; + this->metric_prefix = other.metric_prefix; + this->client_exempt_list.clear(); + for (auto const &ip_range : other.client_exempt_list) { + this->client_exempt_list.mark(ip_range); + } + return *this; +} void ConnectionTracker::config_init(GlobalConfig *global, TxnConfig *txn, RecConfigUpdateCb const &config_cb) @@ -220,6 +286,7 @@ ConnectionTracker::config_init(GlobalConfig *global, TxnConfig *txn, RecConfigUp // Per transaction lookup must be done at call time because it changes. Enable_Config_Var(CONFIG_CLIENT_VAR_ALERT_DELAY, &Config_Update_Conntrack_Client_Alert_Delay, config_cb, global); + Enable_Config_Var(CONFIG_CLIENT_VAR_EXEMPT_LIST, &Config_Update_Conntrack_Client_Exempt_List, config_cb, global); Enable_Config_Var(CONFIG_SERVER_VAR_MIN, &Config_Update_Conntrack_Min, config_cb, txn); Enable_Config_Var(CONFIG_SERVER_VAR_MAX, &Config_Update_Conntrack_Max, config_cb, txn); Enable_Config_Var(CONFIG_SERVER_VAR_MATCH, &Config_Update_Conntrack_Match, config_cb, txn); @@ -228,10 +295,74 @@ ConnectionTracker::config_init(GlobalConfig *global, TxnConfig *txn, RecConfigUp Enable_Config_Var(CONFIG_SERVER_VAR_METRIC_PREFIX, &Config_Update_Conntrack_Metric_Prefix, config_cb, global); } +bool +ConnectionTracker::set_client_exempt_list(swoc::IPRangeSet const &ip_ranges) +{ + if (_global_config == nullptr) { + Warning("ConnectionTracker::set_client_exempt_list called before config_init"); + return false; + } + + // Clear the existing exempt list and copy the new ranges. + _global_config->client_exempt_list.clear(); + for (auto const &ip_range : ip_ranges) { + _global_config->client_exempt_list.mark(ip_range); + } + + return true; +} + +bool +ConnectionTracker::add_client_exempt_range(swoc::IPRange const &ip_range) +{ + if (_global_config == nullptr) { + Warning("ConnectionTracker::add_client_exempt_range called before config_init"); + return false; + } + + // Add the new range to the existing exempt list. + _global_config->client_exempt_list.mark(ip_range); + + return true; +} + +bool +ConnectionTracker::remove_client_exempt_range(swoc::IPRange const &ip_range) +{ + if (_global_config == nullptr) { + Warning("ConnectionTracker::remove_client_exempt_range called before config_init"); + return false; + } + + // Remove the range from the existing exempt list. + _global_config->client_exempt_list.erase(ip_range); + + return true; +} + +void +ConnectionTracker::clear_client_exempt_list() +{ + if (_global_config == nullptr) { + Warning("ConnectionTracker::clear_client_exempt_list called before config_init"); + return; + } + + // Clear all ranges from the exempt list. + _global_config->client_exempt_list.clear(); +} + ConnectionTracker::TxnState ConnectionTracker::obtain_inbound(IpEndpoint const &addr) { - TxnState zret; + TxnState zret; + if (_global_config->client_exempt_list.contains(swoc::IPAddr{addr})) { + // This short-circuits all our connection throttling logic. Save time by + // just setting the flag for the caller to see that connections are exempt + // this address. + zret._exempt_p = true; + return zret; + } CryptoHash hash; Group::Key key{addr, hash, MatchType::MATCH_IP}; std::lock_guard lock(_inbound_table._mutex); // Table lock diff --git a/src/iocore/net/Net.cc b/src/iocore/net/Net.cc index 7c8abcb7272..1c81eefb1fd 100644 --- a/src/iocore/net/Net.cc +++ b/src/iocore/net/Net.cc @@ -79,7 +79,8 @@ register_net_stats() net_rsb.connections_throttled_in = Metrics::Counter::createPtr("proxy.process.net.connections_throttled_in"); net_rsb.per_client_connections_throttled_in = Metrics::Counter::createPtr("proxy.process.net.per_client.connections_throttled_in"); - net_rsb.connections_throttled_out = Metrics::Counter::createPtr("proxy.process.net.connections_throttled_out"); + net_rsb.per_client_connections_exempt_in = Metrics::Counter::createPtr("proxy.process.net.per_client.connections_exempt_in"); + net_rsb.connections_throttled_out = Metrics::Counter::createPtr("proxy.process.net.connections_throttled_out"); net_rsb.tunnel_total_client_connections_blind_tcp = Metrics::Counter::createPtr("proxy.process.tunnel.total_client_connections_blind_tcp"); net_rsb.tunnel_current_client_connections_blind_tcp = diff --git a/src/iocore/net/P_Net.h b/src/iocore/net/P_Net.h index e43a686d72b..eae79f379e9 100644 --- a/src/iocore/net/P_Net.h +++ b/src/iocore/net/P_Net.h @@ -45,6 +45,7 @@ struct NetStatsBlock { Metrics::Gauge::AtomicType *connections_currently_open; Metrics::Counter::AtomicType *connections_throttled_in; Metrics::Counter::AtomicType *per_client_connections_throttled_in; + Metrics::Counter::AtomicType *per_client_connections_exempt_in; Metrics::Counter::AtomicType *connections_throttled_out; Metrics::Counter::AtomicType *default_inactivity_timeout_applied; Metrics::Counter::AtomicType *default_inactivity_timeout_count; diff --git a/src/iocore/net/UnixNetAccept.cc b/src/iocore/net/UnixNetAccept.cc index 63ebb6b21b8..94a88648542 100644 --- a/src/iocore/net/UnixNetAccept.cc +++ b/src/iocore/net/UnixNetAccept.cc @@ -54,8 +54,14 @@ handle_max_client_connections(IpEndpoint const &addr, std::shared_ptr 0) { - auto inbound_tracker = ConnectionTracker::obtain_inbound(addr); - auto const tracked_count = inbound_tracker.reserve(); + auto inbound_tracker = ConnectionTracker::obtain_inbound(addr); + if (inbound_tracker.is_exempt()) { + // The user configured connections like this to not be tracked. Simply exempt it. + Metrics::Counter::increment(net_rsb.per_client_connections_exempt_in); + Dbg(dbg_ctl_iocore_net_accepts, "Ignoring client connection counting for an incoming address in the exempt list."); + return true; + } + auto const tracked_count = inbound_tracker.reserve(); if (tracked_count > client_max) { // close the connection as we are in per client connection throttle state inbound_tracker.release(); @@ -63,6 +69,7 @@ handle_max_client_connections(IpEndpoint const &addr, std::shared_ptr str: class PerClientConnectionMaxTest: """Define an object to test our max client connection behavior.""" - _dns_counter: int = 0 - _server_counter: int = 0 - _ts_counter: int = 0 - _client_counter: int = 0 + _process_counter: int = 0 _max_client_connections: int = 3 _protocol_to_replay_file = { Protocol.HTTP: 'http_slow_origins.replay.yaml', @@ -58,15 +56,36 @@ class PerClientConnectionMaxTest: Protocol.HTTP2: 'http2_slow_origins.replay.yaml', } - def __init__(self, protocol: int) -> None: + def __init__(self, protocol: int, exempt_list: str = '', exempt_list_file: str = '', exempt_list_applies: bool = False) -> None: """Configure the test processes in preparation for the TestRun. :param protocol: The protocol to test. + :param exempt_list: A comma-separated string of IP addresses or ranges to exempt. + The default empty string implies that no exempt list will be configured. + :param exempt_list_file: A file containing a list of IP addresses or ranges to exempt. + The default empty string implies that no exempt list will be configured. + :param exempt_list_applies: If True, the exempt list is assumed to exempt + the test connections. Thus the per client max connections is expected + not to be enforced for the connections. """ + self._process_counter = PerClientConnectionMaxTest._process_counter + PerClientConnectionMaxTest._process_counter += 1 self._protocol = protocol protocol_string = Protocol.to_str(protocol) self._replay_file = self._protocol_to_replay_file[protocol] - tr = Test.AddTestRun(f'proxy.config.net.per_client.connection.max: {protocol_string}') + self._exempt_list = exempt_list + self._exempt_list_file = exempt_list_file + self._exempt_list_applies = exempt_list_applies + + exempt_list_description = 'exempted' if exempt_list_applies else 'not exempted' + exempt_description = 'no exempt list' + if exempt_list: + exempt_description = 'exempt list string' + elif exempt_list_file: + exempt_description = 'exempt list file' + tr = Test.AddTestRun( + f'proxy.config.net.per_client.connection.max: {protocol_string}, ' + f'{exempt_description}: {exempt_list_description}') self._configure_dns(tr) self._configure_server(tr) self._configure_trafficserver() @@ -78,37 +97,33 @@ def _configure_dns(self, tr: 'TestRun') -> None: :param tr: The TestRun to add the nameserver to. """ - name = f'dns{PerClientConnectionMaxTest._dns_counter}' + name = f'dns{self._process_counter}' self._dns = tr.MakeDNServer(name, default='127.0.0.1') - PerClientConnectionMaxTest._dns_counter += 1 def _configure_server(self, tr: 'TestRun') -> None: """Configure the server to be used in the test. :param tr: The TestRun to add the server to. """ - name = f'server{PerClientConnectionMaxTest._server_counter}' + name = f'server{self._process_counter}' self._server = tr.AddVerifierServerProcess(name, self._replay_file) - PerClientConnectionMaxTest._server_counter += 1 - self._server.Streams.All += Testers.ContainsExpression( - "first-request", "Verify the first request should have been received.") - self._server.Streams.All += Testers.ContainsExpression( - "second-request", "Verify the second request should have been received.") - self._server.Streams.All += Testers.ContainsExpression( - "third-request", "Verify the third request should have been received.") - self._server.Streams.All += Testers.ContainsExpression( - "fifth-request", "Verify the fifth request should have been received.") - - # The fourth request should be blocked due to too many connections. - self._server.Streams.All += Testers.ExcludesExpression( - "fourth-request", "Verify the fourth request should not be received.") + self._server.Streams.All += Testers.ContainsExpression("first-request", "Verify the first request was received.") + self._server.Streams.All += Testers.ContainsExpression("second-request", "Verify the second request was received.") + self._server.Streams.All += Testers.ContainsExpression("third-request", "Verify the third request was received.") + self._server.Streams.All += Testers.ContainsExpression("fifth-request", "Verify the fifth request was received.") + + if self._exempt_list_applies: + # The fourth request should be allowed due to the exempt_list. + self._server.Streams.All += Testers.ContainsExpression("fourth-request", "Verify the fourth request was received.") + else: + # The fourth request should be blocked due to too many connections. + self._server.Streams.All += Testers.ExcludesExpression("fourth-request", "Verify the fourth request was not received.") def _configure_trafficserver(self) -> None: """Configure Traffic Server to be used in the test.""" # Associate ATS with the Test so that metrics can be verified. - name = f'ts{PerClientConnectionMaxTest._ts_counter}' + name = f'ts{self._process_counter}' self._ts = Test.MakeATSProcess(name, enable_cache=False, enable_tls=True) - PerClientConnectionMaxTest._ts_counter += 1 self._ts.addDefaultSSLFiles() self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') if self._protocol == Protocol.HTTP: @@ -126,48 +141,63 @@ def _configure_trafficserver(self) -> None: 'proxy.config.dns.nameservers': f"127.0.0.1:{self._dns.Variables.Port}", 'proxy.config.dns.resolv_conf': 'NULL', 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'socket|http|net_queue|iocore_net|conn_track', + 'proxy.config.diags.debug.tags': 'socket|http|net_queue|iocore_net|conn_track|cripts', 'proxy.config.net.per_client.max_connections_in': self._max_client_connections, # Disable keep-alive so we close the client connections when the # transactions are done. This allows us to verify cleanup is working # per the ConnectionTracker metrics. 'proxy.config.http.keep_alive_enabled_in': 0, }) - self._ts.Disk.diags_log.Content += Testers.ContainsExpression( - f'WARNING:.*too many connections:.*limit={self._max_client_connections}', - 'Verify the user is warned about the connection limit being hit.') + if self._exempt_list_file: + exempt_list_absolute = os.path.join(self._ts.Variables.CONFIGDIR, os.path.basename(self._exempt_list_file)) + self._ts.Setup.Copy(self._exempt_list_file, exempt_list_absolute) + self._ts.Disk.plugin_config.AddLine(f'connection_exempt_list.so {exempt_list_absolute}') + elif self._exempt_list: + self._ts.Disk.records_config.update({ + 'proxy.config.http.per_client.connection.exempt_list': self._exempt_list, + }) + if self._exempt_list_applies: + self._ts.Disk.diags_log.Content += Testers.ExcludesExpression( + f'WARNING:.*too many connections:', 'Connections should not be throttled due to the exempt list.') + else: + self._ts.Disk.diags_log.Content += Testers.ContainsExpression( + f'WARNING:.*too many connections:.*limit={self._max_client_connections}', + 'Verify the user is warned about the connection limit being hit.') def _configure_client(self, tr: 'TestRun') -> None: """Configure the TestRun. :param tr: The TestRun to add the client to. """ - name = f'client{PerClientConnectionMaxTest._client_counter}' + name = f'client{self._process_counter}' p = tr.AddVerifierClientProcess( name, self._replay_file, http_ports=[self._ts.Variables.port], https_ports=[self._ts.Variables.ssl_port]) - PerClientConnectionMaxTest._client_counter += 1 p.StartBefore(self._dns) p.StartBefore(self._server) p.StartBefore(self._ts) - # Because the fourth connection will be aborted, the client will have a - # non-zero return code. - p.ReturnCode = 1 - p.Streams.All += Testers.ContainsExpression("first-request", "Verify the first request should have been received.") - p.Streams.All += Testers.ContainsExpression("second-request", "Verify the second request should have been received.") - p.Streams.All += Testers.ContainsExpression("third-request", "Verify the third request should have been received.") - p.Streams.All += Testers.ContainsExpression("fifth-request", "Verify the fifth request should have been received.") - if self._protocol == Protocol.HTTP: - p.Streams.All += Testers.ContainsExpression( - "The peer closed the connection while reading.", - "A connection should be closed due to too many client connections.") - p.Streams.All += Testers.ContainsExpression( - "Failed HTTP/1 transaction with key: fourth-request", "The fourth request should fail.") + p.Streams.All += Testers.ContainsExpression("first-request", "Verify the first request was received.") + p.Streams.All += Testers.ContainsExpression("second-request", "Verify the second request was received.") + p.Streams.All += Testers.ContainsExpression("third-request", "Verify the third request was received.") + p.Streams.All += Testers.ContainsExpression("fifth-request", "Verify the fifth request was received.") + if self._exempt_list_applies: + p.ReturnCode = 0 + p.Streams.All += Testers.ContainsExpression("fourth-request", "Verify the fourth request was received.") else: - p.Streams.All += Testers.ContainsExpression( - "ECONNRESET: Connection reset by peer", "A connection should be closed due to too many client connections.") - p.Streams.All += Testers.ExcludesExpression("fourth-request", "The fourth request should fail.") + # Because the fourth connection will be aborted, the client will have a + # non-zero return code. + p.ReturnCode = 1 + if self._protocol == Protocol.HTTP: + p.Streams.All += Testers.ContainsExpression( + "The peer closed the connection while reading.", + "A connection should be closed due to too many client connections.") + p.Streams.All += Testers.ContainsExpression( + "Failed HTTP/1 transaction with key: fourth-request", "The fourth request should fail.") + else: + p.Streams.All += Testers.ContainsExpression( + "ECONNRESET: Connection reset by peer", "A connection should be closed due to too many client connections.") + p.Streams.All += Testers.ExcludesExpression("fourth-request", "The fourth request should fail.") def _verify_metrics(self) -> None: """Verify the per client connection metrics.""" @@ -176,10 +206,20 @@ def _verify_metrics(self) -> None: tr.Processes.Default.Command = ( 'traffic_ctl metric get ' 'proxy.process.net.per_client.connections_throttled_in ' + 'proxy.process.net.per_client.connections_exempt_in ' 'proxy.process.net.connection_tracker_table_size') tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Streams.All += Testers.ContainsExpression( - 'proxy.process.net.per_client.connections_throttled_in 1', 'Verify the per client throttled metric is correct.') + if self._exempt_list_applies: + tr.Processes.Default.Streams.All += Testers.ContainsExpression( + 'proxy.process.net.per_client.connections_throttled_in 0', 'Verify no connections were recorded as throttled.') + tr.Processes.Default.Streams.All += Testers.ContainsExpression( + 'proxy.process.net.per_client.connections_exempt_in 5', + 'Verify that the connections were all recorded as exempted.') + else: + tr.Processes.Default.Streams.All += Testers.ContainsExpression( + 'proxy.process.net.per_client.connections_throttled_in 1', 'Verify the connection was recorded as throttled.') + tr.Processes.Default.Streams.All += Testers.ContainsExpression( + 'proxy.process.net.per_client.connections_exempt_in 0', 'Verify no connections were recorded as exempt.') tr.Processes.Default.Streams.All += Testers.ContainsExpression( 'proxy.process.net.connection_tracker_table_size 0', 'Verify the table was cleaned up correctly.') @@ -187,3 +227,11 @@ def _verify_metrics(self) -> None: PerClientConnectionMaxTest(Protocol.HTTP) PerClientConnectionMaxTest(Protocol.HTTPS) PerClientConnectionMaxTest(Protocol.HTTP2) + +PerClientConnectionMaxTest(Protocol.HTTP, exempt_list='127.0.0.1,::1', exempt_list_applies=True) +PerClientConnectionMaxTest(Protocol.HTTPS, exempt_list='1.2.3.4,5.6.0.0/16', exempt_list_applies=False) +PerClientConnectionMaxTest(Protocol.HTTP2, exempt_list='0/0,::/0', exempt_list_applies=True) + +PerClientConnectionMaxTest(Protocol.HTTP, exempt_list_file='exempt_lists/exempt_localhost.yaml', exempt_list_applies=True) +PerClientConnectionMaxTest(Protocol.HTTP, exempt_list_file='exempt_lists/no_localhost.yaml', exempt_list_applies=False) +PerClientConnectionMaxTest(Protocol.HTTP, exempt_list_file='exempt_lists/exempt_all.yaml', exempt_list_applies=True) diff --git a/tests/gold_tests/cripts/files/basic.cript b/tests/gold_tests/cripts/files/basic.cript index 9c9343c0928..789f40b31e2 100644 --- a/tests/gold_tests/cripts/files/basic.cript +++ b/tests/gold_tests/cripts/files/basic.cript @@ -20,21 +20,21 @@ do_read_response() { - borrow resp = cripts::Server::Response::Get(); + borrow resp = cripts::Server::Response::Get(); resp["responseHeader"] = "changed"; } do_send_response() { - borrow resp = cripts::Client::Response::Get(); - borrow conn =cripts::Client::Connection::Get(); + borrow resp = cripts::Client::Response::Get(); + borrow conn = cripts::Client::Connection::Get(); resp["criptsResponseHeader"] = "response"; if (conn.IsTLS()) { - const auto tls = cripts::Certs::Server(conn); - resp["X-Subject"] = tls.subject; + const auto tls = cripts::Certs::Server(conn); + resp["X-Subject"] = tls.subject; resp["X-NotBefore"] = tls.notBefore; - resp["X-NotAfter"] = tls.notAfter; + resp["X-NotAfter"] = tls.notAfter; } } diff --git a/tools/clang-format.sh b/tools/clang-format.sh index 8e1d68d4ece..23e17bb6a46 100755 --- a/tools/clang-format.sh +++ b/tools/clang-format.sh @@ -100,7 +100,7 @@ EOF start_time_file=$(mktemp -t clang-format-start-time.XXXXXXXXXX) touch ${start_time_file} - target_files=$(find $DIR -iname \*.[ch] -o -iname \*.cc -o -iname \*.h.in -o -iname \*.hpp | grep -vE 'lib/(Catch2|fastlz|ls-hpack|swoc|systemtap|yamlcpp)') + target_files=$(find $DIR -iname \*.[ch] -o -iname \*.cc -o -iname \*.h.in -o -iname \*.hpp -o -iname \*.cript | grep -vE 'lib/(Catch2|fastlz|ls-hpack|swoc|systemtap|yamlcpp)') for file in ${target_files}; do # The ink_autoconf.h and ink_autoconf.h.in files are generated files, # so they do not need to be re-formatted by clang-format. Doing so diff --git a/tools/git/pre-commit b/tools/git/pre-commit index ed859b8c5b7..e947fca2b4d 100755 --- a/tools/git/pre-commit +++ b/tools/git/pre-commit @@ -67,7 +67,7 @@ REPO_ROOT=$(cd $(dirname $0)/../.. && git rev-parse --show-toplevel) YAPF_CONFIG=${REPO_ROOT}/.style.yapf git diff-index --cached --diff-filter=ACMR --name-only HEAD | grep -vE "lib/(Catch2|fastlz|ls-hpack|swoc|yamlcpp)" | while read file; do case "$file" in - *.cc | *.c | *.h | *.h.in) + *.cc | *.c | *.h | *.h.in | *.cript) ${FORMAT} "$file" | diff -u "$file" - >>"$clang_patch_file" ;; # Keep this list of Python extensions the same with the list of