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

dmeden 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 ab25c40a07 jsonrpc - Implement handler to fetch connection tracker 
information. (#12260)
ab25c40a07 is described below

commit ab25c40a075b05db10afaa7e2d3d1cd56303b449
Author: Damian Meden <[email protected]>
AuthorDate: Wed Jun 11 11:00:33 2025 +0200

    jsonrpc - Implement handler to fetch connection tracker information. 
(#12260)
    
    This pr implements the rpc handler, and some unit tests to validate
    basic output.
---
 doc/developer-guide/jsonrpc/jsonrpc-api.en.rst     | 84 ++++++++++++++++++++++
 include/mgmt/rpc/handlers/server/Server.h          |  1 +
 src/mgmt/rpc/handlers/server/Server.cc             | 81 +++++++++++++++++++++
 src/traffic_server/RpcAdminPubHandlers.cc          |  3 +-
 .../traffic_ctl/traffic_ctl_server_output.test.py  | 16 +++++
 .../traffic_ctl/traffic_ctl_test_utils.py          | 40 +++++++++++
 6 files changed, 224 insertions(+), 1 deletion(-)

diff --git a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst 
b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
index c1b7986345..1a966da87b 100644
--- a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
+++ b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
@@ -469,6 +469,8 @@ JSONRPC API
 
 * `filemanager.get_files_registry`_
 
+* `get_connection_tracker_info`_
+
 .. _jsonapi-management-records:
 
 
@@ -1667,6 +1669,88 @@ Response:
       }
    }
 
+.. _get_connection_tracker_info:
+
+get_connection_tracker_info
+----------------------------
+
+|method|
+
+Description
+~~~~~~~~~~~
+
+Get inbound and outbound connection tracking data from |TS|.
+
+Parameters
+~~~~~~~~~~
+
+======================= ============= 
==================================================================================
+Field                   Type          Description
+======================= ============= 
==================================================================================
+``table``               |str|         Specifies which group table to query, 
``inbound`` or ``outbound``.  If value is
+                                      ``both`` then both tables will be added 
to the response. The default is ``outbound``.
+======================= ============= 
==================================================================================
+
+You can query this api using `invoke` functionality from 
:ref:`traffic_ctl_rpc`.
+
+   .. code-block:: bash
+      :linenos:
+
+      $ traffic_ctl rpc invoke get_connection_tracker_info -p 'table: both'  
-f json
+
+
+.. note::
+
+   JSON output can be formatted by any tool like `jq`.
+
+Result
+~~~~~~
+
+The response will contain inbound/outbound info or an error. 
:ref:`jsonrpc-node-errors`.
+
+Response examples
+~~~~~~~~~~~~~~~~~
+
+   .. code-block:: json
+      :linenos:
+
+      {
+         "id":"f4477ac4-0d44-11eb-958d-001fc69cc946",
+         "jsonrpc":"2.0",
+         "result":{
+            "outbound":{
+               "count":"1",
+               "list":[
+                  {
+                     "type":"both",
+                     "ip":"127.0.0.1:80",
+                     "fqdn":"127.0.0.1",
+                     "current":"8",
+                     "max":"8",
+                     "blocked":"0",
+                     "alert":"0"
+                  }
+               ]
+            },
+            "inbound":{
+               "count":"1",
+               "list":[
+                  {
+                     "type":"ip",
+                     "ip":"127.0.0.1:34226",
+                     "fqdn":"",
+                     "current":"2",
+                     "max":"0",
+                     "blocked":"0",
+                     "alert":"0"
+                  }
+               ]
+            }
+         }
+      }
+
+
+
 See also
 ========
 
diff --git a/include/mgmt/rpc/handlers/server/Server.h 
b/include/mgmt/rpc/handlers/server/Server.h
index face7053f4..fec31c66bf 100644
--- a/include/mgmt/rpc/handlers/server/Server.h
+++ b/include/mgmt/rpc/handlers/server/Server.h
@@ -28,5 +28,6 @@ swoc::Rv<YAML::Node> server_start_drain(std::string_view 
const &id, YAML::Node c
 swoc::Rv<YAML::Node> server_stop_drain(std::string_view const &id, YAML::Node 
const &);
 void                 server_shutdown(YAML::Node const &);
 swoc::Rv<YAML::Node> get_server_status(std::string_view const &id, YAML::Node 
const &);
+swoc::Rv<YAML::Node> get_connection_tracker_info(std::string_view const &id, 
YAML::Node const &params);
 
 } // namespace rpc::handlers::server
diff --git a/src/mgmt/rpc/handlers/server/Server.cc 
b/src/mgmt/rpc/handlers/server/Server.cc
index c032e7a7ff..bd251a8f52 100644
--- a/src/mgmt/rpc/handlers/server/Server.cc
+++ b/src/mgmt/rpc/handlers/server/Server.cc
@@ -19,6 +19,7 @@
 */
 
 #include "../../../../iocore/cache/P_CacheDir.h"
+#include "iocore/net/ConnectionTracker.h"
 #include "mgmt/rpc/handlers/server/Server.h"
 #include "mgmt/rpc/handlers/common/ErrorUtils.h"
 #include "mgmt/rpc/handlers/common/Utils.h"
@@ -42,6 +43,33 @@ namespace field_names
 struct DrainInfo {
   bool noNewConnections{false};
 };
+
+struct ConnectionTrackingInfo {
+  enum TableFlags {
+    NOT_SET  = 0,
+    INBOUND  = 1 << 0, // Inbound table
+    OUTBOUND = 1 << 1  // Outbound table
+  };
+  TableFlags table{TableFlags::OUTBOUND}; // default.
+};
+using TFLags = ConnectionTrackingInfo::TableFlags;
+constexpr TFLags
+operator|(const TFLags rhs, const TFLags lhs)
+{
+  return static_cast<TFLags>(static_cast<uint32_t>(rhs) | 
static_cast<uint32_t>(lhs));
+}
+
+constexpr TFLags &
+operator|=(TFLags &rhs, TFLags lhs)
+{
+  return rhs = rhs | lhs;
+}
+
+constexpr TFLags
+operator&(TFLags rhs, TFLags lhs)
+{
+  return static_cast<TFLags>(static_cast<uint32_t>(rhs) & 
static_cast<uint32_t>(lhs));
+}
 } // namespace rpc::handlers::server
 namespace YAML
 {
@@ -61,6 +89,31 @@ template <> struct convert<rpc::handlers::server::DrainInfo> 
{
     return true;
   }
 };
+template <> struct convert<rpc::handlers::server::ConnectionTrackingInfo> {
+  static constexpr auto table{"table"};
+  static bool
+  decode(const Node &node, rpc::handlers::server::ConnectionTrackingInfo &rhs)
+  {
+    namespace field = rpc::handlers::server::field_names;
+    if (!node.IsMap()) {
+      return false;
+    }
+    // optional
+    if (auto n = node[table]; !n.IsNull()) {
+      auto const &val = n.as<std::string>();
+      if (val == "both") {
+        rhs.table = rpc::handlers::server::TFLags::INBOUND | 
rpc::handlers::server::TFLags::OUTBOUND;
+      } else if (val == "inbound") {
+        rhs.table = rpc::handlers::server::TFLags::INBOUND;
+      } else if (val == "outbound") {
+        rhs.table = rpc::handlers::server::TFLags::OUTBOUND;
+      } else {
+        throw std::runtime_error("Invalid table type. Use 
[both|inbound|outbound]");
+      }
+    }
+    return true;
+  }
+};
 } // namespace YAML
 
 namespace rpc::handlers::server
@@ -150,4 +203,32 @@ get_server_status(std::string_view const & /* params 
ATS_UNUSED */, YAML::Node c
   }
   return resp;
 }
+
+swoc::Rv<YAML::Node>
+get_connection_tracker_info(std::string_view const & /* params ATS_UNUSED */, 
YAML::Node const &params)
+{
+  swoc::Rv<YAML::Node> resp;
+  try {
+    ConnectionTrackingInfo p;
+    if (!params.IsNull()) {
+      p = params.as<ConnectionTrackingInfo>();
+    }
+
+    if (p.table & TFLags::OUTBOUND) {
+      std::string json{ConnectionTracker::outbound_to_json_string()};
+      resp.result()["outbound"] = YAML::Load(json);
+    }
+
+    if (p.table & TFLags::INBOUND) {
+      std::string json{ConnectionTracker::inbound_to_json_string()};
+      resp.result()["inbound"] = YAML::Load(json);
+    }
+
+  } catch (std::exception const &ex) {
+    resp.errata()
+      .assign(std::error_code{errors::Codes::SERVER})
+      .note("Error found when calling get_connection_tracker_info API: {}", 
ex.what());
+  }
+  return resp;
+}
 } // namespace rpc::handlers::server
diff --git a/src/traffic_server/RpcAdminPubHandlers.cc 
b/src/traffic_server/RpcAdminPubHandlers.cc
index 7e6e0fdff7..4269a37399 100644
--- a/src/traffic_server/RpcAdminPubHandlers.cc
+++ b/src/traffic_server/RpcAdminPubHandlers.cc
@@ -57,7 +57,8 @@ register_admin_jsonrpc_handlers()
                           {{rpc::RESTRICTED_API}});
   rpc::add_method_handler("get_server_status", &get_server_status, 
&core_ats_rpc_service_provider_handle,
                           {{rpc::NON_RESTRICTED_API}});
-
+  rpc::add_method_handler("get_connection_tracker_info", 
&get_connection_tracker_info, &core_ats_rpc_service_provider_handle,
+                          {{rpc::NON_RESTRICTED_API}});
   // storage
   using namespace rpc::handlers::storage;
   rpc::add_method_handler("admin_storage_set_device_offline", 
&set_storage_offline, &core_ats_rpc_service_provider_handle,
diff --git a/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py 
b/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py
index 747eb2a97f..c6362de166 100644
--- a/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py
+++ b/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py
@@ -43,3 +43,19 @@ traffic_ctl.server().drain().exec()
 traffic_ctl.server().status().validate_with_text(
     '{"initialized_done": "true", "is_ssl_handshaking_stopped": "false", 
"is_draining": "true", "is_event_system_shut_down": "false"}'
 )
+
+# Get basic and empty connection tracker info.
+traffic_ctl.rpc().invoke(
+    handler="get_connection_tracker_info", params='"table: 
both"').validate_result_with_text(
+        '{"outbound": {"count": "0", "list": []}, "inbound": {"count": "0", 
"list": []}}')
+# default = outbound only
+traffic_ctl.rpc().invoke(
+    
handler="get_connection_tracker_info").validate_result_with_text('{"outbound": 
{"count": "0", "list": []}}')
+# requets inbound oonly
+traffic_ctl.rpc().invoke(
+    handler="get_connection_tracker_info",
+    params='"table: inbound"').validate_result_with_text('{"inbound": 
{"count": "0", "list": []}}')
+# requets outbound only
+traffic_ctl.rpc().invoke(
+    handler="get_connection_tracker_info",
+    params='"table: outbound"').validate_result_with_text('{"outbound": 
{"count": "0", "list": []}}')
diff --git a/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py 
b/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py
index c8a2d57576..624e8e1f5f 100644
--- a/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py
+++ b/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py
@@ -150,6 +150,42 @@ class Server():
         self.__finish()
 
 
+class RPC():
+    """
+        Handy class to map traffic_ctl server options.
+    """
+
+    def __init__(self, dir, tr, tn):
+        self._cmd = "traffic_ctl rpc "
+        self._tr = tr
+        self._dir = dir
+        self._tn = tn
+
+    def invoke(self, handler: str, params={}):
+        if not params:
+            self._cmd = f'{self._cmd}  invoke {handler} -f json'
+        else:
+            self._cmd = f'{self._cmd}  invoke {handler} -p {str(params)} -f 
json'
+
+        return self
+
+    def __finish(self):
+        """
+            Sets the command to the test. Make sure this gets called after
+            validation is set. Without this call the test will fail.
+        """
+        self._tr.Processes.Default.Command = self._cmd
+
+    def validate_with_text(self, text: str):
+        self._tr.Processes.Default.Streams.stdout = MakeGoldFileWithText(text, 
self._dir, self._tn)
+        self.__finish()
+
+    def validate_result_with_text(self, text: str):
+        full_text = f'{{\"jsonrpc\": \"2.0\", \"result\": {text}, \"id\": 
{"``"}}}'
+        self._tr.Processes.Default.Streams.stdout = 
MakeGoldFileWithText(full_text, self._dir, self._tn)
+        self.__finish()
+
+
 '''
 
 Handy wrapper around traffic_ctl, ATS and the autest output validation 
mechanism.
@@ -213,6 +249,10 @@ class TrafficCtl(Config, Server):
         self.add_test()
         return Server(self._Test.TestDirectory, 
self._tests[self.__get_index()], self._testNumber)
 
+    def rpc(self):
+        self.add_test()
+        return RPC(self._Test.TestDirectory, self._tests[self.__get_index()], 
self._testNumber)
+
 
 def Make_traffic_ctl(test, records_yaml=None):
     tctl = TrafficCtl(test, records_yaml)

Reply via email to