linux/tools/testing/selftests/net/netlink-dumps.c
Jakub Kicinski 9bb88c6596 selftests: net: test extacks in netlink dumps
Test that extacks in dumps work. The test fills up the receive buffer
to test both the inline dump (as part of sendmsg()) and delayed one
(run during recvmsg()).

Use YNL helpers to parse the messages. We need to add the test to YNL
file to make sure the right include path are used.

Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
Link: https://patch.msgid.link/20241119224432.1713040-2-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2024-11-24 17:00:06 -08:00

240 lines
5.0 KiB
C

// 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;
};
/* 0: no done, 1: done found, 2: extack found, -1: error */
static int nl_get_extack(char *buf, size_t n, struct ext_ack *ea)
{
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 -1;
if (nlh->nlmsg_type != NLMSG_DONE)
continue;
ea->err = -*(int *)NLMSG_DATA(nlh);
if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
return 1;
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 2;
}
return 0;
}
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;
char buf[8192];
int one = 1;
int i, cnt;
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 = 64;
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);
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));
EXPECT_EQ(nl_get_extack(buf, n, &ea), 2);
EXPECT_EQ(ea.attr_offs,
sizeof(struct nlmsghdr) + sizeof(struct ndmsg));
}
}
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