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 */
76static void
77disable_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");
132cleanup_and_restore:
133		(void)close(fd);
134		(void)unlink(SYSCTLBAKFILE);
135restore_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 */
146static void
147restore_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 */
190static void
191connect_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
297ATF_TC_WITH_CLEANUP(basic_ipv4);
298ATF_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
309ATF_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
322ATF_TC_CLEANUP(basic_ipv4, tc)
323{
324
325	restore_random_ports();
326}
327
328ATF_TC_WITH_CLEANUP(basic_ipv6);
329ATF_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
340ATF_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
353ATF_TC_CLEANUP(basic_ipv6, tc)
354{
355
356	restore_random_ports();
357}
358
359ATF_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