summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/net/netlink-dumps.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/net/netlink-dumps.c')
-rw-r--r--tools/testing/selftests/net/netlink-dumps.c263
1 files changed, 263 insertions, 0 deletions
diff --git a/tools/testing/selftests/net/netlink-dumps.c b/tools/testing/selftests/net/netlink-dumps.c
new file mode 100644
index 000000000000..51129c564d0a
--- /dev/null
+++ b/tools/testing/selftests/net/netlink-dumps.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <linux/genetlink.h>
+#include <linux/neighbour.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/mqueue.h>
+#include <linux/rtnetlink.h>
+
+#include "kselftest_harness.h"
+
+#include <ynl.h>
+
+struct ext_ack {
+ int err;
+
+ __u32 attr_offs;
+ __u32 miss_type;
+ __u32 miss_nest;
+ const char *str;
+};
+
+enum get_ea_ret {
+ ERROR = -1,
+ NO_CTRL = 0,
+ FOUND_DONE,
+ FOUND_ERR,
+ FOUND_EXTACK,
+};
+
+static enum get_ea_ret
+nl_get_extack(char *buf, size_t n, struct ext_ack *ea)
+{
+ enum get_ea_ret ret = NO_CTRL;
+ const struct nlmsghdr *nlh;
+ const struct nlattr *attr;
+ ssize_t rem;
+
+ for (rem = n; rem > 0; NLMSG_NEXT(nlh, rem)) {
+ nlh = (struct nlmsghdr *)&buf[n - rem];
+ if (!NLMSG_OK(nlh, rem))
+ return ERROR;
+
+ if (nlh->nlmsg_type == NLMSG_ERROR)
+ ret = FOUND_ERR;
+ else if (nlh->nlmsg_type == NLMSG_DONE)
+ ret = FOUND_DONE;
+ else
+ continue;
+
+ ea->err = -*(int *)NLMSG_DATA(nlh);
+
+ if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+ return ret;
+
+ ynl_attr_for_each(attr, nlh, sizeof(int)) {
+ switch (ynl_attr_type(attr)) {
+ case NLMSGERR_ATTR_OFFS:
+ ea->attr_offs = ynl_attr_get_u32(attr);
+ break;
+ case NLMSGERR_ATTR_MISS_TYPE:
+ ea->miss_type = ynl_attr_get_u32(attr);
+ break;
+ case NLMSGERR_ATTR_MISS_NEST:
+ ea->miss_nest = ynl_attr_get_u32(attr);
+ break;
+ case NLMSGERR_ATTR_MSG:
+ ea->str = ynl_attr_get_str(attr);
+ break;
+ }
+ }
+
+ return FOUND_EXTACK;
+ }
+
+ return ret;
+}
+
+static const struct {
+ struct nlmsghdr nlhdr;
+ struct ndmsg ndm;
+ struct nlattr ahdr;
+ __u32 val;
+} dump_neigh_bad = {
+ .nlhdr = {
+ .nlmsg_len = sizeof(dump_neigh_bad),
+ .nlmsg_type = RTM_GETNEIGH,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP,
+ .nlmsg_seq = 1,
+ },
+ .ndm = {
+ .ndm_family = 123,
+ },
+ .ahdr = {
+ .nla_len = 4 + 4,
+ .nla_type = NDA_FLAGS_EXT,
+ },
+ .val = -1, // should fail MASK validation
+};
+
+TEST(dump_extack)
+{
+ int netlink_sock;
+ int i, cnt, ret;
+ char buf[8192];
+ int one = 1;
+ ssize_t n;
+
+ netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ ASSERT_GE(netlink_sock, 0);
+
+ n = setsockopt(netlink_sock, SOL_NETLINK, NETLINK_CAP_ACK,
+ &one, sizeof(one));
+ ASSERT_EQ(n, 0);
+ n = setsockopt(netlink_sock, SOL_NETLINK, NETLINK_EXT_ACK,
+ &one, sizeof(one));
+ ASSERT_EQ(n, 0);
+ n = setsockopt(netlink_sock, SOL_NETLINK, NETLINK_GET_STRICT_CHK,
+ &one, sizeof(one));
+ ASSERT_EQ(n, 0);
+
+ /* Dump so many times we fill up the buffer */
+ cnt = 80;
+ for (i = 0; i < cnt; i++) {
+ n = send(netlink_sock, &dump_neigh_bad,
+ sizeof(dump_neigh_bad), 0);
+ ASSERT_EQ(n, sizeof(dump_neigh_bad));
+ }
+
+ /* Read out the ENOBUFS */
+ n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
+ EXPECT_EQ(n, -1);
+ EXPECT_EQ(errno, ENOBUFS);
+
+ ret = NO_CTRL;
+ for (i = 0; i < cnt; i++) {
+ struct ext_ack ea = {};
+
+ n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
+ if (n < 0) {
+ ASSERT_GE(i, 10);
+ break;
+ }
+ ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
+
+ ret = nl_get_extack(buf, n, &ea);
+ /* Once we fill the buffer we'll see one ENOBUFS followed
+ * by a number of EBUSYs. Then the last recv() will finally
+ * trigger and complete the dump.
+ */
+ if (ret == FOUND_ERR && (ea.err == ENOBUFS || ea.err == EBUSY))
+ continue;
+ EXPECT_EQ(ret, FOUND_EXTACK);
+ EXPECT_EQ(ea.err, EINVAL);
+ EXPECT_EQ(ea.attr_offs,
+ sizeof(struct nlmsghdr) + sizeof(struct ndmsg));
+ }
+ /* Make sure last message was a full DONE+extack */
+ EXPECT_EQ(ret, FOUND_EXTACK);
+}
+
+static const struct {
+ struct nlmsghdr nlhdr;
+ struct genlmsghdr genlhdr;
+ struct nlattr ahdr;
+ __u16 val;
+ __u16 pad;
+} dump_policies = {
+ .nlhdr = {
+ .nlmsg_len = sizeof(dump_policies),
+ .nlmsg_type = GENL_ID_CTRL,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP,
+ .nlmsg_seq = 1,
+ },
+ .genlhdr = {
+ .cmd = CTRL_CMD_GETPOLICY,
+ .version = 2,
+ },
+ .ahdr = {
+ .nla_len = 6,
+ .nla_type = CTRL_ATTR_FAMILY_ID,
+ },
+ .val = GENL_ID_CTRL,
+ .pad = 0,
+};
+
+// Sanity check for the test itself, make sure the dump doesn't fit in one msg
+TEST(test_sanity)
+{
+ int netlink_sock;
+ char buf[8192];
+ ssize_t n;
+
+ netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ ASSERT_GE(netlink_sock, 0);
+
+ n = send(netlink_sock, &dump_policies, sizeof(dump_policies), 0);
+ ASSERT_EQ(n, sizeof(dump_policies));
+
+ n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
+ ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
+
+ n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
+ ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
+
+ close(netlink_sock);
+}
+
+TEST(close_in_progress)
+{
+ int netlink_sock;
+ ssize_t n;
+
+ netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ ASSERT_GE(netlink_sock, 0);
+
+ n = send(netlink_sock, &dump_policies, sizeof(dump_policies), 0);
+ ASSERT_EQ(n, sizeof(dump_policies));
+
+ close(netlink_sock);
+}
+
+TEST(close_with_ref)
+{
+ char cookie[NOTIFY_COOKIE_LEN] = {};
+ int netlink_sock, mq_fd;
+ struct sigevent sigev;
+ ssize_t n;
+
+ netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ ASSERT_GE(netlink_sock, 0);
+
+ n = send(netlink_sock, &dump_policies, sizeof(dump_policies), 0);
+ ASSERT_EQ(n, sizeof(dump_policies));
+
+ mq_fd = syscall(__NR_mq_open, "sed", O_CREAT | O_WRONLY, 0600, 0);
+ ASSERT_GE(mq_fd, 0);
+
+ memset(&sigev, 0, sizeof(sigev));
+ sigev.sigev_notify = SIGEV_THREAD;
+ sigev.sigev_value.sival_ptr = cookie;
+ sigev.sigev_signo = netlink_sock;
+
+ syscall(__NR_mq_notify, mq_fd, &sigev);
+
+ close(netlink_sock);
+
+ // give mqueue time to fire
+ usleep(100 * 1000);
+}
+
+TEST_HARNESS_MAIN