Run bind(2) and connect(2) stress test also with IPv6.
authorbluhm <bluhm@openbsd.org>
Thu, 7 Dec 2023 23:47:48 +0000 (23:47 +0000)
committerbluhm <bluhm@openbsd.org>
Thu, 7 Dec 2023 23:47:48 +0000 (23:47 +0000)
regress/sys/netinet/bindconnect/Makefile
regress/sys/netinet/bindconnect/README
regress/sys/netinet/bindconnect/bindconnect.c

index 258a6a3..e2cb329 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.2 2023/12/06 22:57:14 bluhm Exp $
+#      $OpenBSD: Makefile,v 1.3 2023/12/07 23:47:48 bluhm Exp $
 
 PROG=          bindconnect
 LDADD=         -lpthread
@@ -8,6 +8,7 @@ WARNINGS=       yes
 CLEANFILES=    ktrace.out
 
 LOCAL_NET ?=
+LOCAL_NET6 ?=
 
 REGRESS_ROOT_TARGETS +=        setup-maxfiles run-100000 run-localnet-connect-delete
 
@@ -18,52 +19,68 @@ setup-maxfiles:
 
 REGRESS_SETUP +=       ${PROG}
 
-.if ! empty(LOCAL_NET)
-REGRESS_CLEANUP +=     cleanup-delete
-.endif
-cleanup-delete:
-       -${SUDO} time ./${PROG} -s 0 -o 0 -b 0 -c 0 -d 1 -N ${LOCAL_NET} -t 1
-
 REGRESS_TARGETS +=     run-default
 run-default:
        time ${KTRACE} ./${PROG}
 
-REGRESS_TARGETS +=     run-bind
-run-bind:
-       time ${KTRACE} ./${PROG} -n 16 -s 2 -o 1 -b 5 -c 0
-
-REGRESS_TARGETS +=     run-connect
-run-connect:
-       time ${KTRACE} ./${PROG} -n 16 -s 2 -o 1 -b 0 -c 5
-
-REGRESS_TARGETS +=     run-bind-connect
-run-bind-connect:
-       time ${KTRACE} ./${PROG} -n 16 -s 2 -o 1 -b 3 -c 3
+NET_inet =     ${LOCAL_NET}
+NET_inet6 =    ${LOCAL_NET6}
 
-REGRESS_TARGETS +=     run-100000
-run-100000:
-       ${SUDO} time ${KTRACE} ./${PROG} -n 100000 -s 2 -o 1 -b 3 -c 3
+.for af in inet inet6
 
-REGRESS_TARGETS +=     run-reuseport
-run-reuseport:
-       time ${KTRACE} ./${PROG} -n 16 -s 2 -o 1 -b 3 -c 3 -r
-
-.if empty(LOCAL_NET)
-REGRESS_SKIP_TARGETS +=        run-localnet-connect run-localnet-bind-connect \
-                       run-localnet-connect-delete
+.if ! empty(NET_${af})
+REGRESS_CLEANUP +=     cleanup-${af}-delete
+.endif
+cleanup-${af}-delete:
+       -${SUDO} time ./${PROG} \
+           -f ${af} -s 0 -o 0 -b 0 -c 0 -d 1 -N ${NET_${af}} -t 1
+
+REGRESS_TARGETS +=     run-${af}-bind
+run-${af}-bind:
+       time ${KTRACE} ./${PROG} \
+           -f ${af} -n 16 -s 2 -o 1 -b 6 -c 0
+
+REGRESS_TARGETS +=     run-${af}-connect
+run-${af}-connect:
+       time ${KTRACE} ./${PROG} \
+           -f ${af} -n 16 -s 2 -o 1 -b 0 -c 6
+
+REGRESS_TARGETS +=     run-${af}-bind-connect
+run-${af}-bind-connect:
+       time ${KTRACE} ./${PROG} \
+           -f ${af} -n 16 -s 2 -o 1 -b 3 -c 3
+
+REGRESS_TARGETS +=     run-${af}-100000
+run-${af}-100000:
+       ${SUDO} time ${KTRACE} ./${PROG} \
+           -f ${af} -n 100000 -s 2 -o 1 -b 3 -c 3
+
+REGRESS_TARGETS +=     run-${af}-reuseport
+run-${af}-reuseport:
+       time ${KTRACE} ./${PROG} \
+           -f ${af} -n 16 -s 2 -o 1 -b 3 -c 3 -r
+
+.if empty(NET_${af})
+REGRESS_SKIP_TARGETS +=        run-${af}-localnet-connect \
+                       run-${af}-localnet-bind-connect \
+                       run-${af}-localnet-connect-delete
 .endif
 
-REGRESS_TARGETS +=     run-localnet-connect
-run-localnet-connect:
-       time ${KTRACE} ./${PROG} -n 16 -s 2 -o 1 -c 5 -N ${LOCAL_NET}
+REGRESS_TARGETS +=     run-${af}-localnet-connect
+run-${af}-localnet-connect:
+       time ${KTRACE} ./${PROG} \
+           -f ${af} -n 16 -s 2 -o 1 -c 6 -N ${NET_${af}}
+
+REGRESS_TARGETS +=     run-${af}-localnet-bind-connect
+run-${af}-localnet-bind-connect:
+       time ${KTRACE} ./${PROG} \
+           -f ${af} -n 16 -s 2 -o 1 -b 3 -c 3 -N ${NET_${af}}
 
-REGRESS_TARGETS +=     run-localnet-bind-connect
-run-localnet-bind-connect:
-       time ${KTRACE} ./${PROG} -n 16 -s 2 -o 1 -b 3 -c 3 -N ${LOCAL_NET}
+REGRESS_TARGETS +=     run-${af}-localnet-connect-delete
+run-${af}-localnet-connect-delete:
+       ${SUDO} time ${KTRACE} ./${PROG} \
+           -f ${af} -n 16 -s 2 -o 1 -b 0 -c 6 -d 3 -N ${NET_${af}}
 
-REGRESS_TARGETS +=     run-localnet-connect-delete
-run-localnet-connect-delete:
-       ${SUDO} time ${KTRACE} \
-           ./${PROG} -n 16 -s 2 -o 1 -b 0 -c 5 -d 3 -N ${LOCAL_NET}
+.endfor
 
 .include <bsd.regress.mk>
index 0c168e6..f425908 100644 (file)
@@ -5,6 +5,7 @@ bindconnect [-r] [-b bind] [-c connect] [-d delroute]
     -b bind      threads binding sockets, default 1
     -c connect   threads connecting sockets, default 1
     -d delroute  threads deleting cloned routes, default 0
+    -f family    address family inet or inet6, default inet
     -N addr/net  connect to any address within network
     -n num       number of file descriptors, default 128
     -o close     threads closing sockets, default 1
@@ -19,9 +20,9 @@ system calls operate on random file descriptors.  By setting the
 number of threads for each system call and the number of available
 file descriptors, the focus for the stress test can be changed.
 
-Currently only IPv4 UDP sockets are supported.  Per default the
-address to bind and connect is 127.0.0.1.  LOCAL_NET environment
-variable allows to bind on a local address and connect to all
-directly attached hosts.  This triggers creation of cloned routes
-during source address selection.  To stress test routing table,
-these routes can be deleted in another thread.
+Currently IPv4 and IPv6 UDP sockets are supported.  Per default the
+address to bind and connect is 127.0.0.1 or ::1.  LOCAL_NET or
+LOCAL_NET6 environment variable allows to bind on a local address
+and connect to all directly attached hosts.  This triggers creation
+of cloned routes during source address selection.  To stress test
+routing table, these routes can be deleted in another thread.
index 19966d9..7ed5eaf 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: bindconnect.c,v 1.2 2023/12/06 22:57:14 bluhm Exp $   */
+/*     $OpenBSD: bindconnect.c,v 1.3 2023/12/07 23:47:48 bluhm Exp $   */
 
 /*
  * Copyright (c) 2023 Alexander Bluhm <bluhm@openbsd.org>
 
 #define MAX(a, b)      ((a) > (b) ? (a) : (b))
 
+#define s6_addr8       __u6_addr.__u6_addr8
+#define s6_addr16      __u6_addr.__u6_addr16
+#define s6_addr32      __u6_addr.__u6_addr32
+
+union sockaddr_union {
+       struct sockaddr         su_sa;
+       struct sockaddr_in      su_sin;
+       struct sockaddr_in6     su_sin6;
+};
+
+union inaddr_union {
+       struct in_addr  au_inaddr;
+       struct in6_addr au_in6addr;
+};
+
 int fd_base;
 unsigned int fd_num = 128;
 unsigned int run_time = 10;
 unsigned int socket_num = 1, close_num = 1, bind_num = 1, connect_num = 1,
     delroute_num = 0;
 int reuse_port = 0;
-struct in_addr addr, mask;
-int prefix = -1, route_sock = -1;
+const char *family = "inet";
+union inaddr_union addr, mask;
+int af, prefix = -1, route_sock = -1;
 
 static void __dead
 usage(void)
@@ -51,23 +67,18 @@ usage(void)
            "    -b bind      threads binding sockets, default %u\n"
            "    -c connect   threads connecting sockets, default %u\n"
            "    -d delroute  threads deleting cloned routes, default %u\n"
+           "    -f family    address family inet or inet6, default %s\n"
            "    -N addr/net  connect to any address within network\n"
            "    -n num       number of file descriptors, default %u\n"
            "    -o close     threads closing sockets, default %u\n"
            "    -r           set reuse port socket option\n"
            "    -s socket    threads creating sockets, default %u\n"
            "    -t time      run time in seconds, default %u\n",
-           bind_num, connect_num, delroute_num, fd_num, close_num, socket_num,
-           run_time);
+           bind_num, connect_num, delroute_num, family, fd_num, close_num,
+           socket_num, run_time);
        exit(2);
 }
 
-static inline struct sockaddr *
-sintosa(struct sockaddr_in *sin)
-{
-       return ((struct sockaddr *)(sin));
-}
-
 static void
 in_prefixlen2mask(struct in_addr *maskp, int plen)
 {
@@ -77,6 +88,72 @@ in_prefixlen2mask(struct in_addr *maskp, int plen)
                maskp->s_addr = htonl(0xffffffff << (32 - plen));
 }
 
+static void
+in6_prefixlen2mask(struct in6_addr *maskp, int len)
+{
+       u_char maskarray[8] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
+       int bytelen, bitlen, i;
+
+       bzero(maskp, sizeof(*maskp));
+       bytelen = len / 8;
+       bitlen = len % 8;
+       for (i = 0; i < bytelen; i++)
+               maskp->s6_addr[i] = 0xff;
+       /* len == 128 is ok because bitlen == 0 then */
+       if (bitlen)
+               maskp->s6_addr[bytelen] = maskarray[bitlen - 1];
+}
+
+static void
+fill_sockaddr(union sockaddr_union *su)
+{
+       memset(su, 0, sizeof(*su));
+       su->su_sa.sa_family = af;
+       if (af == AF_INET) {
+               su->su_sin.sin_len = sizeof(su->su_sin);
+               if (prefix >= 0)
+                       su->su_sin.sin_addr = addr.au_inaddr;
+               else
+                       su->su_sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       }
+       if (af == AF_INET6) {
+               su->su_sin6.sin6_len = sizeof(su->su_sin6);
+               if (prefix >= 0)
+                       su->su_sin6.sin6_addr = addr.au_in6addr;
+               else
+                       su->su_sin6.sin6_addr = in6addr_loopback;
+       }
+}
+
+static void
+mask_sockaddr(union sockaddr_union *su)
+{
+       if (af == AF_INET) {
+               if (prefix >=0 && prefix != 32) {
+                       su->su_sin.sin_addr.s_addr &=
+                           mask.au_inaddr.s_addr;
+                       /* do only 8 bits variation, routes should be reused */
+                       su->su_sin.sin_addr.s_addr |= htonl(255) &
+                           ~mask.au_inaddr.s_addr & arc4random();
+               }
+       }
+       if (af == AF_INET6) {
+               if (prefix >=0 && prefix != 128) {
+                       su->su_sin6.sin6_addr.s6_addr32[0] &=
+                           mask.au_in6addr.s6_addr32[0];
+                       su->su_sin6.sin6_addr.s6_addr32[1] &=
+                           mask.au_in6addr.s6_addr32[1];
+                       su->su_sin6.sin6_addr.s6_addr32[2] &=
+                           mask.au_in6addr.s6_addr32[2];
+                       su->su_sin6.sin6_addr.s6_addr32[3] &=
+                           mask.au_in6addr.s6_addr32[3];
+                       /* do only 8 bits variation, routes should be reused */
+                       su->su_sin6.sin6_addr.s6_addr32[3] |= htonl(255) &
+                           ~mask.au_in6addr.s6_addr32[3] & arc4random();
+               }
+       }
+}
+
 static void *
 thread_socket(void *arg)
 {
@@ -87,7 +164,7 @@ thread_socket(void *arg)
        for (count = 0; *run; count++) {
                int opt;
 
-               fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+               fd = socket(af, SOCK_DGRAM, IPPROTO_UDP);
                if (fd < 0 || !reuse_port)
                        continue;
                opt = 1;
@@ -118,19 +195,13 @@ thread_bind(void *arg)
        volatile int *run = arg;
        unsigned long count;
        int fd;
-       struct sockaddr_in sin;
-
-       memset(&sin, 0, sizeof(sin));
-       sin.sin_len = sizeof(sin);
-       sin.sin_family = AF_INET;
-       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       union sockaddr_union su;
 
-       if (prefix >= 0)
-               sin.sin_addr = addr;
+       fill_sockaddr(&su);
 
        for (count = 0; *run; count++) {
                fd = fd_base + arc4random_uniform(fd_num);
-               bind(fd, sintosa(&sin), sizeof(sin));
+               bind(fd, &su.su_sa, su.su_sa.sa_len);
        }
 
        return (void *)count;
@@ -142,24 +213,18 @@ thread_connect(void *arg)
        volatile int *run = arg;
        unsigned long count;
        int fd;
-       struct sockaddr_in sin;
+       union sockaddr_union su;
 
-       memset(&sin, 0, sizeof(sin));
-       sin.sin_len = sizeof(sin);
-       sin.sin_family = AF_INET;
-       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-
-       if (prefix >= 0)
-               sin.sin_addr = addr;
+       fill_sockaddr(&su);
 
        for (count = 0; *run; count++) {
                fd = fd_base + arc4random_uniform(fd_num);
-               if (prefix >=0 && prefix != 32) {
-                       sin.sin_addr.s_addr &= mask.s_addr;
-                       sin.sin_addr.s_addr |= ~mask.s_addr & arc4random();
-               }
-               sin.sin_port = arc4random();
-               connect(fd, sintosa(&sin), sizeof(sin));
+               mask_sockaddr(&su);
+               if (af == AF_INET)
+                       su.su_sin.sin_port = arc4random();
+               if (af == AF_INET6)
+                       su.su_sin6.sin6_port = arc4random();
+               connect(fd, &su.su_sa, su.su_sa.sa_len);
        }
 
        return (void *)count;
@@ -175,7 +240,7 @@ thread_delroute(void *arg)
                struct rt_msghdr        m_rtm;
                char                    m_space[512];
        } m_rtmsg;
-       struct sockaddr_in sin;
+       union sockaddr_union su;
 
 #define rtm \
        m_rtmsg.m_rtm
@@ -185,8 +250,8 @@ thread_delroute(void *arg)
        (x += ROUNDUP((n)->sa_len))
 #define NEXTADDR(w, sa)                                \
        if (rtm.rtm_addrs & (w)) {              \
-               int l = ROUNDUP(sa->sa_len);    \
-               memcpy(cp, sa, l);              \
+               int l = ROUNDUP((sa)->sa_len);  \
+               memcpy(cp, (sa), l);            \
                cp += l;                        \
        }
 
@@ -195,21 +260,16 @@ thread_delroute(void *arg)
        rtm.rtm_flags = RTF_HOST;
        rtm.rtm_version = RTM_VERSION;
        rtm.rtm_addrs = RTA_DST;
-       rtm.rtm_priority = 0;  /* XXX */
        rtm.rtm_hdrlen = sizeof(rtm);
 
-       memset(&sin, 0, sizeof(sin));
-       sin.sin_len = sizeof(sin);
-       sin.sin_family = AF_INET;
-       sin.sin_addr = addr;
+       fill_sockaddr(&su);
 
        for (count = 0; *run; count++) {
                char *cp = m_rtmsg.m_space;
 
                rtm.rtm_seq = ++seq;
-               sin.sin_addr.s_addr &= mask.s_addr;
-               sin.sin_addr.s_addr |= ~mask.s_addr & arc4random();
-               NEXTADDR(RTA_DST, sintosa(&sin));
+               mask_sockaddr(&su);
+               NEXTADDR(RTA_DST, &su.su_sa);
                rtm.rtm_msglen = cp - (char *)&m_rtmsg;
                write(route_sock, &m_rtmsg, rtm.rtm_msglen);
        }
@@ -228,12 +288,14 @@ main(int argc, char *argv[])
        struct rlimit rlim;
        pthread_t *tsocket, *tclose, *tbind, *tconnect, *tdelroute;
        const char *errstr, *addr_net = NULL;
+       char buf[128], *p;
        int ch, run;
        unsigned int n;
        unsigned long socket_count, close_count, bind_count, connect_count,
            delroute_count;
+       union sockaddr_union su;
 
-       while ((ch = getopt(argc, argv, "b:c:d:N:n:o:rs:t:")) != -1) {
+       while ((ch = getopt(argc, argv, "b:c:d:f:N:n:o:rs:t:")) != -1) {
                switch (ch) {
                case 'b':
                        bind_num = strtonum(optarg, 0, UINT_MAX, &errstr);
@@ -250,6 +312,9 @@ main(int argc, char *argv[])
                        if (errstr != NULL)
                                errx(1, "delroute is %s: %s", errstr, optarg);
                        break;
+               case 'f':
+                       family = optarg;
+                       break;
                case 'N':
                        addr_net = optarg;
                        break;
@@ -285,27 +350,58 @@ main(int argc, char *argv[])
        if (argc > 0)
                usage();
 
+       if (strcmp(family, "inet") == 0)
+               af = AF_INET;
+       else if (strcmp(family, "inet6") == 0)
+               af = AF_INET6;
+       else
+               errx(1, "bad address family %s", family);
+
+       /* split addr/net into addr, mask, prefix */
        if (addr_net != NULL) {
-               prefix = inet_net_pton(AF_INET, addr_net, &addr, sizeof(addr));
+               prefix = inet_net_pton(af, addr_net, &addr, sizeof(addr));
                if (prefix < 0)
                        err(1, "inet_net_pton %s", addr_net);
-               in_prefixlen2mask(&mask, prefix);
+               if (af == AF_INET6) {
+                       /*
+                        * Man page says inet_net_pton() preserves lower
+                        * bits.  That is not true, call inet_pton() again.
+                        */
+                       if (strlcpy(buf, addr_net, sizeof(buf)) >= sizeof(buf))
+                               err(1, "strlcpy %s", addr_net);
+                       p = strchr(buf, '/');
+                       if (p != NULL ) {
+                               *p = '\0';
+                               if (inet_pton(af, buf, &addr) < 0)
+                                       err(1, "inet_pton %s", buf);
+                       }
+               }
+               if (af == AF_INET)
+                       in_prefixlen2mask(&mask.au_inaddr, prefix);
+               if (af == AF_INET6)
+                       in6_prefixlen2mask(&mask.au_in6addr, prefix);
        }
+
+       /* preopen route socket before file descriptor limits are set */
        if (delroute_num > 0) {
                if (prefix < 0 || prefix == 32)
                        errx(1, "delroute %u needs addr/net", delroute_num);
-               route_sock = socket(AF_ROUTE, SOCK_RAW, AF_INET);
+               route_sock = socket(AF_ROUTE, SOCK_RAW, af);
                if (route_sock < 0)
                        err(1, "socket route");
                if (shutdown(route_sock, SHUT_RD) < 0)
                        err(1, "shutdown read route");
        }
 
-       fd_base = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+       /* detect lowest file desciptor, test bind, close everything above */
+       fd_base = socket(af, SOCK_DGRAM, IPPROTO_UDP);
        if (fd_base < 0)
                err(1, "socket fd_base");
        if (fd_base > INT_MAX - (int)fd_num)
                err(1, "fd base %d and num %u overflow", fd_base, fd_num);
+       fill_sockaddr(&su);
+       if (bind(fd_base, &su.su_sa, su.su_sa.sa_len) < 0)
+               err(1, "bind %s", inet_ntop(af, &addr, buf, sizeof(buf)));
        if (closefrom(fd_base) < 0)
                err(1, "closefrom %d", fd_base);