[PATCH net v3 2/2] selftests: net: exercise SO_MARK on connected sockets
From: Xia Yan
Date: Sun Nov 08 2026 - 20:47:13 EST
cmsg_so_mark.sh sends to explicit destinations, so it does not test the
route cached by connect().
Add -k to cmsg_sender to connect first and send without msg_name.
Check connected IPv4 and IPv6 UDP, ping and raw sockets with equal,
prohibited and zero per-message marks.
Add an exact mark 0 prohibit rule before the connected cases. Connect
with an allowed socket mark, then require rejection of a mark 0 cmsg.
The same test fails four IPv6 cases before the kernel fix and passes
all 54 with it.
Signed-off-by: Xia Yan <yanxia@xxxxxxxxxxxxxxxxxxxx>
---
v3:
- Add an exact mark 0 prohibit rule for the connected cases.
- Require rejection of a mark 0 cmsg after an allowed connect().
v2: https://lore.kernel.org/netdev/20261104092252.15712-1-yanxia@xxxxxxxxxxxxxxxxxxxx/T/
tools/testing/selftests/net/cmsg_sender.c | 21 +++++++++++++++++---
tools/testing/selftests/net/cmsg_so_mark.sh | 22 +++++++++++++++++++++
2 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/net/cmsg_sender.c b/tools/testing/selftests/net/cmsg_sender.c
index 67a72b1..035ab54 100644
--- a/tools/testing/selftests/net/cmsg_sender.c
+++ b/tools/testing/selftests/net/cmsg_sender.c
@@ -34,6 +34,7 @@ enum {
ERN_CMSG_RD,
ERN_CMSG_RCV,
ERN_SEND_MORE,
+ ERN_CONNECT,
};
struct option_cmsg_u32 {
@@ -48,6 +49,7 @@ struct options {
unsigned int size;
unsigned int num_pkt;
bool msg_more;
+ bool connected;
struct {
unsigned int mark;
unsigned int dontfrag;
@@ -93,6 +95,7 @@ static void __attribute__((noreturn)) cs_usage(const char *bin)
printf("Usage: %s [opts] <dst host> <dst port / service>\n", bin);
printf("Options:\n"
"\t\t-s Silent send() failures\n"
+ "\t\t-k Connect before sending\n"
"\t\t-S send() size\n"
"\t\t-4/-6 Force IPv4 / IPv6 only\n"
"\t\t-p prot Socket protocol\n"
@@ -121,8 +124,12 @@ static void cs_parse_args(int argc, char *argv[])
{
int o;
- while ((o = getopt(argc, argv, "46sS:p:P:m:M:n:d:tf:F:c:C:l:L:H:Q:")) != -1) {
+ while ((o = getopt(argc, argv,
+ "46ksS:p:P:m:M:n:d:tf:F:c:C:l:L:H:Q:")) != -1) {
switch (o) {
+ case 'k':
+ opt.connected = true;
+ break;
case 's':
opt.silent_send = true;
break;
@@ -521,6 +528,12 @@ int main(int argc, char *argv[])
ca_set_sockopts(fd);
+ if (opt.connected && connect(fd, ai->ai_addr, ai->ai_addrlen)) {
+ fprintf(stderr, "Can't connect socket: %s\n", strerror(errno));
+ err = ERN_CONNECT;
+ goto err_out;
+ }
+
if (clock_gettime(CLOCK_REALTIME, &time_start_real))
error(ERN_GETTIME, errno, "gettime REALTIME");
if (clock_gettime(CLOCK_MONOTONIC, &time_start_mono))
@@ -530,8 +543,10 @@ int main(int argc, char *argv[])
iov[0].iov_len = opt.size;
memset(&msg, 0, sizeof(msg));
- msg.msg_name = ai->ai_addr;
- msg.msg_namelen = ai->ai_addrlen;
+ if (!opt.connected) {
+ msg.msg_name = ai->ai_addr;
+ msg.msg_namelen = ai->ai_addrlen;
+ }
msg.msg_iov = iov;
msg.msg_iovlen = 1;
diff --git a/tools/testing/selftests/net/cmsg_so_mark.sh b/tools/testing/selftests/net/cmsg_so_mark.sh
index 772ad0c..341abfc 100644
--- a/tools/testing/selftests/net/cmsg_so_mark.sh
+++ b/tools/testing/selftests/net/cmsg_so_mark.sh
@@ -68,6 +68,28 @@ for ovr in setsock cmsg both; do
done
done
+# Reject an explicit zero mark while leaving the socket mark allowed.
+ip -netns "$NS" rule add fwmark 0/0xffffffff lookup 300 || exit 1
+ip -6 -netns "$NS" rule add fwmark 0/0xffffffff lookup 300 || exit 1
+
+# Check per-message marks after connect() has populated the route cache.
+for i in 4 6; do
+ [ "$i" = 4 ] && TGT=$TGT4 || TGT=$TGT6
+
+ for p in u i r; do
+ for mark in "$((MARK + 1))" "$MARK" 0; do
+ expected=0
+ if [ "$mark" -eq "$MARK" ] || [ "$mark" -eq 0 ]; then
+ expected=1
+ fi
+
+ ip netns exec "$NS" ./cmsg_sender -"$i" -p "$p" -k \
+ -M "$((MARK + 1))" -m "$mark" -s "$TGT" 1234
+ check_result "$?" "$expected" "IPv$i $p connected mark $mark"
+ done
+ done
+done
+
# Summary
if [ $BAD -ne 0 ]; then
echo "FAIL - $BAD/$TOTAL cases failed"