git: 0ad011ececb9 - main - tests/netlink: add netlink socket buffer test

From: Gleb Smirnoff <glebius_at_FreeBSD.org>
Date: Tue, 02 Jan 2024 21:06:56 UTC
The branch main has been updated by glebius:

URL: https://cgit.FreeBSD.org/src/commit/?id=0ad011ececb978e22a9bff2acf76633b094f1ff6

commit 0ad011ececb978e22a9bff2acf76633b094f1ff6
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2024-01-02 21:03:49 +0000
Commit:     Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2024-01-02 21:03:49 +0000

    tests/netlink: add netlink socket buffer test
    
    With upcoming protocol specific socket buffer for Netlink we need some
    additional tests that cover basic socket operations, w/o much of actual
    Netlink knowledge.  Following tests are performed:
    
    1) Overflow.  If an application keeps sending messages to the kernel,
    but doesn't read out the replies, then first the receive buffer shall
    fill and after that further messages from applications will be queued
    on the send buffer until it is filled.  After that socket operations
    should block.  However, reading from the receive buffer some data should
    wake up the taskqueue and the send buffer should start draining again.
    
    2) Peek & trunc.  Check that socket correctly reports amount of readable
    data with MSG_PEEK & MSG_TRUNC.  This is typical pattern of Netlink apps.
    
    3) Sizes. Check that zero size read doesn't affect the socket, undersize
    read will return one truncated message and the message is removed from
    the buffer.  Check that large buffer will be filled in one read, without
    any boundaries imposed by internal representation of the buffer.  Check
    that any meaningful read is amended with control data if requested so.
    
    Reviewed by:            melifaro
    Differential Revision:  https://reviews.freebsd.org/D42525
---
 tests/sys/netlink/Makefile         |   3 +-
 tests/sys/netlink/netlink_socket.c | 266 +++++++++++++++++++++++++++++++++++++
 2 files changed, 268 insertions(+), 1 deletion(-)

diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile
index 7a80b4de21d0..c52149674ceb 100644
--- a/tests/sys/netlink/Makefile
+++ b/tests/sys/netlink/Makefile
@@ -4,7 +4,8 @@ WARNS?=		1
 
 TESTSDIR=       ${TESTSBASE}/sys/netlink
 
-ATF_TESTS_C +=	test_snl test_snl_generic
+ATF_TESTS_C+=	netlink_socket
+ATF_TESTS_C+=	test_snl test_snl_generic
 ATF_TESTS_PYTEST +=	test_nl_core.py
 ATF_TESTS_PYTEST +=	test_rtnl_iface.py
 ATF_TESTS_PYTEST +=	test_rtnl_ifaddr.py
diff --git a/tests/sys/netlink/netlink_socket.c b/tests/sys/netlink/netlink_socket.c
new file mode 100644
index 000000000000..51a3824a31f2
--- /dev/null
+++ b/tests/sys/netlink/netlink_socket.c
@@ -0,0 +1,266 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023 Gleb Smirnoff <glebius@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/module.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_route.h>
+
+#include <atf-c.h>
+
+static struct itimerval itv = {
+	.it_interval = { 0, 0 },
+	.it_value = { 1, 0 },	/* one second */
+};
+static sig_atomic_t timer_done = 0;
+static void
+sigalarm(int sig __unused)
+{
+
+	timer_done = 1;
+}
+
+static struct sigaction sigact = {
+	.sa_handler = sigalarm,
+};
+
+static struct nlmsghdr hdr = (struct nlmsghdr) {
+	.nlmsg_type = RTM_GETLINK,
+	.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+	.nlmsg_len = sizeof(struct nlmsghdr),
+};
+
+#define	BUFLEN	1000
+
+static int
+fullsocket(void)
+{
+	char buf[BUFLEN];
+	socklen_t slen = sizeof(int);
+	int fd, sendspace, recvspace, sendavail, recvavail, rsize;
+	u_int cnt = 0;
+
+	ATF_REQUIRE((fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) != -1);
+	ATF_REQUIRE(getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendspace,
+	    &slen) == 0);
+	ATF_REQUIRE(getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &recvspace,
+	    &slen) == 0);
+
+	/* Check the expected size of reply on a single RTM_GETLINK. */
+	ATF_REQUIRE(send(fd, &hdr, sizeof(hdr), 0) == sizeof(hdr));
+	ATF_REQUIRE(recv(fd, buf, sizeof(hdr), MSG_WAITALL | MSG_PEEK) ==
+	    sizeof(hdr));
+	ATF_REQUIRE(ioctl(fd, FIONREAD, &rsize) != -1);
+
+
+	/*
+	 * Flood the socket with requests, without reading out the replies.
+	 * While we are flooding, the kernel tries to process the requests.
+	 * Kernel takes off requests from the send buffer and puts replies
+	 * on receive buffer.  Once the receive buffer is full it stops working
+	 * on queue in the send buffer.  At this point we must get a solid
+	 * failure.  However, if we flood faster than kernel taskqueue runs,
+	 * we may get intermittent failures.
+	 */
+	do {
+		ssize_t rv;
+
+		rv = send(fd, &hdr, sizeof(hdr), MSG_DONTWAIT);
+		if (__predict_true(rv == sizeof(hdr)))
+			cnt++;
+		else {
+			ATF_REQUIRE(errno == EAGAIN);
+			ATF_REQUIRE(sizeof(hdr) * cnt > sendspace);
+		}
+		ATF_REQUIRE(ioctl(fd, FIONREAD, &recvavail) != -1);
+		ATF_REQUIRE(ioctl(fd, FIONWRITE, &sendavail) != -1);
+	} while (recvavail <= recvspace - rsize ||
+		 sendavail <= sendspace - sizeof(hdr));
+
+	return (fd);
+}
+
+ATF_TC_WITHOUT_HEAD(overflow);
+ATF_TC_BODY(overflow, tc)
+{
+	char buf[BUFLEN];
+	int fd;
+
+	fd = fullsocket();
+
+	/* Both buffers full: block. */
+	timer_done = 0;
+	ATF_REQUIRE(sigaction(SIGALRM, &sigact, NULL) == 0);
+	ATF_REQUIRE(setitimer(ITIMER_REAL, &itv, NULL) == 0);
+	ATF_REQUIRE(send(fd, &hdr, sizeof(hdr), 0) == -1);
+	ATF_REQUIRE(errno == EINTR);
+	ATF_REQUIRE(timer_done == 1);
+
+	/*
+	 * Now, reading something from the receive buffer should wake up the
+	 * taskqueue and send buffer should start getting drained.
+	 */
+	ATF_REQUIRE(recv(fd, buf, BUFLEN, 0) > sizeof(hdr));
+	timer_done = 0;
+	ATF_REQUIRE(setitimer(ITIMER_REAL, &itv, NULL) == 0);
+	ATF_REQUIRE(send(fd, &hdr, sizeof(hdr), 0) == sizeof(hdr));
+	ATF_REQUIRE(timer_done == 0);
+}
+
+ATF_TC_WITHOUT_HEAD(peek);
+ATF_TC_BODY(peek, tc)
+{
+	char *buf;
+	ssize_t ss, ss1;
+	int fd;
+
+	ATF_REQUIRE((fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) != -1);
+
+	ATF_REQUIRE(send(fd, &hdr, sizeof(hdr), 0) == sizeof(hdr));
+	ss = recv(fd, buf, 0, MSG_WAITALL | MSG_PEEK | MSG_TRUNC);
+	ATF_REQUIRE((buf = malloc(ss)) != NULL);
+	ATF_REQUIRE(recv(fd, buf, ss, MSG_WAITALL) == ss);
+}
+
+struct nl_control {
+	struct nlattr nla;
+	uint32_t val;
+};
+
+static void
+cmsg_check(struct msghdr *msg)
+{
+	static pid_t pid = 0;
+	struct cmsghdr *cmsg;
+	struct nl_control *nlc;
+
+	ATF_REQUIRE((cmsg = CMSG_FIRSTHDR(msg)) != NULL);
+	ATF_REQUIRE(cmsg->cmsg_level == SOL_NETLINK);
+	ATF_REQUIRE(cmsg->cmsg_type == NETLINK_MSG_INFO);
+	nlc = (struct nl_control *)CMSG_DATA(cmsg);
+	ATF_REQUIRE(nlc[0].nla.nla_type == NLMSGINFO_ATTR_PROCESS_ID);
+	if (pid == 0)
+		pid = getpid();
+	ATF_REQUIRE(nlc[0].val == pid);
+	ATF_REQUIRE(nlc[1].nla.nla_type == NLMSGINFO_ATTR_PORT_ID);
+	/* XXX need another test to test port id */
+	ATF_REQUIRE(nlc[1].val == 0);
+	ATF_REQUIRE(CMSG_NXTHDR(msg, cmsg) == NULL);
+	ATF_REQUIRE((msg->msg_flags & MSG_CTRUNC) == 0);
+}
+
+ATF_TC_WITHOUT_HEAD(sizes);
+ATF_TC_BODY(sizes, tc)
+{
+#define	NLMSG_LARGE 2048		/* XXX: match kernel nl_buf */
+	char buf[NLMSG_LARGE * 10];
+	char cbuf[CMSG_SPACE(sizeof(struct nl_control) * 2)];
+	struct iovec iov;
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = cbuf,
+		.msg_controllen = sizeof(cbuf),
+	};
+	ssize_t ss;
+	int fd, size, rsize;
+
+	fd = fullsocket();
+
+	/*
+	 * Set NETLINK_MSG_INFO, so that later cmsg_check will check that any
+	 * read is accompanied with control data.
+	 */
+	ATF_REQUIRE(setsockopt(fd, SOL_NETLINK, NETLINK_MSG_INFO,
+	    &(int){1}, sizeof(int)) == 0);
+
+	iov = (struct iovec ){
+		.iov_base = &hdr,
+		.iov_len = sizeof(hdr),
+	};
+	/* Obtain size of the first message in the socket. */
+	ss = recvmsg(fd, &msg, MSG_WAITALL | MSG_PEEK | MSG_TRUNC);
+	ATF_REQUIRE(ss == hdr.nlmsg_len);
+	/* And overall amount of data in the socket. */
+	ATF_REQUIRE(ioctl(fd, FIONREAD, &rsize) != -1);
+	cmsg_check(&msg);
+
+	/* Zero-sized read should not affect state of the socket buffer. */
+	ATF_REQUIRE(recv(fd, buf, 0, 0) == 0);
+	ATF_REQUIRE(ioctl(fd, FIONREAD, &size) != -1);
+	ATF_REQUIRE(size == rsize);
+
+	/*
+	 * Undersized read should lose a message.  This isn't exactly
+	 * pronounced in the Netlink RFC, but it always says that Netlink
+	 * socket is an analog of the BSD routing socket, and this is how
+	 * a route(4) socket deals with undersized read.
+	 */
+	iov = (struct iovec ){
+		.iov_base = buf,
+		.iov_len = sizeof(hdr),
+	};
+	ATF_REQUIRE(recvmsg(fd, &msg, 0) == sizeof(hdr));
+	ATF_REQUIRE(msg.msg_flags & MSG_TRUNC);
+	ATF_REQUIRE(hdr.nlmsg_len > sizeof(hdr));
+	size = rsize - hdr.nlmsg_len;
+	ATF_REQUIRE(ioctl(fd, FIONREAD, &rsize) != -1);
+	ATF_REQUIRE(size == rsize);
+	cmsg_check(&msg);
+
+	/*
+	 * Large read should span several nl_bufs, seeing no boundaries.
+	 */
+	iov = (struct iovec ){
+		.iov_base = buf,
+		.iov_len = sizeof(buf) < rsize ? sizeof(buf) : rsize,
+	};
+	ss = recvmsg(fd, &msg, 0);
+	ATF_REQUIRE(ss > NLMSG_LARGE * 9 || ss == rsize);
+	cmsg_check(&msg);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	if (modfind("netlink") == -1)
+		atf_tc_skip("netlink module not loaded");
+
+	ATF_TP_ADD_TC(tp, overflow);
+	ATF_TP_ADD_TC(tp, peek);
+	ATF_TP_ADD_TC(tp, sizes);
+
+	return (atf_no_error());
+}