diff options
Diffstat (limited to 'tests/sys/netinet/so_reuseport_lb_test.c')
-rw-r--r-- | tests/sys/netinet/so_reuseport_lb_test.c | 322 |
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()); } |