Home | History | Annotate | Line # | Download | only in inetd
      1 /*	$NetBSD: t_inetd.c,v 1.3 2025/01/05 08:23:33 riastradh 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.3 2025/01/05 08:23:33 riastradh 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