From nobody Tue Jan 02 21:06:56 2024 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4T4QQT36gHz56KSs; Tue, 2 Jan 2024 21:06:57 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4T4QQT02Dwz4bxP; Tue, 2 Jan 2024 21:06:57 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1704229617; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=01fTYb93E6X1gNn835hLjjeFDpojfoWljKCx/IPv7z8=; b=p5/BKOgiUl9rxpfvazV+9avJy8cpoIIkT9Xy8dJ+HgTjhm0PRzlgY6lyqpMp5iTrx5k4dA nptJBCRoJySwIk5M/iYqEC6F9lbaORhENbYZQ2hlZtMhAVrafYbPgGhiIa9mhpOJkLGms+ RYJ68lpISm/0S+HiQZ7eBjhGfAszKKv0MXpaALUwdi8ZzEPGKzcumc9M33NI3VECWdVyHW 9MWpuzyMHJpa53v/TUieXMWI7L8PZ5nlVggTqg5QBYDw3rwY0sz2Ft08RsfvsfiRs02ajt iT1BROyYrNeyZYyU+edPo9yqifmr8ss1M5/KtqhYohQRQREgLHayzaAnHsMhug== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1704229617; a=rsa-sha256; cv=none; b=D3C055/2qX1+JafO5H/OKTc2wuqponOaaz194Ld8gRFMOGby0nL6TN3kR/m+bNjZZRxLEv 2UFsSndLNe/OE81UMt4lDmIStL1UDLGa9QXE685xbVSudGLNxSzCU9UPS1c0HYqpP2w1ON h9wtrB+x0S8P3i1FDYwG2GMxZAgkzz066sLvJ5JxWQZvPNzZiNH8kMrg6qqxyobmiV7/c6 bIDI8Pb6s98zUTtau6EdTssN8ybI8S7nyHRA2lpSw7LsbdXWW2QmVP4wYkgc19T2OPvrBl k2bARaRxBpkKCesDEOPafn5SaboBZesujRDv9HOhV/2NVVKaCU4iFpahSCzr5g== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1704229617; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=01fTYb93E6X1gNn835hLjjeFDpojfoWljKCx/IPv7z8=; b=rkqAMCVzvaneqzERjrv2e1fKdHSa36JxMiAB0rhQt3gnK9VuBtEBbl7TmO7si9Xalnhw3Z cTL5+TRJV4kiKHpbGHeRPBK9OUumhnzRTw2zp3e6ycmf69hICrX6JKdsNxa2Fv0LUg41yU vrJapuFC6qGM+E/uTXwg1JV9Xpv+4K/ueE/FZRVtgcqSGAtaX/v4M21qPGngHfdE7D5VQQ avlEr6J5M6hAJaUVK60II+nYBdmCkgH2D7hLvvJPdgTtQcc9WWf18NVBcs6ptHpF/Op7R5 K98GmivqUPFFAU7qz2tKZmP2ThtxqzkNg0hyrj+3yJ9OtXN212Cqcbb9rnNaKQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4T4QQS6BTKz1GKZ; Tue, 2 Jan 2024 21:06:56 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 402L6uok055991; Tue, 2 Jan 2024 21:06:56 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 402L6uw4055988; Tue, 2 Jan 2024 21:06:56 GMT (envelope-from git) Date: Tue, 2 Jan 2024 21:06:56 GMT Message-Id: <202401022106.402L6uw4055988@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Gleb Smirnoff Subject: git: 0ad011ececb9 - main - tests/netlink: add netlink socket buffer test List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: glebius X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 0ad011ececb978e22a9bff2acf76633b094f1ff6 Auto-Submitted: auto-generated The branch main has been updated by glebius: URL: https://cgit.FreeBSD.org/src/commit/?id=0ad011ececb978e22a9bff2acf76633b094f1ff6 commit 0ad011ececb978e22a9bff2acf76633b094f1ff6 Author: Gleb Smirnoff AuthorDate: 2024-01-02 21:03:49 +0000 Commit: Gleb Smirnoff 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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()); +}