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

JosiahWI 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 4efc34477e Generalize client packet mark test (#13384)
4efc34477e is described below

commit 4efc34477e3523f27f906ceb8f82abe9f500e183
Author: JosiahWI <[email protected]>
AuthorDate: Thu Jul 16 09:53:26 2026 -0500

    Generalize client packet mark test (#13384)
    
    * Add util file for packet mark test plugin
    
    This introduces common utilities to make it easy to add a
    server-side packet mark test.
    
    * Generalize packet mark AuTest name
    
    * Ensure header value is null-terminated
---
 .../packet_mark.test.py}                           |   0
 tests/tools/plugins/CMakeLists.txt                 |   2 +-
 tests/tools/plugins/client_packet_mark.cc          | 118 ++-----------
 tests/tools/plugins/packet_mark_common.cc          | 187 +++++++++++++++++++++
 tests/tools/plugins/packet_mark_common.h           |  47 ++++++
 5 files changed, 249 insertions(+), 105 deletions(-)

diff --git 
a/tests/gold_tests/pluginTest/client_packet_mark/client_packet_mark.test.py 
b/tests/gold_tests/pluginTest/packet_mark/packet_mark.test.py
similarity index 100%
rename from 
tests/gold_tests/pluginTest/client_packet_mark/client_packet_mark.test.py
rename to tests/gold_tests/pluginTest/packet_mark/packet_mark.test.py
diff --git a/tests/tools/plugins/CMakeLists.txt 
b/tests/tools/plugins/CMakeLists.txt
index a658ba9aa2..701022dd27 100644
--- a/tests/tools/plugins/CMakeLists.txt
+++ b/tests/tools/plugins/CMakeLists.txt
@@ -15,7 +15,7 @@
 #
 #######################
 
-add_autest_plugin(client_packet_mark client_packet_mark.cc)
+add_autest_plugin(client_packet_mark client_packet_mark.cc 
packet_mark_common.cc)
 add_autest_plugin(conf_remap_stripped conf_remap_stripped.cc)
 add_autest_plugin(continuations_verify continuations_verify.cc)
 add_autest_plugin(cont_schedule cont_schedule.cc)
diff --git a/tests/tools/plugins/client_packet_mark.cc 
b/tests/tools/plugins/client_packet_mark.cc
index 72acab439f..6abd6aba88 100644
--- a/tests/tools/plugins/client_packet_mark.cc
+++ b/tests/tools/plugins/client_packet_mark.cc
@@ -27,121 +27,31 @@
   limitations under the License.
  */
 
-#include <ts/ts.h>
+#include "packet_mark_common.h"
 
-extern "C" {
-#include <sys/socket.h>
-}
+#include <ts/ts.h>
 
-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <optional>
-#include <string>
 #include <string_view>
 
 namespace
 {
-constexpr std::string_view PLUGIN_NAME = "client_packet_mark";
-constexpr std::string_view MARK_HEADER = "X-Set-Mark";
-constexpr std::string_view ECHO_HEADER = "X-Client-Packet-Mark";
-
-DbgCtl dbg_ctl{PLUGIN_NAME.data()};
-
-/** Read a header field and interpret its value as a 32-bit unsigned quantity.
-
-    Values are parsed with strtoul (base 0), so "0x0000000A" and "10" are both
-    accepted. Returns std::nullopt if the header is absent. */
-std::optional<uint32_t>
-get_uint_header(TSMBuffer bufp, TSMLoc hdr_loc, std::string_view header)
-{
-  TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, header.data(), 
static_cast<int>(header.length()));
-  if (field_loc == TS_NULL_MLOC) {
-    return std::nullopt;
-  }
+constexpr char PLUGIN_NAME[] = "client_packet_mark";
+constexpr char MARK_HEADER[] = "X-Set-Mark";
+constexpr char ECHO_HEADER[] = "X-Client-Packet-Mark";
 
-  int         value_len = 0;
-  const char *value_str = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, 
field_loc, -1, &value_len);
-  uint32_t    result    = 0;
-  if (value_str != nullptr && value_len > 0) {
-    std::string value(value_str, value_len);
-    result = static_cast<uint32_t>(strtoul(value.c_str(), nullptr, 0));
-  }
-  TSHandleMLocRelease(bufp, hdr_loc, field_loc);
-  return result;
-}
-
-/** Create the echo header on the response with the value formatted as 0x%08x. 
*/
-void
-set_echo_header(TSMBuffer bufp, TSMLoc hdr_loc, uint32_t value)
-{
-  // 0x + 8 hex digits for a uint32_t + NUL = 11 bytes; 16 is comfortably 
enough.
-  char formatted[16];
-  std::snprintf(formatted, sizeof(formatted), "0x%08x", value);
-
-  TSMLoc field_loc = TS_NULL_MLOC;
-  if (TSMimeHdrFieldCreateNamed(bufp, hdr_loc, ECHO_HEADER.data(), 
static_cast<int>(ECHO_HEADER.length()), &field_loc) ==
-      TS_SUCCESS) {
-    // -1 length lets the API strlen the null-terminated buffer, so we do not
-    // rely on snprintf's return value (which is the would-be length, not the
-    // truncated length) as a byte count.
-    TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, formatted, -1);
-    TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
-    TSHandleMLocRelease(bufp, hdr_loc, field_loc);
-  }
-}
+DbgCtl dbg_ctl{PLUGIN_NAME};
 
 int
 handle_send_response(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
 {
   TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
 
-  if (event != TS_EVENT_HTTP_SEND_RESPONSE_HDR) {
-    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
-    return 0;
-  }
-
-  TSMBuffer req_bufp = nullptr;
-  TSMLoc    req_loc  = TS_NULL_MLOC;
-  if (TSHttpTxnClientReqGet(txnp, &req_bufp, &req_loc) != TS_SUCCESS) {
-    TSError("[%s] Failed to get client request headers", PLUGIN_NAME.data());
-    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
-    return 0;
-  }
-
-  std::optional<uint32_t> mark = get_uint_header(req_bufp, req_loc, 
MARK_HEADER);
-  TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, req_loc);
-
-  if (mark.has_value()) {
-    Dbg(dbg_ctl, "Setting client packet mark to 0x%08x", *mark);
-    TSHttpTxnClientPacketMarkSet(txnp, static_cast<int>(*mark));
-  }
-
-  uint32_t observed = 0;
-#if defined(SO_MARK)
-  int client_fd = -1;
-  if (TSHttpTxnClientFdGet(txnp, &client_fd) == TS_SUCCESS && client_fd >= 0) {
-    socklen_t optlen = sizeof(observed);
-    if (getsockopt(client_fd, SOL_SOCKET, SO_MARK, &observed, &optlen) != 0) {
-      TSError("[%s] getsockopt(SO_MARK) failed on fd %d", PLUGIN_NAME.data(), 
client_fd);
-    }
-  } else {
-    TSError("[%s] Failed to obtain client fd", PLUGIN_NAME.data());
-  }
-#else
-  // SO_MARK is Linux-only. On other platforms the accompanying AuTest is 
skipped
-  // via Test.SkipUnless, so this readback path is never exercised; keep it
-  // compilable so the plugin still builds everywhere.
-  TSError("[%s] SO_MARK is not supported on this platform", 
PLUGIN_NAME.data());
-#endif
-
-  TSMBuffer resp_bufp = nullptr;
-  TSMLoc    resp_loc  = TS_NULL_MLOC;
-  if (TSHttpTxnClientRespGet(txnp, &resp_bufp, &resp_loc) == TS_SUCCESS) {
-    set_echo_header(resp_bufp, resp_loc, observed);
-    TSHandleMLocRelease(resp_bufp, TS_NULL_MLOC, resp_loc);
-  } else {
-    TSError("[%s] Failed to get client response headers", PLUGIN_NAME.data());
+  if (event == TS_EVENT_HTTP_SEND_RESPONSE_HDR) {
+    // The client connection is live here; this applies the mark to it and 
reads
+    // it back off the client socket.
+    packet_mark::LogContext log{PLUGIN_NAME, dbg_ctl};
+    packet_mark::apply_client_mark(log, txnp, MARK_HEADER);
+    packet_mark::echo_client_mark(log, txnp, ECHO_HEADER);
   }
 
   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
@@ -154,12 +64,12 @@ void
 TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */)
 {
   TSPluginRegistrationInfo info;
-  info.plugin_name   = PLUGIN_NAME.data();
+  info.plugin_name   = PLUGIN_NAME;
   info.vendor_name   = "Apache Software Foundation";
   info.support_email = "[email protected]";
 
   if (TSPluginRegister(&info) != TS_SUCCESS) {
-    TSError("[%s] Plugin registration failed", PLUGIN_NAME.data());
+    TSError("[%s] Plugin registration failed", PLUGIN_NAME);
     return;
   }
 
diff --git a/tests/tools/plugins/packet_mark_common.cc 
b/tests/tools/plugins/packet_mark_common.cc
new file mode 100644
index 0000000000..ac47799c12
--- /dev/null
+++ b/tests/tools/plugins/packet_mark_common.cc
@@ -0,0 +1,187 @@
+/** @file
+
+  Shared helpers for the packet-mark test plugins.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "packet_mark_common.h"
+
+extern "C" {
+#include <sys/socket.h>
+}
+
+#include <cerrno>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <optional>
+#include <string>
+
+namespace packet_mark
+{
+namespace
+{
+  // The tsapi setter and getters are bound as non-type template parameters 
below,
+  // which pins the correct client/server trio at compile time. These must be 
raw
+  // function-pointer types, not std::function: a non-type template parameter 
has
+  // to be a structural type, and std::function is a runtime type-erasure 
wrapper,
+  // so template <std::function<...> Setter> is ill-formed.
+  using MarkSetter = TSReturnCode (*)(TSHttpTxn, int);
+  using FdGetter   = TSReturnCode (*)(TSHttpTxn, int *);
+  using RespGetter = TSReturnCode (*)(TSHttpTxn, TSMBuffer *, TSMLoc *);
+
+  std::optional<uint32_t>
+  get_uint_header(TSMBuffer bufp, TSMLoc hdr_loc, std::string_view header)
+  {
+    // Values are parsed with strtoul (base 0), so "0x0000000A" and "10" are 
both
+    // accepted. Returns std::nullopt if the header is absent, empty, or not a 
valid
+    // number -- a malformed value is a test-harness error, not a silent 0.
+    TSMLoc field_loc{TSMimeHdrFieldFind(bufp, hdr_loc, header.data(), 
static_cast<int>(header.length()))};
+    if (field_loc == TS_NULL_MLOC) {
+      return std::nullopt;
+    }
+
+    int         value_len{0};
+    char const *value_str{TSMimeHdrFieldValueStringGet(bufp, hdr_loc, 
field_loc, -1, &value_len)};
+
+    std::optional<uint32_t> result{std::nullopt};
+    if (value_str != nullptr && value_len > 0) {
+      std::string value{value_str, static_cast<std::size_t>(value_len)};
+      char       *end{nullptr};
+      errno = 0;
+      unsigned long const parsed{std::strtoul(value.c_str(), &end, 0)};
+      // Reject empty, partially-numeric, or out-of-range values: a malformed
+      // header is a test-harness error, not a silent 0.
+      if (errno == 0 && end == value.c_str() + value.size() && parsed <= 
UINT32_MAX) {
+        result = static_cast<uint32_t>(parsed);
+      }
+    }
+    TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+    return result;
+  }
+
+  void
+  set_echo_header(TSMBuffer bufp, TSMLoc hdr_loc, std::string_view header, 
uint32_t value)
+  {
+    // 0x + 8 hex digits for a uint32_t + NUL = 11 bytes; 16 is comfortably 
enough.
+    char formatted[16];
+    std::snprintf(formatted, sizeof(formatted), "0x%08x", value);
+
+    TSMLoc field_loc{TS_NULL_MLOC};
+    if (TSMimeHdrFieldCreateNamed(bufp, hdr_loc, header.data(), 
static_cast<int>(header.length()), &field_loc) == TS_SUCCESS) {
+      // -1 length lets the API strlen the null-terminated buffer, so we do not
+      // rely on snprintf's return value (which is the would-be length, not the
+      // truncated length) as a byte count.
+      TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, formatted, 
-1);
+      TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
+      TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+    }
+  }
+
+  std::optional<uint32_t>
+  get_so_mark([[maybe_unused]] int fd)
+  {
+#if defined(SO_MARK)
+    if (fd < 0) {
+      return std::nullopt;
+    }
+
+    uint32_t  observed{0};
+    socklen_t optlen{sizeof(observed)};
+    if (getsockopt(fd, SOL_SOCKET, SO_MARK, &observed, &optlen) != 0) {
+      return std::nullopt;
+    }
+    return observed;
+#else
+    // SO_MARK is Linux-only. On other platforms the accompanying AuTest is
+    // skipped via Test.SkipUnless, so this readback path is never exercised;
+    // keep it compilable so the plugins still build everywhere.
+    return std::nullopt;
+#endif
+  }
+
+  // Parameterized on the exact tsapi function and kept private to this file,
+  // driven only by the named entry points below. The public API is split by
+  // client/server rather than taking the function as an argument so each 
plugin
+  // links against exactly the tsapi trio it exercises.
+  template <MarkSetter Setter>
+  void
+  apply_mark_from_header(const LogContext &log, TSHttpTxn txnp, 
std::string_view header)
+  {
+    TSMBuffer req_bufp{nullptr};
+    TSMLoc    req_loc{TS_NULL_MLOC};
+    if (TSHttpTxnClientReqGet(txnp, &req_bufp, &req_loc) != TS_SUCCESS) {
+      TSError("[%.*s] Failed to get client request headers", 
static_cast<int>(log.plugin_name.length()), log.plugin_name.data());
+      return;
+    }
+
+    std::optional<uint32_t> const mark{get_uint_header(req_bufp, req_loc, 
header)};
+    TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, req_loc);
+
+    if (mark.has_value()) {
+      Dbg(log.dbg_ctl, "Setting packet mark to 0x%08x (via %.*s)", *mark, 
static_cast<int>(header.length()), header.data());
+      if (Setter(txnp, static_cast<int>(*mark)) != TS_SUCCESS) {
+        TSError("[%.*s] Failed to set packet mark 0x%08x", 
static_cast<int>(log.plugin_name.length()), log.plugin_name.data(),
+                *mark);
+      }
+    }
+  }
+
+  template <FdGetter FdGet, RespGetter RespGet>
+  void
+  echo_observed_mark(const LogContext &log, TSHttpTxn txnp, std::string_view 
echo_header)
+  {
+    int fd{-1};
+    if (FdGet(txnp, &fd) != TS_SUCCESS || fd < 0) {
+      TSError("[%.*s] Failed to obtain socket fd", 
static_cast<int>(log.plugin_name.length()), log.plugin_name.data());
+      return;
+    }
+
+    std::optional<uint32_t> const observed{get_so_mark(fd)};
+    if (!observed.has_value()) {
+      TSError("[%.*s] Failed to read SO_MARK on fd %d", 
static_cast<int>(log.plugin_name.length()), log.plugin_name.data(), fd);
+      return;
+    }
+
+    TSMBuffer resp_bufp{nullptr};
+    TSMLoc    resp_loc{TS_NULL_MLOC};
+    if (RespGet(txnp, &resp_bufp, &resp_loc) != TS_SUCCESS) {
+      TSError("[%.*s] Failed to get response headers", 
static_cast<int>(log.plugin_name.length()), log.plugin_name.data());
+      return;
+    }
+
+    set_echo_header(resp_bufp, resp_loc, echo_header, *observed);
+    TSHandleMLocRelease(resp_bufp, TS_NULL_MLOC, resp_loc);
+  }
+} // anonymous namespace
+
+void
+apply_client_mark(const LogContext &log, TSHttpTxn txnp, std::string_view 
header)
+{
+  apply_mark_from_header<TSHttpTxnClientPacketMarkSet>(log, txnp, header);
+}
+
+void
+echo_client_mark(const LogContext &log, TSHttpTxn txnp, std::string_view 
echo_header)
+{
+  echo_observed_mark<TSHttpTxnClientFdGet, TSHttpTxnClientRespGet>(log, txnp, 
echo_header);
+}
+
+} // namespace packet_mark
diff --git a/tests/tools/plugins/packet_mark_common.h 
b/tests/tools/plugins/packet_mark_common.h
new file mode 100644
index 0000000000..3e43e53d0b
--- /dev/null
+++ b/tests/tools/plugins/packet_mark_common.h
@@ -0,0 +1,47 @@
+/** @file
+
+  Shared helpers for the packet-mark test plugins.
+
+  The plugin reads a target mark out of a request header, applies it to a
+  connection via the tsapi under test, reads the applied mark back off the
+  relevant socket with getsockopt(SO_MARK), and echoes the observed value into 
a
+  response header for the accompanying AuTest to assert on. Everything except
+  the tsapi call and the fd getter is identical, so it lives here.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include <ts/ts.h>
+
+#include <string_view>
+
+namespace packet_mark
+{
+struct LogContext {
+  std::string_view plugin_name;
+  const DbgCtl    &dbg_ctl;
+};
+
+void apply_client_mark(const LogContext &log, TSHttpTxn txnp, std::string_view 
header);
+
+void echo_client_mark(const LogContext &log, TSHttpTxn txnp, std::string_view 
echo_header);
+
+} // namespace packet_mark

Reply via email to