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