This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 0e42870205 Add a per client connection max exempt list cript (#12476)
0e42870205 is described below

commit 0e4287020548e7316156a4debfed49c0fedc7312
Author: Brian Neradt <[email protected]>
AuthorDate: Wed Dec 10 22:52:14 2025 -0600

    Add a per client connection max exempt list cript (#12476)
    
    This updates our build system to be able to make pre-compiled cripts.
    Thus, adding a cript is now as easy as adding a plugin. Simply use
    add_cript instead of add_atsplugin in CMakeLists.txt, and it will build
    your cript for you.
    
    As a part of this, this adds connection_exempt_list.cript, our first
    cript plugin that sets the per client exempt list.
---
 .gitignore                                         |   3 +-
 .vscode/settings.json                              |   3 +-
 cmake/ExperimentalPlugins.cmake                    |   1 +
 cmake/add_cript.cmake                              |  35 +++++++
 doc/Doxyfile                                       |   2 +-
 .../plugins/connection_exempt_list.en.rst          | 103 +++++++++++++++++++++
 doc/admin-guide/plugins/index.en.rst               |   5 +
 include/cripts/Epilogue.hpp                        |   6 +-
 plugins/CMakeLists.txt                             |   1 +
 plugins/experimental/CMakeLists.txt                |   3 +
 .../connection_exempt_list/CMakeLists.txt          |  19 ++++
 .../connection_exempt_list.cript                   |  86 +++++++++++++++++
 src/api/InkAPI.cc                                  |   6 ++
 src/cripts/Instance.cc                             |   9 ++
 src/iocore/net/ConnectionTracker.cc                |   5 +
 tests/gold_tests/autest-site/setup.cli.ext         |   1 +
 .../gold_tests/autest-site/trafficserver.test.ext  |   7 +-
 .../client_connection/exempt_lists/exempt_all.yaml |  19 ++++
 .../exempt_lists/exempt_localhost.yaml             |  19 ++++
 .../exempt_lists/no_localhost.yaml                 |  19 ++++
 .../per_client_connection_max.test.py              |  27 +++++-
 tests/gold_tests/cripts/files/basic.cript          |  12 +--
 tools/clang-format.sh                              |   2 +-
 tools/git/pre-commit                               |   2 +-
 24 files changed, 375 insertions(+), 20 deletions(-)

diff --git a/.gitignore b/.gitignore
index b3d53eed65..707dfb8f74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -170,7 +170,8 @@ rc/trafficserver.service
 .libs/
 
 .svn/
-.vscode/
+.vscode/*
+!.vscode/settings.json
 target
 
 tsxs
diff --git a/.vscode/settings.json b/.vscode/settings.json
index bf5756ef90..9757af98a9 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,7 @@
 {
     "files.associations": {
+        "*.cript": "cpp",
+        "*.test.py": "python",
         "*.test.ext": "python"
     }
 }
-
diff --git a/cmake/ExperimentalPlugins.cmake b/cmake/ExperimentalPlugins.cmake
index e8ea614134..487e0bfed1 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 0000000000..4a14537073
--- /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 b8022942e6..aa1e06da9c 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/plugins/connection_exempt_list.en.rst 
b/doc/admin-guide/plugins/connection_exempt_list.en.rst
new file mode 100644
index 0000000000..cb1f93372e
--- /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 3334bdc2d7..8de06d9724 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -170,6 +170,7 @@ directory of the |TS| source tree. Experimental plugins can 
be compiled by passi
    Cache Fill <cache_fill.en>
    Certifier <certifier.en>
    Cert Reporting Tool <cert_reporting_tool.en>
+   Connection Exempt List <connection_exempt_list.en>
    Cookie Remap <cookie_remap.en>
    GeoIP ACL <geoip_acl.en>
    FQ Pacing <fq_pacing.en>
@@ -208,6 +209,10 @@ directory of the |TS| source tree. Experimental plugins 
can be compiled by passi
 :doc:`Cert Reporting Tool <cert_reporting_tool.en>`
    Examines and logs information on loaded certificates.
 
+:doc:`Connection Exempt List <connection_exempt_list.en>`
+   Provides a way for administrators to set
+   :ts:cv:`proxy.config.http.per_client.connection.exempt_list` via a YAML 
file.
+
 :doc:`Cookie Remap <cookie_remap.en>`
    Makes decisions on destinations based on cookies.
 
diff --git a/include/cripts/Epilogue.hpp b/include/cripts/Epilogue.hpp
index c729982898..715673ccb0 100644
--- a/include/cripts/Epilogue.hpp
+++ b/include/cripts/Epilogue.hpp
@@ -753,9 +753,13 @@ TSPluginInit(int argc, const char *argv[])
     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/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index e4f4f7af3f..d61fa837f2 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 20a54f4705..33e9b29faa 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 0000000000..e58158b484
--- /dev/null
+++ b/plugins/experimental/connection_exempt_list/CMakeLists.txt
@@ -0,0 +1,19 @@
+#######################
+#
+#  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.
+#
+#######################
+
+# 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 0000000000..b94edace2f
--- /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 <cripts/Preamble.hpp>
+#include <yaml-cpp/yaml.h>
+
+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<std::string>();
+
+      // 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 <cripts/Epilogue.hpp>
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index 1353be4f77..cabfb5309d 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -8947,12 +8947,15 @@ 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;
     }
@@ -8964,12 +8967,15 @@ 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;
     }
diff --git a/src/cripts/Instance.cc b/src/cripts/Instance.cc
index 598e992d17..ebc1f3a34b 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 5a0b1c59ca..b7ea956723 100644
--- a/src/iocore/net/ConnectionTracker.cc
+++ b/src/iocore/net/ConnectionTracker.cc
@@ -234,12 +234,14 @@ Config_Update_Conntrack_Client_Exempt_List(const char * 
/* name ATS_UNUSED */, R
   // This ensures we don't lose the previous configuration if parsing fails.
   swoc::IPRangeSet new_exempt_list;
   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<int>(range_sv.size()), range_sv.data(), 
ConnectionTracker::CONFIG_CLIENT_VAR_EXEMPT_LIST.data());
@@ -251,6 +253,7 @@ Config_Update_Conntrack_Client_Exempt_List(const char * /* 
name ATS_UNUSED */, R
 
   // Parsing succeeded. Now acquire the lock and replace the global exempt 
list.
   std::lock_guard<ts::bravo::shared_mutex> 
lock(config->client_exempt_list_mutex);
+
   config->client_exempt_list.clear();
   for (auto const &ip_range : new_exempt_list) {
     config->client_exempt_list.mark(ip_range);
@@ -267,6 +270,7 @@ ConnectionTracker::GlobalConfig::GlobalConfig(GlobalConfig 
const &other)
   this->server_alert_delay = other.server_alert_delay;
   this->metric_enabled     = other.metric_enabled;
   this->metric_prefix      = other.metric_prefix;
+
   // Lock the source to safely copy the exempt list.
   // Note: the mutex itself is not copied; it's default-constructed.
   ts::bravo::shared_lock<ts::bravo::shared_mutex> 
lock(other.client_exempt_list_mutex);
@@ -322,6 +326,7 @@ ConnectionTracker::set_client_exempt_list(swoc::IPRangeSet 
const &ip_ranges)
 
   // Acquire exclusive lock and replace the exempt list.
   std::lock_guard<ts::bravo::shared_mutex> 
lock(_global_config->client_exempt_list_mutex);
+
   _global_config->client_exempt_list.clear();
   for (auto const &ip_range : ip_ranges) {
     _global_config->client_exempt_list.mark(ip_range);
diff --git a/tests/gold_tests/autest-site/setup.cli.ext 
b/tests/gold_tests/autest-site/setup.cli.ext
index 8f16da423f..f7460ffa00 100644
--- a/tests/gold_tests/autest-site/setup.cli.ext
+++ b/tests/gold_tests/autest-site/setup.cli.ext
@@ -111,6 +111,7 @@ if ENV['ATS_BIN'] is not None:
     host.WriteVerbose(['ats'], "Traffic server version:", out)
 
 Variables.AtsExampleDir = os.path.join(AutestSitePath, '..', '..', '..', 
'example')
+Variables.AtsToolsDir = os.path.join(AutestSitePath, '..', '..', '..', 'tools')
 Variables.AtsTestToolsDir = os.path.join(AutestSitePath, '..', '..', 'tools')
 Variables.VerifierBinPath = ENV['VERIFIER_BIN']
 Variables.BuildRoot = ENV['BUILD_ROOT']
diff --git a/tests/gold_tests/autest-site/trafficserver.test.ext 
b/tests/gold_tests/autest-site/trafficserver.test.ext
index a76762d029..4cc7a2704f 100755
--- a/tests/gold_tests/autest-site/trafficserver.test.ext
+++ b/tests/gold_tests/autest-site/trafficserver.test.ext
@@ -408,10 +408,11 @@ def MakeATSProcess(
         })
 
     if enable_cripts:
-        p.Setup.Copy(
-            os.path.join(p.Variables.RepoDir, 'tools', 'cripts', 
'compiler.sh'), os.path.join(bin_path, 'cripts_compiler.sh'))
+        cripts_compiler = os.path.join(bin_path, 'cripts_compiler.sh')
+        p.Setup.Copy(os.path.join(p.Variables.RepoDir, 'tools', 'cripts', 
'compiler.sh'), cripts_compiler)
+        p.Variables.cripts_compiler = cripts_compiler
         p.Disk.records_config.update({
-            'proxy.config.plugin.compiler_path': os.path.join(bin_path, 
'cripts_compiler.sh'),
+            'proxy.config.plugin.compiler_path': cripts_compiler,
         })
         # ATS_ROOT is used by compiler.sh to find the ATS install directory.
         p.Env['ATS_ROOT'] = p.Variables.PREFIX
diff --git a/tests/gold_tests/client_connection/exempt_lists/exempt_all.yaml 
b/tests/gold_tests/client_connection/exempt_lists/exempt_all.yaml
new file mode 100644
index 0000000000..b6b2b1f105
--- /dev/null
+++ b/tests/gold_tests/client_connection/exempt_lists/exempt_all.yaml
@@ -0,0 +1,19 @@
+#  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.
+
+exempt_list:
+  - 0/0
+  - ::/0
diff --git 
a/tests/gold_tests/client_connection/exempt_lists/exempt_localhost.yaml 
b/tests/gold_tests/client_connection/exempt_lists/exempt_localhost.yaml
new file mode 100644
index 0000000000..37988009bc
--- /dev/null
+++ b/tests/gold_tests/client_connection/exempt_lists/exempt_localhost.yaml
@@ -0,0 +1,19 @@
+#  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.
+
+exempt_list:
+  - 127.0.0.1
+  - ::1
diff --git a/tests/gold_tests/client_connection/exempt_lists/no_localhost.yaml 
b/tests/gold_tests/client_connection/exempt_lists/no_localhost.yaml
new file mode 100644
index 0000000000..31bd6d75f8
--- /dev/null
+++ b/tests/gold_tests/client_connection/exempt_lists/no_localhost.yaml
@@ -0,0 +1,19 @@
+#  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.
+
+exempt_list:
+  - 1.2.3.4
+  - 5.6.0.0/16
diff --git 
a/tests/gold_tests/client_connection/per_client_connection_max.test.py 
b/tests/gold_tests/client_connection/per_client_connection_max.test.py
index c2eaa2a128..aa887d0768 100644
--- a/tests/gold_tests/client_connection/per_client_connection_max.test.py
+++ b/tests/gold_tests/client_connection/per_client_connection_max.test.py
@@ -18,6 +18,7 @@ Verify the behavior of 
proxy.config.net.per_client.connection.max.
 #  limitations under the License.
 
 from enum import Enum
+import os
 
 Test.Summary = __doc__
 
@@ -55,15 +56,17 @@ class PerClientConnectionMaxTest:
         Protocol.HTTP2: 'http2_slow_origins.replay.yaml',
     }
 
-    def __init__(self, protocol: int, exempt_list: str = '', 
exempt_list_applies: bool = False) -> 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
-          to be enforced for the connections.
+          not to be enforced for the connections.
         """
         self._process_counter = PerClientConnectionMaxTest._process_counter
         PerClientConnectionMaxTest._process_counter += 1
@@ -71,12 +74,18 @@ class PerClientConnectionMaxTest:
         protocol_string = Protocol.to_str(protocol)
         self._replay_file = self._protocol_to_replay_file[protocol]
         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_list: {exempt_list_description}')
+            f'{exempt_description}: {exempt_list_description}')
         self._configure_dns(tr)
         self._configure_server(tr)
         self._configure_trafficserver()
@@ -132,14 +141,18 @@ class PerClientConnectionMaxTest:
                 '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,
             })
-        if self._exempt_list:
+        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,
             })
@@ -222,3 +235,7 @@ 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 9c9343c092..789f40b31e 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 8e1d68d4ec..23e17bb6a4 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 285d36afdb..e5a710a6a5 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

Reply via email to