1 /* $NetBSD: t_inetd.c,v 1.2 2021/09/01 06:12:50 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2021 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by James Browning, Gabe Coffland, Alex Gavin, and Solomon Ritzow. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __RCSID("$NetBSD: t_inetd.c,v 1.2 2021/09/01 06:12:50 christos Exp $"); 34 35 #include <atf-c.h> 36 #include <spawn.h> 37 #include <errno.h> 38 #include <stdlib.h> 39 #include <signal.h> 40 #include <stdio.h> 41 #include <string.h> 42 #include <err.h> 43 #include <sys/wait.h> 44 #include <sys/time.h> 45 #include <sys/socket.h> 46 #include <netdb.h> 47 48 #define CHECK_ERROR(expr) ATF_REQUIRE_MSG((expr) != -1,\ 49 "%s", strerror(errno)) 50 51 #define TCP 6 52 #define UDP 17 53 54 static pid_t run(const char *, char *const *); 55 static char *concat(const char *restrict, const char *restrict); 56 static void waitfor(pid_t, const char *); 57 static bool run_udp_client(const char *); 58 static int create_socket(const char *, const char *, int, int, time_t, struct sockaddr_storage *); 59 static bool run_tcp_client(const char *); 60 61 /* This test should take around 5 to 7 seconds to complete. */ 62 ATF_TC(test_ratelimit); 63 64 ATF_TC_HEAD(test_ratelimit, tc) 65 { 66 atf_tc_set_md_var(tc, "descr", "Test inetd rate limiting values, " 67 "uses UDP/TCP ports 5432-5439 with localhost."); 68 /* Need to run as root so inetd can set uid */ 69 atf_tc_set_md_var(tc, "require.user", "root"); 70 atf_tc_set_md_var(tc, "require.progs", "inetd"); 71 /* Time out after 10 seconds, just in case */ 72 atf_tc_set_md_var(tc, "timeout", "10"); 73 } 74 75 ATF_TC_BODY(test_ratelimit, tc) 76 { 77 pid_t proc; 78 79 /* Copy test server to relative path used in inetd_ratelimit.conf */ 80 atf_utils_copy_file( 81 concat(atf_tc_get_config_var(tc, "srcdir"), "/test_server"), 82 "test_server" 83 ); 84 85 /* Run inetd in debug mode using specified config file */ 86 proc = run("inetd", (char* const []) { 87 __UNCONST("inetd"), __UNCONST("-d"), 88 concat(atf_tc_get_config_var(tc, "srcdir"), 89 "/inetd_ratelimit.conf"), 90 NULL 91 }); 92 93 /* Wait for inetd to load services */ 94 sleep(1); 95 96 /* 97 * TODO test dgram/nowait? Specified in manpage but doesn't seem to 98 * work 99 */ 100 101 /* dgram/wait ip_max of 3, should receive these 3 responses */ 102 for (int i = 0; i < 3; i++) { 103 ATF_REQUIRE(run_udp_client("5432")); 104 } 105 106 /* Rate limiting should prevent a response to this request */ 107 ATF_REQUIRE(!run_udp_client("5432")); 108 109 /* dgram/wait ip_max of 0 */ 110 ATF_REQUIRE(!run_udp_client("5433")); 111 112 /* dgram/wait service_max of 2 */ 113 ATF_REQUIRE(run_udp_client("5434")); 114 ATF_REQUIRE(run_udp_client("5434")); 115 ATF_REQUIRE(!run_udp_client("5434")); 116 117 /* dgram/wait service_max of 0 */ 118 ATF_REQUIRE(!run_udp_client("5435")); 119 120 /* stream/wait service_max of 2 */ 121 ATF_REQUIRE(run_tcp_client("5434")); 122 ATF_REQUIRE(run_tcp_client("5434")); 123 ATF_REQUIRE(!run_tcp_client("5434")); 124 125 /* stream/wait service_max of 0 */ 126 ATF_REQUIRE(!run_tcp_client("5435")); 127 128 /* stream/nowait ip_max of 3 */ 129 for (int i = 0; i < 3; i++) { 130 ATF_REQUIRE(run_tcp_client("5436")); 131 } 132 ATF_REQUIRE(!run_tcp_client("5436")); 133 134 /* stream/nowait ip_max of 0 */ 135 ATF_REQUIRE(!run_tcp_client("5437")); 136 137 /* dgram/wait service_max of 2 */ 138 ATF_REQUIRE(run_tcp_client("5438")); 139 ATF_REQUIRE(run_tcp_client("5438")); 140 ATF_REQUIRE(!run_tcp_client("5438")); 141 142 /* dgram/wait service_max of 0 */ 143 ATF_REQUIRE(!run_tcp_client("5439")); 144 145 /* Exit inetd */ 146 CHECK_ERROR(kill(proc, SIGTERM)); 147 148 waitfor(proc, "inetd"); 149 } 150 151 ATF_TP_ADD_TCS(tp) 152 { 153 ATF_TP_ADD_TC(tp, test_ratelimit); 154 155 return atf_no_error(); 156 } 157 158 /* Return true if successfully received message, false if timeout */ 159 static bool 160 run_udp_client(const char *port) 161 { 162 char buffer[] = "test"; 163 struct sockaddr_storage addr; 164 165 int udp = create_socket("127.0.0.1", port, SOCK_DGRAM, UDP, 1, &addr); 166 167 CHECK_ERROR(sendto(udp, buffer, sizeof(buffer), 0, 168 (struct sockaddr *)&addr, addr.ss_len)); 169 170 struct iovec iov = { 171 .iov_base = buffer, 172 .iov_len = sizeof(buffer) 173 }; 174 175 struct msghdr msg = { 176 .msg_name = &addr, 177 .msg_namelen = addr.ss_len, /* is this correct? */ 178 .msg_iov = &iov, 179 .msg_iovlen = 1 180 }; 181 182 ssize_t count = recvmsg(udp, &msg, 0); 183 if (count == -1) { 184 if (errno == EAGAIN) { 185 /* Timed out, return false */ 186 CHECK_ERROR(close(udp)); 187 return false; 188 } else { 189 /* All other errors fatal */ 190 CHECK_ERROR(-1); 191 } 192 } 193 CHECK_ERROR(close(udp)); 194 return true; 195 } 196 197 /* Run localhost tcp echo, return true if successful, false if timeout/disconnect */ 198 static bool 199 run_tcp_client(const char *port) 200 { 201 struct sockaddr_storage remote; 202 ssize_t count; 203 int tcp; 204 char buffer[] = "test"; 205 206 tcp = create_socket("127.0.0.1", port, SOCK_STREAM, TCP, 1, &remote); 207 CHECK_ERROR(connect(tcp, (const struct sockaddr *)&remote, 208 remote.ss_len)); 209 CHECK_ERROR(send(tcp, buffer, sizeof(buffer), 0)); 210 count = recv(tcp, buffer, sizeof(buffer), 0); 211 if (count == -1) { 212 /* 213 * Connection reset by peer indicates the connection was 214 * dropped. EAGAIN indicates the timeout expired. Any other 215 * error is unexpected for this client program test. 216 */ 217 if(errno == ECONNRESET || errno == EAGAIN) { 218 return false; 219 } else { 220 CHECK_ERROR(-1); 221 return false; 222 } 223 } 224 225 if (count == 0) { 226 /* socket was shutdown by inetd, no more data available */ 227 return false; 228 } 229 return true; 230 } 231 232 /* 233 * Create a socket with the characteristics inferred by the args, return parsed 234 * socket address in dst. 235 */ 236 static int 237 create_socket(const char *address, const char *port, 238 int socktype, int proto, time_t timeout_sec, struct sockaddr_storage *dst) 239 { 240 struct addrinfo hints = { 241 .ai_flags = AI_NUMERICHOST, 242 .ai_socktype = socktype, 243 .ai_protocol = proto 244 }; 245 struct addrinfo * res; 246 int error, fd; 247 248 ATF_REQUIRE_EQ_MSG(error = getaddrinfo(address, port, &hints, &res), 0, 249 "%s", gai_strerror(error)); 250 251 /* Make sure there is only one possible bind address */ 252 ATF_REQUIRE_MSG(res->ai_next == NULL, "Ambiguous create_socket args"); 253 CHECK_ERROR(fd = socket(res->ai_family, 254 res->ai_socktype, res->ai_protocol)); 255 struct timeval timeout = { timeout_sec, 0 }; 256 CHECK_ERROR(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, 257 sizeof(timeout))); 258 memcpy(dst, res->ai_addr, res->ai_addrlen); 259 freeaddrinfo(res); 260 return fd; 261 } 262 263 /* Run program with args */ 264 static pid_t 265 run(const char *prog, char *const *args) 266 { 267 pid_t proc; 268 extern char **environ; 269 ATF_REQUIRE_EQ(posix_spawnp(&proc, prog, 270 NULL, NULL, args, environ), 0); 271 return proc; 272 } 273 274 /* Wait for a process to exit, check return value */ 275 static void 276 waitfor(pid_t pid, const char *taskname) 277 { 278 int status; 279 int rpid = waitpid(pid, &status, WALLSIG); 280 ATF_REQUIRE_MSG(rpid == pid, "wait %d != %d %s", 281 rpid, pid, strerror(errno)); 282 283 ATF_REQUIRE_EQ_MSG(WEXITSTATUS(status), EXIT_SUCCESS, 284 "%s failed with " 285 "exit status %d", taskname, WEXITSTATUS(status)); 286 } 287 288 /* Concatenate two const strings, do not free arguments */ 289 static char * 290 concat(const char *restrict left, const char *restrict right) 291 { 292 char *res; 293 if (asprintf(&res, "%s%s", left, right) == -1) 294 return NULL; 295 return res; 296 } 297