From nobody Mon Aug 10 20:51:06 2026 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 4hJn3N5Qdcz6jxpq for ; Mon, 10 Aug 2026 20:51:12 +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 "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hJn3M2JNNz3TZN for ; Mon, 10 Aug 2026 20:51:11 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1786395071; 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=/r7pvxuH5MjU0V00NUV0LHuoaYqn0WZReuO04BTt6/8=; b=Soq8ifpIP/RW+WFchmGY7Wapwr36y4SdLFIIL+/PFaFt+6vhwJsZryi52FuxJnqV0zad+I To+xnk0RluVAicCMPm0LQv/Eaq71exUYwUpo4NIGqVZTtYSITe/FyRnvGr5PUISn0uvHuV W83QHm3S1uWnzBK9iMEMQKIvm5PoLF+Jdhz1WUdVdkRF82AHwEodUF6tPUPMN9OaWPxIyt f8IW3/I2VDFzo0Bm1oby3K8dnJ07aO9qdWDxoFpFJvqDaMAo1cKD+HJ8y3bY8z5UU1nt66 DWDvrkSvvcfLFDRVMt7+wyRdgJU3FTtJmZ50uwQatjYdCjmCSGcIScOLcVBfRA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1786395071; a=rsa-sha256; cv=none; b=Mo6676HTdIXLx2bixW44TEMh2tdihh/ZDZQ+YfYKQpeqaMqhw8geQVCYrmBTelzF47Kiz6 YdpBGLRwcpobhIwTpYBhgVSJHkyH5qL2kIPyUaD5fRVSoK2itNl7K5ShG3i3V0XziDy+Ft mLl7roEkQ6KLTERbnvyk3feqmUHYikmV5Yz7M1l2A3pMcnmJYLmuZmx311BU7dL70H8sML 7DFTmHMiaC+7iZYo+KWd7pBzNCS3LHBtCOz9S6X/QNDw7siLXJHJpSA1YeORvp5U+PMFVn x9TGnPZJ42O4uVUqzsK6v40z1kFboshSoUMmaROoaG0w37SgCpsP02bPtblJZA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1786395071; 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=/r7pvxuH5MjU0V00NUV0LHuoaYqn0WZReuO04BTt6/8=; b=dra2V6Jom2wc78Nv0DBSLV5hmi07t1YwowEpLmBjyI8jOvwoPkDzxW4JhPyyHPLK52ZGRP axB1jUavS2HSthPZ/5CiY+OqoXAhChEHUt3X1kj2C1jtn40F44yD7exrqmdc/bj5ManU0v m9zyZilikWUGO8Xehz9ISYfAe9wvVmnIs1HckFx1v1P8+SnFOLWLWo3MRXeqOF1/dKfC8u kwtsmgz+SVIXcJzsR9Je0PirSCF96YwawGMmm9xDLCjDFc38m9tzcJOtJDcGZMlnqdSOcJ 2Mw7jZFe5qBqSyqzbEe6bB2oeUmHCoO+hLByJDeCOTRBCafg04GVY5UlnkPQyQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hJn3G1GKbz9Dw for ; Mon, 10 Aug 2026 20:51:06 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 24078 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 10 Aug 2026 20:51:06 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Gleb Smirnoff Subject: git: 8f6d8a901671 - main - tests/netinet/socket_afinet: unroll multibind test into a table 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: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: glebius X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 8f6d8a901671164360dee8fe40414006c8e9eaef Auto-Submitted: auto-generated Date: Mon, 10 Aug 2026 20:51:06 +0000 Message-Id: <6a7a39ba.24078.48d6d96d@gitrepo.freebsd.org> The branch main has been updated by glebius: URL: https://cgit.FreeBSD.org/src/commit/?id=8f6d8a901671164360dee8fe40414006c8e9eaef commit 8f6d8a901671164360dee8fe40414006c8e9eaef Author: Gleb Smirnoff AuthorDate: 2026-08-10 20:43:59 +0000 Commit: Gleb Smirnoff CommitDate: 2026-08-10 20:50:22 +0000 tests/netinet/socket_afinet: unroll multibind test into a table The test has 6 dimensions: address family, socket type, socket option on the first socket, socket option on the second socket, is first socket bound to specific address or wildcard and is the second socket priveleged or not. Before the change 3 dimensions are implemented as 3 nested for() loops, 2 dimensions are implemented as repetitions in the test body and one dimension as two actions in the innermost loop. I'm about to add one more dimension: whether the second socket is bound to a specific address or wildcard instead of using first socket's getsockopt(2) result. Also, there is a change under discussion that would make SOCK_STREAM sockets behave different to SOCK_DGRAM. That would break result consistency in the dimensions of socket type. We expect that consistency in the dimension of address families shall never break, thus this one remains a for() loop. The priveleged & non- privileged bind(2) attempts also remain as two actions, but expected results are in the table. The rest of dimensions are unrolled into a table, which at the moment has quite a lot of lines with identical results. However, as more tests are added and SOCK_STREAM behavior changes, the table will get more mixed results. Also, reading a test that is written in a declarative manner a table is much easier and modifying it is more resistent to accidential breakage. Differential Revision: https://reviews.freebsd.org/D58085 --- tests/sys/netinet/socket_afinet.c | 273 +++++++++++++++++++++++--------------- 1 file changed, 165 insertions(+), 108 deletions(-) diff --git a/tests/sys/netinet/socket_afinet.c b/tests/sys/netinet/socket_afinet.c index 98cc2be00f25..787c340466b0 100644 --- a/tests/sys/netinet/socket_afinet.c +++ b/tests/sys/netinet/socket_afinet.c @@ -346,7 +346,7 @@ static enum bind_res { BIND_REUSE_SUCCESS = 6, BIND_BADERR2 = 7, } -child_bind(const atf_tc_t *tc, int type, struct sockaddr *sa, int opt, +child_bind(const atf_tc_t *tc, int type, const struct sockaddr *sa, int opt, bool unpriv) { const char *user; @@ -400,109 +400,6 @@ child_bind(const atf_tc_t *tc, int type, struct sockaddr *sa, int opt, } } -static enum bind_res -child_bind_priv(const atf_tc_t *tc, int type, struct sockaddr *sa, int opt) -{ - return (child_bind(tc, type, sa, opt, false)); -} - -static enum bind_res -child_bind_unpriv(const atf_tc_t *tc, int type, struct sockaddr *sa, int opt) -{ - return (child_bind(tc, type, sa, opt, true)); -} - -static int -bind_socket(int domain, int type, int opt, bool unspec, struct sockaddr *sa) -{ - socklen_t slen; - int s; - - s = socket(domain, type, 0); - ATF_REQUIRE(s >= 0); - - if (domain == AF_INET) { - struct sockaddr_in sin; - - bzero(&sin, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_len = sizeof(sin); - sin.sin_addr.s_addr = htonl(unspec ? - INADDR_ANY : INADDR_LOOPBACK); - sin.sin_port = htons(0); - ATF_REQUIRE(bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0); - - slen = sizeof(sin); - } else /* if (domain == AF_INET6) */ { - struct sockaddr_in6 sin6; - - bzero(&sin6, sizeof(sin6)); - sin6.sin6_family = AF_INET6; - sin6.sin6_len = sizeof(sin6); - sin6.sin6_addr = unspec ? in6addr_any : in6addr_loopback; - sin6.sin6_port = htons(0); - ATF_REQUIRE(bind(s, (struct sockaddr *)&sin6, sizeof(sin6)) == 0); - - slen = sizeof(sin6); - } - - if (opt != 0) { - ATF_REQUIRE(setsockopt(s, SOL_SOCKET, opt, &(int){1}, - sizeof(int)) == 0); - } - - ATF_REQUIRE(getsockname(s, sa, &slen) == 0); - - return (s); -} - -static void -multibind_test(const atf_tc_t *tc, int domain, int type) -{ - struct sockaddr_storage ss; - int opts[4] = { 0, SO_REUSEADDR, SO_REUSEPORT, SO_REUSEPORT_LB }; - int s; - bool flags[2] = { false, true }; - enum bind_res res; - - for (size_t flagi = 0; flagi < nitems(flags); flagi++) { - for (size_t opti = 0; opti < nitems(opts); opti++) { - s = bind_socket(domain, type, opts[opti], flags[flagi], - (struct sockaddr *)&ss); - for (size_t optj = 0; optj < nitems(opts); optj++) { - int opt; - - opt = opts[optj]; - res = child_bind_priv(tc, type, - (struct sockaddr *)&ss, opt); - /* - * Multi-binding is only allowed when both - * sockets have SO_REUSEPORT or SO_REUSEPORT_LB - * set. - */ - if (opts[opti] != 0 && - opts[opti] != SO_REUSEADDR && opti == optj) - ATF_REQUIRE(res == BIND_REUSE_SUCCESS); - else - ATF_REQUIRE(res == BIND_FAILED); - - res = child_bind_unpriv(tc, type, - (struct sockaddr *)&ss, opt); - /* - * Multi-binding is only allowed when both - * sockets have the same owner. - */ - ATF_REQUIRE_MSG(res == BIND_FAILED, - "domain %u type %u opts %u:%u: " - "result %u (expected %u)", - domain, type, opts[opti], opts[optj], - res, BIND_FAILED); - } - ATF_REQUIRE(close(s) == 0); - } - } -} - /* * Try to bind two sockets to the same address/port tuple. Under some * conditions this is permitted. @@ -513,12 +410,172 @@ ATF_TC_HEAD(multibind, tc) atf_tc_set_md_var(tc, "require.user", "root"); atf_tc_set_md_var(tc, "require.config", "unprivileged_user"); } + ATF_TC_BODY(multibind, tc) { - multibind_test(tc, AF_INET, SOCK_STREAM); - multibind_test(tc, AF_INET, SOCK_DGRAM); - multibind_test(tc, AF_INET6, SOCK_STREAM); - multibind_test(tc, AF_INET6, SOCK_DGRAM); + const struct { + int type; + int opt1; + bool wild1; + int opt2; + bool wild2; /* not exercised yet, uses getsockname() rv */ + enum bind_res priv_res; + enum bind_res unpriv_res; + } tests[] = { +#define ADDR SO_REUSEADDR +#define PORT SO_REUSEPORT +#define LB SO_REUSEPORT_LB +#define x true +#define o false +#define F BIND_FAILED +#define R BIND_REUSE_SUCCESS + /* p u */ + { SOCK_STREAM, 0, o, 0, o, F, F }, + { SOCK_STREAM, 0, o, ADDR, o, F, F }, + { SOCK_STREAM, 0, o, PORT, o, F, F }, + { SOCK_STREAM, 0, o, LB, o, F, F }, + { SOCK_STREAM, ADDR, o, 0, o, F, F }, + { SOCK_STREAM, ADDR, o, ADDR, o, F, F }, + { SOCK_STREAM, ADDR, o, PORT, o, F, F }, + { SOCK_STREAM, ADDR, o, LB, o, F, F }, + { SOCK_STREAM, PORT, o, 0, o, F, F }, + { SOCK_STREAM, PORT, o, ADDR, o, F, F }, + { SOCK_STREAM, PORT, o, PORT, o, R, F }, + { SOCK_STREAM, PORT, o, LB, o, F, F }, + { SOCK_STREAM, LB, o, 0, o, F, F }, + { SOCK_STREAM, LB, o, ADDR, o, F, F }, + { SOCK_STREAM, LB, o, PORT, o, F, F }, + { SOCK_STREAM, LB, o, LB, o, R, F }, + /* + * ATM, expected result with wildcard first bind(2) is the same + * as the result for the specified first bind(2). Thus, the + * below block is copy-and-paste of the above with wild1 set. + */ + { SOCK_STREAM, 0, x, 0, o, F, F }, + { SOCK_STREAM, 0, x, ADDR, o, F, F }, + { SOCK_STREAM, 0, x, PORT, o, F, F }, + { SOCK_STREAM, 0, x, LB, o, F, F }, + { SOCK_STREAM, ADDR, x, 0, o, F, F }, + { SOCK_STREAM, ADDR, x, ADDR, o, F, F }, + { SOCK_STREAM, ADDR, x, PORT, o, F, F }, + { SOCK_STREAM, ADDR, x, LB, o, F, F }, + { SOCK_STREAM, PORT, x, 0, o, F, F }, + { SOCK_STREAM, PORT, x, ADDR, o, F, F }, + { SOCK_STREAM, PORT, x, PORT, o, R, F }, + { SOCK_STREAM, PORT, x, LB, o, F, F }, + { SOCK_STREAM, LB, x, 0, o, F, F }, + { SOCK_STREAM, LB, x, ADDR, o, F, F }, + { SOCK_STREAM, LB, x, PORT, o, F, F }, + { SOCK_STREAM, LB, x, LB, o, R, F }, + /* + * ATM, expected result for SOCK_DGRAM is the same as for + * SOCK_STREAM. Thus the below is copy-and-paste of the above. + */ + { SOCK_DGRAM, 0, o, 0, o, F, F }, + { SOCK_DGRAM, 0, o, ADDR, o, F, F }, + { SOCK_DGRAM, 0, o, PORT, o, F, F }, + { SOCK_DGRAM, 0, o, LB, o, F, F }, + { SOCK_DGRAM, ADDR, o, 0, o, F, F }, + { SOCK_DGRAM, ADDR, o, ADDR, o, F, F }, + { SOCK_DGRAM, ADDR, o, PORT, o, F, F }, + { SOCK_DGRAM, ADDR, o, LB, o, F, F }, + { SOCK_DGRAM, PORT, o, 0, o, F, F }, + { SOCK_DGRAM, PORT, o, ADDR, o, F, F }, + { SOCK_DGRAM, PORT, o, PORT, o, R, F }, + { SOCK_DGRAM, PORT, o, LB, o, F, F }, + { SOCK_DGRAM, LB, o, 0, o, F, F }, + { SOCK_DGRAM, LB, o, ADDR, o, F, F }, + { SOCK_DGRAM, LB, o, PORT, o, F, F }, + { SOCK_DGRAM, LB, o, LB, o, R, F }, + /* wild1 -> true */ + { SOCK_DGRAM, 0, x, 0, o, F, F }, + { SOCK_DGRAM, 0, x, ADDR, o, F, F }, + { SOCK_DGRAM, 0, x, PORT, o, F, F }, + { SOCK_DGRAM, 0, x, LB, o, F, F }, + { SOCK_DGRAM, ADDR, x, 0, o, F, F }, + { SOCK_DGRAM, ADDR, x, ADDR, o, F, F }, + { SOCK_DGRAM, ADDR, x, PORT, o, F, F }, + { SOCK_DGRAM, ADDR, x, LB, o, F, F }, + { SOCK_DGRAM, PORT, x, 0, o, F, F }, + { SOCK_DGRAM, PORT, x, ADDR, o, F, F }, + { SOCK_DGRAM, PORT, x, PORT, o, R, F }, + { SOCK_DGRAM, PORT, x, LB, o, F, F }, + { SOCK_DGRAM, LB, x, 0, o, F, F }, + { SOCK_DGRAM, LB, x, ADDR, o, F, F }, + { SOCK_DGRAM, LB, x, PORT, o, F, F }, + { SOCK_DGRAM, LB, x, LB, o, R, F }, +#undef F +#undef R +#undef x +#undef o +#undef ADDR +#undef PORT +#undef LB + }; + /* + * Expected results for IPv4 and IPv6 shall always be the same, so + * this dimension is implemented as a cycle rather than table entry. + */ + const union sockaddr_union { + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr sa; + } wild[] = { + { + .sin.sin_family = AF_INET, + .sin.sin_len = sizeof(struct sockaddr_in), + }, + { + .sin6.sin6_family = AF_INET6, + .sin6.sin6_len = sizeof(struct sockaddr_in6), + }, + }, loop[] = { + { + .sin.sin_family = AF_INET, + .sin.sin_len = sizeof(struct sockaddr_in), + .sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }, + { + .sin6.sin6_family = AF_INET6, + .sin6.sin6_len = sizeof(struct sockaddr_in6), + .sin6.sin6_addr = in6addr_loopback, + }, + }; + + _Static_assert(nitems(wild) == nitems(loop), "EDOOFUS"); + for (u_int af = 0; af < nitems(wild); af++) { + for (u_int i = 0; i < nitems(tests); i++) { + const struct sockaddr *sa; + union sockaddr_union su; + socklen_t slen; + enum bind_res res; + int s; + + s = socket(wild[af].sa.sa_family, tests[i].type, 0); + ATF_REQUIRE(s >= 0); + sa = tests[i].wild1 ? &wild[af].sa : &loop[af].sa; + slen = sa->sa_len; + ATF_REQUIRE(bind(s, sa, slen) == 0); + if (tests[i].opt1 != 0) + ATF_REQUIRE(setsockopt(s, SOL_SOCKET, + tests[i].opt1, &(int){1}, sizeof(int)) == + 0); + ATF_REQUIRE(getsockname(s, &su.sa, &slen) == 0); + + sa = &su.sa; + res = child_bind(tc, tests[i].type, sa, tests[i].opt2, + false); + ATF_REQUIRE_MSG(tests[i].priv_res == res, + "af %d test #%d (priv) failed", sa->sa_family, i); + + res = child_bind(tc, tests[i].type, sa, tests[i].opt2, + true); + ATF_REQUIRE_MSG(tests[i].unpriv_res == res, + "af %d test #%d (unpriv) failed", sa->sa_family, i); + + ATF_REQUIRE(close(s) == 0); + } + } } /*