From nobody Tue Aug 9 12:09:40 2022 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4M2BhN3SnGz4YnMr; Tue, 9 Aug 2022 12:09:40 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4M2BhN2ZWnz3ljh; Tue, 9 Aug 2022 12:09:40 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1660046980; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=7a2Hw+0Cte+fCNj+e+IRg+7qhbSZ7iFKYRB7K3hl+mg=; b=PSlbb3asYwmyz1EJjRiRIvina1DhsMb912n3BddToAjsbaI/Xaiy0DBgZ9ny1k2tTvqFhk j/qKIsjXOSrub3bWx82/SRoTouFOri7VVPMvRl+ZUpSe+Mf1asYJ6KlhQD2ZHwUcUwnM9F mq+1R/am9bBtu7Y9cMjHuWv56yN2HOrUDyx6gAud62/K5mr1AKrtZVrVK9vjIS0RPuUgGi xrKNZkma4gUianKSniSf01+XPwym+vnBjXm82o+DtMea/WcefA3f9mpk+e7wSdIo9xUJ1z 4I7heMNGFNRGjfvpVg7ACZQvoA3e/aqqdcrWMTvsA/f3tZdeKwzQN7TkacGEVw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4M2BhN1QhXzWWW; Tue, 9 Aug 2022 12:09:40 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 279C9eaV081892; Tue, 9 Aug 2022 12:09:40 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 279C9ewp081891; Tue, 9 Aug 2022 12:09:40 GMT (envelope-from git) Date: Tue, 9 Aug 2022 12:09:40 GMT Message-Id: <202208091209.279C9ewp081891@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Mike Karels Subject: git: ec00e95190ad - main - netinet tests: Add test for IPv6 mapped-v4 bind problem List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-main@freebsd.org X-BeenThere: dev-commits-src-main@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: karels X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: ec00e95190ad9c54222d5c30e6156e375769478e Auto-Submitted: auto-generated ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1660046980; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=7a2Hw+0Cte+fCNj+e+IRg+7qhbSZ7iFKYRB7K3hl+mg=; b=bkAX1Wf/g+CkNnGb61J83VeHNLROuusTAyGHGAD4uAjCHniLW7rpGoWDTeG6qHrJy32Tb8 73HBZgPaEd5mgCRM893IV9sE9qcPAFXjSM++SQ3+1kG5kSmijHwuAH9QaVH25jKgMsiB73 A4lifbFKAtyKUR3cOLZSPiJvvFMFMBWu3NOmrMNiK+8oCLYdIqyBkENgu6i0T7akq7FtOe l/p10Qq7C6CCaXLKE+xq2sJpEJm6LP3PoFbzqHzdo+xjB5NtnFItkPlDB9e9v9OakMtQ0E 6KJ01W01sm3aBoV+rKohTetyPKC+kK7hWdCoXmw+IFmnSlGn+4/fkuOHRj9tKg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1660046980; a=rsa-sha256; cv=none; b=TZarcN0GEvUCVc+gL335hwxJWBH8eIpTs0/5BYCawiT+MyjuCEKg4cDRCPmP7Py4zECK6a bQXdznA5S/oTj2on89XNKR5fPjsBRuB0JRGImi90gP9H7IbAI2dTfQZlnA7ggNTo8qoQ6Z W8x2oziQlkR+hc2QjJ6TRiThsUah/sgPi86bIfm+PO2nVCxoZN7I6Z1vhPaNLk3YLG6JUD 3e2JOfSbBNHv+42qskvcOWpduP5P4KmJ+dkYciGt1d+y4X4TaUgTHMhKgGeZDEcWLxEDJx tOtvk8rskwq70l10GF5O5OVMTWdmhZoXl6yyYcbu2EHBpYORs+P9zmNgmseq8w== ARC-Authentication-Results: i=1; mx1.freebsd.org; none X-ThisMailContainsUnwantedMimeParts: N The branch main has been updated by karels: URL: https://cgit.FreeBSD.org/src/commit/?id=ec00e95190ad9c54222d5c30e6156e375769478e commit ec00e95190ad9c54222d5c30e6156e375769478e Author: Mike Karels AuthorDate: 2022-08-09 12:08:09 +0000 Commit: Mike Karels CommitDate: 2022-08-09 12:08:09 +0000 netinet tests: Add test for IPv6 mapped-v4 bind problem Test fix in 637f317c6d9c, verifying that when ports run out, we get an EADDRNOTAVAIL error from bind() rather than an EADDRINUSE error from connect(). Use small port range to exhaust ports and see which error happens. Reviewed by: tuexen, glebius, melifaro Differential Revision: https://reviews.freebsd.org/D36056 MFC after: 3 days (with 637f317c6d9c) --- tests/sys/netinet/Makefile | 2 + tests/sys/netinet/tcp6_v4mapped_bind_test.c | 333 ++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+) diff --git a/tests/sys/netinet/Makefile b/tests/sys/netinet/Makefile index f21e254cd30f..7bcbfc202700 100644 --- a/tests/sys/netinet/Makefile +++ b/tests/sys/netinet/Makefile @@ -10,11 +10,13 @@ TESTS_SUBDIRS+= libalias ATF_TESTS_C= ip_reass_test \ so_reuseport_lb_test \ socket_afinet \ + tcp6_v4mapped_bind_test \ tcp_connect_port_test \ tcp_md5_getsockopt ATF_TESTS_SH= carp fibs fibs_test redirect divert forward output lpm arp TEST_METADATA.output+= required_programs="python" +TEST_METADATA.tcp6_v4mapped_bind_test+= is_exclusive="true" PROGS= udp_dontroute tcp_user_cookie diff --git a/tests/sys/netinet/tcp6_v4mapped_bind_test.c b/tests/sys/netinet/tcp6_v4mapped_bind_test.c new file mode 100644 index 000000000000..0d768500a150 --- /dev/null +++ b/tests/sys/netinet/tcp6_v4mapped_bind_test.c @@ -0,0 +1,333 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Michael J. Karels. + * Copyright (c) 2020 Netflix, Inc. + * + * 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 AUTHOR 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 AUTHOR 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. + */ + +/* + * This test is derived from tcp_connect_port_test.c. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SYSCTLBAKFILE "tmp.net.inet.ip.portrange.values" + +#define PORT_FIRST 10000 /* normal default */ +#define PORT_LAST 10003 +#define LOOPS 10 /* 5 should be enough */ + +struct portrange { + int first; + int last; +}; + +/* + * Set first and last ports in the ipport range. Save the old values + * of the sysctls so they can be restored later. + */ +static void +set_portrange(void) +{ + int error, fd, first_new, last_new; + struct portrange save_ports; + size_t sysctlsz; + + /* + * Pre-emptively unlink our restoration file, so we will do no + * restoration on error. + */ + unlink(SYSCTLBAKFILE); + + /* + * Set the net.inet.ip.portrange.{first,last} sysctls. Save the + * old values so we can restore them. + */ + first_new = PORT_FIRST; + sysctlsz = sizeof(save_ports.first); + error = sysctlbyname("net.inet.ip.portrange.first", &save_ports.first, + &sysctlsz, &first_new, sizeof(first_new)); + if (error) { + warn("sysctlbyname(\"net.inet.ip.portrange.first\") " + "failed"); + atf_tc_skip("Unable to set sysctl"); + } + if (sysctlsz != sizeof(save_ports.first)) { + fprintf(stderr, "Error: unexpected sysctl value size " + "(expected %zu, actual %zu)\n", sizeof(save_ports.first), + sysctlsz); + goto restore_sysctl; + } + + last_new = PORT_LAST; + sysctlsz = sizeof(save_ports.last); + error = sysctlbyname("net.inet.ip.portrange.last", &save_ports.last, + &sysctlsz, &last_new, sizeof(last_new)); + if (error) { + warn("sysctlbyname(\"net.inet.ip.portrange.last\") " + "failed"); + atf_tc_skip("Unable to set sysctl"); + } + if (sysctlsz != sizeof(save_ports.last)) { + fprintf(stderr, "Error: unexpected sysctl value size " + "(expected %zu, actual %zu)\n", sizeof(save_ports.last), + sysctlsz); + goto restore_sysctl; + } + + /* Open the backup file, write the contents, and close it. */ + fd = open(SYSCTLBAKFILE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, + S_IRUSR|S_IWUSR); + if (fd < 0) { + warn("error opening sysctl backup file"); + goto restore_sysctl; + } + error = write(fd, &save_ports, sizeof(save_ports)); + if (error < 0) { + warn("error writing saved value to sysctl backup file"); + goto cleanup_and_restore; + } + if (error != (int)sizeof(save_ports)) { + fprintf(stderr, + "Error writing saved value to sysctl backup file: " + "(expected %zu, actual %d)\n", sizeof(save_ports), error); + goto cleanup_and_restore; + } + error = close(fd); + if (error) { + warn("error closing sysctl backup file"); +cleanup_and_restore: + (void)close(fd); + (void)unlink(SYSCTLBAKFILE); +restore_sysctl: + sysctlsz = sizeof(save_ports.first); + (void)sysctlbyname("net.inet.ip.portrange.first", NULL, + NULL, &save_ports.first, sysctlsz); + sysctlsz = sizeof(save_ports.last); + (void)sysctlbyname("net.inet.ip.portrange.last", NULL, + NULL, &save_ports.last, sysctlsz); + atf_tc_skip("Error setting sysctl"); + } +} + +/* + * Restore the sysctl values from the backup file and delete the backup file. + */ +static void +restore_portrange(void) +{ + int error, fd; + struct portrange save_ports; + + /* Open the backup file, read the contents, close it, and delete it. */ + fd = open(SYSCTLBAKFILE, O_RDONLY); + if (fd < 0) { + warn("error opening sysctl backup file"); + return; + } + error = read(fd, &save_ports, sizeof(save_ports)); + if (error < 0) { + warn("error reading saved values from sysctl backup file"); + return; + } + if (error != (int)sizeof(save_ports)) { + fprintf(stderr, + "Error reading saved values from sysctl backup file: " + "(expected %zu, actual %d)\n", sizeof(save_ports), error); + return; + } + error = close(fd); + if (error) + warn("error closing sysctl backup file"); + error = unlink(SYSCTLBAKFILE); + if (error) + warn("error removing sysctl backup file"); + + /* Restore the saved sysctl values. */ + error = sysctlbyname("net.inet.ip.portrange.first", NULL, NULL, + &save_ports.first, sizeof(save_ports.first)); + if (error) + warn("sysctlbyname(\"net.inet.ip.portrange.first\") " + "failed while restoring value"); + error = sysctlbyname("net.inet.ip.portrange.last", NULL, NULL, + &save_ports.last, sizeof(save_ports.last)); + if (error) + warn("sysctlbyname(\"net.inet.ip.portrange.last\") " + "failed while restoring value"); +} + +ATF_TC_WITH_CLEANUP(v4mapped); +ATF_TC_HEAD(v4mapped, tc) +{ + + /* root is only required for sysctls (setup and cleanup). */ + atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "require.config", "allow_sysctl_side_effects"); + atf_tc_set_md_var(tc, "descr", + "Check local port assignment with bind and mapped V4 addresses"); +} + +/* + * Create a listening IPv4 socket, then connect to it repeatedly using a + * bound IPv6 socket using a v4 mapped address. With a small port range, + * this should fail on a bind() call with EADDRNOTAVAIL. However, in + * previous systems, the bind() would succeed, binding a duplicate port, + * and then the connect would fail with EADDRINUSE. Make sure we get + * the right error. + */ +ATF_TC_BODY(v4mapped, tc) +{ + union { + struct sockaddr saddr; + struct sockaddr_in saddr4; + struct sockaddr_in6 saddr6; + } su_clnt, su_srvr, su_mapped; + struct addrinfo ai_hint, *aip; + socklen_t salen; + int csock, error, i, lsock, off = 0; + bool got_bind_error = false; + + /* + * Set the net.inet.ip.portrange.{first,last} sysctls to use a small + * range, allowing us to generate port exhaustion quickly. + */ + set_portrange(); + + /* Setup the listen socket. */ + lsock = socket(PF_INET, SOCK_STREAM, 0); + ATF_REQUIRE_MSG(lsock >= 0, "socket() for listen socket failed: %s", + strerror(errno)); + + memset(&su_srvr.saddr4, 0, sizeof(su_srvr.saddr4)); + su_srvr.saddr4.sin_family = AF_INET; + error = bind(lsock, &su_srvr.saddr, sizeof(su_srvr.saddr4)); + ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno)); + error = listen(lsock, LOOPS + 1); + ATF_REQUIRE_MSG(error == 0, "listen() failed: %s", strerror(errno)); + + /* Get the address of the listen socket. */ + salen = sizeof(su_srvr); + error = getsockname(lsock, &su_srvr.saddr, &salen); + ATF_REQUIRE_MSG(error == 0, + "getsockname() for listen socket failed: %s", + strerror(errno)); + ATF_REQUIRE_MSG(salen == sizeof(struct sockaddr_in), + "unexpected sockaddr size"); + ATF_REQUIRE_MSG(su_srvr.saddr.sa_len == sizeof(struct sockaddr_in), + "unexpected sa_len size"); + + /* Set up destination address for client sockets. */ + memset(&ai_hint, 0, sizeof(ai_hint)); + ai_hint.ai_family = AF_INET6; + ai_hint.ai_flags = AI_NUMERICHOST | AI_V4MAPPED; + error = getaddrinfo("127.0.0.1", NULL, &ai_hint, &aip); + ATF_REQUIRE_MSG(error == 0, "getaddrinfo: %s", gai_strerror(error)); + memcpy(&su_mapped.saddr6, aip->ai_addr, sizeof(su_mapped.saddr6)); + su_mapped.saddr6.sin6_port = su_srvr.saddr4.sin_port; + freeaddrinfo(aip); + + /* Set up address to bind for client sockets (unspecified). */ + memset(&su_clnt.saddr6, 0, sizeof(su_clnt.saddr6)); + su_clnt.saddr6.sin6_family = AF_INET6; + + /* Open connections in a loop. */ + for (i = 0; i < LOOPS; i++) { + csock = socket(PF_INET6, SOCK_STREAM, 0); + ATF_REQUIRE_MSG(csock >= 0, + "socket() for client socket %d failed: %s", + i, strerror(errno)); + error = setsockopt(csock, IPPROTO_IPV6, IPV6_V6ONLY, &off, + sizeof(off)); + ATF_REQUIRE_MSG(error == 0, + "setsockopt(IPV6_ONLY = 0) failed: %s", strerror(errno)); + + /* + * A bind would not be necessary for operation, but + * provokes the error. + */ + error = bind(csock, &su_clnt.saddr, sizeof(su_clnt.saddr6)); + if (error != 0) { + if (errno == EADDRNOTAVAIL) { /* Success, expected */ + got_bind_error = true; + break; + } + ATF_REQUIRE_MSG(error == 0, + "client bind %d failed: %s", i, strerror(errno)); + } + + error = connect(csock, &su_mapped.saddr, su_mapped.saddr.sa_len); + if (error != 0 && errno == EADDRINUSE) { + /* This is the specific error we were looking for. */ + ATF_REQUIRE_MSG(error == 0, + "client connect %d failed, " + " client had duplicate port: %s", + i, strerror(errno)); + } + ATF_REQUIRE_MSG(error == 0, + "connect() for client socket %d failed: %s", + i, strerror(errno)); + + /* + * We don't accept the new socket from the server socket + * or close the client socket, as we want the ports to + * remain busy. The range is small enough that this is + * not a problem. + */ + } + ATF_REQUIRE_MSG(i >= 1, "No successful connections"); + ATF_REQUIRE_MSG(got_bind_error == true, "No expected bind error"); +} + +ATF_TC_CLEANUP(v4mapped, tc) +{ + + restore_portrange(); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, v4mapped); + + return (atf_no_error()); +} +