aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/netinet/so_reuseport_lb_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys/netinet/so_reuseport_lb_test.c')
-rw-r--r--tests/sys/netinet/so_reuseport_lb_test.c322
1 files changed, 322 insertions, 0 deletions
diff --git a/tests/sys/netinet/so_reuseport_lb_test.c b/tests/sys/netinet/so_reuseport_lb_test.c
index 64fe0b53618d..fa9d6e425884 100644
--- a/tests/sys/netinet/so_reuseport_lb_test.c
+++ b/tests/sys/netinet/so_reuseport_lb_test.c
@@ -28,12 +28,16 @@
*/
#include <sys/param.h>
+#include <sys/event.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#include <netinet/tcp.h>
#include <err.h>
#include <errno.h>
+#include <pthread.h>
+#include <stdatomic.h>
#include <stdlib.h>
#include <unistd.h>
@@ -235,10 +239,328 @@ ATF_TC_BODY(basic_ipv6, tc)
}
}
+struct concurrent_add_softc {
+ struct sockaddr_storage ss;
+ int socks[128];
+ int kq;
+};
+
+static void *
+listener(void *arg)
+{
+ for (struct concurrent_add_softc *sc = arg;;) {
+ struct kevent kev;
+ ssize_t n;
+ int error, count, cs, s;
+ uint8_t b;
+
+ count = kevent(sc->kq, NULL, 0, &kev, 1, NULL);
+ ATF_REQUIRE_MSG(count == 1,
+ "kevent() failed: %s", strerror(errno));
+
+ s = (int)kev.ident;
+ cs = accept(s, NULL, NULL);
+ ATF_REQUIRE_MSG(cs >= 0,
+ "accept() failed: %s", strerror(errno));
+
+ b = 'M';
+ n = write(cs, &b, sizeof(b));
+ ATF_REQUIRE_MSG(n >= 0, "write() failed: %s", strerror(errno));
+ ATF_REQUIRE(n == 1);
+
+ error = close(cs);
+ ATF_REQUIRE_MSG(error == 0 || errno == ECONNRESET,
+ "close() failed: %s", strerror(errno));
+ }
+}
+
+static void *
+connector(void *arg)
+{
+ for (struct concurrent_add_softc *sc = arg;;) {
+ ssize_t n;
+ int error, s;
+ uint8_t b;
+
+ s = socket(sc->ss.ss_family, SOCK_STREAM, 0);
+ ATF_REQUIRE_MSG(s >= 0, "socket() failed: %s", strerror(errno));
+
+ error = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (int[]){1},
+ sizeof(int));
+
+ error = connect(s, (struct sockaddr *)&sc->ss, sc->ss.ss_len);
+ ATF_REQUIRE_MSG(error == 0, "connect() failed: %s",
+ strerror(errno));
+
+ n = read(s, &b, sizeof(b));
+ ATF_REQUIRE_MSG(n >= 0, "read() failed: %s",
+ strerror(errno));
+ ATF_REQUIRE(n == 1);
+ ATF_REQUIRE(b == 'M');
+ error = close(s);
+ ATF_REQUIRE_MSG(error == 0,
+ "close() failed: %s", strerror(errno));
+ }
+}
+
+/*
+ * Run three threads. One accepts connections from listening sockets on a
+ * kqueue, while the other makes connections. The third thread slowly adds
+ * sockets to the LB group. This is meant to help flush out race conditions.
+ */
+ATF_TC_WITHOUT_HEAD(concurrent_add);
+ATF_TC_BODY(concurrent_add, tc)
+{
+ struct concurrent_add_softc sc;
+ struct sockaddr_in *sin;
+ pthread_t threads[4];
+ int error;
+
+ sc.kq = kqueue();
+ ATF_REQUIRE_MSG(sc.kq >= 0, "kqueue() failed: %s", strerror(errno));
+
+ error = pthread_create(&threads[0], NULL, listener, &sc);
+ ATF_REQUIRE_MSG(error == 0, "pthread_create() failed: %s",
+ strerror(error));
+
+ sin = (struct sockaddr_in *)&sc.ss;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(0);
+ sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ for (size_t i = 0; i < nitems(sc.socks); i++) {
+ struct kevent kev;
+ int s;
+
+ sc.socks[i] = s = socket(AF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE_MSG(s >= 0, "socket() failed: %s", strerror(errno));
+
+ error = setsockopt(s, SOL_SOCKET, SO_REUSEPORT_LB, (int[]){1},
+ sizeof(int));
+ ATF_REQUIRE_MSG(error == 0,
+ "setsockopt(SO_REUSEPORT_LB) failed: %s", strerror(errno));
+
+ error = bind(s, (struct sockaddr *)sin, sizeof(*sin));
+ ATF_REQUIRE_MSG(error == 0, "bind() failed: %s",
+ strerror(errno));
+
+ error = listen(s, 5);
+ ATF_REQUIRE_MSG(error == 0, "listen() failed: %s",
+ strerror(errno));
+
+ EV_SET(&kev, s, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
+ error = kevent(sc.kq, &kev, 1, NULL, 0, NULL);
+ ATF_REQUIRE_MSG(error == 0, "kevent() failed: %s",
+ strerror(errno));
+
+ if (i == 0) {
+ socklen_t slen = sizeof(sc.ss);
+
+ error = getsockname(sc.socks[i],
+ (struct sockaddr *)&sc.ss, &slen);
+ ATF_REQUIRE_MSG(error == 0, "getsockname() failed: %s",
+ strerror(errno));
+ ATF_REQUIRE(sc.ss.ss_family == AF_INET);
+
+ for (size_t j = 1; j < nitems(threads); j++) {
+ error = pthread_create(&threads[j], NULL,
+ connector, &sc);
+ ATF_REQUIRE_MSG(error == 0,
+ "pthread_create() failed: %s",
+ strerror(error));
+ }
+ }
+
+ usleep(20000);
+ }
+}
+
+/*
+ * Try calling listen(2) twice on a socket with SO_REUSEPORT_LB set.
+ */
+ATF_TC_WITHOUT_HEAD(double_listen_ipv4);
+ATF_TC_BODY(double_listen_ipv4, tc)
+{
+ struct sockaddr_in sin;
+ int error, s;
+
+ s = lb_listen_socket(PF_INET, 0);
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(0);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ error = bind(s, (struct sockaddr *)&sin, sizeof(sin));
+ ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno));
+
+ error = listen(s, 1);
+ ATF_REQUIRE_MSG(error == 0, "listen() failed: %s", strerror(errno));
+ error = listen(s, 2);
+ ATF_REQUIRE_MSG(error == 0, "listen() failed: %s", strerror(errno));
+
+ error = close(s);
+ ATF_REQUIRE_MSG(error == 0, "close() failed: %s", strerror(errno));
+}
+
+/*
+ * Try calling listen(2) twice on a socket with SO_REUSEPORT_LB set.
+ */
+ATF_TC_WITHOUT_HEAD(double_listen_ipv6);
+ATF_TC_BODY(double_listen_ipv6, tc)
+{
+ struct sockaddr_in6 sin6;
+ int error, s;
+
+ s = lb_listen_socket(PF_INET6, 0);
+
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_len = sizeof(sin6);
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(0);
+ sin6.sin6_addr = in6addr_loopback;
+ error = bind(s, (struct sockaddr *)&sin6, sizeof(sin6));
+ ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno));
+
+ error = listen(s, 1);
+ ATF_REQUIRE_MSG(error == 0, "listen() failed: %s", strerror(errno));
+ error = listen(s, 2);
+ ATF_REQUIRE_MSG(error == 0, "listen() failed: %s", strerror(errno));
+
+ error = close(s);
+ ATF_REQUIRE_MSG(error == 0, "close() failed: %s", strerror(errno));
+}
+
+/*
+ * Try binding many sockets to the same lbgroup without calling listen(2) on
+ * them.
+ */
+ATF_TC_WITHOUT_HEAD(bind_without_listen);
+ATF_TC_BODY(bind_without_listen, tc)
+{
+ const int nsockets = 100;
+ struct sockaddr_in sin;
+ socklen_t socklen;
+ int error, s, s2[nsockets];
+
+ s = lb_listen_socket(PF_INET, 0);
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(0);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ error = bind(s, (struct sockaddr *)&sin, sizeof(sin));
+ ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno));
+
+ socklen = sizeof(sin);
+ error = getsockname(s, (struct sockaddr *)&sin, &socklen);
+ ATF_REQUIRE_MSG(error == 0, "getsockname() failed: %s",
+ strerror(errno));
+
+ for (int i = 0; i < nsockets; i++) {
+ s2[i] = lb_listen_socket(PF_INET, 0);
+ error = bind(s2[i], (struct sockaddr *)&sin, sizeof(sin));
+ ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno));
+ }
+ for (int i = 0; i < nsockets; i++) {
+ error = listen(s2[i], 1);
+ ATF_REQUIRE_MSG(error == 0, "listen() failed: %s", strerror(errno));
+ }
+ for (int i = 0; i < nsockets; i++) {
+ error = close(s2[i]);
+ ATF_REQUIRE_MSG(error == 0, "close() failed: %s", strerror(errno));
+ }
+
+ error = close(s);
+ ATF_REQUIRE_MSG(error == 0, "close() failed: %s", strerror(errno));
+}
+
+/*
+ * Check that SO_REUSEPORT_LB doesn't mess with connect(2).
+ * Two sockets:
+ * 1) auxiliary peer socket 'p', where we connect to
+ * 2) test socket 's', that sets SO_REUSEPORT_LB and then connect(2)s to 'p'
+ */
+ATF_TC_WITHOUT_HEAD(connect_not_bound);
+ATF_TC_BODY(connect_not_bound, tc)
+{
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_len = sizeof(sin),
+ .sin_addr = { htonl(INADDR_LOOPBACK) },
+ };
+ socklen_t slen = sizeof(struct sockaddr_in);
+ int p, s, rv;
+
+ ATF_REQUIRE((p = socket(PF_INET, SOCK_STREAM, 0)) > 0);
+ ATF_REQUIRE(bind(p, (struct sockaddr *)&sin, sizeof(sin)) == 0);
+ ATF_REQUIRE(listen(p, 1) == 0);
+ ATF_REQUIRE(getsockname(p, (struct sockaddr *)&sin, &slen) == 0);
+
+ s = lb_listen_socket(PF_INET, 0);
+ rv = connect(s, (struct sockaddr *)&sin, sizeof(sin));
+ ATF_REQUIRE_MSG(rv == -1 && errno == EOPNOTSUPP,
+ "Expected EOPNOTSUPP on connect(2) not met. Got %d, errno %d",
+ rv, errno);
+ rv = sendto(s, "test", 4, 0, (struct sockaddr *)&sin,
+ sizeof(sin));
+ ATF_REQUIRE_MSG(rv == -1 && errno == EOPNOTSUPP,
+ "Expected EOPNOTSUPP on sendto(2) not met. Got %d, errno %d",
+ rv, errno);
+
+ close(p);
+ close(s);
+}
+
+/*
+ * Same as above, but we also bind(2) between setsockopt(2) of SO_REUSEPORT_LB
+ * and the connect(2).
+ */
+ATF_TC_WITHOUT_HEAD(connect_bound);
+ATF_TC_BODY(connect_bound, tc)
+{
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_len = sizeof(sin),
+ .sin_addr = { htonl(INADDR_LOOPBACK) },
+ };
+ socklen_t slen = sizeof(struct sockaddr_in);
+ int p, s, rv;
+
+ ATF_REQUIRE((p = socket(PF_INET, SOCK_STREAM, 0)) > 0);
+ ATF_REQUIRE(bind(p, (struct sockaddr *)&sin, sizeof(sin)) == 0);
+ ATF_REQUIRE(listen(p, 1) == 0);
+
+ s = lb_listen_socket(PF_INET, 0);
+ ATF_REQUIRE(bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0);
+ ATF_REQUIRE(getsockname(p, (struct sockaddr *)&sin, &slen) == 0);
+ rv = connect(s, (struct sockaddr *)&sin, sizeof(sin));
+ ATF_REQUIRE_MSG(rv == -1 && errno == EOPNOTSUPP,
+ "Expected EOPNOTSUPP on connect(2) not met. Got %d, errno %d",
+ rv, errno);
+ rv = sendto(s, "test", 4, 0, (struct sockaddr *)&sin,
+ sizeof(sin));
+ ATF_REQUIRE_MSG(rv == -1 && errno == EOPNOTSUPP,
+ "Expected EOPNOTSUPP on sendto(2) not met. Got %d, errno %d",
+ rv, errno);
+
+ close(p);
+ close(s);
+}
+
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, basic_ipv4);
ATF_TP_ADD_TC(tp, basic_ipv6);
+ ATF_TP_ADD_TC(tp, concurrent_add);
+ ATF_TP_ADD_TC(tp, double_listen_ipv4);
+ ATF_TP_ADD_TC(tp, double_listen_ipv6);
+ ATF_TP_ADD_TC(tp, bind_without_listen);
+ ATF_TP_ADD_TC(tp, connect_not_bound);
+ ATF_TP_ADD_TC(tp, connect_bound);
return (atf_no_error());
}