./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
./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
# 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= \
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
# 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"
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`
+/*
+ * 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
#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.
*/
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);
+}
#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,
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 */
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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 */
+}
--- /dev/null
+/* 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(ðer, 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, ðer, 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, ðer, 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 */
+}