]> Zhao Yanbai Git Server - minix.git/commitdiff
tests: add advanced TCP/IP tests (test91-94) 88/3488/2
authorDavid van Moolenbroek <david@minix3.org>
Thu, 29 Sep 2016 23:16:22 +0000 (23:16 +0000)
committerDavid van Moolenbroek <david@minix3.org>
Sun, 30 Apr 2017 13:16:25 +0000 (13:16 +0000)
Change-Id: I052102f6122f82b3307595990bf91f64e97a45a8

distrib/sets/lists/minix-debug/mi
distrib/sets/lists/minix-tests/mi
minix/tests/Makefile
minix/tests/run
minix/tests/socklib.c
minix/tests/socklib.h
minix/tests/test91.c [new file with mode: 0644]
minix/tests/test92.c [new file with mode: 0644]
minix/tests/test93.c [new file with mode: 0644]
minix/tests/test94.c [new file with mode: 0644]

index c3c7e0980aa196b00e4c1ecfeaaf17101f5ba7c1..27aabb7dc357ea5d615e870fb8a605d790224cb9 100644 (file)
 ./usr/libdata/debug/usr/tests/minix-posix/test89.debug  minix-debug     debug
 ./usr/libdata/debug/usr/tests/minix-posix/test9.debug   minix-debug     debug
 ./usr/libdata/debug/usr/tests/minix-posix/test90.debug  minix-debug     debug
+./usr/libdata/debug/usr/tests/minix-posix/test91.debug  minix-debug     debug
+./usr/libdata/debug/usr/tests/minix-posix/test92.debug  minix-debug     debug
+./usr/libdata/debug/usr/tests/minix-posix/test93.debug  minix-debug     debug
+./usr/libdata/debug/usr/tests/minix-posix/test94.debug  minix-debug     debug
 ./usr/libdata/debug/usr/tests/minix-posix/testvm.debug  minix-debug     debug
 ./usr/libdata/debug/usr/tests/minix-posix/tvnd.debug    minix-debug     debug
 ./usr/libdata/debug/usr/tests/usr.bin/id/h_id.debug     minix-debug     debug
index 1a1668e643de1387e0696f1fc423a572974f4353..d3ddc45b79be1a6dc888ba81df03b3ba2270a437 100644 (file)
 ./usr/tests/minix-posix/test89                          minix-tests
 ./usr/tests/minix-posix/test9                           minix-tests
 ./usr/tests/minix-posix/test90                          minix-tests
+./usr/tests/minix-posix/test91                          minix-tests
+./usr/tests/minix-posix/test92                          minix-tests
+./usr/tests/minix-posix/test93                          minix-tests
+./usr/tests/minix-posix/test94                          minix-tests
 ./usr/tests/minix-posix/testinterp                      minix-tests
 ./usr/tests/minix-posix/testisofs                       minix-tests
 ./usr/tests/minix-posix/testkyua                        minix-tests
index f1e17785760349dda06e9c56967a5c2b4b4ceac1..c0bcc8557b220d701fefe5236c6fb5deff225ba7 100644 (file)
@@ -56,9 +56,21 @@ FILES += testvm.conf
 
 # Network stack testing programs
 OBJS.test90+=  socklib.o
+OBJS.test91+=  socklib.o
+OBJS.test92+=  socklib.o
+OBJS.test93+=  socklib.o
 # Uncomment the following lines to use SOCKLIB_SWEEP_GENERATE=1/2 in socklib.c
 #.PATH: ${NETBSDSRCDIR}/minix/usr.bin/trace
 #OBJS.test90+= error.o
+#OBJS.test91+= error.o
+#OBJS.test92+= error.o
+#OBJS.test93+= error.o
+
+.if ${USE_INET6} == "no"
+# Tests 91-94 will fail without IPv6 support, but they should at least compile.
+CPPFLAGS.socklib.c += -DNO_INET6
+CPPFLAGS.test94.c += -DNO_INET6
+.endif # ${USE_INET6} == "no"
 
 # Tests to compile, For every architecture
 MINIX_TESTS= \
@@ -66,7 +78,7 @@ MINIX_TESTS= \
 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
 41 42 43 44 45 46    48 49 50    52 53 54 55 56    58 59 60 \
 61       64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \
-81 82 83 84 85 86 87 88 89 90
+81 82 83 84 85 86 87 88 89 90 91 92 93 94
 
 FILES += t84_h_nonexec.sh
 
index a5baa809c02fec8c47df666525b6d0126cceefc5..14f5a39dcda8771fb568fea82ae05f96f5b4ff5a 100755 (executable)
@@ -22,7 +22,8 @@ export USENETWORK             # set to "yes" for test48+82 to use the network
 
 # Programs that require setuid
 setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \
-        test69 test73 test74 test78 test83 test85 test87 test88 test89"
+        test69 test73 test74 test78 test83 test85 test87 test88 test89 \
+        test92 test93 test94"
 # Scripts that require to be run as root
 rootscripts="testisofs testvnd testrmib testrelpol"
 
@@ -30,7 +31,7 @@ alltests="1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 \
          21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
          41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 \
          61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \
-         81 82 83 84 85 86 87 88 89 90 \
+         81 82 83 84 85 86 87 88 89 90 91 92 93 94 \
         sh1 sh2 interp mfs isofs vnd rmib"
 tests_no=`expr 0`
 
index 3608f6c1681028d4b3a36b7163130208504baaad..af09280ee94caa17dfa8d5a3e0510259a3f9f7c5 100644 (file)
@@ -1,3 +1,8 @@
+/*
+ * Socket test code library.  This file contains code that is worth sharing
+ * between TCP/IP and UDS tests, as well as code that is worth sharing between
+ * various TCP/IP tests.
+ */
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet6/in6_var.h>
+#include <arpa/inet.h>
+#include <ifaddrs.h>
 #include <unistd.h>
 #include <fcntl.h>
 
 #include "common.h"
 #include "socklib.h"
 
+#define TEST_PORT_A    12345   /* this port should be free and usable */
+#define TEST_PORT_B    12346   /* this port should be free and usable */
+
+#define LOOPBACK_IFNAME                "lo0"           /* loopback interface name */
+#define LOOPBACK_IPV4          "127.0.0.1"     /* IPv4 address */
+#define LOOPBACK_IPV6_LL       "fe80::1"       /* link-local IPv6 address */
+
+/* These address should simply eat all packets. */
+#define TEST_BLACKHOLE_IPV4    "127.255.0.254"
+#define TEST_BLACKHOLE_IPV6    "::2"
+#define TEST_BLACKHOLE_IPV6_LL "fe80::ffff"
+
+/* Addresses for multicast-related testing. */
+#define TEST_MULTICAST_IPV4    "233.252.0.1"           /* RFC 5771 Sec. 9.2 */
+#define TEST_MULTICAST_IPV6    "ff0e::db8:0:1"         /* RFC 6676 Sec. 3 */
+#define TEST_MULTICAST_IPV6_LL "ff02::db8:0:1"
+#define TEST_MULTICAST_IPV6_BAD        "ff00::db8:0:1"
+
+#define BAD_IFINDEX    255     /* guaranteed not to belong to an interface */
+
 /* 0 = check, 1 = generate source, 2 = generate CSV */
 #define SOCKLIB_SWEEP_GENERATE 0
 
@@ -445,6 +477,280 @@ socklib_sweep(int domain, int type, int protocol, const enum state * states,
 #endif
 }
 
+/*
+ * Test for setting and retrieving UDP/RAW multicast transmission options.
+ * This is an interface-level test only: we do not (yet) test whether the
+ * options have any effect.  The given 'type' must be SOCK_DGRAM or SOCK_RAW.
+ */
+void
+socklib_multicast_tx_options(int type)
+{
+       struct in_addr in_addr;
+       socklen_t len;
+       unsigned int ifindex;
+       uint8_t byte;
+       int fd, val;
+
+       subtest = 10;
+
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       /*
+        * Initially, the multicast TTL is expected be 1, looping should be
+        * enabled, and the multicast source address should be <any>.
+        */
+       byte = 0;
+       len = sizeof(byte);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte, &len) != 0)
+               e(0);
+       if (len != sizeof(byte)) e(0);
+       if (type != SOCK_STREAM && byte != 1) e(0);
+
+       byte = 0;
+       len = sizeof(byte);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte, &len) != 0)
+               e(0);
+       if (len != sizeof(byte)) e(0);
+       if (byte != 1) e(0);
+
+       len = sizeof(in_addr);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &in_addr, &len) != 0)
+               e(0);
+       if (len != sizeof(in_addr)) e(0);
+       if (in_addr.s_addr != htonl(INADDR_ANY)) e(0);
+
+       /* It must not be possible to get/set IPv6 options on IPv4 sockets. */
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val) /*wrong but it doesn't matter*/) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+           &len) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val,
+           &len) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != -1)
+               e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       /*
+        * Expect the same defaults as for IPv4.  IPV6_MULTICAST_IF uses an
+        * interface index rather than an IP address, though.
+        */
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (type != SOCK_STREAM && val != 1) e(0);
+
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 1) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       /* It must not be possible to get/set IPv4 options on IPv6 sockets. */
+       byte = 0;
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte,
+           sizeof(byte)) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte,
+           sizeof(byte)) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &byte,
+           sizeof(byte) /* wrong but it doesn't matter */) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       len = sizeof(byte);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, &len) != -1)
+               e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, &len) != -1)
+               e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &val, &len) != -1)
+               e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test changing options. */
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       byte = 129;
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte,
+           sizeof(byte)) != 0) e(0);
+
+       byte = 0;
+       len = sizeof(byte);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte, &len) != 0)
+               e(0);
+       if (len != sizeof(byte)) e(0);
+       if (byte != 129) e(0);
+
+       byte = 0;
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte,
+           sizeof(byte)) != 0)
+               e(0);
+
+       byte = 1;
+       len = sizeof(byte);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte, &len) != 0)
+               e(0);
+       if (len != sizeof(byte)) e(0);
+       if (byte != 0) e(0);
+
+       in_addr.s_addr = htonl(INADDR_LOOPBACK);
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &in_addr,
+          sizeof(in_addr)) != 0)
+               e(0);
+
+       in_addr.s_addr = htonl(INADDR_ANY);
+       len = sizeof(in_addr);
+       if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &in_addr, &len) != 0)
+               e(0);
+       if (len != sizeof(in_addr)) e(0);
+       if (in_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       val = 137;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+           sizeof(val)) != 0) e(0);
+
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 137) e(0);
+
+       val = -2;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       val = 256;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 137) e(0);
+
+       val = -1; /* use default */
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+           sizeof(val)) != 0) e(0);
+
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 1) e(0);
+
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val,
+           sizeof(val)) != 0) e(0);
+
+       val = 1;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val,
+           sizeof(val)) != 0) e(0);
+
+       val = -1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       val = 2;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 1) e(0);
+
+       val = -1;
+       ifindex = if_nametoindex(LOOPBACK_IFNAME);
+
+       val = ifindex;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != ifindex) e(0);
+
+       val = BAD_IFINDEX;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != -1) e(0);
+
+       val = -1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != -1) e(0);
+
+       val = 0;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != ifindex) e(0);
+
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       val = ifindex;
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
 /*
  * Test for large sends and receives on stream sockets with MSG_WAITALL.
  */
@@ -1021,3 +1327,2274 @@ socklib_stream_recv(int (* socket_pair)(int, int, int, int *), int domain,
                                                            rlowat, len, bits,
                                                            act, break_recv);
 }
+
+/*
+ * Obtain information for a matching protocol control block, using sysctl(7).
+ * The PCB is to be obtained through the given sysctl path string, and must
+ * match the other given parameters.  Return 1 if found with 'ki' filled with
+ * the PCB information, or 0 if not.
+ */
+int
+socklib_find_pcb(const char * path, int protocol, uint16_t local_port,
+       uint16_t remote_port, struct kinfo_pcb * ki)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       struct kinfo_pcb *array;
+       size_t i, miblen, oldlen;
+       uint16_t lport, rport;
+       int mib[CTL_MAXNAME], found;
+
+       miblen = __arraycount(mib);
+       if (sysctlnametomib(path, mib, &miblen) != 0) e(0);
+       if (miblen > __arraycount(mib) - 4) e(0);
+       mib[miblen++] = 0;
+       mib[miblen++] = 0;
+       mib[miblen++] = sizeof(*array);
+       mib[miblen++] = 0;
+
+       if (sysctl(mib, miblen, NULL, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen == 0)
+               return 0;       /* should not happen due to added slop space */
+       if (oldlen % sizeof(*array)) e(0);
+
+       if ((array = (struct kinfo_pcb *)malloc(oldlen)) == NULL) e(0);
+
+       if (sysctl(mib, miblen, array, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen % sizeof(*array)) e(0);
+
+       found = -1;
+       for (i = 0; i < oldlen / sizeof(*array); i++) {
+               /* Perform some basic checks. */
+               if (array[i].ki_pcbaddr == 0) e(0);
+               if (array[i].ki_ppcbaddr == 0) e(0);
+               if (array[i].ki_family != mib[1]) e(0);
+
+               if (mib[1] == AF_INET6) {
+                       memcpy(&sin6, &array[i].ki_src, sizeof(sin6));
+                       if (sin6.sin6_family != AF_INET6) e(0);
+                       if (sin6.sin6_len != sizeof(sin6)) e(0);
+                       lport = ntohs(sin6.sin6_port);
+
+                       memcpy(&sin6, &array[i].ki_dst, sizeof(sin6));
+                       if (sin6.sin6_family != AF_INET6) e(0);
+                       if (sin6.sin6_len != sizeof(sin6)) e(0);
+                       rport = ntohs(sin6.sin6_port);
+               } else {
+                       memcpy(&sin, &array[i].ki_src, sizeof(sin));
+                       if (sin.sin_family != AF_INET) e(0);
+                       if (sin.sin_len != sizeof(sin)) e(0);
+                       lport = ntohs(sin.sin_port);
+
+                       memcpy(&sin, &array[i].ki_dst, sizeof(sin));
+                       if (sin.sin_family != AF_UNSPEC) {
+                               if (sin.sin_family != AF_INET) e(0);
+                               if (sin.sin_len != sizeof(sin)) e(0);
+                               rport = ntohs(sin.sin_port);
+                       } else
+                               rport = 0;
+               }
+
+               /* Try to match every PCB.  We must find at most one match. */
+               if (array[i].ki_protocol == protocol && lport == local_port &&
+                   rport == remote_port) {
+                       if (found != -1) e(0);
+
+                       found = (int)i;
+               }
+       }
+
+       if (found >= 0)
+               memcpy(ki, &array[found], sizeof(*ki));
+
+       free(array);
+
+       return (found != -1);
+}
+
+#ifdef NO_INET6
+const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
+
+void
+inet6_getscopeid(struct sockaddr_in6 * sin6 __unused, int flags __unused)
+{
+
+       /*
+        * Nothing.  The tests linked to socklib make heavy use of IPv6, and
+        * are expected to fail if IPv6 support is disabled at compile time.
+        * Therefore, what this replacement function does is not relevant.
+        */
+}
+#endif /* NO_INET6 */
+
+#define F_ANY  0x01    /* not bound, or bound to an 'any' address */
+#define F_V4   0x02    /* address is IPv4-mapped IPv6 address */
+#define F_REM  0x04    /* address is remote (not assigned to an interface) */
+#define F_MIX  0x08    /* address has non-loopback scope */
+
+/*
+ * Test local and remote IPv6 address handling on TCP or UDP sockets.
+ */
+void
+socklib_test_addrs(int type, int protocol)
+{
+       struct sockaddr_in6 sin6, sin6_any, sin6_any_scope, sin6_lo,
+           sin6_lo_scope, sin6_ll_all, sin6_ll_lo, sin6_ll_rem, sin6_ll_kame,
+           sin6_ll_bad, sin6_ll_mix, sin6_rem, sin6_v4_any, sin6_v4_lo,
+           sin6_v4_rem, rsin6;
+       const struct sockaddr_in6 *sin6p;
+       const struct {
+               const struct sockaddr_in6 *addr;
+               int res;
+               int flags;
+               const struct sockaddr_in6 *name;
+       } bind_array[] = {
+               { NULL,                 0, F_ANY,       &sin6_any },
+               { &sin6_any,            0, F_ANY,       &sin6_any },
+               { &sin6_any_scope,      0, F_ANY,       &sin6_any },
+               { &sin6_lo,             0, 0,           &sin6_lo },
+               { &sin6_lo_scope,       0, 0,           &sin6_lo },
+               { &sin6_ll_lo,          0, 0,           &sin6_ll_lo },
+               { &sin6_v4_lo,          0, F_V4,        &sin6_v4_lo },
+               { &sin6_rem,            EADDRNOTAVAIL },
+               { &sin6_ll_all,         EADDRNOTAVAIL },
+               { &sin6_ll_rem,         EADDRNOTAVAIL },
+               { &sin6_ll_kame,        EINVAL },
+               { &sin6_ll_bad,         ENXIO },
+               { &sin6_v4_any,         EADDRNOTAVAIL },
+               { &sin6_v4_rem,         EADDRNOTAVAIL },
+               /* The following entry MUST be last. */
+               { &sin6_ll_mix,         EADDRNOTAVAIL },
+       }, *bp;
+       const struct {
+               const struct sockaddr_in6 *addr;
+               int res;
+               int flags;
+               const struct sockaddr_in6 *name;
+       } conn_array[] = {
+               { &sin6_any,            EHOSTUNREACH, 0 },
+               { &sin6_any_scope,      EHOSTUNREACH, 0 },
+               { &sin6_ll_kame,        EINVAL, 0 },
+               { &sin6_ll_bad,         ENXIO, 0 },
+               { &sin6_v4_any,         EHOSTUNREACH, F_V4 },
+               { &sin6_lo,             0, 0,           &sin6_lo },
+               { &sin6_lo_scope,       0, 0,           &sin6_lo },
+               { &sin6_ll_all,         0, 0,           &sin6_ll_lo },
+               { &sin6_ll_lo,          0, 0,           &sin6_ll_lo },
+               { &sin6_v4_lo,          0, F_V4,        &sin6_v4_lo },
+               { &sin6_rem,            0, F_REM,       &sin6_rem },
+               { &sin6_ll_rem,         0, F_REM,       &sin6_ll_rem },
+               { &sin6_v4_rem,         0, F_V4|F_REM,  &sin6_v4_rem },
+               /* The following entry MUST be last. */
+               { &sin6_ll_mix,         0, F_REM|F_MIX, &sin6_ll_mix },
+       }, *cp;
+       struct ifaddrs *ifa, *ifp, *ifp2;
+       struct in6_ifreq ifr;
+       char name[IF_NAMESIZE], buf[1];
+       socklen_t len;
+       uint32_t port;
+       unsigned int i, j, ifindex, ifindex2, have_mix, found;
+       int r, fd, fd2, fd3, val, sfl, exp, link_state;
+
+       ifindex = if_nametoindex(LOOPBACK_IFNAME);
+       if (ifindex == 0) e(0);
+
+       /* An IPv6 'any' address - ::0. */
+       memset(&sin6_any, 0, sizeof(sin6_any));
+       sin6_any.sin6_len = sizeof(sin6_any);
+       sin6_any.sin6_family = AF_INET6;
+       memcpy(&sin6_any.sin6_addr, &in6addr_any, sizeof(sin6_any.sin6_addr));
+
+       /* An IPv6 'any' address, but with a bad scope ID set. */
+       memcpy(&sin6_any_scope, &sin6_any, sizeof(sin6_any_scope));
+       sin6_any_scope.sin6_scope_id = BAD_IFINDEX;
+
+       /* An IPv6 loopback address - ::1. */
+       memcpy(&sin6_lo, &sin6_any, sizeof(sin6_lo));
+       memcpy(&sin6_lo.sin6_addr, &in6addr_loopback,
+           sizeof(sin6_lo.sin6_addr));
+
+       /* An IPv6 loopback address, but with a bad scope ID set. */
+       memcpy(&sin6_lo_scope, &sin6_lo, sizeof(sin6_lo_scope));
+       sin6_lo_scope.sin6_scope_id = BAD_IFINDEX;
+
+       /* An IPv6 link-local address without scope - fe80::1. */
+       memcpy(&sin6_ll_all, &sin6_any, sizeof(sin6_ll_all));
+       if (inet_pton(AF_INET6, LOOPBACK_IPV6_LL, &sin6_ll_all.sin6_addr) != 1)
+               e(0);
+
+       /* An IPv6 link-local address with the loopback scope - fe80::1%lo0. */
+       memcpy(&sin6_ll_lo, &sin6_ll_all, sizeof(sin6_ll_lo));
+       sin6_ll_lo.sin6_scope_id = ifindex;
+
+       /* An unassigned IPv6 link-local address - fe80::ffff%lo0. */
+       memcpy(&sin6_ll_rem, &sin6_ll_lo, sizeof(sin6_ll_rem));
+       if (inet_pton(AF_INET6, TEST_BLACKHOLE_IPV6_LL,
+           &sin6_ll_rem.sin6_addr) != 1) e(0);
+
+       /* A KAME-style IPv6 link-local loopback address - fe80:ifindex::1. */
+       memcpy(&sin6_ll_kame, &sin6_ll_all, sizeof(sin6_ll_kame));
+       sin6_ll_kame.sin6_addr.s6_addr[2] = ifindex >> 8;
+       sin6_ll_kame.sin6_addr.s6_addr[3] = ifindex % 0xff;
+
+       /* An IPv6 link-local address with a bad scope - fe80::1%<bad>. */
+       memcpy(&sin6_ll_bad, &sin6_ll_all, sizeof(sin6_ll_bad));
+       sin6_ll_bad.sin6_scope_id = BAD_IFINDEX;
+
+       /* A global IPv6 address not assigned to any interface - ::2. */
+       memcpy(&sin6_rem, &sin6_any, sizeof(sin6_rem));
+       if (inet_pton(AF_INET6, TEST_BLACKHOLE_IPV6,
+           &sin6_rem.sin6_addr) != 1) e(0);
+
+       /* An IPv4-mapped IPv6 address for 'any' - ::ffff:0.0.0.0. */
+       memcpy(&sin6_v4_any, &sin6_any, sizeof(sin6_v4_any));
+       if (inet_pton(AF_INET6, "::ffff:0:0", &sin6_v4_any.sin6_addr) != 1)
+               e(0);
+
+       /* An IPv4-mapped IPv6 loopback address - ::ffff:127.0.0.1. */
+       memcpy(&sin6_v4_lo, &sin6_any, sizeof(sin6_v4_lo));
+       if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4,
+           &sin6_v4_lo.sin6_addr) != 1) e(0);
+
+       /* An unassigned IPv4-mapped IPv6 address - ::ffff:127.255.0.254. */
+       memcpy(&sin6_v4_rem, &sin6_any, sizeof(sin6_v4_rem));
+       if (inet_pton(AF_INET6, "::ffff:"TEST_BLACKHOLE_IPV4,
+           &sin6_v4_rem.sin6_addr) != 1) e(0);
+
+       /*
+        * An IPv6 link-local address with a scope for another interface, for
+        * example fe80::1%em0.  Since no other interfaces may be present, we
+        * may not be able to generate such an address.
+        */
+       have_mix = 0;
+       for (i = 1; i < BAD_IFINDEX; i++) {
+               if (if_indextoname(i, name) == NULL) {
+                       if (errno != ENXIO) e(0);
+                       continue;
+               }
+
+               if (!strcmp(name, LOOPBACK_IFNAME))
+                       continue;
+
+               /* Found one! */
+               memcpy(&sin6_ll_mix, &sin6_ll_all, sizeof(sin6_ll_mix));
+               sin6_ll_mix.sin6_scope_id = i;
+               have_mix = 1;
+               break;
+       }
+
+       /*
+        * Test a whole range of combinations of local and remote addresses,
+        * both for TCP and UDP, and for UDP both for connect+send and sendto.
+        * Not all addresses and not all combinations are compatible, and that
+        * is exactly what we want to test.  We first test binding to local
+        * addresses.  Then we test connect (and for UDP, on success, send)
+        * with remote addresses on those local addresses that could be bound
+        * to.  Finally, for UDP sockets, we separately test sendto.
+        */
+       for (i = 0; i < __arraycount(bind_array) - !have_mix; i++) {
+               bp = &bind_array[i];
+
+               /* Test bind(2) and getsockname(2). */
+               if (bind_array[i].addr != NULL) {
+                       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+                       val = 0;
+                       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                           sizeof(val)) != 0) e(0);
+
+                       r = bind(fd, (struct sockaddr *)bp->addr,
+                           sizeof(*bp->addr));
+
+                       /* Did the bind(2) call produce the expected result? */
+                       if (r == 0) {
+                               if (bp->res != 0) e(0);
+                       } else
+                               if (r != -1 || bp->res != errno) e(0);
+
+                       /* The rest is for successful bind(2) calls. */
+                       if (r != 0) {
+                               if (close(fd) != 0) e(0);
+
+                               continue;
+                       }
+
+                       /* Get the bound address. */
+                       len = sizeof(sin6);
+                       if (getsockname(fd, (struct sockaddr *)&sin6,
+                           &len) != 0) e(0);
+                       if (len != sizeof(sin6)) e(0);
+
+                       /* A port must be set.  Clear it for the comparison. */
+                       if ((sin6.sin6_port == 0) == (type != SOCK_RAW)) e(0);
+
+                       sin6.sin6_port = 0;
+                       if (memcmp(&sin6, bp->name, sizeof(sin6)) != 0) e(0);
+
+                       if (close(fd) != 0) e(0);
+               }
+
+               /* Test connect(2), send(2), and getpeername(2). */
+               for (j = 0; j < __arraycount(conn_array) - !have_mix; j++) {
+                       cp = &conn_array[j];
+
+                       /*
+                        * We cannot test remote addresses without having bound
+                        * to a local address, because we may end up generating
+                        * external traffic as a result.
+                        */
+                       if ((bp->flags & F_ANY) && (cp->flags & F_REM))
+                               continue;
+
+                       /*
+                        * Use non-blocking sockets only if connecting is going
+                        * to take a while before ultimately failing; TCP only.
+                        */
+                       sfl = ((cp->flags & F_REM) && (type == SOCK_STREAM)) ?
+                           SOCK_NONBLOCK : 0;
+                       if ((fd = socket(AF_INET6, type | sfl, protocol)) < 0)
+                               e(0);
+
+                       val = 0;
+                       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                           sizeof(val)) != 0) e(0);
+
+                       if (bp->addr != NULL) {
+                               if (bind(fd, (struct sockaddr *)bp->addr,
+                                   sizeof(*bp->addr)) != 0) e(0);
+
+                               len = sizeof(sin6);
+                               if (getsockname(fd, (struct sockaddr *)&sin6,
+                                   &len) != 0) e(0);
+
+                               port = sin6.sin6_port;
+                       } else
+                               port = 0;
+
+                       memcpy(&sin6, cp->addr, sizeof(sin6));
+                       sin6.sin6_port = htons(TEST_PORT_A);
+
+                       if ((exp = cp->res) == 0 && type == SOCK_STREAM) {
+                               if (cp->flags & F_REM)
+                                       exp = EINPROGRESS;
+                               if (cp->flags & F_MIX)
+                                       exp = EHOSTUNREACH;
+                       }
+
+                       /*
+                        * The IPv4/IPv6 mismatch check precedes most other
+                        * checks, but (currently) not the bad-scope-ID check.
+                        */
+                       if (exp != ENXIO && !(bp->flags & F_ANY) &&
+                           ((bp->flags ^ cp->flags) & F_V4))
+                               exp = EINVAL;
+
+                       /*
+                        * Create a listening or receiving socket if we expect
+                        * the test to succeed and operate on a loopback target
+                        * so that we can test addresses on that end as well.
+                        */
+                       if (exp == 0 && !(cp->flags & F_REM)) {
+                               if ((fd2 = socket(AF_INET6, type,
+                                   protocol)) < 0) e(0);
+
+                               val = 0;
+                               if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY,
+                                   &val, sizeof(val)) != 0) e(0);
+
+                               val = 1;
+                               if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR,
+                                   &val, sizeof(val)) != 0) e(0);
+
+                               memcpy(&rsin6, cp->name, sizeof(rsin6));
+                               rsin6.sin6_port = htons(TEST_PORT_A);
+
+                               if (bind(fd2, (struct sockaddr *)&rsin6,
+                                   sizeof(rsin6)) != 0) e(0);
+
+                               if (type == SOCK_STREAM && listen(fd2, 1) != 0)
+                                       e(0);
+                       } else
+                               fd2 = -1;
+
+                       r = connect(fd, (struct sockaddr *)&sin6,
+                           sizeof(sin6));
+
+                       if (r == 0) {
+                               if (exp != 0) e(0);
+                       } else
+                               if (r != -1 || exp != errno) e(0);
+
+                       if (r != 0) {
+                               if (close(fd) != 0) e(0);
+
+                               continue;
+                       }
+
+                       /*
+                        * Connecting should always assign a local address if
+                        * no address was assigned, even if a port was assigned
+                        * already.  In the latter case, the port number must
+                        * obviously not change.  Test getsockname(2) again, if
+                        * we can.
+                        */
+                       len = sizeof(sin6);
+                       if (getsockname(fd, (struct sockaddr *)&sin6,
+                           &len) != 0) e(0);
+                       if (len != sizeof(sin6)) e(0);
+
+                       if (type != SOCK_RAW) {
+                               if (sin6.sin6_port == 0) e(0);
+                               if (port != 0 && port != sin6.sin6_port) e(0);
+                       } else
+                               if (sin6.sin6_port != 0) e(0);
+                       port = sin6.sin6_port;
+
+                       if (!(bp->flags & F_ANY))
+                               sin6p = bp->name;
+                       else if (!(cp->flags & F_REM))
+                               sin6p = cp->name;
+                       else
+                               sin6p = NULL; /* can't test: may vary */
+
+                       if (sin6p != NULL) {
+                               sin6.sin6_port = 0;
+
+                               if (memcmp(&sin6, sin6p, sizeof(sin6)) != 0)
+                                       e(0);
+                       }
+
+                       /*
+                        * Test getpeername(2).  It should always be the
+                        * "normalized" version of the target address.
+                        */
+                       len = sizeof(sin6);
+                       if (getpeername(fd, (struct sockaddr *)&sin6,
+                           &len) != 0) e(0);
+                       if (len != sizeof(sin6)) e(0);
+
+                       if (type != SOCK_RAW) {
+                               if (sin6.sin6_port != htons(TEST_PORT_A)) e(0);
+                       } else {
+                               if (sin6.sin6_port != 0) e(0);
+                       }
+
+                       sin6.sin6_port = 0;
+                       if (memcmp(&sin6, cp->name, sizeof(sin6)) != 0) e(0);
+
+                       /* Test send(2) on UDP sockets. */
+                       if (type != SOCK_STREAM) {
+                               r = send(fd, "A", 1, 0);
+
+                               /*
+                                * For remote (rejected) addresses and scope
+                                * mixing, actual send calls may fail after the
+                                * connect succeeded.
+                                */
+                               if (r == -1 &&
+                                   !(cp->flags & (F_REM | F_MIX))) e(0);
+                               else if (r != -1 && r != 1) e(0);
+
+                               if (r != 1 && fd2 != -1) {
+                                       if (close(fd2) != 0) e(0);
+                                       fd2 = -1;
+                               }
+                       }
+
+                       if (fd2 == -1) {
+                               if (close(fd) != 0) e(0);
+
+                               continue;
+                       }
+
+                       /*
+                        * The connect or send call succeeded, so we should now
+                        * be able to check the other end.
+                        */
+                       if (type == SOCK_STREAM) {
+                               /* Test accept(2). */
+                               len = sizeof(sin6);
+                               if ((fd3 = accept(fd2,
+                                   (struct sockaddr *)&sin6, &len)) < 0) e(0);
+                               if (len != sizeof(sin6)) e(0);
+
+                               if (close(fd2) != 0) e(0);
+
+                               if (sin6.sin6_port != port) e(0);
+                               sin6.sin6_port = 0;
+
+                               if (memcmp(&sin6, sin6p, sizeof(sin6)) != 0)
+                                       e(0);
+
+                               /* Test getpeername(2). */
+                               if (getpeername(fd3, (struct sockaddr *)&sin6,
+                                   &len) != 0) e(0);
+                               if (len != sizeof(sin6)) e(0);
+
+                               if (sin6.sin6_port != port) e(0);
+                               sin6.sin6_port = 0;
+
+                               if (memcmp(&sin6, sin6p, sizeof(sin6)) != 0)
+                                       e(0);
+
+                               /* Test getsockname(2). */
+                               if (getsockname(fd3, (struct sockaddr *)&sin6,
+                                   &len) != 0) e(0);
+                               if (len != sizeof(sin6)) e(0);
+
+                               if (sin6.sin6_port != htons(TEST_PORT_A)) e(0);
+                               sin6.sin6_port = 0;
+
+                               if (memcmp(&sin6, cp->name, sizeof(sin6)) != 0)
+                                       e(0);
+
+                               if (close(fd3) != 0) e(0);
+                       } else {
+                               /* Test recvfrom(2). */
+                               len = sizeof(sin6);
+                               if (recvfrom(fd2, buf, sizeof(buf), 0,
+                                   (struct sockaddr *)&sin6, &len) != 1) e(0);
+
+                               if (buf[0] != 'A') e(0);
+                               if (len != sizeof(sin6)) e(0);
+
+                               if (sin6.sin6_port != port) e(0);
+                               sin6.sin6_port = 0;
+
+                               if (memcmp(&sin6, sin6p, sizeof(sin6)) != 0)
+                                       e(0);
+
+                               if (close(fd2) != 0) e(0);
+                       }
+
+                       if (close(fd) != 0) e(0);
+               }
+
+               if (type == SOCK_STREAM)
+                       continue;
+
+               /* Test sendto(2). */
+               for (j = 0; j < __arraycount(conn_array) - !have_mix; j++) {
+                       cp = &conn_array[j];
+
+                       /*
+                        * We cannot test remote addresses without having bound
+                        * to a local address, because we may end up generating
+                        * external traffic as a result.
+                        */
+                       if ((bp->flags & F_ANY) && (cp->flags & F_REM))
+                               continue;
+
+                       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+                       val = 0;
+                       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                           sizeof(val)) != 0) e(0);
+
+                       if (bp->addr != NULL) {
+                               if (bind(fd, (struct sockaddr *)bp->addr,
+                                  sizeof(*bp->addr)) != 0) e(0);
+
+                               len = sizeof(sin6);
+                               if (getsockname(fd, (struct sockaddr *)&sin6,
+                                   &len) != 0) e(0);
+
+                               port = sin6.sin6_port;
+                       } else
+                               port = 0;
+
+                       memcpy(&sin6, cp->addr, sizeof(sin6));
+                       if (type != SOCK_RAW)
+                               sin6.sin6_port = htons(TEST_PORT_B);
+
+                       if ((exp = cp->res) == 0) {
+                               if (cp->flags & (F_REM | F_MIX))
+                                       exp = EHOSTUNREACH;
+                       }
+
+                       /*
+                        * The IPv4/IPv6 mismatch check precedes most other
+                        * checks, but (currently) not the bad-scope-ID check.
+                        */
+                       if (exp != ENXIO && !(bp->flags & F_ANY) &&
+                           ((bp->flags ^ cp->flags) & F_V4))
+                               exp = EINVAL;
+
+                       /*
+                        * If we expect the sendto(2) call to succeed and to be
+                        * able to receive the packet, create a receiving
+                        * socket to test recvfrom(2) addresses.
+                        */
+                       if (exp == 0 && !(cp->flags & F_REM)) {
+                               if ((fd2 = socket(AF_INET6, type,
+                                   protocol)) < 0) e(0);
+
+                               val = 0;
+                               if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY,
+                                   &val, sizeof(val)) != 0) e(0);
+
+                               val = 1;
+                               if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR,
+                                   &val, sizeof(val)) != 0) e(0);
+
+                               memcpy(&rsin6, cp->name, sizeof(rsin6));
+                               if (type != SOCK_RAW)
+                                       rsin6.sin6_port = htons(TEST_PORT_B);
+
+                               if (bind(fd2, (struct sockaddr *)&rsin6,
+                                   sizeof(rsin6)) != 0) e(0);
+                       } else
+                               fd2 = -1;
+
+                       r = sendto(fd, "B", 1, 0, (struct sockaddr *)&sin6,
+                           sizeof(sin6));
+
+                       if (r != 1) {
+                               if (r != -1 || exp != errno) e(0);
+
+                               if (close(fd) != 0) e(0);
+
+                               continue;
+                       }
+
+                       if (exp != 0) e(0);
+
+                       /*
+                        * The sendto(2) call should assign a local port to the
+                        * socket if none was assigned before, but it must not
+                        * assign a local address.
+                        */
+                       len = sizeof(sin6);
+                       if (getsockname(fd, (struct sockaddr *)&sin6,
+                           &len) != 0) e(0);
+                       if (len != sizeof(sin6)) e(0);
+
+                       if (type != SOCK_RAW) {
+                               if (sin6.sin6_port == 0) e(0);
+                               if (port != 0 && port != sin6.sin6_port) e(0);
+                       } else
+                               if (sin6.sin6_port != 0) e(0);
+                       port = sin6.sin6_port;
+
+                       sin6.sin6_port = 0;
+                       if (memcmp(&sin6, bp->name, sizeof(sin6)) != 0) e(0);
+
+                       if (fd2 != -1) {
+                               /* Test recvfrom(2) on the receiving socket. */
+                               len = sizeof(sin6);
+                               if (recvfrom(fd2, buf, sizeof(buf), 0,
+                                   (struct sockaddr *)&sin6, &len) != 1) e(0);
+
+                               if (buf[0] != 'B') e(0);
+                               if (len != sizeof(sin6)) e(0);
+
+                               if (sin6.sin6_port != port) e(0);
+                               sin6.sin6_port = 0;
+
+                               if (bp->flags & F_ANY)
+                                       sin6p = cp->name;
+                               else
+                                       sin6p = bp->name;
+
+                               if (memcmp(&sin6, sin6p, sizeof(sin6)) != 0)
+                                       e(0);
+
+                               if (close(fd2) != 0) e(0);
+                       }
+
+                       if (close(fd) != 0) e(0);
+               }
+       }
+
+       /*
+        * Test that scoped addresses actually work as expected.  For this we
+        * need two interfaces with assigned link-local addresses, one of which
+        * being the loopback interface.  Start by finding another one.
+        */
+       if (getifaddrs(&ifa) != 0) e(0);
+
+       found = 0;
+       for (ifp = ifa; ifp != NULL; ifp = ifp->ifa_next) {
+               if (strcmp(ifp->ifa_name, LOOPBACK_IFNAME) == 0)
+                       continue;
+
+               if (!(ifp->ifa_flags & IFF_UP) || ifp->ifa_addr == NULL ||
+                   ifp->ifa_addr->sa_family != AF_INET6)
+                       continue;
+
+               memcpy(&sin6, ifp->ifa_addr, sizeof(sin6));
+
+               if (!IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr))
+                       continue;
+
+               /*
+                * Not only the interface, but also the link has to be up for
+                * this to work.  lwIP will drop all packets, including those
+                * sent to locally assigned addresses, if the link is down.
+                * Of course, figuring out whether the interface link is down
+                * is by no means convenient, especially if we want to do it
+                * right (i.e., not rely on getifaddrs' address sorting).
+                */
+               link_state = LINK_STATE_DOWN;
+
+               for (ifp2 = ifa; ifp2 != NULL; ifp2 = ifp2->ifa_next) {
+                       if (!strcmp(ifp2->ifa_name, ifp->ifa_name) &&
+                           ifp2->ifa_addr != NULL &&
+                           ifp2->ifa_addr->sa_family == AF_LINK &&
+                           ifp2->ifa_data != NULL) {
+                               memcpy(&link_state, &((struct if_data *)
+                                   ifp2->ifa_data)->ifi_link_state,
+                                   sizeof(link_state));
+
+                               break;
+                       }
+               }
+
+               if (link_state == LINK_STATE_DOWN)
+                       continue;
+
+               /*
+                * In addition, the address has to be in a state where it can
+                * be used as source address.  In practice, that means it must
+                * not be in ND6 duplicated or tentative state.
+                */
+               memset(&ifr, 0, sizeof(ifr));
+               strlcpy(ifr.ifr_name, ifp->ifa_name, sizeof(ifr.ifr_name));
+               memcpy(&ifr.ifr_addr, &sin6, sizeof(sin6));
+
+               if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+               if (ioctl(fd, SIOCGIFAFLAG_IN6, &ifr) != 0) e(0);
+
+               if (close(fd) != 0) e(0);
+
+               if (ifr.ifr_ifru.ifru_flags6 &
+                   (IN6_IFF_DUPLICATED | IN6_IFF_TENTATIVE))
+                       continue;
+
+               /* Compensate for poor decisions made by the KAME project. */
+               inet6_getscopeid(&sin6, INET6_IS_ADDR_LINKLOCAL);
+
+               if (sin6.sin6_scope_id == 0 || sin6.sin6_scope_id == ifindex)
+                       e(0);
+
+               found = 1;
+
+               break;
+       }
+
+       freeifaddrs(ifa);
+
+       /*
+        * If no second interface with a link-local address was found, we
+        * cannot perform the rest of this subtest.
+        */
+       if (!found)
+               return;
+
+       /*
+        * Create one socket that binds to the link-local address of the
+        * non-loopback interface.  The main goal of this subtest is to ensure
+        * that traffic directed to that same link-local address but with the
+        * loopback scope ID does not arrive on this socket.
+        */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+
+       ifindex2 = sin6.sin6_scope_id;
+
+       if (type == SOCK_STREAM) {
+               if (listen(fd, 2) != 0) e(0);
+
+               if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               /* Connecting to the loopback-scope address should time out. */
+               signal(SIGALRM, socklib_got_signal);
+               alarm(1);
+
+               sin6.sin6_scope_id = ifindex;
+
+               if (connect(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != -1)
+                       e(0);
+
+               if (errno != EINTR) e(0);
+
+               if (close(fd2) != 0) e(0);
+
+               /* Connecting to the real interface's address should work. */
+               if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               sin6.sin6_scope_id = ifindex2;
+
+               if (connect(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != 0)
+                       e(0);
+
+               if (close(fd2) != 0) e(0);
+       } else {
+               /*
+                * First connect+send.  Sending to the loopback-scope address
+                * should result in a rejected packet.
+                */
+               if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               sin6.sin6_scope_id = ifindex;
+
+               if (connect(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != 0)
+                       e(0);
+
+               if (send(fd2, "C", 1, 0) != -1) e(0);
+               if (errno != EHOSTUNREACH) e(0);
+
+               if (close(fd2) != 0) e(0);
+
+               /* Sending to the real-interface address should work. */
+               if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               sin6.sin6_scope_id = ifindex2;
+
+               if (connect(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != 0)
+                       e(0);
+
+               if (send(fd2, "D", 1, 0) != 1) e(0);
+
+               if (close(fd2) != 0) e(0);
+
+               /*
+                * Then sendto.  Sending to the loopback-scope address should
+                * result in a rejected packet.
+                */
+               if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               sin6.sin6_scope_id = ifindex;
+
+               if (sendto(fd2, "E", 1, 0, (struct sockaddr *)&sin6,
+                   sizeof(sin6)) != -1) e(0);
+               if (errno != EHOSTUNREACH) e(0);
+
+               if (close(fd2) != 0) e(0);
+
+               /* Sending to the real-interface address should work. */
+               if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               sin6.sin6_scope_id = ifindex2;
+
+               if (sendto(fd2, "F", 1, 0, (struct sockaddr *)&sin6,
+                   sizeof(sin6)) != 1) e(0);
+
+               if (close(fd2) != 0) e(0);
+
+               len = sizeof(sin6);
+               if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6,
+                   &len) != 1) e(0);
+               if (buf[0] != 'D') e(0);
+
+               if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6,
+                   &len) != 1) e(0);
+               if (buf[0] != 'F') e(0);
+       }
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test multicast support for the given socket type, which may be SOCK_DGRAM or
+ * SOCK_RAW.
+ */
+void
+socklib_test_multicast(int type, int protocol)
+{
+       struct sockaddr_in sinA, sinB, sin_array[3];
+       struct sockaddr_in6 sin6A, sin6B, sin6_array[3];
+       struct ip_mreq imr;
+       struct ipv6_mreq ipv6mr;
+       struct in6_pktinfo ipi6;
+       struct iovec iov;
+       struct msghdr msg;
+       struct cmsghdr *cmsg;
+       socklen_t len, hdrlen;
+       unsigned int count, ifindex, ifindex2;
+       union {
+               struct cmsghdr cmsg;
+               char buf[256];
+       } control;
+       char buf[sizeof(struct ip) + 1], *buf2, name[IF_NAMESIZE];
+       uint8_t byte, ttl;
+       int i, j, r, fd, fd2, val;
+
+       /*
+        * Start with testing join/leave mechanics, for both IPv4 and IPv6.
+        * Note that we cannot test specifying no interface along with a
+        * multicast address (except for scoped IPv6 addresses), because the
+        * auto-selected interface is likely a public one, and joining the
+        * group will thus create external traffic, which is generally
+        * something we want to avoid in the tests.
+        */
+       if ((fd = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       memset(&imr, 0, sizeof(imr));
+
+       /* Basic join-leave combo. */
+       imr.imr_multiaddr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+       imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       /* Joining the same multicast group twice is an error. */
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != EEXIST) e(0);
+
+       /* If an interface address is specified, it must match an interface. */
+       imr.imr_interface.s_addr = htonl(TEST_BLACKHOLE_IPV4);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       /* The given multicast address must be an actual multicast address. */
+       imr.imr_multiaddr.s_addr = htonl(INADDR_ANY);
+       imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       imr.imr_multiaddr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       /* Leaving a multicast group not joined is an error. */
+       imr.imr_multiaddr.s_addr =
+           htonl(ntohl(inet_addr(TEST_MULTICAST_IPV4)) + 1);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       /*
+        * When leaving a group, an interface address need not be specified,
+        * even if one was specified when joining.  As mentioned, we cannot
+        * test joining the same address on multiple interfaces, though.
+        */
+       imr.imr_multiaddr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+       imr.imr_interface.s_addr = htonl(INADDR_ANY);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       /* There must be a reasonable per-socket group membership limit. */
+       imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+
+       for (count = 0; count < IP_MAX_MEMBERSHIPS + 1; count++) {
+               imr.imr_multiaddr.s_addr =
+                   htonl(ntohl(inet_addr(TEST_MULTICAST_IPV4)) + count);
+
+               r = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+                   sizeof(imr));
+
+               if (r != 0) {
+                       if (r != -1 || errno != ENOBUFS) e(0);
+                       break;
+               }
+       }
+       if (count < 8 || count > IP_MAX_MEMBERSHIPS) e(0);
+
+       /* Test leaving a group at the start of the per-socket list. */
+       imr.imr_multiaddr.s_addr =
+           htonl(ntohl(inet_addr(TEST_MULTICAST_IPV4)) + count - 1);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       /* Test leaving a group in the middle of the per-socket list. */
+       imr.imr_multiaddr.s_addr =
+           htonl(ntohl(inet_addr(TEST_MULTICAST_IPV4)) + count / 2);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       /* Test leaving a group at the end of the per-socket list. */
+       imr.imr_multiaddr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Still basic join/leave mechanics.. on to IPv6.. */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&ipv6mr, 0, sizeof(ipv6mr));
+
+       /* Basic join-leave combo. */
+       ifindex = if_nametoindex(LOOPBACK_IFNAME);
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       /* Joining the same multicast group twice is an error. */
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != EEXIST) e(0);
+
+       /* If an interface index is specified, it must be valid. */
+       ipv6mr.ipv6mr_interface = BAD_IFINDEX;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ENXIO) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ENXIO) e(0);
+
+       ipv6mr.ipv6mr_interface = 0x80000000UL | ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ENXIO) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ENXIO) e(0);
+
+       /* The given multicast address must be an actual multicast address. */
+       ipv6mr.ipv6mr_interface = ifindex;
+       memcpy(&ipv6mr.ipv6mr_multiaddr, &in6addr_any, sizeof(in6addr_any));
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       memcpy(&ipv6mr.ipv6mr_multiaddr, &in6addr_loopback,
+           sizeof(in6addr_loopback));
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       /* Leaving a multicast group not joined is an error. */
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_multiaddr.s6_addr[15]++;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       /*
+        * When leaving a group, an interface index need not be specified,
+        * even if one was specified when joining.  If one is specified, it
+        * must match, though.  As mentioned, we cannot test joining the same
+        * address on multiple interfaces, though.
+        */
+       ipv6mr.ipv6mr_multiaddr.s6_addr[15]--;
+       ipv6mr.ipv6mr_interface = ifindex + 1; /* lazy: may or may not exist */
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ENXIO && errno != ESRCH) e(0);
+
+       ipv6mr.ipv6mr_interface = 0;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       /* For link-local addresses, an interface must always be specified. */
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6_LL,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = 0;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       ipv6mr.ipv6mr_interface = 0;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       /* IPv4-mapped IPv6 multicast addresses are currently not supported. */
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       if (inet_pton(AF_INET6, "::ffff:"TEST_MULTICAST_IPV4,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       /*
+        * There must be a reasonable per-socket group membership limit.
+        * Apparently there is no IPv6 equivalent of IP_MAX_MEMBERSHIPS..
+        */
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       for (count = 0; count < IP_MAX_MEMBERSHIPS + 1; count++) {
+               r = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+                   sizeof(ipv6mr));
+
+               if (r != 0) {
+                       if (r != -1 || errno != ENOBUFS) e(0);
+                       break;
+               }
+
+               ipv6mr.ipv6mr_multiaddr.s6_addr[15]++;
+       }
+       if (count < 8 || count > IP_MAX_MEMBERSHIPS) e(0);
+
+       /* Test leaving a group at the start of the per-socket list. */
+       ipv6mr.ipv6mr_multiaddr.s6_addr[15]--;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       /* Test leaving a group in the middle of the per-socket list. */
+       ipv6mr.ipv6mr_multiaddr.s6_addr[15] -= count / 2;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       /* Test leaving a group at the end of the per-socket list. */
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != ESRCH) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Test sending multicast packets, multicast transmission options, and
+        * basic receipt.  Note that we cannot test IP(V6)_MULTICAST_LOOP
+        * because no extra duplicates are generated on loopback interfaces.
+        */
+       if ((fd = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       /* For UDP, get an assigned port number. */
+       memset(&sinA, 0, sizeof(sinA));
+       sinA.sin_family = AF_INET;
+
+       if (type == SOCK_DGRAM) {
+               sinA.sin_addr.s_addr = htonl(INADDR_ANY);
+
+               if (bind(fd, (struct sockaddr *)&sinA,
+                   sizeof(sinA)) != 0) e(0);
+
+               len = sizeof(sinA);
+               if (getsockname(fd, (struct sockaddr *)&sinA, &len) != 0) e(0);
+       }
+
+       imr.imr_multiaddr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+       imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       /* Regular packet, default unicast TTL, sendto. */
+       sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (sendto(fd2, "A", 1, 0, (struct sockaddr *)&sinA,
+           sizeof(sinA)) != 1) e(0);
+
+       /* Multicast packet, default multicast TTL, sendto. */
+       if (setsockopt(fd2, IPPROTO_IP, IP_MULTICAST_IF, &sinA.sin_addr,
+           sizeof(sinA.sin_addr)) != 0) e(0);
+
+       sinA.sin_addr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+
+       if (sendto(fd2, "B", 1, 0, (struct sockaddr *)&sinA,
+           sizeof(sinA)) != 1) e(0);
+
+       /* Multicast packet, custom multicast TTL, connect+send. */
+       byte = 123;
+       if (setsockopt(fd2, IPPROTO_IP, IP_MULTICAST_TTL, &byte,
+           sizeof(byte)) != 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
+
+       if (send(fd2, "C", 1, 0) != 1) e(0);
+
+       /* Receive and validate what we sent. */
+       len = sizeof(sinA);
+       if (getsockname(fd2, (struct sockaddr *)&sinA, &len) != 0) e(0);
+
+       len = sizeof(ttl);
+       if (getsockopt(fd2, IPPROTO_IP, IP_TTL, &ttl, &len) != 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &val, sizeof(val)) != 0)
+               e(0);
+
+       hdrlen = (type == SOCK_RAW) ? sizeof(struct ip) : 0;
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = hdrlen + 1;
+
+       for (i = 0; i < 3; ) {
+               memset(&msg, 0, sizeof(msg));
+               msg.msg_name = &sinB;
+               msg.msg_namelen = sizeof(sinB);
+               msg.msg_iov = &iov;
+               msg.msg_iovlen = 1;
+               msg.msg_control = control.buf;
+               msg.msg_controllen = sizeof(control);
+
+               r = recvmsg(fd, &msg, 0);
+               if (r < 0) e(0);
+
+               if (msg.msg_namelen != sizeof(sinB)) e(0);
+
+               /*
+                * There is a tiny possibility that we receive other packets
+                * on the receiving socket, as it is not bound to a particular
+                * address, and there is currently no way to bind a socket to
+                * a particular interface.  We therefore skip packets not from
+                * the sending socket, conveniently testing the accuracy of the
+                * reported source address as a side effect.
+                */
+               if (memcmp(&sinA, &sinB, sizeof(sinA)))
+                       continue;
+
+               if (r != hdrlen + 1) e(0);
+               if (buf[hdrlen] != 'A' + i) e(0);
+
+               if (msg.msg_flags & MSG_BCAST) e(0);
+
+               if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) e(0);
+               if (cmsg->cmsg_level != IPPROTO_IP) e(0);
+               if (cmsg->cmsg_type != IP_TTL) e(0);
+               if (cmsg->cmsg_len != CMSG_LEN(sizeof(byte))) e(0);
+               memcpy(&byte, CMSG_DATA(cmsg), sizeof(byte));
+
+               switch (i) {
+               case 0:
+                       if (msg.msg_flags & MSG_MCAST) e(0);
+                       if (byte != ttl) e(0);
+                       break;
+               case 1:
+                       if (!(msg.msg_flags & MSG_MCAST)) e(0);
+                       if (byte != 1) e(0);
+                       break;
+               case 2:
+                       if (!(msg.msg_flags & MSG_MCAST)) e(0);
+                       if (byte != 123) e(0);
+                       break;
+               }
+
+               i++;
+       }
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* Still the send tests, but now IPv6.. */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       /* For UDP, get an assigned port number. */
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+
+       if (type == SOCK_DGRAM) {
+               if (bind(fd, (struct sockaddr *)&sin6A,
+                   sizeof(sin6A)) != 0) e(0);
+
+               len = sizeof(sin6A);
+               if (getsockname(fd, (struct sockaddr *)&sin6A, &len) != 0)
+                       e(0);
+       }
+
+       memcpy(&sin6B, &sin6A, sizeof(sin6B));
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       /* Regular packet, default unicast TTL, sendto. */
+       if (inet_pton(AF_INET6, LOOPBACK_IPV6_LL, &sin6A.sin6_addr) != 1) e(0);
+       sin6A.sin6_scope_id = ifindex;
+
+       if (sendto(fd2, "D", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       /* Multicast packet, default multicast TTL, sendto. */
+       val = (int)ifindex;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &sin6A.sin6_addr) != 1) e(0);
+       sin6A.sin6_scope_id = 0;
+
+       if (sendto(fd2, "E", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       /* Multicast packet, custom multicast TTL, connect+send. */
+       val = 125;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (send(fd2, "F", 1, 0) != 1) e(0);
+
+       len = sizeof(sin6A);
+       if (getsockname(fd2, (struct sockaddr *)&sin6A, &len) != 0) e(0);
+
+       /*
+        * Repeat the last two tests, but now with a link-local multicast
+        * address.  In particular link-local destination addresses do not need
+        * a zone ID, and the system should be smart enough to pick the right
+        * zone ID if an outgoing multicast interface is configured.  Zone
+        * violations should be detected and result in errors.
+        */
+       if (close(fd2) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       if (bind(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       memcpy(&sin6A, &sin6B, sizeof(sin6A));
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6_LL,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       /* Link-local multicast packet, sendto. */
+       val = (int)ifindex;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6_LL,
+           &sin6A.sin6_addr) != 1) e(0);
+       sin6A.sin6_scope_id = 0;
+
+       if (sendto(fd2, "G", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       sin6A.sin6_scope_id = ifindex + 1; /* lazy: may or may not be valid */
+
+       if (sendto(fd2, "X", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != -1) e(0);
+       if (errno != ENXIO && errno != EHOSTUNREACH) e(0);
+
+       sin6A.sin6_scope_id = ifindex;
+
+       if (sendto(fd2, "H", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       /* Link-local multicast packet, connect+send. */
+       sin6A.sin6_scope_id = 0;
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (send(fd2, "I", 1, 0) != 1) e(0);
+
+       len = sizeof(sin6A);
+       if (getsockname(fd2, (struct sockaddr *)&sin6A, &len) != 0) e(0);
+
+       /* Receive and validate what we sent. */
+       len = sizeof(val);
+       if (getsockopt(fd2, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+           &len) != 0) e(0);
+       ttl = (uint8_t)val;
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val,
+           sizeof(val)) != 0) e(0);
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = 1;
+
+       for (i = 0; i < 6; ) {
+               memset(&msg, 0, sizeof(msg));
+               msg.msg_name = &sin6B;
+               msg.msg_namelen = sizeof(sin6B);
+               msg.msg_iov = &iov;
+               msg.msg_iovlen = 1;
+               msg.msg_control = control.buf;
+               msg.msg_controllen = sizeof(control);
+
+               r = recvmsg(fd, &msg, 0);
+               if (r < 0) e(0);
+
+               if (msg.msg_namelen != sizeof(sin6B)) e(0);
+
+               if (memcmp(&sin6A, &sin6B, sizeof(sin6A)))
+                       continue;
+
+               if (r != 1) e(0);
+               if (buf[0] != 'D' + i) e(0);
+
+               if (msg.msg_flags & MSG_BCAST) e(0);
+
+               if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) e(0);
+               if (cmsg->cmsg_level != IPPROTO_IPV6) e(0);
+               if (cmsg->cmsg_type != IPV6_HOPLIMIT) e(0);
+               if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0);
+               memcpy(&val, CMSG_DATA(cmsg), sizeof(val));
+
+               switch (i) {
+               case 0:
+                       if (msg.msg_flags & MSG_MCAST) e(0);
+                       if (val != (int)ttl) e(0);
+                       break;
+               case 1:
+               case 3:
+               case 4:
+               case 5:
+                       if (!(msg.msg_flags & MSG_MCAST)) e(0);
+                       if (val != 1) e(0);
+                       break;
+               case 2:
+                       if (!(msg.msg_flags & MSG_MCAST)) e(0);
+                       if (val != 125) e(0);
+                       break;
+               }
+
+               i++;
+       }
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Test receiving multicast packets on a bound socket.  We have already
+        * tested receiving packets on an unbound socket, so we need not
+        * incorporate that into this test as well.
+        */
+       memset(sin_array, 0, sizeof(sin_array));
+       sin_array[0].sin_family = AF_INET;
+       sin_array[0].sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       sin_array[1].sin_family = AF_INET;
+       sin_array[1].sin_addr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+       sin_array[2].sin_family = AF_INET;
+       sin_array[2].sin_addr.s_addr =
+           htonl(ntohl(sin_array[1].sin_addr.s_addr) + 1);
+
+       for (i = 0; i < __arraycount(sin_array); i++) {
+               if ((fd = socket(AF_INET, type, protocol)) < 0) e(0);
+
+               if (bind(fd, (struct sockaddr *)&sin_array[i],
+                   sizeof(sin_array[i])) != 0) e(0);
+
+               imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+               memcpy(&imr.imr_multiaddr, &sin_array[1].sin_addr,
+                   sizeof(imr.imr_multiaddr));
+               if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+                   sizeof(imr)) != 0) e(0);
+
+               memcpy(&imr.imr_multiaddr, &sin_array[2].sin_addr,
+                   sizeof(imr.imr_multiaddr));
+               if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+                   sizeof(imr)) != 0) e(0);
+
+               len = sizeof(sinA);
+               if (getsockname(fd, (struct sockaddr *)&sinA, &len) != 0) e(0);
+
+               if ((fd2 = socket(AF_INET, type, protocol)) < 0) e(0);
+
+               if (setsockopt(fd2, IPPROTO_IP, IP_MULTICAST_IF,
+                   &imr.imr_interface, sizeof(imr.imr_interface)) != 0) e(0);
+
+               for (j = 0; j < __arraycount(sin_array); j++) {
+                       memcpy(&sinA.sin_addr, &sin_array[j].sin_addr,
+                           sizeof(sinA.sin_addr));
+
+                       byte = 'A' + j;
+                       if (sendto(fd2, &byte, sizeof(byte), 0,
+                           (struct sockaddr *)&sinA,
+                           sizeof(sinA)) != sizeof(byte)) e(0);
+               }
+
+               if (recv(fd, buf, sizeof(buf), 0) !=
+                   hdrlen + sizeof(byte)) e(0);
+               if (buf[hdrlen] != 'A' + i) e(0);
+
+               if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+               if (errno != EWOULDBLOCK) e(0);
+
+               if (close(fd2) != 0) e(0);
+               if (close(fd) != 0) e(0);
+       }
+
+       /* Still testing receiving on bound sockets, now IPv6.. */
+       memset(sin6_array, 0, sizeof(sin6_array));
+       sin6_array[0].sin6_family = AF_INET6;
+       memcpy(&sin6_array[0].sin6_addr, &in6addr_loopback,
+           sizeof(sin6_array[0].sin6_addr));
+       sin6_array[1].sin6_family = AF_INET6;
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &sin6_array[1].sin6_addr) != 1) e(0);
+       sin6_array[2].sin6_family = AF_INET6;
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6_LL,
+           &sin6_array[2].sin6_addr) != 1) e(0);
+
+       /*
+        * As with unicast addresses, binding to link-local multicast addresses
+        * requires a proper zone ID.
+        */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6_array[2],
+           sizeof(sin6_array[2])) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       sin6_array[2].sin6_scope_id = BAD_IFINDEX;
+
+       if (bind(fd, (struct sockaddr *)&sin6_array[2],
+           sizeof(sin6_array[2])) != -1) e(0);
+       if (errno != ENXIO) e(0);
+
+       sin6_array[2].sin6_scope_id = ifindex;
+
+       if (close(fd) != 0) e(0);
+
+       for (i = 0; i < __arraycount(sin6_array); i++) {
+               if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               if (bind(fd, (struct sockaddr *)&sin6_array[i],
+                   sizeof(sin6_array[i])) != 0) e(0);
+
+               ipv6mr.ipv6mr_interface = ifindex;
+               memcpy(&ipv6mr.ipv6mr_multiaddr, &sin6_array[1].sin6_addr,
+                   sizeof(ipv6mr.ipv6mr_multiaddr));
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+                   sizeof(ipv6mr)) != 0) e(0);
+
+               memcpy(&ipv6mr.ipv6mr_multiaddr, &sin6_array[2].sin6_addr,
+                   sizeof(ipv6mr.ipv6mr_multiaddr));
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+                   sizeof(ipv6mr)) != 0) e(0);
+
+               len = sizeof(sin6A);
+               if (getsockname(fd, (struct sockaddr *)&sin6A,
+                   &len) != 0) e(0);
+
+               if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+               val = (int)ifindex;
+               if (setsockopt(fd2, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+                   sizeof(val)) != 0) e(0);
+
+               for (j = 0; j < __arraycount(sin6_array); j++) {
+                       memcpy(&sin6A.sin6_addr, &sin6_array[j].sin6_addr,
+                           sizeof(sin6A.sin6_addr));
+
+                       byte = 'A' + j;
+                       if (sendto(fd2, &byte, sizeof(byte), 0,
+                           (struct sockaddr *)&sin6A,
+                           sizeof(sin6A)) != sizeof(byte)) e(0);
+               }
+
+               if (recv(fd, buf, sizeof(buf), 0) != sizeof(byte)) e(0);
+               if (buf[0] != 'A' + i) e(0);
+
+               if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+               if (errno != EWOULDBLOCK) e(0);
+
+               if (close(fd2) != 0) e(0);
+               if (close(fd) != 0) e(0);
+       }
+
+       /*
+        * Now test *sending* on a socket bound to a multicast address.  The
+        * multicast address must not show up as the packet's source address.
+        * No actual multicast groups are involved here.
+        */
+       if ((fd = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin_array[1],
+           sizeof(sin_array[1])) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       if (bind(fd2, (struct sockaddr *)&sin_array[0],
+           sizeof(sin_array[0])) != 0) e(0);
+
+       len = sizeof(sinA);
+       if (getsockname(fd2, (struct sockaddr *)&sinA, &len) != 0) e(0);
+
+       if (sendto(fd, "D", 1, 0, (struct sockaddr *)&sinA, sizeof(sinA)) != 1)
+               e(0);
+
+       len = sizeof(sinB);
+       if (recvfrom(fd2, buf, sizeof(buf), 0, (struct sockaddr *)&sinB,
+           &len) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'D') e(0);
+
+       if (sinB.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* Sending from a bound socket, IPv6 version.. */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6_array[1],
+           sizeof(sin6_array[1])) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       if (bind(fd2, (struct sockaddr *)&sin6_array[0],
+           sizeof(sin6_array[0])) != 0) e(0);
+
+       len = sizeof(sin6A);
+       if (getsockname(fd2, (struct sockaddr *)&sin6A, &len) != 0) e(0);
+
+       if (sendto(fd, "E", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       len = sizeof(sin6B);
+       if (recvfrom(fd2, buf, sizeof(buf), 0, (struct sockaddr *)&sin6B,
+           &len) != 1) e(0);
+       if (buf[0] != 'E') e(0);
+
+       if (!IN6_IS_ADDR_LOOPBACK(&sin6B.sin6_addr)) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /*
+        * A quick, partial test to see if connecting to a particular address
+        * does not accidentally block packet receipt.  What we do not test is
+        * whether connecting does filter traffic from other sources.
+        */
+       if ((fd = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       memset(&sinA, 0, sizeof(sinA));
+       sinA.sin_family = AF_INET;
+       sinA.sin_addr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+
+       if (bind(fd, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
+
+       len = sizeof(sinA);
+       if (getsockname(fd, (struct sockaddr *)&sinA, &len) != 0) e(0);
+
+       imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+       imr.imr_multiaddr.s_addr = sinA.sin_addr.s_addr;
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       memset(&sinB, 0, sizeof(sinB));
+       sinB.sin_family = AF_INET;
+       sinB.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (bind(fd2, (struct sockaddr *)&sinB, sizeof(sinB)) != 0) e(0);
+
+       len = sizeof(sinB);
+       if (getsockname(fd2, (struct sockaddr *)&sinB, &len) != 0) e(0);
+
+       if (connect(fd, (struct sockaddr *)&sinB, sizeof(sinB)) != 0) e(0);
+
+       /* Note that binding to a particular source address is not enough! */
+       if (setsockopt(fd2, IPPROTO_IP, IP_MULTICAST_IF, &imr.imr_interface,
+           sizeof(imr.imr_interface)) != 0) e(0);
+
+       if (sendto(fd2, "F", 1, 0, (struct sockaddr *)&sinA,
+           sizeof(sinA)) != 1) e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'F') e(0);
+
+       if (close(fd) != 0) e(0);
+       if (close(fd2) != 0) e(0);
+
+       /* Also try connecting with IPv6. */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6_LL,
+           &sin6A.sin6_addr) != 1) e(0);
+       sin6A.sin6_scope_id = ifindex;
+
+       if (bind(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       len = sizeof(sin6A);
+       if (getsockname(fd, (struct sockaddr *)&sin6A, &len) != 0) e(0);
+
+       ipv6mr.ipv6mr_interface = ifindex;
+       memcpy(&ipv6mr.ipv6mr_multiaddr, &sin6A.sin6_addr,
+           sizeof(ipv6mr.ipv6mr_multiaddr));
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&sin6B, 0, sizeof(sin6B));
+       sin6B.sin6_family = AF_INET6;
+       memcpy(&sin6B.sin6_addr, &in6addr_loopback, sizeof(sin6B.sin6_addr));
+
+       if (bind(fd2, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
+
+       len = sizeof(sin6B);
+       if (getsockname(fd2, (struct sockaddr *)&sin6B, &len) != 0) e(0);
+
+       if (connect(fd, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
+
+       /* Unlike with IPv4, here the interface is implied by the zone. */
+       if (sendto(fd2, "G", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'G') e(0);
+
+       if (close(fd) != 0) e(0);
+       if (close(fd2) != 0) e(0);
+
+       /*
+        * Test multiple receivers.  For UDP, we need to set the SO_REUSEADDR
+        * option on all sockets for this to be guaranteed to work.
+        */
+       if ((fd = socket(AF_INET, type, protocol)) < 0) e(0);
+       if ((fd2 = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       memset(&sinA, 0, sizeof(sinA));
+       sinA.sin_family = AF_INET;
+
+       if (type == SOCK_DGRAM) {
+               if (bind(fd, (struct sockaddr *)&sinA, sizeof(sinA)) != 0)
+                       e(0);
+
+               len = sizeof(sinA);
+               if (getsockname(fd, (struct sockaddr *)&sinA, &len) != 0)
+                       e(0);
+
+               val = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (bind(fd2, (struct sockaddr *)&sinA, sizeof(sinA)) != 0)
+                       e(0);
+       }
+
+       imr.imr_multiaddr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+       imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd2, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr.imr_interface,
+           sizeof(imr.imr_interface)) != 0) e(0);
+
+       sinA.sin_addr.s_addr = imr.imr_multiaddr.s_addr;
+
+       if (sendto(fd, "H", 1, 0, (struct sockaddr *)&sinA, sizeof(sinA)) != 1)
+               e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'H') e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'H') e(0);
+
+       /*
+        * Also test with a larger buffer, to ensure that packet duplication
+        * actually works properly.  As of writing, we need to patch lwIP to
+        * make this work at all.
+        */
+       len = 8000;
+       if ((buf2 = malloc(hdrlen + len + 1)) == NULL) e(0);
+       buf2[len - 1] = 'I';
+
+       if (sendto(fd, buf2, len, 0, (struct sockaddr *)&sinA,
+           sizeof(sinA)) != len) e(0);
+
+       buf2[hdrlen + len - 1] = '\0';
+       if (recv(fd, buf2, hdrlen + len + 1, 0) != hdrlen + len) e(0);
+       if (buf2[hdrlen + len - 1] != 'I') e(0);
+
+       buf2[hdrlen + len - 1] = '\0';
+       if (recv(fd2, buf2, hdrlen + len + 1, 0) != hdrlen + len) e(0);
+       if (buf2[hdrlen + len - 1] != 'I') e(0);
+
+       free(buf2);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* Multiple-receivers test, IPv6 version. */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+
+       if (type == SOCK_DGRAM) {
+               if (bind(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0)
+                       e(0);
+
+               len = sizeof(sin6A);
+               if (getsockname(fd, (struct sockaddr *)&sin6A, &len) != 0)
+                       e(0);
+
+               val = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (bind(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0)
+                       e(0);
+       }
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       val = (int)ifindex;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       memcpy(&sin6A.sin6_addr, &ipv6mr.ipv6mr_multiaddr,
+           sizeof(sin6A.sin6_addr));
+
+       if (sendto(fd, "J", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'J') e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'J') e(0);
+
+       len = 8000;
+       if ((buf2 = malloc(len + 1)) == NULL) e(0);
+       buf2[len - 1] = 'K';
+
+       if (sendto(fd, buf2, len, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != len) e(0);
+
+       buf2[len - 1] = '\0';
+       if (recv(fd, buf2, len + 1, 0) != len) e(0);
+       if (buf2[len - 1] != 'K') e(0);
+
+       buf2[len - 1] = '\0';
+       if (recv(fd2, buf2, len + 1, 0) != len) e(0);
+       if (buf2[len - 1] != 'K') e(0);
+
+       free(buf2);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Test proper multicast group departure.  This test relies on the fact
+        * that actual group membership is not checked on arrival of a
+        * multicast-destined packet, so that membership of one socket can be
+        * tested by another socket sending packets to itself while having
+        * joined a different group.  We test both explicit group departure
+        * and implicit departure on close.
+        */
+       if ((fd = socket(AF_INET, type, protocol)) < 0) e(0);
+       if ((fd2 = socket(AF_INET, type, protocol)) < 0) e(0);
+
+       memset(&sinA, 0, sizeof(sinA));
+       sinA.sin_family = AF_INET;
+
+       if (type == SOCK_DGRAM) {
+               if (bind(fd, (struct sockaddr *)&sinA, sizeof(sinA)) != 0)
+                       e(0);
+
+               len = sizeof(sinA);
+               if (getsockname(fd, (struct sockaddr *)&sinA, &len) != 0)
+                       e(0);
+
+               val = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (bind(fd2, (struct sockaddr *)&sinA, sizeof(sinA)) != 0)
+                       e(0);
+       }
+
+       imr.imr_multiaddr.s_addr = inet_addr(TEST_MULTICAST_IPV4);
+       imr.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       imr.imr_multiaddr.s_addr = htonl(ntohl(imr.imr_multiaddr.s_addr) + 1);
+
+       if (setsockopt(fd2, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr.imr_interface,
+           sizeof(imr.imr_interface)) != 0) e(0);
+
+       sinA.sin_addr.s_addr = imr.imr_multiaddr.s_addr;
+
+       if (sendto(fd, "L", 1, 0, (struct sockaddr *)&sinA, sizeof(sinA)) != 1)
+               e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'L') e(0);
+
+       if (setsockopt(fd2, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (sendto(fd, "M", 1, 0, (struct sockaddr *)&sinA, sizeof(sinA)) != 1)
+               e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'L') e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (setsockopt(fd2, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) != 0) e(0);
+
+       if (sendto(fd, "N", 1, 0, (struct sockaddr *)&sinA, sizeof(sinA)) != 1)
+               e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'N') e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       if (sendto(fd, "O", 1, 0, (struct sockaddr *)&sinA, sizeof(sinA)) != 1)
+               e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != hdrlen + 1) e(0);
+       if (buf[hdrlen] != 'N') e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Multicast group departure, now IPv6.. this is getting boring. */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+
+       if (type == SOCK_DGRAM) {
+               if (bind(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0)
+                       e(0);
+
+               len = sizeof(sin6A);
+               if (getsockname(fd, (struct sockaddr *)&sin6A, &len) != 0)
+                       e(0);
+
+               val = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (bind(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0)
+                       e(0);
+       }
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       ipv6mr.ipv6mr_multiaddr.s6_addr[15]++;
+
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       val = (int)ifindex;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       memcpy(&sin6A.sin6_addr, &ipv6mr.ipv6mr_multiaddr,
+           sizeof(sin6A.sin6_addr));
+
+       if (sendto(fd, "P", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'P') e(0);
+
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (sendto(fd, "Q", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'P') e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if (sendto(fd, "R", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'R') e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       if (sendto(fd, "S", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'R') e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Lastly, some IPv6-only tests.
+        */
+       /*
+        * Test that IPV6_PKTINFO overrides IPV6_MULTICAST_IF.  For this we
+        * need two valid interface indices.  If we cannot find a second one,
+        * simply test that the IPV6_PKTINFO information is used at all.
+        */
+       for (ifindex2 = 1; ifindex2 < BAD_IFINDEX; ifindex2++) {
+               if (if_indextoname(ifindex2, name) == NULL) {
+                       if (errno != ENXIO) e(0);
+                       continue;
+               }
+
+               if (strcmp(name, LOOPBACK_IFNAME))
+                       break;
+       }
+
+       if (ifindex2 == BAD_IFINDEX)
+               ifindex2 = 0; /* too bad; fallback mode */
+
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+
+       if (type == SOCK_DGRAM) {
+               if (bind(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0)
+                       e(0);
+
+               len = sizeof(sin6A);
+               if (getsockname(fd, (struct sockaddr *)&sin6A, &len) != 0)
+                       e(0);
+       }
+
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6_LL,
+           &ipv6mr.ipv6mr_multiaddr) != 1) e(0);
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memcpy(&sin6A.sin6_addr, &ipv6mr.ipv6mr_multiaddr,
+           sizeof(sin6A.sin6_addr));
+
+       val = (int)ifindex2;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = "T";
+       iov.iov_len = 1;
+
+       memset(&ipi6, 0, sizeof(ipi6));
+       memcpy(&ipi6.ipi6_addr, &in6addr_loopback, sizeof(ipi6.ipi6_addr));
+       ipi6.ipi6_ifindex = ifindex;
+
+       control.cmsg.cmsg_len = CMSG_LEN(sizeof(ipi6));
+       control.cmsg.cmsg_level = IPPROTO_IPV6;
+       control.cmsg.cmsg_type = IPV6_PKTINFO;
+       memcpy(CMSG_DATA(&control.cmsg), &ipi6, sizeof(ipi6));
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = &sin6A;
+       msg.msg_namelen = sizeof(sin6A);
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = control.cmsg.cmsg_len;
+
+       if (sendmsg(fd2, &msg, 0) != 1) e(0);
+
+       len = sizeof(sin6B);
+       if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6B,
+           &len) != 1) e(0);
+       if (buf[0] != 'T') e(0);
+
+       if (len != sizeof(sin6B)) e(0);
+       if (sin6B.sin6_len != sizeof(sin6B)) e(0);
+       if (sin6B.sin6_family != AF_INET6) e(0);
+       if (memcmp(&sin6B.sin6_addr, &in6addr_loopback,
+           sizeof(sin6B.sin6_addr)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       /* Repeat the same test, but now with a sticky IPV6_PKTINFO setting. */
+       if ((fd2 = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&ipi6, 0, sizeof(ipi6));
+       memcpy(&ipi6.ipi6_addr, &in6addr_loopback, sizeof(ipi6.ipi6_addr));
+       ipi6.ipi6_ifindex = ifindex;
+
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_PKTINFO, &ipi6,
+           sizeof(ipi6)) != 0) e(0);
+
+       val = (int)ifindex2;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (sendto(fd2, "U", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       len = sizeof(sin6B);
+       if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6B,
+           &len) != 1) e(0);
+       if (buf[0] != 'U') e(0);
+
+       if (len != sizeof(sin6B)) e(0);
+       if (sin6B.sin6_len != sizeof(sin6B)) e(0);
+       if (sin6B.sin6_family != AF_INET6) e(0);
+       if (memcmp(&sin6B.sin6_addr, &in6addr_loopback,
+           sizeof(sin6B.sin6_addr)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Test that invalid multicast addresses are not accepted anywhere.
+        */
+       if ((fd = socket(AF_INET6, type, protocol)) < 0) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+       if (inet_pton(AF_INET6, TEST_MULTICAST_IPV6_BAD,
+           &sin6A.sin6_addr) != 1) e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       sin6A.sin6_port = htons(TEST_PORT_A);
+       if (connect(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (sendto(fd, "X", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       memcpy(&ipv6mr.ipv6mr_multiaddr, &sin6A.sin6_addr,
+           sizeof(ipv6mr.ipv6mr_multiaddr));
+       ipv6mr.ipv6mr_interface = ifindex;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (close(fd) != 0) e(0);
+}
index 8fa9447155c7e5d6b8bb3e773f1605b3ad56b5f7..7a7390f10fb3b89d8b42274160e23f7cf18bd1a5 100644 (file)
@@ -1,6 +1,28 @@
 #ifndef MINIX_TEST_SOCKLIB_H
 #define MINIX_TEST_SOCKLIB_H
 
+/* TCP/IP test values. */
+#define TEST_PORT_A    12345   /* this port should be free and usable */
+#define TEST_PORT_B    12346   /* this port should be free and usable */
+
+#define LOOPBACK_IFNAME                "lo0"           /* loopback interface name */
+#define LOOPBACK_IPV4          "127.0.0.1"     /* IPv4 address */
+#define LOOPBACK_LL_IPV6       "fe80::1"       /* link-local IPv6 address */
+
+/* These address should simply eat all packets. */
+/*
+ * IMPORTANT: the ::2 address works only if there is a route for ::/64.  This
+ * route is supposed to be added by /etc/rc.d/network, and is not present by
+ * default.  As a result, the tests will pass only when regular system/network
+ * initialization is not skipped.  We cannot add the route ourselves, since not
+ * all tests run as root.
+ */
+#define TEST_BLACKHOLE_IPV4    "127.255.0.254"
+#define TEST_BLACKHOLE_IPV6    "::2"
+#define TEST_BLACKHOLE_LL_IPV6 "fe80::ffff"
+
+#define BAD_SCOPE_ID   255     /* guaranteed not to belong to an interface */
+
 enum state {
        S_NEW,
        S_N_SHUT_R,
@@ -81,9 +103,14 @@ void socklib_sweep(int domain, int type, int protocol,
        int (* proc)(int domain, int type, int protocol, enum state,
        enum call));
 
+void socklib_multicast_tx_options(int type);
 void socklib_large_transfers(int fd[2]);
 void socklib_producer_consumer(int fd[2]);
 void socklib_stream_recv(int (* socket_pair)(int, int, int, int *), int domain,
        int type, int (* break_recv)(int, const char *, size_t));
+int socklib_find_pcb(const char * path, int protocol, uint16_t local_port,
+       uint16_t remote_port, struct kinfo_pcb * ki);
+void socklib_test_addrs(int type, int protocol);
+void socklib_test_multicast(int type, int protocol);
 
 #endif /* !MINIX_TEST_SOCKLIB_H */
diff --git a/minix/tests/test91.c b/minix/tests/test91.c
new file mode 100644 (file)
index 0000000..13e996c
--- /dev/null
@@ -0,0 +1,5396 @@
+/* Advanced tests for TCP and UDP sockets (LWIP) - by D.C. van Moolenbroek */
+/*
+ * This is a somewhat random collection of in-depth tests, complementing the
+ * more general functionality tests in test80 and test81.  The overall test set
+ * is still by no means expected to be "complete."  The subtests are in random
+ * order.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/tcp.h>
+#include <netinet/tcp_fsm.h>
+#include <netinet/tcp_timer.h>
+#include <netinet/tcp_var.h>
+#include <netinet/udp.h>
+#include <netinet6/in6_pcb.h>
+#include <netinet6/in6_var.h>
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <machine/vmparam.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "common.h"
+#include "socklib.h"
+
+#define ITERATIONS     1
+
+static const enum state tcp_states[] = {
+               S_NEW,          S_N_SHUT_R,     S_BOUND,        S_LISTENING,
+               S_L_SHUT_R,     S_L_SHUT_W,     S_L_SHUT_RW,    S_CONNECTING,
+               S_C_SHUT_R,     S_C_SHUT_W,     S_C_SHUT_RW,    S_CONNECTED,
+               S_ACCEPTED,     S_SHUT_R,       S_SHUT_W,       S_SHUT_RW,
+               S_RSHUT_R,      S_RSHUT_W,      S_RSHUT_RW,     S_SHUT2_R,
+               S_SHUT2_W,      S_SHUT2_RW,     S_PRE_EOF,      S_AT_EOF,
+               S_POST_EOF,     S_PRE_SHUT_R,   S_EOF_SHUT_R,   S_POST_SHUT_R,
+               S_PRE_SHUT_W,   S_EOF_SHUT_W,   S_POST_SHUT_W,  S_PRE_SHUT_RW,
+               S_EOF_SHUT_RW,  S_POST_SHUT_RW, S_PRE_RESET,    S_AT_RESET,
+               S_POST_RESET,   S_FAILED,       S_POST_FAILED
+};
+
+static const int tcp_results[][__arraycount(tcp_states)] = {
+       [C_ACCEPT]              = {
+               -EINVAL,        -EINVAL,        -EINVAL,        -EAGAIN,
+               -ECONNABORTED,  -ECONNABORTED,  -ECONNABORTED,  -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,
+       },
+       [C_BIND]                = {
+               0,              0,              -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,
+       },
+       [C_CONNECT]             = {
+               -EINPROGRESS,   -EINPROGRESS,   -EINPROGRESS,   -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EALREADY,
+               -EALREADY,      -EINVAL,        -EINVAL,        -EISCONN,
+               -EISCONN,       -EISCONN,       -EISCONN,       -EISCONN,
+               -EISCONN,       -EISCONN,       -EISCONN,       -EISCONN,
+               -EINVAL,        -EINVAL,        -EISCONN,       -EISCONN,
+               -EISCONN,       -EISCONN,       -EISCONN,       -EISCONN,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,
+       },
+       [C_GETPEERNAME]         = {
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               -ENOTCONN,      -ENOTCONN,      0,              0,
+               0,              0,              0,              0,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+       },
+       [C_GETSOCKNAME]         = {
+               0,              0,              0,              0,
+               -EINVAL,        -EINVAL,        -EINVAL,        0,
+               0,              -EINVAL,        -EINVAL,        0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               -EINVAL,        -EINVAL,        0,              0,
+               0,              0,              0,              0,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,
+       },
+       [C_GETSOCKOPT_ERR]      = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              -ECONNRESET,    -ECONNRESET,
+               0,              -ECONNREFUSED,  0,
+       },
+       [C_GETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,
+       },
+       [C_GETSOCKOPT_RB]       = {
+               0,              0,              0,              0,
+               -ECONNRESET,    -ECONNRESET,    -ECONNRESET,    0,
+               0,              -ECONNRESET,    -ECONNRESET,    0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               -ECONNRESET,    -ECONNRESET,    0,              0,
+               0,              0,              0,              0,
+               -ECONNRESET,    -ECONNRESET,    -ECONNRESET,    -ECONNRESET,
+               -ECONNRESET,    -ECONNRESET,    -ECONNRESET,    -ECONNRESET,
+               -ECONNRESET,    -ECONNRESET,    -ECONNRESET,
+       },
+       [C_IOCTL_NREAD]         = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              1,              0,
+               0,              0,              0,              0,
+               1,              0,              0,              0,
+               0,              0,              1,              0,
+               0,              0,              0,
+       },
+       [C_LISTEN]              = {
+               0,              0,              0,              0,
+               0,              0,              0,              -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,
+       },
+       [C_RECV]                = {
+               -ENOTCONN,      0,              -ENOTCONN,      -ENOTCONN,
+               0,              0,              0,              -EAGAIN,
+               0,              0,              0,              -EAGAIN,
+               -EAGAIN,        0,              -EAGAIN,        0,
+               -EAGAIN,        0,              0,              0,
+               0,              0,              1,              0,
+               0,              0,              0,              0,
+               1,              0,              0,              0,
+               0,              0,              1,              -ECONNRESET,
+               0,              -ECONNREFUSED,  0,
+       },
+       [C_RECVFROM]            = {
+               -ENOTCONN,      0,              -ENOTCONN,      -ENOTCONN,
+               0,              0,              0,              -EAGAIN,
+               0,              0,              0,              -EAGAIN,
+               -EAGAIN,        0,              -EAGAIN,        0,
+               -EAGAIN,        0,              0,              0,
+               0,              0,              1,              0,
+               0,              0,              0,              0,
+               1,              0,              0,              0,
+               0,              0,              1,              -ECONNRESET,
+               0,              -ECONNREFUSED,  0,
+       },
+       [C_SEND]                = {
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -EPIPE,         -EPIPE,         -EPIPE,         -EAGAIN,
+               -EAGAIN,        -EPIPE,         -EPIPE,         1,
+               1,              1,              -EPIPE,         -EPIPE,
+               1,              1,              1,              1,
+               -EPIPE,         -EPIPE,         1,              1,
+               1,              1,              1,              1,
+               -EPIPE,         -EPIPE,         -EPIPE,         -EPIPE,
+               -EPIPE,         -EPIPE,         -ECONNRESET,    -ECONNRESET,
+               -EPIPE,         -ECONNREFUSED,  -EPIPE,
+       },
+       [C_SENDTO]              = {
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -EPIPE,         -EPIPE,         -EPIPE,         -EAGAIN,
+               -EAGAIN,        -EPIPE,         -EPIPE,         1,
+               1,              1,              -EPIPE,         -EPIPE,
+               1,              1,              1,              1,
+               -EPIPE,         -EPIPE,         1,              1,
+               1,              1,              1,              1,
+               -EPIPE,         -EPIPE,         -EPIPE,         -EPIPE,
+               -EPIPE,         -EPIPE,         -ECONNRESET,    -ECONNRESET,
+               -EPIPE,         -ECONNREFUSED,  -EPIPE,
+       },
+       [C_SELECT_R]            = {
+               1,              1,              1,              0,
+               1,              1,              1,              0,
+               1,              1,              1,              0,
+               0,              1,              0,              1,
+               0,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,
+       },
+       [C_SELECT_W]            = {
+               1,              1,              1,              1,
+               1,              1,              1,              0,
+               0,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,
+       },
+       [C_SELECT_X]            = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,
+       },
+       [C_SETSOCKOPT_BC]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,
+       },
+       [C_SETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,
+       },
+       [C_SETSOCKOPT_L]        = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,
+       },
+       [C_SETSOCKOPT_RA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,
+       },
+       [C_SHUTDOWN_R]          = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,
+       },
+       [C_SHUTDOWN_RW]         = {
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      0,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      0,
+               0,              -ENOTCONN,      -ENOTCONN,      0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               -ENOTCONN,      -ENOTCONN,      0,              0,
+               0,              0,              0,              0,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+       },
+       [C_SHUTDOWN_W]          = {
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      0,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      0,
+               0,              -ENOTCONN,      -ENOTCONN,      0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               -ENOTCONN,      -ENOTCONN,      0,              0,
+               0,              0,              0,              0,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+       },
+};
+
+/*
+ * Set up a TCP socket file descriptor in the requested state and pass it to
+ * socklib_sweep_call() along with local and remote addresses and their length.
+ */
+static int
+tcp_sweep(int domain, int type, int protocol, enum state state, enum call call)
+{
+       struct sockaddr_in sinA, sinB, sinC, sinD;
+       struct sockaddr_in6 sin6A, sin6B, sin6C, sin6D;
+       struct sockaddr *addrA, *addrB, *addrC, *addrD;
+       socklen_t addr_len, len;
+       struct linger l;
+       fd_set fds;
+       char buf[1];
+       int r, fd, fd2, fd3, tmpfd, val;
+
+       if (domain == AF_INET) {
+               memset(&sin6A, 0, sizeof(sin6A));
+               sinA.sin_family = domain;
+               sinA.sin_port = htons(TEST_PORT_A);
+               sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+               memcpy(&sinB, &sinA, sizeof(sinB));
+               sinB.sin_port = htons(0);
+
+               memcpy(&sinC, &sinA, sizeof(sinC));
+               sinC.sin_addr.s_addr = inet_addr(TEST_BLACKHOLE_IPV4);
+
+               memcpy(&sinD, &sinA, sizeof(sinD));
+               sinD.sin_port = htons(TEST_PORT_B);
+
+               addrA = (struct sockaddr *)&sinA;
+               addrB = (struct sockaddr *)&sinB;
+               addrC = (struct sockaddr *)&sinC;
+               addrD = (struct sockaddr *)&sinD;
+               addr_len = sizeof(sinA);
+       } else {
+               assert(domain == AF_INET6);
+
+               memset(&sin6A, 0, sizeof(sin6A));
+               sin6A.sin6_family = domain;
+               sin6A.sin6_port = htons(TEST_PORT_A);
+               memcpy(&sin6A.sin6_addr, &in6addr_loopback,
+                   sizeof(sin6A.sin6_addr));
+
+               memcpy(&sin6B, &sin6A, sizeof(sin6B));
+               sin6B.sin6_port = htons(0);
+
+               memcpy(&sin6C, &sin6A, sizeof(sin6C));
+               if (inet_pton(domain, TEST_BLACKHOLE_IPV6,
+                   &sin6C.sin6_addr) != 1) e(0);
+
+               memcpy(&sin6D, &sin6A, sizeof(sin6D));
+               sin6D.sin6_port = htons(TEST_PORT_B);
+
+               addrA = (struct sockaddr *)&sin6A;
+               addrB = (struct sockaddr *)&sin6B;
+               addrC = (struct sockaddr *)&sin6C;
+               addrD = (struct sockaddr *)&sin6D;
+               addr_len = sizeof(sin6A);
+       }
+
+       /* Create a bound remote socket. */
+       if ((fd2 = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0);
+
+       if (bind(fd2, addrB, addr_len) != 0) e(0);
+
+       len = addr_len;
+       if (getsockname(fd2, addrB, &len) != 0) e(0);
+       if (len != addr_len) e(0);
+
+       if (listen(fd2, 1) != 0) e(0);
+
+       fd3 = -1;
+
+       switch (state) {
+       case S_NEW:
+       case S_N_SHUT_R:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               val = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (state == S_N_SHUT_R && shutdown(fd, SHUT_RD)) e(0);
+
+               break;
+
+       case S_BOUND:
+       case S_LISTENING:
+       case S_L_SHUT_R:
+       case S_L_SHUT_W:
+       case S_L_SHUT_RW:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               val = 1;
+               if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (bind(fd, addrA, addr_len) != 0) e(0);
+
+               if (state == S_BOUND)
+                       break;
+
+               if (listen(fd, 1) != 0) e(0);
+
+               switch (state) {
+               case S_L_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_L_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_L_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               break;
+
+       case S_CONNECTING:
+       case S_C_SHUT_R:
+       case S_C_SHUT_W:
+       case S_C_SHUT_RW:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               if (connect(fd, addrC, addr_len) != -1) e(0);
+               if (errno != EINPROGRESS) e(0);
+
+               switch (state) {
+               case S_C_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_C_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_C_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               break;
+
+       case S_CONNECTED:
+       case S_ACCEPTED:
+       case S_SHUT_R:
+       case S_SHUT_W:
+       case S_SHUT_RW:
+       case S_RSHUT_R:
+       case S_RSHUT_W:
+       case S_RSHUT_RW:
+       case S_SHUT2_R:
+       case S_SHUT2_W:
+       case S_SHUT2_RW:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               if (connect(fd, addrB, addr_len) != -1) e(0);
+               if (errno != EINPROGRESS) e(0);
+
+               /* Just to make sure, wait for the socket to be acceptable. */
+               FD_ZERO(&fds);
+               FD_SET(fd2, &fds);
+               if (select(fd2 + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+
+               len = addr_len;
+               if ((fd3 = accept(fd2, addrC, &len)) < 0) e(0);
+
+               /* Just to make sure, wait for the socket to be connected. */
+               FD_ZERO(&fds);
+               FD_SET(fd, &fds);
+               if (select(fd + 1, NULL, &fds, NULL, NULL) != 1) e(0);
+
+               switch (state) {
+               case S_SHUT_R:
+               case S_SHUT2_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_SHUT_W:
+               case S_SHUT2_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_SHUT_RW:
+               case S_SHUT2_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               switch (state) {
+               case S_RSHUT_R:
+               case S_SHUT2_R: if (shutdown(fd3, SHUT_RD)) e(0); break;
+               case S_RSHUT_W:
+               case S_SHUT2_W: if (shutdown(fd3, SHUT_WR)) e(0); break;
+               case S_RSHUT_RW:
+               case S_SHUT2_RW: if (shutdown(fd3, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               if (state == S_ACCEPTED) {
+                       tmpfd = fd;
+                       fd = fd3;
+                       fd3 = tmpfd;
+               }
+
+               break;
+
+       case S_PRE_EOF:
+       case S_AT_EOF:
+       case S_POST_EOF:
+       case S_PRE_SHUT_R:
+       case S_EOF_SHUT_R:
+       case S_POST_SHUT_R:
+       case S_PRE_SHUT_W:
+       case S_EOF_SHUT_W:
+       case S_POST_SHUT_W:
+       case S_PRE_SHUT_RW:
+       case S_EOF_SHUT_RW:
+       case S_POST_SHUT_RW:
+       case S_PRE_RESET:
+       case S_AT_RESET:
+       case S_POST_RESET:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               if (connect(fd, addrB, addr_len) != -1) e(0);
+               if (errno != EINPROGRESS) e(0);
+
+               /* Just to make sure, wait for the socket to be acceptable. */
+               FD_ZERO(&fds);
+               FD_SET(fd2, &fds);
+               if (select(fd2 + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+
+               len = addr_len;
+               if ((fd3 = accept(fd2, addrC, &len)) < 0) e(0);
+
+               if (send(fd3, "", 1, 0) != 1) e(0);
+
+               switch (state) {
+               case S_PRE_RESET:
+               case S_AT_RESET:
+               case S_POST_RESET:
+                       l.l_onoff = 1;
+                       l.l_linger = 0;
+
+                       if (setsockopt(fd3, SOL_SOCKET, SO_LINGER, &l,
+                           sizeof(l)) != 0) e(0);
+
+                       break;
+               default:
+                       break;
+               }
+
+               if (close(fd3) != 0) e(0);
+               fd3 = -1;
+
+               /* Just to make sure, wait for the socket to receive data. */
+               FD_ZERO(&fds);
+               FD_SET(fd, &fds);
+               if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+
+               switch (state) {
+               case S_AT_EOF:
+               case S_EOF_SHUT_R:
+               case S_EOF_SHUT_W:
+               case S_EOF_SHUT_RW:
+               case S_AT_RESET:
+                       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+                       break;
+               case S_POST_EOF:
+               case S_POST_SHUT_R:
+               case S_POST_SHUT_W:
+               case S_POST_SHUT_RW:
+                       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+                       if (recv(fd, buf, sizeof(buf), 0) != 0) e(0);
+                       break;
+               case S_POST_RESET:
+                       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+                       (void)recv(fd, buf, sizeof(buf), 0);
+                       break;
+               default:
+                       break;
+               }
+
+               switch (state) {
+               case S_PRE_SHUT_R:
+               case S_EOF_SHUT_R:
+               case S_POST_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_PRE_SHUT_W:
+               case S_EOF_SHUT_W:
+               case S_POST_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_PRE_SHUT_RW:
+               case S_EOF_SHUT_RW:
+               case S_POST_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               break;
+
+       case S_FAILED:
+       case S_POST_FAILED:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               if (connect(fd, addrD, addr_len) != -1) e(0);
+               if (errno != EINPROGRESS) e(0);
+
+               FD_ZERO(&fds);
+               FD_SET(fd, &fds);
+               if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+
+               if (state == S_POST_FAILED) {
+                       if (recv(fd, buf, sizeof(buf), 0) != -1) e(0);
+                       if (errno != ECONNREFUSED) e(0);
+               }
+
+               break;
+
+       default:
+               fd = -1;
+               e(0);
+       }
+
+       r = socklib_sweep_call(call, fd, addrA, addrB, addr_len);
+
+       if (close(fd) != 0) e(0);
+       if (fd2 != -1 && close(fd2) != 0) e(0);
+       if (fd3 != -1 && close(fd3) != 0) e(0);
+
+       return r;
+}
+
+static const enum state udp_states[] = {
+               S_NEW,          S_N_SHUT_R,     S_N_SHUT_W,     S_N_SHUT_RW,
+               S_BOUND,        S_CONNECTED,    S_SHUT_R,       S_SHUT_W,
+               S_SHUT_RW,      S_RSHUT_R,      S_RSHUT_W,      S_RSHUT_RW,
+               S_SHUT2_R,      S_SHUT2_W,      S_SHUT2_RW,     S_PRE_RESET,
+               S_AT_RESET,     S_POST_RESET
+};
+
+static const int udp_results[][__arraycount(udp_states)] = {
+       [C_ACCEPT]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_BIND]                = {
+               0,              0,              0,              0,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,        -EINVAL,
+       },
+       [C_CONNECT]             = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_GETPEERNAME]         = {
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_GETSOCKNAME]         = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_GETSOCKOPT_ERR]      = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_GETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_GETSOCKOPT_RB]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_IOCTL_NREAD]         = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              1,
+               0,              0,
+       },
+       [C_LISTEN]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_RECV]                = {
+               -EAGAIN,        0,              -EAGAIN,        0,
+               -EAGAIN,        -EAGAIN,        0,              -EAGAIN,
+               0,              -EAGAIN,        -EAGAIN,        -EAGAIN,
+               0,              -EAGAIN,        0,              1,
+               -EAGAIN,        -EAGAIN,
+       },
+       [C_RECVFROM]            = {
+               -EAGAIN,        0,              -EAGAIN,        0,
+               -EAGAIN,        -EAGAIN,        0,              -EAGAIN,
+               0,              -EAGAIN,        -EAGAIN,        -EAGAIN,
+               0,              -EAGAIN,        0,              1,
+               -EAGAIN,        -EAGAIN,
+       },
+       [C_SEND]                = {
+               -EDESTADDRREQ,  -EDESTADDRREQ,  -EPIPE,         -EPIPE,
+               -EDESTADDRREQ,  1,              1,              -EPIPE,
+               -EPIPE,         1,              1,              1,
+               1,              -EPIPE,         -EPIPE,         1,
+               1,              1,
+       },
+       [C_SENDTO]              = {
+               1,              1,              -EPIPE,         -EPIPE,
+               1,              1,              1,              -EPIPE,
+               -EPIPE,         1,              1,              1,
+               1,              -EPIPE,         -EPIPE,         1,
+               1,              1,
+       },
+       [C_SELECT_R]            = {
+               0,              1,              0,              1,
+               0,              0,              1,              0,
+               1,              0,              0,              0,
+               1,              0,              1,              1,
+               0,              0,
+       },
+       [C_SELECT_W]            = {
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,              1,
+       },
+       [C_SELECT_X]            = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_SETSOCKOPT_BC]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_SETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_SETSOCKOPT_L]        = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_SETSOCKOPT_RA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_SHUTDOWN_R]          = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_SHUTDOWN_RW]         = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+       [C_SHUTDOWN_W]          = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,              0,
+       },
+};
+
+/*
+ * Set up a UDP socket file descriptor in the requested state and pass it to
+ * socklib_sweep_call() along with local and remote addresses and their length.
+ */
+static int
+udp_sweep(int domain, int type, int protocol, enum state state, enum call call)
+{
+       struct sockaddr_in sinA, sinB;
+       struct sockaddr_in6 sin6A, sin6B;
+       struct sockaddr *addrA, *addrB;
+       socklen_t addr_len;
+       char buf[1];
+       int r, fd, fd2;
+
+       if (domain == AF_INET) {
+               memset(&sinA, 0, sizeof(sinA));
+               sinA.sin_family = domain;
+               sinA.sin_port = htons(TEST_PORT_A);
+               sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+               memcpy(&sinB, &sinA, sizeof(sinB));
+               sinB.sin_port = htons(TEST_PORT_B);
+
+               addrA = (struct sockaddr *)&sinA;
+               addrB = (struct sockaddr *)&sinB;
+               addr_len = sizeof(sinA);
+       } else {
+               assert(domain == AF_INET6);
+
+               memset(&sin6A, 0, sizeof(sin6A));
+               sin6A.sin6_family = domain;
+               sin6A.sin6_port = htons(TEST_PORT_A);
+               memcpy(&sin6A.sin6_addr, &in6addr_loopback,
+                   sizeof(sin6A.sin6_addr));
+
+               memcpy(&sin6B, &sin6A, sizeof(sin6B));
+               sin6B.sin6_port = htons(TEST_PORT_B);
+
+               addrA = (struct sockaddr *)&sin6A;
+               addrB = (struct sockaddr *)&sin6B;
+               addr_len = sizeof(sin6A);
+       }
+
+       /* Create a bound remote socket. */
+       if ((fd2 = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0);
+
+       if (bind(fd2, addrB, addr_len) != 0) e(0);
+
+       switch (state) {
+       case S_NEW:
+       case S_N_SHUT_R:
+       case S_N_SHUT_W:
+       case S_N_SHUT_RW:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               switch (state) {
+               case S_N_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_N_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_N_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               break;
+
+       case S_BOUND:
+       case S_CONNECTED:
+       case S_SHUT_R:
+       case S_SHUT_W:
+       case S_SHUT_RW:
+       case S_RSHUT_R:
+       case S_RSHUT_W:
+       case S_RSHUT_RW:
+       case S_SHUT2_R:
+       case S_SHUT2_W:
+       case S_SHUT2_RW:
+       case S_PRE_RESET:
+       case S_AT_RESET:
+       case S_POST_RESET:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               if (bind(fd, addrA, addr_len) != 0) e(0);
+
+               if (state == S_BOUND)
+                       break;
+
+               if (connect(fd, addrB, addr_len) != 0) e(0);
+
+               switch (state) {
+               case S_SHUT_R:
+               case S_SHUT2_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_SHUT_W:
+               case S_SHUT2_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_SHUT_RW:
+               case S_SHUT2_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               switch (state) {
+               case S_RSHUT_R:
+               case S_SHUT2_R: if (shutdown(fd2, SHUT_RD)) e(0); break;
+               case S_RSHUT_W:
+               case S_SHUT2_W: if (shutdown(fd2, SHUT_WR)) e(0); break;
+               case S_RSHUT_RW:
+               case S_SHUT2_RW: if (shutdown(fd2, SHUT_RDWR)) e(0); break;
+               case S_PRE_RESET:
+               case S_AT_RESET:
+               case S_POST_RESET:
+                       if (sendto(fd2, "", 1, 0, addrA, addr_len) != 1) e(0);
+
+                       if (close(fd2) != 0) e(0);
+                       fd2 = -1;
+
+                       if (state != S_PRE_RESET) {
+                               if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+                       }
+                       if (state == S_POST_RESET) {
+                               (void)recv(fd, buf, sizeof(buf), 0);
+                       }
+               default:
+                       break;
+               }
+
+               break;
+
+       default:
+               fd = -1;
+               e(0);
+       }
+
+       r = socklib_sweep_call(call, fd, addrA, addrB, addr_len);
+
+       if (close(fd) != 0) e(0);
+       if (fd2 != -1 && close(fd2) != 0) e(0);
+
+       return r;
+}
+
+/*
+ * Sweep test for socket calls versus socket states of TCP and UDP sockets.
+ */
+static void
+test91a(void)
+{
+
+       subtest = 1;
+
+       socklib_sweep(AF_INET, SOCK_STREAM, 0, tcp_states,
+           __arraycount(tcp_states), (const int *)tcp_results, tcp_sweep);
+
+       socklib_sweep(AF_INET6, SOCK_STREAM, 0, tcp_states,
+           __arraycount(tcp_states), (const int *)tcp_results, tcp_sweep);
+
+       socklib_sweep(AF_INET, SOCK_DGRAM, 0, udp_states,
+           __arraycount(udp_states), (const int *)udp_results, udp_sweep);
+
+       socklib_sweep(AF_INET6, SOCK_DGRAM, 0, udp_states,
+           __arraycount(udp_states), (const int *)udp_results, udp_sweep);
+}
+
+#define F_SKIP -1      /* skip this entry */
+#define F_NO   0       /* binding or connecting should fail */
+#define F_YES  1       /* binding or connecting should succeed */
+#define F_DUAL 2       /* always fails on IPV6_V6ONLY sockets */
+#define F_ZONE 4       /* binding works only if a scope ID is given */
+#define F_UDP  8       /* do not test on TCP sockets */
+#define F_BAD  16      /* operations on this address result in EINVAL */
+
+static const struct {
+       const char *addr;
+       int may_bind;
+       int may_connect;        /* UDP only */
+} addrs_v4[] = {
+       { "0.0.0.0",            F_YES,          F_NO },
+       { "0.0.0.1",            F_NO,           F_SKIP },
+       { "127.0.0.1",          F_YES,          F_YES },
+       { "127.0.0.255",        F_NO,           F_YES },
+       { "127.255.255.255",    F_NO,           F_YES },
+       { "172.31.255.254",     F_NO,           F_SKIP }, /* may be valid.. */
+       { "224.0.0.0",          F_YES | F_UDP,  F_SKIP },
+       { "239.255.255.255",    F_YES | F_UDP,  F_SKIP },
+       { "240.0.0.0",          F_NO,           F_SKIP },
+       { "255.255.255.255",    F_NO,           F_SKIP },
+};
+
+static const struct {
+       const char *addr;
+       int may_bind;
+       int may_connect;        /* UDP only */
+} addrs_v6[] = {
+       { "::0",                F_YES,                  F_NO },
+       { "::1",                F_YES,                  F_YES },
+       { "::2",                F_NO,                   F_YES },
+       { "::127.0.0.1",        F_NO,                   F_YES },
+       { "::ffff:7f00:1",      F_YES | F_DUAL,         F_YES | F_DUAL },
+       { "::ffff:7f00:ff",     F_NO | F_DUAL,          F_YES | F_DUAL },
+       { "100::1",             F_NO,                   F_SKIP },
+       { "2fff:ffff::",        F_NO,                   F_SKIP },
+       { "fc00::1",            F_NO,                   F_SKIP },
+       { "fe00::1",            F_NO,                   F_SKIP },
+       { "fe80::1",            F_YES | F_ZONE,         F_YES | F_ZONE },
+       { "fec0::1",            F_NO,                   F_SKIP },
+       { "ff01::1",            F_YES | F_ZONE | F_UDP, F_YES | F_ZONE },
+       { "ff02::1",            F_YES | F_ZONE | F_UDP, F_YES | F_ZONE },
+       { "ff02::2",            F_YES | F_ZONE | F_UDP, F_YES | F_ZONE },
+       { "ff0e::1",            F_YES | F_UDP,          F_SKIP },
+       { "ffff::1",            F_NO | F_UDP | F_BAD,   F_NO | F_BAD },
+};
+
+/*
+ * Test binding sockets of a particular type to various addresses.
+ */
+static void
+sub91b(int type)
+{
+       struct sockaddr_in sin, lsin;
+       struct sockaddr_in6 sin6, lsin6;
+       socklen_t len;
+       unsigned int i, ifindex;
+       int r, fd, val;
+
+       ifindex = if_nametoindex(LOOPBACK_IFNAME);
+
+       /* Test binding IPv4 sockets to IPv4 addresses. */
+       for (i = 0; i < __arraycount(addrs_v4); i++) {
+               if (type == SOCK_STREAM && (addrs_v4[i].may_bind & F_UDP))
+                       continue;
+
+               if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_family = AF_INET;
+               if (inet_pton(AF_INET, addrs_v4[i].addr, &sin.sin_addr) != 1)
+                       e(0);
+
+               r = bind(fd, (struct sockaddr *)&sin, sizeof(sin));
+               if (r == -1 && errno != EADDRNOTAVAIL) e(0);
+               if (r + 1 != !!(addrs_v4[i].may_bind & F_YES)) e(0);
+
+               len = sizeof(lsin);
+               if (getsockname(fd, (struct sockaddr *)&lsin, &len) != 0) e(0);
+               if (lsin.sin_len != sizeof(lsin)) e(0);
+               if (lsin.sin_family != AF_INET) e(0);
+               if (r == 0) {
+                       if (lsin.sin_port == 0) e(0);
+                       if (lsin.sin_addr.s_addr != sin.sin_addr.s_addr) e(0);
+               } else {
+                       if (lsin.sin_port != 0) e(0);
+                       if (lsin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0);
+               }
+
+               /* Rebinding never works; binding after a failed bind does. */
+               sin.sin_addr.s_addr = htonl(INADDR_ANY);
+               r = bind(fd, (struct sockaddr *)&sin, sizeof(sin));
+               if (r == -1 && errno != EINVAL) e(0);
+               if (!!r != !!(addrs_v4[i].may_bind & F_YES)) e(0);
+
+               if (close(fd) != 0) e(0);
+       }
+
+       /* Test binding IPv6 sockets to IPv6 addresses. */
+       for (i = 0; i < __arraycount(addrs_v6); i++) {
+               if (type == SOCK_STREAM && (addrs_v6[i].may_bind & F_UDP))
+                       continue;
+
+               /* Try without IPV6_V6ONLY. */
+               if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+               /* IPV6_V6ONLY may or may not be enabled by default.. */
+               val = 0;
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                   sizeof(val)) != 0) e(0);
+
+               memset(&sin6, 0, sizeof(sin6));
+               sin6.sin6_family = AF_INET6;
+               if (inet_pton(AF_INET6, addrs_v6[i].addr,
+                   &sin6.sin6_addr) != 1) e(0);
+
+               if (addrs_v6[i].may_bind & F_ZONE) {
+                       if (bind(fd, (struct sockaddr *)&sin6,
+                           sizeof(sin6)) != -1) e(0);
+                       if (errno != EADDRNOTAVAIL) e(0);
+
+                       sin6.sin6_scope_id = ifindex;
+               }
+
+               r = bind(fd, (struct sockaddr *)&sin6, sizeof(sin6));
+               if (r == -1) {
+                       if (addrs_v6[i].may_bind & F_BAD) {
+                               if (errno != EINVAL) e(0);
+                       } else {
+                               if (errno != EADDRNOTAVAIL) e(0);
+                       }
+               }
+               if (r + 1 != !!(addrs_v6[i].may_bind & F_YES)) e(0);
+
+               len = sizeof(lsin6);
+               if (getsockname(fd, (struct sockaddr *)&lsin6, &len) != 0)
+                       e(0);
+               if (lsin6.sin6_len != sizeof(lsin6)) e(0);
+               if (lsin6.sin6_family != AF_INET6) e(0);
+               if (r == 0) {
+                       if (lsin6.sin6_port == 0) e(0);
+                       if (memcmp(&lsin6.sin6_addr, &sin6.sin6_addr,
+                           sizeof(lsin6.sin6_addr))) e(0);
+                       if (lsin6.sin6_scope_id !=
+                           ((addrs_v6[i].may_bind & F_ZONE) ? ifindex : 0))
+                               e(0);
+               } else {
+                       if (lsin6.sin6_port != 0) e(0);
+                       if (!IN6_IS_ADDR_UNSPECIFIED(&lsin6.sin6_addr)) e(0);
+                       if (lsin6.sin6_scope_id != 0) e(0);
+               }
+
+               if (close(fd) != 0) e(0);
+
+               /* Try with IPV6_V6ONLY. */
+               if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+               val = 1;
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                   sizeof(val)) != 0) e(0);
+
+               r = bind(fd, (struct sockaddr *)&sin6, sizeof(sin6));
+               if (r == -1) {
+                       if (addrs_v6[i].may_bind & (F_BAD | F_DUAL)) {
+                               if (errno != EINVAL) e(0);
+                       } else
+                               if (errno != EADDRNOTAVAIL) e(0);
+               }
+               if (r + 1 !=
+                   ((addrs_v6[i].may_bind & (F_YES | F_DUAL)) == F_YES)) e(0);
+
+               if (close(fd) != 0) e(0);
+       }
+
+       /* Test binding an IPv6 socket to an IPv4 address. */
+       if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+
+       if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       assert(sizeof(sin) <= sizeof(sin6));
+       memset(&sin6, 0, sizeof(sin6));
+       memcpy(&sin6, &sin, sizeof(sin));
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0);
+       if (errno != EAFNOSUPPORT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test binding an IPv4 socket to an IPv6 address. */
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr));
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin)) != -1) e(0);
+       if (errno != EAFNOSUPPORT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test binding a socket to AF_UNSPEC. */
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_UNSPEC;
+
+       if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EAFNOSUPPORT) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test binding sockets to various addresses.
+ */
+static void
+test91b(void)
+{
+
+       subtest = 2;
+
+       sub91b(SOCK_STREAM);
+
+       sub91b(SOCK_DGRAM);
+}
+
+/*
+ * Test connecting TCP sockets to various addresses.  We cannot test much here,
+ * because we do not actually want this test to generate outgoing traffic.  In
+ * effect, we test calls that should fail only.
+ */
+static void
+sub91c_tcp(void)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       int fd, val;
+
+       /*
+        * Test connecting to address zero (0.0.0.0 and ::0).  Apparently the
+        * traditional BSD behavior for IPv4 is to use the first interface's
+        * local address as destination instead, but our implementation does
+        * not support that at this time: these 'any' addresses always result
+        * in connection failures right away, hopefully eliminating some tricky
+        * implementation boundary cases.
+        */
+       if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+
+       if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EHOSTUNREACH && errno != ENETUNREACH) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+       memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr));
+
+       if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0);
+       if (errno != EHOSTUNREACH && errno != ENETUNREACH) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Test connecting to an IPv6-mapped IPv4 address on an IPv6 socket
+        * with INET6_V6ONLY enabled.
+        */
+       if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+       if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1)
+               e(0);
+
+       if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test connecting to an AF_UNSPEC address. */
+       if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_UNSPEC;
+
+       if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EAFNOSUPPORT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test connecting to port zero. */
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(0);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test connecting UDP sockets to various addresses.
+ */
+static void
+sub91c_udp(void)
+{
+       struct sockaddr_in sin, rsin;
+       struct sockaddr_in6 sin6, rsin6;
+       socklen_t len;
+       unsigned int i, ifindex;
+       int r, fd, val;
+
+       ifindex = if_nametoindex(LOOPBACK_IFNAME);
+
+       /* Test connecting IPv4 sockets to IPv4 addresses. */
+       for (i = 0; i < __arraycount(addrs_v4); i++) {
+               if (addrs_v4[i].may_connect == F_SKIP)
+                       continue;
+
+               if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_family = AF_INET;
+               sin.sin_port = htons(TEST_PORT_A);
+               if (inet_pton(AF_INET, addrs_v4[i].addr, &sin.sin_addr) != 1)
+                       e(0);
+
+               r = connect(fd, (struct sockaddr *)&sin, sizeof(sin));
+               if (r + 1 != !!(addrs_v4[i].may_connect & F_YES)) e(0);
+
+               len = sizeof(rsin);
+               if (r == 0) {
+                       if (getpeername(fd, (struct sockaddr *)&rsin,
+                           &len) != 0) e(0);
+                       if (rsin.sin_len != sizeof(rsin)) e(0);
+                       if (rsin.sin_family != AF_INET) e(0);
+                       if (rsin.sin_port != htons(TEST_PORT_A)) e(0);
+                       if (rsin.sin_addr.s_addr != sin.sin_addr.s_addr) e(0);
+               } else {
+                       if (getpeername(fd, (struct sockaddr *)&rsin,
+                           &len) != -1) e(0);
+                       if (errno != ENOTCONN) e(0);
+               }
+
+               sin.sin_addr.s_addr = htonl(INADDR_ANY);
+               r = bind(fd, (struct sockaddr *)&sin, sizeof(sin));
+               if (r == -1 && errno != EINVAL) e(0);
+               if (r + 1 != !(addrs_v4[i].may_connect & F_YES)) e(0);
+
+               if (close(fd) != 0) e(0);
+       }
+
+       /* Test connecting IPv6 sockets to IPv6 addresses. */
+       for (i = 0; i < __arraycount(addrs_v6); i++) {
+               if (addrs_v6[i].may_connect == F_SKIP)
+                       continue;
+
+               /* Try without IPV6_V6ONLY. */
+               if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+               /* IPV6_V6ONLY may or may not be enabled by default.. */
+               val = 0;
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                   sizeof(val)) != 0) e(0);
+
+               memset(&sin6, 0, sizeof(sin6));
+               sin6.sin6_family = AF_INET6;
+               sin6.sin6_port = htons(TEST_PORT_A);
+               if (inet_pton(AF_INET6, addrs_v6[i].addr,
+                   &sin6.sin6_addr) != 1) e(0);
+               sin6.sin6_scope_id = ifindex;
+
+               r = connect(fd, (struct sockaddr *)&sin6, sizeof(sin6));
+               if (r + 1 != !!(addrs_v6[i].may_connect & F_YES)) e(0);
+
+               len = sizeof(rsin6);
+               if (r == 0) {
+                       if (getpeername(fd, (struct sockaddr *)&rsin6,
+                           &len) != 0) e(0);
+                       if (rsin6.sin6_len != sizeof(rsin6)) e(0);
+                       if (rsin6.sin6_family != AF_INET6) e(0);
+                       if (rsin6.sin6_port != htons(TEST_PORT_A)) e(0);
+                       if (memcmp(&rsin6.sin6_addr, &sin6.sin6_addr,
+                           sizeof(rsin6.sin6_addr))) e(0);
+                       if (rsin6.sin6_scope_id !=
+                           ((addrs_v6[i].may_connect & F_ZONE) ? ifindex : 0))
+                               e(0);
+               } else {
+                       if (getpeername(fd, (struct sockaddr *)&rsin,
+                           &len) != -1) e(0);
+                       if (errno != ENOTCONN) e(0);
+               }
+
+               if (close(fd) != 0) e(0);
+
+               /* Try with IPV6_V6ONLY. */
+               if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+               val = 1;
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                   sizeof(val)) != 0) e(0);
+
+               r = connect(fd, (struct sockaddr *)&sin6, sizeof(sin6));
+               if (r == -1 && errno != EINVAL && errno != EHOSTUNREACH) e(0);
+               if (r + 1 !=
+                   ((addrs_v6[i].may_connect & (F_YES | F_DUAL)) == F_YES))
+                       e(0);
+
+               if (close(fd) != 0) e(0);
+       }
+
+       /* Test connecting an IPv6 socket to an IPv4 address. */
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       assert(sizeof(sin) <= sizeof(sin6));
+       memset(&sin6, 0, sizeof(sin6));
+       memcpy(&sin6, &sin, sizeof(sin));
+       if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0);
+       if (errno != EAFNOSUPPORT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test connecting an IPv4 socket to an IPv6 address. */
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr));
+
+       if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin)) != -1) e(0);
+       if (errno != EAFNOSUPPORT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test unconnecting a socket using AF_UNSPEC. */
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_UNSPEC;
+
+       if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       len = sizeof(rsin);
+       if (getpeername(fd, (struct sockaddr *)&rsin, &len) != -1) e(0);
+       if (errno != ENOTCONN) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test connecting to port zero. */
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(0);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EADDRNOTAVAIL) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test connecting sockets to various addresses.
+ */
+static void
+test91c(void)
+{
+
+       subtest = 3;
+
+       sub91c_tcp();
+
+       sub91c_udp();
+}
+
+/*
+ * Test binding with IPv4/IPv6 on the same port for the given socket type.
+ */
+static void
+sub91d(int type)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       int r, fd, fd2, val;
+
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+
+       if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       /* IPv4 bound; IPv6 bind without IPV6_V6ONLY may or may not work. */
+       if ((fd2 = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+       memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr));
+
+       r = bind(fd2, (struct sockaddr *)&sin6, sizeof(sin6));
+       if (r == -1 && errno != EADDRINUSE) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       /* IPv4 bound; IPv6 bind with IPV6_V6ONLY should work. */
+       if ((fd2 = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0)
+               e(0);
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       if (bind(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* IPv6 bound with IPV6_V6ONLY; IPv4 bind may or may not work. */
+       if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0)
+               e(0);
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, type, 0)) < 0) e(0);
+
+       r = bind(fd2, (struct sockaddr *)&sin, sizeof(sin));
+       if (r == -1 && errno != EADDRINUSE) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* IPv6 bound with IPV6_V6ONLY; IPv4 bind should work. */
+       if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0)
+               e(0);
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, type, 0)) < 0) e(0);
+
+       if (bind(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test binding with IPv4/IPv6 on the same port, and IPV6_V6ONLY.
+ */
+static void
+test91d(void)
+{
+
+       subtest = 4;
+
+       sub91d(SOCK_STREAM);
+
+       sub91d(SOCK_DGRAM);
+}
+
+/*
+ * Test sending large and small UDP packets.
+ */
+static void
+test91e(void)
+{
+       struct sockaddr_in sin;
+       struct msghdr msg;
+       struct iovec iov;
+       char *buf;
+       unsigned int i, j;
+       int r, fd, fd2, val;
+
+       subtest = 5;
+
+       if ((buf = malloc(65536)) == NULL) e(0);
+
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       val = 65536;
+       if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0)
+               e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       /*
+        * A maximum send buffer size of a full packet size's worth may always
+        * be set, although this is not necessarily the actual maximum.
+        */
+       val = 65535;
+       if (setsockopt(fd2, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) != 0)
+               e(0);
+
+       /* Find the largest possible packet size that can actually be sent. */
+       for (i = 0; i < val; i += sizeof(int)) {
+               j = i ^ 0xdeadbeef;
+               memcpy(&buf[i], &j, sizeof(j));
+       }
+
+       for (val = 65536; val > 0; val--) {
+               if ((r = sendto(fd2, buf, val, 0, (struct sockaddr *)&sin,
+                   sizeof(sin))) == val)
+                       break;
+               if (r != -1) e(0);
+               if (errno != EMSGSIZE) e(0);
+       }
+
+       if (val != 65535 - sizeof(struct udphdr) - sizeof(struct ip)) e(0);
+
+       memset(buf, 0, val);
+       buf[val] = 'X';
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = val + 1;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != val) e(0);
+       if (msg.msg_flags != 0) e(0);
+
+       for (i = 0; i < val; i += sizeof(int)) {
+               j = i ^ 0xdeadbeef;
+               if (memcmp(&buf[i], &j, MIN(sizeof(j), val - i))) e(0);
+       }
+       if (buf[val] != 'X') e(0);
+
+       if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin, sizeof(sin)) !=
+           val) e(0);
+
+       /*
+        * Make sure that there are no off-by-one errors in the receive code,
+        * and that MSG_TRUNC is set (only) when not the whole packet was
+        * received.
+        */
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = val;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != val) e(0);
+       if (msg.msg_flags != 0) e(0);
+
+       if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin, sizeof(sin)) !=
+           val) e(0);
+
+       buf[val - 1] = 'Y';
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = val - 1;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != val - 1) e(0);
+       if (msg.msg_flags != MSG_TRUNC) e(0);
+
+       for (i = 0; i < val - 1; i += sizeof(int)) {
+               j = i ^ 0xdeadbeef;
+               if (memcmp(&buf[i], &j, MIN(sizeof(j), val - 1 - i))) e(0);
+       }
+       if (buf[val - 1] != 'Y') e(0);
+
+       if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin, sizeof(sin)) !=
+           val) e(0);
+
+       buf[0] = 'Z';
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = 0;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != 0) e(0);
+       if (msg.msg_flags != MSG_TRUNC) e(0);
+       if (buf[0] != 'Z') e(0);
+
+       /* Make sure that zero-sized packets can be sent and received. */
+       if (sendto(fd2, buf, 0, 0, (struct sockaddr *)&sin, sizeof(sin)) != 0)
+               e(0);
+
+       /*
+        * Note how we currently assume that packets sent over localhost will
+        * arrive immediately, so that we can use MSG_DONTWAIT to avoid that
+        * the test freezes.
+        */
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, MSG_DONTWAIT) != 0) e(0);
+       if (msg.msg_flags != 0) e(0);
+       if (buf[0] != 'Z') e(0);
+
+       if (recv(fd, buf, val, MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       /*
+        * When sending lots of small packets, ensure that fewer packets arrive
+        * than we sent.  This sounds weird, but we cannot actually check the
+        * internal TCP/IP buffer granularity and yet we want to make sure that
+        * the receive queue is measured in terms of buffers rather than packet
+        * sizes.  In addition, we check that older packets are favored,
+        * instead discarding new ones when the receive buffer is full.
+        */
+       for (i = 0; i < 65536 / sizeof(j); i++) {
+               j = i;
+               if (sendto(fd2, &j, sizeof(j), 0, (struct sockaddr *)&sin,
+                   sizeof(sin)) != sizeof(j)) e(0);
+       }
+
+       for (i = 0; i < 1025; i++) {
+               r = recv(fd, &j, sizeof(j), MSG_DONTWAIT);
+               if (r == -1) {
+                       if (errno != EWOULDBLOCK) e(0);
+                       break;
+               }
+               if (r != sizeof(j)) e(0);
+               if (i != j) e(0);
+       }
+       if (i == 1025) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       free(buf);
+}
+
+/*
+ * Test setting and retrieving IP-level options for the given socket type.  For
+ * TCP sockets, we cannot test whether they are actually applied, but for UDP
+ * sockets, we do a more complete test later on.
+ */
+static void
+sub91f(int type)
+{
+       socklen_t len;
+       int fd, val, def;
+
+       /* Test IPv4 first. */
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       /* Test obtaining the default TOS and TTL values. */
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IP, IP_TOS, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       len = sizeof(def);
+       if (getsockopt(fd, IPPROTO_IP, IP_TTL, &def, &len) != 0) e(0);
+       if (len != sizeof(def)) e(0);
+       if (def < 16 || def > UINT8_MAX) e(0);
+
+       /* Test changing the TOS field. */
+       for (val = 0; val <= UINT8_MAX; val++)
+               if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != 0)
+                       e(0);
+       val = -1; /* not a special value for IPv4 */
+       if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+       val = UINT8_MAX + 1;
+       if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IP, IP_TOS, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != UINT8_MAX) e(0);
+
+       /* Test changing the TTL field. */
+       for (val = 0; val <= UINT8_MAX; val++)
+               if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != 0)
+                       e(0);
+       val = 39;
+       if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != 0) e(0);
+       val = -1; /* not a special value for IPv4 */
+       if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+       val = UINT8_MAX + 1;
+       if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IP, IP_TTL, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 39) e(0);
+
+       /* It must not be possible to set IPv6 options on IPv4 sockets. */
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != -1)
+               e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, &len) != -1)
+               e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test IPv6 next. */
+       if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       /* Test obtaining the default TCLASS and HOPS values. */
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       len = sizeof(def);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &def, &len) != 0)
+               e(0);
+       if (len != sizeof(def)) e(0);
+       if (def < 16 || def > UINT8_MAX) e(0);
+
+       /* Test changing the TCLASS field. */
+       for (val = 0; val <= UINT8_MAX; val++)
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val,
+                   sizeof(val)) != 0) e(0);
+       val = -2;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != -1)
+               e(0);
+       if (errno != EINVAL) e(0);
+       val = UINT8_MAX + 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != -1)
+               e(0);
+       if (errno != EINVAL) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != UINT8_MAX) e(0);
+
+       val = -1; /* reset to default */
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != 0)
+               e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       /* Test changing the HOPS field. */
+       for (val = 0; val <= UINT8_MAX; val++)
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+                   sizeof(val)) != 0) e(0);
+       val = 49;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+           sizeof(val)) != 0) e(0);
+       val = -2;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+       val = UINT8_MAX + 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 49) e(0);
+
+       val = -1; /* reset to default */
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val,
+           sizeof(val)) != 0) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, &len) != 0)
+               e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != def) e(0);
+
+       /* It must not be possible to set IPv4 options on IPv6 sockets. */
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IP, IP_TOS, &val, &len) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+       if (getsockopt(fd, IPPROTO_IP, IP_TTL, &val, &len) != -1) e(0);
+       if (errno != ENOPROTOOPT) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test setting and retrieving IP-level options.
+ */
+static void
+test91f(void)
+{
+
+       subtest = 6;
+
+       sub91f(SOCK_STREAM);
+
+       sub91f(SOCK_DGRAM);
+}
+
+/*
+ * Test setting and retrieving IP-level options on UDP sockets and packets.
+ * As part of this, ensure that the maximum set of supported control options
+ * can be both sent and received, both for IPv4 and IPv6.  Any options that are
+ * newly added to the service and may be combined with the existing ones should
+ * be added to this subtest as well.  The control data handling code is shared
+ * between UDP and RAW, so there is no need to repeat this test for the latter.
+ */
+static void
+test91g(void)
+{
+       struct sockaddr_in6 sin6;
+       struct sockaddr_in sin;
+       struct iovec iov;
+       struct msghdr msg;
+       struct cmsghdr *cmsg, cmsg2;
+       struct in_pktinfo ipi;
+       struct in6_pktinfo ipi6;
+       unsigned int ifindex;
+       char buf[1];
+       union {
+               struct cmsghdr cmsg;
+               char buf[256];
+       } control;
+       uint8_t byte;
+       size_t size;
+       int fd, fd2, val, seen_tos, seen_ttl, seen_pktinfo;
+
+       subtest = 7;
+
+       ifindex = if_nametoindex(LOOPBACK_IFNAME);
+       if (ifindex == 0) e(0);
+
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       val = 1;
+       /* Strangely, IP_RECVTOS is not a thing.. */
+       if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &val, sizeof(val)) != 0)
+               e(0);
+       if (setsockopt(fd, IPPROTO_IP, IP_RECVPKTINFO, &val, sizeof(val)) != 0)
+               e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       iov.iov_base = "A";
+       iov.iov_len = 1;
+
+       val = 39;
+       control.cmsg.cmsg_len = CMSG_LEN(sizeof(val));
+       control.cmsg.cmsg_level = IPPROTO_IP;
+       control.cmsg.cmsg_type = IP_TTL;
+       memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val));
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = (struct sockaddr *)&sin;
+       msg.msg_namelen = sizeof(sin);
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = control.cmsg.cmsg_len;
+
+       if (sendmsg(fd2, &msg, 0) != 1) e(0);
+
+       iov.iov_base = buf;
+       iov.iov_len = sizeof(buf);
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = sizeof(control);
+
+       if (recvmsg(fd, &msg, 0) != 1) e(0);
+       if (buf[0] != 'A') e(0);
+
+       seen_ttl = seen_pktinfo = 0;
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+           cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level != IPPROTO_IP) e(0);
+               switch (cmsg->cmsg_type) {
+               case IP_TTL:
+                       /* The odd one out, using a uint8_t.. */
+                       if (seen_ttl++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(byte))) e(0);
+                       memcpy(&byte, CMSG_DATA(cmsg), sizeof(byte));
+                       if (byte != 39) e(0);
+                       break;
+               case IP_PKTINFO:
+                       if (seen_pktinfo++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(ipi))) e(0);
+                       memcpy(&ipi, CMSG_DATA(cmsg), sizeof(ipi));
+                       if (ipi.ipi_addr.s_addr != sin.sin_addr.s_addr) e(0);
+                       if (ipi.ipi_ifindex != ifindex) e(0);
+                       break;
+               default:
+                       e(0);
+               }
+       }
+       if (!seen_ttl) e(0);
+       if (!seen_pktinfo) e(0);
+
+       /* Test that we can provide all supported IPv4 options at once. */
+       iov.iov_base = "B";
+       iov.iov_len = 1;
+
+       val = 1;
+       control.cmsg.cmsg_len = CMSG_LEN(sizeof(val));
+       control.cmsg.cmsg_level = IPPROTO_IP;
+       control.cmsg.cmsg_type = IP_TOS;
+       memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val));
+
+       size = CMSG_SPACE(sizeof(val));
+
+       if ((cmsg = CMSG_NXTHDR(&msg, &control.cmsg)) == NULL) e(0);
+       val = 41;
+       cmsg2.cmsg_len = CMSG_LEN(sizeof(val));
+       cmsg2.cmsg_level = IPPROTO_IP;
+       cmsg2.cmsg_type = IP_TTL;
+       memcpy(cmsg, &cmsg2, sizeof(cmsg2));
+       memcpy(CMSG_DATA(cmsg), &val, sizeof(val));
+
+       size += CMSG_SPACE(sizeof(val));
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = (struct sockaddr *)&sin;
+       msg.msg_namelen = sizeof(sin);
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = size;
+
+       if (sendmsg(fd2, &msg, 0) != 1) e(0);
+
+       iov.iov_base = buf;
+       iov.iov_len = sizeof(buf);
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = sizeof(control);
+
+       if (recvmsg(fd, &msg, 0) != 1) e(0);
+       if (buf[0] != 'B') e(0);
+
+       /* Check just the TTL this time. */
+       seen_ttl = 0;
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+           cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level != IPPROTO_IP) e(0);
+               if (cmsg->cmsg_type == IP_TTL) {
+                       /* The odd one out, using a uint8_t.. */
+                       if (seen_ttl++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(byte))) e(0);
+                       memcpy(&byte, CMSG_DATA(cmsg), sizeof(byte));
+                       if (byte != 41) e(0);
+               }
+       }
+       if (!seen_ttl) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* That was IPv4, onto IPv6.. */
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &val,
+           sizeof(val)) != 0) e(0);
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val,
+           sizeof(val)) != 0) e(0);
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+           sizeof(val)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       val = 94;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != 0)
+               e(0);
+
+       iov.iov_base = "C";
+       iov.iov_len = 1;
+
+       val = 39;
+       control.cmsg.cmsg_len = CMSG_LEN(sizeof(val));
+       control.cmsg.cmsg_level = IPPROTO_IPV6;
+       control.cmsg.cmsg_type = IPV6_HOPLIMIT;
+       memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val));
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = (struct sockaddr *)&sin6;
+       msg.msg_namelen = sizeof(sin6);
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = control.cmsg.cmsg_len;
+
+       if (sendmsg(fd2, &msg, 0) != 1) e(0);
+
+       iov.iov_base = buf;
+       iov.iov_len = sizeof(buf);
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = sizeof(control);
+
+       if (recvmsg(fd, &msg, 0) != 1) e(0);
+       if (buf[0] != 'C') e(0);
+
+       seen_tos = seen_ttl = seen_pktinfo = 0;
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+           cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level != IPPROTO_IPV6) e(0);
+               switch (cmsg->cmsg_type) {
+               case IPV6_TCLASS:
+                       if (seen_tos++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0);
+                       memcpy(&val, CMSG_DATA(cmsg), sizeof(val));
+                       if (val != 94) e(0);
+                       break;
+               case IPV6_HOPLIMIT:
+                       if (seen_ttl++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0);
+                       memcpy(&val, CMSG_DATA(cmsg), sizeof(val));
+                       if (val != 39) e(0);
+                       break;
+               case IPV6_PKTINFO:
+                       if (seen_pktinfo++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(ipi6))) e(0);
+                       memcpy(&ipi6, CMSG_DATA(cmsg), sizeof(ipi6));
+                       if (memcmp(&ipi6.ipi6_addr, &in6addr_loopback,
+                           sizeof(ipi6.ipi6_addr))) e(0);
+                       if (ipi6.ipi6_ifindex != ifindex) e(0);
+                       break;
+               default:
+                       e(0);
+               }
+       }
+       if (!seen_tos) e(0);
+       if (!seen_ttl) e(0);
+       if (!seen_pktinfo) e(0);
+
+       /*
+        * Test that (for IPv6) an option of -1 overrides setsockopt.
+        * Also test that we can provide all supported IPv6 options at once.
+        */
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+           sizeof(val)) != 0) e(0);
+
+       iov.iov_base = "D";
+       iov.iov_len = 1;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_control = control.buf;
+       msg.msg_controllen = sizeof(control.buf);
+
+       val = -1;
+       control.cmsg.cmsg_len = CMSG_LEN(sizeof(val));
+       control.cmsg.cmsg_level = IPPROTO_IPV6;
+       control.cmsg.cmsg_type = IPV6_TCLASS;
+       memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val));
+
+       size = CMSG_SPACE(sizeof(val));
+
+       if ((cmsg = CMSG_NXTHDR(&msg, &control.cmsg)) == NULL) e(0);
+       val = 78;
+       cmsg2.cmsg_len = CMSG_LEN(sizeof(val));
+       cmsg2.cmsg_level = IPPROTO_IPV6;
+       cmsg2.cmsg_type = IPV6_HOPLIMIT;
+       memcpy(cmsg, &cmsg2, sizeof(cmsg2));
+       memcpy(CMSG_DATA(cmsg), &val, sizeof(val));
+
+       size += CMSG_SPACE(sizeof(val));
+
+       if ((cmsg = CMSG_NXTHDR(&msg, cmsg)) == NULL) e(0);
+       cmsg2.cmsg_len = CMSG_LEN(sizeof(ipi6));
+       cmsg2.cmsg_level = IPPROTO_IPV6;
+       cmsg2.cmsg_type = IPV6_PKTINFO;
+       memcpy(cmsg, &cmsg2, sizeof(cmsg2));
+       memset(&ipi6, 0, sizeof(ipi6));
+       memcpy(CMSG_DATA(cmsg), &ipi6, sizeof(ipi6));
+
+       size += CMSG_SPACE(sizeof(ipi6));
+
+       if (size > sizeof(control.buf)) e(0);
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = (struct sockaddr *)&sin6;
+       msg.msg_namelen = sizeof(sin6);
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = size;
+
+       if (sendmsg(fd2, &msg, 0) != 1) e(0);
+
+       iov.iov_base = buf;
+       iov.iov_len = sizeof(buf);
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = control.buf;
+       msg.msg_controllen = sizeof(control);
+
+       if (recvmsg(fd, &msg, 0) != 1) e(0);
+       if (buf[0] != 'D') e(0);
+
+       seen_tos = seen_ttl = 0;
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+           cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level != IPPROTO_IPV6) e(0);
+               switch (cmsg->cmsg_type) {
+               case IPV6_TCLASS:
+                       if (seen_tos++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0);
+                       memcpy(&val, CMSG_DATA(cmsg), sizeof(val));
+                       if (val != 0) e(0);
+                       break;
+               case IPV6_HOPLIMIT:
+                       if (seen_ttl++) e(0);
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0);
+                       memcpy(&val, CMSG_DATA(cmsg), sizeof(val));
+                       if (val != 78) e(0);
+                       break;
+               default:
+                       e(0);
+               }
+       }
+       if (!seen_tos) e(0);
+       if (!seen_ttl) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test receiving IPv4 packets on IPv6 sockets.
+ */
+static void
+test91h(void)
+{
+       struct sockaddr_in6 sin6;
+       struct sockaddr_in sin;
+       struct iovec iov;
+       struct msghdr msg;
+       struct cmsghdr *cmsg;
+       struct in6_pktinfo ipi6;
+       unsigned int ifindex;
+       char buf[1], buf2[256];
+       int fd, fd2, val;
+
+       subtest = 8;
+
+       ifindex = if_nametoindex(LOOPBACK_IFNAME);
+       if (ifindex == 0) e(0);
+
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+           sizeof(val)) != 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (sendto(fd2, "A", 1, 0, (struct sockaddr *)&sin, sizeof(sin)) != 1)
+               e(0);
+
+       iov.iov_base = buf;
+       iov.iov_len = sizeof(buf);
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_name = (struct sockaddr *)&sin6;
+       msg.msg_namelen = sizeof(sin6);
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = buf2;
+       msg.msg_controllen = sizeof(buf2);
+
+       if (recvmsg(fd, &msg, 0) != 1) e(0);
+       if (buf[0] != 'A') e(0);
+
+       if (msg.msg_namelen != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) e(0);
+       if (cmsg->cmsg_level != IPPROTO_IPV6) e(0);
+       if (cmsg->cmsg_type != IPV6_PKTINFO) e(0);
+       if (cmsg->cmsg_len != CMSG_LEN(sizeof(ipi6))) e(0);
+
+       /*
+        * The packet was sent from loopback to loopback, both with IPv4-mapped
+        * IPv6 addresses, so we can simply compare source and destination.
+        */
+       memcpy(&ipi6, CMSG_DATA(cmsg), sizeof(ipi6));
+       if (memcmp(&sin6.sin6_addr, &ipi6.ipi6_addr, sizeof(sin6.sin6_addr)))
+               e(0);
+       if (ipi6.ipi6_ifindex != ifindex) e(0);
+
+       if (CMSG_NXTHDR(&msg, cmsg) != NULL) e(0);
+
+       /*
+        * Sqeeze in a quick test to see what happens if the receiver end does
+        * not provide a control buffer after having requested control data,
+        * because a half-complete version of this test triggered a bug there..
+        */
+       if (sendto(fd2, "B", 1, 0, (struct sockaddr *)&sin, sizeof(sin)) != 1)
+               e(0);
+
+       if (recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL) != 1) e(0);
+       if (buf[0] != 'B') e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test that binding a socket of the given type to a privileged port is
+ * disallowed.
+ */
+static void
+sub91i(int type)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       int fd, port;
+
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       for (port = IPPORT_RESERVED - 1; port >= 0; port--) {
+               sin.sin_port = htons(port);
+
+               if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+               if (errno == EADDRINUSE) continue;
+               if (errno != EACCES) e(0);
+               break;
+       }
+
+       for (port = IPPORT_RESERVED; port <= UINT16_MAX; port++) {
+               sin.sin_port = htons(port);
+
+               if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == 0)
+                       break;
+               if (errno != EADDRINUSE) e(0);
+       }
+
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET6, type, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       for (port = IPV6PORT_RESERVED - 1; port >= 0; port--) {
+               sin6.sin6_port = htons(port);
+
+               if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1)
+                       e(0);
+               if (errno == EADDRINUSE) continue;
+               if (errno != EACCES) e(0);
+               break;
+       }
+
+       for (port = IPV6PORT_RESERVED; port <= UINT16_MAX; port++) {
+               sin6.sin6_port = htons(port);
+
+               if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) == 0)
+                       break;
+               if (errno != EADDRINUSE) e(0);
+       }
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test that binding to privileged ports is disallowed for non-root users.
+ * Also make sure that such users cannot create raw sockets at all.  This test
+ * is not to be run by root, but for convenience we first try to drop
+ * privileges for the duration of the test anyway.
+ */
+static void
+test91i(void)
+{
+       int i;
+
+       subtest = 9;
+
+       (void)seteuid(1);
+
+       sub91i(SOCK_STREAM);
+
+       sub91i(SOCK_DGRAM);
+
+       for (i = 0; i < IPPROTO_MAX; i++) {
+               if (socket(AF_INET, SOCK_RAW, i) != -1) e(0);
+               if (errno != EACCES) e(0);
+               if (socket(AF_INET6, SOCK_RAW, i) != -1) e(0);
+               if (errno != EACCES) e(0);
+       }
+
+       (void)seteuid(0);
+}
+
+/*
+ * Test setting and getting basic UDP/RAW multicast transmission options.
+ */
+static void
+test91j(void)
+{
+
+       subtest = 10;
+
+       socklib_multicast_tx_options(SOCK_DGRAM);
+}
+
+/*
+ * Test TCP socket state changes related to the listen queue.  This test is
+ * derived from test90y, but sufficiently different to be its own copy.
+ */
+static void
+test91k(void)
+{
+       struct sockaddr_in6 sin6A, sin6B, sin6C;
+       socklen_t len;
+       struct timeval tv;
+       struct linger l;
+       fd_set fds;
+       char buf[7];
+       int fd, fd2, fd3, fd4, val, fl;
+
+       subtest = 11;
+
+       if ((fd = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+       sin6A.sin6_port = htons(TEST_PORT_A);
+       memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr));
+
+       if (bind(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       /*
+        * Any socket options should be inherited from the listening socket at
+        * connect time, and not be re-inherited at accept time, to the extent
+        * that they are inherited at all.  TCP/IP level options are not.
+        */
+       val = 123;
+       if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val)) != 0)
+               e(0);
+       val = 32768;
+       if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0)
+               e(0);
+
+       if (listen(fd, 5) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&sin6B, 0, sizeof(sin6B));
+       sin6B.sin6_family = AF_INET6;
+       sin6B.sin6_port = htons(0);
+       memcpy(&sin6B.sin6_addr, &in6addr_loopback, sizeof(sin6B.sin6_addr));
+
+       val = 1;
+       if (setsockopt(fd2, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) != 0)
+               e(0);
+
+       if (bind(fd2, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
+
+       len = sizeof(sin6B);
+       if (getsockname(fd2, (struct sockaddr *)&sin6B, &len) != 0) e(0);
+       if (len != sizeof(sin6B)) e(0);
+       if (sin6B.sin6_port == htons(0)) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       val = 456;
+       if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val)) != 0)
+               e(0);
+       val = 16384;
+       if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0)
+               e(0);
+
+       /*
+        * Obtaining the peer name should work.  As always, the name should be
+        * inherited from the listening socket.
+        */
+       len = sizeof(sin6C);
+       if (getpeername(fd2, (struct sockaddr *)&sin6C, &len) != 0) e(0);
+       if (sin6C.sin6_len != sizeof(sin6C)) e(0);
+       if (sin6C.sin6_family != AF_INET6) e(0);
+       if (sin6C.sin6_port != htons(TEST_PORT_A)) e(0);
+       if (memcmp(&sin6C.sin6_addr, &in6addr_loopback,
+           sizeof(sin6C.sin6_addr)) != 0) e(0);
+
+       /*
+        * Sending to the socket should work, and it should be possible to
+        * receive the data from the other side once accepted.
+        */
+       if (send(fd2, "Hello, ", 7, 0) != 7) e(0);
+       if (send(fd2, "world!", 6, 0) != 6) e(0);
+
+       /* Shutdown settings should be visible after accepting, too. */
+       if (shutdown(fd2, SHUT_RDWR) != 0) e(0);
+
+       memset(&sin6C, 0, sizeof(sin6C));
+       len = sizeof(sin6C);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6C, &len)) < 0) e(0);
+       if (sin6C.sin6_len != sizeof(sin6C)) e(0);
+       if (sin6C.sin6_family != AF_INET6) e(0);
+       if (sin6C.sin6_port != sin6B.sin6_port) e(0);
+       if (memcmp(&sin6C.sin6_addr, &in6addr_loopback,
+           sizeof(sin6C.sin6_addr)) != 0) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd3, SOL_SOCKET, SO_SNDLOWAT, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 123) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd3, SOL_SOCKET, SO_RCVBUF, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 32768) e(0);
+
+       if ((fl = fcntl(fd3, F_GETFL)) == -1) e(0);
+       if (!(fl & O_NONBLOCK)) e(0);
+       if (fcntl(fd3, F_SETFL, fl & ~O_NONBLOCK) != 0) e(0);
+
+       if (recv(fd3, buf, 7, 0) != 7) e(0);
+       if (memcmp(buf, "Hello, ", 7) != 0) e(0);
+       if (recv(fd3, buf, 7, 0) != 6) e(0);
+       if (memcmp(buf, "world!", 6) != 0) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0);
+
+       /*
+        * Unlike in the UDS test, the other side's shutdown-for-reading is not
+        * visible to this side, so sending data should work just fine until we
+        * close or shut down the socket ourselves.  The other side will simply
+        * discard the incoming data.
+        */
+       if (send(fd3, "", 1, MSG_NOSIGNAL) != 1) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd3) != 0) e(0);
+
+       /*
+        * If the connection pending acceptance is closed, the connection must
+        * remain on the queue, and the accepting party will read EOF from it.
+        * Try once without pending data, once with pending data.
+        */
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       len = sizeof(sin6B);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd3, SOL_SOCKET, SO_SNDLOWAT, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 456) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd3, SOL_SOCKET, SO_RCVBUF, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 16384) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0);
+
+       if (close(fd3) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (send(fd2, "Hello!", 6, 0) != 6) e(0);
+       if (close(fd2) != 0) e(0);
+
+       len = sizeof(sin6B);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != 6) e(0);
+       if (memcmp(buf, "Hello!", 6) != 0) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0);
+
+       if (close(fd3) != 0) e(0);
+
+       /*
+        * If the connection pending acceptance is aborted, the listening
+        * socket should pretend as though the connection was never there.
+        */
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       memset(&l, 0, sizeof(l));
+       l.l_onoff = 1;
+       l.l_linger = 0;
+       if (setsockopt(fd2, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
+       if (FD_ISSET(fd, &fds)) e(0);
+
+       len = sizeof(sin6B);
+       if (accept(fd, (struct sockaddr *)&sin6B, &len) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       /*
+        * Try the same thing, but now with the connection sandwiched between
+        * two different pending connections, which should be left intact.
+        */
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (send(fd2, "A", 1, 0) != 1) e(0);
+
+       if ((fd3 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd3, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (send(fd3, "B", 1, 0) != 1) e(0);
+
+       if ((fd4 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd4, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (send(fd4, "C", 1, 0) != 1) e(0);
+
+       if (setsockopt(fd3, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0);
+
+       if (close(fd3) != 0) e(0);
+
+       len = sizeof(sin6B);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'A') e(0);
+
+       if (close(fd3) != 0) e(0);
+       if (close(fd2) != 0) e(0);
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       len = sizeof(sin6B);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'C') e(0);
+
+       if (close(fd3) != 0) e(0);
+       if (close(fd4) != 0) e(0);
+
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
+       if (FD_ISSET(fd, &fds)) e(0);
+
+       len = sizeof(sin6B);
+       if (accept(fd, (struct sockaddr *)&sin6B, &len) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       /*
+        * If the listening socket was closed, the sockets pending acceptance
+        * should be reset.
+        */
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if ((fd3 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd3, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != -1) e(0);
+       if (errno != ECONNRESET) e(0);
+
+       if (recv(fd2, buf, sizeof(buf), 0) != 0) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != -1) e(0);
+       if (errno != ECONNRESET) e(0);
+
+       if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0);
+
+       if (close(fd3) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+}
+
+/*
+ * Obtain a pair of connected TCP socket.
+ */
+static int
+get_tcp_pair(int domain, int type, int protocol, int fd[2])
+{
+       struct sockaddr_in6 sin6;
+       struct sockaddr_in sin;
+       struct sockaddr *addr;
+       socklen_t addr_len, len;
+       int lfd, val;
+
+       if (domain == AF_INET6) {
+               memset(&sin6, 0, sizeof(sin6));
+               sin6.sin6_family = AF_INET6;
+               memcpy(&sin6.sin6_addr, &in6addr_loopback,
+                   sizeof(sin6.sin6_addr));
+
+               addr = (struct sockaddr *)&sin6;
+               addr_len = sizeof(sin6);
+       } else {
+               assert(domain == AF_INET);
+
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_family = AF_INET;
+               sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+               addr = (struct sockaddr *)&sin;
+               addr_len = sizeof(sin);
+       }
+
+       if ((lfd = socket(domain, type, protocol)) < 0) e(0);
+
+       if (bind(lfd, addr, addr_len) != 0) e(0);
+
+       len = addr_len;
+       if (getsockname(lfd, addr, &len) != 0) e(0);
+       if (len != addr_len) e(0);
+
+       if (listen(lfd, 1) != 0) e(0);
+
+       if ((fd[0] = socket(domain, type, protocol)) < 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd[0], IPPROTO_TCP, TCP_NODELAY, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (connect(fd[0], addr, addr_len) != 0) e(0);
+
+       len = addr_len;
+       if ((fd[1] = accept(lfd, addr, &len)) < 0) e(0);
+       if (len != addr_len) e(0);
+
+       if (setsockopt(fd[1], IPPROTO_TCP, TCP_NODELAY, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (close(lfd) != 0) e(0);
+
+       return 0;
+}
+
+/*
+ * Test large transfers and MSG_WAITALL.
+ */
+static void
+test91l(void)
+{
+       int fd[2];
+
+       subtest = 12;
+
+       get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd);
+
+       socklib_large_transfers(fd);
+
+       get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd);
+
+       socklib_large_transfers(fd);
+}
+
+/*
+ * A randomized producer-consumer test for stream sockets.  As part of this,
+ * we also perform very basic bulk functionality tests of FIONREAD, MSG_PEEK,
+ * MSG_DONTWAIT, and MSG_WAITALL.
+ */
+static void
+test91m(void)
+{
+       int fd[2];
+
+       subtest = 13;
+
+       get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd);
+
+       socklib_producer_consumer(fd);
+
+       get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd);
+
+       socklib_producer_consumer(fd);
+}
+
+/*
+ * Cause a receive call on the peer side of the connection of 'fd' to be
+ * aborted in a protocol-specific way.  Return -1 to indicate that the given
+ * file descriptor has been closed.
+ */
+static int
+test91_reset(int fd, const char * data __unused, size_t len __unused)
+{
+       struct linger l;
+
+       l.l_onoff = 1;
+       l.l_linger = 0;
+       if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       return -1;
+}
+
+/*
+ * Test for receiving on stream sockets.  In particular, test SO_RCVLOWAT,
+ * MSG_PEEK, MSG_DONTWAIT, and MSG_WAITALL.
+ */
+static void
+test91n(void)
+{
+
+       subtest = 14;
+
+       socklib_stream_recv(get_tcp_pair, AF_INET, SOCK_STREAM,
+           test91_reset);
+}
+
+/*
+ * Return the send and receive buffer sizes for sockets of the given type.  The
+ * two individual values are stored in 'sndbuf' and 'rcvbuf', for each that is
+ * not NULL, and the sum is returned from the call.
+ */
+static int
+get_buf_sizes(int type, int * sndbufp, int * rcvbufp)
+{
+       socklen_t len;
+       int fd, sndbuf, rcvbuf;
+
+       if ((fd = socket(AF_INET, type, 0)) < 0) e(0);
+
+       len = sizeof(sndbuf);
+       if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &len) != 0) e(0);
+       if (len != sizeof(sndbuf)) e(0);
+       if (sndbufp != NULL)
+               *sndbufp = sndbuf;
+
+       len = sizeof(rcvbuf);
+       if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) != 0) e(0);
+       if (len != sizeof(rcvbuf)) e(0);
+       if (rcvbufp != NULL)
+               *rcvbufp = rcvbuf;
+
+       if (close(fd) != 0) e(0);
+
+       return sndbuf + rcvbuf;
+}
+
+/*
+ * The following constant should be set to the window size used within lwIP.
+ * There is currently no way to obtain this constant from the LWIP service, nor
+ * would that be information that should ever be used by general applications,
+ * but we need it to fill socket receive queues in a reliable way.  TODO: find
+ * a better solution for this general problem.
+ */
+#define WINDOW_SIZE    16384   /* TCP_WND in lwipopt.h, keep in sync! */
+
+#define CHUNK          4096    /* base I/O chunk size */
+#define USLEEP_TIME    250000  /* increase on wimpy platforms if needed */
+
+/*
+ * Fill the receive of socket 'rfd' with data, and if 'fill_send' is non-zero,
+ * also the send queue of socket 'sfd'.  If 'fill_send' is zero, 'delta' may be
+ * a non-zero value indicating how many bytes extra (delta > 0) or fewer
+ * (delta < 0) should be sent compared to the receive queue size.
+ */
+static void
+fill_tcp_bufs(int sfd, int rfd, int fill_send, int delta)
+{
+       unsigned char buf[CHUNK], c;
+       socklen_t len;
+       int sndbuf, rcvbuf, mss, chunk, left, res;
+
+       assert(!fill_send || delta == 0);
+
+       (void)get_buf_sizes(SOCK_STREAM, &sndbuf, &rcvbuf);
+
+       len = sizeof(mss);
+       if (getsockopt(sfd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len) != 0) e(0);
+
+       left = rcvbuf;
+       if (delta < 0)
+               left += delta;
+
+       memset(buf, 0, sizeof(buf));
+
+       /*
+        * In general, TCP is not designed for what we want to do here, which
+        * is to control the contents of the receive buffer down to the last
+        * byte.  We already assume that the caller has disabled the Nagle
+        * algorithm, but we still have to deal with other algorithms that
+        * effectively get in the way of full control of the receive buffer.
+        *
+        * In particular, we have to work around an issue where lwIP decides to
+        * start shrinking the window earlier than necessary.  This issue
+        * triggers during the transition from a fully open window to a reduced
+        * window.  If no acknowledgement is sent when exactly that point is
+        * reached, the next acknowlegment will not announce the full size of
+        * the remainder of the window.  This appears to be part of the silly
+        * window avoidance logic, so it is probably intentional behavior and
+        * thus we have to work around it.
+        *
+        * So far it appears that filling up just the window size does the job,
+        * as long as the last segment is a full MSS-sized segment and each
+        * segment is acknowledged (which is why we send data in the other
+        * direction).  Anything short of that may trigger edge cases that, in
+        * some cases, show up only on slow platforms (e.g. BeagleBones).
+        *
+        * Note that while test91z also fills up receive queues using its own
+        * algorithm, it sets the receive queue to the window size, thereby
+        * avoiding the need for this more complicated algorithm.
+        */
+       for (left = rcvbuf - WINDOW_SIZE; left > 0; left -= chunk) {
+               chunk = (left % mss != 0) ? (left % mss) : mss;
+               assert(chunk <= left);
+
+               if (send(sfd, buf, chunk, 0) != chunk) e(0);
+
+               if (send(rfd, ".", 1, 0) != 1) e(0);
+
+               if (recv(sfd, &c, 1, 0) != 1) e(0);
+               if (c != '.') e(0);
+       }
+
+       /* We are done with the hard part.  Now fill up the rest. */
+       if (fill_send)
+               delta = sndbuf;
+
+       for (left = WINDOW_SIZE + delta; left > 0; left -= res) {
+               chunk = MIN(left, sizeof(buf));
+
+               res = send(sfd, buf, chunk, 0);
+
+               if (res <= 0) e(0);
+               if (res > chunk) e(0);
+       }
+}
+
+/*
+ * Signal handler which just needs to exist, so that invoking it will interrupt
+ * an ongoing system call.
+ */
+static void
+test91_got_signal(int sig __unused)
+{
+
+       /* Nothing. */
+}
+
+/*
+ * Test for sending on stream sockets.  The quick summary here is that send()
+ * should basically act as the mirror of recv(MSG_WAITALL), i.e., it should
+ * keep suspending until all data is sent (or the call is interrupted or no
+ * more can possibly be sent), and, SO_SNDLOWAT, mirroring SO_RCVLOWAT, acts as
+ * an admission test for the send: nothing is sent until there is room in the
+ * send buffer (i.e., the peer's receive buffer) for at least the low send
+ * watermark, or the whole send request length, whichever is smaller.  In
+ * addition, select(2) should use the same threshold.
+ *
+ * This test is a copy of test90v, and would be in socklib instead, were it not
+ * for the fact that TCP's segmentation and silly window avoidance make it
+ * impossible to perform the same exact, byte-granular test.  Instead, this TCP
+ * implementation paints with a somewhat broader brush, using send and receive
+ * chunk sizes large enough to overcome the normally desirable TCP features
+ * that are now getting in the way.  As a result, this copy of the test is not
+ * only somewhat less effective but also a bit more reliant on specific (TCP)
+ * settings, although the whole test is still way too useful to skip at all.
+ */
+static void
+sub91o(int iroom, int istate, int slowat, int len, int bits, int act)
+{
+       struct sigaction sa;
+       struct timeval tv;
+       char buf[CHUNK * 4];
+       fd_set fds;
+       pid_t pid;
+       int fd[2], min, flags, res, err;
+       int pfd[2], orig_iroom, eroom, tstate, fl, status;
+
+       if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0);
+
+       /*
+        * Set up the initial condition on the sockets.
+        */
+       fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/);
+
+       /*
+        * Receive a bit more than we send, to free up enough room (the MSS) to
+        * get things going again.
+        */
+       orig_iroom = iroom;
+       iroom += iroom / 2;
+       if (iroom > 0)
+               if (recv(fd[1], buf, iroom, 0) != iroom) e(0);
+
+       switch (istate) {
+       case 0: break;
+       case 1: if (shutdown(fd[0], SHUT_WR) != 0) e(0); break;
+       case 2: if (close(fd[1]) != 0) e(0); break;
+       }
+
+       if (setsockopt(fd[0], SOL_SOCKET, SO_SNDLOWAT, &slowat,
+           sizeof(slowat)) != 0) e(0);
+
+       /* SO_SNDLOWAT is always bounded by the actual send length. */
+       min = MIN(len, slowat);
+
+       flags = MSG_NOSIGNAL;
+       if (bits & 1) flags |= MSG_DONTWAIT;
+
+       /*
+        * Do a quick select test to see if its result indeed matches whether
+        * the available space in the "send" buffer meets the threshold.
+        */
+       FD_ZERO(&fds);
+       FD_SET(fd[0], &fds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       res = select(fd[0] + 1, NULL, &fds, NULL, &tv);
+       if (res < 0 || res > 1) e(0);
+       if (res != (iroom >= slowat || istate > 0)) e(0);
+       if (res == 1 && !FD_ISSET(fd[0], &fds)) e(0);
+
+       /*
+        * Cut short a whole lot of cases, to avoid the overhead of forking,
+        * namely when we know the call should return immediately.  This is the
+        * case when the socket state disallows further sending, or when all
+        * data could be sent, or when the call was non-blocking.  The low
+        * send watermark only helps determine whether anything was sent here.
+        */
+       if (istate > 0 || iroom >= len || (flags & MSG_DONTWAIT)) {
+               res = send(fd[0], buf, len, flags);
+
+               if (istate > 0) {
+                       if (res != -1) e(0);
+                       if (errno != EPIPE && errno != ECONNRESET) e(0);
+               } else if (iroom >= len) {
+                       if (res != len) e(0);
+               } else if (iroom >= min) {
+                       if (res < orig_iroom || res > iroom) e(0);
+               } else {
+                       if (res != -1) e(0);
+                       if (errno != EWOULDBLOCK) e(0);
+               }
+
+               /* Early cleanup and return to avoid even more code clutter. */
+               if (istate != 2 && close(fd[1]) != 0) e(0);
+               if (close(fd[0]) != 0) e(0);
+
+               return;
+       }
+
+       /*
+        * Now starts the interesting stuff: the send call should now block,
+        * even though if we add MSG_DONTWAIT it may not return EWOULDBLOCK,
+        * because MSG_DONTWAIT prevents the send from blocking after partial
+        * completion.  As such, we can only test our expectations by letting
+        * the call block, in a child process, and waiting.  We do test as much
+        * of the above assumption as we can for safety right here, but this is
+        * not a substitute for actually blocking even in these cases!
+        */
+       if (iroom < min) {
+               if (send(fd[0], buf, len, flags | MSG_DONTWAIT) != -1) e(0);
+               if (errno != EWOULDBLOCK) e(0);
+       }
+
+       /*
+        * If (act < 9), we receive 0, 1, or 2 bytes from the receive queue
+        * before forcing the send call to terminate in one of three ways.
+        *
+        * If (act == 9), we use a signal to interrupt the send call.
+        */
+       if (act < 9) {
+               eroom = (act % 3) * (CHUNK + CHUNK / 2 - 1);
+               tstate = act / 3;
+       } else
+               eroom = tstate = 0;
+
+       if (pipe2(pfd, O_NONBLOCK) != 0) e(0);
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               if (close(fd[1]) != 0) e(0);
+               if (close(pfd[0]) != 0) e(0);
+
+               if (act == 9) {
+                       memset(&sa, 0, sizeof(sa));
+                       sa.sa_handler = test91_got_signal;
+                       if (sigaction(SIGUSR1, &sa, NULL) != 0) e(0);
+               }
+
+               res = send(fd[0], buf, len, flags);
+               err = errno;
+
+               if (write(pfd[1], &res, sizeof(res)) != sizeof(res)) e(0);
+               if (write(pfd[1], &err, sizeof(err)) != sizeof(err)) e(0);
+
+               exit(errct);
+       case -1:
+               e(0);
+       }
+
+       if (close(pfd[1]) != 0) e(0);
+
+       /*
+        * Allow the child to enter the blocking send(2), and check the pipe
+        * to see if it is really blocked.
+        */
+       if (usleep(USLEEP_TIME) != 0) e(0);
+
+       if (read(pfd[0], &res, sizeof(res)) != -1) e(0);
+       if (errno != EAGAIN) e(0);
+
+       if (eroom > 0) {
+               if (recv(fd[1], buf, eroom, 0) != eroom) e(0);
+
+               /*
+                * The threshold for the send is now met if the entire request
+                * has been satisfied.
+                */
+               if (iroom + eroom >= len) {
+                       if ((fl = fcntl(pfd[0], F_GETFL, 0)) == -1) e(0);
+                       if (fcntl(pfd[0], F_SETFL, fl & ~O_NONBLOCK) != 0)
+                               e(0);
+
+                       if (read(pfd[0], &res, sizeof(res)) != sizeof(res))
+                               e(0);
+                       if (read(pfd[0], &err, sizeof(err)) != sizeof(err))
+                               e(0);
+
+                       if (res != len) e(0);
+
+                       /* Bail out. */
+                       goto cleanup;
+               }
+       }
+
+       if (act < 9) {
+               /*
+                * Now test various ways to terminate the send call.
+                *
+                * For other socket drivers, there should also be a case where
+                * a socket error is raised instead.  For UDS there is no way
+                * to do that on stream-type sockets, not even with SO_LINGER.
+                */
+               switch (tstate) {
+               case 0: if (shutdown(fd[0], SHUT_WR) != 0) e(0); break;
+               case 1: if (close(fd[1]) != 0) e(0); fd[1] = -1; break;
+               case 2: fd[1] = test91_reset(fd[1], NULL, 0); break;
+               }
+       } else
+               if (kill(pid, SIGUSR1) != 0) e(0);
+
+       if ((fl = fcntl(pfd[0], F_GETFL, 0)) == -1) e(0);
+       if (fcntl(pfd[0], F_SETFL, fl & ~O_NONBLOCK) != 0) e(0);
+
+       if (read(pfd[0], &res, sizeof(res)) != sizeof(res)) e(0);
+       if (read(pfd[0], &err, sizeof(err)) != sizeof(err)) e(0);
+
+       /*
+        * If the send met the threshold before being terminate or interrupted,
+        * we should at least have sent something.  Otherwise, the send was
+        * never admitted and should return EPIPE or ECONNRESET (if the send
+        * was terminated) or EINTR (if the child was killed).
+        */
+       if (iroom + eroom >= min) {
+               if (res < MIN(orig_iroom, len)) e(0);
+               if (res > MIN(iroom + eroom, len)) e(0);
+       } else {
+               if (res != -1) e(0);
+               if (act < 9) {
+                       if (err != EPIPE && err != ECONNRESET) e(0);
+               } else
+                       if (err != EINTR) e(0);
+       }
+
+cleanup:
+       if (close(pfd[0]) != 0) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       if (fd[1] != -1 && close(fd[1]) != 0) e(0);
+       if (close(fd[0]) != 0) e(0);
+}
+
+/*
+ * Test for sending on stream sockets.  In particular, test SO_SNDLOWAT and
+ * MSG_DONTWAIT.
+ */
+static void
+test91o(void)
+{
+       int iroom, istate, slowat, len, bits, act;
+
+       subtest = 15;
+
+       /* Insanity. */
+       for (iroom = 0; iroom <= CHUNK * 2; iroom += CHUNK)
+               for (istate = 0; istate <= 2; istate++)
+                       for (slowat = CHUNK; slowat <= CHUNK * 2;
+                           slowat += CHUNK)
+                               for (len = CHUNK; len <= CHUNK * 2;
+                                   len += CHUNK)
+                                       for (bits = 0; bits < 2; bits++)
+                                               for (act = 0; act <= 9; act++)
+                                                       sub91o(iroom, istate,
+                                                           slowat, len, bits,
+                                                           act);
+}
+
+/*
+ * Test filling up the TCP receive queue.  In particular, verify that one bug I
+ * ran into (lwIP bug #49128) is resolved.
+ */
+static void
+test91p(void)
+{
+       char buf[CHUNK];
+       size_t total, left;
+       ssize_t res;
+       int fd[2];
+
+       subtest = 16;
+
+       if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0);
+
+       /*
+        * Fill up the sockets' queues.
+        */
+       total = get_buf_sizes(SOCK_STREAM, NULL, NULL);
+
+       fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/);
+
+       /*
+        * Wait long enough for the zero window probing to kick in, which used
+        * to cause an ACK storm livelock (lwIP bug #49128).
+        */
+       sleep(1);
+
+       /*
+        * Actually sleep a bit longer, so that the polling timer kicks in and
+        * at least attempts to send more.  This is merely an attempt to
+        * exercise some of the polling code, and should not have any actual
+        * effect on the rest of the test.
+        */
+       sleep(5);
+
+       /*
+        * Make sure all the data still arrives.
+        */
+       for (left = total; left > 0; left -= res) {
+               res = recv(fd[1], buf, sizeof(buf), 0);
+               if (res <= 0) e(0);
+               if (res > left) e(0);
+       }
+
+       if (recv(fd[1], buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       /*
+        * Attempt to shut down the socket for writing after filling up the
+        * send queue.  The TCP FIN should then arrive after all the data.
+        */
+       for (left = total; left > 0; left -= res) {
+               res = send(fd[0], buf, MIN(left, sizeof(buf)), 0);
+               if (res <= 0) e(0);
+               if (res > left) e(0);
+       }
+
+       if (shutdown(fd[0], SHUT_WR) != 0) e(0);
+
+       for (left = total; left > 0; left -= res) {
+               res = recv(fd[1], buf, sizeof(buf), 0);
+               if (res <= 0) e(0);
+               if (res > left) e(0);
+       }
+
+       if (recv(fd[1], buf, sizeof(buf), 0) != 0) e(0);
+
+       if (send(fd[1], "A", 1, 0) != 1) e(0);
+
+       if (recv(fd[0], buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'A') e(0);
+
+       if (close(fd[1]) != 0) e(0);
+       if (close(fd[0]) != 0) e(0);
+}
+
+/*
+ * Attempt to fill up a TCP send queue with small amounts of data.  While it
+ * may or may not be possible to fill up the entire send queue with small
+ * requests, but at least trying should not cause any problems, like the one I
+ * filed as lwIP bug #49218.
+ */
+static void
+test91q(void)
+{
+       ssize_t res;
+       size_t count;
+       char c, c2;
+       int fd[2];
+
+       subtest = 17;
+
+       if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0);
+
+       count = 0;
+       for (c = 0; (res = send(fd[0], &c, sizeof(c), MSG_DONTWAIT)) > 0; c++)
+               count += res;
+       if (res != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+       if (count < CHUNK) e(0);
+
+       if (shutdown(fd[0], SHUT_WR) != 0) e(0);
+
+       for (c2 = 0; count > 0; count--, c2++) {
+               if (recv(fd[1], &c, sizeof(c), 0) != 1) e(0);
+               if (c != c2) e(0);
+       }
+
+       if (recv(fd[1], &c, sizeof(c), 0) != 0) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+       if (close(fd[1]) != 0) e(0);
+}
+
+/*
+ * Test that SO_RCVLOWAT is limited to the size of the receive buffer.
+ */
+static void
+sub91r_recv(int fill_delta, int rlowat_delta, int exp_delta)
+{
+       char *buf;
+       size_t buflen;
+       int fd[2], rlowat, rcvlen, res;
+
+       if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0);
+
+       /*
+        * Fill up the socket's receive queue, possibly minus one byte.
+        */
+       (void)get_buf_sizes(SOCK_STREAM, NULL, &rcvlen);
+
+       buflen = MAX(CHUNK, rcvlen + 1);
+       if ((buf = malloc(buflen)) == NULL) e(0);
+
+       fill_tcp_bufs(fd[1], fd[0], 0 /*fill_send*/, fill_delta);
+
+       rlowat = rcvlen + rlowat_delta;
+       if (setsockopt(fd[0], SOL_SOCKET, SO_RCVLOWAT, &rlowat,
+           sizeof(rlowat)) != 0) e(0);
+
+       if (ioctl(fd[0], FIONREAD, &res) != 0) e(0);
+       if (res != rcvlen + fill_delta) e(0);
+
+       res = recv(fd[0], buf, rcvlen + 1, MSG_DONTWAIT);
+       if (exp_delta < 0) {
+               if (res != -1) e(0);
+               if (errno != EWOULDBLOCK) e(0);
+       } else
+               if (res != rcvlen - exp_delta) e(0);
+
+       free(buf);
+
+       if (close(fd[0]) != 0) e(0);
+       if (close(fd[1]) != 0) e(0);
+}
+
+/*
+ * Test that SO_SNDLOWAT is limited to the size of the send buffer.
+ */
+static void
+sub91r_send(int fill, int slowat_delta, int exp_delta)
+{
+       char *buf;
+       size_t buflen;
+       int fd[2], sndlen, slowat, res;
+
+       if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0);
+
+       /*
+        * Fill up the socket's receive queue, and possibly put one extra byte
+        * in the other socket's send queue.
+        */
+       (void)get_buf_sizes(SOCK_STREAM, &sndlen, NULL);
+
+       buflen = MAX(CHUNK, sndlen + 1);
+       if ((buf = malloc(buflen)) == NULL) e(0);
+
+       memset(buf, 0, buflen);
+
+       fill_tcp_bufs(fd[0], fd[1], 0 /*fill_send*/, 0 /*delta*/);
+
+       slowat = sndlen + slowat_delta;
+
+       if (fill > 0) {
+               memset(buf, 0, fill);
+
+               if (send(fd[0], buf, fill, 0) != fill) e(0);
+       }
+
+       if (setsockopt(fd[0], SOL_SOCKET, SO_SNDLOWAT, &slowat,
+           sizeof(slowat)) != 0) e(0);
+
+       res = send(fd[0], buf, sndlen + 1, MSG_DONTWAIT);
+       if (exp_delta < 0) {
+               if (res != -1) e(0);
+               if (errno != EWOULDBLOCK) e(0);
+       } else
+               if (res != sndlen - exp_delta) e(0);
+
+       free(buf);
+
+       if (close(fd[0]) != 0) e(0);
+       if (close(fd[1]) != 0) e(0);
+}
+
+/*
+ * Test that on stream sockets, SO_RCVLOWAT and SO_SNDLOWAT are limited to
+ * their respective buffer sizes.  This test is derived from test90w, but
+ * merging the two into socklib would get too messy unfortunately.
+ */
+static void
+test91r(void)
+{
+
+       subtest = 18;
+
+       /*
+        * With the receive buffer filled except for one byte, all data should
+        * be retrieved unless the threshold is not met.
+        */
+       sub91r_recv(-1, -1, 1);
+       sub91r_recv(-1, 0, -1);
+       sub91r_recv(-1, 1, -1);
+
+       /*
+        * With the receive buffer filled completely, all data should be
+        * retrieved in all cases.
+        */
+       sub91r_recv(0, -1, 0);
+       sub91r_recv(0, 0, 0);
+       sub91r_recv(0, 1, 0);
+
+       /*
+        * With a send buffer that contains one byte, all data should be sent
+        * unless the threshold is not met.
+        */
+       sub91r_send(1, -1, 1);
+       sub91r_send(1, 0, -1);
+       sub91r_send(1, 1, -1);
+
+       /*
+        * With the send buffer filled completely, all data should be sent
+        * in all cases.
+        */
+       sub91r_send(0, -1, 0);
+       sub91r_send(0, 0, 0);
+       sub91r_send(0, 1, 0);
+}
+
+/*
+ * Test sending and receiving with bad pointers on a TCP socket.
+ */
+static void
+sub91s_tcp(char * ptr)
+{
+       int fd[2];
+
+       memset(ptr, 'X', PAGE_SIZE);
+
+       if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0);
+
+       if (send(fd[0], "A", 1, 0) != 1) e(0);
+
+       if (send(fd[0], ptr, PAGE_SIZE * 2, MSG_DONTWAIT) != -1) e(0);
+       if (errno != EFAULT) e(0);
+
+       if (send(fd[0], "B", 1, 0) != 1) e(0);
+
+       if (shutdown(fd[0], SHUT_WR) != 0) e(0);
+
+       if (recv(fd[1], &ptr[PAGE_SIZE - 1], PAGE_SIZE, MSG_WAITALL) != -1)
+               e(0);
+       if (errno != EFAULT) e(0);
+
+       if (recv(fd[1], ptr, 3, MSG_DONTWAIT) != 2) e(0);
+       if (ptr[0] != 'A') e(0);
+       if (ptr[1] != 'B') e(0);
+
+       if (close(fd[0]) != 0) e(0);
+       if (close(fd[1]) != 0) e(0);
+}
+
+/*
+ * Test sending and receiving with bad pointers on a UDP socket.
+ */
+static void
+sub91s_udp(char * ptr)
+{
+       struct sockaddr_in6 sin6;
+       int i, fd;
+
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       memset(ptr, 'A', PAGE_SIZE);
+
+       if (sendto(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0,
+           (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0);
+       if (errno != EFAULT) e(0);
+
+       memset(ptr, 'B', PAGE_SIZE);
+
+       if (sendto(fd, ptr, PAGE_SIZE, 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != PAGE_SIZE) e(0);
+
+       memset(ptr, 0, PAGE_SIZE);
+
+       if (recvfrom(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0, NULL, 0) != -1)
+               e(0);
+       if (errno != EFAULT) e(0);
+
+       if (recvfrom(fd, ptr, PAGE_SIZE * 2, 0, NULL, 0) != PAGE_SIZE) e(0);
+       for (i = 0; i < PAGE_SIZE; i++)
+               if (ptr[i] != 'B') e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test sending and receiving with bad pointers.
+ */
+static void
+test91s(void)
+{
+       char *ptr;
+
+       subtest = 19;
+
+       if ((ptr = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE,
+           MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED) e(0);
+
+       if (munmap(&ptr[PAGE_SIZE], PAGE_SIZE) != 0) e(0);
+
+       sub91s_tcp(ptr);
+       sub91s_udp(ptr);
+
+       if (munmap(ptr, PAGE_SIZE) != 0) e(0);
+}
+
+/*
+ * Test closing TCP sockets and SO_LINGER.
+ */
+static void
+test91t(void)
+{
+       char buf[CHUNK];
+       size_t total, left;
+       ssize_t res;
+       int i, fd[2];
+
+       subtest = 20;
+
+       total = get_buf_sizes(SOCK_STREAM, NULL, NULL);
+
+       memset(buf, 0, sizeof(buf));
+
+       /*
+        * Test two cases of handling connection closure:
+        *
+        * 1) the FIN+ACK case, where the closing side finishes the close
+        *    operation once its FIN has been acknowledged;
+        * 2) the FIN+FIN case, where the closing side finishes the close
+        *    operation once it has sent its own FIN (possibly without getting
+        *    an ACK yet) and also receives a FIN from the other side.
+        *
+        * Since lwIP prevents us from detecting #1 without polling, which
+        * happens twice a second, we can test #2 by shutting down the peer
+        * connection immediately after (i=0/2) or even before (i=4/5) closing
+        * this side.
+        */
+       for (i = 0; i <= 5; i++) {
+               if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0);
+
+               fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/);
+
+               if (close(fd[0]) != 0) e(0);
+
+               if (i >= 4 && shutdown(fd[1], SHUT_WR) != 0) e(0);
+
+               for (left = total; left > 0; left -= res) {
+                       res = recv(fd[1], buf, sizeof(buf), 0);
+                       if (res <= 0) e(0);
+                       if (res > left) e(0);
+               }
+
+               if (recv(fd[1], buf, sizeof(buf), 0) != 0) e(0);
+
+               sleep(i & 1);
+
+               /*
+                * We can still send to the receiving end, but this will cause
+                * a reset.  We do this only if we have not just shut down the
+                * writing end of this socket.  Also test regular closing.
+                */
+               if (i / 2 == 1) {
+                       if (send(fd[1], "B", 1, 0) != 1) e(0);
+
+                       if (recv(fd[1], buf, sizeof(buf), 0) != -1) e(0);
+                       if (errno != ECONNRESET) e(0);
+               }
+
+               if (close(fd[1]) != 0) e(0);
+       }
+
+       /*
+        * Test that closing a socket with data still in its receive queue
+        * causes a RST to be issued.
+        */
+       if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0);
+
+       if (send(fd[0], "C", 1, 0) != 1) e(0);
+
+       if (recv(fd[1], buf, sizeof(buf), MSG_PEEK) != 1) e(0);
+
+       if (close(fd[1]) != 0) e(0);
+
+       if (recv(fd[0], buf, sizeof(buf), 0) != -1) e(0);
+       if (errno != ECONNRESET) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+}
+
+/*
+ * Test closing a socket with a particular SO_LINGER setting.
+ */
+static void
+sub91u(int nb, int mode, int intr, int onoff, int linger)
+{
+       char buf[CHUNK];
+       struct timeval tv1, tv2;
+       struct linger l;
+       pid_t pid;
+       int fd[2], pfd[2], fl, val, res, status;
+
+       get_tcp_pair((mode & 1) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, fd);
+
+       /*
+        * Set up the socket pair.
+        */
+       fill_tcp_bufs(fd[0], fd[1], 0 /*fill_send*/, 1 /*delta*/);
+
+       if (mode == 3 && shutdown(fd[1], SHUT_WR) != 0) e(0);
+
+       l.l_onoff = onoff;
+       l.l_linger = (linger) ? (2 + intr) : 0;
+       if (setsockopt(fd[0], SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0);
+
+       if (nb) {
+               if ((fl = fcntl(fd[0], F_GETFL)) == -1) e(0);
+               if (fcntl(fd[0], F_SETFL, fl | O_NONBLOCK) != 0) e(0);
+       }
+
+       /* We need two-way parent-child communication for this test. */
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0) e(0);
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               if (close(pfd[1]) != 0) e(0);
+
+               if (close(fd[1]) != 0) e(0);
+
+               signal(SIGUSR1, test91_got_signal);
+
+               /*
+                * Do not start closing the file descriptor until after the
+                * parent has closed its copy.
+                */
+               if (read(pfd[0], &val, sizeof(val)) != sizeof(val)) e(0);
+               if (val != 0) e(0);
+
+               if (gettimeofday(&tv1, NULL) != 0) e(0);
+
+               /* Perform the possibly blocking close(2) call. */
+               if (intr) {
+                       if (close(fd[0]) != -1) e(0);
+                       if (errno != EINPROGRESS) e(0);
+               } else
+                       if (close(fd[0]) != 0) e(0);
+
+               if (gettimeofday(&tv2, NULL) != 0) e(0);
+
+               timersub(&tv2, &tv1, &tv1);
+
+               /* Polling may take 500ms. */
+               val = tv1.tv_sec + ((tv1.tv_usec > 750000) ? 1 : 0);
+
+               if (val < 0 || val > 2) e(0);
+
+               /* Tell the parent how long the close(2) took, in seconds. */
+               if (write(pfd[0], &val, sizeof(val)) != sizeof(val)) e(0);
+
+               exit(errct);
+       case -1:
+               e(0);
+       }
+
+       /* Close file descriptors here and then let the child run. */
+       if (close(pfd[0]) != 0) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+
+       val = 0;
+       if (write(pfd[1], &val, sizeof(val)) != sizeof(val)) e(0);
+
+       /*
+        * Wait one second until we try to close the connection ourselves, if
+        * applicable.  If we are killing the child, we add yet another second
+        * to tell the difference between a clean close and a timeout/reset.
+        */
+       sleep(1);
+
+       if (intr) {
+               if (kill(pid, SIGUSR1) != 0) e(0);
+
+               sleep(1);
+       }
+
+       /*
+        * Trigger various ways in which the connection is closed, or not, in
+        * which case the linger timeout should cause a reset.
+        */
+       switch (mode) {
+       case 0: /* do nothing; expect reset */
+               break;
+
+       case 1: /* FIN + rFIN */
+               if (shutdown(fd[1], SHUT_WR) != 0) e(0);
+
+               /*
+                * The FIN cannot yet be sent due to the zero-sized receive
+                * window.  Make some room so that it can be sent.
+                */
+               /* FALLTHROUGH */
+       case 2: /* FIN + ACK */
+       case 3: /* rFIN + FIN */
+               if (recv(fd[1], buf, sizeof(buf), 0) <= 0) e(0);
+               break;
+
+       case 4:         /* RST */
+               l.l_onoff = 1;
+               l.l_linger = 0;
+               if (setsockopt(fd[1], SOL_SOCKET, SO_LINGER, &l,
+                   sizeof(l)) != 0) e(0);
+
+               if (close(fd[1]) != 0) e(0);
+               fd[1] = -1;
+               break;
+
+       default:
+               e(0);
+       }
+
+       /*
+        * Make absolutely sure that the linger timer has triggered and we do
+        * not end up exploiting race conditions in the tests below.  As a
+        * result this subtest takes over a minute but at least it has already
+        * triggered a whole bunch of bugs (and produced lwIP patch #9125).
+        */
+       sleep(2);
+
+       /* Get the number of seconds spent in the close(2) call. */
+       if (read(pfd[1], &val, sizeof(val)) != sizeof(val)) e(0);
+
+       /*
+        * See if the close(2) call took as long as expected and check that the
+        * other side of the connection sees either EOF or a reset as expected.
+        */
+       if (mode == 0) {
+               if (nb) {
+                       if (val != 0) e(0);
+
+                       sleep(2);
+               } else if (!intr) {
+                       if (val != linger * 2) e(0);
+               } else
+                       if (val != 1) e(0);
+
+               /* See if the connection was indeed reset. */
+               while ((res = recv(fd[1], buf, sizeof(buf), 0)) > 0)
+                       ;
+               if (res != -1) e(0);
+               if (errno != ECONNRESET) e(0);
+       } else {
+               if (val != ((onoff && !nb) || intr)) e(0);
+
+               /* Check for EOF unless we already closed the socket. */
+               if (fd[1] != -1) {
+                       while ((res = recv(fd[1], buf, sizeof(buf), 0)) > 0)
+                               ;
+                       if (res != 0) e(0);
+               }
+       }
+
+       /* Clean up. */
+       if (fd[1] != -1 && close(fd[1]) != 0) e(0);
+
+       if (close(pfd[1]) != 0) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+}
+
+/*
+ * Test SO_LINGER support in various configurations.  It is worth noting that I
+ * implemented a somewhat broken version of SO_LINGER because lwIP does not
+ * allow for proper detection of our FIN being acknowledged in all cases (this
+ * is documented in the service).  As a result, a close(2) call may return
+ * earlier than it is supposed to, namely as soon as 1) we sent a FIN, and
+ * 2) we received a FIN from the other side.  We also test the somewhat broken
+ * behavior here, as above all else the aim is to make sure that the service
+ * code works as expected.
+ */
+static void
+test91u(void)
+{
+       int nb, mode;
+
+       subtest = 21;
+
+       /*
+        *
+        * In all of the following scenarios, close(2) should only ever return
+        * success, so that the caller knows that the file descriptor has been
+        * closed.
+        */
+       for (nb = 0; nb <= 1; nb++) {
+               /*
+                * SO_LINGER off: the close(2) call should return immediately,
+                * and the connection should be closed in the background.
+                */
+               for (mode = 1; mode <= 4; mode++)
+                       sub91u(nb, mode, 0, 0, 0);
+
+               /*
+                * SO_LINGER on with a zero timeout: the close(2) call should
+                * return immediately, and the connection should be reset.
+                */
+               sub91u(nb, 0, 0, 1, 0);
+
+               /*
+                * SO_LINGER on with a non-zero timeout: the close(2) call
+                * should return immediately for non-blocking sockets only, and
+                * otherwise as soon as either the connection is closed or the
+                * timeout triggers, in which case the connection is reset.
+                */
+               for (mode = 0; mode <= 4; mode++)
+                       sub91u(nb, mode, 0, 1, 1);
+       }
+
+       /*
+        * Test signal-interrupting blocked close(2) calls with SO_LINGER.  In
+        * such cases, the close(2) should return EINPROGRESS to indicate that
+        * the file descriptor has been closed, and the original close action
+        * (with the original timeout) should proceed in the background.
+        */
+       for (mode = 0; mode <= 4; mode++)
+               sub91u(0, mode, 1, 1, 1);
+}
+
+/*
+ * Test shutdown on listening TCP sockets.
+ */
+static void
+sub91v(int how)
+{
+       struct sockaddr_in sin;
+       socklen_t len;
+       char c;
+       int fd, fd2, fd3, fl;
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       len = sizeof(sin);
+       if (getsockname(fd, (struct sockaddr *)&sin, &len) != 0) e(0);
+       if (len != sizeof(sin)) e(0);
+
+       if (listen(fd, 1) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       if (shutdown(fd, how) != 0) e(0);
+
+       len = sizeof(sin);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin, &len)) < 0) e(0);
+       if (len != sizeof(sin)) e(0);
+
+       if (write(fd2, "A", 1) != 1) e(0);
+       if (read(fd3, &c, 1) != 1) e(0);
+       if (c != 'A') e(0);
+
+       if (write(fd3, "B", 1) != 1) e(0);
+       if (read(fd2, &c, 1) != 1) e(0);
+       if (c != 'B') e(0);
+
+       len = sizeof(sin);
+       if (accept(fd, (struct sockaddr *)&sin, &len) != -1) e(0);
+       if (errno != ECONNABORTED) e(0);
+
+       if ((fl = fcntl(fd, F_GETFL)) == -1) e(0);
+       if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0);
+
+       len = sizeof(sin);
+       if (accept(fd, (struct sockaddr *)&sin, &len) != -1) e(0);
+       if (errno != ECONNABORTED) e(0);
+
+       if (close(fd3) != 0) e(0);
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test shutdown on listening TCP sockets.  This test is derived from test90x.
+ */
+static void
+test91v(void)
+{
+       const int hows[] = { SHUT_RD, SHUT_WR, SHUT_RDWR };
+       int i;
+
+       subtest = 22;
+
+       for (i = 0; i < __arraycount(hows); i++)
+               sub91v(hows[i]);
+}
+
+/*
+ * Test basic sysctl(2) socket enumeration support.
+ */
+static void
+test91w(void)
+{
+       struct kinfo_pcb ki;
+       struct sockaddr_in lsin, rsin;
+       struct sockaddr_in6 lsin6, rsin6;
+       char buf[CHUNK];
+       uint16_t local_port, remote_port;
+       socklen_t len;
+       int fd[2], val, sndbuf, rcvbuf;
+
+       subtest = 23;
+
+       /*
+        * First test TCP.
+        */
+       get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd);
+
+       val = 0;
+       if (setsockopt(fd[1], IPPROTO_TCP, TCP_NODELAY, &val,
+           sizeof(val)) != 0) e(0);
+
+       len = sizeof(lsin);
+       if (getsockname(fd[0], (struct sockaddr *)&lsin, &len) != 0) e(0);
+       if (len != sizeof(lsin)) e(0);
+       local_port = ntohs(lsin.sin_port);
+
+       if (getpeername(fd[0], (struct sockaddr *)&rsin, &len) != 0) e(0);
+       if (len != sizeof(rsin)) e(0);
+       remote_port = ntohs(rsin.sin_port);
+
+       if (send(fd[0], "ABCDE", 5, 0) != 5) e(0);
+
+       /* Allow the data to reach the other side and be acknowledged. */
+       sleep(1);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_ESTABLISHED) e(0);
+       if (!(ki.ki_tflags & TF_NODELAY)) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, remote_port,
+           local_port, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_ESTABLISHED) e(0);
+       if (ki.ki_tflags & TF_NODELAY) e(0);
+       if (memcmp(&ki.ki_src, &rsin, sizeof(rsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &lsin, sizeof(lsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 5) e(0);
+
+       if (recv(fd[1], buf, sizeof(buf), 0) != 5) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+       if (close(fd[1]) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_TIME_WAIT) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       /* Test IPv6 sockets as well. */
+       get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd);
+
+       len = sizeof(lsin6);
+       if (getsockname(fd[0], (struct sockaddr *)&lsin6, &len) != 0) e(0);
+       if (len != sizeof(lsin6)) e(0);
+       local_port = ntohs(lsin6.sin6_port);
+
+       if (getpeername(fd[0], (struct sockaddr *)&rsin6, &len) != 0) e(0);
+       if (len != sizeof(rsin6)) e(0);
+       remote_port = ntohs(rsin6.sin6_port);
+
+       memset(buf, 0, sizeof(buf));
+
+       /* We fill up the queues so we do not need to sleep in this case. */
+       (void)get_buf_sizes(SOCK_STREAM, &sndbuf, &rcvbuf);
+
+       fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/);
+
+       if (send(fd[0], buf, 1, MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_ESTABLISHED) e(0);
+       if (!(ki.ki_tflags & TF_NODELAY)) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (ki.ki_sndq != (size_t)sndbuf) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP,
+           remote_port, local_port, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_ESTABLISHED) e(0);
+       if (!(ki.ki_tflags & TF_NODELAY)) e(0);
+       if (memcmp(&ki.ki_src, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != (size_t)rcvbuf) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+       if (close(fd[1]) != 0) e(0);
+
+       /* Bound and listening sockets should show up as well. */
+       if ((fd[0] = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, 0, 0,
+           &ki) != 0) e(0);
+
+       memset(&lsin, 0, sizeof(lsin));
+       lsin.sin_len = sizeof(lsin);
+       lsin.sin_family = AF_INET;
+       lsin.sin_port = htons(TEST_PORT_A);
+       lsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       if (bind(fd[0], (struct sockaddr *)&lsin, sizeof(lsin)) != 0) e(0);
+
+       memset(&rsin, 0, sizeof(rsin));
+       rsin.sin_len = sizeof(rsin);
+       rsin.sin_family = AF_INET;
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, TEST_PORT_A,
+           0, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_CLOSED) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       if (listen(fd[0], 1)) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, TEST_PORT_A,
+           0, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_LISTEN) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+
+       /* Test IPv6 sockets as well. */
+       if ((fd[0] = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, 0, 0,
+           &ki) != 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd[0], IPPROTO_IPV6, IPV6_V6ONLY, &val,
+           sizeof(val)) != 0) e(0);
+
+       memset(&lsin6, 0, sizeof(lsin6));
+       lsin6.sin6_len = sizeof(lsin6);
+       lsin6.sin6_family = AF_INET6;
+       lsin6.sin6_port = htons(TEST_PORT_A);
+       memcpy(&lsin6.sin6_addr, &in6addr_loopback, sizeof(lsin6.sin6_addr));
+       if (bind(fd[0], (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0);
+
+       memset(&rsin6, 0, sizeof(rsin6));
+       rsin6.sin6_len = sizeof(rsin6);
+       rsin6.sin6_family = AF_INET6;
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP,
+           TEST_PORT_A, 0, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_CLOSED) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0);
+
+       if (listen(fd[0], 1)) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP,
+           TEST_PORT_A, 0, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_LISTEN) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+
+       /*
+        * I do not dare binding to ANY so we cannot test IPV6_V6ONLY properly
+        * here.  Instead we repeat the test and ensure the IN6P_IPV6_V6ONLY
+        * flag accurately represents the current state.
+        */
+       if ((fd[0] = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, 0, 0,
+           &ki) != 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd[0], IPPROTO_IPV6, IPV6_V6ONLY, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (bind(fd[0], (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP,
+           TEST_PORT_A, 0, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_CLOSED) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, TEST_PORT_A,
+           0, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_TCP, TEST_PORT_A,
+           0, &ki) != 0) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+
+       /*
+        * Then test UDP.
+        */
+       if ((fd[0] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, 0, 0,
+           &ki) != 0) e(0);
+
+       memset(&lsin, 0, sizeof(lsin));
+       lsin.sin_len = sizeof(lsin);
+       lsin.sin_family = AF_INET;
+       lsin.sin_port = htons(TEST_PORT_A);
+       lsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       memset(&rsin, 0, sizeof(rsin));
+       rsin.sin_len = sizeof(rsin);
+       rsin.sin_family = AF_INET;
+
+       if (bind(fd[0], (struct sockaddr *)&lsin, sizeof(lsin)) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_A,
+           0, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_DGRAM) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       rsin.sin_port = htons(TEST_PORT_B);
+       rsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       if (connect(fd[0], (struct sockaddr *)&rsin, sizeof(rsin)) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_A,
+           TEST_PORT_B, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_DGRAM) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_B,
+           TEST_PORT_A, &ki) != 0) e(0);
+
+       if ((fd[1] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       if (bind(fd[1], (struct sockaddr *)&rsin, sizeof(rsin)) != 0) e(0);
+
+       if (sendto(fd[1], "ABC", 3, 0, (struct sockaddr *)&lsin,
+           sizeof(lsin)) != 3) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_A,
+           TEST_PORT_B, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_DGRAM) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq < 3) e(0);       /* size is rounded up */
+
+       if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP,
+           TEST_PORT_A, TEST_PORT_B, &ki) != 0) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+       if (close(fd[1]) != 0) e(0);
+
+       /* Test IPv6 sockets as well. */
+       if ((fd[0] = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, 0, 0,
+           &ki) != 0) e(0);
+
+       memset(&lsin6, 0, sizeof(lsin6));
+       lsin6.sin6_len = sizeof(lsin6);
+       lsin6.sin6_family = AF_INET6;
+       lsin6.sin6_port = htons(TEST_PORT_A);
+       memcpy(&lsin6.sin6_addr, &in6addr_loopback, sizeof(lsin6.sin6_addr));
+       if (bind(fd[0], (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0);
+
+       memset(&rsin6, 0, sizeof(rsin6));
+       rsin6.sin6_len = sizeof(rsin6);
+       rsin6.sin6_family = AF_INET6;
+
+       if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP,
+           TEST_PORT_A, 0, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_DGRAM) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+       if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0);
+
+       rsin6.sin6_port = htons(TEST_PORT_B);
+       memcpy(&rsin6.sin6_addr, &in6addr_loopback, sizeof(rsin6.sin6_addr));
+       if (connect(fd[0], (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0)
+               e(0);
+
+       if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP,
+           TEST_PORT_A, TEST_PORT_B, &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_DGRAM) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+       if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0);
+
+       if (close(fd[0]) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP,
+           TEST_PORT_A, TEST_PORT_B, &ki) != 0) e(0);
+}
+
+/*
+ * Test socket enumeration of sockets using IPv4-mapped IPv6 addresses.
+ */
+static void
+test91x(void)
+{
+       struct sockaddr_in6 sin6;
+       struct sockaddr_in sin;
+       socklen_t len;
+       struct kinfo_pcb ki;
+       unsigned short local_port, remote_port;
+       int fd, fd2, fd3, val;
+
+       subtest = 24;
+
+       /*
+        * Test that information from an IPv6 socket bound to an IPv4-mapped
+        * IPv6 address is as expected.  For socket enumeration, due to lwIP
+        * limitations we return an IPv4 address instead of an IPv4-mapped IPv6
+        * address, and that is what this test checks for various sockets.
+        */
+       if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1)
+               e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       local_port = ntohs(sin6.sin6_port);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port,
+           0, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port,
+           0, &ki) != 1) e(0);
+
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_CLOSED) e(0);
+
+       memcpy(&sin, &ki.ki_src, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(local_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       memcpy(&sin, &ki.ki_dst, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(0)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0);
+
+       if (listen(fd, 1) != 0) e(0);
+
+       /*
+        * Test that information from an accepted (IPv6) socket is correct
+        * for a connection from an IPv4 address.
+        */
+       if ((fd2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(local_port);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (connect(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       len = sizeof(sin);
+       if (getsockname(fd2, (struct sockaddr *)&sin, &len) != 0) e(0);
+       if (len != sizeof(sin)) e(0);
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       remote_port = ntohs(sin.sin_port);
+
+       len = sizeof(sin6);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6, &len)) < 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(remote_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(local_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       len = sizeof(sin6);
+       if (getpeername(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(remote_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 1) e(0);
+
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_ESTABLISHED) e(0);
+
+       memcpy(&sin, &ki.ki_src, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(local_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       memcpy(&sin, &ki.ki_dst, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(remote_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       if (close(fd3) != 0) e(0);
+       if (close(fd2) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 1) e(0);
+
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_TIME_WAIT) e(0);
+
+       memcpy(&sin, &ki.ki_src, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(local_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       memcpy(&sin, &ki.ki_dst, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(remote_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       /*
+        * Test that information from a connected (IPv6) socket is correct
+        * after connecting it to an IPv4 address.
+        */
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(local_port);
+       if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1)
+               e(0);
+
+       if (connect(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd2, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+       remote_port = ntohs(sin6.sin6_port);
+
+       len = sizeof(sin6);
+       if (getpeername(fd2, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(local_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP,
+           remote_port, local_port, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, remote_port,
+           local_port, &ki) != 1) e(0);
+
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_ESTABLISHED) e(0);
+
+       memcpy(&sin, &ki.ki_src, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(remote_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       memcpy(&sin, &ki.ki_dst, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(local_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       len = sizeof(sin6);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6, &len)) < 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(remote_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd3) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Do one more test on an accepted socket, now without binding the
+        * listening socket to an IPv4-mapped IPv6 address.
+        */
+       if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr));
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       local_port = ntohs(sin6.sin6_port);
+
+       if (listen(fd, 1) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(local_port);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       if (connect(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       len = sizeof(sin);
+       if (getsockname(fd2, (struct sockaddr *)&sin, &len) != 0) e(0);
+       if (len != sizeof(sin)) e(0);
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       remote_port = ntohs(sin.sin_port);
+
+       len = sizeof(sin6);
+       if ((fd3 = accept(fd, (struct sockaddr *)&sin6, &len)) < 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(remote_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(local_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       len = sizeof(sin6);
+       if (getpeername(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port != htons(remote_port)) e(0);
+       if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0);
+       if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK))
+               e(0);
+
+       if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port,
+           remote_port, &ki) != 1) e(0);
+
+       if (ki.ki_type != SOCK_STREAM) e(0);
+       if (ki.ki_tstate != TCPS_ESTABLISHED) e(0);
+
+       memcpy(&sin, &ki.ki_src, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(local_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       memcpy(&sin, &ki.ki_dst, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(remote_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       if (close(fd3) != 0) e(0);
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /*
+        * Do some very simple UDP socket enumeration tests.  The rest is
+        * already tested elsewhere.
+        */
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1)
+               e(0);
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       local_port = ntohs(sin6.sin6_port);
+
+       if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, local_port,
+           0, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, local_port,
+           0, &ki) != 1) e(0);
+
+       if (ki.ki_type != SOCK_DGRAM) e(0);
+       if (ki.ki_tstate != 0) e(0);
+
+       memcpy(&sin, &ki.ki_src, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(local_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       memcpy(&sin, &ki.ki_dst, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(0)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0)
+               e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_A);
+       if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1)
+               e(0);
+
+       if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       local_port = ntohs(sin6.sin6_port);
+
+       if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, local_port,
+           TEST_PORT_A, &ki) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, local_port,
+           TEST_PORT_A, &ki) != 1) e(0);
+
+       if (ki.ki_type != SOCK_DGRAM) e(0);
+       if (ki.ki_tstate != 0) e(0);
+
+       memcpy(&sin, &ki.ki_src, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(local_port)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       memcpy(&sin, &ki.ki_dst, sizeof(sin));
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port != htons(TEST_PORT_A)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test local and remote IPv6 address handling.  In particular, test scope IDs
+ * and IPv4-mapped IPv6 addresses.
+ */
+static void
+test91y(void)
+{
+
+       subtest = 25;
+
+       socklib_test_addrs(SOCK_STREAM, 0);
+
+       socklib_test_addrs(SOCK_DGRAM, 0);
+}
+
+/*
+ * Test low-memory conditions for TCP.
+ */
+static void
+test91z(void)
+{
+       struct sockaddr_in6 sin6;
+       socklen_t len;
+       unsigned char buf[CHUNK];
+       struct timeval tv;
+       unsigned int i, j, k;
+       ssize_t res, left;
+       pid_t pid, pid2;
+       static int fds[OPEN_MAX];
+       static size_t pos[OPEN_MAX];
+       int lfd, pfd[2], val, sndlen, rcvlen, status;
+
+       subtest = 26;
+
+       /*
+        * We use custom send and receive buffer sizes, such that we can
+        * trigger the case that we run out of send buffers without causing
+        * buffers used on the receiving side to empty the buffer pool first.
+        * While the latter case is not unrealistic for practical scenarios, it
+        * is not what we want to test here.  It would also cause practical
+        * problems for this test, as the result may be that the loopback
+        * interface (that we use here) starts dropping packets due to being
+        * unable to make copies.
+        *
+        * The aim with these two is that the ratio is such that we run into
+        * the 75% usage limit for the send side without using the other 25%
+        * for receiving purposes.  Since our TCP buffer merging guarantees at
+        * most a 50% overhead on the receiving side, the minimum ratio of 5:1
+        * translates to a worst-case ratio is 10:3 which is just above 75%.
+        * Thus, we should be able to use 80K:16K.  Instead, we use 128K:16K,
+        * because otherwise we will run out of sockets before we run out of
+        * buffers.  After all, we are not generating any traffic on the socket
+        * pairs in the other direction--something for which we do provision.
+        */
+       sndlen = 131072;
+       rcvlen = 16384;
+
+       /*
+        * Unfortunately, filling up receive queues is not easy, and for any
+        * size other than the window size (which is by nature also the minimum
+        * receive queue length that may be set) we would need to work around
+        * the same issue described in fill_tcp_bufs(), which would massively
+        * complicate the implementation of this subtest.  For now, make sure
+        * that inconsistent internal changes will trigger this assert.
+        */
+       assert(rcvlen == WINDOW_SIZE);
+
+       if ((lfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       if (bind(lfd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(lfd, (struct sockaddr *)&sin6, &len) != 0) e(0);
+
+       if (listen(lfd, 1) != 0) e(0);
+
+       /*
+        * Start a child process for the receiving ends.  We have to use
+        * another process because we aim to open a total concurrent number of
+        * TCP sockets that exceeds OPEN_MAX.
+        */
+       if (pipe(pfd) != 0) e(0);
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               if (close(lfd) != 0) e(0);
+               if (close(pfd[1]) != 0) e(0);
+
+               /* Create socket pairs. */
+               for (i = 0; ; i++) {
+                       if (i == __arraycount(fds)) e(0);
+
+                       if ((fds[i] = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
+                           e(0);
+
+                       if (connect(fds[i], (struct sockaddr *)&sin6,
+                           sizeof(sin6)) != 0) e(0);
+
+                       val = 1;
+                       if (setsockopt(fds[i], IPPROTO_TCP, TCP_NODELAY, &val,
+                           sizeof(val)) != 0) e(0);
+
+                       if (setsockopt(fds[i], SOL_SOCKET, SO_RCVBUF, &rcvlen,
+                           sizeof(rcvlen)) != 0) e(0);
+
+                       /* Synchronization point A. */
+                       if (read(pfd[0], &k, sizeof(k)) != sizeof(k)) e(0);
+                       if (k == 0)
+                               break;
+               }
+
+               /* Synchronization point B. */
+               if (read(pfd[0], &k, sizeof(k)) != sizeof(k)) e(0);
+               if (k != 2) e(0);
+
+               /* Receive some data from one socket. */
+               pos[0] = 0;
+               for (left = sizeof(buf) * 2; left > 0; left -= res) {
+                       res = recv(fds[0], buf, MIN(left, sizeof(buf)), 0);
+                       if (res <= 0) e(0);
+                       if (res > left) e(0);
+
+                       for (j = 0; j < res; j++)
+                               if (buf[j] != (unsigned char)(pos[0]++)) e(0);
+               }
+
+               /* Synchronization point C. */
+               if (read(pfd[0], &k, sizeof(k)) != sizeof(k)) e(0);
+               if (k != 3) e(0);
+
+               /*
+                * Receive all remaining data from all sockets.  Do this in two
+                * steps.  First enlarge the receive buffer and empty it, so
+                * that upon resumption, all remaining data is transferred from
+                * the sender to the receiver in one go.  Then actually wait
+                * for any remaining data, and the EOF.  If we do both in one
+                * step, this part of the test will take several minutes to
+                * complete.  Note that the last socket needs special treatment
+                * because its send queue may not have been filled entirely.
+                */
+               for (k = 0; k <= i; k++) {
+                       if (setsockopt(fds[i], SOL_SOCKET, SO_RCVBUF, &rcvlen,
+                           sizeof(rcvlen)) != 0) e(0);
+
+                       pos[k] = (k == 0) ? (sizeof(buf) * 2) : 0;
+
+                       for (left = sndlen + rcvlen - pos[k]; left > 0;
+                           left -= res) {
+                               res = recv(fds[k], buf, MIN(left, sizeof(buf)),
+                                   MSG_DONTWAIT);
+                               if (res == -1 && errno == EWOULDBLOCK)
+                                       break;
+                               if (res == 0 && k == i) {
+                                       pos[i] = sndlen + rcvlen;
+                                       break;
+                               }
+                               if (res <= 0) e(0);
+                               if (res > left) e(0);
+
+                               for (j = 0; j < res; j++)
+                                       if (buf[j] != (unsigned char)(k +
+                                           pos[k]++)) e(0);
+                       }
+               }
+
+               for (k = 0; k <= i; k++) {
+                       for (left = sndlen + rcvlen - pos[k]; left > 0;
+                          left -= res) {
+                               res = recv(fds[k], buf, MIN(left, sizeof(buf)),
+                                   0);
+                               if (res == 0 && k == i)
+                                       break;
+                               if (res <= 0) e(0);
+                               if (res > left) e(0);
+
+                               for (j = 0; j < res; j++)
+                                       if (buf[j] != (unsigned char)(k +
+                                           pos[k]++)) e(0);
+                       }
+
+                       if (recv(fds[k], buf, 1, 0) != 0) e(0);
+               }
+
+               /* Clean up. */
+               do {
+                       if (close(fds[i]) != 0) e(0);
+               } while (i-- > 0);
+
+               exit(errct);
+       case -1:
+               e(0);
+       }
+
+       if (close(pfd[0]) != 0) e(0);
+
+       for (i = 0; ; i++) {
+               if (i == __arraycount(fds)) e(0);
+
+               len = sizeof(sin6);
+               if ((fds[i] = accept(lfd, (struct sockaddr *)&sin6, &len)) < 0)
+                       e(0);
+
+               val = 1;
+               if (setsockopt(fds[i], IPPROTO_TCP, TCP_NODELAY, &val,
+                   sizeof(val)) != 0) e(0);
+
+               if (setsockopt(fds[i], SOL_SOCKET, SO_SNDBUF, &sndlen,
+                   sizeof(sndlen)) != 0) e(0);
+
+               /*
+                * Try to pump as much data into one end of the socket.  This
+                * may fail at any time due to being out of buffers, so we use
+                * a send timeout to break the resulting blocking call.
+                */
+               tv.tv_sec = 1;
+               tv.tv_usec = 0;
+
+               if (setsockopt(fds[i], SOL_SOCKET, SO_SNDTIMEO, &tv,
+                   sizeof(tv)) != 0) e(0);
+
+               /*
+                * Since buffer corruption is most likely to be detected when
+                * lots of buffers are actually in use, also make sure that we
+                * (eventually) receive what we send.
+                */
+               res = sizeof(buf);
+               pos[i] = 0;
+               for (left = sndlen + rcvlen; left > 0; left -= res) {
+                       /* One byte at a time, for simplicity.. */
+                       for (j = sizeof(buf) - res; j < sizeof(buf); j++)
+                               buf[j] = (unsigned char)(i + pos[i]++);
+
+                       res = send(fds[i], buf, MIN(left, sizeof(buf)), 0);
+                       if (res == -1 && errno == EWOULDBLOCK)
+                               break;
+
+                       if (res <= 0) e(0);
+                       if (res > left) e(0);
+
+                       if (res < sizeof(buf))
+                               memmove(buf, &buf[res], sizeof(buf) - res);
+               }
+
+               /* Synchronization point A. */
+               k = (left == 0);
+               if (write(pfd[1], &k, sizeof(k)) != sizeof(k)) e(0);
+
+               if (left > 0)
+                       break;
+       }
+
+       if (close(lfd) != 0) e(0);
+
+       /*
+        * We should always be able to fill at least two socket pairs' buffers
+        * completely this way; in fact with a 512x512 pool it should be three,
+        * but some sockets may be in use in the background.  With the default
+        * settings of the memory pool system, we should ideally be able to get
+        * up to 96 socket pairs.
+        */
+       if (i < 3) e(0);
+
+       /*
+        * Mix things up a bit by fully shutting down one file descriptor and
+        * closing another, both on the sending side.
+        */
+       if (shutdown(fds[1], SHUT_RDWR) != 0) e(0);
+       if (close(fds[2]) != 0) e(0);
+
+       /*
+        * Make sure that when there is buffer space available again, pending
+        * send() calls get woken up.  We do this using a child process that
+        * blocks on a send() call and a parent process that frees up some
+        * buffer space by receiving from another socket.
+        */
+       pid2 = fork();
+       switch (pid2) {
+       case 0:
+               errct = 0;
+
+               /* Disable the timeout again. */
+               tv.tv_sec = 0;
+               tv.tv_usec = 0;
+
+               if (setsockopt(fds[i], SOL_SOCKET, SO_SNDTIMEO, &tv,
+                   sizeof(tv)) != 0) e(0);
+
+               /*
+                * Try sending.  This should block until there are more buffers
+                * available.
+                */
+               res = send(fds[i], buf, MIN(left, sizeof(buf)), 0);
+               if (res <= 0) e(0);
+               if (res > left) e(0);
+
+               exit(errct);
+
+       case -1:
+               e(0);
+       }
+
+       /* Make sure the child's send() call is indeed hanging. */
+       sleep(2);
+
+       if (waitpid(pid2, &status, WNOHANG) != 0) e(0);
+
+       /* Then receive some data on another socket. */
+
+       /* Synchronization point B. */
+       k = 2;
+       if (write(pfd[1], &k, sizeof(k)) != sizeof(k)) e(0);
+
+       /* The send() call should now be woken up, eventually. */
+       if (waitpid(pid2, &status, 0) != pid2) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /*
+        * Shut down all (remaining) sending file descriptors for sending, so
+        * that we can receive until we get EOF.  For all but the last socket,
+        * we must get the full size of what we intended to send; for the first
+        * socket, we have already received two buffers worth of data.  Note
+        * that the receipt may take a while, mainly because it takes some time
+        * for sockets that were previously blocked to get going again.
+        */
+       for (k = 0; k <= i; k++) {
+               if (k != 1 && k != 2 && shutdown(fds[k], SHUT_WR) != 0)
+                       e(0);
+       }
+
+       /* Synchronization point C. */
+       k = 3;
+       if (write(pfd[1], &k, sizeof(k)) != sizeof(k)) e(0);
+
+       if (close(pfd[1]) != 0) e(0);
+
+       /* Wait for the child to receive everything and terminate. */
+       if (waitpid(pid, &status, 0) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /* Clean up. */
+       do {
+               if (i != 2 && close(fds[i]) != 0) e(0);
+       } while (i-- > 0);
+}
+
+/*
+ * Test multicast support.
+ */
+static void
+test91aa(void)
+{
+
+       subtest = 27;
+
+       socklib_test_multicast(SOCK_DGRAM, 0);
+}
+
+/*
+ * Test that putting an unbound TCP socket in listening mode will bind the
+ * socket to a port.
+ */
+static void
+test91ab(void)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       socklen_t len;
+       int fd;
+
+       subtest = 28;
+
+       if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (listen(fd, 1) != 0) e(0);
+
+       len = sizeof(sin);
+       if (getsockname(fd, (struct sockaddr *)&sin, &len) != 0) e(0);
+       if (len != sizeof(sin)) e(0);
+       if (sin.sin_len != sizeof(sin)) e(0);
+       if (sin.sin_family != AF_INET) e(0);
+       if (sin.sin_port == htons(0)) e(0);
+       if (sin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (listen(fd, 1) != 0) e(0);
+
+       len = sizeof(sin6);
+       if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0);
+       if (len != sizeof(sin6)) e(0);
+       if (sin6.sin6_len != sizeof(sin6)) e(0);
+       if (sin6.sin6_family != AF_INET6) e(0);
+       if (sin6.sin6_port == htons(0)) e(0);
+       if (memcmp(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr)) != 0)
+               e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test for connecting to the same remote TCP endpoint with the same local
+ * endpoint twice in a row.  The second connection should fail due to the
+ * TIME_WAIT state left behind from the first connection, but this previously
+ * caused an infinite loop instead.  lwIP bug #50498.
+ */
+static void
+test91ac(void)
+{
+       struct sockaddr_in6 lsin6, rsin6;
+       socklen_t len;
+       int fd, fd2, fd3;
+
+       subtest = 29;
+
+       if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       memset(&rsin6, 0, sizeof(rsin6));
+       rsin6.sin6_family = AF_INET6;
+       memcpy(&rsin6.sin6_addr, &in6addr_loopback, sizeof(rsin6.sin6_addr));
+
+       if (bind(fd, (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0) e(0);
+
+       len = sizeof(rsin6);
+       if (getsockname(fd, (struct sockaddr *)&rsin6, &len) != 0) e(0);
+       if (len != sizeof(rsin6)) e(0);
+
+       if (listen(fd, 1) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (connect(fd2, (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0) e(0);
+
+       if ((fd3 = accept(fd, (struct sockaddr *)&lsin6, &len)) < 0) e(0);
+       if (len != sizeof(rsin6)) e(0);
+
+       /* The server end must initiate the close for this to work. */
+       if (close(fd3) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0);
+
+       if (bind(fd2, (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0);
+
+       /*
+        * The timeout should occur almost immediately, due to a shortcut in
+        * lwIP (which was also the source of the problem here).  The actual
+        * error code is not really important though.  In fact, if in the
+        * future the connection does get established, that is still not an
+        * issue - in fact, it would be nice to have a working rsh(1), which is
+        * how this problem showed up in the first place - but at the very
+        * least the service should keep operating.
+        */
+       if (connect(fd2, (struct sockaddr *)&rsin6, sizeof(rsin6)) != -1) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test program for LWIP TCP/UDP sockets.
+ */
+int
+main(int argc, char ** argv)
+{
+       unsigned int m;
+       int i;
+
+       start(91);
+
+       if (argc == 2)
+               m = atoi(argv[1]);
+       else
+               m = 0xFFFFFFFF;
+
+       for (i = 0; i < ITERATIONS; i++) {
+               if (m & 0x00000001) test91a();
+               if (m & 0x00000002) test91b();
+               if (m & 0x00000004) test91c();
+               if (m & 0x00000008) test91d();
+               if (m & 0x00000010) test91e();
+               if (m & 0x00000020) test91f();
+               if (m & 0x00000040) test91g();
+               if (m & 0x00000080) test91h();
+               if (m & 0x00000100) test91i();
+               if (m & 0x00000200) test91j();
+               if (m & 0x00000400) test91k();
+               if (m & 0x00000800) test91l();
+               if (m & 0x00001000) test91m();
+               if (m & 0x00002000) test91n();
+               if (m & 0x00004000) test91o();
+               if (m & 0x00008000) test91p();
+               if (m & 0x00010000) test91q();
+               if (m & 0x00020000) test91r();
+               if (m & 0x00040000) test91s();
+               if (m & 0x00080000) test91t();
+               if (m & 0x00100000) test91u();
+               if (m & 0x00200000) test91v();
+               if (m & 0x00400000) test91w();
+               if (m & 0x00800000) test91x();
+               if (m & 0x01000000) test91y();
+               if (m & 0x02000000) test91z();
+               if (m & 0x04000000) test91aa();
+               if (m & 0x08000000) test91ab();
+               if (m & 0x10000000) test91ac();
+       }
+
+       quit();
+       /* NOTREACHED */
+}
diff --git a/minix/tests/test92.c b/minix/tests/test92.c
new file mode 100644 (file)
index 0000000..df5c71d
--- /dev/null
@@ -0,0 +1,1735 @@
+/* Tests for RAW sockets (LWIP) - by D.C. van Moolenbroek */
+/* This test needs to be run as root: creating raw sockets is root-only. */
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <netinet/icmp6.h>
+#include <netinet/in_pcb.h>
+#include <netinet6/in6_pcb.h>
+#include <arpa/inet.h>
+#include <machine/vmparam.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "common.h"
+#include "socklib.h"
+
+#define ITERATIONS     2
+
+#define TEST_PROTO             253     /* from RFC 3692 */
+#define TEST_ICMPV6_TYPE_A     200     /* from RFC 4443 */
+#define TEST_ICMPV6_TYPE_B     201     /* from RFC 4443 */
+
+static const enum state raw_states[] = {
+               S_NEW,          S_N_SHUT_R,     S_N_SHUT_W,     S_N_SHUT_RW,
+               S_BOUND,        S_CONNECTED,    S_SHUT_R,       S_SHUT_W,
+               S_SHUT_RW,
+};
+
+static const int raw_results[][__arraycount(raw_states)] = {
+       [C_ACCEPT]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,
+       },
+       [C_BIND]                = {
+               0,              0,              0,              0,
+               0,              -EINVAL,        -EINVAL,        -EINVAL,
+               -EINVAL,
+       },
+       [C_CONNECT]             = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_GETPEERNAME]         = {
+               -ENOTCONN,      -ENOTCONN,      -ENOTCONN,      -ENOTCONN,
+               -ENOTCONN,      0,              0,              0,
+               0,
+       },
+       [C_GETSOCKNAME]         = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_GETSOCKOPT_ERR]      = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_GETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_GETSOCKOPT_RB]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_IOCTL_NREAD]         = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_LISTEN]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+               -EOPNOTSUPP,
+       },
+       [C_RECV]                = {
+               -EAGAIN,        0,              -EAGAIN,        0,
+               -EAGAIN,        -EAGAIN,        0,              -EAGAIN,
+               0,
+       },
+       [C_RECVFROM]            = {
+               -EAGAIN,        0,              -EAGAIN,        0,
+               -EAGAIN,        -EAGAIN,        0,              -EAGAIN,
+               0,
+       },
+       [C_SEND]                = {
+               -EDESTADDRREQ,  -EDESTADDRREQ,  -EPIPE,         -EPIPE,
+               -EDESTADDRREQ,  1,              1,              -EPIPE,
+               -EPIPE,
+       },
+       [C_SENDTO]              = {
+               1,              1,              -EPIPE,         -EPIPE,
+               1,              1,              1,              -EPIPE,
+               -EPIPE,
+       },
+       [C_SELECT_R]            = {
+               0,              1,              0,              1,
+               0,              0,              1,              0,
+               1,
+       },
+       [C_SELECT_W]            = {
+               1,              1,              1,              1,
+               1,              1,              1,              1,
+               1,
+       },
+       [C_SELECT_X]            = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_SETSOCKOPT_BC]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_SETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_SETSOCKOPT_L]        = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_SETSOCKOPT_RA]       = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_SHUTDOWN_R]          = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_SHUTDOWN_RW]         = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+       [C_SHUTDOWN_W]          = {
+               0,              0,              0,              0,
+               0,              0,              0,              0,
+               0,
+       },
+};
+
+/*
+ * Set up a RAW socket file descriptor in the requested state and pass it to
+ * socklib_sweep_call() along with local and remote addresses and their length.
+ */
+static int
+raw_sweep(int domain, int type, int protocol, enum state state,
+       enum call call)
+{
+       struct sockaddr_in sinA, sinB;
+       struct sockaddr_in6 sin6A, sin6B;
+       struct sockaddr *addrA, *addrB;
+       socklen_t addr_len;
+       int r, fd, fd2;
+
+       if (domain == AF_INET) {
+               memset(&sinA, 0, sizeof(sinA));
+               sinA.sin_family = domain;
+               sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+               memcpy(&sinB, &sinA, sizeof(sinB));
+
+               addrA = (struct sockaddr *)&sinA;
+               addrB = (struct sockaddr *)&sinB;
+               addr_len = sizeof(sinA);
+       } else {
+               assert(domain == AF_INET6);
+
+               memset(&sin6A, 0, sizeof(sin6A));
+               sin6A.sin6_family = domain;
+               memcpy(&sin6A.sin6_addr, &in6addr_loopback,
+                   sizeof(sin6A.sin6_addr));
+
+               memcpy(&sin6B, &sin6A, sizeof(sin6B));
+
+               addrA = (struct sockaddr *)&sin6A;
+               addrB = (struct sockaddr *)&sin6B;
+               addr_len = sizeof(sin6A);
+       }
+
+       /* Create a bound remote socket. */
+       if ((fd2 = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0);
+
+       if (bind(fd2, addrB, addr_len) != 0) e(0);
+
+       switch (state) {
+       case S_NEW:
+       case S_N_SHUT_R:
+       case S_N_SHUT_W:
+       case S_N_SHUT_RW:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               switch (state) {
+               case S_N_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_N_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_N_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               break;
+
+       case S_BOUND:
+       case S_CONNECTED:
+       case S_SHUT_R:
+       case S_SHUT_W:
+       case S_SHUT_RW:
+               if ((fd = socket(domain, type | SOCK_NONBLOCK,
+                   protocol)) < 0) e(0);
+
+               if (bind(fd, addrA, addr_len) != 0) e(0);
+
+               if (state == S_BOUND)
+                       break;
+
+               if (connect(fd, addrB, addr_len) != 0) e(0);
+
+               switch (state) {
+               case S_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+               case S_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+               case S_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+               default: break;
+               }
+
+               break;
+
+       default:
+               fd = -1;
+               e(0);
+       }
+
+       r = socklib_sweep_call(call, fd, addrA, addrB, addr_len);
+
+       if (close(fd) != 0) e(0);
+       if (fd2 != -1 && close(fd2) != 0) e(0);
+
+       return r;
+}
+
+/*
+ * Sweep test for socket calls versus socket states of RAW sockets.
+ */
+static void
+test92a(void)
+{
+
+       subtest = 1;
+
+       socklib_sweep(AF_INET, SOCK_RAW, TEST_PROTO, raw_states,
+           __arraycount(raw_states), (const int *)raw_results, raw_sweep);
+
+       socklib_sweep(AF_INET6, SOCK_RAW, TEST_PROTO, raw_states,
+           __arraycount(raw_states), (const int *)raw_results, raw_sweep);
+}
+
+/*
+ * Basic I/O test for raw sockets.
+ */
+static void
+test92b(void)
+{
+       struct sockaddr_in sinA, sinB, sinC;
+       struct sockaddr_in6 sin6A, sin6B, sin6C;
+       socklen_t len;
+       unsigned int i;
+       uint8_t buf[256], packet[5];
+       int fd, fd2;
+
+       subtest = 2;
+
+       /* First test IPv4. */
+       if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       memset(&sinA, 0, sizeof(sinA));
+       sinA.sin_family = AF_INET;
+       sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       for (i = 0; i < __arraycount(packet); i++)
+               packet[i] = (uint8_t)(-i);
+
+       if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sinA,
+           sizeof(sinA)) != sizeof(packet)) e(0);
+
+       memset(buf, 0, sizeof(buf));
+       len = sizeof(sinB);
+       if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sinB,
+           &len) != sizeof(struct ip) + sizeof(packet)) e(0);
+
+       if (memcmp(&buf[sizeof(struct ip)], packet, sizeof(packet)) != 0) e(0);
+
+       if (len != sizeof(sinB)) e(0);
+       if (sinB.sin_len != sizeof(sinB)) e(0);
+       if (sinB.sin_family != AF_INET) e(0);
+       if (sinB.sin_port != htons(0)) e(0);
+       if (sinB.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0);
+
+       /*
+        * Test two additional things:
+        *
+        * 1) a non-zero port number is ignored when sending;
+        * 2) multiple raw sockets may receive the same packet.
+        */
+       sinA.sin_port = htons(22);
+
+       if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sinA,
+           sizeof(sinA)) != sizeof(packet)) e(0);
+
+       memset(buf, 0, sizeof(buf));
+       len = sizeof(sinC);
+       if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sinC,
+           &len) != sizeof(struct ip) + sizeof(packet)) e(0);
+
+       if (memcmp(&buf[sizeof(struct ip)], packet, sizeof(packet)) != 0) e(0);
+
+       if (len != sizeof(sinC)) e(0);
+       if (memcmp(&sinB, &sinC, sizeof(sinB)) != 0) e(0);
+
+       memset(buf, 0, sizeof(buf));
+       len = sizeof(sinC);
+       if (recvfrom(fd2, buf, sizeof(buf), 0, (struct sockaddr *)&sinC,
+           &len) != sizeof(struct ip) + sizeof(packet)) e(0);
+
+       if (memcmp(&buf[sizeof(struct ip)], packet, sizeof(packet)) != 0) e(0);
+
+       if (len != sizeof(sinC)) e(0);
+       if (memcmp(&sinB, &sinC, sizeof(sinB)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* Then test IPv6. */
+       if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+       memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr));
+
+       if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != sizeof(packet)) e(0);
+
+       memset(buf, 0, sizeof(buf));
+       len = sizeof(sin6B);
+       if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6B,
+           &len) != sizeof(packet)) e(0);
+
+       if (memcmp(buf, packet, sizeof(packet)) != 0) e(0);
+
+       if (len != sizeof(sin6B)) e(0);
+       if (sin6B.sin6_len != sizeof(sin6B)) e(0);
+       if (sin6B.sin6_family != AF_INET6) e(0);
+       if (sin6B.sin6_port != htons(0)) e(0);
+       if (memcmp(&sin6B.sin6_addr, &in6addr_loopback,
+           sizeof(sin6B.sin6_addr)) != 0) e(0);
+
+       /* As above. */
+       sin6A.sin6_port = htons(22);
+
+       if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != sizeof(packet)) e(0);
+
+       memset(buf, 0, sizeof(buf));
+       len = sizeof(sin6C);
+       if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6C,
+           &len) != sizeof(packet)) e(0);
+
+       if (memcmp(buf, packet, sizeof(packet)) != 0) e(0);
+
+       if (len != sizeof(sin6C)) e(0);
+       if (memcmp(&sin6B, &sin6C, sizeof(sin6B)) != 0) e(0);
+
+       memset(buf, 0, sizeof(buf));
+       len = sizeof(sin6C);
+       if (recvfrom(fd2, buf, sizeof(buf), 0, (struct sockaddr *)&sin6C,
+           &len) != sizeof(packet)) e(0);
+
+       if (memcmp(buf, packet, sizeof(packet)) != 0) e(0);
+
+       if (len != sizeof(sin6C)) e(0);
+       if (memcmp(&sin6B, &sin6C, sizeof(sin6B)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test the IPV6_CHECKSUM socket option.
+ */
+static void
+test92c(void)
+{
+       struct sockaddr_in6 sin6;
+       struct icmp6_hdr icmp6_hdr;
+       uint8_t buf[6], buf2[6], *buf3;
+       socklen_t len;
+       unsigned int i;
+       int fd, fd2, val;
+
+       subtest = 3;
+
+       if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       if (shutdown(fd, SHUT_RD) != 0) e(0);
+
+       /* For non-ICMPv6 sockets, checksumming is disabled by default. */
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != -1) e(0);
+
+       /* Test bad offsets. */
+       val = -2;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       /* Now test real checksum computation. */
+       val = 0;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       if (shutdown(fd2, SHUT_WR) != 0) e(0);
+
+       memset(buf, 0, sizeof(buf));
+       buf[2] = 0xfe;
+       buf[3] = 0x95;
+       buf[4] = 0x4d;
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       if (sendto(fd, buf, 5, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 5)
+               e(0);
+
+       if (recv(fd2, buf2, sizeof(buf2), 0) != 5) e(0);
+
+       if (buf2[0] != 0xb3 || buf2[1] != 0x65) e(0);
+       if (memcmp(&buf2[2], &buf[2], 3) != 0) e(0);
+
+       /* Turn on checksum verification on the receiving socket. */
+       val = 0;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       /*
+        * The current value of the checksum field should not be incorporated
+        * in the checksum, as that would result in an invalid checksum.
+        */
+       buf[0] = 0xab;
+       buf[1] = 0xcd;
+
+       if (sendto(fd, buf, 5, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 5)
+               e(0);
+
+       if (recv(fd2, buf2, sizeof(buf2), 0) != 5) e(0);
+
+       if (buf2[0] != 0xb3 || buf2[1] != 0x65) e(0);
+       if (memcmp(&buf2[2], &buf[2], 3) != 0) e(0);
+
+       /*
+        * Turn off checksum computation on the sending side, so that the
+        * packet ends up being dropped on the receiving side.
+        */
+       val = -1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (sendto(fd, buf, 5, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 5)
+               e(0);
+
+       /* Send some packets that are too small to contain the checksum. */
+       if (sendto(fd, buf, 0, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 0)
+               e(0);
+       if (sendto(fd, buf, 1, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 1)
+               e(0);
+
+       /*
+        * If this recv call is "too soon" (it should not be) and the packets
+        * arrive later anyway, then we will get a failure below.
+        */
+       if (recv(fd2, buf2, sizeof(buf2), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       buf[0] = 0;
+       buf[1] = 0x67;
+       if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != 4) e(0);
+
+       if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0);
+       if (memcmp(buf, buf2, 4) != 0) e(0);
+
+       /*
+        * We repeat some of the tests with a non-zero checksum offset, just to
+        * be sure.
+        */
+       val = 2;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 2) e(0);
+
+       buf[0] = 0x56;
+       buf[1] = 0x78;
+
+       for (i = 0; i <= 3; i++) {
+               if (sendto(fd, buf, i, 0, (struct sockaddr *)&sin6,
+                   sizeof(sin6)) != -1) e(0);
+               if (errno != EINVAL) e(0);
+       }
+
+       val = 2;
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != 4) e(0);
+
+       if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0);
+       if (memcmp(buf, buf2, 2) != 0) e(0);
+       if (buf2[2] != 0xa8 || buf2[3] != 0x84) e(0);
+
+       val = -1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       buf[2] = 0xa8;
+       buf[3] = 0x85; /* deliberately bad checksum */
+
+       /* All these should be dropped on the receiver side. */
+       for (i = 0; i <= 4; i++) {
+               if (sendto(fd, buf, i, 0, (struct sockaddr *)&sin6,
+                   sizeof(sin6)) != i) e(0);
+       }
+
+       buf[3] = 0x84; /* good checksum */
+       if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 4)
+               e(0);
+
+       if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0);
+       if (memcmp(buf, buf2, 4) != 0) e(0);
+
+       if (recv(fd2, buf2, sizeof(buf2), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       val = -1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+       if (setsockopt(fd2, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       buf[3] = 0x85;
+       if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 4)
+               e(0);
+
+       if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0);
+       if (memcmp(buf, buf2, 4) != 0) e(0);
+
+       /*
+        * The following is a lwIP-specific test: lwIP does not support storing
+        * generated checksums beyond the first pbuf.  We do not know the size
+        * of the first pbuf until we actually send a packet, so the setsockopt
+        * call will not fail, but sending the packet will.  Depending on the
+        * buffer allocation strategy, the following test may or may not
+        * trigger this case; simply ensure that we do not crash the service.
+        */
+       if ((buf3 = malloc(4096)) == NULL) e(0);
+
+       val = 4094;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != 0) e(0);
+
+       /* This call may or may not fail, but if it fails, it yields EINVAL. */
+       if (sendto(fd, buf3, 4096, 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) == -1 && errno != EINVAL) e(0);
+
+       free(buf3);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* For ICMPv6 packets, checksumming is always enabled. */
+       if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 2) e(0);
+
+       val = -1;
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+           sizeof(val)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       memset(&icmp6_hdr, 0, sizeof(icmp6_hdr));
+       icmp6_hdr.icmp6_type = TEST_ICMPV6_TYPE_A;
+       icmp6_hdr.icmp6_code = 123;
+       icmp6_hdr.icmp6_cksum = htons(0);
+
+       len = offsetof(struct icmp6_hdr, icmp6_dataun);
+       if (sendto(fd, &icmp6_hdr, len, 0, (struct sockaddr *)&sin6,
+          sizeof(sin6)) != len) e(0);
+
+       if (recv(fd, &icmp6_hdr, sizeof(icmp6_hdr), 0) != len) e(0);
+
+       if (icmp6_hdr.icmp6_type != TEST_ICMPV6_TYPE_A) e(0);
+       if (icmp6_hdr.icmp6_code != 123) e(0);
+       if (ntohs(icmp6_hdr.icmp6_cksum) != 0x3744) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* For IPv4 and non-RAW IPv6 sockets, the option does not work. */
+       for (i = 0; i <= 2; i++) {
+               switch (i) {
+               case 0: fd = socket(AF_INET6, SOCK_DGRAM, 0); break;
+               case 1: fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6); break;
+               case 2: fd = socket(AF_INET, SOCK_RAW, TEST_PROTO); break;
+               }
+               if (fd < 0) e(0);
+
+               val = -1;
+               if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+                   sizeof(val)) != -1) e(0);
+               if (errno != ENOPROTOOPT) e(0);
+
+               len = sizeof(val);
+               if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val,
+                   &len) != -1) e(0);
+               if (errno != ENOPROTOOPT) e(0);
+
+               if (close(fd) != 0) e(0);
+       }
+}
+
+/*
+ * Test the ICMP6_FILTER socket option.
+ */
+static void
+test92d(void)
+{
+       struct sockaddr_in6 sin6;
+       struct sockaddr_in sin;
+       struct icmp6_filter filter;
+       struct icmp6_hdr packet;
+       socklen_t len;
+       struct timeval tv;
+       unsigned int i;
+       int fd, fd2;
+
+       subtest = 4;
+
+       /*
+        * We use two different sockets to eliminate the possibility that the
+        * filter is also applied when sending packets--it should not be.
+        */
+       if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0);
+
+       if (shutdown(fd, SHUT_WR) != 0) e(0);
+
+       len = sizeof(filter);
+       if (getsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, &len) != 0)
+               e(0);
+
+       /* We do not aim to test the ICMP6_FILTER macros here. */
+       for (i = 0; i <= UINT8_MAX; i++)
+               if (!ICMP6_FILTER_WILLPASS(i, &filter)) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0);
+
+       ICMP6_FILTER_SETBLOCKALL(&filter);
+       if (setsockopt(fd2, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+           sizeof(filter)) != 0) e(0);
+
+       len = sizeof(filter);
+       if (getsockopt(fd2, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, &len) != 0)
+               e(0);
+
+       for (i = 0; i <= UINT8_MAX; i++)
+               if (ICMP6_FILTER_WILLPASS(i, &filter)) e(0);
+
+       ICMP6_FILTER_SETPASSALL(&filter);
+       ICMP6_FILTER_SETBLOCK(TEST_ICMPV6_TYPE_A, &filter);
+       if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+           sizeof(filter)) != 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       memset(&packet, 0, sizeof(packet));
+       packet.icmp6_type = TEST_ICMPV6_TYPE_A;
+       packet.icmp6_code = 12;
+
+       if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != sizeof(packet)) e(0);
+
+       packet.icmp6_type = TEST_ICMPV6_TYPE_B;
+       packet.icmp6_code = 34;
+
+       if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != sizeof(packet)) e(0);
+
+       memset(&packet, 0, sizeof(packet));
+
+       if (recv(fd, &packet, sizeof(packet), 0) != sizeof(packet)) e(0);
+       if (packet.icmp6_type != TEST_ICMPV6_TYPE_B) e(0);
+       if (packet.icmp6_code != 34) e(0);
+
+       if (recv(fd, &packet, sizeof(packet), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       ICMP6_FILTER_SETBLOCKALL(&filter);
+       ICMP6_FILTER_SETPASS(TEST_ICMPV6_TYPE_A, &filter);
+       if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+           sizeof(filter)) != 0) e(0);
+
+       memset(&packet, 0, sizeof(packet));
+       packet.icmp6_type = TEST_ICMPV6_TYPE_B;
+       packet.icmp6_code = 56;
+
+       if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != sizeof(packet)) e(0);
+
+       packet.icmp6_type = TEST_ICMPV6_TYPE_A;
+       packet.icmp6_code = 78;
+
+       if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != sizeof(packet)) e(0);
+
+       /*
+        * RFC 3542 states that setting a zero-length filter resets the filter.
+        * This seems like one of those things that a standardization RFC
+        * should not mandate: it is redundant at the API level (one can set a
+        * PASSALL filter, which is the required default), it relies on an edge
+        * case (setsockopt taking a zero-length argument), and as a "shortcut"
+        * it does not even cover a case that is likely to occur (no actual
+        * program would reset its filter on a regular basis).  Presumably it
+        * is a way to deallocate filter memory on some platforms, but was that
+        * worth the RFC inclusion?  Anyhow, we support it; NetBSD does not.
+        */
+       if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, NULL, 0) != 0) e(0);
+
+       packet.icmp6_type = TEST_ICMPV6_TYPE_B;
+       packet.icmp6_code = 90;
+
+       if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != sizeof(packet)) e(0);
+
+       memset(&packet, 0, sizeof(packet));
+
+       if (recv(fd, &packet, sizeof(packet), 0) != sizeof(packet)) e(0);
+       if (packet.icmp6_type != TEST_ICMPV6_TYPE_A) e(0);
+       if (packet.icmp6_code != 78) e(0);
+
+       if (recv(fd, &packet, sizeof(packet), 0) != sizeof(packet)) e(0);
+       if (packet.icmp6_type != TEST_ICMPV6_TYPE_B) e(0);
+       if (packet.icmp6_code != 90) e(0);
+
+       if (recv(fd, &packet, sizeof(packet), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (recv(fd2, &packet, sizeof(packet), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       len = sizeof(filter);
+       if (getsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, &len) != 0)
+               e(0);
+
+       for (i = 0; i <= UINT8_MAX; i++)
+               if (!ICMP6_FILTER_WILLPASS(i, &filter)) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       /*
+        * Let's get weird and send an ICMPv6 packet from an IPv4 socket.
+        * Currently, such packets are always dropped based on the rule that
+        * IPv6 sockets with checksumming enabled drop all IPv4 packets.  As it
+        * happens, that is also all that is keeping this packet from arriving.
+        */
+       if ((fd2 = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0);
+
+       ICMP6_FILTER_SETBLOCKALL(&filter);
+       if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+           sizeof(filter)) != 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       memset(&packet, 0, sizeof(packet));
+       packet.icmp6_type = TEST_ICMPV6_TYPE_A;
+       packet.icmp6_code = 123;
+       packet.icmp6_cksum = htons(0); /* TODO: use valid checksum */
+
+       if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != sizeof(packet)) e(0);
+
+       /*
+        * If the packet were to arrive at all, it should arrive instantly, so
+        * this is just an excuse to use SO_RCVTIMEO.
+        */
+       tv.tv_sec = 0;
+       tv.tv_usec = 100000;
+       if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0)
+               e(0);
+
+       if (recv(fd, &packet, sizeof(packet), 0) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Make sure ICMP6_FILTER works on IPv6-ICMPv6 sockets only. */
+       for (i = 0; i <= 2; i++) {
+               switch (i) {
+               case 0: fd = socket(AF_INET6, SOCK_DGRAM, 0); break;
+               case 1: fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO); break;
+               case 2: fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6); break;
+               }
+               if (fd < 0) e(0);
+
+               if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+                   sizeof(filter)) != -1) e(0);
+               if (errno != ENOPROTOOPT) e(0);
+
+               len = sizeof(filter);
+               if (getsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+                   &len) != -1) e(0);
+               if (errno != ENOPROTOOPT) e(0);
+
+               if (close(fd) != 0) e(0);
+       }
+}
+
+/*
+ * Test that IPPROTO_ICMPV6 has no special value on IPv4 raw sockets.  In
+ * particular, test that no checksum is generated or verified.  By now we have
+ * already tested that none of the IPv6 socket options work on such sockets.
+ */
+static void
+test92e(void)
+{
+       char buf[sizeof(struct ip) + sizeof(struct icmp6_hdr)];
+       struct sockaddr_in sin;
+       struct icmp6_hdr packet;
+       int fd;
+
+       subtest = 5;
+
+       if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0);
+
+       memset(&packet, 0, sizeof(packet));
+       packet.icmp6_type = TEST_ICMPV6_TYPE_A;
+       packet.icmp6_code = 123;
+       packet.icmp6_cksum = htons(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (sendto(fd, &packet, sizeof(packet), 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != sizeof(packet)) e(0);
+
+       if (recv(fd, buf, sizeof(buf), 0) != sizeof(buf)) e(0);
+
+       memcpy(&packet, &buf[sizeof(struct ip)], sizeof(packet));
+       if (packet.icmp6_type != TEST_ICMPV6_TYPE_A) e(0);
+       if (packet.icmp6_code != 123) e(0);
+       if (packet.icmp6_cksum != htons(0)) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+struct testpkt {
+       struct ip ip;
+       struct udphdr udp;
+       uint8_t data[6];
+} __packed;
+
+/*
+ * Test the IP_HDRINCL socket option.
+ */
+static void
+test92f(void)
+{
+       struct sockaddr_in sin;
+       struct testpkt pkt, pkt2;
+       socklen_t len;
+       char buf[7];
+       unsigned int i;
+       int fd, fd2, val;
+
+       subtest = 6;
+
+       /* See if we can successfully feign a UDP packet. */
+       memset(&pkt, 0, sizeof(pkt));
+       pkt.ip.ip_v = IPVERSION;
+       pkt.ip.ip_hl = sizeof(pkt.ip) >> 2;
+       pkt.ip.ip_tos = 123;
+       pkt.ip.ip_len = sizeof(pkt);                    /* swapped by OS */
+       pkt.ip.ip_id = htons(456);
+       pkt.ip.ip_off = IP_DF;                          /* swapped by OS */
+       pkt.ip.ip_ttl = 78;
+       pkt.ip.ip_p = IPPROTO_UDP;
+       pkt.ip.ip_sum = htons(0);                       /* filled by OS */
+       pkt.ip.ip_src.s_addr = htonl(INADDR_LOOPBACK);
+       pkt.ip.ip_dst.s_addr = htonl(INADDR_LOOPBACK);
+       pkt.udp.uh_sport = htons(TEST_PORT_B);
+       pkt.udp.uh_dport = htons(TEST_PORT_A);
+       pkt.udp.uh_sum = htons(0); /* lazy.. */
+       pkt.udp.uh_ulen = htons(sizeof(pkt.udp) + sizeof(pkt.data));
+       memcpy(pkt.data, "Hello!", sizeof(pkt.data));
+
+       if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) e(0);
+
+       if (shutdown(fd, SHUT_RD) != 0) e(0);
+
+       /* IP_HDRINCL is never enabled by default. */
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val)) != 0)
+               e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 1) e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_A);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (bind(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       sin.sin_port = htons(0);
+
+       if (sendto(fd, &pkt, sizeof(pkt), 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != sizeof(pkt)) e(0);
+
+       if (recv(fd2, &buf, sizeof(buf), 0) != sizeof(pkt.data)) e(0);
+       if (memcmp(buf, pkt.data, sizeof(pkt.data)) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       len = sizeof(val);
+       if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, &len) != 0) e(0);
+       if (len != sizeof(val)) e(0);
+       if (val != 0) e(0);
+
+       if (shutdown(fd, SHUT_RD) != 0) e(0);
+
+       /* See if we can receive a packet for our own protocol. */
+       pkt.ip.ip_p = TEST_PROTO;
+
+       if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val)) != 0)
+               e(0);
+
+       if (sendto(fd, &pkt, sizeof(pkt), 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != sizeof(pkt)) e(0);
+
+       if (recv(fd2, &pkt2, sizeof(pkt2), 0) != sizeof(pkt2)) e(0);
+
+       if (pkt2.ip.ip_v != pkt.ip.ip_v) e(0);
+       if (pkt2.ip.ip_hl != pkt.ip.ip_hl) e(0);
+       if (pkt2.ip.ip_tos != pkt.ip.ip_tos) e(0);
+       if (pkt2.ip.ip_len != pkt.ip.ip_len) e(0);
+       if (pkt2.ip.ip_id != pkt.ip.ip_id) e(0);
+       if (pkt2.ip.ip_off != pkt.ip.ip_off) e(0);
+       if (pkt2.ip.ip_ttl != pkt.ip.ip_ttl) e(0);
+       if (pkt2.ip.ip_p != pkt.ip.ip_p) e(0);
+       if (pkt2.ip.ip_sum == htons(0)) e(0);
+       if (pkt2.ip.ip_src.s_addr != pkt.ip.ip_src.s_addr) e(0);
+       if (pkt2.ip.ip_dst.s_addr != pkt.ip.ip_dst.s_addr) e(0);
+
+       /*
+        * Test sending packets with weird sizes to ensure that we do not crash
+        * the service.  These packets would never arrive anyway.
+        */
+       if (sendto(fd, &pkt, 0, 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+       if (sendto(fd, &pkt, sizeof(pkt.ip) - 1, 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+       if (sendto(fd, &pkt, sizeof(pkt.ip), 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != sizeof(pkt.ip)) e(0);
+
+       if (recv(fd2, &pkt2, sizeof(pkt2), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* Ensure that the socket option does not work on other types. */
+       for (i = 0; i <= 1; i++) {
+               switch (i) {
+               case 0: fd = socket(AF_INET, SOCK_DGRAM, 0); break;
+               case 1: fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO); break;
+               }
+               if (fd < 0) e(0);
+
+               len = sizeof(val);
+               if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val,
+                   &len) != -1) e(0);
+               if (errno != ENOPROTOOPT) e(0);
+
+               if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val,
+                   sizeof(val)) != -1) e(0);
+               if (errno != ENOPROTOOPT) e(0);
+
+               if (close(fd) != 0) e(0);
+       }
+}
+
+/*
+ * Test the IPPROTO_RAW socket protocol.  This test mostly shows that the
+ * IPPROTO_RAW protocol is nothing special: for both IPv4 and IPv6, it sends
+ * and receives packets with that protocol number.  We already tested earlier
+ * that IP_HDRINCL is disabled by default on IPPROTO_RAW sockets, too.
+ */
+static void
+test92g(void)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       char buf[sizeof(struct ip) + 1];
+       int fd;
+
+       subtest = 7;
+
+       if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (sendto(fd, "A", 1, 0, (struct sockaddr *)&sin,
+           sizeof(sin)) != 1) e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != sizeof(buf)) e(0);
+       if (buf[sizeof(struct ip)] != 'A') e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       if (sendto(fd, "B", 1, 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != 1) e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != 1) e(0);
+       if (buf[0] != 'B') e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test that connected raw sockets perform correct source-based filtering.
+ */
+static void
+test92h(void)
+{
+       struct sockaddr_in sinA, sinB;
+       struct sockaddr_in6 sin6A, sin6B;
+       struct sockaddr sa;
+       socklen_t len;
+       char buf[sizeof(struct ip) + 1];
+       int fd, fd2;
+
+       subtest = 8;
+
+       if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       len = sizeof(sinB);
+       if (getpeername(fd, (struct sockaddr *)&sinB, &len) != -1) e(0);
+       if (errno != ENOTCONN) e(0);
+
+       memset(&sinA, 0, sizeof(sinA));
+       sinA.sin_family = AF_INET;
+       sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       /*
+        * First test that packets with the right source are accepted.
+        * Unfortunately, source and destination are the same in this case, so
+        * this test is far from perfect.
+        */
+       if (connect(fd, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
+
+       if (getpeername(fd, (struct sockaddr *)&sinB, &len) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       if (sendto(fd2, "A", 1, 0, (struct sockaddr *)&sinA,
+           sizeof(sinA)) != 1) e(0);
+
+       buf[0] = '\0';
+       if (recv(fd2, buf, sizeof(buf), 0) != sizeof(struct ip) + 1) e(0);
+       if (buf[sizeof(struct ip)] != 'A') e(0);
+
+       buf[0] = '\0';
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) !=
+           sizeof(struct ip) + 1) e(0);
+       if (buf[sizeof(struct ip)] != 'A') e(0);
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_family = AF_UNSPEC;
+
+       sinA.sin_addr.s_addr = htonl(INADDR_NONE);
+
+       /* While here, test unconnecting the socket. */
+       if (connect(fd, &sa, sizeof(sa)) != 0) e(0);
+
+       if (getpeername(fd, (struct sockaddr *)&sinB, &len) != -1) e(0);
+       if (errno != ENOTCONN) e(0);
+
+       /* Then test that packets with the wrong source are ignored. */
+       if (connect(fd, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
+
+       if (sendto(fd2, "B", 1, 0, (struct sockaddr *)&sinB,
+           sizeof(sinB)) != 1) e(0);
+
+       buf[0] = '\0';
+       if (recv(fd2, buf, sizeof(buf), 0) != sizeof(struct ip) + 1) e(0);
+       if (buf[sizeof(struct ip)] != 'B') e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       /* Repeat for IPv6, but now the other way around. */
+       if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       len = sizeof(sin6B);
+       if (getpeername(fd, (struct sockaddr *)&sin6B, &len) != -1) e(0);
+       if (errno != ENOTCONN) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+       memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr));
+
+       memcpy(&sin6B, &sin6A, sizeof(sin6B));
+       if (inet_pton(AF_INET6, "::2", &sin6B.sin6_addr) != 1) e(0);
+
+       if (connect(fd, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       if (sendto(fd2, "C", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       buf[0] = '\0';
+       if (recv(fd2, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'C') e(0);
+
+       if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (connect(fd, &sa, sizeof(sa)) != 0) e(0);
+
+       if (connect(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (sendto(fd2, "D", 1, 0, (struct sockaddr *)&sin6A,
+           sizeof(sin6A)) != 1) e(0);
+
+       buf[0] = '\0';
+       if (recv(fd2, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'D') e(0);
+
+       buf[0] = '\0';
+       if (recv(fd, buf, sizeof(buf), 0) != 1) e(0);
+       if (buf[0] != 'D') e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test sending large and small RAW packets.  This test is an altered copy of
+ * test91e, but has been changed to IPv6 to cover a greater spectrum together.
+ */
+static void
+test92i(void)
+{
+       struct sockaddr_in6 sin6;
+       struct msghdr msg;
+       struct iovec iov;
+       char *buf;
+       unsigned int i, j;
+       int r, fd, fd2, val;
+
+       subtest = 9;
+
+       if ((buf = malloc(65536)) == NULL) e(0);
+
+       if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       val = 65536;
+       if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0)
+               e(0);
+
+       if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       /*
+        * A maximum send buffer size of a full packet size's worth may always
+        * be set, although this is not necessarily the actual maximum.
+        */
+       val = 65535;
+       if (setsockopt(fd2, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) != 0)
+               e(0);
+
+       /* Find the largest possible packet size that can actually be sent. */
+       for (i = 0; i < val; i += sizeof(int)) {
+               j = i ^ 0xdeadbeef;
+               memcpy(&buf[i], &j, sizeof(j));
+       }
+
+       for (val = 65536; val > 0; val--) {
+               if ((r = sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6,
+                   sizeof(sin6))) == val)
+                       break;
+               if (r != -1) e(0);
+               if (errno != EMSGSIZE) e(0);
+       }
+
+       if (val != 65535 - sizeof(struct ip6_hdr)) e(0);
+
+       memset(buf, 0, val);
+       buf[val] = 'X';
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = val + 1;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != val) e(0);
+       if (msg.msg_flags != 0) e(0);
+
+       for (i = 0; i < val; i += sizeof(int)) {
+               j = i ^ 0xdeadbeef;
+               if (memcmp(&buf[i], &j, MIN(sizeof(j), val - i))) e(0);
+       }
+       if (buf[val] != 'X') e(0);
+
+       if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6, sizeof(sin6)) !=
+           val) e(0);
+
+       /*
+        * Make sure that there are no off-by-one errors in the receive code,
+        * and that MSG_TRUNC is set (only) when not the whole packet was
+        * received.
+        */
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = val;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != val) e(0);
+       if (msg.msg_flags != 0) e(0);
+
+       if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6, sizeof(sin6)) !=
+           val) e(0);
+
+       buf[val - 1] = 'Y';
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = val - 1;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != val - 1) e(0);
+       if (msg.msg_flags != MSG_TRUNC) e(0);
+
+       for (i = 0; i < val - 1; i += sizeof(int)) {
+               j = i ^ 0xdeadbeef;
+               if (memcmp(&buf[i], &j, MIN(sizeof(j), val - 1 - i))) e(0);
+       }
+       if (buf[val - 1] != 'Y') e(0);
+
+       if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6, sizeof(sin6)) !=
+           val) e(0);
+
+       buf[0] = 'Z';
+
+       memset(&iov, 0, sizeof(iov));
+       iov.iov_base = buf;
+       iov.iov_len = 0;
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, 0) != 0) e(0);
+       if (msg.msg_flags != MSG_TRUNC) e(0);
+       if (buf[0] != 'Z') e(0);
+
+       /* Make sure that zero-sized packets can be sent and received. */
+       if (sendto(fd2, buf, 0, 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != 0) e(0);
+
+       /*
+        * Note how we currently assume that packets sent over localhost will
+        * arrive immediately, so that we can use MSG_DONTWAIT to avoid that
+        * the test freezes.
+        */
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       if (recvmsg(fd, &msg, MSG_DONTWAIT) != 0) e(0);
+       if (msg.msg_flags != 0) e(0);
+       if (buf[0] != 'Z') e(0);
+
+       if (recv(fd, buf, val, MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       /*
+        * When sending lots of small packets, ensure that fewer packets arrive
+        * than we sent.  This sounds weird, but we cannot actually check the
+        * internal TCP/IP buffer granularity and yet we want to make sure that
+        * the receive queue is measured in terms of buffers rather than packet
+        * sizes.  In addition, we check that older packets are favored,
+        * instead discarding new ones when the receive buffer is full.
+        */
+       for (i = 0; i < 65536 / sizeof(j); i++) {
+               j = i;
+               if (sendto(fd2, &j, sizeof(j), 0, (struct sockaddr *)&sin6,
+                   sizeof(sin6)) != sizeof(j)) e(0);
+       }
+
+       for (i = 0; i < 1025; i++) {
+               r = recv(fd, &j, sizeof(j), MSG_DONTWAIT);
+               if (r == -1) {
+                       if (errno != EWOULDBLOCK) e(0);
+                       break;
+               }
+               if (r != sizeof(j)) e(0);
+               if (i != j) e(0);
+       }
+       if (i == 1025) e(0);
+
+       if (close(fd2) != 0) e(0);
+       if (close(fd) != 0) e(0);
+
+       free(buf);
+}
+
+/*
+ * Test sending and receiving with bad pointers.
+ */
+static void
+test92j(void)
+{
+       struct sockaddr_in sin;
+       char *ptr;
+       int i, fd;
+
+       subtest = 10;
+
+       if ((ptr = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE,
+           MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED) e(0);
+
+       if (munmap(&ptr[PAGE_SIZE], PAGE_SIZE) != 0) e(0);
+
+       if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       memset(ptr, 'A', PAGE_SIZE);
+
+       if (sendto(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0,
+           (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0);
+       if (errno != EFAULT) e(0);
+
+       memset(ptr, 'B', PAGE_SIZE);
+
+       if (sendto(fd, ptr, PAGE_SIZE - sizeof(struct ip), 0,
+           (struct sockaddr *)&sin, sizeof(sin)) !=
+           PAGE_SIZE - sizeof(struct ip)) e(0);
+
+       memset(ptr, 0, PAGE_SIZE);
+
+       if (recvfrom(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0, NULL, 0) != -1)
+               e(0);
+       if (errno != EFAULT) e(0);
+
+       if (recvfrom(fd, ptr, PAGE_SIZE * 2, 0, NULL, 0) != PAGE_SIZE) e(0);
+       for (i = sizeof(struct ip); i < PAGE_SIZE; i++)
+               if (ptr[i] != 'B') e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if (munmap(ptr, PAGE_SIZE) != 0) e(0);
+}
+
+/*
+ * Test basic sysctl(2) socket enumeration support.
+ */
+static void
+test92k(void)
+{
+       struct kinfo_pcb ki;
+       struct sockaddr_in lsin, rsin;
+       struct sockaddr_in6 lsin6, rsin6;
+       int fd, fd2, val;
+
+       subtest = 11;
+
+       if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 0) e(0);
+
+       if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       memset(&lsin, 0, sizeof(lsin));
+       lsin.sin_len = sizeof(lsin);
+       lsin.sin_family = AF_INET;
+
+       memset(&rsin, 0, sizeof(rsin));
+       rsin.sin_len = sizeof(rsin);
+       rsin.sin_family = AF_INET;
+
+       if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_RAW) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 0) e(0);
+
+       lsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+       if (bind(fd, (struct sockaddr *)&lsin, sizeof(lsin)) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_RAW) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+       if (ki.ki_pflags & INP_HDRINCL) e(0);
+
+       rsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       if (connect(fd, (struct sockaddr *)&rsin, sizeof(rsin)) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_RAW) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       if (sendto(fd2, "ABC", 3, 0, (struct sockaddr *)&lsin,
+           sizeof(lsin)) != 3) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       val = 1;
+       if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val)) != 0)
+               e(0);
+
+       if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_RAW) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq < 3) e(0);       /* size is rounded up */
+       if (!(ki.ki_pflags & INP_HDRINCL)) e(0);
+
+       if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       /* Test IPv6 sockets as well. */
+       if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0);
+
+       memset(&lsin6, 0, sizeof(lsin6));
+       lsin6.sin6_len = sizeof(lsin6);
+       lsin6.sin6_family = AF_INET6;
+
+       memset(&rsin6, 0, sizeof(rsin6));
+       rsin6.sin6_len = sizeof(rsin6);
+       rsin6.sin6_family = AF_INET6;
+
+       if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_RAW) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+
+       memcpy(&lsin6.sin6_addr, &in6addr_loopback, sizeof(lsin6.sin6_addr));
+       if (bind(fd, (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_RAW) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+       if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0);
+
+       memcpy(&rsin6.sin6_addr, &in6addr_loopback, sizeof(rsin6.sin6_addr));
+       if (connect(fd, (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0)
+               e(0);
+
+       if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 1) e(0);
+       if (ki.ki_type != SOCK_RAW) e(0);
+       if (ki.ki_tstate != 0) e(0);
+       if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0);
+       if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0);
+       if (ki.ki_sndq != 0) e(0);
+       if (ki.ki_rcvq != 0) e(0);
+       if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0);
+
+       if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0,
+           &ki) != 0) e(0);
+}
+
+/*
+ * Test local and remote IPv6 address handling.  In particular, test scope IDs
+ * and IPv4-mapped IPv6 addresses.
+ */
+static void
+test92l(void)
+{
+
+       subtest = 12;
+
+       socklib_test_addrs(SOCK_RAW, TEST_PROTO);
+}
+
+/*
+ * Test setting and retrieving basic multicast transmission options.
+ */
+static void
+test92m(void)
+{
+
+       subtest = 13;
+
+       socklib_multicast_tx_options(SOCK_RAW);
+}
+
+/*
+ * Test multicast support.
+ */
+static void
+test92n(void)
+{
+
+       subtest = 14;
+
+       socklib_test_multicast(SOCK_RAW, TEST_PROTO);
+}
+
+/*
+ * Test small and large ICMP echo ("ping") packets.  This test aims to confirm
+ * expected behavior resulting from the LWIP service's memory pool policies:
+ * lwIP should reply to ICMP echo requests that fit in a single 512-byte buffer
+ * (including space for ethernet headers, even on loopback interfaces), but not
+ * to requests exceeding a single buffer.
+ */
+static void
+test92o(void)
+{
+       struct sockaddr_in6 sin6;
+       struct icmp6_hdr packet;
+       char buf[512];
+       int fd;
+
+       subtest = 15;
+
+       /* IPv6 only for now, for simplicity reasons. */
+       if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+
+       memset(&packet, 0, sizeof(packet));
+       packet.icmp6_type = ICMP6_ECHO_REQUEST;
+       packet.icmp6_code = 0;
+       packet.icmp6_id = getpid();
+       packet.icmp6_seq = 1;
+
+       memset(buf, 'A', sizeof(buf));
+       memcpy(buf, &packet, sizeof(packet));
+
+       if (sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != sizeof(buf)) e(0);
+
+       packet.icmp6_seq = 2;
+
+       memset(buf, 'B', sizeof(buf));
+       memcpy(buf, &packet, sizeof(packet));
+
+       if (sendto(fd, buf, sizeof(buf) - 100, 0, (struct sockaddr *)&sin6,
+           sizeof(sin6)) != sizeof(buf) - 100) e(0);
+
+       do {
+               memset(buf, '\0', sizeof(buf));
+
+               if (recv(fd, buf, sizeof(buf), 0) <= 0) e(0);
+
+               memcpy(&packet, buf, sizeof(packet));
+       } while (packet.icmp6_type == ICMP6_ECHO_REQUEST);
+
+       if (packet.icmp6_type != ICMP6_ECHO_REPLY) e(0);
+       if (packet.icmp6_code != 0) e(0);
+       if (packet.icmp6_id != getpid()) e(0);
+       if (packet.icmp6_seq != 2) e(0);
+       if (buf[sizeof(buf) - 101] != 'B') e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test program for LWIP RAW sockets.
+ */
+int
+main(int argc, char ** argv)
+{
+       int i, m;
+
+       start(92);
+
+       if (argc == 2)
+               m = atoi(argv[1]);
+       else
+               m = 0xFFFF;
+
+       for (i = 0; i < ITERATIONS; i++) {
+               if (m & 0x0001) test92a();
+               if (m & 0x0002) test92b();
+               if (m & 0x0004) test92c();
+               if (m & 0x0008) test92d();
+               if (m & 0x0010) test92e();
+               if (m & 0x0020) test92f();
+               if (m & 0x0040) test92g();
+               if (m & 0x0080) test92h();
+               if (m & 0x0100) test92i();
+               if (m & 0x0200) test92j();
+               if (m & 0x0400) test92k();
+               if (m & 0x0400) test92k();
+               if (m & 0x0800) test92l();
+               if (m & 0x1000) test92m();
+               if (m & 0x2000) test92n();
+               if (m & 0x4000) test92o();
+       }
+
+       quit();
+       /* NOTREACHED */
+}
diff --git a/minix/tests/test93.c b/minix/tests/test93.c
new file mode 100644 (file)
index 0000000..cf04383
--- /dev/null
@@ -0,0 +1,839 @@
+/* Tests for network interfaces and routing (LWIP) - by D.C. van Moolenbroek */
+/* This test needs to be run as root: it manipulates network settings. */
+/*
+ * TODO: due to time constraints, this test is currently absolutely minimal.
+ * It does not yet test by far most of the service code it is supposed to test,
+ * in particular interface management code, interface address assignment code,
+ * routing sockets code, and routing code. The second subtest (test93b) in this
+ * file serves as a reasonable example of how many of the future subtests
+ * should operate, though: by issuing interface IOCTLs and routing commands on
+ * a loopback interface created for the occasion.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+#include <arpa/inet.h>
+
+#include "common.h"
+#include "socklib.h"
+
+#define TEST_IFNAME    "lo93"
+
+#define ITERATIONS 2
+
+static const enum state rtlnk_states[] = {
+               S_NEW,          S_N_SHUT_R,     S_N_SHUT_W,     S_N_SHUT_RW,
+};
+
+static const int rt_results[][__arraycount(rtlnk_states)] = {
+       [C_ACCEPT]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_BIND]                = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_CONNECT]             = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_GETPEERNAME]         = {
+               0,              0,              0,              0,
+       },
+       [C_GETSOCKNAME]         = {
+               0,              0,              0,              0,
+       },
+       [C_GETSOCKOPT_ERR]      = {
+               0,              0,              0,              0,
+       },
+       [C_GETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+       },
+       [C_GETSOCKOPT_RB]       = {
+               0,              0,              0,              0,
+       },
+       [C_IOCTL_NREAD]         = {
+               0,              0,              0,              0,
+       },
+       [C_LISTEN]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_RECV]                = {
+               -EAGAIN,        0,              -EAGAIN,        0,
+       },
+       [C_RECVFROM]            = {
+               -EAGAIN,        0,              -EAGAIN,        0,
+       },
+       [C_SEND]                = {
+               -ENOBUFS,       -ENOBUFS,       -EPIPE,         -EPIPE,
+       },
+       [C_SENDTO]              = {
+               -EISCONN,       -EISCONN,       -EPIPE,         -EPIPE,
+       },
+       [C_SELECT_R]            = {
+               0,              1,              0,              1,
+       },
+       [C_SELECT_W]            = {
+               1,              1,              1,              1,
+       },
+       [C_SELECT_X]            = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_BC]       = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_L]        = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_RA]       = {
+               0,              0,              0,              0,
+       },
+       [C_SHUTDOWN_R]          = {
+               0,              0,              0,              0,
+       },
+       [C_SHUTDOWN_RW]         = {
+               0,              0,              0,              0,
+       },
+       [C_SHUTDOWN_W]          = {
+               0,              0,              0,              0,
+       },
+};
+
+static const int lnk_results[][__arraycount(rtlnk_states)] = {
+       [C_ACCEPT]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_BIND]                = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_CONNECT]             = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_GETPEERNAME]         = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_GETSOCKNAME]         = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_GETSOCKOPT_ERR]      = {
+               0,              0,              0,              0,
+       },
+       [C_GETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+       },
+       [C_GETSOCKOPT_RB]       = {
+               -ENOPROTOOPT,   -ENOPROTOOPT,   -ENOPROTOOPT,   -ENOPROTOOPT,
+       },
+       [C_IOCTL_NREAD]         = {
+               0,              0,              0,              0,
+       },
+       [C_LISTEN]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,    -EOPNOTSUPP,
+       },
+       [C_RECV]                = {
+               -EOPNOTSUPP,    0,              -EOPNOTSUPP,    0,
+       },
+       [C_RECVFROM]            = {
+               -EOPNOTSUPP,    0,              -EOPNOTSUPP,    0,
+       },
+       [C_SEND]                = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EPIPE,         -EPIPE,
+       },
+       [C_SENDTO]              = {
+               -EOPNOTSUPP,    -EOPNOTSUPP,    -EPIPE,         -EPIPE,
+       },
+       [C_SELECT_R]            = {
+               1,              1,              1,              1,
+       },
+       [C_SELECT_W]            = {
+               1,              1,              1,              1,
+       },
+       [C_SELECT_X]            = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_BC]       = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_KA]       = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_L]        = {
+               0,              0,              0,              0,
+       },
+       [C_SETSOCKOPT_RA]       = {
+               0,              0,              0,              0,
+       },
+       [C_SHUTDOWN_R]          = {
+               0,              0,              0,              0,
+       },
+       [C_SHUTDOWN_RW]         = {
+               0,              0,              0,              0,
+       },
+       [C_SHUTDOWN_W]          = {
+               0,              0,              0,              0,
+       },
+};
+
+/*
+ * Set up a routing or link socket file descriptor in the requested state and
+ * pass it to socklib_sweep_call() along with local and remote addresses and
+ * their lengths.
+ */
+static int
+rtlnk_sweep(int domain, int type, int protocol, enum state state,
+       enum call call)
+{
+       struct sockaddr sa;
+       int r, fd;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_family = domain;
+
+       if ((fd = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0);
+
+       switch (state) {
+       case S_NEW: break;
+       case S_N_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break;
+       case S_N_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break;
+       case S_N_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break;
+       default: e(0);
+       }
+
+       r = socklib_sweep_call(call, fd, &sa, &sa,
+           offsetof(struct sockaddr, sa_data));
+
+       if (close(fd) != 0) e(0);
+
+       return r;
+}
+
+/*
+ * Sweep test for socket calls versus socket states of routing and link
+ * sockets.
+ */
+static void
+test93a(void)
+{
+
+       subtest = 1;
+
+       socklib_sweep(AF_ROUTE, SOCK_RAW, 0, rtlnk_states,
+           __arraycount(rtlnk_states), (const int *)rt_results, rtlnk_sweep);
+
+       /*
+        * Our implementation of link sockets currently serves only one
+        * purpose, and that is to pass on ioctl() calls issued on the socket.
+        * As such, the results here are not too important.  The test mostly
+        * ensures that all calls actually complete--for example, that there is
+        * no function pointer NULL check missing in libsockevent.
+        */
+       socklib_sweep(AF_LINK, SOCK_DGRAM, 0, rtlnk_states,
+           __arraycount(rtlnk_states), (const int *)lnk_results, rtlnk_sweep);
+}
+
+/*
+ * Attempt to destroy the test loopback interface.  Return 0 if destruction was
+ * successful, or -1 if no such interface existed.
+ */
+static int
+test93_destroy_if(void)
+{
+       struct ifreq ifr;
+       int r, fd;
+
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, TEST_IFNAME, sizeof(ifr.ifr_name));
+
+       r = ioctl(fd, SIOCIFDESTROY, &ifr);
+       if (r != 0 && (r != -1 || errno != ENXIO)) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       return r;
+}
+
+/*
+ * Destroy the test interface at exit.  It is always safe to do so as its name
+ * is sufficiently unique, and we do not want to leave it around.
+ */
+static void
+test93_destroy_if_atexit(void)
+{
+       static int atexit_set = 0;
+
+       if (!atexit_set) {
+               (void)test93_destroy_if();
+
+               atexit_set = 1;
+       }
+}
+
+/*
+ * Attempt to create a test loopback interface.  Return 0 if creation was
+ * successful, or -1 if no more interfaces could be created.
+ */
+static int
+test93_create_if(void)
+{
+       struct ifreq ifr;
+       int r, fd;
+
+       (void)test93_destroy_if();
+
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, TEST_IFNAME, sizeof(ifr.ifr_name));
+
+       r = ioctl(fd, SIOCIFCREATE, &ifr);
+       if (r != 0 && (r != -1 || errno != ENOBUFS)) e(0);
+
+       if (close(fd) != 0) e(0);
+
+       atexit(test93_destroy_if_atexit);
+
+       return r;
+}
+
+/*
+ * Set the interface-up value for an interface to the given boolean value.
+ */
+static void
+test93_set_if_up(const char * ifname, int up)
+{
+       struct ifreq ifr;
+       int fd;
+
+       if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, TEST_IFNAME, sizeof(ifr.ifr_name));
+
+       if (ioctl(fd, SIOCGIFFLAGS, &ifr) != 0) e(0);
+
+       if (up)
+               ifr.ifr_flags |= IFF_UP;
+       else
+               ifr.ifr_flags &= ~IFF_UP;
+
+       if (ioctl(fd, SIOCSIFFLAGS, &ifr) != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Construct an IPv6 network mask for a certain prefix length.
+ */
+static void
+test93_make_netmask6(struct sockaddr_in6 * sin6, unsigned int prefix)
+{
+       unsigned int byte, bit;
+
+       if (prefix > 128) e(0);
+       memset(sin6, 0, sizeof(*sin6));
+       sin6->sin6_family = AF_INET6;
+
+       byte = prefix / NBBY;
+       bit = prefix % NBBY;
+
+       if (byte > 0)
+               memset(sin6->sin6_addr.s6_addr, 0xff, byte);
+       if (bit != 0)
+               sin6->sin6_addr.s6_addr[byte] = 0xff << (NBBY - bit);
+}
+
+/*
+ * Issue a modifying routing command, which must be one of RTM_ADD, RTM_CHANGE,
+ * RTM_DELETE, or RTM_LOCK.  The destination address (IPv4 or IPv6) and netmask
+ * prefix are required.  The flags (RTF_), interface name, and gateway are
+ * optional depending on the command (and flags) being issued.  Return 0 on
+ * success, and -1 with errno set on failure.
+ */
+static int
+test93_route_cmd(int cmd, const struct sockaddr * dest, socklen_t dest_len,
+       unsigned int prefix, int flags, const char * ifname,
+       const struct sockaddr * gw, socklen_t gw_len)
+{
+       static unsigned int seq = 0;
+       struct sockaddr_storage destss, maskss, ifpss, gwss;
+       struct sockaddr_in mask4;
+       struct sockaddr_in6 mask6;
+       struct sockaddr_dl ifp;
+       struct rt_msghdr rtm;
+       struct iovec iov[5];
+       struct msghdr msg;
+       unsigned int i, iovlen;
+       int r, fd, err;
+
+       memset(&rtm, 0, sizeof(rtm));
+       rtm.rtm_version = RTM_VERSION;
+       rtm.rtm_type = cmd;
+       rtm.rtm_flags = flags;
+       rtm.rtm_addrs = RTA_DST | RTA_NETMASK;
+       rtm.rtm_seq = ++seq;
+
+       iovlen = 0;
+       iov[iovlen].iov_base = &rtm;
+       iov[iovlen++].iov_len = sizeof(rtm);
+
+       memset(&destss, 0, sizeof(destss));
+       memcpy(&destss, dest, dest_len);
+       destss.ss_len = dest_len;
+
+       iov[iovlen].iov_base = &destss;
+       iov[iovlen++].iov_len = RT_ROUNDUP(dest_len);
+
+       /* Do this in RTA order. */
+       memset(&gwss, 0, sizeof(gwss));
+       if (gw != NULL) {
+               memcpy(&gwss, gw, gw_len);
+               gwss.ss_len = gw_len;
+
+               rtm.rtm_addrs |= RTA_GATEWAY;
+               iov[iovlen].iov_base = &gwss;
+               iov[iovlen++].iov_len = RT_ROUNDUP(gwss.ss_len);
+       }
+
+       memset(&maskss, 0, sizeof(maskss));
+       switch (dest->sa_family) {
+       case AF_INET:
+               if (prefix > 32) e(0);
+               memset(&mask4, 0, sizeof(mask4));
+               mask4.sin_family = AF_INET;
+               if (prefix < 32)
+                       mask4.sin_addr.s_addr = htonl(0xffffffffUL << prefix);
+
+               memcpy(&maskss, &mask4, sizeof(mask4));
+               maskss.ss_len = sizeof(mask4);
+
+               break;
+
+       case AF_INET6:
+               test93_make_netmask6(&mask6, prefix);
+
+               memcpy(&maskss, &mask6, sizeof(mask6));
+               maskss.ss_len = sizeof(mask6);
+
+               break;
+
+       default:
+               e(0);
+       }
+
+       iov[iovlen].iov_base = &maskss;
+       iov[iovlen++].iov_len = RT_ROUNDUP(maskss.ss_len);
+
+       if (ifname != NULL) {
+               memset(&ifp, 0, sizeof(ifp));
+               ifp.sdl_nlen = strlen(ifname);
+               ifp.sdl_len = offsetof(struct sockaddr_dl, sdl_data) +
+                   ifp.sdl_nlen;
+               ifp.sdl_family = AF_LINK;
+
+               memset(&ifpss, 0, sizeof(ifpss));
+               memcpy(&ifpss, &ifp, ifp.sdl_len);
+               memcpy(&((struct sockaddr_dl *)&ifpss)->sdl_data, ifname,
+                   ifp.sdl_nlen);
+
+               rtm.rtm_addrs |= RTA_IFP;
+               iov[iovlen].iov_base = &ifpss;
+               iov[iovlen++].iov_len = RT_ROUNDUP(ifpss.ss_len);
+       }
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = iov;
+       msg.msg_iovlen = iovlen;
+
+       if ((fd = socket(AF_ROUTE, SOCK_RAW, 0)) < 0) e(0);
+
+       for (i = 0; i < iovlen; i++)
+               rtm.rtm_msglen += iov[i].iov_len;
+
+       r = sendmsg(fd, &msg, 0);
+       if (r != rtm.rtm_msglen && r != -1) e(0);
+       err = errno;
+
+       /*
+        * We could just shut down the socket for reading, but this is just an
+        * extra test we can do basically for free.
+        */
+       rtm.rtm_seq = 0;
+       do {
+               iov[0].iov_base = &rtm;
+               iov[0].iov_len = sizeof(rtm);
+
+               if (recvmsg(fd, &msg, 0) <= 0) e(0);
+       } while (rtm.rtm_pid != getpid() || rtm.rtm_seq != seq);
+
+       if (r == -1) {
+               if (rtm.rtm_errno != err) e(0);
+               if (rtm.rtm_flags & RTF_DONE) e(0);
+       } else {
+               if (rtm.rtm_errno != 0) e(0);
+               if (!(rtm.rtm_flags & RTF_DONE)) e(0);
+       }
+
+       if (close(fd) != 0) e(0);
+
+       errno = err;
+       return (r > 0) ? 0 : -1;
+}
+
+/*
+ * Add or delete an IPv6 address to or from an interface.  The interface name,
+ * address, and prefix length must always be given.  When adding, a set of
+ * flags (IN6_IFF) and lifetimes must be given as well.
+ */
+static void
+test93_ipv6_addr(int add, const char * ifname,
+       const struct sockaddr_in6 * sin6, unsigned int prefix, int flags,
+       uint32_t valid_life, uint32_t pref_life)
+{
+       struct in6_aliasreq ifra;
+       int fd;
+
+       memset(&ifra, 0, sizeof(ifra));
+       strlcpy(ifra.ifra_name, ifname, sizeof(ifra.ifra_name));
+       memcpy(&ifra.ifra_addr, sin6, sizeof(ifra.ifra_addr));
+       /* leave ifra_dstaddr blank */
+       test93_make_netmask6(&ifra.ifra_prefixmask, prefix);
+       ifra.ifra_flags = flags;
+       ifra.ifra_lifetime.ia6t_vltime = valid_life;
+       ifra.ifra_lifetime.ia6t_pltime = pref_life;
+
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       if (ioctl(fd, (add) ? SIOCAIFADDR_IN6 : SIOCDIFADDR_IN6, &ifra) != 0)
+               e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+static const struct {
+       int result; /* 0..2 = prefer srcN, -1 = no preference */
+       const char *dest_addr;
+       const char *src0_addr;
+       unsigned int src0_prefix;
+       int src0_flags;
+       const char *src1_addr;
+       unsigned int src1_prefix;
+       int src1_flags;
+       const char *src2_addr;
+       unsigned int src2_prefix;
+       int src2_flags;
+} test93b_table[] = {
+       /*
+        * These are all the applicable tests from RFC 6724 Sec. 10.1, slightly
+        * changed not to use the default link-local address of lo0.
+        */
+       /* Prefer appropriate scope: */
+       { 0, "2001:db8:1::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 },
+       /* Prefer appropriate scope: */
+       { 0, "ff05::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 },
+       /* Prefer same address: */
+       { 0, "2001:db8:1::1", "2001:db8:1::1", 64, IN6_IFF_DEPRECATED,
+           "2001:db8:2::1", 64, 0 },
+       /* Prefer appropriate scope: */
+       { 0, "fe80::93:1", "fe80::93:2", 64, IN6_IFF_DEPRECATED,
+           "2001:db8:2::1", 64, 0 },
+       /* Longest matching prefix: */
+       { 0, "2001:db8:1::1", "2001:db8:1::2", 64, 0, "2001:db8:3::2", 64, 0 },
+       /* Prefer matching label: */
+       { 0, "2002:c633:6401::1", "2002:c633:6401::d5e3:7953:13eb:22e8", 64,
+           IN6_IFF_TEMPORARY, "2001:db8:1::2", 64, 0 },
+       /* Prefer temporary address: */
+       { 1, "2001:db8:1::d5e3:0:0:1", "2001:db8:1::2", 64, 0,
+           "2001:db8:1::d5e3:7953:13eb:22e8", 64, IN6_IFF_TEMPORARY },
+       /*
+        * Our own additional tests.
+        */
+       /* Prefer same address: */
+       { 1, "4000:93::1", "2001:db8:3::1", 64, 0, "4000:93::1", 64, 0 },
+       { 2, "2001:db8:1::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0,
+           "2001:db8:1::1", 64, 0 },
+       /* Prefer appropriate scope: */
+       { 1, "ff01::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 },
+       { 1, "ff02::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 },
+       { 0, "ff0e::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 },
+       { 1, "fd00:93::1", "2001:db8:3::1", 64, 0, "fd00::93:2", 64, 0 },
+       { 1, "fd00:93::1", "fe80::93:1", 64, 0, "fd00::93:2", 64, 0 },
+       { 0, "fd00:93::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 },
+       { 1, "2001:db8:1::1", "fe80::93:1", 64, 0, "fd00::93:2", 64, 0 },
+       { 0, "2001:db8:1::1", "2001:db8:3::1", 64, 0, "4000:93::1", 64, 0 },
+       { 0, "4000:93::2", "2001:db8:3::1", 64, 0, "4000:93::1", 64, 0 },
+       { 2, "2001:db8:1::1", "fe80::93:1", 64, 0, "fd00::93:1", 64, 0,
+           "2001:db8:3::1", 64, 0 },
+       { 2, "2001:db8:1::1", "fe80::93:1", 64, IN6_IFF_DEPRECATED,
+           "fe80::93:2", 64, 0, "2001:db8:3::1", 64, 0 },
+       /* Avoid deprecated address: */
+       { 1, "2002:c633:6401::1", "2002:c633:6401::d5e3:7953:13eb:22e8", 64,
+           IN6_IFF_DEPRECATED, "2001:db8:1::2", 64, 0 },
+       { 2, "2001:db8:1::1", "2001:db8:1::3", 64, IN6_IFF_DEPRECATED,
+           "2001:db8:2::1", 64, IN6_IFF_DEPRECATED, "2001:db8:3::1", 64, 0 },
+       { 2, "2001:db8:1::1", "2002:db8:1::3", 64, IN6_IFF_DEPRECATED,
+           "2001:db8:2::1", 64, IN6_IFF_DEPRECATED, "2001:db8:3::1", 64, 0 },
+       /* Prefer matching label: */
+       { 0, "2002:c633:6401::1", "2002:c633:6401::d5e3:7953:13eb:22e8", 64, 0,
+           "2001:db8:1::2", 64, IN6_IFF_TEMPORARY },
+       { 2, "2002:c633:6401::1", "2001:db8:3::2", 64, 0, "2001:db8:1::2", 64,
+           IN6_IFF_TEMPORARY, "2002:c633:6401::d5e3:7953:13eb:22e8", 64, 0 },
+       { 2, "2001:db8:1::1", "2003:db8::1", 64, 0, "3ffe:db8::1", 64, 0,
+           "2001:db8:3::1", 64, 0 },
+       /* Prefer temporary address: */
+       { 0, "2001:db8:1::d5e3:0:0:1", "2001:db8:1::2", 96, IN6_IFF_TEMPORARY,
+           "2001:db8:1::d5e3:7953:13eb:22e8", 96, 0 },
+       { 2, "2002:c633:6401::1", "2001:db8:3::2", 64, 0, "2002:c633:6401::2",
+           64, 0, "2002:c633:6401::d5e3:7953:13eb:22e8", 64,
+           IN6_IFF_TEMPORARY },
+       /* Longest matching prefix: */
+       { 1, "2001:db8:1::d5e3:0:0:1", "2001:db8:1::2", 96, 0,
+           "2001:db8:1::d5e3:7953:13eb:22e8", 96, 0 },
+       { 2, "2001:db8:1:1::1", "2001:db8:2:1::2", 64, 0, "2001:db8:1:2::2",
+           64, 0, "2001:db8:1:1::2", 64, 0 },
+       { 0, "2001:db8:1::1", "2001:db8:1::2", 47, 0, "2001:db8:3::2", 47, 0 },
+       /* No preference (a tie): */
+       { -1, "2001:db8:1::1", "2001:db8:1::2", 46, 0, "2001:db8:3::2", 46,
+           0 },
+       { -1, "2001:db8::1:0:0:1", "2001:db8::1:0:0:2", 64, 0,
+           "2001:db8::2:0:0:2", 64, 0, "2001:db8::3:0:0:2", 64, 0 },
+};
+
+struct src_addr {
+       struct sockaddr_in6 addr;
+       unsigned int prefix;
+       int flags;
+};
+
+/*
+ * Test source address selection with a particular destination address and two
+ * or three source addresses.
+ */
+static void
+sub93b(int result, const struct sockaddr_in6 * dest, unsigned int ifindex,
+       const struct src_addr * src0, const struct src_addr * src1,
+       const struct src_addr * src2)
+{
+       struct sockaddr_in6 dest_copy, src;
+       socklen_t len;
+       int fd, rt_res;
+
+       /* Add the candidate source addresses. */
+       test93_ipv6_addr(1, TEST_IFNAME, &src0->addr, src0->prefix,
+           src0->flags, 0xffffffffUL, 0xffffffffUL);
+
+       test93_ipv6_addr(1, TEST_IFNAME, &src1->addr, src1->prefix,
+           src1->flags, 0xffffffffUL, 0xffffffffUL);
+
+       if (src2 != NULL)
+               test93_ipv6_addr(1, TEST_IFNAME, &src2->addr, src2->prefix,
+                   src2->flags, 0xffffffffUL, 0xffffffffUL);
+
+       /*
+        * We need to make sure that packets to the destination are routed to
+        * our test interface at all, so create a route for it.  Creating the
+        * route may fail if the destination address is equal to either of the
+        * source addresses, but that is fine.  We use a blackhole route here,
+        * but this test should not generate any traffic anyway.
+        */
+       rt_res = test93_route_cmd(RTM_ADD, (struct sockaddr *)dest,
+           sizeof(*dest), 128, RTF_UP | RTF_BLACKHOLE | RTF_STATIC,
+           TEST_IFNAME, NULL, 0);
+       if (rt_res != 0 && (rt_res != -1 || errno != EEXIST)) e(0);
+
+       if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       /* Set a scope ID if necessary. */
+       memcpy(&dest_copy, dest, sizeof(dest_copy));
+       dest_copy.sin6_port = 1; /* anything that is not zero */
+       if (IN6_IS_ADDR_LINKLOCAL(&dest_copy.sin6_addr) ||
+           IN6_IS_ADDR_MC_NODELOCAL(&dest_copy.sin6_addr) ||
+           IN6_IS_ADDR_MC_LINKLOCAL(&dest_copy.sin6_addr))
+               dest_copy.sin6_scope_id = ifindex;
+
+       /* Connecting also selects a source address. */
+       if (connect(fd, (struct sockaddr *)&dest_copy, sizeof(dest_copy)) != 0)
+               e(0);
+
+       /* Obtain the selected source address. */
+       len = sizeof(src);
+       if (getsockname(fd, (struct sockaddr *)&src, &len) != 0) e(0);
+
+       /*
+        * If the chosen destination address has a scope ID, it must be for our
+        * test interface.
+        */
+       if (src.sin6_scope_id != 0 && src.sin6_scope_id != ifindex) e(0);
+
+       /* Is it the expected candidate source address? */
+       if (!memcmp(&src.sin6_addr, &src0->addr.sin6_addr,
+           sizeof(src.sin6_addr))) {
+               if (result != 0) e(0);
+       } else if (!memcmp(&src.sin6_addr, &src1->addr.sin6_addr,
+           sizeof(src.sin6_addr))) {
+               if (result != 1) e(0);
+       } else if (src2 != NULL && !memcmp(&src.sin6_addr,
+           &src2->addr.sin6_addr, sizeof(src.sin6_addr))) {
+               if (result != 2) e(0);
+       } else
+               e(0);
+
+       /* Clean up. */
+       if (close(fd) != 0) e(0);
+
+       if (rt_res == 0) {
+               if (test93_route_cmd(RTM_DELETE, (struct sockaddr *)dest,
+                   sizeof(*dest), 128, 0, NULL, NULL, 0) != 0) e(0);
+       }
+
+       if (src2 != NULL)
+               test93_ipv6_addr(0, TEST_IFNAME, &src2->addr, src2->prefix, 0,
+                   0, 0);
+
+       test93_ipv6_addr(0, TEST_IFNAME, &src1->addr, src1->prefix, 0, 0, 0);
+
+       test93_ipv6_addr(0, TEST_IFNAME, &src0->addr, src0->prefix, 0, 0, 0);
+}
+
+/*
+ * IPv6 source address selection algorithm test.
+ */
+static void
+test93b(void)
+{
+       static const int order[][3] = {
+               { 0, 1, 2 },
+               { 1, 0, 2 },
+               { 0, 2, 1 },
+               { 1, 2, 0 },
+               { 2, 0, 1 },
+               { 2, 1, 0 }
+       };
+       struct sockaddr_in6 dest;
+       struct src_addr src[3];
+       unsigned int i, j, k, count, ifindex;
+       int result;
+
+       subtest = 2;
+
+       if (test93_create_if() != 0)
+               return; /* skip this test */
+
+       if ((ifindex = if_nametoindex(TEST_IFNAME)) == 0) e(0);
+
+       test93_set_if_up(TEST_IFNAME, 1);
+
+       for (i = 0; i < __arraycount(test93b_table); i++) {
+               memset(&dest, 0, sizeof(dest));
+               dest.sin6_family = AF_INET6;
+               if (inet_pton(AF_INET6, test93b_table[i].dest_addr,
+                   &dest.sin6_addr) != 1) e(0);
+
+               memset(&src[0].addr, 0, sizeof(src[0].addr));
+               src[0].addr.sin6_family = AF_INET6;
+               if (inet_pton(AF_INET6, test93b_table[i].src0_addr,
+                   &src[0].addr.sin6_addr) != 1) e(0);
+               src[0].prefix = test93b_table[i].src0_prefix;
+               src[0].flags = test93b_table[i].src0_flags;
+
+               memset(&src[1].addr, 0, sizeof(src[1].addr));
+               src[1].addr.sin6_family = AF_INET6;
+               if (inet_pton(AF_INET6, test93b_table[i].src1_addr,
+                   &src[1].addr.sin6_addr) != 1) e(0);
+               src[1].prefix = test93b_table[i].src1_prefix;
+               src[1].flags = test93b_table[i].src1_flags;
+
+               if (test93b_table[i].src2_addr != NULL) {
+                       memset(&src[2].addr, 0, sizeof(src[2].addr));
+                       src[2].addr.sin6_family = AF_INET6;
+                       if (inet_pton(AF_INET6, test93b_table[i].src2_addr,
+                           &src[2].addr.sin6_addr) != 1) e(0);
+                       src[2].prefix = test93b_table[i].src2_prefix;
+                       src[2].flags = test93b_table[i].src2_flags;
+
+                       count = 6;
+               } else
+                       count = 2;
+
+               result = test93b_table[i].result;
+
+               /*
+                * Try all orders for the source addresses.  The permutation
+                * part can be done much better, but it really does not matter.
+                */
+               for (j = 0; j < count; j++) {
+                       for (k = 0; k < count; k++)
+                               if (result == -1 || order[j][k] == result)
+                                       break;
+
+                       sub93b((result != -1) ? k : 0, &dest, ifindex,
+                           &src[order[j][0]], &src[order[j][1]],
+                           (count > 2) ? &src[order[j][2]] : NULL);
+               }
+       }
+
+       if (test93_destroy_if() != 0) e(0);
+}
+
+/*
+ * Interface index number wrapping test.
+ */
+static void
+test93c(void)
+{
+       unsigned int i;
+
+       subtest = 3;
+
+       /* There might not be an available loopback interface at all. */
+       if (test93_create_if() != 0)
+               return; /* skip this test */
+
+       if (test93_destroy_if() != 0) e(0);
+
+       /*
+        * During the development of the LWIP service, the lwIP library's
+        * interface index assignment was still in its infancy.  This test aims
+        * to ensure that future changes in the library do not break our
+        * service.
+        */
+       for (i = 0; i < UINT8_MAX + 1; i++) {
+               if (test93_create_if() != 0) e(0);
+
+               if (test93_destroy_if() != 0) e(0);
+       }
+}
+
+/*
+ * Test program for LWIP interface and routing management.
+ */
+int
+main(int argc, char ** argv)
+{
+       int i, m;
+
+       start(93);
+
+       if (argc == 2)
+               m = atoi(argv[1]);
+       else
+               m = 0xFF;
+
+       for (i = 0; i < ITERATIONS; i++) {
+               if (m & 0x01) test93a();
+               if (m & 0x02) test93b();
+               if (m & 0x04) test93c();
+       }
+
+       quit();
+       /* NOTREACHED */
+}
diff --git a/minix/tests/test94.c b/minix/tests/test94.c
new file mode 100644 (file)
index 0000000..0587b76
--- /dev/null
@@ -0,0 +1,2650 @@
+/* Tests for BPF devices (LWIP) - by D.C. van Moolenbroek */
+/* This test needs to be run as root: opening BPF devices is root-only. */
+/*
+ * We do not attempt to test the BPF filter code here.  Such a test is better
+ * done through standardized tests and with direct use of the filter code.
+ * The current BPF filter implementation has been run through the FreeBSD
+ * BPF filter regression tests (from their tools/regression/bpf/bpf_filter), of
+ * which only the last test (0084 - "Check very long BPF program") failed due
+ * to our lower and strictly enforced BPF_MAXINSNS value.  Future modifications
+ * of the BPF filter code should be tested against at least that test set.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+#include <net/bpf.h>
+#include <net/bpfdesc.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_ether.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <ifaddrs.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+
+#include "common.h"
+
+#define ITERATIONS     2
+
+#define LOOPBACK_IFNAME        "lo0"
+
+#define TEST_PORT_A    12345
+#define TEST_PORT_B    12346
+
+#define SLEEP_TIME     250000  /* (us) - increases may require code changes */
+
+#define NONROOT_USER   "bin"   /* name of any unprivileged user */
+
+#ifdef NO_INET6
+const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
+#endif /* NO_INET6 */
+
+static unsigned int got_signal;
+
+/*
+ * Signal handler.
+ */
+static void
+test94_signal(int sig)
+{
+
+       if (sig != SIGUSR1) e(0);
+
+       got_signal++;
+}
+
+/*
+ * Send UDP packets on the given socket 'fd' so as to fill up a BPF store
+ * buffer of size 'size' exactly.  The provided buffer 'buf' may be used for
+ * packet generation and is at least of 'size' bytes.  Return the number of
+ * packets sent.
+ */
+static uint32_t
+test94_fill_exact(int fd, uint8_t * buf, size_t size, uint32_t seq)
+{
+       size_t hdrlen, len;
+
+       hdrlen = BPF_WORDALIGN(sizeof(struct bpf_hdr)) + sizeof(struct ip) +
+           sizeof(struct udphdr) + sizeof(seq);
+
+       for (len = 16; len <= hdrlen; len <<= 1);
+       if (len > size) e(0);
+
+       hdrlen = BPF_WORDALIGN(hdrlen - sizeof(seq));
+
+       for (; size > 0; seq++) {
+               memset(buf, 'Y', len - hdrlen);
+               if (len - hdrlen > sizeof(seq))
+                       buf[sizeof(seq)] = 'X';
+               buf[len - hdrlen - 1] = 'Z';
+               memcpy(buf, &seq, sizeof(seq));
+
+               if (write(fd, buf, len - hdrlen) != len - hdrlen) e(0);
+
+               size -= len;
+       }
+
+       return seq;
+}
+
+/*
+ * Send UDP packets on the given socket 'fd' so as to fill up at least a BPF
+ * store buffer of size 'size', with at least one more packet being sent.  The
+ * provided buffer 'buf' may be used for packet generation and is at least of
+ * 'size' bytes.
+ */
+static void
+test94_fill_random(int fd, uint8_t * buf, size_t size)
+{
+       size_t hdrlen, len;
+       ssize_t left;
+       uint32_t seq;
+
+       hdrlen = BPF_WORDALIGN(BPF_WORDALIGN(sizeof(struct bpf_hdr)) +
+           sizeof(struct ip) + sizeof(struct udphdr));
+
+       /* Even if we fill the buffer exactly, we send one more packet. */
+       for (left = (ssize_t)size, seq = 1; left >= 0; seq++) {
+               len = hdrlen + sizeof(seq) + lrand48() % (size / 10);
+
+               memset(buf, 'Y', len - hdrlen);
+               if (len - hdrlen > sizeof(seq))
+                       buf[sizeof(seq)] = 'X';
+               buf[len - hdrlen - 1] = 'Z';
+               memcpy(buf, &seq, sizeof(seq));
+
+               if (write(fd, buf, len - hdrlen) != len - hdrlen) e(0);
+
+               left -= BPF_WORDALIGN(len);
+       }
+}
+
+/*
+ * Send a UDP packet with a specific size of 'size' bytes and sequence number
+ * 'seq' on socket 'fd', using 'buf' as scratch buffer.
+ */
+static void
+test94_add_specific(int fd, uint8_t * buf, size_t size, uint32_t seq)
+{
+
+       size += sizeof(seq);
+
+       memset(buf, 'Y', size);
+       if (size > sizeof(seq))
+               buf[sizeof(seq)] = 'X';
+       buf[size - 1] = 'Z';
+       memcpy(buf, &seq, sizeof(seq));
+
+       if (write(fd, buf, size) != size) e(0);
+}
+
+/*
+ * Send a randomly sized, relatively small UDP packet on the given socket 'fd',
+ * using sequence number 'seq'.  The buffer 'buf' may be used as scratch buffer
+ * which is at most 'size' bytes--the same size as the total BPF buffer.
+ */
+static void
+test94_add_random(int fd, uint8_t * buf, size_t size, uint32_t seq)
+{
+
+       test94_add_specific(fd, buf, lrand48() % (size / 10), seq);
+}
+
+/*
+ * Check whether the packet in 'buf' of 'caplen' captured bytes out of
+ * 'datalen' data bytes is one we sent.  If so, return an offset to the packet
+ * data.  If not, return a negative value.
+ */
+static ssize_t
+test94_check_pkt(uint8_t * buf, ssize_t caplen, ssize_t datalen)
+{
+       struct ip ip;
+       struct udphdr uh;
+
+       if (caplen < sizeof(ip))
+               return -1;
+
+       memcpy(&ip, buf, sizeof(ip));
+
+       if (ip.ip_v != IPVERSION)
+               return -1;
+       if (ip.ip_hl != sizeof(ip) >> 2)
+               return -1;
+       if (ip.ip_p != IPPROTO_UDP)
+               return -1;
+
+       if (caplen - sizeof(ip) < sizeof(uh))
+               return -1;
+
+       memcpy(&uh, buf + sizeof(ip), sizeof(uh));
+
+       if (uh.uh_sport != htons(TEST_PORT_A))
+               return -1;
+       if (uh.uh_dport != htons(TEST_PORT_B))
+               return -1;
+
+       if (datalen - sizeof(ip) != ntohs(uh.uh_ulen)) e(0);
+
+       return sizeof(ip) + sizeof(uh);
+}
+
+/*
+ * Check whether the capture in 'buf' of 'len' bytes looks like a valid set of
+ * captured packets.  The valid packets start from sequence number 'seq'; the
+ * next expected sequence number is returned.  If 'filtered' is set, there
+ * should be no other packets in the capture; otherwise, other packets are
+ * ignored.
+ */
+static uint32_t
+test94_check(uint8_t * buf, ssize_t len, uint32_t seq, int filtered,
+       uint32_t * caplen, uint32_t * datalen)
+{
+       struct bpf_hdr bh;
+       ssize_t off;
+       uint32_t nseq;
+
+       while (len > 0) {
+               /*
+                * We rely on the assumption that the last packet in the buffer
+                * is padded to alignment as well; if not, this check fails.
+                */
+               if (len < BPF_WORDALIGN(sizeof(bh))) e(0);
+
+               memcpy(&bh, buf, sizeof(bh));
+
+               /*
+                * The timestamp fields should be filled in.  The tests that
+                * use this function do not set a capture length below the
+                * packet length.  The header must be exactly as large as we
+                * expect: no small-size tricks (as NetBSD uses) and no
+                * unexpected extra padding.
+                */
+               if (bh.bh_tstamp.tv_sec == 0 && bh.bh_tstamp.tv_usec == 0)
+                    e(0);
+               if (caplen != NULL) {
+                       if (bh.bh_caplen != *caplen) e(0);
+                       if (bh.bh_datalen != *datalen) e(0);
+
+                       caplen++;
+                       datalen++;
+               } else
+                       if (bh.bh_datalen != bh.bh_caplen) e(0);
+               if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
+
+               if (bh.bh_hdrlen + BPF_WORDALIGN(bh.bh_caplen) > len) e(0);
+
+               buf += bh.bh_hdrlen;
+               len -= bh.bh_hdrlen;
+
+               if ((off = test94_check_pkt(buf, bh.bh_caplen,
+                   bh.bh_datalen)) < 0) {
+                       if (filtered) e(0);
+
+                       buf += BPF_WORDALIGN(bh.bh_caplen);
+                       len -= BPF_WORDALIGN(bh.bh_caplen);
+
+                       continue;
+               }
+
+               if (bh.bh_caplen < off + sizeof(seq)) e(0);
+
+               memcpy(&nseq, &buf[off], sizeof(nseq));
+
+               if (nseq != seq++) e(0);
+
+               off += sizeof(seq);
+               if (off < bh.bh_caplen) {
+                       /* If there is just one byte, it is 'Z'. */
+                       if (off < bh.bh_caplen && off < bh.bh_datalen - 1) {
+                               if (buf[off] != 'X') e(0);
+
+                               for (off++; off < bh.bh_caplen &&
+                                   off < bh.bh_datalen - 1; off++)
+                                       if (buf[off] != 'Y') e(0);
+                       }
+                       if (off < bh.bh_caplen && off == bh.bh_datalen - 1 &&
+                           buf[off] != 'Z') e(0);
+               }
+
+               buf += BPF_WORDALIGN(bh.bh_caplen);
+               len -= BPF_WORDALIGN(bh.bh_caplen);
+       }
+
+       return seq;
+}
+
+/*
+ * Filter program to ensure that the given (datalink-headerless) packet is an
+ * IPv4 UDP packet from port 12345 to port 12346.  Important: the 'k' value of
+ * the last instruction must be the accepted packet size, and is modified by
+ * some of the tests further down!
+ */
+static struct bpf_insn test94_filter[] = {
+       { BPF_LD+BPF_B+BPF_ABS, 0, 0, 0 },      /* is this an IPv4 header? */
+       { BPF_ALU+BPF_RSH+BPF_K, 0, 0, 4 },
+       { BPF_JMP+BPF_JEQ+BPF_K, 0, 7, 4 },
+       { BPF_LD+BPF_B+BPF_ABS, 0, 0, 9 },      /* is this a UDP packet? */
+       { BPF_JMP+BPF_JEQ+BPF_K, 0, 5, IPPROTO_UDP },
+       { BPF_LDX+BPF_B+BPF_MSH, 0, 0, 0 },
+       { BPF_LD+BPF_H+BPF_IND, 0, 0, 0 },      /* source port 12345? */
+       { BPF_JMP+BPF_JEQ+BPF_K, 0, 2, TEST_PORT_A },
+       { BPF_LD+BPF_H+BPF_IND, 0, 0, 2 },      /* destination port 12346? */
+       { BPF_JMP+BPF_JEQ+BPF_K, 1, 0, TEST_PORT_B },
+       { BPF_RET+BPF_K, 0, 0, 0 },             /* reject the packet */
+       { BPF_RET+BPF_K, 0, 0, (uint32_t)-1 },  /* accept the (whole) packet */
+};
+
+/*
+ * Set up a BPF device, a pair of sockets of which traffic will be captured on
+ * the BPF device, a buffer for capturing packets, and optionally a filter.
+ * If the given size is non-zero, use that as buffer size.  Return the BPF
+ * device's actual buffer size, which is also the size of 'buf'.
+ */
+static size_t
+test94_setup(int * fd, int * fd2, int * fd3, uint8_t ** buf, unsigned int size,
+       int set_filter)
+{
+       struct sockaddr_in sinA, sinB;
+       struct ifreq ifr;
+       struct bpf_program bf;
+       unsigned int dlt;
+
+       if ((*fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
+
+       if (size != 0 && ioctl(*fd, BIOCSBLEN, &size) != 0) e(0);
+
+       if (ioctl(*fd, BIOCGBLEN, &size) != 0) e(0);
+       if (size < 1024 || size > BPF_MAXBUFSIZE) e(0);
+
+       if ((*buf = malloc(size)) == NULL) e(0);
+
+       if (set_filter) {
+               /*
+                * Install a filter to improve predictability for the tests.
+                */
+               memset(&bf, 0, sizeof(bf));
+               bf.bf_len = __arraycount(test94_filter);
+               bf.bf_insns = test94_filter;
+               if (ioctl(*fd, BIOCSETF, &bf) != 0) e(0);
+       }
+
+       /* Bind to the loopback device. */
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
+       if (ioctl(*fd, BIOCSETIF, &ifr) != 0) e(0);
+
+       /*
+        * If the loopback device's data link type is not DLT_RAW, our filter
+        * and size calculations will not work.
+        */
+       if (ioctl(*fd, BIOCGDLT, &dlt) != 0) e(0);
+       if (dlt != DLT_RAW) e(0);
+
+       /* We use UDP traffic for our test packets. */
+       if ((*fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sinA, 0, sizeof(sinA));
+       sinA.sin_family = AF_INET;
+       sinA.sin_port = htons(TEST_PORT_A);
+       sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       if (bind(*fd2, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
+
+       memcpy(&sinB, &sinA, sizeof(sinB));
+       sinB.sin_port = htons(TEST_PORT_B);
+       if (connect(*fd2, (struct sockaddr *)&sinB, sizeof(sinB)) != 0) e(0);
+
+       if ((*fd3 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       if (bind(*fd3, (struct sockaddr *)&sinB, sizeof(sinB)) != 0) e(0);
+
+       if (connect(*fd3, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0);
+
+       return size;
+}
+
+/*
+ * Clean up resources allocated by test94_setup().
+ */
+static void
+test94_cleanup(int fd, int fd2, int fd3, uint8_t * buf)
+{
+
+       if (close(fd3) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       free(buf);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test reading packets from a BPF device, using regular mode.
+ */
+static void
+test94a(void)
+{
+       struct bpf_program bf;
+       struct timeval tv;
+       fd_set fds;
+       uint8_t *buf;
+       pid_t pid;
+       size_t size;
+       ssize_t len;
+       uint32_t seq;
+       int fd, fd2, fd3, status, bytes, fl;
+
+       subtest = 1;
+
+       size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
+           0 /*set_filter*/);
+
+       /*
+        * Test that a filled-up store buffer will be returned to a pending
+        * read call.  Perform this first test without a filter, to ensure that
+        * the default behavior is to accept all packets.  The side effect is
+        * that we may receive other loopback traffic as part of our capture.
+        */
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               usleep(SLEEP_TIME);
+
+               test94_fill_random(fd2, buf, size);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       len = read(fd, buf, size);
+
+       if (len < size * 3/4) e(0);
+       if (len > size) e(0);
+       test94_check(buf, len, 1 /*seq*/, 0 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /* Only the exact buffer size may be used in read calls. */
+       if (read(fd, buf, size - 1) != -1) e(0);
+       if (errno != EINVAL) e(0);
+       if (read(fd, buf, size + 1) != -1) e(0);
+       if (errno != EINVAL) e(0);
+       if (read(fd, buf, sizeof(struct bpf_hdr)) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       /*
+        * Install a filter to improve predictability for the remaining tests.
+        */
+       memset(&bf, 0, sizeof(bf));
+       bf.bf_len = __arraycount(test94_filter);
+       bf.bf_insns = test94_filter;
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       /*
+        * Next we want to test that an already filled-up buffer will be
+        * returned to a read call immediately.  We take the opportunity to
+        * test that filling the buffer will also wake up a blocked select
+        * call.  In addition, we test ioctl(FIONREAD).
+        */
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
+       if (FD_ISSET(fd, &fds)) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+       if (bytes != 0) e(0);
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               usleep(SLEEP_TIME);
+
+               test94_fill_random(fd2, buf, size);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+
+       if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       len = read(fd, buf, size);
+
+       if (len < size * 3/4) e(0);
+       if (len > size) e(0);
+       seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
+           NULL /*caplen*/, NULL /*datalen*/);
+
+       if (len != bytes) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /* There is one more packet in the store buffer at this point. */
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
+       if (FD_ISSET(fd, &fds)) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+       if (bytes != 0) e(0);
+
+       /*
+        * Next, we test whether read timeouts work, first checking that a
+        * timed-out read call returns any packets currently in the buffer.
+        * We use sleep and a signal as a crude way to test that the call was
+        * actually blocked until the timeout occurred.
+        */
+       got_signal = 0;
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               signal(SIGUSR1, test94_signal);
+
+               usleep(SLEEP_TIME);
+
+               test94_add_random(fd2, buf, size, seq + 1);
+
+               usleep(SLEEP_TIME);
+
+               if (got_signal != 0) e(0);
+               pause();
+               if (got_signal != 1) e(0);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       tv.tv_sec = 0;
+       tv.tv_usec = SLEEP_TIME * 3;
+       if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* two packets < 3/4 of the size */
+       if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq + 2) e(0);
+
+       if (kill(pid, SIGUSR1) != 0) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /*
+        * Next, see if a timed-out read will all buffers empty yields EAGAIN.
+        */
+       tv.tv_sec = 0;
+       tv.tv_usec = SLEEP_TIME;
+       if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
+
+       if (read(fd, buf, size) != -1) e(0);
+       if (errno != EAGAIN) e(0);
+
+       /*
+        * Verify that resetting the timeout to zero makes the call block
+        * forever (for short test values of "forever" anyway), because
+        * otherwise this may create a false illusion of correctness in the
+        * next test, for non-blocking calls.  As a side effect, this tests
+        * read call signal interruption, and ensures no partial results are
+        * returned in that case.
+        */
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               signal(SIGUSR1, test94_signal);
+
+               if (read(fd, buf, size) != -1) e(0);
+               if (errno != EINTR) e(0);
+
+               if (got_signal != 1) e(0);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       usleep(SLEEP_TIME * 2);
+
+       if (kill(pid, SIGUSR1) != 0) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /*
+        * Repeat the same test with a non-full, non-empty buffer, to ensure
+        * that interrupted reads do not return partial results.
+        */
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               signal(SIGUSR1, test94_signal);
+
+               if (read(fd, buf, size) != -1) e(0);
+               if (errno != EINTR) e(0);
+
+               if (got_signal != 1) e(0);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       usleep(SLEEP_TIME);
+
+       test94_add_random(fd2, buf, size, 2);
+
+       usleep(SLEEP_TIME);
+
+       if (kill(pid, SIGUSR1) != 0) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /*
+        * Test non-blocking reads with empty, full, and non-empty buffers.
+        * Against common sense, the last case should return whatever is in
+        * the buffer rather than EAGAIN, like immediate-mode reads would.
+        */
+       if ((fl = fcntl(fd, F_GETFL)) == -1) e(0);
+       if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* one packet < 3/4 of the size */
+       seq = test94_check(buf, len, 2 /*seq*/, 1 /*filtered*/,
+           NULL /*caplen*/, NULL /*datalen*/);
+
+       if (read(fd, buf, size) != -1) e(0);
+       if (errno != EAGAIN) e(0);
+
+       test94_fill_random(fd2, buf, size);
+
+       len = read(fd, buf, size);
+       if (len < size * 3/4) e(0);
+       if (len > size) e(0);
+       seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
+           NULL /*caplen*/, NULL /*datalen*/);
+
+       len = read(fd, buf, size);
+
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* one packet < 3/4 of the size */
+       if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq + 1) e(0);
+
+       if (fcntl(fd, F_SETFL, fl) != 0) e(0);
+
+       /*
+        * Test two remaining aspects of select(2): single-packet arrivals do
+        * not cause a wake-up, and the read timer has no effect.  The latter
+        * is a deliberate implementation choice where we diverge from NetBSD,
+        * because it requires keeping state in a way that violates the
+        * principle of system call independence.
+        */
+       tv.tv_sec = 0;
+       tv.tv_usec = SLEEP_TIME * 2;
+       if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               usleep(SLEEP_TIME);
+
+               test94_add_random(fd2, buf, size, 1);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       tv.tv_sec = 1;
+       tv.tv_usec = 0;
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       test94_cleanup(fd, fd2, fd3, buf);
+}
+
+/*
+ * Test reading packets from a BPF device, using immediate mode.
+ */
+static void
+test94b(void)
+{
+       struct timeval tv;
+       fd_set fds;
+       uint8_t *buf;
+       unsigned int val;
+       size_t size;
+       ssize_t len;
+       uint32_t seq;
+       pid_t pid;
+       int fd, fd2, fd3, bytes, status, fl;
+
+       subtest = 2;
+
+       size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
+           1 /*set_filter*/);
+
+       val = 1;
+       if (ioctl(fd, BIOCIMMEDIATE, &val) != 0) e(0);
+
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+       if (bytes != 0) e(0);
+
+       /*
+        * Ensure that if the hold buffer is full, an immediate-mode read
+        * returns the content of the hold buffer, even if the store buffer is
+        * not empty.
+        */
+       test94_fill_random(fd2, buf, size);
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+
+       len = read(fd, buf, size);
+       if (len < size * 3/4) e(0);
+       if (len > size) e(0);
+       seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
+           NULL /*caplen*/, NULL /*datalen*/);
+
+       if (len != bytes) e(0);
+
+       /*
+        * There is one packet left in the buffer.  In immediate mode, this
+        * packet should be returned immediately.
+        */
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* one packet < 3/4 of the size */
+       if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq + 1) e(0);
+
+       if (len != bytes) e(0);
+
+       /* The buffer is now empty again. */
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+       if (bytes != 0) e(0);
+
+       /*
+        * Immediate-mode reads may return multiple packets from the store
+        * buffer.
+        */
+       test94_add_random(fd2, buf, size, seq + 1);
+       test94_add_random(fd2, buf, size, seq + 2);
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* two packets < 3/4 of the size */
+       if (test94_check(buf, len, seq + 1, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq + 3) e(0);
+
+       if (len != bytes) e(0);
+
+       /*
+        * Now test waking up suspended calls, read(2) first.
+        */
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               usleep(SLEEP_TIME);
+
+               test94_add_random(fd2, buf, size, seq + 3);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* one packet < 3/4 of the size */
+       if (test94_check(buf, len, seq + 3, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq + 4) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /*
+        * Then select(2).
+        */
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               usleep(SLEEP_TIME);
+
+               test94_add_random(fd2, buf, size, seq + 4);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+
+       if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* one packet < 3/4 of the size */
+       if (test94_check(buf, len, seq + 4, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq + 5) e(0);
+
+       if (len != bytes) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /*
+        * Non-blocking reads should behave just as with regular mode.
+        */
+       if ((fl = fcntl(fd, F_GETFL)) == -1) e(0);
+       if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0);
+
+       if (read(fd, buf, size) != -1) e(0);
+       if (errno != EAGAIN) e(0);
+
+       test94_fill_random(fd2, buf, size);
+
+       len = read(fd, buf, size);
+       if (len < size * 3/4) e(0);
+       if (len > size) e(0);
+       seq = test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/,
+           NULL /*caplen*/, NULL /*datalen*/);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* one packet < 3/4 of the size */
+       if (test94_check(buf, len, seq, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq + 1) e(0);
+
+       if (fcntl(fd, F_SETFL, fl) != 0) e(0);
+
+       /*
+        * Timeouts should work with immediate mode.
+        */
+       tv.tv_sec = 0;
+       tv.tv_usec = SLEEP_TIME;
+       if (ioctl(fd, BIOCSRTIMEOUT, &tv) != 0) e(0);
+
+       if (read(fd, buf, size) != -1) e(0);
+       if (errno != EAGAIN) e(0);
+
+       test94_cleanup(fd, fd2, fd3, buf);
+}
+
+/*
+ * Test reading packets from a BPF device, with an exactly filled buffer.  The
+ * idea is that normally the store buffer is considered "full" if the next
+ * packet does not fit in it, but if no more bytes are left in it, it can be
+ * rotated immediately.  This is a practically useless edge case, but we
+ * support it, so we might as well test it.  Also, some of the code for this
+ * case is shared with other rare cases that we cannot test here (interfaces
+ * disappearing, to be specific), and exactly filling up the buffers does test
+ * some other bounds checks so all that might make this worth it anyway.  While
+ * we are exercising full control over our buffers, also check statistics.
+ */
+static void
+test94c(void)
+{
+       struct bpf_stat bs;
+       fd_set fds;
+       uint8_t *buf;
+       size_t size;
+       pid_t pid;
+       uint32_t count, seq;
+       int fd, fd2, fd3, bytes, status, fl;
+
+       subtest = 3;
+
+       size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
+           1 /*set_filter*/);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_capt != 0) e(0);
+       if (bs.bs_drop != 0) e(0);
+
+       /*
+        * Test read, select, and ioctl(FIONREAD) on an exactly filled buffer.
+        */
+       count = test94_fill_exact(fd2, buf, size, 0);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_capt != count) e(0);
+       if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
+       if (bs.bs_drop != 0) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+       if (bytes != size) e(0);
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       if (read(fd, buf, size) != size) e(0);
+       test94_check(buf, size, 0 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/);
+
+       /*
+        * If the store buffer is full, the buffers should be swapped after
+        * emptying the hold buffer.
+        */
+       seq = test94_fill_exact(fd2, buf, size, 1);
+       test94_fill_exact(fd2, buf, size, seq);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_capt != count * 3) e(0);
+       if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
+       if (bs.bs_drop != 0) e(0);
+
+       test94_add_random(fd2, buf, size, 0); /* this one will get dropped */
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_capt != count * 3 + 1) e(0);
+       if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
+       if (bs.bs_drop != 1) e(0);
+
+       test94_add_random(fd2, buf, size, 0); /* this one will get dropped */
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_capt != count * 3 + 2) e(0);
+       if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
+       if (bs.bs_drop != 2) e(0);
+
+       if (ioctl(fd, FIONREAD, &bytes) != 0) e(0);
+       if (bytes != size) e(0);
+
+       if (read(fd, buf, size) != size) e(0);
+       if (test94_check(buf, size, 1 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != seq) e(0);
+
+       if (read(fd, buf, size) != size) e(0);
+       if (test94_check(buf, size, seq, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != count * 2 + 1) e(0);
+
+       /*
+        * See if an exactly filled buffer resumes reads...
+        */
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               usleep(SLEEP_TIME);
+
+               test94_fill_exact(fd2, buf, size, 1);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       if (read(fd, buf, size) != size) e(0);
+       test94_check(buf, size, 1 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       /*
+        * ...and selects.
+        */
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               usleep(SLEEP_TIME);
+
+               test94_fill_exact(fd2, buf, size, seq);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       if ((fl = fcntl(fd, F_GETFL)) == -1) e(0);
+       if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0);
+
+       if (read(fd, buf, size) != size) e(0);
+       test94_check(buf, size, seq, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/);
+
+       if (read(fd, buf, size) != -1) e(0);
+       if (errno != EAGAIN) e(0);
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_capt != count * 5 + 2) e(0);
+       if (bs.bs_recv < bs.bs_capt) e(0); /* may be more */
+       if (bs.bs_drop != 2) e(0);
+
+       test94_cleanup(fd, fd2, fd3, buf);
+}
+
+/*
+ * Test receipt of large packets on BPF devices.  Large packets should be
+ * truncated to the size of the buffer, but unless the filter specifies a
+ * smaller capture size, no more than that.
+ */
+static void
+test94d(void)
+{
+       struct bpf_hdr bh;
+       uint8_t *buf, *buf2;
+       size_t size;
+       ssize_t len;
+       int fd, fd2, fd3, datalen;
+
+       subtest = 4;
+
+       /*
+        * Specify a size smaller than the largest packet we can send on the
+        * loopback device.  The size we specify here is currently the default
+        * size already anyway, but that might change in the future.
+        */
+       size = test94_setup(&fd, &fd2, &fd3, &buf, 32768 /*size*/,
+           1 /*set_filter*/);
+       if (size != 32768) e(0);
+
+       datalen = 65000;
+       if (setsockopt(fd2, SOL_SOCKET, SO_SNDBUF, &datalen,
+           sizeof(datalen)) != 0) e(0);
+
+       if ((buf2 = malloc(datalen)) == NULL) e(0);
+
+       memset(buf2, 'Y', datalen);
+       buf2[0] = 'X';
+       buf2[size - sizeof(struct udphdr) - sizeof(struct ip) -
+           BPF_WORDALIGN(sizeof(bh)) - 1] = 'Z';
+
+       if (write(fd2, buf2, datalen) != datalen) e(0);
+
+       if (read(fd, buf, size) != size) e(0);
+
+       memcpy(&bh, buf, sizeof(bh));
+
+       if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
+       if (bh.bh_caplen != size - BPF_WORDALIGN(sizeof(bh))) e(0);
+       if (bh.bh_datalen !=
+           sizeof(struct ip) + sizeof(struct udphdr) + datalen) e(0);
+
+       if (buf[BPF_WORDALIGN(sizeof(bh)) + sizeof(struct ip) +
+           sizeof(struct udphdr)] != 'X') e(0);
+       if (buf[size - 2] != 'Y') e(0);
+       if (buf[size - 1] != 'Z') e(0);
+
+       /*
+        * Add a smaller packet in between, to ensure that 1) the large packet
+        * is not split across buffers, and 2) the packet is truncated to the
+        * size of the buffer, not the available part of the buffer.  Note how
+        * forced rotation and our exact-fill policy preclude us from having to
+        * use immediate mode for any of this.
+        */
+       test94_add_random(fd2, buf, size, 1 /*seq*/);
+
+       if (write(fd2, buf2, datalen) != datalen) e(0);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+       if (len >= size * 3/4) e(0);    /* one packet < 3/4 of the size */
+       if (test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/, NULL /*caplen*/,
+           NULL /*datalen*/) != 2) e(0);
+
+       if (read(fd, buf, size) != size) e(0);
+
+       memcpy(&bh, buf, sizeof(bh));
+
+       if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
+       if (bh.bh_caplen != size - BPF_WORDALIGN(sizeof(bh))) e(0);
+       if (bh.bh_datalen !=
+           sizeof(struct ip) + sizeof(struct udphdr) + datalen) e(0);
+
+       if (buf[BPF_WORDALIGN(sizeof(bh)) + sizeof(struct ip) +
+           sizeof(struct udphdr)] != 'X') e(0);
+       if (buf[size - 2] != 'Y') e(0);
+       if (buf[size - 1] != 'Z') e(0);
+
+       free(buf2);
+
+       test94_cleanup(fd, fd2, fd3, buf);
+}
+
+/*
+ * Test whether our filter is active through two-way communication and a
+ * subsequent check on the BPF statistics.  We do not actually look through the
+ * captured packets, because who knows what else is active on the loopback
+ * device (e.g., X11) and the extra code specifically to extract our packets in
+ * the other direction is simply not worth it.
+ */
+static void
+test94_comm(int fd, int fd2, int fd3, int filtered)
+{
+       struct bpf_stat bs;
+       char c;
+
+       if (write(fd2, "A", 1) != 1) e(0);
+
+       if (read(fd3, &c, 1) != 1) e(0);
+       if (c != 'A') e(0);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv == 0) e(0);
+       if (bs.bs_capt == 0) e(0);
+
+       if (ioctl(fd, BIOCFLUSH) != 0) e(0);
+
+       if (write(fd3, "B", 1) != 1) e(0);
+
+       if (read(fd2, &c, 1) != 1) e(0);
+       if (c != 'B') e(0);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv == 0) e(0);
+
+       if (filtered) {
+               if (bs.bs_capt != 0) e(0);
+               if (bs.bs_drop != 0) e(0);
+       } else
+               if (bs.bs_capt == 0) e(0);
+
+       if (ioctl(fd, BIOCFLUSH) != 0) e(0);
+}
+
+/*
+ * Test filter installation and mechanics.
+ */
+static void
+test94e(void)
+{
+       struct bpf_program bf;
+       struct bpf_stat bs;
+       struct bpf_hdr bh;
+       uint8_t *buf;
+       size_t size, len, plen, alen, off;
+       uint32_t seq, caplen[4], datalen[4];
+       int i, fd, fd2, fd3, val;
+
+       subtest = 5;
+
+       /*
+        * We have already tested installing a filter both before and after
+        * attaching to an interface by now, so we do not repeat that here.
+        */
+       size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
+           0 /*set_filter*/);
+
+       val = 1;
+       if (ioctl(fd, BIOCIMMEDIATE, &val) != 0) e(0);
+
+       /*
+        * A filter that is too large is rejected.  Unfortunately, due to
+        * necessary IOCTL rewriting, this tests libc, not the service.
+        */
+       memset(&bf, 0, sizeof(bf));
+       bf.bf_len = BPF_MAXINSNS + 1;
+       bf.bf_insns = NULL;
+       if (ioctl(fd, BIOCSETF, &bf) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       /*
+        * An invalid filter is rejected.  In this test case, the truncated
+        * filter has a jump target beyond the end of the filter program.
+        */
+       memset(&bf, 0, sizeof(bf));
+       bf.bf_len = __arraycount(test94_filter) - 1;
+       bf.bf_insns = test94_filter;
+       if (ioctl(fd, BIOCSETF, &bf) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       test94_comm(fd, fd2, fd3, 0 /*filtered*/);
+
+       bf.bf_len++;
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       test94_comm(fd, fd2, fd3, 1 /*filtered*/);
+
+       /*
+        * Installing a zero-length filter clears the current filter, if any.
+        */
+       memset(&bf, 0, sizeof(bf));
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       test94_comm(fd, fd2, fd3, 0 /*filtered*/);
+
+       /* Test this twice to trip over unconditional filter deallocation. */
+       memset(&bf, 0, sizeof(bf));
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       test94_comm(fd, fd2, fd3, 0 /*filtered*/);
+
+       /*
+        * Test both aligned and unaligned capture sizes.  For each, test
+        * sizes larger than, equal to, and smaller than the capture size.
+        * In both cases, aggregate the packets into a single buffer and only
+        * then go through them, to see whether alignment was done correctly.
+        * We cannot do everything in one go as BIOCSETF implies a BIOCFLUSH.
+        */
+       plen = sizeof(struct ip) + sizeof(struct udphdr) + sizeof(seq);
+       if (BPF_WORDALIGN(plen) != plen) e(0);
+       alen = BPF_WORDALIGN(plen + 1);
+       if (alen - 2 <= plen + 1) e(0);
+
+       /* First the aligned cases. */
+       test94_filter[__arraycount(test94_filter) - 1].k = alen;
+
+       memset(&bf, 0, sizeof(bf));
+       bf.bf_len = __arraycount(test94_filter);
+       bf.bf_insns = test94_filter;
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       test94_comm(fd, fd2, fd3, 1 /*filtered*/);
+
+       test94_add_specific(fd2, buf, alen + 1 - plen, 1);
+       caplen[0] = alen;
+       datalen[0] = alen + 1;
+
+       test94_add_specific(fd2, buf, alen - plen, 2);
+       caplen[1] = alen;
+       datalen[1] = alen;
+
+       test94_add_specific(fd2, buf, alen + 3 - plen, 3);
+       caplen[2] = alen;
+       datalen[2] = alen + 3;
+
+       test94_add_specific(fd2, buf, alen - 1 - plen, 4);
+       caplen[3] = alen - 1;
+       datalen[3] = alen - 1;
+
+       memset(buf, 0, size);
+
+       len = read(fd, buf, size);
+
+       if (test94_check(buf, len, 1 /*seq*/, 1 /*filtered*/, caplen,
+           datalen) != 5) e(0);
+
+       /* Then the unaligned cases. */
+       test94_filter[__arraycount(test94_filter) - 1].k = alen + 1;
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       test94_add_specific(fd2, buf, alen + 2 - plen, 5);
+       caplen[0] = alen + 1;
+       datalen[0] = alen + 2;
+
+       test94_add_specific(fd2, buf, alen + 1 - plen, 6);
+       caplen[1] = alen + 1;
+       datalen[1] = alen + 1;
+
+       test94_add_specific(fd2, buf, alen + 9 - plen, 7);
+       caplen[2] = alen + 1;
+       datalen[2] = alen + 9;
+
+       test94_add_specific(fd2, buf, alen - plen, 8);
+       caplen[3] = alen;
+       datalen[3] = alen;
+
+       memset(buf, 0, size);
+
+       len = read(fd, buf, size);
+
+       if (test94_check(buf, len, 5 /*seq*/, 1 /*filtered*/, caplen,
+           datalen) != 9) e(0);
+
+       /*
+        * Check that capturing only one byte from packets is possible.  Not
+        * that that would be particularly useful.
+        */
+       test94_filter[__arraycount(test94_filter) - 1].k = 1;
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       test94_add_random(fd2, buf, size, 9);
+       test94_add_random(fd2, buf, size, 10);
+       test94_add_random(fd2, buf, size, 11);
+
+       memset(buf, 0, size);
+
+       len = read(fd, buf, size);
+       if (len <= 0) e(0);
+
+       off = 0;
+       for (i = 0; i < 3; i++) {
+               if (len - off < sizeof(bh)) e(0);
+               memcpy(&bh, &buf[off], sizeof(bh));
+
+               if (bh.bh_tstamp.tv_sec == 0 && bh.bh_tstamp.tv_usec == 0)
+                       e(0);
+               if (bh.bh_caplen != 1) e(0);
+               if (bh.bh_datalen < plen) e(0);
+               if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
+
+               off += bh.bh_hdrlen;
+
+               if (buf[off] != 0x45) e(0);
+
+               off += BPF_WORDALIGN(bh.bh_caplen);
+       }
+       if (off != len) e(0);
+
+       /*
+        * Finally, a zero capture size should result in rejected packets only.
+        */
+       test94_filter[__arraycount(test94_filter) - 1].k = 0;
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       test94_add_random(fd2, buf, size, 12);
+       test94_add_random(fd2, buf, size, 13);
+       test94_add_random(fd2, buf, size, 14);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv < 3) e(0);
+       if (bs.bs_capt != 0) e(0);
+       if (bs.bs_drop != 0) e(0);
+
+       /* Restore the capture limit of the filter to its original state. */
+       test94_filter[__arraycount(test94_filter) - 1].k = (uint32_t)-1;
+
+       test94_cleanup(fd, fd2, fd3, buf);
+}
+
+/*
+ * Compute an IP checksum.
+ */
+static uint16_t
+test94_cksum(uint8_t * buf, size_t len)
+{
+       uint32_t sum, word;
+
+       /* This is a really dumb implementation but *shrug*. */
+       for (sum = 0; len > 0; sum += word) {
+               if (len > 1) {
+                       word = buf[0] << 8 | buf[1];
+                       buf += 2;
+                       len -= 2;
+               } else {
+                       word = buf[0] << 8;
+                       len--;
+               }
+       }
+
+       while (sum > UINT16_MAX)
+               sum = (sum & UINT16_MAX) + (sum >> 16);
+
+       return ~(uint16_t)sum;
+}
+
+/*
+ * Set up UDP headers for a packet.  The packet uses IPv4 unless 'v6' is set,
+ * in which case IPv6 is used.  The given buffer must be large enough to
+ * contain the headers and the (to be appended) data.  The function returns the
+ * offset into the buffer to the data portion of the packet.
+ */
+static size_t
+test94_make_pkt(uint8_t * buf, size_t len, int v6)
+{
+       struct ip ip;
+       struct ip6_hdr ip6;
+       struct udphdr uh;
+       size_t off;
+
+       if (!v6) {
+               memset(&ip, 0, sizeof(ip));
+               ip.ip_v = IPVERSION;
+               ip.ip_hl = sizeof(ip) >> 2;
+               ip.ip_len = htons(sizeof(ip) + sizeof(uh) + len);
+               ip.ip_ttl = 255;
+               ip.ip_p = IPPROTO_UDP;
+               ip.ip_sum = 0;
+               ip.ip_src.s_addr = htonl(INADDR_LOOPBACK);
+               ip.ip_dst.s_addr = htonl(INADDR_LOOPBACK);
+
+               memcpy(buf, &ip, sizeof(ip));
+               ip.ip_sum = htons(test94_cksum(buf, sizeof(ip)));
+               memcpy(buf, &ip, sizeof(ip));
+               if (test94_cksum(buf, sizeof(ip)) != 0) e(0);
+
+               off = sizeof(ip);
+       } else {
+               memset(&ip6, 0, sizeof(ip6));
+               ip6.ip6_vfc = IPV6_VERSION;
+               ip6.ip6_plen = htons(sizeof(uh) + len);
+               ip6.ip6_nxt = IPPROTO_UDP;
+               ip6.ip6_hlim = 255;
+               memcpy(&ip6.ip6_src, &in6addr_loopback, sizeof(ip6.ip6_src));
+               memcpy(&ip6.ip6_dst, &in6addr_loopback, sizeof(ip6.ip6_dst));
+
+               memcpy(buf, &ip6, sizeof(ip6));
+
+               off = sizeof(ip6);
+       }
+
+       memset(&uh, 0, sizeof(uh));
+       uh.uh_sport = htons(TEST_PORT_A);
+       uh.uh_dport = htons(TEST_PORT_B);
+       uh.uh_ulen = htons(sizeof(uh) + len);
+       uh.uh_sum = 0; /* lazy but we also don't have the data yet */
+
+       memcpy(buf + off, &uh, sizeof(uh));
+
+       return off + sizeof(uh);
+}
+
+/*
+ * Test sending packets by writing to a BPF device.
+ */
+static void
+test94f(void)
+{
+       struct bpf_stat bs;
+       struct ifreq ifr;
+       fd_set fds;
+       uint8_t *buf;
+       size_t off;
+       unsigned int i, uval, mtu;
+       int fd, fd2, fd3;
+
+       subtest = 6;
+
+       (void)test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
+           1 /*set_filter*/);
+
+       /*
+        * Select queries should always indicate that the device is writable.
+        */
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, NULL, &fds, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       /*
+        * Test packet size limits.  For loopback devices, the maximum data
+        * link layer level maximum transmission unit should be 65535-4 =
+        * 65531 bytes.  Obtain the actual value anyway; it might have changed.
+        */
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
+
+       if (ioctl(fd2, SIOCGIFMTU, &ifr) != 0) e(0);
+       mtu = ifr.ifr_mtu;
+
+       if ((buf = realloc(buf, UINT16_MAX + 1)) == NULL) e(0);
+
+       memset(buf, 0, UINT16_MAX + 1);
+
+       for (i = UINT16_MAX + 1; i > mtu; i--) {
+               if (write(fd, buf, i) != -1) e(0);
+               if (errno != EMSGSIZE) e(0);
+       }
+
+       /* This packet will be discarded as completely crap.  That's fine. */
+       if (write(fd, buf, mtu) != mtu) e(0);
+
+       /*
+        * Zero-sized writes are accepted but do not do anything.
+        */
+       if (write(fd, buf, 0) != 0) e(0);
+
+       /*
+        * Send an actual packet, and see if it arrives.
+        */
+       off = test94_make_pkt(buf, 6, 0 /*v6*/);
+       memcpy(buf + off, "Hello!", 6);
+
+       if (write(fd, buf, off + 6) != off + 6) e(0);
+
+       memset(buf, 0, mtu);
+       if (read(fd3, buf, mtu) != 6) e(0);
+       if (memcmp(buf, "Hello!", 6) != 0) e(0);
+
+       /*
+        * Enable feedback mode to test that the packet now arrives twice.
+        * Send a somewhat larger packet to test that data copy-in handles
+        * offsets correctly.
+        */
+       uval = 1;
+       if (ioctl(fd, BIOCSFEEDBACK, &uval) != 0) e(0);
+
+       off = test94_make_pkt(buf, 12345, 0 /*v6*/);
+       for (i = 0; i < 12345; i++)
+               buf[off + i] = 1 + (i % 251); /* the largest prime < 255 */
+
+       if (write(fd, buf, off + 12345) != off + 12345) e(0);
+
+       /* We need a default UDP SO_RCVBUF >= 12345 * 2 for this. */
+       memset(buf, 0, UINT16_MAX);
+       if (recv(fd3, buf, UINT16_MAX, 0) != 12345) e(0);
+       for (i = 0; i < 12345; i++)
+               if (buf[i] != 1 + (i % 251)) e(0);
+
+       memset(buf, 0, UINT16_MAX);
+       if (recv(fd3, buf, UINT16_MAX, MSG_DONTWAIT) != 12345) e(0);
+       for (i = 0; i < 12345; i++)
+               if (buf[i] != 1 + (i % 251)) e(0);
+
+       if (recv(fd3, buf, UINT16_MAX, MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       /*
+        * The two valid packets we sent will have been captured by our BPF
+        * device as well, because SEESENT is enabled by default and also
+        * applies to packets written to a BPF device.  The reason for that is
+        * that it allows tcpdump(8) to see what DHCP clients are sending, for
+        * example.  The packets we sent are accepted by the installed filter.
+        */
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_capt != 2) e(0);
+
+       /* Now that we've written data, test select once more. */
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+       if (select(fd + 1, NULL, &fds, NULL, NULL) != 1) e(0);
+       if (!FD_ISSET(fd, &fds)) e(0);
+
+       test94_cleanup(fd, fd2, fd3, buf);
+}
+
+/*
+ * Test read, write, and select operations on unconfigured devices.
+ */
+static void
+test94g(void)
+{
+       fd_set rfds, wfds;
+       uint8_t *buf;
+       unsigned int size;
+       int fd;
+
+       subtest = 7;
+
+       if ((fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
+
+       if (ioctl(fd, BIOCGBLEN, &size) != 0) e(0);
+       if (size < 1024 || size > BPF_MAXBUFSIZE) e(0);
+
+       if ((buf = malloc(size)) == NULL) e(0);
+
+       if (read(fd, buf, size) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (write(fd, buf, size) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       FD_ZERO(&rfds);
+       FD_SET(fd, &rfds);
+       FD_ZERO(&wfds);
+       FD_SET(fd, &wfds);
+
+       if (select(fd + 1, &rfds, &wfds, NULL, NULL) != 2) e(0);
+
+       if (!FD_ISSET(fd, &rfds)) e(0);
+       if (!FD_ISSET(fd, &wfds)) e(0);
+
+       free(buf);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test various IOCTL calls.  Several of these tests are rather superficial,
+ * because we would need a real interface, rather than the loopback device, to
+ * test their functionality properly.  Also note that we skip various checks
+ * performed as part of the earlier subtests.
+ */
+static void
+test94h(void)
+{
+       struct bpf_stat bs;
+       struct bpf_version bv;
+       struct bpf_dltlist bfl;
+       struct ifreq ifr;
+       struct timeval tv;
+       uint8_t *buf;
+       size_t size;
+       unsigned int uval, list[2];
+       int cfd, ufd, fd2, fd3, val;
+
+       subtest = 8;
+
+       /*
+        * Many IOCTLs work only on configured or only on unconfigured BPF
+        * devices, so for convenience we create a file descriptor for each.
+        */
+       size = test94_setup(&cfd, &fd2, &fd3, &buf, 0 /*size*/,
+           1 /*set_filter*/);
+
+       if ((ufd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
+
+       /*
+        * The BIOCSBLEN value is silently corrected to fall within a valid
+        * range, and BIOCGBLEN can be used to obtain the corrected value.  We
+        * do not know the valid range, so we use fairly extreme test values.
+        */
+       uval = 1;
+       if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
+       if (uval < sizeof(struct bpf_hdr) || uval > BPF_MAXBUFSIZE) e(0);
+
+       uval = (unsigned int)-1;
+       if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
+       if (uval < sizeof(struct bpf_hdr) || uval > BPF_MAXBUFSIZE) e(0);
+
+       uval = 0;
+       if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
+       if (uval < sizeof(struct bpf_hdr) || uval > BPF_MAXBUFSIZE) e(0);
+
+       uval = 1024; /* ..a value that should be acceptable but small */
+       if (ioctl(ufd, BIOCSBLEN, &uval) != 0) e(0);
+       if (ioctl(ufd, BIOCGBLEN, &uval) != 0) e(0);
+       if (uval != 1024) e(0);
+
+       /*
+        * For configured devices, it is not possible to adjust the buffer size
+        * but it is possible to obtain its size.
+        */
+       if (ioctl(cfd, BIOCSBLEN, &uval) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (ioctl(cfd, BIOCGBLEN, &uval) != 0) e(0);
+       if (uval != size) e(0);
+
+       /*
+        * BIOCFLUSH resets both buffer contents and statistics.
+        */
+       uval = 1;
+       if (ioctl(cfd, BIOCIMMEDIATE, &uval) != 0) e(0);
+
+       test94_fill_exact(fd2, buf, size, 1 /*seq*/);
+       test94_fill_exact(fd2, buf, size, 1 /*seq*/);
+       test94_fill_exact(fd2, buf, size, 1 /*seq*/);
+
+       if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv == 0) e(0);
+       if (bs.bs_drop == 0) e(0);
+       if (bs.bs_capt == 0) e(0);
+
+       /* Do make sure that statistics are not cleared on retrieval.. */
+       if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv == 0) e(0);
+       if (bs.bs_drop == 0) e(0);
+       if (bs.bs_capt == 0) e(0);
+
+       if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
+       if (val == 0) e(0);
+
+       if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
+
+       /* There is a race condition for bs_recv here, so we cannot test it. */
+       if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_drop != 0) e(0);
+       if (bs.bs_capt != 0) e(0);
+
+       if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
+       if (val != 0) e(0);
+
+       /*
+        * Although practically useless, BIOCFLUSH works on unconfigured
+        * devices.  So does BIOCGSTATS.
+        */
+       if (ioctl(ufd, BIOCFLUSH) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv != 0) e(0);
+       if (bs.bs_drop != 0) e(0);
+       if (bs.bs_capt != 0) e(0);
+
+       /*
+        * BIOCPROMISC works on configured devices only.  On loopback devices
+        * it has no observable effect though.
+        */
+       if (ioctl(ufd, BIOCPROMISC) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (ioctl(cfd, BIOCPROMISC) != 0) e(0);
+
+       /*
+        * BIOCGDLT does not work on unconfigured devices.
+        */
+       if (ioctl(ufd, BIOCGDLT, &uval) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       /*
+        * BIOCGETIF works only on configured devices, where it returns the
+        * associated device name.
+        */
+       if (ioctl(ufd, BIOCGETIF, &ifr) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       memset(&ifr, 'X', sizeof(ifr));
+       if (ioctl(cfd, BIOCGETIF, &ifr) != 0) e(0);
+       if (strcmp(ifr.ifr_name, LOOPBACK_IFNAME) != 0) e(0);
+
+       /*
+        * BIOCSETIF works only on unconfigured devices, and accepts only valid
+        * valid interface names.  The name is forced to be null terminated.
+        */
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
+       if (ioctl(cfd, BIOCSETIF, &ifr) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       memset(&ifr, 0, sizeof(ifr));
+       memset(ifr.ifr_name, 'x', sizeof(ifr.ifr_name));
+       if (ioctl(ufd, BIOCSETIF, &ifr) != -1) e(0);
+       if (errno != ENXIO) e(0);
+
+       /* Anyone that has ten loopback devices is simply insane. */
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
+       ifr.ifr_name[strlen(ifr.ifr_name) - 1] += 9;
+       if (ioctl(ufd, BIOCSETIF, &ifr) != -1) e(0);
+       if (errno != ENXIO) e(0);
+
+       /*
+        * It is possible to turn BIOCIMMEDIATE on and off.  We already enabled
+        * it a bit higher up.  Note that our implementation does not support
+        * toggling the setting while a read call is no progress, and toggling
+        * the setting will have no effect while a select call is in progress;
+        * similar restrictions apply to effectively all relevant settings.
+        * Either way we do not test that here either.
+        */
+       test94_add_random(fd2, buf, size, 1 /*seq*/);
+
+       if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
+       if (val == 0) e(0);
+
+       uval = 0;
+       if (ioctl(cfd, BIOCIMMEDIATE, &uval) != 0) e(0);
+
+       if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
+       if (val != 0) e(0);
+
+       uval = 1;
+       if (ioctl(cfd, BIOCIMMEDIATE, &uval) != 0) e(0);
+
+       if (ioctl(cfd, FIONREAD, &val) != 0) e(0);
+       if (val == 0) e(0);
+
+       if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
+
+       /*
+        * BIOCIMMEDIATE also works on unconfigured devices.
+        */
+       uval = 1;
+       if (ioctl(ufd, BIOCIMMEDIATE, &uval) != 0) e(0);
+
+       uval = 0;
+       if (ioctl(ufd, BIOCIMMEDIATE, &uval) != 0) e(0);
+
+       /*
+        * BIOCVERSION should return the current BPF interface version.
+        */
+       if (ioctl(ufd, BIOCVERSION, &bv) != 0) e(0);
+       if (bv.bv_major != BPF_MAJOR_VERSION) e(0);
+       if (bv.bv_minor != BPF_MINOR_VERSION) e(0);
+
+       /*
+        * BIOCSHDRCMPLT makes sense only for devices with data link headers,
+        * which rules out loopback devices.  Check the default and test
+        * toggling it, and stop there.
+        */
+       /* The default value is off. */
+       uval = 1;
+       if (ioctl(ufd, BIOCGHDRCMPLT, &uval) != 0) e(0);
+       if (uval != 0) e(0);
+
+       uval = 2;
+       if (ioctl(ufd, BIOCSHDRCMPLT, &uval) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGHDRCMPLT, &uval) != 0) e(0);
+       if (uval != 1) e(0);
+
+       uval = 0;
+       if (ioctl(ufd, BIOCSHDRCMPLT, &uval) != 0) e(0);
+
+       uval = 1;
+       if (ioctl(ufd, BIOCGHDRCMPLT, &uval) != 0) e(0);
+       if (uval != 0) e(0);
+
+       /*
+        * BIOCSDLT works on configured devices.  For loopback devices, it can
+        * only set the data link type to its current value, which on MINIX3
+        * for loopback devices is DLT_RAW (i.e., no headers at all).
+        */
+       uval = DLT_RAW;
+       if (ioctl(ufd, BIOCSDLT, &uval) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       uval = DLT_RAW;
+       if (ioctl(cfd, BIOCSDLT, &uval) != 0) e(0);
+
+       uval = DLT_NULL;
+       if (ioctl(cfd, BIOCSDLT, &uval) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       if (ioctl(cfd, BIOCGDLT, &uval) != 0) e(0);
+       if (uval != DLT_RAW) e(0);
+
+       /*
+        * BIOCGDLTLIST works on configured devices only, and may be used to
+        * both query the size of the list and obtain the list.  On MINIX3,
+        * loopback devices will only ever return DLT_RAW.  Unfortunately,
+        * much of the handling for this IOCTL is in libc for us, which is also
+        * why we do not test bad pointers and stuff like that.
+        */
+       memset(&bfl, 0, sizeof(bfl));
+       if (ioctl(ufd, BIOCGDLTLIST, &bfl) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       memset(&bfl, 0, sizeof(bfl));
+       if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
+       if (bfl.bfl_len != 1) e(0);
+       if (bfl.bfl_list != NULL) e(0);
+
+       memset(&bfl, 0, sizeof(bfl));
+       bfl.bfl_len = 2;        /* should be ignored */
+       if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
+       if (bfl.bfl_len != 1) e(0);
+       if (bfl.bfl_list != NULL) e(0);
+
+       memset(&bfl, 0, sizeof(bfl));
+       memset(list, 0, sizeof(list));
+       bfl.bfl_list = list;
+       if (ioctl(cfd, BIOCGDLTLIST, &bfl) != -1) e(0);
+       if (errno != ENOMEM) e(0);
+       if (list[0] != 0) e(0);
+
+       memset(&bfl, 0, sizeof(bfl));
+       bfl.bfl_len = 1;
+       bfl.bfl_list = list;
+       if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
+       if (bfl.bfl_len != 1) e(0);
+       if (bfl.bfl_list != list) e(0);
+       if (list[0] != DLT_RAW) e(0);
+       if (list[1] != 0) e(0);
+
+       memset(&bfl, 0, sizeof(bfl));
+       memset(list, 0, sizeof(list));
+       bfl.bfl_len = 2;
+       bfl.bfl_list = list;
+       if (ioctl(cfd, BIOCGDLTLIST, &bfl) != 0) e(0);
+       if (bfl.bfl_len != 1) e(0);
+       if (bfl.bfl_list != list) e(0);
+       if (list[0] != DLT_RAW) e(0);
+       if (list[1] != 0) e(0);
+
+       /*
+        * For loopback devices, BIOCSSEESENT is a bit weird: packets are
+        * captured on output to get a complete view of loopback traffic, and
+        * not also on input because that would then duplicate the traffic.  As
+        * a result, turning off BIOCSSEESENT for a loopback device means that
+        * no packets will be captured at all anymore.  First test the default
+        * and toggling on the unconfigured device, then reproduce the above on
+        * the configured device.
+        */
+       /* The default value is on. */
+       uval = 0;
+       if (ioctl(ufd, BIOCGSEESENT, &uval) != 0) e(0);
+       if (uval != 1) e(0);
+
+       uval = 0;
+       if (ioctl(ufd, BIOCSSEESENT, &uval) != 0) e(0);
+
+       uval = 1;
+       if (ioctl(ufd, BIOCGSEESENT, &uval) != 0) e(0);
+       if (uval != 0) e(0);
+
+       uval = 2;
+       if (ioctl(ufd, BIOCSSEESENT, &uval) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGSEESENT, &uval) != 0) e(0);
+       if (uval != 1) e(0);
+
+       if (ioctl(cfd, BIOCGSEESENT, &uval) != 0) e(0);
+       if (uval != 1) e(0);
+
+       uval = 0;
+       if (ioctl(cfd, BIOCSSEESENT, &uval) != 0) e(0);
+
+       if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
+
+       test94_add_random(fd2, buf, size, 1 /*seq*/);
+
+       if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv != 0) e(0);
+
+       uval = 1;
+       if (ioctl(cfd, BIOCSSEESENT, &uval) != 0) e(0);
+
+       if (ioctl(cfd, BIOCFLUSH) != 0) e(0);
+
+       test94_add_random(fd2, buf, size, 1 /*seq*/);
+
+       if (ioctl(cfd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv == 0) e(0);
+
+       /*
+        * The BIOCSRTIMEOUT values are rounded up to clock granularity.
+        * Invalid timeout values are rejected.
+        */
+       /* The default value is zero. */
+       tv.tv_sec = 99;
+       if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
+       if (tv.tv_sec != 0) e(0);
+       if (tv.tv_usec != 0) e(0);
+
+       tv.tv_usec = 1000000;
+       if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       tv.tv_usec = -1;
+       if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       tv.tv_sec = -1;
+       tv.tv_usec = 0;
+       if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
+       if (errno != EINVAL) e(0);
+
+       tv.tv_sec = INT_MAX;
+       if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != -1) e(0);
+       if (errno != EDOM) e(0);
+
+       if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
+       if (tv.tv_sec != 0) e(0);
+       if (tv.tv_usec != 0) e(0);
+
+       tv.tv_sec = 123;
+       tv.tv_usec = 1;
+       if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
+       if (tv.tv_sec != 123) e(0);
+       if (tv.tv_usec == 0) e(0); /* rounding should be up */
+
+       tv.tv_sec = 0;
+       tv.tv_usec = 0;
+       if (ioctl(ufd, BIOCSRTIMEOUT, &tv) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGRTIMEOUT, &tv) != 0) e(0);
+       if (tv.tv_sec != 0) e(0);
+       if (tv.tv_usec != 0) e(0);
+
+       /*
+        * BIOCSFEEDBACK is another weird setting for which we only test
+        * default and toggling here.
+        */
+       /* The default value is off. */
+       uval = 1;
+       if (ioctl(ufd, BIOCGFEEDBACK, &uval) != 0) e(0);
+       if (uval != 0) e(0);
+
+       uval = 2;
+       if (ioctl(ufd, BIOCSFEEDBACK, &uval) != 0) e(0);
+
+       if (ioctl(ufd, BIOCGFEEDBACK, &uval) != 0) e(0);
+       if (uval != 1) e(0);
+
+       uval = 0;
+       if (ioctl(ufd, BIOCSFEEDBACK, &uval) != 0) e(0);
+
+       uval = 1;
+       if (ioctl(ufd, BIOCGFEEDBACK, &uval) != 0) e(0);
+       if (uval != 0) e(0);
+
+       /* Clean up. */
+       if (close(ufd) != 0) e(0);
+
+       test94_cleanup(cfd, fd2, fd3, buf);
+}
+
+/* IPv6 version of our filter. */
+static struct bpf_insn test94_filter6[] = {
+       { BPF_LD+BPF_B+BPF_ABS, 0, 0, 0 },      /* is this an IPv6 header? */
+       { BPF_ALU+BPF_RSH+BPF_K, 0, 0, 4 },
+       { BPF_JMP+BPF_JEQ+BPF_K, 0, 6, 6 },
+       { BPF_LD+BPF_B+BPF_ABS, 0, 0, 6 },      /* is this a UDP packet? */
+       { BPF_JMP+BPF_JEQ+BPF_K, 0, 4, IPPROTO_UDP },
+       { BPF_LD+BPF_H+BPF_ABS, 0, 0, 40 },     /* source port 12345? */
+       { BPF_JMP+BPF_JEQ+BPF_K, 0, 2, TEST_PORT_A },
+       { BPF_LD+BPF_H+BPF_ABS, 0, 0, 42 },     /* destination port 12346? */
+       { BPF_JMP+BPF_JEQ+BPF_K, 1, 0, TEST_PORT_B },
+       { BPF_RET+BPF_K, 0, 0, 0 },             /* reject the packet */
+       { BPF_RET+BPF_K, 0, 0, (uint32_t)-1 },  /* accept the (whole) packet */
+};
+
+/*
+ * Test receipt of IPv6 packets, because it was getting a bit messy to
+ * integrate that into the previous subtests.  We just want to make sure that
+ * IPv6 packets are properly filtered and captured at all.  The rest of the
+ * code is entirely version agnostic anyway.
+ */
+static void
+test94i(void)
+{
+       struct sockaddr_in6 sin6A, sin6B;
+       struct bpf_program bf;
+       struct bpf_stat bs;
+       struct bpf_hdr bh;
+       struct ifreq ifr;
+       struct ip6_hdr ip6;
+       struct udphdr uh;
+       uint8_t *buf, c;
+       socklen_t socklen;
+       ssize_t len;
+       size_t off;
+       unsigned int uval, size, dlt;
+       int fd, fd2, fd3;
+
+       subtest = 9;
+
+       if ((fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
+
+       if (ioctl(fd, BIOCGBLEN, &size) != 0) e(0);
+       if (size < 1024 || size > BPF_MAXBUFSIZE) e(0);
+
+       if ((buf = malloc(size)) == NULL) e(0);
+
+       /* Install the filter. */
+       memset(&bf, 0, sizeof(bf));
+       bf.bf_len = __arraycount(test94_filter6);
+       bf.bf_insns = test94_filter6;
+       if (ioctl(fd, BIOCSETF, &bf) != 0) e(0);
+
+       uval = 1;
+       if (ioctl(fd, BIOCIMMEDIATE, &uval) != 0) e(0);
+
+       /* Bind to the loopback device. */
+       memset(&ifr, 0, sizeof(ifr));
+       strlcpy(ifr.ifr_name, LOOPBACK_IFNAME, sizeof(ifr.ifr_name));
+       if (ioctl(fd, BIOCSETIF, &ifr) != 0) e(0);
+
+       /*
+        * If the loopback device's data link type is not DLT_RAW, our filter
+        * and size calculations will not work.
+        */
+       if (ioctl(fd, BIOCGDLT, &dlt) != 0) e(0);
+       if (dlt != DLT_RAW) e(0);
+
+       /* We use UDP traffic for our test packets. */
+       if ((fd2 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin6A, 0, sizeof(sin6A));
+       sin6A.sin6_family = AF_INET6;
+       sin6A.sin6_port = htons(TEST_PORT_A);
+       memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr));
+       if (bind(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       memcpy(&sin6B, &sin6A, sizeof(sin6B));
+       sin6B.sin6_port = htons(TEST_PORT_B);
+       if (connect(fd2, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
+
+       if ((fd3 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       if (bind(fd3, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0);
+
+       if (connect(fd3, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0);
+
+       if (write(fd2, "A", 1) != 1) e(0);
+
+       if (read(fd3, &c, 1) != 1) e(0);
+       if (c != 'A') e(0);
+
+       if (write(fd3, "B", 1) != 1) e(0);
+
+       if (read(fd2, &c, 1) != 1) e(0);
+       if (c != 'B') e(0);
+
+       if (ioctl(fd, BIOCGSTATS, &bs) != 0) e(0);
+       if (bs.bs_recv < 2) e(0);
+       if (bs.bs_capt != 1) e(0);
+       if (bs.bs_drop != 0) e(0);
+
+       memset(buf, 0, size);
+
+       len = read(fd, buf, size);
+
+       if (len != BPF_WORDALIGN(sizeof(bh)) +
+           BPF_WORDALIGN(sizeof(ip6) + sizeof(uh) + 1)) e(0);
+
+       memcpy(&bh, buf, sizeof(bh));
+
+       if (bh.bh_tstamp.tv_sec == 0 && bh.bh_tstamp.tv_usec == 0) e(0);
+       if (bh.bh_caplen != sizeof(ip6) + sizeof(uh) + 1) e(0);
+       if (bh.bh_datalen != bh.bh_caplen) e(0);
+       if (bh.bh_hdrlen != BPF_WORDALIGN(sizeof(bh))) e(0);
+
+       if (buf[bh.bh_hdrlen + sizeof(ip6) + sizeof(uh)] != 'A') e(0);
+
+       /*
+        * Finally, do a quick test to see if we can send IPv6 packets by
+        * writing to the BPF device.  We rely on such packets being generated
+        * properly in a later test.
+        */
+       off = test94_make_pkt(buf, 6, 1 /*v6*/);
+       memcpy(buf + off, "Hello!", 6);
+
+       if (write(fd, buf, off + 6) != off + 6) e(0);
+
+       socklen = sizeof(sin6A);
+       if (recvfrom(fd3, buf, size, 0, (struct sockaddr *)&sin6A,
+           &socklen) != 6) e(0);
+
+       if (memcmp(buf, "Hello!", 6) != 0) e(0);
+       if (socklen != sizeof(sin6A)) e(0);
+       if (sin6A.sin6_family != AF_INET6) e(0);
+       if (sin6A.sin6_port != htons(TEST_PORT_A)) e(0);
+       if (memcmp(&sin6A.sin6_addr, &in6addr_loopback,
+           sizeof(sin6A.sin6_addr)) != 0) e(0);
+
+       free(buf);
+
+       if (close(fd3) != 0) e(0);
+
+       if (close(fd2) != 0) e(0);
+
+       if (close(fd) != 0) e(0);
+}
+
+/*
+ * Test the BPF sysctl(7) interface at a basic level.
+ */
+static void
+test94j(void)
+{
+       struct bpf_stat bs1, bs2;
+       struct bpf_d_ext *bde;
+       uint8_t *buf;
+       unsigned int slot, count, uval;
+       size_t len, oldlen, size, bdesize;
+       int fd, fd2, fd3, val, mib[5], smib[3], found;
+
+       subtest = 10;
+
+       /*
+        * Obtain the maximum buffer size.  The value must be sane.
+        */
+       memset(mib, 0, sizeof(mib));
+       len = __arraycount(mib);
+       if (sysctlnametomib("net.bpf.maxbufsize", mib, &len) != 0) e(0);
+       if (len != 3) e(0);
+
+       oldlen = sizeof(val);
+       if (sysctl(mib, len, &val, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen != sizeof(val)) e(0);
+
+       if (val < 1024 || val > INT_MAX / 2) e(0);
+
+       /*
+        * Attempt to set the maximum buffer size.  This is not (yet) supported
+        * so for now we want to make sure that it really does not work.
+        */
+       if (sysctl(mib, len, NULL, NULL, &val, sizeof(val)) != -1) e(0);
+       if (errno != EPERM) e(0);
+
+       /*
+        * Obtain global statistics.  We check the actual statistics later on.
+        */
+       memset(smib, 0, sizeof(smib));
+       len = __arraycount(smib);
+       if (sysctlnametomib("net.bpf.stats", smib, &len) != 0) e(0);
+       if (len != 3) e(0);
+
+       oldlen = sizeof(bs1);
+       if (sysctl(smib, len, &bs1, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen != sizeof(bs1)) e(0);
+
+       /*
+        * Set up a BPF descriptor, and retrieve the list of BPF peers.  We
+        * should be able to find our BPF peer.
+        */
+       memset(mib, 0, sizeof(mib));
+       len = __arraycount(mib);
+       if (sysctlnametomib("net.bpf.peers", mib, &len) != 0) e(0);
+       if (len != 3) e(0);
+       mib[len++] = sizeof(*bde);      /* size of each element */
+       mib[len++] = INT_MAX;           /* limit on elements to return */
+
+       size = test94_setup(&fd, &fd2, &fd3, &buf, 0 /*size*/,
+           1 /*set_filter*/);
+
+       /* Generate some traffic to bump the statistics. */
+       count = test94_fill_exact(fd2, buf, size, 0);
+       test94_fill_exact(fd2, buf, size, 0);
+       test94_fill_exact(fd2, buf, size, 0);
+
+       if (write(fd3, "X", 1) != 1) e(0);
+
+       if (sysctl(mib, len, NULL, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen == 0) e(0);
+
+       /* Add some slack space ourselves to prevent problems with churn. */
+       bdesize = oldlen + sizeof(*bde) * 8;
+       if ((bde = malloc(bdesize)) == NULL) e(0);
+
+       oldlen = bdesize;
+       if (sysctl(mib, len, bde, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen % sizeof(*bde)) e(0);
+
+       found = 0;
+       for (slot = 0; slot < oldlen / sizeof(*bde); slot++) {
+               if (bde[slot].bde_pid != getpid())
+                       continue;
+
+               if (bde[slot].bde_bufsize != size) e(0);
+               if (bde[slot].bde_promisc != 0) e(0);
+               if (bde[slot].bde_state != BPF_IDLE) e(0);
+               if (bde[slot].bde_immediate != 0) e(0);
+               if (bde[slot].bde_hdrcmplt != 0) e(0);
+               if (bde[slot].bde_seesent != 1) e(0);
+               if (bde[slot].bde_rcount < count * 3 + 1) e(0);
+               if (bde[slot].bde_dcount != count) e(0);
+               if (bde[slot].bde_ccount != count * 3) e(0);
+               if (strcmp(bde[slot].bde_ifname, LOOPBACK_IFNAME) != 0) e(0);
+
+               found++;
+       }
+       if (found != 1) e(0);
+
+       /*
+        * If global statistics are an accumulation of individual devices'
+        * statistics (they currently are not) then such a scheme should take
+        * into account device flushes.
+        */
+       if (ioctl(fd, BIOCFLUSH) != 0) e(0);
+
+       test94_cleanup(fd, fd2, fd3, buf);
+
+       /*
+        * Now see if the global statistics have indeed changed correctly.
+        */
+       oldlen = sizeof(bs2);
+       if (sysctl(smib, __arraycount(smib), &bs2, &oldlen, NULL, 0) != 0)
+               e(0);
+       if (oldlen != sizeof(bs2)) e(0);
+
+       if (bs2.bs_recv < bs1.bs_recv + count * 3 + 1) e(0);
+       if (bs2.bs_drop != bs1.bs_drop + count) e(0);
+       if (bs2.bs_capt != bs1.bs_capt + count * 3) e(0);
+
+       /*
+        * Check an unconfigured BPF device as well.
+        */
+       if ((fd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
+
+       /*
+        * Toggle some flags.  It is too much effort to test them all
+        * individually (which, in the light of copy-paste mistakes, would be
+        * the right thing to do) but at least we'll know something gets set.
+        */
+       uval = 1;
+       if (ioctl(fd, BIOCIMMEDIATE, &uval) != 0) e(0);
+       if (ioctl(fd, BIOCSHDRCMPLT, &uval) != 0) e(0);
+
+       uval = 0;
+       if (ioctl(fd, BIOCSSEESENT, &uval) != 0) e(0);
+
+       oldlen = bdesize;
+       if (sysctl(mib, len, bde, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen % sizeof(*bde)) e(0);
+
+       found = 0;
+       for (slot = 0; slot < oldlen / sizeof(*bde); slot++) {
+               if (bde[slot].bde_pid != getpid())
+                       continue;
+
+               if (bde[slot].bde_bufsize != size) e(0);
+               if (bde[slot].bde_promisc != 0) e(0);
+               if (bde[slot].bde_state != BPF_IDLE) e(0);
+               if (bde[slot].bde_immediate != 1) e(0);
+               if (bde[slot].bde_hdrcmplt != 1) e(0);
+               if (bde[slot].bde_seesent != 0) e(0);
+               if (bde[slot].bde_rcount != 0) e(0);
+               if (bde[slot].bde_dcount != 0) e(0);
+               if (bde[slot].bde_ccount != 0) e(0);
+               if (bde[slot].bde_ifname[0] != '\0') e(0);
+
+               found++;
+       }
+       if (found != 1) e(0);
+
+       close(fd);
+
+       /*
+        * At this point there should be no BPF device left for our PID.
+        */
+       oldlen = bdesize;
+       if (sysctl(mib, len, bde, &oldlen, NULL, 0) != 0) e(0);
+       if (oldlen % sizeof(*bde)) e(0);
+
+       for (slot = 0; slot < oldlen / sizeof(*bde); slot++)
+               if (bde[slot].bde_pid == getpid()) e(0);
+                       found++;
+
+       free(bde);
+}
+
+/*
+ * Test privileged operations as an unprivileged caller.
+ */
+static void
+test94k(void)
+{
+       struct passwd *pw;
+       pid_t pid;
+       size_t len, oldlen;
+       int mib[5], status;
+
+       subtest = 11;
+
+       pid = fork();
+       switch (pid) {
+       case 0:
+               errct = 0;
+
+               if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0);
+
+               if (setuid(pw->pw_uid) != 0) e(0);
+
+               /*
+                * Opening /dev/bpf must fail.  Note that this is a system
+                * configuration issue rather than a LWIP service issue.
+                */
+               if (open(_PATH_BPF, O_RDWR) != -1) e(0);
+               if (errno != EACCES) e(0);
+
+               /*
+                * Retrieving the net.bpf.peers list must fail, too.
+                */
+               memset(mib, 0, sizeof(mib));
+               len = __arraycount(mib);
+               if (sysctlnametomib("net.bpf.peers", mib, &len) != 0) e(0);
+               if (len != 3) e(0);
+               mib[len++] = sizeof(struct bpf_d_ext);
+               mib[len++] = INT_MAX;
+
+               if (sysctl(mib, len, NULL, &oldlen, NULL, 0) != -1) e(0);
+               if (errno != EPERM) e(0);
+
+               exit(errct);
+       case -1:
+               e(0);
+
+               break;
+       default:
+               break;
+       }
+
+       if (wait(&status) != pid) e(0);
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
+}
+
+/*
+ * Test that traffic directed to loopback addresses be dropped on non-loopback
+ * interfaces.  In particular, inbound traffic to 127.0.0.1 and ::1 should not
+ * be accepted on any interface that does not own those addresses.  This test
+ * is here because BPF feedback mode is (currently) the only way in which we
+ * can generate inbound traffic the ethernet level, and even then only as a
+ * side effect of sending outbound traffic.  That is: this test sends the same
+ * test packets to the local network!  As such it must be performed only when
+ * USENETWORK=yes and therefore at the user's risk.
+ */
+static void
+test94l(void)
+{
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       struct sockaddr_dl sdl;
+       struct ifreq ifr;
+       struct ifaddrs *ifa, *ifp;
+       struct if_data *ifdata;
+       uint8_t buf[sizeof(struct ether_header) + MAX(sizeof(struct ip),
+           sizeof(struct ip6_hdr)) + sizeof(struct udphdr) + 6];
+       struct ether_header ether;
+       const uint8_t ether_src[ETHER_ADDR_LEN] =
+           { 0x02, 0x00, 0x01, 0x12, 0x34, 0x56 };
+       unsigned int val;
+       size_t off;
+       int bfd, sfd;
+
+       subtest = 12;
+
+       if (!get_setting_use_network())
+               return;
+
+       memset(&ifr, 0, sizeof(ifr));
+       memset(&ether, 0, sizeof(ether));
+
+       /*
+        * Start by finding a suitable ethernet interface that is up and of
+        * which the link is not down.  Without one, we cannot perform this
+        * test.  Save the interface name and the ethernet address.
+        */
+       if (getifaddrs(&ifa) != 0) e(0);
+
+       for (ifp = ifa; ifp != NULL; ifp = ifp->ifa_next) {
+               if (!(ifp->ifa_flags & IFF_UP) || ifp->ifa_addr == NULL ||
+                   ifp->ifa_addr->sa_family != AF_LINK)
+                       continue;
+
+               ifdata = (struct if_data *)ifp->ifa_data;
+               if (ifdata != NULL && ifdata->ifi_type == IFT_ETHER &&
+                   ifdata->ifi_link_state != LINK_STATE_DOWN) {
+                       strlcpy(ifr.ifr_name, ifp->ifa_name,
+                           sizeof(ifr.ifr_name));
+
+                       memcpy(&sdl, (struct sockaddr_dl *)ifp->ifa_addr,
+                           offsetof(struct sockaddr_dl, sdl_data));
+                       if (sdl.sdl_alen != sizeof(ether.ether_dhost)) e(0);
+                       memcpy(ether.ether_dhost,
+                           ((struct sockaddr_dl *)ifp->ifa_addr)->sdl_data +
+                           sdl.sdl_nlen, sdl.sdl_alen);
+                       break;
+               }
+       }
+
+       freeifaddrs(ifa);
+
+       if (ifp == NULL)
+               return;
+
+       /* Open a BPF device and bind it to the ethernet interface we found. */
+       if ((bfd = open(_PATH_BPF, O_RDWR)) < 0) e(0);
+
+       if (ioctl(bfd, BIOCSETIF, &ifr) != 0) e(0);
+
+       if (ioctl(bfd, BIOCGDLT, &val) != 0) e(0);
+       if (val != DLT_EN10MB) e(0);
+
+       val = 1;
+       if (ioctl(bfd, BIOCSFEEDBACK, &val) != 0) e(0);
+
+       /* We use UDP traffic for our test packets, IPv4 first. */
+       if ((sfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin, 0, sizeof(sin));
+       sin.sin_family = AF_INET;
+       sin.sin_port = htons(TEST_PORT_B);
+       sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0);
+
+       /*
+        * Construct and send a packet.  We already filled in the ethernet
+        * destination address.  Put in a source address that is locally
+        * administered but valid (and as such no reason for packet rejection).
+        */
+       memcpy(ether.ether_shost, ether_src, sizeof(ether.ether_shost));
+       ether.ether_type = htons(ETHERTYPE_IP);
+
+       memcpy(buf, &ether, sizeof(ether));
+       off = sizeof(ether);
+       off += test94_make_pkt(buf + off, 6, 0 /*v6*/);
+       if (off + 6 > sizeof(buf)) e(0);
+       memcpy(buf + off, "Hello!", 6);
+
+       if (write(bfd, buf, off + 6) != off + 6) e(0);
+
+       /* The packet MUST NOT arrive. */
+       if (recv(sfd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (close(sfd) != 0) e(0);
+
+       /* Try the same thing, but now with an IPv6 packet. */
+       if ((sfd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0);
+
+       memset(&sin6, 0, sizeof(sin6));
+       sin6.sin6_family = AF_INET6;
+       sin6.sin6_port = htons(TEST_PORT_B);
+       memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr));
+       if (bind(sfd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0);
+
+       ether.ether_type = htons(ETHERTYPE_IPV6);
+
+       memcpy(buf, &ether, sizeof(ether));
+       off = sizeof(ether);
+       off += test94_make_pkt(buf + off, 6, 1 /*v6*/);
+       if (off + 6 > sizeof(buf)) e(0);
+       memcpy(buf + off, "Hello!", 6);
+
+       if (write(bfd, buf, off + 6) != off + 6) e(0);
+
+       if (recv(sfd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0);
+       if (errno != EWOULDBLOCK) e(0);
+
+       if (close(sfd) != 0) e(0);
+       if (close(bfd) != 0) e(0);
+}
+
+/*
+ * Test program for LWIP BPF.
+ */
+int
+main(int argc, char ** argv)
+{
+       int i, m;
+
+       start(94);
+
+       srand48(time(NULL));
+
+       if (argc == 2)
+               m = atoi(argv[1]);
+       else
+               m = 0xFFF;
+
+       for (i = 0; i < ITERATIONS; i++) {
+               if (m & 0x001) test94a();
+               if (m & 0x002) test94b();
+               if (m & 0x004) test94c();
+               if (m & 0x008) test94d();
+               if (m & 0x010) test94e();
+               if (m & 0x020) test94f();
+               if (m & 0x040) test94g();
+               if (m & 0x080) test94h();
+               if (m & 0x100) test94i();
+               if (m & 0x200) test94j();
+               if (m & 0x400) test94k();
+               if (m & 0x800) test94l();
+       }
+
+       quit();
+       /* NOTREACHED */
+}