Files
linux/tools/testing/selftests/net/netlink-dumps.c
Jakub Kicinski 27bc5eaf00 selftests: net: make the dump test less sensitive to mem accounting
Recent changes to make netlink socket memory accounting must
have broken the implicit assumption of the netlink-dump test
that we can fit exactly 64 dumps into the socket. Handle the
failure mode properly, and increase the dump count to 80
to make sure we still run into the error condition if
the default buffer size increases in the future.

Link: https://patch.msgid.link/20250906211351.3192412-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2025-09-08 12:57:05 -07:00

263 lines
5.6 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;
};
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);
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