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 b1465b4e0b Improve testing and documentation for server firewall marks
(#13385)
b1465b4e0b is described below
commit b1465b4e0b464021972b15581579c5fdd1ac66fe
Author: JosiahWI <[email protected]>
AuthorDate: Mon Jul 20 10:11:30 2026 -0500
Improve testing and documentation for server firewall marks (#13385)
* Improve docs for `TSHttpTxnServerPacketMarkSet`
* Add AuTest for `TSHttpTxnServerPacketMarkSet`
---
.../functions/TSHttpTxnServerPacketMarkSet.en.rst | 22 +++-
include/ts/ts.h | 16 ++-
.../pluginTest/packet_mark/packet_mark.test.py | 130 ++++++++++++++++-----
tests/tools/plugins/CMakeLists.txt | 1 +
tests/tools/plugins/packet_mark_common.cc | 20 +++-
tests/tools/plugins/packet_mark_common.h | 12 +-
tests/tools/plugins/server_packet_mark.cc | 107 +++++++++++++++++
7 files changed, 263 insertions(+), 45 deletions(-)
diff --git
a/doc/developer-guide/api/functions/TSHttpTxnServerPacketMarkSet.en.rst
b/doc/developer-guide/api/functions/TSHttpTxnServerPacketMarkSet.en.rst
index c6641e5d4b..8940f04e05 100644
--- a/doc/developer-guide/api/functions/TSHttpTxnServerPacketMarkSet.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpTxnServerPacketMarkSet.en.rst
@@ -20,8 +20,6 @@
TSHttpTxnServerPacketMarkSet
****************************
-Change packet firewall mark for the server side connection.
-
Synopsis
========
@@ -34,13 +32,25 @@ Synopsis
Description
===========
+Change the packet firewall :arg:`mark` for the server side (origin) connection.
+The entire firewall mark is replaced with :arg:`mark`, which is interpreted as
a
+32-bit unsigned bit pattern.
+
+Always returns :const:`TS_SUCCESS`, including when no server connection has
been
+established yet.
+
.. note::
- The change takes effect immediately. If no OS connection has been
- made, then this sets the mark that will be used. If an OS connection
- is established
+ The firewall mark is only honored on platforms whose OS supports it,
+ specifically Linux via ``SO_MARK``. On platforms without ``SO_MARK`` support
+ the call still returns :const:`TS_SUCCESS`, but setting the mark has no
effect
+ at the OS layer (it is a safe no-op).
+
+.. note::
-.. XXX Third sentence above needs to be completed.
+ If a live server connection exists, the mark is applied to it immediately;
the
+ mark is also recorded on the transaction so that any subsequent server
+ connection for this transaction uses it.
See Also
========
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 2a4eacfd4d..cff3a73b6f 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1602,12 +1602,18 @@ TSReturnCode TSHttpSsnClientFdGet(TSHttpSsn
ssnp, int *fdp);
TSReturnCode TSHttpTxnClientPacketMarkSet(TSHttpTxn txnp, int mark);
/** Change packet firewall mark for the server side connection
- *
- @note The change takes effect immediately, if no OS connection has been
- made, then this sets the mark that will be used IF an OS connection
- is established
- @return TS_SUCCESS if the (future?) server connection was modified
+ Sets the entire server-side packet firewall mark to @a mark; the whole
mark is replaced. @a mark
+ is interpreted as a 32-bit unsigned bit pattern.
+
+ @note The firewall mark is only honored on platforms whose OS supports it,
specifically Linux via
+ @c SO_MARK. On platforms without @c SO_MARK support the call still returns
TS_SUCCESS, but setting
+ the mark has no effect at the OS layer (it is a safe no-op).
+
+ @note If a live server connection exists, the mark is applied to it
immediately; the mark is also
+ recorded on the transaction so that any subsequent server connection for
this transaction uses it.
+
+ @return TS_SUCCESS always, including when no server connection has been
established yet.
*/
TSReturnCode TSHttpTxnServerPacketMarkSet(TSHttpTxn txnp, int mark);
diff --git a/tests/gold_tests/pluginTest/packet_mark/packet_mark.test.py
b/tests/gold_tests/pluginTest/packet_mark/packet_mark.test.py
index 796e6d5d44..1af23f2cbd 100644
--- a/tests/gold_tests/pluginTest/packet_mark/packet_mark.test.py
+++ b/tests/gold_tests/pluginTest/packet_mark/packet_mark.test.py
@@ -18,9 +18,11 @@ import os
import socket
Test.Summary = '''
-Verify TSHttpTxnClientPacketMarkSet sets the client-side firewall mark to the
-supplied value, using a test plugin that reads the applied mark back off the
-client socket.
+Verify TSHttpTxnClientPacketMarkSet and TSHttpTxnServerPacketMarkSet set the
+firewall mark on the client- and server-side connections respectively. Each is
+driven by a test plugin that applies the mark and reads it back off the
relevant
+socket with getsockopt(SO_MARK), echoing the observed value into a response
+header this test asserts on.
'''
@@ -44,60 +46,136 @@ def _can_set_so_mark() -> bool:
Test.SkipUnless(
Condition.IsPlatform("linux"),
- Condition(_can_set_so_mark, "Setting SO_MARK requires Linux with
CAP_NET_ADMIN or CAP_NET_RAW", True),
+ # pass_value defaults to True: run only when the probe reports SO_MARK is
settable.
+ Condition(_can_set_so_mark, "Setting SO_MARK requires Linux with
CAP_NET_ADMIN or CAP_NET_RAW"),
)
+# SOCK_OPT_PACKET_MARK (0x10) | SOCK_OPT_NO_DELAY (0x1). The mark is only
pushed
+# to the socket when the PACKET_MARK bit is set in the sock option flag.
+SOCK_OPT_FLAG_PACKET_MARK = 0x11
-class ClientPacketMarkTest:
- """Drive TSHttpTxnClientPacketMarkSet through a test plugin and assert on
the
- firewall mark read back off the client socket.
- The starting mark is seeded per process via
- proxy.config.net.sock_packet_mark_in, applied at accept time.
+class PacketMarkTest:
+ """Drive a TSHttpTxn*PacketMarkSet API through a test plugin and assert on
the
+ firewall mark read back off the relevant socket.
+
+ This base holds the shared skeleton -- process setup, the common records,
the
+ curl-and-assert case runner. Each subclass supplies its plugin and echo
+ header and extends _configure() (via super()) with the side-specific mark
and
+ flag records.
"""
# Value the plugin sets; the mark is expected to become exactly this.
SET_MARK = 0x0000000A
+ # Seeded starting mark, distinct from SET_MARK so a no-op would be visible.
+ SEED_MARK = 0x0000FF00
+
+ # Bumped per instance so each side gets uniquely-numbered processes.
+ _counter = 0
def __init__(self):
+ self._num = PacketMarkTest._counter
+ PacketMarkTest._counter += 1
self._server = self._make_server()
- self._ts = self._make_ats("ts", seed_mark=0x0000FF00)
+ self._ts = self._make_ats()
+ self._configure(self._ts)
+ self._started = False
- def _make_server(self) -> 'Process':
- server = Test.MakeOriginServer("server")
+ def _make_server(self):
+ server = Test.MakeOriginServer(f"server{self._num}")
request_header = {"headers": "GET / HTTP/1.1\r\nHost:
example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection:
close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
- server.addResponse("sessionlog.json", request_header, response_header)
+ for _ in range(2):
+ server.addResponse("sessionlog.json", request_header,
response_header)
return server
- def _make_ats(self, name: str, seed_mark: int) -> 'Process':
- ts = Test.MakeATSProcess(name, enable_cache=False)
+ def _make_ats(self):
+ return Test.MakeATSProcess(f"ts{self._num}", enable_cache=False)
+
+ def _configure(self, ts):
+ # Records and remap shared by both sides. Subclasses override to add
the
+ # side-specific mark/flag records and load their plugin, calling
super()
+ # for these.
ts.Disk.records_config.update(
{
- 'proxy.config.net.sock_packet_mark_in': seed_mark,
- 'proxy.config.net.sock_option_flag_in': 0x11,
- 'proxy.config.diags.debug.enabled': 1,
- 'proxy.config.diags.debug.tags': 'http|client_packet_mark',
'proxy.config.url_remap.remap_required': 0,
# Keep ATS running as the invoking user inside sudo (no
privilege drop).
'proxy.config.admin.user_id': '#-1',
})
ts.Disk.remap_config.AddLine(f"map /
http://127.0.0.1:{self._server.Variables.Port}")
- Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir,
'client_packet_mark.so'), ts)
- return ts
- def run(self):
+ def _add_case(self, echo_header: str, description: str, set_header: str):
# The mark is set to the supplied value, regardless of the seeded
- # starting mark.
- tr = Test.AddTestRun("TSHttpTxnClientPacketMarkSet sets the mark")
+ # starting mark. The set is driven by whichever request header the
plugin
+ # keys on; the observed mark is echoed into echo_header. The origin
server
+ # and ATS are started before the first case, independent of the order
in
+ # which cases are added.
+ tr = Test.AddTestRun(description)
tr.Processes.Default.StartBefore(self._server)
tr.Processes.Default.StartBefore(self._ts)
+ if not self._started:
+ tr.StillRunningAfter = self._server
+ tr.StillRunningAfter = self._ts
+ self._started = True
tr.MakeCurlCommand(
- f'--verbose --ipv4 --header "X-Set-Mark: 0x{self.SET_MARK:08x}"
http://localhost:{self._ts.Variables.port}/',
+ f'--verbose --ipv4 --header "{set_header}: 0x{self.SET_MARK:08x}"
http://localhost:{self._ts.Variables.port}/',
ts=self._ts)
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.All += Testers.ContainsExpression(
- f"X-Client-Packet-Mark: 0x{self.SET_MARK:08x}", f"Observed client
packet mark should be 0x{self.SET_MARK:08x}")
+ f"{echo_header}: 0x{self.SET_MARK:08x}", f"Observed packet mark
should be 0x{self.SET_MARK:08x}")
+
+
+class ClientPacketMarkTest(PacketMarkTest):
+ """Exercise TSHttpTxnClientPacketMarkSet. The client mark is seeded on the
+ inbound socket.
+ """
+
+ ECHO_HEADER = "X-Client-Packet-Mark"
+
+ def _configure(self, ts):
+ super()._configure(ts)
+ ts.Disk.records_config.update(
+ {
+ 'proxy.config.net.sock_packet_mark_in': self.SEED_MARK,
+ 'proxy.config.net.sock_option_flag_in':
SOCK_OPT_FLAG_PACKET_MARK,
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'http|client_packet_mark',
+ })
+ Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir,
f'client_packet_mark.so'), ts)
+
+ def run(self):
+ self._add_case(self.ECHO_HEADER, "client_packet_mark sets the
client-side mark on the live connection", "X-Set-Mark")
+
+
+class ServerPacketMarkTest(PacketMarkTest):
+ """Exercise TSHttpTxnServerPacketMarkSet. The server mark is seeded on the
+ outbound socket.
+
+ The server API additionally records the mark for a *future* origin
+ connection (TSHttpTxnConfigIntSet on TS_CONFIG_NET_SOCK_PACKET_MARK_OUT),
+ which the client API has no equivalent of. The server plugin exposes this
by
+ honoring X-Set-Mark-Preconnect at READ_REQUEST_HDR, before any origin
+ connection exists -- so the mark can only reach the socket via that seed.
+ """
+
+ ECHO_HEADER = "X-Server-Packet-Mark"
+
+ def _configure(self, ts):
+ super()._configure(ts)
+ ts.Disk.records_config.update(
+ {
+ 'proxy.config.net.sock_packet_mark_out': self.SEED_MARK,
+ 'proxy.config.net.sock_option_flag_out':
SOCK_OPT_FLAG_PACKET_MARK,
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'http|server_packet_mark',
+ })
+ Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir,
f'server_packet_mark.so'), ts)
+
+ def run(self):
+ self._add_case(self.ECHO_HEADER, "server_packet_mark sets the
server-side mark on the live connection", "X-Set-Mark")
+ self._add_case(
+ self.ECHO_HEADER, "server_packet_mark seeds the mark for a future
origin connection", "X-Set-Mark-Preconnect")
ClientPacketMarkTest().run()
+ServerPacketMarkTest().run()
diff --git a/tests/tools/plugins/CMakeLists.txt
b/tests/tools/plugins/CMakeLists.txt
index 701022dd27..a058eea6c6 100644
--- a/tests/tools/plugins/CMakeLists.txt
+++ b/tests/tools/plugins/CMakeLists.txt
@@ -25,6 +25,7 @@ add_autest_plugin(fatal_shutdown fatal_shutdown.cc)
add_autest_plugin(hook_add_plugin hook_add_plugin.cc)
add_autest_plugin(missing_mangled_definition missing_mangled_definition_c.c
missing_mangled_definition_cpp.cc)
add_autest_plugin(missing_ts_plugin_init missing_ts_plugin_init.cc)
+add_autest_plugin(server_packet_mark server_packet_mark.cc
packet_mark_common.cc)
add_autest_plugin(ssl_client_verify_test ssl_client_verify_test.cc)
add_autest_plugin(ssl_hook_test ssl_hook_test.cc)
add_autest_plugin(ssl_secret_load_test ssl_secret_load_test.cc)
diff --git a/tests/tools/plugins/packet_mark_common.cc
b/tests/tools/plugins/packet_mark_common.cc
index ac47799c12..85b72d48d5 100644
--- a/tests/tools/plugins/packet_mark_common.cc
+++ b/tests/tools/plugins/packet_mark_common.cc
@@ -1,6 +1,6 @@
/** @file
- Shared helpers for the packet-mark test plugins.
+ Shared helpers for the client_packet_mark and server_packet_mark test
plugins.
@section license License
@@ -118,9 +118,9 @@ namespace
}
// 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.
+ // driven only by the four named entry points below. See packet_mark_common.h
+ // for why the public API is split by client/server rather than taking the
+ // function as an argument.
template <MarkSetter Setter>
void
apply_mark_from_header(const LogContext &log, TSHttpTxn txnp,
std::string_view header)
@@ -178,10 +178,22 @@ apply_client_mark(const LogContext &log, TSHttpTxn txnp,
std::string_view header
apply_mark_from_header<TSHttpTxnClientPacketMarkSet>(log, txnp, header);
}
+void
+apply_server_mark(const LogContext &log, TSHttpTxn txnp, std::string_view
header)
+{
+ apply_mark_from_header<TSHttpTxnServerPacketMarkSet>(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);
}
+void
+echo_server_mark(const LogContext &log, TSHttpTxn txnp, std::string_view
echo_header)
+{
+ echo_observed_mark<TSHttpTxnServerFdGet, TSHttpTxnServerRespGet>(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
index 3e43e53d0b..2acc840cfd 100644
--- a/tests/tools/plugins/packet_mark_common.h
+++ b/tests/tools/plugins/packet_mark_common.h
@@ -1,10 +1,10 @@
/** @file
- Shared helpers for the packet-mark test plugins.
+ Shared helpers for the client_packet_mark and server_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
+ Both plugins read a target mark out of a request header, apply it to a
+ connection via the tsapi under test, read the applied mark back off the
+ relevant socket with getsockopt(SO_MARK), and echo 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.
@@ -42,6 +42,10 @@ struct LogContext {
void apply_client_mark(const LogContext &log, TSHttpTxn txnp, std::string_view
header);
+void apply_server_mark(const LogContext &log, TSHttpTxn txnp, std::string_view
header);
+
void echo_client_mark(const LogContext &log, TSHttpTxn txnp, std::string_view
echo_header);
+void echo_server_mark(const LogContext &log, TSHttpTxn txnp, std::string_view
echo_header);
+
} // namespace packet_mark
diff --git a/tests/tools/plugins/server_packet_mark.cc
b/tests/tools/plugins/server_packet_mark.cc
new file mode 100644
index 0000000000..2d8c1cb56a
--- /dev/null
+++ b/tests/tools/plugins/server_packet_mark.cc
@@ -0,0 +1,107 @@
+/** @file
+
+ Test plugin for the TSHttpTxnServerPacketMarkSet API.
+
+ The plugin exercises both halves of the TSHttpTxnServerPacketMarkSet
contract,
+ selected by request header:
+
+ - X-Set-Mark: applied at TS_HTTP_READ_RESPONSE_HDR_HOOK, when the origin
+ connection is already live. This tests the "apply to the live server
+ connection immediately" path.
+
+ - X-Set-Mark-Preconnect: applied at TS_HTTP_READ_REQUEST_HDR_HOOK, before
an
+ origin connection exists. This tests the server-only "record the mark so
a
+ future origin connection is opened with it" path -- there is no live vc
to
+ apply to at that point, so the mark reaches the socket only via the
+ transaction config seed.
+
+ Regardless of which header drove the set, the readback happens at
+ TS_HTTP_READ_RESPONSE_HDR_HOOK: the origin fd is valid there and the server
+ response headers -- which propagate to the client response -- carry the
+ observed value (echoed into X-Server-Packet-Mark) back to curl.
+
+ @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"
+
+#include <ts/ts.h>
+
+#include <string_view>
+
+namespace
+{
+constexpr char PLUGIN_NAME[] = "server_packet_mark";
+constexpr char MARK_HEADER[] = "X-Set-Mark";
+constexpr char PRECONNECT_MARK_HEADER[] = "X-Set-Mark-Preconnect";
+constexpr char ECHO_HEADER[] = "X-Server-Packet-Mark";
+
+DbgCtl dbg_ctl{PLUGIN_NAME};
+
+int
+handle_read_request(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
+{
+ TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+
+ if (event == TS_EVENT_HTTP_READ_REQUEST_HDR) {
+ // No origin connection exists yet; this exercises the "seed the mark for a
+ // future server connection" half of the contract.
+ packet_mark::LogContext log{PLUGIN_NAME, dbg_ctl};
+ packet_mark::apply_server_mark(log, txnp, PRECONNECT_MARK_HEADER);
+ }
+
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+}
+
+int
+handle_read_response(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata)
+{
+ TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+
+ if (event == TS_EVENT_HTTP_READ_RESPONSE_HDR) {
+ // The origin connection is live here; this applies the mark to it and
reads
+ // it back off the server socket.
+ packet_mark::LogContext log{PLUGIN_NAME, dbg_ctl};
+ packet_mark::apply_server_mark(log, txnp, MARK_HEADER);
+ packet_mark::echo_server_mark(log, txnp, ECHO_HEADER);
+ }
+
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+}
+
+} // anonymous namespace
+
+void
+TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */)
+{
+ TSPluginRegistrationInfo info;
+ 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);
+ return;
+ }
+
+ TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK,
TSContCreate(handle_read_request, nullptr));
+ TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK,
TSContCreate(handle_read_response, nullptr));
+}