/* $NetBSD: t_inetd.c,v 1.2 2021/09/01 06:12:50 christos Exp $ */ /*- * Copyright (c) 2021 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 __RCSID("$NetBSD: t_inetd.c,v 1.2 2021/09/01 06:12:50 christos Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #define CHECK_ERROR(expr) ATF_REQUIRE_MSG((expr) != -1,\ "%s", strerror(errno)) #define TCP 6 #define UDP 17 static pid_t run(const char *, char *const *); static char *concat(const char *restrict, const char *restrict); static void waitfor(pid_t, const char *); static bool run_udp_client(const char *); static int create_socket(const char *, const char *, int, int, time_t, struct sockaddr_storage *); static bool run_tcp_client(const char *); /* This test should take around 5 to 7 seconds to complete. */ ATF_TC(test_ratelimit); ATF_TC_HEAD(test_ratelimit, tc) { atf_tc_set_md_var(tc, "descr", "Test inetd rate limiting values, " "uses UDP/TCP ports 5432-5439 with localhost."); /* Need to run as root so inetd can set uid */ atf_tc_set_md_var(tc, "require.user", "root"); atf_tc_set_md_var(tc, "require.progs", "inetd"); /* Time out after 10 seconds, just in case */ atf_tc_set_md_var(tc, "timeout", "10"); } ATF_TC_BODY(test_ratelimit, tc) { pid_t proc; /* Copy test server to relative path used in inetd_ratelimit.conf */ atf_utils_copy_file( concat(atf_tc_get_config_var(tc, "srcdir"), "/test_server"), "test_server" ); /* Run inetd in debug mode using specified config file */ proc = run("inetd", (char* const []) { __UNCONST("inetd"), __UNCONST("-d"), concat(atf_tc_get_config_var(tc, "srcdir"), "/inetd_ratelimit.conf"), NULL }); /* Wait for inetd to load services */ sleep(1); /* * TODO test dgram/nowait? Specified in manpage but doesn't seem to * work */ /* dgram/wait ip_max of 3, should receive these 3 responses */ for (int i = 0; i < 3; i++) { ATF_REQUIRE(run_udp_client("5432")); } /* Rate limiting should prevent a response to this request */ ATF_REQUIRE(!run_udp_client("5432")); /* dgram/wait ip_max of 0 */ ATF_REQUIRE(!run_udp_client("5433")); /* dgram/wait service_max of 2 */ ATF_REQUIRE(run_udp_client("5434")); ATF_REQUIRE(run_udp_client("5434")); ATF_REQUIRE(!run_udp_client("5434")); /* dgram/wait service_max of 0 */ ATF_REQUIRE(!run_udp_client("5435")); /* stream/wait service_max of 2 */ ATF_REQUIRE(run_tcp_client("5434")); ATF_REQUIRE(run_tcp_client("5434")); ATF_REQUIRE(!run_tcp_client("5434")); /* stream/wait service_max of 0 */ ATF_REQUIRE(!run_tcp_client("5435")); /* stream/nowait ip_max of 3 */ for (int i = 0; i < 3; i++) { ATF_REQUIRE(run_tcp_client("5436")); } ATF_REQUIRE(!run_tcp_client("5436")); /* stream/nowait ip_max of 0 */ ATF_REQUIRE(!run_tcp_client("5437")); /* dgram/wait service_max of 2 */ ATF_REQUIRE(run_tcp_client("5438")); ATF_REQUIRE(run_tcp_client("5438")); ATF_REQUIRE(!run_tcp_client("5438")); /* dgram/wait service_max of 0 */ ATF_REQUIRE(!run_tcp_client("5439")); /* Exit inetd */ CHECK_ERROR(kill(proc, SIGTERM)); waitfor(proc, "inetd"); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, test_ratelimit); return atf_no_error(); } /* Return true if successfully received message, false if timeout */ static bool run_udp_client(const char *port) { char buffer[] = "test"; struct sockaddr_storage addr; int udp = create_socket("127.0.0.1", port, SOCK_DGRAM, UDP, 1, &addr); CHECK_ERROR(sendto(udp, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr, addr.ss_len)); struct iovec iov = { .iov_base = buffer, .iov_len = sizeof(buffer) }; struct msghdr msg = { .msg_name = &addr, .msg_namelen = addr.ss_len, /* is this correct? */ .msg_iov = &iov, .msg_iovlen = 1 }; ssize_t count = recvmsg(udp, &msg, 0); if (count == -1) { if (errno == EAGAIN) { /* Timed out, return false */ CHECK_ERROR(close(udp)); return false; } else { /* All other errors fatal */ CHECK_ERROR(-1); } } CHECK_ERROR(close(udp)); return true; } /* Run localhost tcp echo, return true if successful, false if timeout/disconnect */ static bool run_tcp_client(const char *port) { struct sockaddr_storage remote; ssize_t count; int tcp; char buffer[] = "test"; tcp = create_socket("127.0.0.1", port, SOCK_STREAM, TCP, 1, &remote); CHECK_ERROR(connect(tcp, (const struct sockaddr *)&remote, remote.ss_len)); CHECK_ERROR(send(tcp, buffer, sizeof(buffer), 0)); count = recv(tcp, buffer, sizeof(buffer), 0); if (count == -1) { /* * Connection reset by peer indicates the connection was * dropped. EAGAIN indicates the timeout expired. Any other * error is unexpected for this client program test. */ if(errno == ECONNRESET || errno == EAGAIN) { return false; } else { CHECK_ERROR(-1); return false; } } if (count == 0) { /* socket was shutdown by inetd, no more data available */ return false; } return true; } /* * Create a socket with the characteristics inferred by the args, return parsed * socket address in dst. */ static int create_socket(const char *address, const char *port, int socktype, int proto, time_t timeout_sec, struct sockaddr_storage *dst) { struct addrinfo hints = { .ai_flags = AI_NUMERICHOST, .ai_socktype = socktype, .ai_protocol = proto }; struct addrinfo * res; int error, fd; ATF_REQUIRE_EQ_MSG(error = getaddrinfo(address, port, &hints, &res), 0, "%s", gai_strerror(error)); /* Make sure there is only one possible bind address */ ATF_REQUIRE_MSG(res->ai_next == NULL, "Ambiguous create_socket args"); CHECK_ERROR(fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)); struct timeval timeout = { timeout_sec, 0 }; CHECK_ERROR(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))); memcpy(dst, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); return fd; } /* Run program with args */ static pid_t run(const char *prog, char *const *args) { pid_t proc; extern char **environ; ATF_REQUIRE_EQ(posix_spawnp(&proc, prog, NULL, NULL, args, environ), 0); return proc; } /* Wait for a process to exit, check return value */ static void waitfor(pid_t pid, const char *taskname) { int status; int rpid = waitpid(pid, &status, WALLSIG); ATF_REQUIRE_MSG(rpid == pid, "wait %d != %d %s", rpid, pid, strerror(errno)); ATF_REQUIRE_EQ_MSG(WEXITSTATUS(status), EXIT_SUCCESS, "%s failed with " "exit status %d", taskname, WEXITSTATUS(status)); } /* Concatenate two const strings, do not free arguments */ static char * concat(const char *restrict left, const char *restrict right) { char *res; if (asprintf(&res, "%s%s", left, right) == -1) return NULL; return res; }