Home | History | Annotate | Line # | Download | only in tcp
      1 /*	$NetBSD: t_tcp_connect_port.c,v 1.2 2022/11/17 08:36:54 ozaki-r Exp $	*/
      2 
      3 /*-
      4  * SPDX-License-Identifier: BSD-2-Clause
      5  *
      6  * Copyright (c) 2020 Netflix, Inc.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions are
     10  * met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in
     15  *    the documentation and/or other materials provided with the
     16  *    distribution.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     28  * SUCH DAMAGE.
     29  */
     30 
     31 #include <sys/cdefs.h>
     32 #ifdef __NetBSD__
     33 __RCSID("$NetBSD: t_tcp_connect_port.c,v 1.2 2022/11/17 08:36:54 ozaki-r Exp $");
     34 #define USE_RUMPKERNEL	1
     35 #else
     36 __FBSDID("$FreeBSD$");
     37 #endif
     38 
     39 #include <sys/param.h>
     40 #include <sys/socket.h>
     41 #include <sys/stat.h>
     42 #include <sys/sysctl.h>
     43 
     44 #include <netinet/in.h>
     45 
     46 #include <err.h>
     47 #include <errno.h>
     48 #include <fcntl.h>
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <unistd.h>
     52 
     53 #include <atf-c.h>
     54 
     55 #ifdef USE_RUMPKERNEL
     56 #include <rump/rump.h>
     57 #include <rump/rump_syscalls.h>
     58 
     59 #define socket	rump_sys_socket
     60 #define bind	rump_sys_bind
     61 #define listen	rump_sys_listen
     62 #define accept	rump_sys_accept
     63 #define connect	rump_sys_connect
     64 #define write	rump_sys_write
     65 #define close	rump_sys_close
     66 #define setsockopt	rump_sys_setsockopt
     67 #define getsockname	rump_sys_getsockname
     68 #endif /* USE_RUMPKERNEL */
     69 
     70 #define	SYSCTLBAKFILE	"tmp.net.inet.ip.portrange.randomized"
     71 
     72 /*
     73  * Check if port allocation is randomized. If so, update it. Save the old
     74  * value of the sysctl so it can be updated later.
     75  */
     76 static void
     77 disable_random_ports(void)
     78 {
     79 #ifdef USE_RUMPKERNEL
     80 	rump_init(); /* XXX */
     81 #else
     82 	int error, fd, random_new, random_save;
     83 	size_t sysctlsz;
     84 
     85 	/*
     86 	 * Pre-emptively unlink our restoration file, so we will do no
     87 	 * restoration on error.
     88 	 */
     89 	unlink(SYSCTLBAKFILE);
     90 
     91 	/*
     92 	 * Disable the net.inet.ip.portrange.randomized sysctl. Save the
     93 	 * old value so we can restore it, if necessary.
     94 	 */
     95 	random_new = 0;
     96 	sysctlsz = sizeof(random_save);
     97 	error = sysctlbyname("net.inet.ip.portrange.randomized", &random_save,
     98 	    &sysctlsz, &random_new, sizeof(random_new));
     99 	if (error) {
    100 		warn("sysctlbyname(\"net.inet.ip.portrange.randomized\") "
    101 		    "failed");
    102 		atf_tc_skip("Unable to set sysctl");
    103 	}
    104 	if (sysctlsz != sizeof(random_save)) {
    105 		fprintf(stderr, "Error: unexpected sysctl value size "
    106 		    "(expected %zu, actual %zu)\n", sizeof(random_save),
    107 		    sysctlsz);
    108 		goto restore_sysctl;
    109 	}
    110 
    111 	/* Open the backup file, write the contents, and close it. */
    112 	fd = open(SYSCTLBAKFILE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL,
    113 	    S_IRUSR|S_IWUSR);
    114 	if (fd < 0) {
    115 		warn("error opening sysctl backup file");
    116 		goto restore_sysctl;
    117 	}
    118 	error = write(fd, &random_save, sizeof(random_save));
    119 	if (error < 0) {
    120 		warn("error writing saved value to sysctl backup file");
    121 		goto cleanup_and_restore;
    122 	}
    123 	if (error != (int)sizeof(random_save)) {
    124 		fprintf(stderr,
    125 		    "Error writing saved value to sysctl backup file: "
    126 		    "(expected %zu, actual %d)\n", sizeof(random_save), error);
    127 		goto cleanup_and_restore;
    128 	}
    129 	error = close(fd);
    130 	if (error) {
    131 		warn("error closing sysctl backup file");
    132 cleanup_and_restore:
    133 		(void)close(fd);
    134 		(void)unlink(SYSCTLBAKFILE);
    135 restore_sysctl:
    136 		(void)sysctlbyname("net.inet.ip.portrange.randomized", NULL,
    137 		    NULL, &random_save, sysctlsz);
    138 		atf_tc_skip("Error setting sysctl");
    139 	}
    140 #endif /* USE_RUMPKERNEL */
    141 }
    142 
    143 /*
    144  * Restore the sysctl value from the backup file and delete the backup file.
    145  */
    146 static void
    147 restore_random_ports(void)
    148 {
    149 #ifndef USE_RUMPKERNEL
    150 	int error, fd, random_save;
    151 
    152 	/* Open the backup file, read the contents, close it, and delete it. */
    153 	fd = open(SYSCTLBAKFILE, O_RDONLY);
    154 	if (fd < 0) {
    155 		warn("error opening sysctl backup file");
    156 		return;
    157 	}
    158 	error = read(fd, &random_save, sizeof(random_save));
    159 	if (error < 0) {
    160 		warn("error reading saved value from sysctl backup file");
    161 		return;
    162 	}
    163 	if (error != (int)sizeof(random_save)) {
    164 		fprintf(stderr,
    165 		    "Error reading saved value from sysctl backup file: "
    166 		    "(expected %zu, actual %d)\n", sizeof(random_save), error);
    167 		return;
    168 	}
    169 	error = close(fd);
    170 	if (error)
    171 		warn("error closing sysctl backup file");
    172 	error = unlink(SYSCTLBAKFILE);
    173 	if (error)
    174 		warn("error removing sysctl backup file");
    175 
    176 	/* Restore the saved sysctl value. */
    177 	error = sysctlbyname("net.inet.ip.portrange.randomized", NULL, NULL,
    178 	    &random_save, sizeof(random_save));
    179 	if (error)
    180 		warn("sysctlbyname(\"net.inet.ip.portrange.randomized\") "
    181 		    "failed while restoring value");
    182 #endif /* USE_RUMPKERNEL */
    183 }
    184 
    185 /*
    186  * Given a domain and sockaddr, open a listening socket with automatic port
    187  * selection. Then, try to connect 64K times. Ensure the connected socket never
    188  * uses an overlapping port.
    189  */
    190 static void
    191 connect_loop(int domain, const struct sockaddr *addr)
    192 {
    193 	union {
    194 		struct sockaddr saddr;
    195 		struct sockaddr_in saddr4;
    196 		struct sockaddr_in6 saddr6;
    197 	} su_clnt, su_srvr;
    198 	socklen_t salen;
    199 	int asock, csock, error, i, lsock;
    200 	const struct linger lopt = { 1, 0 };
    201 
    202 	/*
    203 	 * Disable the net.inet.ip.portrange.randomized sysctl. Assuming an
    204 	 * otherwise idle system, this makes the kernel try all possible
    205 	 * ports sequentially and makes it more likely it will try the
    206 	 * port on which we have a listening socket.
    207 	 */
    208 	disable_random_ports();
    209 
    210 	/* Setup the listen socket. */
    211 	lsock = socket(domain, SOCK_STREAM, 0);
    212 	ATF_REQUIRE_MSG(lsock >= 0, "socket() for listen socket failed: %s",
    213 	    strerror(errno));
    214 	error = bind(lsock, addr, addr->sa_len);
    215 	ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno));
    216 	error = listen(lsock, 1);
    217 	ATF_REQUIRE_MSG(error == 0, "listen() failed: %s", strerror(errno));
    218 
    219 	/*
    220 	 * Get the address of the listen socket, which will be the destination
    221 	 * address for our connection attempts.
    222 	 */
    223 	salen = sizeof(su_srvr);
    224 	error = getsockname(lsock, &su_srvr.saddr, &salen);
    225 	ATF_REQUIRE_MSG(error == 0,
    226 	    "getsockname() for listen socket failed: %s",
    227 	    strerror(errno));
    228 	ATF_REQUIRE_MSG(salen == (domain == PF_INET ?
    229 	    sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)),
    230 	    "unexpected sockaddr size");
    231 	ATF_REQUIRE_MSG(su_srvr.saddr.sa_len == (domain == PF_INET ?
    232 	    sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)),
    233 	    "unexpected sa_len size");
    234 
    235 	/* Open 64K connections in a loop. */
    236 	for (i = 0; i < 65536; i++) {
    237 		csock = socket(domain, SOCK_STREAM, 0);
    238 		ATF_REQUIRE_MSG(csock >= 0,
    239 		    "socket() for client socket %d failed: %s",
    240 		    i, strerror(errno));
    241 
    242 		error = connect(csock, &su_srvr.saddr, su_srvr.saddr.sa_len);
    243 		ATF_REQUIRE_MSG(error == 0,
    244 		    "connect() for client socket %d failed: %s",
    245 		    i, strerror(errno));
    246 
    247 		error = setsockopt(csock, SOL_SOCKET, SO_LINGER, &lopt,
    248 		    sizeof(lopt));
    249 		ATF_REQUIRE_MSG(error == 0,
    250 		    "Setting linger for client socket %d failed: %s",
    251 		    i, strerror(errno));
    252 
    253 		/* Ascertain the client socket address. */
    254 		salen = sizeof(su_clnt);
    255 		error = getsockname(csock, &su_clnt.saddr, &salen);
    256 		ATF_REQUIRE_MSG(error == 0,
    257 		    "getsockname() for client socket %d failed: %s",
    258 		    i, strerror(errno));
    259 		ATF_REQUIRE_MSG(salen == (domain == PF_INET ?
    260 		    sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)),
    261 		    "unexpected sockaddr size for client socket %d", i);
    262 
    263 		/* Ensure the ports do not match. */
    264 		switch (domain) {
    265 		case PF_INET:
    266 			ATF_REQUIRE_MSG(su_clnt.saddr4.sin_port !=
    267 			    su_srvr.saddr4.sin_port,
    268 			    "client socket %d using the same port as server",
    269 			    i);
    270 			break;
    271 		case PF_INET6:
    272 			ATF_REQUIRE_MSG(su_clnt.saddr6.sin6_port !=
    273 			    su_srvr.saddr6.sin6_port,
    274 			    "client socket %d using the same port as server",
    275 			    i);
    276 			break;
    277 		}
    278 
    279 		/* Accept the socket and close both ends. */
    280 		asock = accept(lsock, NULL, NULL);
    281 		ATF_REQUIRE_MSG(asock >= 0,
    282 		    "accept() failed for client socket %d: %s",
    283 		    i, strerror(errno));
    284 
    285 		error = close(asock);
    286 		ATF_REQUIRE_MSG(error == 0,
    287 		    "close() failed for accepted socket %d: %s",
    288 		    i, strerror(errno));
    289 
    290 		error = close(csock);
    291 		ATF_REQUIRE_MSG(error == 0,
    292 		    "close() failed for client socket %d: %s",
    293 		    i, strerror(errno));
    294 	}
    295 }
    296 
    297 ATF_TC_WITH_CLEANUP(basic_ipv4);
    298 ATF_TC_HEAD(basic_ipv4, tc)
    299 {
    300 
    301 	atf_tc_set_md_var(tc, "require.user", "root");
    302 #ifndef USE_RUMPKERNEL
    303 	atf_tc_set_md_var(tc, "require.config", "allow_sysctl_side_effects");
    304 #endif
    305 	atf_tc_set_md_var(tc, "descr",
    306 	    "Check automatic local port assignment during TCP connect calls");
    307 }
    308 
    309 ATF_TC_BODY(basic_ipv4, tc)
    310 {
    311 	struct sockaddr_in saddr4;
    312 
    313 	memset(&saddr4, 0, sizeof(saddr4));
    314 	saddr4.sin_len = sizeof(saddr4);
    315 	saddr4.sin_family = AF_INET;
    316 	saddr4.sin_port = htons(0);
    317 	saddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    318 
    319 	connect_loop(PF_INET, (const struct sockaddr *)&saddr4);
    320 }
    321 
    322 ATF_TC_CLEANUP(basic_ipv4, tc)
    323 {
    324 
    325 	restore_random_ports();
    326 }
    327 
    328 ATF_TC_WITH_CLEANUP(basic_ipv6);
    329 ATF_TC_HEAD(basic_ipv6, tc)
    330 {
    331 
    332 	atf_tc_set_md_var(tc, "require.user", "root");
    333 #ifndef USE_RUMPKERNEL
    334 	atf_tc_set_md_var(tc, "require.config", "allow_sysctl_side_effects");
    335 #endif
    336 	atf_tc_set_md_var(tc, "descr",
    337 	    "Check automatic local port assignment during TCP connect calls");
    338 }
    339 
    340 ATF_TC_BODY(basic_ipv6, tc)
    341 {
    342 	struct sockaddr_in6 saddr6;
    343 
    344 	memset(&saddr6, 0, sizeof(saddr6));
    345 	saddr6.sin6_len = sizeof(saddr6);
    346 	saddr6.sin6_family = AF_INET6;
    347 	saddr6.sin6_port = htons(0);
    348 	saddr6.sin6_addr = in6addr_loopback;
    349 
    350 	connect_loop(PF_INET6, (const struct sockaddr *)&saddr6);
    351 }
    352 
    353 ATF_TC_CLEANUP(basic_ipv6, tc)
    354 {
    355 
    356 	restore_random_ports();
    357 }
    358 
    359 ATF_TP_ADD_TCS(tp)
    360 {
    361 	ATF_TP_ADD_TC(tp, basic_ipv4);
    362 	ATF_TP_ADD_TC(tp, basic_ipv6);
    363 
    364 	return (atf_no_error());
    365 }
    366 
    367