| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- // 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
|