The branch main has been updated by markj:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=7034563f8ef3acbe5bada582f0bb335fcba9dfb4

commit 7034563f8ef3acbe5bada582f0bb335fcba9dfb4
Author:     Mark Johnston <ma...@freebsd.org>
AuthorDate: 2025-02-06 14:16:04 +0000
Commit:     Mark Johnston <ma...@freebsd.org>
CommitDate: 2025-02-06 14:16:04 +0000

    tests: Add some FIB multibind test cases
    
    Reviewed by:    glebius
    MFC after:      2 weeks
    Sponsored by:   Klara, Inc.
    Sponsored by:   Stormshield
    Differential Revision:  https://reviews.freebsd.org/D48665
---
 tests/sys/netinet/Makefile              |   3 +-
 tests/sys/netinet/fibs_multibind_test.c | 754 ++++++++++++++++++++++++++++++++
 2 files changed, 756 insertions(+), 1 deletion(-)

diff --git a/tests/sys/netinet/Makefile b/tests/sys/netinet/Makefile
index 6faaf8ac1df1..279a58985816 100644
--- a/tests/sys/netinet/Makefile
+++ b/tests/sys/netinet/Makefile
@@ -5,7 +5,8 @@ BINDIR=         ${TESTSDIR}
 
 TESTS_SUBDIRS+=        libalias
 
-ATF_TESTS_C=   ip_reass_test \
+ATF_TESTS_C=   fibs_multibind_test \
+               ip_reass_test \
                ip6_v4mapped_test \
                so_reuseport_lb_test \
                socket_afinet \
diff --git a/tests/sys/netinet/fibs_multibind_test.c 
b/tests/sys/netinet/fibs_multibind_test.c
new file mode 100644
index 000000000000..c62e8abdf81b
--- /dev/null
+++ b/tests/sys/netinet/fibs_multibind_test.c
@@ -0,0 +1,754 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024-2025 Stormshield
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <atf-c.h>
+
+#define        MAKETEST_TCP(name)                      \
+ATF_TC_WITHOUT_HEAD(name ## _tcp);             \
+ATF_TC_BODY(name ## _tcp, tc)                  \
+{                                              \
+       name(PF_INET, SOCK_STREAM, tc);         \
+}                                              \
+ATF_TC_WITHOUT_HEAD(name ## _tcp6);            \
+ATF_TC_BODY(name ## _tcp6, tc)                 \
+{                                              \
+       name(PF_INET6, SOCK_STREAM, tc);        \
+}
+#define        MAKETEST_UDP(name)                      \
+ATF_TC_WITHOUT_HEAD(name ## _udp);             \
+ATF_TC_BODY(name ## _udp, tc)                  \
+{                                              \
+       name(PF_INET, SOCK_DGRAM, tc);          \
+}                                              \
+ATF_TC_WITHOUT_HEAD(name ## _udp6);            \
+ATF_TC_BODY(name ## _udp6, tc)                 \
+{                                              \
+       name(PF_INET6, SOCK_DGRAM, tc);         \
+}
+#define        MAKETEST_RAW(name)                      \
+ATF_TC(name ## _raw);                          \
+ATF_TC_HEAD(name ## _raw, tc)                  \
+{                                              \
+       atf_tc_set_md_var(tc, "require.user",   \
+           "root");                            \
+}                                              \
+ATF_TC_BODY(name ## _raw, tc)                  \
+{                                              \
+       name(PF_INET, SOCK_RAW, tc);            \
+}                                              \
+ATF_TC(name ## _raw6);                         \
+ATF_TC_HEAD(name ## _raw6, tc)                 \
+{                                              \
+       atf_tc_set_md_var(tc, "require.user",   \
+           "root");                            \
+}                                              \
+ATF_TC_BODY(name ## _raw6, tc)                 \
+{                                              \
+       name(PF_INET6, SOCK_RAW, tc);           \
+}
+
+#define        MAKETEST(name)                          \
+       MAKETEST_TCP(name)                      \
+       MAKETEST_UDP(name)
+
+#define        LISTTEST_TCP(name)                      \
+       ATF_TP_ADD_TC(tp, name ## _tcp);        \
+       ATF_TP_ADD_TC(tp, name ## _tcp6);
+#define        LISTTEST_UDP(name)                      \
+       ATF_TP_ADD_TC(tp, name ## _udp);        \
+       ATF_TP_ADD_TC(tp, name ## _udp6);
+#define        LISTTEST_RAW(name)                      \
+       ATF_TP_ADD_TC(tp, name ## _raw);        \
+       ATF_TP_ADD_TC(tp, name ## _raw6);
+#define        LISTTEST(name)                          \
+       LISTTEST_TCP(name)                      \
+       LISTTEST_UDP(name)
+
+static void
+checked_close(int s)
+{
+       int error;
+
+       error = close(s);
+       ATF_REQUIRE_MSG(error == 0, "close failed: %s", strerror(errno));
+}
+
+static int
+mksockp(int domain, int type, int fib, int proto)
+{
+       int error, s;
+
+       s = socket(domain, type, proto);
+       ATF_REQUIRE(s != -1);
+       error = setsockopt(s, SOL_SOCKET, SO_SETFIB, &fib, sizeof(fib));
+       ATF_REQUIRE_MSG(error == 0, "setsockopt failed: %s", strerror(errno));
+
+       return (s);
+}
+
+static int
+mksock(int domain, int type, int fib)
+{
+       return (mksockp(domain, type, fib, 0));
+}
+
+static void
+require_fibs_multibind(int socktype, int minfibs)
+{
+       const char *sysctl;
+       size_t sz;
+       int error, fibs, multibind;
+
+       fibs = 0;
+       sz = sizeof(fibs);
+       error = sysctlbyname("net.fibs", &fibs, &sz, NULL, 0);
+       ATF_REQUIRE_MSG(error == 0, "sysctlbyname failed: %s", strerror(errno));
+       ATF_REQUIRE_MSG(fibs >= 1, "strange FIB count %d", fibs);
+       if (fibs == 1)
+               atf_tc_skip("multiple FIBs not enabled");
+       if (fibs < minfibs)
+               atf_tc_skip("not enough FIBs, need %d", minfibs);
+
+       switch (socktype) {
+       case SOCK_STREAM:
+               sysctl = "net.inet.tcp.bind_all_fibs";
+               break;
+       case SOCK_DGRAM:
+               sysctl = "net.inet.udp.bind_all_fibs";
+               break;
+       case SOCK_RAW:
+               sysctl = "net.inet.raw.bind_all_fibs";
+               break;
+       default:
+               atf_tc_fail("unknown socket type %d", socktype);
+               break;
+       }
+
+       multibind = -1;
+       sz = sizeof(multibind);
+       error = sysctlbyname(sysctl, &multibind, &sz, NULL, 0);
+       ATF_REQUIRE_MSG(error == 0, "sysctlbyname failed: %s", strerror(errno));
+       if (multibind != 0)
+               atf_tc_skip("FIB multibind not configured (%s)", sysctl);
+}
+
+/*
+ * Make sure that different users can't bind to the same port from different
+ * FIBs.
+ */
+static void
+multibind_different_user(int domain, int type, const atf_tc_t *tc)
+{
+       struct sockaddr_storage ss;
+       struct passwd *passwd;
+       const char *user;
+       socklen_t sslen;
+       int error, s[2];
+
+       if (geteuid() != 0)
+               atf_tc_skip("need root privileges");
+       if (!atf_tc_has_config_var(tc, "unprivileged_user"))
+               atf_tc_skip("unprivileged_user not set");
+
+       ATF_REQUIRE(domain == PF_INET || domain == PF_INET6);
+       sslen = domain == PF_INET ? sizeof(struct sockaddr_in) :
+           sizeof(struct sockaddr_in6);
+
+       require_fibs_multibind(type, 2);
+
+       s[0] = mksock(domain, type, 0);
+
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = domain;
+       ss.ss_len = sslen;
+       error = bind(s[0], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+
+       error = getsockname(s[0], (struct sockaddr *)&ss, &sslen);
+       ATF_REQUIRE_MSG(error == 0, "getsockname failed: %s", strerror(errno));
+
+       /*
+        * Create a second socket in a different FIB, and bind it to the same
+        * address/port tuple.  This should succeed if done as the same user as
+        * the first socket, and should fail otherwise.
+        */
+       s[1] = mksock(domain, type, 1);
+       error = bind(s[1], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+       ATF_REQUIRE_MSG(close(s[1]) == 0, "close failed: %s", strerror(errno));
+
+       user = atf_tc_get_config_var(tc, "unprivileged_user");
+       passwd = getpwnam(user);
+       ATF_REQUIRE(passwd != NULL);
+       error = seteuid(passwd->pw_uid);
+       ATF_REQUIRE_MSG(error == 0, "seteuid failed: %s", strerror(errno));
+
+       /* Repeat the bind as a different user. */
+       s[1] = mksock(domain, type, 1);
+       error = bind(s[1], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_ERRNO(EADDRINUSE, error == -1);
+       ATF_REQUIRE_MSG(close(s[1]) == 0, "close failed: %s", strerror(errno));
+}
+MAKETEST(multibind_different_user);
+
+/*
+ * Verify that a listening socket only accepts connections originating from the
+ * same FIB.
+ */
+static void
+per_fib_listening_socket(int domain, int type, const atf_tc_t *tc __unused)
+{
+       struct sockaddr_storage ss;
+       socklen_t sslen;
+       int cs1, cs2, error, fib1, fib2, ls1, ls2, ns;
+
+       ATF_REQUIRE(type == SOCK_STREAM);
+       ATF_REQUIRE(domain == PF_INET || domain == PF_INET6);
+       require_fibs_multibind(type, 2);
+
+       fib1 = 0;
+       fib2 = 1;
+
+       ls1 = mksock(domain, type, fib1);
+       ls2 = mksock(domain, type, fib2);
+
+       sslen = domain == PF_INET ? sizeof(struct sockaddr_in) :
+           sizeof(struct sockaddr_in6);
+
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = domain;
+       ss.ss_len = sslen;
+       error = bind(ls1, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+
+       error = getsockname(ls1, (struct sockaddr *)&ss, &sslen);
+       ATF_REQUIRE_MSG(error == 0, "getsockname failed: %s", strerror(errno));
+
+       error = listen(ls1, 5);
+       ATF_REQUIRE_MSG(error == 0, "listen failed: %s", strerror(errno));
+
+       cs1 = mksock(domain, type, fib1);
+       cs2 = mksock(domain, type, fib2);
+
+       /*
+        * Make sure we can connect from the same FIB.
+        */
+       error = connect(cs1, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "connect failed: %s", strerror(errno));
+       ns = accept(ls1, NULL, NULL);
+       ATF_REQUIRE_MSG(ns != -1, "accept failed: %s", strerror(errno));
+       checked_close(ns);
+       checked_close(cs1);
+       cs1 = mksock(domain, type, fib1);
+
+       /*
+        * ... but not from a different FIB.
+        */
+       error = connect(cs2, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == -1, "connect succeeded unexpectedly");
+       ATF_REQUIRE_MSG(errno == ECONNREFUSED, "unexpected error %d", errno);
+       checked_close(cs2);
+       cs2 = mksock(domain, type, fib2);
+
+       /*
+        * ... but if there are multiple listening sockets, we always connect to
+        * the same FIB.
+        */
+       error = bind(ls2, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+       error = listen(ls2, 5);
+       ATF_REQUIRE_MSG(error == 0, "listen failed: %s", strerror(errno));
+
+       for (int i = 0; i < 10; i++) {
+               error = connect(cs1, (struct sockaddr *)&ss, sslen);
+               ATF_REQUIRE_MSG(error == 0, "connect failed: %s",
+                   strerror(errno));
+               ns = accept(ls1, NULL, NULL);
+               ATF_REQUIRE_MSG(ns != -1, "accept failed: %s", strerror(errno));
+
+               checked_close(ns);
+               checked_close(cs1);
+               cs1 = mksock(domain, type, fib1);
+       }
+       for (int i = 0; i < 10; i++) {
+               error = connect(cs2, (struct sockaddr *)&ss, sslen);
+               ATF_REQUIRE_MSG(error == 0, "connect failed: %s",
+                   strerror(errno));
+               ns = accept(ls2, NULL, NULL);
+               ATF_REQUIRE_MSG(ns != -1, "accept failed: %s", strerror(errno));
+
+               checked_close(ns);
+               checked_close(cs2);
+               cs2 = mksock(domain, type, fib2);
+       }
+
+       /*
+        * ... and if we close one of the listening sockets, we're back to only
+        * being able to connect from the same FIB.
+        */
+       checked_close(ls1);
+       error = connect(cs1, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == -1, "connect succeeded unexpectedly");
+       ATF_REQUIRE_MSG(errno == ECONNREFUSED, "unexpected error %d", errno);
+       checked_close(cs1);
+
+       error = connect(cs2, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "connect failed: %s", strerror(errno));
+       ns = accept(ls2, NULL, NULL);
+       ATF_REQUIRE_MSG(ns != -1, "accept failed: %s", strerror(errno));
+       checked_close(ns);
+       checked_close(cs2);
+       checked_close(ls2);
+}
+MAKETEST_TCP(per_fib_listening_socket);
+
+/*
+ * Verify that a bound datagram socket only accepts data from the same FIB.
+ */
+static void
+per_fib_dgram_socket(int domain, int type, const atf_tc_t *tc __unused)
+{
+       struct sockaddr_storage ss;
+       struct sockaddr_in6 *sin6p;
+       socklen_t sslen;
+       ssize_t n;
+       int error, cs1, cs2, fib1, fib2, ss1, ss2;
+       char b;
+
+       ATF_REQUIRE(type == SOCK_DGRAM);
+       ATF_REQUIRE(domain == PF_INET || domain == PF_INET6);
+       require_fibs_multibind(type, 2);
+
+       fib1 = 0;
+       fib2 = 1;
+
+       cs1 = mksock(domain, type, fib1);
+       cs2 = mksock(domain, type, fib2);
+
+       ss1 = mksock(domain, type, fib1);
+       ss2 = mksock(domain, type, fib2);
+
+       sslen = domain == PF_INET ? sizeof(struct sockaddr_in) :
+           sizeof(struct sockaddr_in6);
+
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = domain;
+       ss.ss_len = sslen;
+       error = bind(ss1, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+
+       error = getsockname(ss1, (struct sockaddr *)&ss, &sslen);
+       ATF_REQUIRE_MSG(error == 0, "getsockname failed: %s", strerror(errno));
+
+       if (domain == PF_INET6) {
+               sin6p = (struct sockaddr_in6 *)&ss;
+               sin6p->sin6_addr = in6addr_loopback;
+       }
+
+       /* If we send a byte from cs1, it should be recieved by ss1. */
+       b = 42;
+       n = sendto(cs1, &b, sizeof(b), 0, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(n == 1, "sendto failed: %s", strerror(errno));
+       n = recv(ss1, &b, sizeof(b), 0);
+       ATF_REQUIRE(n == 1);
+       ATF_REQUIRE(b == 42);
+
+       /* If we send a byte from cs2, it should not be received by ss1. */
+       b = 42;
+       n = sendto(cs2, &b, sizeof(b), 0, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(n == 1, "sendto failed: %s", strerror(errno));
+       usleep(10000);
+       n = recv(ss1, &b, sizeof(b), MSG_DONTWAIT);
+       ATF_REQUIRE_ERRNO(EWOULDBLOCK, n == -1);
+
+       error = bind(ss2, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+
+       /* Repeat now that ss2 is bound. */
+       b = 42;
+       n = sendto(cs1, &b, sizeof(b), 0, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(n == 1, "sendto failed: %s", strerror(errno));
+       n = recv(ss1, &b, sizeof(b), 0);
+       ATF_REQUIRE(n == 1);
+       ATF_REQUIRE(b == 42);
+
+       b = 42;
+       n = sendto(cs2, &b, sizeof(b), 0, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(n == 1, "sendto failed: %s", strerror(errno));
+       n = recv(ss2, &b, sizeof(b), 0);
+       ATF_REQUIRE(n == 1);
+       ATF_REQUIRE(b == 42);
+
+       checked_close(ss1);
+       checked_close(ss2);
+       checked_close(cs1);
+       checked_close(cs2);
+}
+MAKETEST_UDP(per_fib_dgram_socket);
+
+static size_t
+ping(int s, const struct sockaddr *sa, socklen_t salen)
+{
+       struct {
+               struct icmphdr icmp;
+               char data[64];
+       } icmp;
+       ssize_t n;
+
+       memset(&icmp, 0, sizeof(icmp));
+       icmp.icmp.icmp_type = ICMP_ECHO;
+       icmp.icmp.icmp_code = 0;
+       icmp.icmp.icmp_cksum = htons(~(ICMP_ECHO << 8));
+       n = sendto(s, &icmp, sizeof(icmp), 0, sa, salen);
+       ATF_REQUIRE_MSG(n == (ssize_t)sizeof(icmp), "sendto failed: %s",
+           strerror(errno));
+
+       return (sizeof(icmp) + sizeof(struct ip));
+}
+
+static size_t
+ping6(int s, const struct sockaddr *sa, socklen_t salen)
+{
+       struct {
+               struct icmp6_hdr icmp6;
+               char data[64];
+       } icmp6;
+       ssize_t n;
+
+       memset(&icmp6, 0, sizeof(icmp6));
+       icmp6.icmp6.icmp6_type = ICMP6_ECHO_REQUEST;
+       icmp6.icmp6.icmp6_code = 0;
+       icmp6.icmp6.icmp6_cksum = htons(~(ICMP6_ECHO_REQUEST << 8));
+       n = sendto(s, &icmp6, sizeof(icmp6), 0, sa, salen);
+       ATF_REQUIRE_MSG(n == (ssize_t)sizeof(icmp6), "sendto failed: %s",
+           strerror(errno));
+
+       return (sizeof(icmp6));
+}
+
+static void
+per_fib_raw_socket(int domain, int type, const atf_tc_t *tc __unused)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       ssize_t n;
+       size_t sz;
+       int error, cs, s[2], proto;
+       uint8_t b[256];
+
+       ATF_REQUIRE(type == SOCK_RAW);
+       ATF_REQUIRE(domain == PF_INET || domain == PF_INET6);
+       require_fibs_multibind(type, 2);
+
+       proto = domain == PF_INET ? IPPROTO_ICMP : IPPROTO_ICMPV6;
+       s[0] = mksockp(domain, type, 0, proto);
+       s[1] = mksockp(domain, type, 1, proto);
+
+       if (domain == PF_INET) {
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_family = domain;
+               sin.sin_len = sizeof(sin);
+               sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+               error = bind(s[0], (struct sockaddr *)&sin, sizeof(sin));
+       } else /* if (domain == PF_INET6) */ {
+               memset(&sin6, 0, sizeof(sin6));
+               sin6.sin6_family = domain;
+               sin6.sin6_len = sizeof(sin6);
+               sin6.sin6_addr = in6addr_loopback;
+               error = bind(s[0], (struct sockaddr *)&sin6, sizeof(sin6));
+       }
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+
+       for (int i = 0; i < 2; i++) {
+               cs = mksockp(domain, type, i, proto);
+               if (domain == PF_INET) {
+                       sz = ping(cs, (struct sockaddr *)&sin, sizeof(sin));
+               } else /* if (domain == PF_INET6) */ {
+                       sz = ping6(cs, (struct sockaddr *)&sin6, sizeof(sin6));
+               }
+               n = recv(s[i], b, sizeof(b), 0);
+               ATF_REQUIRE_MSG(n > 0, "recv failed: %s", strerror(errno));
+               ATF_REQUIRE_MSG(n == (ssize_t)sz,
+                   "short packet received: %zd", n);
+
+               if (domain == PF_INET6) {
+                       /* Get the echo reply as well. */
+                       n = recv(s[i], b, sizeof(b), 0);
+                       ATF_REQUIRE_MSG(n > 0,
+                           "recv failed: %s", strerror(errno));
+                       ATF_REQUIRE_MSG(n == (ssize_t)sz,
+                           "short packet received: %zd", n);
+               }
+
+               /* Make sure that the other socket didn't receive anything. */
+               n = recv(s[1 - i], b, sizeof(b), MSG_DONTWAIT);
+               printf("n = %zd i = %d\n", n, i);
+               ATF_REQUIRE_ERRNO(EWOULDBLOCK, n == -1);
+
+               checked_close(cs);
+       }
+
+       checked_close(s[0]);
+       checked_close(s[1]);
+}
+MAKETEST_RAW(per_fib_raw_socket);
+
+/*
+ * Create a pair of load-balancing listening socket groups, one in each FIB, 
and
+ * make sure that connections to the group are only load-balanced within the
+ * same FIB.
+ */
+static void
+multibind_lbgroup_stream(int domain, int type, const atf_tc_t *tc __unused)
+{
+       struct sockaddr_storage ss;
+       socklen_t sslen;
+       int error, as, cs, s[3];
+
+       ATF_REQUIRE(type == SOCK_STREAM);
+       ATF_REQUIRE(domain == PF_INET || domain == PF_INET6);
+       require_fibs_multibind(type, 2);
+
+       s[0] = mksock(domain, type, 0);
+       ATF_REQUIRE(setsockopt(s[0], SOL_SOCKET, SO_REUSEPORT_LB, &(int){1},
+           sizeof(int)) == 0);
+       ATF_REQUIRE(fcntl(s[0], F_SETFL, O_NONBLOCK) == 0);
+       s[1] = mksock(domain, type, 0);
+       ATF_REQUIRE(setsockopt(s[1], SOL_SOCKET, SO_REUSEPORT_LB, &(int){1},
+           sizeof(int)) == 0);
+       ATF_REQUIRE(fcntl(s[1], F_SETFL, O_NONBLOCK) == 0);
+       s[2] = mksock(domain, type, 1);
+       ATF_REQUIRE(setsockopt(s[2], SOL_SOCKET, SO_REUSEPORT_LB, &(int){1},
+           sizeof(int)) == 0);
+
+       sslen = domain == PF_INET ? sizeof(struct sockaddr_in) :
+           sizeof(struct sockaddr_in6);
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = domain;
+       ss.ss_len = sslen;
+       error = bind(s[0], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+       error = listen(s[0], 5);
+       ATF_REQUIRE_MSG(error == 0, "listen failed: %s", strerror(errno));
+       error = getsockname(s[0], (struct sockaddr *)&ss, &sslen);
+       ATF_REQUIRE_MSG(error == 0, "getsockname failed: %s", strerror(errno));
+
+       error = bind(s[1], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+       error = listen(s[1], 5);
+       ATF_REQUIRE_MSG(error == 0, "listen failed: %s", strerror(errno));
+
+       error = bind(s[2], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+       error = listen(s[2], 5);
+       ATF_REQUIRE_MSG(error == 0, "listen failed: %s", strerror(errno));
+
+       /*
+        * Initiate connections from FIB 0, make sure they go to s[0] or s[1].
+        */
+       for (int count = 0; count < 100; count++) {
+               cs = mksock(domain, type, 0);
+               error = connect(cs, (struct sockaddr *)&ss, sslen);
+               ATF_REQUIRE_MSG(error == 0, "connect failed: %s",
+                   strerror(errno));
+
+               do {
+                       as = accept(s[0], NULL, NULL);
+                       if (as == -1) {
+                               ATF_REQUIRE_MSG(errno == EWOULDBLOCK,
+                                   "accept failed: %s", strerror(errno));
+                               as = accept(s[1], NULL, NULL);
+                               if (as == -1) {
+                                       ATF_REQUIRE_MSG(errno == EWOULDBLOCK,
+                                           "accept failed: %s",
+                                           strerror(errno));
+                               }
+                       }
+               } while (as == -1);
+               checked_close(as);
+               checked_close(cs);
+       }
+
+       /*
+        * Initiate connections from FIB 1, make sure they go to s[2].
+        */
+       for (int count = 0; count < 100; count++) {
+               cs = mksock(domain, type, 1);
+               error = connect(cs, (struct sockaddr *)&ss, sslen);
+               ATF_REQUIRE_MSG(error == 0, "connect failed: %s",
+                   strerror(errno));
+
+               as = accept(s[2], NULL, NULL);
+               ATF_REQUIRE_MSG(as != -1, "accept failed: %s", strerror(errno));
+               checked_close(as);
+               checked_close(cs);
+       }
+
+       checked_close(s[0]);
+       checked_close(s[1]);
+       checked_close(s[2]);
+}
+MAKETEST_TCP(multibind_lbgroup_stream);
+
+static void
+multibind_lbgroup_dgram(int domain, int type, const atf_tc_t *tc __unused)
+{
+       struct sockaddr_storage ss;
+       struct sockaddr_in6 *sin6p;
+       socklen_t sslen;
+       ssize_t n;
+       int error, cs, s[3];
+       char b;
+
+       ATF_REQUIRE(type == SOCK_DGRAM);
+       ATF_REQUIRE(domain == PF_INET || domain == PF_INET6);
+       require_fibs_multibind(type, 2);
+
+       s[0] = mksock(domain, type, 0);
+       ATF_REQUIRE(setsockopt(s[0], SOL_SOCKET, SO_REUSEPORT_LB, &(int){1},
+           sizeof(int)) == 0);
+       s[1] = mksock(domain, type, 0);
+       ATF_REQUIRE(setsockopt(s[1], SOL_SOCKET, SO_REUSEPORT_LB, &(int){1},
+           sizeof(int)) == 0);
+       s[2] = mksock(domain, type, 1);
+       ATF_REQUIRE(setsockopt(s[2], SOL_SOCKET, SO_REUSEPORT_LB, &(int){1},
+           sizeof(int)) == 0);
+
+       sslen = domain == PF_INET ? sizeof(struct sockaddr_in) :
+           sizeof(struct sockaddr_in6);
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = domain;
+       ss.ss_len = sslen;
+       error = bind(s[0], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+       error = getsockname(s[0], (struct sockaddr *)&ss, &sslen);
+       ATF_REQUIRE_MSG(error == 0, "getsockname failed: %s", strerror(errno));
+
+       error = bind(s[1], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+       error = bind(s[2], (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+
+       if (domain == PF_INET6) {
+               sin6p = (struct sockaddr_in6 *)&ss;
+               sin6p->sin6_addr = in6addr_loopback;
+       }
+
+       /*
+        * Send a packet from FIB 0, make sure it goes to s[0] or s[1].
+        */
+       cs = mksock(domain, type, 0);
+       for (int count = 0; count < 100; count++) {
+               int bytes, rs;
+
+               b = 42;
+               n = sendto(cs, &b, sizeof(b), 0, (struct sockaddr *)&ss, sslen);
+               ATF_REQUIRE_MSG(n == 1, "sendto failed: %s", strerror(errno));
+               usleep(1000);
+
+               error = ioctl(s[0], FIONREAD, &bytes);
+               ATF_REQUIRE_MSG(error == 0, "ioctl failed: %s",
+                   strerror(errno));
+               if (bytes == 0) {
+                       error = ioctl(s[1], FIONREAD, &bytes);
+                       ATF_REQUIRE_MSG(error == 0, "ioctl failed: %s",
+                           strerror(errno));
+                       rs = s[1];
+               } else {
+                       rs = s[0];
+               }
+               n = recv(rs, &b, sizeof(b), 0);
+               ATF_REQUIRE(n == 1);
+               ATF_REQUIRE(b == 42);
+               ATF_REQUIRE(bytes == 1);
+       }
+       checked_close(cs);
+
+       /*
+        * Send a packet from FIB 1, make sure it goes to s[2].
+        */
+       cs = mksock(domain, type, 1);
+       for (int count = 0; count < 100; count++) {
+               b = 42;
+               n = sendto(cs, &b, sizeof(b), 0, (struct sockaddr *)&ss, sslen);
+               ATF_REQUIRE_MSG(n == 1, "sendto failed: %s", strerror(errno));
+               usleep(1000);
+
+               n = recv(s[2], &b, sizeof(b), 0);
+               ATF_REQUIRE(n == 1);
+               ATF_REQUIRE(b == 42);
+       }
+       checked_close(cs);
+
+       checked_close(s[0]);
+       checked_close(s[1]);
+       checked_close(s[2]);
+}
+MAKETEST_UDP(multibind_lbgroup_dgram);
+
+/*
+ * Make sure that we can't change the FIB of a bound socket.
+ */
+static void
+no_setfib_after_bind(int domain, int type, const atf_tc_t *tc __unused)
+{
+       struct sockaddr_storage ss;
+       socklen_t sslen;
+       int error, s;
+
+       ATF_REQUIRE(domain == PF_INET || domain == PF_INET6);
+       require_fibs_multibind(type, 2);
+
+       s = mksock(domain, type, 0);
+
+       sslen = domain == PF_INET ? sizeof(struct sockaddr_in) :
+           sizeof(struct sockaddr_in6);
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = domain;
+       ss.ss_len = sslen;
+       error = bind(s, (struct sockaddr *)&ss, sslen);
+       ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+
+       error = setsockopt(s, SOL_SOCKET, SO_SETFIB, &(int){1}, sizeof(int));
+       ATF_REQUIRE_ERRNO(EISCONN, error == -1);
+
+       /* It's ok to set the FIB number to its current value. */
+       error = setsockopt(s, SOL_SOCKET, SO_SETFIB, &(int){0}, sizeof(int));
+       ATF_REQUIRE_MSG(error == 0, "setsockopt failed: %s", strerror(errno));
+
+       checked_close(s);
+}
+MAKETEST(no_setfib_after_bind);
+
+ATF_TP_ADD_TCS(tp)
+{
+       LISTTEST(multibind_different_user);
+       LISTTEST_TCP(per_fib_listening_socket);
+       LISTTEST_UDP(per_fib_dgram_socket);
+       LISTTEST_RAW(per_fib_raw_socket);
+       LISTTEST_TCP(multibind_lbgroup_stream);
+       LISTTEST_UDP(multibind_lbgroup_dgram);
+       LISTTEST(no_setfib_after_bind);
+
+       return (atf_no_error());
+}

Reply via email to