diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile --- a/tests/sys/netlink/Makefile +++ b/tests/sys/netlink/Makefile @@ -4,7 +4,8 @@ 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 --- /dev/null +++ b/tests/sys/netlink/netlink_socket.c @@ -0,0 +1,161 @@ +/*- + * 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, +}; + +ATF_TC_WITHOUT_HEAD(overflow); +ATF_TC_BODY(overflow, tc) +{ +#define BUFLEN 1000 + struct nlmsghdr hdr; + int fd, sendspace, recvspace, sendavail, recvavail, rsize; + u_int cnt = 0; + socklen_t slen = sizeof(int); + char buf[BUFLEN]; + + 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); + + hdr = (struct nlmsghdr) { + .nlmsg_type = RTM_GETLINK, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_len = sizeof(struct nlmsghdr), + }; + + /* 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)); + /* 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) +{ +#define BUFLEN 1000 + struct nlmsghdr hdr; + char buf[BUFLEN]; + ssize_t ss; + int fd; + + ATF_REQUIRE((fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) != -1); + + hdr = (struct nlmsghdr) { + .nlmsg_type = RTM_GETLINK, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_len = sizeof(struct nlmsghdr), + }; + + ATF_REQUIRE(send(fd, &hdr, sizeof(hdr), 0) == sizeof(hdr)); + ss = -recv(fd, buf, 0, MSG_WAITALL | MSG_PEEK | MSG_TRUNC); + ATF_REQUIRE(recv(fd, buf, ss, MSG_WAITALL) == ss); +} + +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); + + return (atf_no_error()); +}