summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorJhonata Poma-Hansen <jhonata.poma@gmail.com>2026-10-27 15:04:02 +0100
committerJakub Kicinski <kuba@kernel.org>2026-10-29 09:15:16 -0700
commite615d0a8b20161fd470ca2748001105bcd3c44b1 (patch)
tree917c3f1132d4d0b288c6cbbed359a3a4cdf9c099 /tools/testing
parentb45d37740cfb4bdfccef3fbd9fd970fd3224438f (diff)
selftests: net: netlink-dumps: start a dump with a notification queued
Add a test which starts a link dump on a socket with SO_RCVBUF 2048 while a link notification is still queued, after a large read made the kernel attempt large dump skbs. It reads like libnl does, peeking before each read, and expects the dump to complete with a zero status and at least one link. The notifications come from bringing lo up and down, so the test runs in its own netns and is skipped without the privileges to create one. The test fails without the previous patch. Signed-off-by: Jhonata Poma-Hansen <jhonata.poma@gmail.com> Link: https://patch.msgid.link/20261027-netlink-dump-livelock-v2-2-c4bb05352a33@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/net/netlink-dumps.c102
1 files changed, 102 insertions, 0 deletions
diff --git a/tools/testing/selftests/net/netlink-dumps.c b/tools/testing/selftests/net/netlink-dumps.c
index 51129c564d0a..4a94a25167e2 100644
--- a/tools/testing/selftests/net/netlink-dumps.c
+++ b/tools/testing/selftests/net/netlink-dumps.c
@@ -2,9 +2,12 @@
#define _GNU_SOURCE
+#include <errno.h>
#include <fcntl.h>
+#include <sched.h>
#include <stdio.h>
#include <string.h>
+#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
@@ -260,4 +263,103 @@ TEST(close_with_ref)
usleep(100 * 1000);
}
+static int lo_set_up(int sock, bool up)
+{
+ struct ifreq ifr = {};
+
+ strcpy(ifr.ifr_name, "lo");
+ if (ioctl(sock, SIOCGIFFLAGS, &ifr))
+ return -1;
+ if (up)
+ ifr.ifr_flags |= IFF_UP;
+ else
+ ifr.ifr_flags &= ~IFF_UP;
+ return ioctl(sock, SIOCSIFFLAGS, &ifr);
+}
+
+static const struct {
+ struct nlmsghdr nlhdr;
+ struct ifinfomsg ifm;
+} dump_links = {
+ .nlhdr = {
+ .nlmsg_len = sizeof(dump_links),
+ .nlmsg_type = RTM_GETLINK,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP,
+ .nlmsg_seq = 1,
+ },
+ .ifm = {
+ .ifi_family = AF_UNSPEC,
+ },
+};
+
+// A dump started while a notification is still queued must make progress
+// for a reader which peeks before reading, like libnl does
+TEST(dump_with_queued_ntf)
+{
+ struct sockaddr_nl addr = {
+ .nl_family = AF_NETLINK,
+ .nl_groups = RTMGRP_LINK,
+ };
+ int netlink_sock, inet_sock, i, ret, links = 0;
+ int rcvbuf = 2048;
+ bool done = false;
+ char buf[32768];
+ ssize_t n;
+
+ ret = unshare(CLONE_NEWNET);
+ if (ret && errno == EPERM)
+ SKIP(return, "Needs CAP_SYS_ADMIN for a netns");
+ ASSERT_EQ(ret, 0);
+
+ inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
+ ASSERT_GE(inet_sock, 0);
+ netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ ASSERT_GE(netlink_sock, 0);
+ ASSERT_EQ(setsockopt(netlink_sock, SOL_SOCKET, SO_RCVBUF,
+ &rcvbuf, sizeof(rcvbuf)), 0);
+ ASSERT_EQ(bind(netlink_sock, (struct sockaddr *)&addr,
+ sizeof(addr)), 0);
+
+ // A large read makes the kernel attempt large dump skbs
+ ASSERT_EQ(lo_set_up(inet_sock, true), 0);
+ n = recv(netlink_sock, buf, sizeof(buf), 0);
+ ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
+
+ // Leave one notification queued, then start the dump
+ ASSERT_EQ(lo_set_up(inet_sock, false), 0);
+ n = send(netlink_sock, &dump_links, sizeof(dump_links), 0);
+ ASSERT_EQ(n, sizeof(dump_links));
+
+ for (i = 0; i < 100 && !done; i++) {
+ struct nlmsghdr *nlh;
+
+ n = recv(netlink_sock, buf, sizeof(buf),
+ MSG_PEEK | MSG_TRUNC | MSG_DONTWAIT);
+ if (n < 0)
+ continue;
+ n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
+ if (n < 0)
+ continue;
+
+ for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, n);
+ nlh = NLMSG_NEXT(nlh, n)) {
+ if (nlh->nlmsg_seq != dump_links.nlhdr.nlmsg_seq)
+ continue;
+ if (nlh->nlmsg_type == NLMSG_ERROR)
+ EXPECT_EQ(*(int *)NLMSG_DATA(nlh), 0);
+ if (nlh->nlmsg_type == RTM_NEWLINK)
+ links++;
+ if (nlh->nlmsg_type == NLMSG_DONE) {
+ EXPECT_EQ(*(int *)NLMSG_DATA(nlh), 0);
+ done = true;
+ }
+ }
+ }
+ EXPECT_TRUE(done);
+ EXPECT_GE(links, 1);
+
+ close(netlink_sock);
+ close(inet_sock);
+}
+
TEST_HARNESS_MAIN