From 3ba6090f825dfbac97538b19928b0ab1e37909d6 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Thu, 29 Sep 2016 23:16:22 +0000 Subject: [PATCH] tests: add advanced TCP/IP tests (test91-94) Change-Id: I052102f6122f82b3307595990bf91f64e97a45a8 --- distrib/sets/lists/minix-debug/mi | 4 + distrib/sets/lists/minix-tests/mi | 4 + minix/tests/Makefile | 14 +- minix/tests/run | 5 +- minix/tests/socklib.c | 2577 ++++++++++++++ minix/tests/socklib.h | 27 + minix/tests/test91.c | 5396 +++++++++++++++++++++++++++++ minix/tests/test92.c | 1735 ++++++++++ minix/tests/test93.c | 839 +++++ minix/tests/test94.c | 2650 ++++++++++++++ 10 files changed, 13248 insertions(+), 3 deletions(-) create mode 100644 minix/tests/test91.c create mode 100644 minix/tests/test92.c create mode 100644 minix/tests/test93.c create mode 100644 minix/tests/test94.c diff --git a/distrib/sets/lists/minix-debug/mi b/distrib/sets/lists/minix-debug/mi index c3c7e0980..27aabb7dc 100644 --- a/distrib/sets/lists/minix-debug/mi +++ b/distrib/sets/lists/minix-debug/mi @@ -1106,6 +1106,10 @@ ./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 diff --git a/distrib/sets/lists/minix-tests/mi b/distrib/sets/lists/minix-tests/mi index 1a1668e64..d3ddc45b7 100644 --- a/distrib/sets/lists/minix-tests/mi +++ b/distrib/sets/lists/minix-tests/mi @@ -194,6 +194,10 @@ ./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 diff --git a/minix/tests/Makefile b/minix/tests/Makefile index f1e177857..c0bcc8557 100644 --- a/minix/tests/Makefile +++ b/minix/tests/Makefile @@ -56,9 +56,21 @@ FILES += testvm.conf # Network stack testing programs OBJS.test90+= socklib.o +OBJS.test91+= socklib.o +OBJS.test92+= socklib.o +OBJS.test93+= socklib.o # Uncomment the following lines to use SOCKLIB_SWEEP_GENERATE=1/2 in socklib.c #.PATH: ${NETBSDSRCDIR}/minix/usr.bin/trace #OBJS.test90+= error.o +#OBJS.test91+= error.o +#OBJS.test92+= error.o +#OBJS.test93+= error.o + +.if ${USE_INET6} == "no" +# Tests 91-94 will fail without IPv6 support, but they should at least compile. +CPPFLAGS.socklib.c += -DNO_INET6 +CPPFLAGS.test94.c += -DNO_INET6 +.endif # ${USE_INET6} == "no" # Tests to compile, For every architecture MINIX_TESTS= \ @@ -66,7 +78,7 @@ MINIX_TESTS= \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 48 49 50 52 53 54 55 56 58 59 60 \ 61 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \ -81 82 83 84 85 86 87 88 89 90 +81 82 83 84 85 86 87 88 89 90 91 92 93 94 FILES += t84_h_nonexec.sh diff --git a/minix/tests/run b/minix/tests/run index a5baa809c..14f5a39dc 100755 --- a/minix/tests/run +++ b/minix/tests/run @@ -22,7 +22,8 @@ export USENETWORK # set to "yes" for test48+82 to use the network # Programs that require setuid setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \ - test69 test73 test74 test78 test83 test85 test87 test88 test89" + test69 test73 test74 test78 test83 test85 test87 test88 test89 \ + test92 test93 test94" # Scripts that require to be run as root rootscripts="testisofs testvnd testrmib testrelpol" @@ -30,7 +31,7 @@ alltests="1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 \ 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \ - 81 82 83 84 85 86 87 88 89 90 \ + 81 82 83 84 85 86 87 88 89 90 91 92 93 94 \ sh1 sh2 interp mfs isofs vnd rmib" tests_no=`expr 0` diff --git a/minix/tests/socklib.c b/minix/tests/socklib.c index 3608f6c16..af09280ee 100644 --- a/minix/tests/socklib.c +++ b/minix/tests/socklib.c @@ -1,3 +1,8 @@ +/* + * Socket test code library. This file contains code that is worth sharing + * between TCP/IP and UDS tests, as well as code that is worth sharing between + * various TCP/IP tests. + */ #include #include #include @@ -6,12 +11,39 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include "common.h" #include "socklib.h" +#define TEST_PORT_A 12345 /* this port should be free and usable */ +#define TEST_PORT_B 12346 /* this port should be free and usable */ + +#define LOOPBACK_IFNAME "lo0" /* loopback interface name */ +#define LOOPBACK_IPV4 "127.0.0.1" /* IPv4 address */ +#define LOOPBACK_IPV6_LL "fe80::1" /* link-local IPv6 address */ + +/* These address should simply eat all packets. */ +#define TEST_BLACKHOLE_IPV4 "127.255.0.254" +#define TEST_BLACKHOLE_IPV6 "::2" +#define TEST_BLACKHOLE_IPV6_LL "fe80::ffff" + +/* Addresses for multicast-related testing. */ +#define TEST_MULTICAST_IPV4 "233.252.0.1" /* RFC 5771 Sec. 9.2 */ +#define TEST_MULTICAST_IPV6 "ff0e::db8:0:1" /* RFC 6676 Sec. 3 */ +#define TEST_MULTICAST_IPV6_LL "ff02::db8:0:1" +#define TEST_MULTICAST_IPV6_BAD "ff00::db8:0:1" + +#define BAD_IFINDEX 255 /* guaranteed not to belong to an interface */ + /* 0 = check, 1 = generate source, 2 = generate CSV */ #define SOCKLIB_SWEEP_GENERATE 0 @@ -445,6 +477,280 @@ socklib_sweep(int domain, int type, int protocol, const enum state * states, #endif } +/* + * Test for setting and retrieving UDP/RAW multicast transmission options. + * This is an interface-level test only: we do not (yet) test whether the + * options have any effect. The given 'type' must be SOCK_DGRAM or SOCK_RAW. + */ +void +socklib_multicast_tx_options(int type) +{ + struct in_addr in_addr; + socklen_t len; + unsigned int ifindex; + uint8_t byte; + int fd, val; + + subtest = 10; + + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + /* + * Initially, the multicast TTL is expected be 1, looping should be + * enabled, and the multicast source address should be . + */ + byte = 0; + len = sizeof(byte); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte, &len) != 0) + e(0); + if (len != sizeof(byte)) e(0); + if (type != SOCK_STREAM && byte != 1) e(0); + + byte = 0; + len = sizeof(byte); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte, &len) != 0) + e(0); + if (len != sizeof(byte)) e(0); + if (byte != 1) e(0); + + len = sizeof(in_addr); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &in_addr, &len) != 0) + e(0); + if (len != sizeof(in_addr)) e(0); + if (in_addr.s_addr != htonl(INADDR_ANY)) e(0); + + /* It must not be possible to get/set IPv6 options on IPv4 sockets. */ + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, + sizeof(val) /*wrong but it doesn't matter*/) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != -1) + e(0); + if (errno != ENOPROTOOPT) e(0); + + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + /* + * Expect the same defaults as for IPv4. IPV6_MULTICAST_IF uses an + * interface index rather than an IP address, though. + */ + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (type != SOCK_STREAM && val != 1) e(0); + + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 1) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + /* It must not be possible to get/set IPv4 options on IPv6 sockets. */ + byte = 0; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte, + sizeof(byte)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte, + sizeof(byte)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &byte, + sizeof(byte) /* wrong but it doesn't matter */) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + len = sizeof(byte); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, &len) != -1) + e(0); + if (errno != ENOPROTOOPT) e(0); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, &len) != -1) + e(0); + if (errno != ENOPROTOOPT) e(0); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &val, &len) != -1) + e(0); + if (errno != ENOPROTOOPT) e(0); + + if (close(fd) != 0) e(0); + + /* Test changing options. */ + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + byte = 129; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte, + sizeof(byte)) != 0) e(0); + + byte = 0; + len = sizeof(byte); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &byte, &len) != 0) + e(0); + if (len != sizeof(byte)) e(0); + if (byte != 129) e(0); + + byte = 0; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte, + sizeof(byte)) != 0) + e(0); + + byte = 1; + len = sizeof(byte); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &byte, &len) != 0) + e(0); + if (len != sizeof(byte)) e(0); + if (byte != 0) e(0); + + in_addr.s_addr = htonl(INADDR_LOOPBACK); + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &in_addr, + sizeof(in_addr)) != 0) + e(0); + + in_addr.s_addr = htonl(INADDR_ANY); + len = sizeof(in_addr); + if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &in_addr, &len) != 0) + e(0); + if (len != sizeof(in_addr)) e(0); + if (in_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + val = 137; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)) != 0) e(0); + + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 137) e(0); + + val = -2; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + val = 256; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 137) e(0); + + val = -1; /* use default */ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)) != 0) e(0); + + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 1) e(0); + + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != 0) e(0); + + val = 1; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != 0) e(0); + + val = -1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + val = 2; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 1) e(0); + + val = -1; + ifindex = if_nametoindex(LOOPBACK_IFNAME); + + val = ifindex; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, + sizeof(val)) != 0) e(0); + + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != ifindex) e(0); + + val = BAD_IFINDEX; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, + sizeof(val)) != -1) e(0); + + val = -1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, + sizeof(val)) != -1) e(0); + + val = 0; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != ifindex) e(0); + + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, + sizeof(val)) != 0) e(0); + + val = ifindex; + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + if (close(fd) != 0) e(0); +} + /* * Test for large sends and receives on stream sockets with MSG_WAITALL. */ @@ -1021,3 +1327,2274 @@ socklib_stream_recv(int (* socket_pair)(int, int, int, int *), int domain, rlowat, len, bits, act, break_recv); } + +/* + * Obtain information for a matching protocol control block, using sysctl(7). + * The PCB is to be obtained through the given sysctl path string, and must + * match the other given parameters. Return 1 if found with 'ki' filled with + * the PCB information, or 0 if not. + */ +int +socklib_find_pcb(const char * path, int protocol, uint16_t local_port, + uint16_t remote_port, struct kinfo_pcb * ki) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct kinfo_pcb *array; + size_t i, miblen, oldlen; + uint16_t lport, rport; + int mib[CTL_MAXNAME], found; + + miblen = __arraycount(mib); + if (sysctlnametomib(path, mib, &miblen) != 0) e(0); + if (miblen > __arraycount(mib) - 4) e(0); + mib[miblen++] = 0; + mib[miblen++] = 0; + mib[miblen++] = sizeof(*array); + mib[miblen++] = 0; + + if (sysctl(mib, miblen, NULL, &oldlen, NULL, 0) != 0) e(0); + if (oldlen == 0) + return 0; /* should not happen due to added slop space */ + if (oldlen % sizeof(*array)) e(0); + + if ((array = (struct kinfo_pcb *)malloc(oldlen)) == NULL) e(0); + + if (sysctl(mib, miblen, array, &oldlen, NULL, 0) != 0) e(0); + if (oldlen % sizeof(*array)) e(0); + + found = -1; + for (i = 0; i < oldlen / sizeof(*array); i++) { + /* Perform some basic checks. */ + if (array[i].ki_pcbaddr == 0) e(0); + if (array[i].ki_ppcbaddr == 0) e(0); + if (array[i].ki_family != mib[1]) e(0); + + if (mib[1] == AF_INET6) { + memcpy(&sin6, &array[i].ki_src, sizeof(sin6)); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + lport = ntohs(sin6.sin6_port); + + memcpy(&sin6, &array[i].ki_dst, sizeof(sin6)); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + rport = ntohs(sin6.sin6_port); + } else { + memcpy(&sin, &array[i].ki_src, sizeof(sin)); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_len != sizeof(sin)) e(0); + lport = ntohs(sin.sin_port); + + memcpy(&sin, &array[i].ki_dst, sizeof(sin)); + if (sin.sin_family != AF_UNSPEC) { + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_len != sizeof(sin)) e(0); + rport = ntohs(sin.sin_port); + } else + rport = 0; + } + + /* Try to match every PCB. We must find at most one match. */ + if (array[i].ki_protocol == protocol && lport == local_port && + rport == remote_port) { + if (found != -1) e(0); + + found = (int)i; + } + } + + if (found >= 0) + memcpy(ki, &array[found], sizeof(*ki)); + + free(array); + + return (found != -1); +} + +#ifdef NO_INET6 +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; + +void +inet6_getscopeid(struct sockaddr_in6 * sin6 __unused, int flags __unused) +{ + + /* + * Nothing. The tests linked to socklib make heavy use of IPv6, and + * are expected to fail if IPv6 support is disabled at compile time. + * Therefore, what this replacement function does is not relevant. + */ +} +#endif /* NO_INET6 */ + +#define F_ANY 0x01 /* not bound, or bound to an 'any' address */ +#define F_V4 0x02 /* address is IPv4-mapped IPv6 address */ +#define F_REM 0x04 /* address is remote (not assigned to an interface) */ +#define F_MIX 0x08 /* address has non-loopback scope */ + +/* + * Test local and remote IPv6 address handling on TCP or UDP sockets. + */ +void +socklib_test_addrs(int type, int protocol) +{ + struct sockaddr_in6 sin6, sin6_any, sin6_any_scope, sin6_lo, + sin6_lo_scope, sin6_ll_all, sin6_ll_lo, sin6_ll_rem, sin6_ll_kame, + sin6_ll_bad, sin6_ll_mix, sin6_rem, sin6_v4_any, sin6_v4_lo, + sin6_v4_rem, rsin6; + const struct sockaddr_in6 *sin6p; + const struct { + const struct sockaddr_in6 *addr; + int res; + int flags; + const struct sockaddr_in6 *name; + } bind_array[] = { + { NULL, 0, F_ANY, &sin6_any }, + { &sin6_any, 0, F_ANY, &sin6_any }, + { &sin6_any_scope, 0, F_ANY, &sin6_any }, + { &sin6_lo, 0, 0, &sin6_lo }, + { &sin6_lo_scope, 0, 0, &sin6_lo }, + { &sin6_ll_lo, 0, 0, &sin6_ll_lo }, + { &sin6_v4_lo, 0, F_V4, &sin6_v4_lo }, + { &sin6_rem, EADDRNOTAVAIL }, + { &sin6_ll_all, EADDRNOTAVAIL }, + { &sin6_ll_rem, EADDRNOTAVAIL }, + { &sin6_ll_kame, EINVAL }, + { &sin6_ll_bad, ENXIO }, + { &sin6_v4_any, EADDRNOTAVAIL }, + { &sin6_v4_rem, EADDRNOTAVAIL }, + /* The following entry MUST be last. */ + { &sin6_ll_mix, EADDRNOTAVAIL }, + }, *bp; + const struct { + const struct sockaddr_in6 *addr; + int res; + int flags; + const struct sockaddr_in6 *name; + } conn_array[] = { + { &sin6_any, EHOSTUNREACH, 0 }, + { &sin6_any_scope, EHOSTUNREACH, 0 }, + { &sin6_ll_kame, EINVAL, 0 }, + { &sin6_ll_bad, ENXIO, 0 }, + { &sin6_v4_any, EHOSTUNREACH, F_V4 }, + { &sin6_lo, 0, 0, &sin6_lo }, + { &sin6_lo_scope, 0, 0, &sin6_lo }, + { &sin6_ll_all, 0, 0, &sin6_ll_lo }, + { &sin6_ll_lo, 0, 0, &sin6_ll_lo }, + { &sin6_v4_lo, 0, F_V4, &sin6_v4_lo }, + { &sin6_rem, 0, F_REM, &sin6_rem }, + { &sin6_ll_rem, 0, F_REM, &sin6_ll_rem }, + { &sin6_v4_rem, 0, F_V4|F_REM, &sin6_v4_rem }, + /* The following entry MUST be last. */ + { &sin6_ll_mix, 0, F_REM|F_MIX, &sin6_ll_mix }, + }, *cp; + struct ifaddrs *ifa, *ifp, *ifp2; + struct in6_ifreq ifr; + char name[IF_NAMESIZE], buf[1]; + socklen_t len; + uint32_t port; + unsigned int i, j, ifindex, ifindex2, have_mix, found; + int r, fd, fd2, fd3, val, sfl, exp, link_state; + + ifindex = if_nametoindex(LOOPBACK_IFNAME); + if (ifindex == 0) e(0); + + /* An IPv6 'any' address - ::0. */ + memset(&sin6_any, 0, sizeof(sin6_any)); + sin6_any.sin6_len = sizeof(sin6_any); + sin6_any.sin6_family = AF_INET6; + memcpy(&sin6_any.sin6_addr, &in6addr_any, sizeof(sin6_any.sin6_addr)); + + /* An IPv6 'any' address, but with a bad scope ID set. */ + memcpy(&sin6_any_scope, &sin6_any, sizeof(sin6_any_scope)); + sin6_any_scope.sin6_scope_id = BAD_IFINDEX; + + /* An IPv6 loopback address - ::1. */ + memcpy(&sin6_lo, &sin6_any, sizeof(sin6_lo)); + memcpy(&sin6_lo.sin6_addr, &in6addr_loopback, + sizeof(sin6_lo.sin6_addr)); + + /* An IPv6 loopback address, but with a bad scope ID set. */ + memcpy(&sin6_lo_scope, &sin6_lo, sizeof(sin6_lo_scope)); + sin6_lo_scope.sin6_scope_id = BAD_IFINDEX; + + /* An IPv6 link-local address without scope - fe80::1. */ + memcpy(&sin6_ll_all, &sin6_any, sizeof(sin6_ll_all)); + if (inet_pton(AF_INET6, LOOPBACK_IPV6_LL, &sin6_ll_all.sin6_addr) != 1) + e(0); + + /* An IPv6 link-local address with the loopback scope - fe80::1%lo0. */ + memcpy(&sin6_ll_lo, &sin6_ll_all, sizeof(sin6_ll_lo)); + sin6_ll_lo.sin6_scope_id = ifindex; + + /* An unassigned IPv6 link-local address - fe80::ffff%lo0. */ + memcpy(&sin6_ll_rem, &sin6_ll_lo, sizeof(sin6_ll_rem)); + if (inet_pton(AF_INET6, TEST_BLACKHOLE_IPV6_LL, + &sin6_ll_rem.sin6_addr) != 1) e(0); + + /* A KAME-style IPv6 link-local loopback address - fe80:ifindex::1. */ + memcpy(&sin6_ll_kame, &sin6_ll_all, sizeof(sin6_ll_kame)); + sin6_ll_kame.sin6_addr.s6_addr[2] = ifindex >> 8; + sin6_ll_kame.sin6_addr.s6_addr[3] = ifindex % 0xff; + + /* An IPv6 link-local address with a bad scope - fe80::1%. */ + 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); +} diff --git a/minix/tests/socklib.h b/minix/tests/socklib.h index 8fa944715..7a7390f10 100644 --- a/minix/tests/socklib.h +++ b/minix/tests/socklib.h @@ -1,6 +1,28 @@ #ifndef MINIX_TEST_SOCKLIB_H #define MINIX_TEST_SOCKLIB_H +/* TCP/IP test values. */ +#define TEST_PORT_A 12345 /* this port should be free and usable */ +#define TEST_PORT_B 12346 /* this port should be free and usable */ + +#define LOOPBACK_IFNAME "lo0" /* loopback interface name */ +#define LOOPBACK_IPV4 "127.0.0.1" /* IPv4 address */ +#define LOOPBACK_LL_IPV6 "fe80::1" /* link-local IPv6 address */ + +/* These address should simply eat all packets. */ +/* + * IMPORTANT: the ::2 address works only if there is a route for ::/64. This + * route is supposed to be added by /etc/rc.d/network, and is not present by + * default. As a result, the tests will pass only when regular system/network + * initialization is not skipped. We cannot add the route ourselves, since not + * all tests run as root. + */ +#define TEST_BLACKHOLE_IPV4 "127.255.0.254" +#define TEST_BLACKHOLE_IPV6 "::2" +#define TEST_BLACKHOLE_LL_IPV6 "fe80::ffff" + +#define BAD_SCOPE_ID 255 /* guaranteed not to belong to an interface */ + enum state { S_NEW, S_N_SHUT_R, @@ -81,9 +103,14 @@ void socklib_sweep(int domain, int type, int protocol, int (* proc)(int domain, int type, int protocol, enum state, enum call)); +void socklib_multicast_tx_options(int type); void socklib_large_transfers(int fd[2]); void socklib_producer_consumer(int fd[2]); void socklib_stream_recv(int (* socket_pair)(int, int, int, int *), int domain, int type, int (* break_recv)(int, const char *, size_t)); +int socklib_find_pcb(const char * path, int protocol, uint16_t local_port, + uint16_t remote_port, struct kinfo_pcb * ki); +void socklib_test_addrs(int type, int protocol); +void socklib_test_multicast(int type, int protocol); #endif /* !MINIX_TEST_SOCKLIB_H */ diff --git a/minix/tests/test91.c b/minix/tests/test91.c new file mode 100644 index 000000000..13e996cdc --- /dev/null +++ b/minix/tests/test91.c @@ -0,0 +1,5396 @@ +/* Advanced tests for TCP and UDP sockets (LWIP) - by D.C. van Moolenbroek */ +/* + * This is a somewhat random collection of in-depth tests, complementing the + * more general functionality tests in test80 and test81. The overall test set + * is still by no means expected to be "complete." The subtests are in random + * order. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "socklib.h" + +#define ITERATIONS 1 + +static const enum state tcp_states[] = { + S_NEW, S_N_SHUT_R, S_BOUND, S_LISTENING, + S_L_SHUT_R, S_L_SHUT_W, S_L_SHUT_RW, S_CONNECTING, + S_C_SHUT_R, S_C_SHUT_W, S_C_SHUT_RW, S_CONNECTED, + S_ACCEPTED, S_SHUT_R, S_SHUT_W, S_SHUT_RW, + S_RSHUT_R, S_RSHUT_W, S_RSHUT_RW, S_SHUT2_R, + S_SHUT2_W, S_SHUT2_RW, S_PRE_EOF, S_AT_EOF, + S_POST_EOF, S_PRE_SHUT_R, S_EOF_SHUT_R, S_POST_SHUT_R, + S_PRE_SHUT_W, S_EOF_SHUT_W, S_POST_SHUT_W, S_PRE_SHUT_RW, + S_EOF_SHUT_RW, S_POST_SHUT_RW, S_PRE_RESET, S_AT_RESET, + S_POST_RESET, S_FAILED, S_POST_FAILED +}; + +static const int tcp_results[][__arraycount(tcp_states)] = { + [C_ACCEPT] = { + -EINVAL, -EINVAL, -EINVAL, -EAGAIN, + -ECONNABORTED, -ECONNABORTED, -ECONNABORTED, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, + }, + [C_BIND] = { + 0, 0, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, + }, + [C_CONNECT] = { + -EINPROGRESS, -EINPROGRESS, -EINPROGRESS, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EALREADY, + -EALREADY, -EINVAL, -EINVAL, -EISCONN, + -EISCONN, -EISCONN, -EISCONN, -EISCONN, + -EISCONN, -EISCONN, -EISCONN, -EISCONN, + -EINVAL, -EINVAL, -EISCONN, -EISCONN, + -EISCONN, -EISCONN, -EISCONN, -EISCONN, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, + }, + [C_GETPEERNAME] = { + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + -ENOTCONN, -ENOTCONN, 0, 0, + 0, 0, 0, 0, + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, + }, + [C_GETSOCKNAME] = { + 0, 0, 0, 0, + -EINVAL, -EINVAL, -EINVAL, 0, + 0, -EINVAL, -EINVAL, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + -EINVAL, -EINVAL, 0, 0, + 0, 0, 0, 0, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, + }, + [C_GETSOCKOPT_ERR] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, -ECONNRESET, -ECONNRESET, + 0, -ECONNREFUSED, 0, + }, + [C_GETSOCKOPT_KA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, + }, + [C_GETSOCKOPT_RB] = { + 0, 0, 0, 0, + -ECONNRESET, -ECONNRESET, -ECONNRESET, 0, + 0, -ECONNRESET, -ECONNRESET, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + -ECONNRESET, -ECONNRESET, 0, 0, + 0, 0, 0, 0, + -ECONNRESET, -ECONNRESET, -ECONNRESET, -ECONNRESET, + -ECONNRESET, -ECONNRESET, -ECONNRESET, -ECONNRESET, + -ECONNRESET, -ECONNRESET, -ECONNRESET, + }, + [C_IOCTL_NREAD] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, + }, + [C_LISTEN] = { + 0, 0, 0, 0, + 0, 0, 0, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, + }, + [C_RECV] = { + -ENOTCONN, 0, -ENOTCONN, -ENOTCONN, + 0, 0, 0, -EAGAIN, + 0, 0, 0, -EAGAIN, + -EAGAIN, 0, -EAGAIN, 0, + -EAGAIN, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, -ECONNRESET, + 0, -ECONNREFUSED, 0, + }, + [C_RECVFROM] = { + -ENOTCONN, 0, -ENOTCONN, -ENOTCONN, + 0, 0, 0, -EAGAIN, + 0, 0, 0, -EAGAIN, + -EAGAIN, 0, -EAGAIN, 0, + -EAGAIN, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, -ECONNRESET, + 0, -ECONNREFUSED, 0, + }, + [C_SEND] = { + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -EPIPE, -EPIPE, -EPIPE, -EAGAIN, + -EAGAIN, -EPIPE, -EPIPE, 1, + 1, 1, -EPIPE, -EPIPE, + 1, 1, 1, 1, + -EPIPE, -EPIPE, 1, 1, + 1, 1, 1, 1, + -EPIPE, -EPIPE, -EPIPE, -EPIPE, + -EPIPE, -EPIPE, -ECONNRESET, -ECONNRESET, + -EPIPE, -ECONNREFUSED, -EPIPE, + }, + [C_SENDTO] = { + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -EPIPE, -EPIPE, -EPIPE, -EAGAIN, + -EAGAIN, -EPIPE, -EPIPE, 1, + 1, 1, -EPIPE, -EPIPE, + 1, 1, 1, 1, + -EPIPE, -EPIPE, 1, 1, + 1, 1, 1, 1, + -EPIPE, -EPIPE, -EPIPE, -EPIPE, + -EPIPE, -EPIPE, -ECONNRESET, -ECONNRESET, + -EPIPE, -ECONNREFUSED, -EPIPE, + }, + [C_SELECT_R] = { + 1, 1, 1, 0, + 1, 1, 1, 0, + 1, 1, 1, 0, + 0, 1, 0, 1, + 0, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, + }, + [C_SELECT_W] = { + 1, 1, 1, 1, + 1, 1, 1, 0, + 0, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, + }, + [C_SELECT_X] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, + }, + [C_SETSOCKOPT_BC] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, + }, + [C_SETSOCKOPT_KA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, + }, + [C_SETSOCKOPT_L] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, + }, + [C_SETSOCKOPT_RA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, + }, + [C_SHUTDOWN_R] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, + }, + [C_SHUTDOWN_RW] = { + -ENOTCONN, -ENOTCONN, -ENOTCONN, 0, + -ENOTCONN, -ENOTCONN, -ENOTCONN, 0, + 0, -ENOTCONN, -ENOTCONN, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + -ENOTCONN, -ENOTCONN, 0, 0, + 0, 0, 0, 0, + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, + }, + [C_SHUTDOWN_W] = { + -ENOTCONN, -ENOTCONN, -ENOTCONN, 0, + -ENOTCONN, -ENOTCONN, -ENOTCONN, 0, + 0, -ENOTCONN, -ENOTCONN, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + -ENOTCONN, -ENOTCONN, 0, 0, + 0, 0, 0, 0, + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, -ENOTCONN, -ENOTCONN, + }, +}; + +/* + * Set up a TCP socket file descriptor in the requested state and pass it to + * socklib_sweep_call() along with local and remote addresses and their length. + */ +static int +tcp_sweep(int domain, int type, int protocol, enum state state, enum call call) +{ + struct sockaddr_in sinA, sinB, sinC, sinD; + struct sockaddr_in6 sin6A, sin6B, sin6C, sin6D; + struct sockaddr *addrA, *addrB, *addrC, *addrD; + socklen_t addr_len, len; + struct linger l; + fd_set fds; + char buf[1]; + int r, fd, fd2, fd3, tmpfd, val; + + if (domain == AF_INET) { + memset(&sin6A, 0, sizeof(sin6A)); + sinA.sin_family = domain; + sinA.sin_port = htons(TEST_PORT_A); + sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + memcpy(&sinB, &sinA, sizeof(sinB)); + sinB.sin_port = htons(0); + + memcpy(&sinC, &sinA, sizeof(sinC)); + sinC.sin_addr.s_addr = inet_addr(TEST_BLACKHOLE_IPV4); + + memcpy(&sinD, &sinA, sizeof(sinD)); + sinD.sin_port = htons(TEST_PORT_B); + + addrA = (struct sockaddr *)&sinA; + addrB = (struct sockaddr *)&sinB; + addrC = (struct sockaddr *)&sinC; + addrD = (struct sockaddr *)&sinD; + addr_len = sizeof(sinA); + } else { + assert(domain == AF_INET6); + + memset(&sin6A, 0, sizeof(sin6A)); + sin6A.sin6_family = domain; + sin6A.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6A.sin6_addr, &in6addr_loopback, + sizeof(sin6A.sin6_addr)); + + memcpy(&sin6B, &sin6A, sizeof(sin6B)); + sin6B.sin6_port = htons(0); + + memcpy(&sin6C, &sin6A, sizeof(sin6C)); + if (inet_pton(domain, TEST_BLACKHOLE_IPV6, + &sin6C.sin6_addr) != 1) e(0); + + memcpy(&sin6D, &sin6A, sizeof(sin6D)); + sin6D.sin6_port = htons(TEST_PORT_B); + + addrA = (struct sockaddr *)&sin6A; + addrB = (struct sockaddr *)&sin6B; + addrC = (struct sockaddr *)&sin6C; + addrD = (struct sockaddr *)&sin6D; + addr_len = sizeof(sin6A); + } + + /* Create a bound remote socket. */ + if ((fd2 = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0); + + if (bind(fd2, addrB, addr_len) != 0) e(0); + + len = addr_len; + if (getsockname(fd2, addrB, &len) != 0) e(0); + if (len != addr_len) e(0); + + if (listen(fd2, 1) != 0) e(0); + + fd3 = -1; + + switch (state) { + case S_NEW: + case S_N_SHUT_R: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + sizeof(val)) != 0) e(0); + + if (state == S_N_SHUT_R && shutdown(fd, SHUT_RD)) e(0); + + break; + + case S_BOUND: + case S_LISTENING: + case S_L_SHUT_R: + case S_L_SHUT_W: + case S_L_SHUT_RW: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + sizeof(val)) != 0) e(0); + + if (bind(fd, addrA, addr_len) != 0) e(0); + + if (state == S_BOUND) + break; + + if (listen(fd, 1) != 0) e(0); + + switch (state) { + case S_L_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_L_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_L_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + break; + + case S_CONNECTING: + case S_C_SHUT_R: + case S_C_SHUT_W: + case S_C_SHUT_RW: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + if (connect(fd, addrC, addr_len) != -1) e(0); + if (errno != EINPROGRESS) e(0); + + switch (state) { + case S_C_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_C_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_C_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + break; + + case S_CONNECTED: + case S_ACCEPTED: + case S_SHUT_R: + case S_SHUT_W: + case S_SHUT_RW: + case S_RSHUT_R: + case S_RSHUT_W: + case S_RSHUT_RW: + case S_SHUT2_R: + case S_SHUT2_W: + case S_SHUT2_RW: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + if (connect(fd, addrB, addr_len) != -1) e(0); + if (errno != EINPROGRESS) e(0); + + /* Just to make sure, wait for the socket to be acceptable. */ + FD_ZERO(&fds); + FD_SET(fd2, &fds); + if (select(fd2 + 1, &fds, NULL, NULL, NULL) != 1) e(0); + + len = addr_len; + if ((fd3 = accept(fd2, addrC, &len)) < 0) e(0); + + /* Just to make sure, wait for the socket to be connected. */ + FD_ZERO(&fds); + FD_SET(fd, &fds); + if (select(fd + 1, NULL, &fds, NULL, NULL) != 1) e(0); + + switch (state) { + case S_SHUT_R: + case S_SHUT2_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_SHUT_W: + case S_SHUT2_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_SHUT_RW: + case S_SHUT2_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + switch (state) { + case S_RSHUT_R: + case S_SHUT2_R: if (shutdown(fd3, SHUT_RD)) e(0); break; + case S_RSHUT_W: + case S_SHUT2_W: if (shutdown(fd3, SHUT_WR)) e(0); break; + case S_RSHUT_RW: + case S_SHUT2_RW: if (shutdown(fd3, SHUT_RDWR)) e(0); break; + default: break; + } + + if (state == S_ACCEPTED) { + tmpfd = fd; + fd = fd3; + fd3 = tmpfd; + } + + break; + + case S_PRE_EOF: + case S_AT_EOF: + case S_POST_EOF: + case S_PRE_SHUT_R: + case S_EOF_SHUT_R: + case S_POST_SHUT_R: + case S_PRE_SHUT_W: + case S_EOF_SHUT_W: + case S_POST_SHUT_W: + case S_PRE_SHUT_RW: + case S_EOF_SHUT_RW: + case S_POST_SHUT_RW: + case S_PRE_RESET: + case S_AT_RESET: + case S_POST_RESET: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + if (connect(fd, addrB, addr_len) != -1) e(0); + if (errno != EINPROGRESS) e(0); + + /* Just to make sure, wait for the socket to be acceptable. */ + FD_ZERO(&fds); + FD_SET(fd2, &fds); + if (select(fd2 + 1, &fds, NULL, NULL, NULL) != 1) e(0); + + len = addr_len; + if ((fd3 = accept(fd2, addrC, &len)) < 0) e(0); + + if (send(fd3, "", 1, 0) != 1) e(0); + + switch (state) { + case S_PRE_RESET: + case S_AT_RESET: + case S_POST_RESET: + l.l_onoff = 1; + l.l_linger = 0; + + if (setsockopt(fd3, SOL_SOCKET, SO_LINGER, &l, + sizeof(l)) != 0) e(0); + + break; + default: + break; + } + + if (close(fd3) != 0) e(0); + fd3 = -1; + + /* Just to make sure, wait for the socket to receive data. */ + FD_ZERO(&fds); + FD_SET(fd, &fds); + if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0); + + switch (state) { + case S_AT_EOF: + case S_EOF_SHUT_R: + case S_EOF_SHUT_W: + case S_EOF_SHUT_RW: + case S_AT_RESET: + if (recv(fd, buf, sizeof(buf), 0) != 1) e(0); + break; + case S_POST_EOF: + case S_POST_SHUT_R: + case S_POST_SHUT_W: + case S_POST_SHUT_RW: + if (recv(fd, buf, sizeof(buf), 0) != 1) e(0); + if (recv(fd, buf, sizeof(buf), 0) != 0) e(0); + break; + case S_POST_RESET: + if (recv(fd, buf, sizeof(buf), 0) != 1) e(0); + (void)recv(fd, buf, sizeof(buf), 0); + break; + default: + break; + } + + switch (state) { + case S_PRE_SHUT_R: + case S_EOF_SHUT_R: + case S_POST_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_PRE_SHUT_W: + case S_EOF_SHUT_W: + case S_POST_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_PRE_SHUT_RW: + case S_EOF_SHUT_RW: + case S_POST_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + break; + + case S_FAILED: + case S_POST_FAILED: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + if (connect(fd, addrD, addr_len) != -1) e(0); + if (errno != EINPROGRESS) e(0); + + FD_ZERO(&fds); + FD_SET(fd, &fds); + if (select(fd + 1, &fds, NULL, NULL, NULL) != 1) e(0); + + if (state == S_POST_FAILED) { + if (recv(fd, buf, sizeof(buf), 0) != -1) e(0); + if (errno != ECONNREFUSED) e(0); + } + + break; + + default: + fd = -1; + e(0); + } + + r = socklib_sweep_call(call, fd, addrA, addrB, addr_len); + + if (close(fd) != 0) e(0); + if (fd2 != -1 && close(fd2) != 0) e(0); + if (fd3 != -1 && close(fd3) != 0) e(0); + + return r; +} + +static const enum state udp_states[] = { + S_NEW, S_N_SHUT_R, S_N_SHUT_W, S_N_SHUT_RW, + S_BOUND, S_CONNECTED, S_SHUT_R, S_SHUT_W, + S_SHUT_RW, S_RSHUT_R, S_RSHUT_W, S_RSHUT_RW, + S_SHUT2_R, S_SHUT2_W, S_SHUT2_RW, S_PRE_RESET, + S_AT_RESET, S_POST_RESET +}; + +static const int udp_results[][__arraycount(udp_states)] = { + [C_ACCEPT] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_BIND] = { + 0, 0, 0, 0, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, -EINVAL, + }, + [C_CONNECT] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_GETPEERNAME] = { + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_GETSOCKNAME] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_GETSOCKOPT_ERR] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_GETSOCKOPT_KA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_GETSOCKOPT_RB] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_IOCTL_NREAD] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1, + 0, 0, + }, + [C_LISTEN] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_RECV] = { + -EAGAIN, 0, -EAGAIN, 0, + -EAGAIN, -EAGAIN, 0, -EAGAIN, + 0, -EAGAIN, -EAGAIN, -EAGAIN, + 0, -EAGAIN, 0, 1, + -EAGAIN, -EAGAIN, + }, + [C_RECVFROM] = { + -EAGAIN, 0, -EAGAIN, 0, + -EAGAIN, -EAGAIN, 0, -EAGAIN, + 0, -EAGAIN, -EAGAIN, -EAGAIN, + 0, -EAGAIN, 0, 1, + -EAGAIN, -EAGAIN, + }, + [C_SEND] = { + -EDESTADDRREQ, -EDESTADDRREQ, -EPIPE, -EPIPE, + -EDESTADDRREQ, 1, 1, -EPIPE, + -EPIPE, 1, 1, 1, + 1, -EPIPE, -EPIPE, 1, + 1, 1, + }, + [C_SENDTO] = { + 1, 1, -EPIPE, -EPIPE, + 1, 1, 1, -EPIPE, + -EPIPE, 1, 1, 1, + 1, -EPIPE, -EPIPE, 1, + 1, 1, + }, + [C_SELECT_R] = { + 0, 1, 0, 1, + 0, 0, 1, 0, + 1, 0, 0, 0, + 1, 0, 1, 1, + 0, 0, + }, + [C_SELECT_W] = { + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, + }, + [C_SELECT_X] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_SETSOCKOPT_BC] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_SETSOCKOPT_KA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_SETSOCKOPT_L] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_SETSOCKOPT_RA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_SHUTDOWN_R] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_SHUTDOWN_RW] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, + [C_SHUTDOWN_W] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, + }, +}; + +/* + * Set up a UDP socket file descriptor in the requested state and pass it to + * socklib_sweep_call() along with local and remote addresses and their length. + */ +static int +udp_sweep(int domain, int type, int protocol, enum state state, enum call call) +{ + struct sockaddr_in sinA, sinB; + struct sockaddr_in6 sin6A, sin6B; + struct sockaddr *addrA, *addrB; + socklen_t addr_len; + char buf[1]; + int r, fd, fd2; + + if (domain == AF_INET) { + memset(&sinA, 0, sizeof(sinA)); + sinA.sin_family = domain; + sinA.sin_port = htons(TEST_PORT_A); + sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + memcpy(&sinB, &sinA, sizeof(sinB)); + sinB.sin_port = htons(TEST_PORT_B); + + addrA = (struct sockaddr *)&sinA; + addrB = (struct sockaddr *)&sinB; + addr_len = sizeof(sinA); + } else { + assert(domain == AF_INET6); + + memset(&sin6A, 0, sizeof(sin6A)); + sin6A.sin6_family = domain; + sin6A.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6A.sin6_addr, &in6addr_loopback, + sizeof(sin6A.sin6_addr)); + + memcpy(&sin6B, &sin6A, sizeof(sin6B)); + sin6B.sin6_port = htons(TEST_PORT_B); + + addrA = (struct sockaddr *)&sin6A; + addrB = (struct sockaddr *)&sin6B; + addr_len = sizeof(sin6A); + } + + /* Create a bound remote socket. */ + if ((fd2 = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0); + + if (bind(fd2, addrB, addr_len) != 0) e(0); + + switch (state) { + case S_NEW: + case S_N_SHUT_R: + case S_N_SHUT_W: + case S_N_SHUT_RW: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + switch (state) { + case S_N_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_N_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_N_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + break; + + case S_BOUND: + case S_CONNECTED: + case S_SHUT_R: + case S_SHUT_W: + case S_SHUT_RW: + case S_RSHUT_R: + case S_RSHUT_W: + case S_RSHUT_RW: + case S_SHUT2_R: + case S_SHUT2_W: + case S_SHUT2_RW: + case S_PRE_RESET: + case S_AT_RESET: + case S_POST_RESET: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + if (bind(fd, addrA, addr_len) != 0) e(0); + + if (state == S_BOUND) + break; + + if (connect(fd, addrB, addr_len) != 0) e(0); + + switch (state) { + case S_SHUT_R: + case S_SHUT2_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_SHUT_W: + case S_SHUT2_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_SHUT_RW: + case S_SHUT2_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + switch (state) { + case S_RSHUT_R: + case S_SHUT2_R: if (shutdown(fd2, SHUT_RD)) e(0); break; + case S_RSHUT_W: + case S_SHUT2_W: if (shutdown(fd2, SHUT_WR)) e(0); break; + case S_RSHUT_RW: + case S_SHUT2_RW: if (shutdown(fd2, SHUT_RDWR)) e(0); break; + case S_PRE_RESET: + case S_AT_RESET: + case S_POST_RESET: + if (sendto(fd2, "", 1, 0, addrA, addr_len) != 1) e(0); + + if (close(fd2) != 0) e(0); + fd2 = -1; + + if (state != S_PRE_RESET) { + if (recv(fd, buf, sizeof(buf), 0) != 1) e(0); + } + if (state == S_POST_RESET) { + (void)recv(fd, buf, sizeof(buf), 0); + } + default: + break; + } + + break; + + default: + fd = -1; + e(0); + } + + r = socklib_sweep_call(call, fd, addrA, addrB, addr_len); + + if (close(fd) != 0) e(0); + if (fd2 != -1 && close(fd2) != 0) e(0); + + return r; +} + +/* + * Sweep test for socket calls versus socket states of TCP and UDP sockets. + */ +static void +test91a(void) +{ + + subtest = 1; + + socklib_sweep(AF_INET, SOCK_STREAM, 0, tcp_states, + __arraycount(tcp_states), (const int *)tcp_results, tcp_sweep); + + socklib_sweep(AF_INET6, SOCK_STREAM, 0, tcp_states, + __arraycount(tcp_states), (const int *)tcp_results, tcp_sweep); + + socklib_sweep(AF_INET, SOCK_DGRAM, 0, udp_states, + __arraycount(udp_states), (const int *)udp_results, udp_sweep); + + socklib_sweep(AF_INET6, SOCK_DGRAM, 0, udp_states, + __arraycount(udp_states), (const int *)udp_results, udp_sweep); +} + +#define F_SKIP -1 /* skip this entry */ +#define F_NO 0 /* binding or connecting should fail */ +#define F_YES 1 /* binding or connecting should succeed */ +#define F_DUAL 2 /* always fails on IPV6_V6ONLY sockets */ +#define F_ZONE 4 /* binding works only if a scope ID is given */ +#define F_UDP 8 /* do not test on TCP sockets */ +#define F_BAD 16 /* operations on this address result in EINVAL */ + +static const struct { + const char *addr; + int may_bind; + int may_connect; /* UDP only */ +} addrs_v4[] = { + { "0.0.0.0", F_YES, F_NO }, + { "0.0.0.1", F_NO, F_SKIP }, + { "127.0.0.1", F_YES, F_YES }, + { "127.0.0.255", F_NO, F_YES }, + { "127.255.255.255", F_NO, F_YES }, + { "172.31.255.254", F_NO, F_SKIP }, /* may be valid.. */ + { "224.0.0.0", F_YES | F_UDP, F_SKIP }, + { "239.255.255.255", F_YES | F_UDP, F_SKIP }, + { "240.0.0.0", F_NO, F_SKIP }, + { "255.255.255.255", F_NO, F_SKIP }, +}; + +static const struct { + const char *addr; + int may_bind; + int may_connect; /* UDP only */ +} addrs_v6[] = { + { "::0", F_YES, F_NO }, + { "::1", F_YES, F_YES }, + { "::2", F_NO, F_YES }, + { "::127.0.0.1", F_NO, F_YES }, + { "::ffff:7f00:1", F_YES | F_DUAL, F_YES | F_DUAL }, + { "::ffff:7f00:ff", F_NO | F_DUAL, F_YES | F_DUAL }, + { "100::1", F_NO, F_SKIP }, + { "2fff:ffff::", F_NO, F_SKIP }, + { "fc00::1", F_NO, F_SKIP }, + { "fe00::1", F_NO, F_SKIP }, + { "fe80::1", F_YES | F_ZONE, F_YES | F_ZONE }, + { "fec0::1", F_NO, F_SKIP }, + { "ff01::1", F_YES | F_ZONE | F_UDP, F_YES | F_ZONE }, + { "ff02::1", F_YES | F_ZONE | F_UDP, F_YES | F_ZONE }, + { "ff02::2", F_YES | F_ZONE | F_UDP, F_YES | F_ZONE }, + { "ff0e::1", F_YES | F_UDP, F_SKIP }, + { "ffff::1", F_NO | F_UDP | F_BAD, F_NO | F_BAD }, +}; + +/* + * Test binding sockets of a particular type to various addresses. + */ +static void +sub91b(int type) +{ + struct sockaddr_in sin, lsin; + struct sockaddr_in6 sin6, lsin6; + socklen_t len; + unsigned int i, ifindex; + int r, fd, val; + + ifindex = if_nametoindex(LOOPBACK_IFNAME); + + /* Test binding IPv4 sockets to IPv4 addresses. */ + for (i = 0; i < __arraycount(addrs_v4); i++) { + if (type == SOCK_STREAM && (addrs_v4[i].may_bind & F_UDP)) + continue; + + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + if (inet_pton(AF_INET, addrs_v4[i].addr, &sin.sin_addr) != 1) + e(0); + + r = bind(fd, (struct sockaddr *)&sin, sizeof(sin)); + if (r == -1 && errno != EADDRNOTAVAIL) e(0); + if (r + 1 != !!(addrs_v4[i].may_bind & F_YES)) e(0); + + len = sizeof(lsin); + if (getsockname(fd, (struct sockaddr *)&lsin, &len) != 0) e(0); + if (lsin.sin_len != sizeof(lsin)) e(0); + if (lsin.sin_family != AF_INET) e(0); + if (r == 0) { + if (lsin.sin_port == 0) e(0); + if (lsin.sin_addr.s_addr != sin.sin_addr.s_addr) e(0); + } else { + if (lsin.sin_port != 0) e(0); + if (lsin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0); + } + + /* Rebinding never works; binding after a failed bind does. */ + sin.sin_addr.s_addr = htonl(INADDR_ANY); + r = bind(fd, (struct sockaddr *)&sin, sizeof(sin)); + if (r == -1 && errno != EINVAL) e(0); + if (!!r != !!(addrs_v4[i].may_bind & F_YES)) e(0); + + if (close(fd) != 0) e(0); + } + + /* Test binding IPv6 sockets to IPv6 addresses. */ + for (i = 0; i < __arraycount(addrs_v6); i++) { + if (type == SOCK_STREAM && (addrs_v6[i].may_bind & F_UDP)) + continue; + + /* Try without IPV6_V6ONLY. */ + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + /* IPV6_V6ONLY may or may not be enabled by default.. */ + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + sizeof(val)) != 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, addrs_v6[i].addr, + &sin6.sin6_addr) != 1) e(0); + + if (addrs_v6[i].may_bind & F_ZONE) { + if (bind(fd, (struct sockaddr *)&sin6, + sizeof(sin6)) != -1) e(0); + if (errno != EADDRNOTAVAIL) e(0); + + sin6.sin6_scope_id = ifindex; + } + + r = bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)); + if (r == -1) { + if (addrs_v6[i].may_bind & F_BAD) { + if (errno != EINVAL) e(0); + } else { + if (errno != EADDRNOTAVAIL) e(0); + } + } + if (r + 1 != !!(addrs_v6[i].may_bind & F_YES)) e(0); + + len = sizeof(lsin6); + if (getsockname(fd, (struct sockaddr *)&lsin6, &len) != 0) + e(0); + if (lsin6.sin6_len != sizeof(lsin6)) e(0); + if (lsin6.sin6_family != AF_INET6) e(0); + if (r == 0) { + if (lsin6.sin6_port == 0) e(0); + if (memcmp(&lsin6.sin6_addr, &sin6.sin6_addr, + sizeof(lsin6.sin6_addr))) e(0); + if (lsin6.sin6_scope_id != + ((addrs_v6[i].may_bind & F_ZONE) ? ifindex : 0)) + e(0); + } else { + if (lsin6.sin6_port != 0) e(0); + if (!IN6_IS_ADDR_UNSPECIFIED(&lsin6.sin6_addr)) e(0); + if (lsin6.sin6_scope_id != 0) e(0); + } + + if (close(fd) != 0) e(0); + + /* Try with IPV6_V6ONLY. */ + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + sizeof(val)) != 0) e(0); + + r = bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)); + if (r == -1) { + if (addrs_v6[i].may_bind & (F_BAD | F_DUAL)) { + if (errno != EINVAL) e(0); + } else + if (errno != EADDRNOTAVAIL) e(0); + } + if (r + 1 != + ((addrs_v6[i].may_bind & (F_YES | F_DUAL)) == F_YES)) e(0); + + if (close(fd) != 0) e(0); + } + + /* Test binding an IPv6 socket to an IPv4 address. */ + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EINVAL) e(0); + + assert(sizeof(sin) <= sizeof(sin6)); + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin6, &sin, sizeof(sin)); + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0); + if (errno != EAFNOSUPPORT) e(0); + + if (close(fd) != 0) e(0); + + /* Test binding an IPv4 socket to an IPv6 address. */ + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr)); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0); + if (errno != EINVAL) e(0); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin)) != -1) e(0); + if (errno != EAFNOSUPPORT) e(0); + + if (close(fd) != 0) e(0); + + /* Test binding a socket to AF_UNSPEC. */ + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_UNSPEC; + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EAFNOSUPPORT) e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test binding sockets to various addresses. + */ +static void +test91b(void) +{ + + subtest = 2; + + sub91b(SOCK_STREAM); + + sub91b(SOCK_DGRAM); +} + +/* + * Test connecting TCP sockets to various addresses. We cannot test much here, + * because we do not actually want this test to generate outgoing traffic. In + * effect, we test calls that should fail only. + */ +static void +sub91c_tcp(void) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + int fd, val; + + /* + * Test connecting to address zero (0.0.0.0 and ::0). Apparently the + * traditional BSD behavior for IPv4 is to use the first interface's + * local address as destination instead, but our implementation does + * not support that at this time: these 'any' addresses always result + * in connection failures right away, hopefully eliminating some tricky + * implementation boundary cases. + */ + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EHOSTUNREACH && errno != ENETUNREACH) e(0); + + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr)); + + if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0); + if (errno != EHOSTUNREACH && errno != ENETUNREACH) e(0); + + if (close(fd) != 0) e(0); + + /* + * Test connecting to an IPv6-mapped IPv4 address on an IPv6 socket + * with INET6_V6ONLY enabled. + */ + if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1) + e(0); + + if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0); + if (errno != EINVAL) e(0); + + if (close(fd) != 0) e(0); + + /* Test connecting to an AF_UNSPEC address. */ + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_UNSPEC; + + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EAFNOSUPPORT) e(0); + + if (close(fd) != 0) e(0); + + /* Test connecting to port zero. */ + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(0); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EADDRNOTAVAIL) e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test connecting UDP sockets to various addresses. + */ +static void +sub91c_udp(void) +{ + struct sockaddr_in sin, rsin; + struct sockaddr_in6 sin6, rsin6; + socklen_t len; + unsigned int i, ifindex; + int r, fd, val; + + ifindex = if_nametoindex(LOOPBACK_IFNAME); + + /* Test connecting IPv4 sockets to IPv4 addresses. */ + for (i = 0; i < __arraycount(addrs_v4); i++) { + if (addrs_v4[i].may_connect == F_SKIP) + continue; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + if (inet_pton(AF_INET, addrs_v4[i].addr, &sin.sin_addr) != 1) + e(0); + + r = connect(fd, (struct sockaddr *)&sin, sizeof(sin)); + if (r + 1 != !!(addrs_v4[i].may_connect & F_YES)) e(0); + + len = sizeof(rsin); + if (r == 0) { + if (getpeername(fd, (struct sockaddr *)&rsin, + &len) != 0) e(0); + if (rsin.sin_len != sizeof(rsin)) e(0); + if (rsin.sin_family != AF_INET) e(0); + if (rsin.sin_port != htons(TEST_PORT_A)) e(0); + if (rsin.sin_addr.s_addr != sin.sin_addr.s_addr) e(0); + } else { + if (getpeername(fd, (struct sockaddr *)&rsin, + &len) != -1) e(0); + if (errno != ENOTCONN) e(0); + } + + sin.sin_addr.s_addr = htonl(INADDR_ANY); + r = bind(fd, (struct sockaddr *)&sin, sizeof(sin)); + if (r == -1 && errno != EINVAL) e(0); + if (r + 1 != !(addrs_v4[i].may_connect & F_YES)) e(0); + + if (close(fd) != 0) e(0); + } + + /* Test connecting IPv6 sockets to IPv6 addresses. */ + for (i = 0; i < __arraycount(addrs_v6); i++) { + if (addrs_v6[i].may_connect == F_SKIP) + continue; + + /* Try without IPV6_V6ONLY. */ + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + /* IPV6_V6ONLY may or may not be enabled by default.. */ + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + sizeof(val)) != 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + if (inet_pton(AF_INET6, addrs_v6[i].addr, + &sin6.sin6_addr) != 1) e(0); + sin6.sin6_scope_id = ifindex; + + r = connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)); + if (r + 1 != !!(addrs_v6[i].may_connect & F_YES)) e(0); + + len = sizeof(rsin6); + if (r == 0) { + if (getpeername(fd, (struct sockaddr *)&rsin6, + &len) != 0) e(0); + if (rsin6.sin6_len != sizeof(rsin6)) e(0); + if (rsin6.sin6_family != AF_INET6) e(0); + if (rsin6.sin6_port != htons(TEST_PORT_A)) e(0); + if (memcmp(&rsin6.sin6_addr, &sin6.sin6_addr, + sizeof(rsin6.sin6_addr))) e(0); + if (rsin6.sin6_scope_id != + ((addrs_v6[i].may_connect & F_ZONE) ? ifindex : 0)) + e(0); + } else { + if (getpeername(fd, (struct sockaddr *)&rsin, + &len) != -1) e(0); + if (errno != ENOTCONN) e(0); + } + + if (close(fd) != 0) e(0); + + /* Try with IPV6_V6ONLY. */ + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + sizeof(val)) != 0) e(0); + + r = connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)); + if (r == -1 && errno != EINVAL && errno != EHOSTUNREACH) e(0); + if (r + 1 != + ((addrs_v6[i].may_connect & (F_YES | F_DUAL)) == F_YES)) + e(0); + + if (close(fd) != 0) e(0); + } + + /* Test connecting an IPv6 socket to an IPv4 address. */ + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EINVAL) e(0); + + assert(sizeof(sin) <= sizeof(sin6)); + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin6, &sin, sizeof(sin)); + if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0); + if (errno != EAFNOSUPPORT) e(0); + + if (close(fd) != 0) e(0); + + /* Test connecting an IPv4 socket to an IPv6 address. */ + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr)); + + if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0); + if (errno != EINVAL) e(0); + + if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin)) != -1) e(0); + if (errno != EAFNOSUPPORT) e(0); + + if (close(fd) != 0) e(0); + + /* Test unconnecting a socket using AF_UNSPEC. */ + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_UNSPEC; + + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + len = sizeof(rsin); + if (getpeername(fd, (struct sockaddr *)&rsin, &len) != -1) e(0); + if (errno != ENOTCONN) e(0); + + if (close(fd) != 0) e(0); + + /* Test connecting to port zero. */ + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(0); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EADDRNOTAVAIL) e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test connecting sockets to various addresses. + */ +static void +test91c(void) +{ + + subtest = 3; + + sub91c_tcp(); + + sub91c_udp(); +} + +/* + * Test binding with IPv4/IPv6 on the same port for the given socket type. + */ +static void +sub91d(int type) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + int r, fd, fd2, val; + + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + /* IPv4 bound; IPv6 bind without IPV6_V6ONLY may or may not work. */ + if ((fd2 = socket(AF_INET6, type, 0)) < 0) e(0); + + val = 0; + if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr)); + + r = bind(fd2, (struct sockaddr *)&sin6, sizeof(sin6)); + if (r == -1 && errno != EADDRINUSE) e(0); + + if (close(fd2) != 0) e(0); + + /* IPv4 bound; IPv6 bind with IPV6_V6ONLY should work. */ + if ((fd2 = socket(AF_INET6, type, 0)) < 0) e(0); + + val = 1; + if (setsockopt(fd2, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0) + e(0); + if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + if (bind(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* IPv6 bound with IPV6_V6ONLY; IPv4 bind may or may not work. */ + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0) + e(0); + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + if ((fd2 = socket(AF_INET, type, 0)) < 0) e(0); + + r = bind(fd2, (struct sockaddr *)&sin, sizeof(sin)); + if (r == -1 && errno != EADDRINUSE) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* IPv6 bound with IPV6_V6ONLY; IPv4 bind should work. */ + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0) + e(0); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + if ((fd2 = socket(AF_INET, type, 0)) < 0) e(0); + + if (bind(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); +} + +/* + * Test binding with IPv4/IPv6 on the same port, and IPV6_V6ONLY. + */ +static void +test91d(void) +{ + + subtest = 4; + + sub91d(SOCK_STREAM); + + sub91d(SOCK_DGRAM); +} + +/* + * Test sending large and small UDP packets. + */ +static void +test91e(void) +{ + struct sockaddr_in sin; + struct msghdr msg; + struct iovec iov; + char *buf; + unsigned int i, j; + int r, fd, fd2, val; + + subtest = 5; + + if ((buf = malloc(65536)) == NULL) e(0); + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + val = 65536; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0) + e(0); + + if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + /* + * A maximum send buffer size of a full packet size's worth may always + * be set, although this is not necessarily the actual maximum. + */ + val = 65535; + if (setsockopt(fd2, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) != 0) + e(0); + + /* Find the largest possible packet size that can actually be sent. */ + for (i = 0; i < val; i += sizeof(int)) { + j = i ^ 0xdeadbeef; + memcpy(&buf[i], &j, sizeof(j)); + } + + for (val = 65536; val > 0; val--) { + if ((r = sendto(fd2, buf, val, 0, (struct sockaddr *)&sin, + sizeof(sin))) == val) + break; + if (r != -1) e(0); + if (errno != EMSGSIZE) e(0); + } + + if (val != 65535 - sizeof(struct udphdr) - sizeof(struct ip)) e(0); + + memset(buf, 0, val); + buf[val] = 'X'; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = val + 1; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != val) e(0); + if (msg.msg_flags != 0) e(0); + + for (i = 0; i < val; i += sizeof(int)) { + j = i ^ 0xdeadbeef; + if (memcmp(&buf[i], &j, MIN(sizeof(j), val - i))) e(0); + } + if (buf[val] != 'X') e(0); + + if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin, sizeof(sin)) != + val) e(0); + + /* + * Make sure that there are no off-by-one errors in the receive code, + * and that MSG_TRUNC is set (only) when not the whole packet was + * received. + */ + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = val; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != val) e(0); + if (msg.msg_flags != 0) e(0); + + if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin, sizeof(sin)) != + val) e(0); + + buf[val - 1] = 'Y'; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = val - 1; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != val - 1) e(0); + if (msg.msg_flags != MSG_TRUNC) e(0); + + for (i = 0; i < val - 1; i += sizeof(int)) { + j = i ^ 0xdeadbeef; + if (memcmp(&buf[i], &j, MIN(sizeof(j), val - 1 - i))) e(0); + } + if (buf[val - 1] != 'Y') e(0); + + if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin, sizeof(sin)) != + val) e(0); + + buf[0] = 'Z'; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = 0; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != 0) e(0); + if (msg.msg_flags != MSG_TRUNC) e(0); + if (buf[0] != 'Z') e(0); + + /* Make sure that zero-sized packets can be sent and received. */ + if (sendto(fd2, buf, 0, 0, (struct sockaddr *)&sin, sizeof(sin)) != 0) + e(0); + + /* + * Note how we currently assume that packets sent over localhost will + * arrive immediately, so that we can use MSG_DONTWAIT to avoid that + * the test freezes. + */ + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, MSG_DONTWAIT) != 0) e(0); + if (msg.msg_flags != 0) e(0); + if (buf[0] != 'Z') e(0); + + if (recv(fd, buf, val, MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + /* + * When sending lots of small packets, ensure that fewer packets arrive + * than we sent. This sounds weird, but we cannot actually check the + * internal TCP/IP buffer granularity and yet we want to make sure that + * the receive queue is measured in terms of buffers rather than packet + * sizes. In addition, we check that older packets are favored, + * instead discarding new ones when the receive buffer is full. + */ + for (i = 0; i < 65536 / sizeof(j); i++) { + j = i; + if (sendto(fd2, &j, sizeof(j), 0, (struct sockaddr *)&sin, + sizeof(sin)) != sizeof(j)) e(0); + } + + for (i = 0; i < 1025; i++) { + r = recv(fd, &j, sizeof(j), MSG_DONTWAIT); + if (r == -1) { + if (errno != EWOULDBLOCK) e(0); + break; + } + if (r != sizeof(j)) e(0); + if (i != j) e(0); + } + if (i == 1025) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + free(buf); +} + +/* + * Test setting and retrieving IP-level options for the given socket type. For + * TCP sockets, we cannot test whether they are actually applied, but for UDP + * sockets, we do a more complete test later on. + */ +static void +sub91f(int type) +{ + socklen_t len; + int fd, val, def; + + /* Test IPv4 first. */ + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + /* Test obtaining the default TOS and TTL values. */ + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_TOS, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + len = sizeof(def); + if (getsockopt(fd, IPPROTO_IP, IP_TTL, &def, &len) != 0) e(0); + if (len != sizeof(def)) e(0); + if (def < 16 || def > UINT8_MAX) e(0); + + /* Test changing the TOS field. */ + for (val = 0; val <= UINT8_MAX; val++) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != 0) + e(0); + val = -1; /* not a special value for IPv4 */ + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + val = UINT8_MAX + 1; + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_TOS, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != UINT8_MAX) e(0); + + /* Test changing the TTL field. */ + for (val = 0; val <= UINT8_MAX; val++) + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != 0) + e(0); + val = 39; + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != 0) e(0); + val = -1; /* not a special value for IPv4 */ + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + val = UINT8_MAX + 1; + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_TTL, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 39) e(0); + + /* It must not be possible to set IPv6 options on IPv4 sockets. */ + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != -1) + e(0); + if (errno != ENOPROTOOPT) e(0); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, + sizeof(val)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, &len) != -1) + e(0); + if (errno != ENOPROTOOPT) e(0); + + if (close(fd) != 0) e(0); + + /* Test IPv6 next. */ + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + /* Test obtaining the default TCLASS and HOPS values. */ + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + len = sizeof(def); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &def, &len) != 0) + e(0); + if (len != sizeof(def)) e(0); + if (def < 16 || def > UINT8_MAX) e(0); + + /* Test changing the TCLASS field. */ + for (val = 0; val <= UINT8_MAX; val++) + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, + sizeof(val)) != 0) e(0); + val = -2; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != -1) + e(0); + if (errno != EINVAL) e(0); + val = UINT8_MAX + 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != -1) + e(0); + if (errno != EINVAL) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != UINT8_MAX) e(0); + + val = -1; /* reset to default */ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != 0) + e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + /* Test changing the HOPS field. */ + for (val = 0; val <= UINT8_MAX; val++) + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, + sizeof(val)) != 0) e(0); + val = 49; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, + sizeof(val)) != 0) e(0); + val = -2; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + val = UINT8_MAX + 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != 49) e(0); + + val = -1; /* reset to default */ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, + sizeof(val)) != 0) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, &len) != 0) + e(0); + if (len != sizeof(val)) e(0); + if (val != def) e(0); + + /* It must not be possible to set IPv4 options on IPv6 sockets. */ + val = 0; + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &val, sizeof(val)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_TOS, &val, &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + if (getsockopt(fd, IPPROTO_IP, IP_TTL, &val, &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test setting and retrieving IP-level options. + */ +static void +test91f(void) +{ + + subtest = 6; + + sub91f(SOCK_STREAM); + + sub91f(SOCK_DGRAM); +} + +/* + * Test setting and retrieving IP-level options on UDP sockets and packets. + * As part of this, ensure that the maximum set of supported control options + * can be both sent and received, both for IPv4 and IPv6. Any options that are + * newly added to the service and may be combined with the existing ones should + * be added to this subtest as well. The control data handling code is shared + * between UDP and RAW, so there is no need to repeat this test for the latter. + */ +static void +test91g(void) +{ + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg, cmsg2; + struct in_pktinfo ipi; + struct in6_pktinfo ipi6; + unsigned int ifindex; + char buf[1]; + union { + struct cmsghdr cmsg; + char buf[256]; + } control; + uint8_t byte; + size_t size; + int fd, fd2, val, seen_tos, seen_ttl, seen_pktinfo; + + subtest = 7; + + ifindex = if_nametoindex(LOOPBACK_IFNAME); + if (ifindex == 0) e(0); + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + val = 1; + /* Strangely, IP_RECVTOS is not a thing.. */ + if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &val, sizeof(val)) != 0) + e(0); + if (setsockopt(fd, IPPROTO_IP, IP_RECVPKTINFO, &val, sizeof(val)) != 0) + e(0); + + if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + iov.iov_base = "A"; + iov.iov_len = 1; + + val = 39; + control.cmsg.cmsg_len = CMSG_LEN(sizeof(val)); + control.cmsg.cmsg_level = IPPROTO_IP; + control.cmsg.cmsg_type = IP_TTL; + memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val)); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *)&sin; + msg.msg_namelen = sizeof(sin); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = control.cmsg.cmsg_len; + + if (sendmsg(fd2, &msg, 0) != 1) e(0); + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = sizeof(control); + + if (recvmsg(fd, &msg, 0) != 1) e(0); + if (buf[0] != 'A') e(0); + + seen_ttl = seen_pktinfo = 0; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != IPPROTO_IP) e(0); + switch (cmsg->cmsg_type) { + case IP_TTL: + /* The odd one out, using a uint8_t.. */ + if (seen_ttl++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(byte))) e(0); + memcpy(&byte, CMSG_DATA(cmsg), sizeof(byte)); + if (byte != 39) e(0); + break; + case IP_PKTINFO: + if (seen_pktinfo++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(ipi))) e(0); + memcpy(&ipi, CMSG_DATA(cmsg), sizeof(ipi)); + if (ipi.ipi_addr.s_addr != sin.sin_addr.s_addr) e(0); + if (ipi.ipi_ifindex != ifindex) e(0); + break; + default: + e(0); + } + } + if (!seen_ttl) e(0); + if (!seen_pktinfo) e(0); + + /* Test that we can provide all supported IPv4 options at once. */ + iov.iov_base = "B"; + iov.iov_len = 1; + + val = 1; + control.cmsg.cmsg_len = CMSG_LEN(sizeof(val)); + control.cmsg.cmsg_level = IPPROTO_IP; + control.cmsg.cmsg_type = IP_TOS; + memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val)); + + size = CMSG_SPACE(sizeof(val)); + + if ((cmsg = CMSG_NXTHDR(&msg, &control.cmsg)) == NULL) e(0); + val = 41; + cmsg2.cmsg_len = CMSG_LEN(sizeof(val)); + cmsg2.cmsg_level = IPPROTO_IP; + cmsg2.cmsg_type = IP_TTL; + memcpy(cmsg, &cmsg2, sizeof(cmsg2)); + memcpy(CMSG_DATA(cmsg), &val, sizeof(val)); + + size += CMSG_SPACE(sizeof(val)); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *)&sin; + msg.msg_namelen = sizeof(sin); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = size; + + if (sendmsg(fd2, &msg, 0) != 1) e(0); + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = sizeof(control); + + if (recvmsg(fd, &msg, 0) != 1) e(0); + if (buf[0] != 'B') e(0); + + /* Check just the TTL this time. */ + seen_ttl = 0; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != IPPROTO_IP) e(0); + if (cmsg->cmsg_type == IP_TTL) { + /* The odd one out, using a uint8_t.. */ + if (seen_ttl++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(byte))) e(0); + memcpy(&byte, CMSG_DATA(cmsg), sizeof(byte)); + if (byte != 41) e(0); + } + } + if (!seen_ttl) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* That was IPv4, onto IPv6.. */ + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &val, + sizeof(val)) != 0) e(0); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, + sizeof(val)) != 0) e(0); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + sizeof(val)) != 0) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + val = 94; + if (setsockopt(fd2, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)) != 0) + e(0); + + iov.iov_base = "C"; + iov.iov_len = 1; + + val = 39; + control.cmsg.cmsg_len = CMSG_LEN(sizeof(val)); + control.cmsg.cmsg_level = IPPROTO_IPV6; + control.cmsg.cmsg_type = IPV6_HOPLIMIT; + memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val)); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *)&sin6; + msg.msg_namelen = sizeof(sin6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = control.cmsg.cmsg_len; + + if (sendmsg(fd2, &msg, 0) != 1) e(0); + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = sizeof(control); + + if (recvmsg(fd, &msg, 0) != 1) e(0); + if (buf[0] != 'C') e(0); + + seen_tos = seen_ttl = seen_pktinfo = 0; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != IPPROTO_IPV6) e(0); + switch (cmsg->cmsg_type) { + case IPV6_TCLASS: + if (seen_tos++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0); + memcpy(&val, CMSG_DATA(cmsg), sizeof(val)); + if (val != 94) e(0); + break; + case IPV6_HOPLIMIT: + if (seen_ttl++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0); + memcpy(&val, CMSG_DATA(cmsg), sizeof(val)); + if (val != 39) e(0); + break; + case IPV6_PKTINFO: + if (seen_pktinfo++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(ipi6))) e(0); + memcpy(&ipi6, CMSG_DATA(cmsg), sizeof(ipi6)); + if (memcmp(&ipi6.ipi6_addr, &in6addr_loopback, + sizeof(ipi6.ipi6_addr))) e(0); + if (ipi6.ipi6_ifindex != ifindex) e(0); + break; + default: + e(0); + } + } + if (!seen_tos) e(0); + if (!seen_ttl) e(0); + if (!seen_pktinfo) e(0); + + /* + * Test that (for IPv6) an option of -1 overrides setsockopt. + * Also test that we can provide all supported IPv6 options at once. + */ + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + sizeof(val)) != 0) e(0); + + iov.iov_base = "D"; + iov.iov_len = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_control = control.buf; + msg.msg_controllen = sizeof(control.buf); + + val = -1; + control.cmsg.cmsg_len = CMSG_LEN(sizeof(val)); + control.cmsg.cmsg_level = IPPROTO_IPV6; + control.cmsg.cmsg_type = IPV6_TCLASS; + memcpy(CMSG_DATA(&control.cmsg), &val, sizeof(val)); + + size = CMSG_SPACE(sizeof(val)); + + if ((cmsg = CMSG_NXTHDR(&msg, &control.cmsg)) == NULL) e(0); + val = 78; + cmsg2.cmsg_len = CMSG_LEN(sizeof(val)); + cmsg2.cmsg_level = IPPROTO_IPV6; + cmsg2.cmsg_type = IPV6_HOPLIMIT; + memcpy(cmsg, &cmsg2, sizeof(cmsg2)); + memcpy(CMSG_DATA(cmsg), &val, sizeof(val)); + + size += CMSG_SPACE(sizeof(val)); + + if ((cmsg = CMSG_NXTHDR(&msg, cmsg)) == NULL) e(0); + cmsg2.cmsg_len = CMSG_LEN(sizeof(ipi6)); + cmsg2.cmsg_level = IPPROTO_IPV6; + cmsg2.cmsg_type = IPV6_PKTINFO; + memcpy(cmsg, &cmsg2, sizeof(cmsg2)); + memset(&ipi6, 0, sizeof(ipi6)); + memcpy(CMSG_DATA(cmsg), &ipi6, sizeof(ipi6)); + + size += CMSG_SPACE(sizeof(ipi6)); + + if (size > sizeof(control.buf)) e(0); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *)&sin6; + msg.msg_namelen = sizeof(sin6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = size; + + if (sendmsg(fd2, &msg, 0) != 1) e(0); + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control.buf; + msg.msg_controllen = sizeof(control); + + if (recvmsg(fd, &msg, 0) != 1) e(0); + if (buf[0] != 'D') e(0); + + seen_tos = seen_ttl = 0; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != IPPROTO_IPV6) e(0); + switch (cmsg->cmsg_type) { + case IPV6_TCLASS: + if (seen_tos++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0); + memcpy(&val, CMSG_DATA(cmsg), sizeof(val)); + if (val != 0) e(0); + break; + case IPV6_HOPLIMIT: + if (seen_ttl++) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(val))) e(0); + memcpy(&val, CMSG_DATA(cmsg), sizeof(val)); + if (val != 78) e(0); + break; + default: + e(0); + } + } + if (!seen_tos) e(0); + if (!seen_ttl) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); +} + +/* + * Test receiving IPv4 packets on IPv6 sockets. + */ +static void +test91h(void) +{ + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + struct in6_pktinfo ipi6; + unsigned int ifindex; + char buf[1], buf2[256]; + int fd, fd2, val; + + subtest = 8; + + ifindex = if_nametoindex(LOOPBACK_IFNAME); + if (ifindex == 0) e(0); + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + sizeof(val)) != 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (sendto(fd2, "A", 1, 0, (struct sockaddr *)&sin, sizeof(sin)) != 1) + e(0); + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *)&sin6; + msg.msg_namelen = sizeof(sin6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = buf2; + msg.msg_controllen = sizeof(buf2); + + if (recvmsg(fd, &msg, 0) != 1) e(0); + if (buf[0] != 'A') e(0); + + if (msg.msg_namelen != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) e(0); + if (cmsg->cmsg_level != IPPROTO_IPV6) e(0); + if (cmsg->cmsg_type != IPV6_PKTINFO) e(0); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(ipi6))) e(0); + + /* + * The packet was sent from loopback to loopback, both with IPv4-mapped + * IPv6 addresses, so we can simply compare source and destination. + */ + memcpy(&ipi6, CMSG_DATA(cmsg), sizeof(ipi6)); + if (memcmp(&sin6.sin6_addr, &ipi6.ipi6_addr, sizeof(sin6.sin6_addr))) + e(0); + if (ipi6.ipi6_ifindex != ifindex) e(0); + + if (CMSG_NXTHDR(&msg, cmsg) != NULL) e(0); + + /* + * Sqeeze in a quick test to see what happens if the receiver end does + * not provide a control buffer after having requested control data, + * because a half-complete version of this test triggered a bug there.. + */ + if (sendto(fd2, "B", 1, 0, (struct sockaddr *)&sin, sizeof(sin)) != 1) + e(0); + + if (recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL) != 1) e(0); + if (buf[0] != 'B') e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); +} + +/* + * Test that binding a socket of the given type to a privileged port is + * disallowed. + */ +static void +sub91i(int type) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + int fd, port; + + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + for (port = IPPORT_RESERVED - 1; port >= 0; port--) { + sin.sin_port = htons(port); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno == EADDRINUSE) continue; + if (errno != EACCES) e(0); + break; + } + + for (port = IPPORT_RESERVED; port <= UINT16_MAX; port++) { + sin.sin_port = htons(port); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == 0) + break; + if (errno != EADDRINUSE) e(0); + } + + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET6, type, 0)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + for (port = IPV6PORT_RESERVED - 1; port >= 0; port--) { + sin6.sin6_port = htons(port); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != -1) + e(0); + if (errno == EADDRINUSE) continue; + if (errno != EACCES) e(0); + break; + } + + for (port = IPV6PORT_RESERVED; port <= UINT16_MAX; port++) { + sin6.sin6_port = htons(port); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) == 0) + break; + if (errno != EADDRINUSE) e(0); + } + + if (close(fd) != 0) e(0); +} + +/* + * Test that binding to privileged ports is disallowed for non-root users. + * Also make sure that such users cannot create raw sockets at all. This test + * is not to be run by root, but for convenience we first try to drop + * privileges for the duration of the test anyway. + */ +static void +test91i(void) +{ + int i; + + subtest = 9; + + (void)seteuid(1); + + sub91i(SOCK_STREAM); + + sub91i(SOCK_DGRAM); + + for (i = 0; i < IPPROTO_MAX; i++) { + if (socket(AF_INET, SOCK_RAW, i) != -1) e(0); + if (errno != EACCES) e(0); + if (socket(AF_INET6, SOCK_RAW, i) != -1) e(0); + if (errno != EACCES) e(0); + } + + (void)seteuid(0); +} + +/* + * Test setting and getting basic UDP/RAW multicast transmission options. + */ +static void +test91j(void) +{ + + subtest = 10; + + socklib_multicast_tx_options(SOCK_DGRAM); +} + +/* + * Test TCP socket state changes related to the listen queue. This test is + * derived from test90y, but sufficiently different to be its own copy. + */ +static void +test91k(void) +{ + struct sockaddr_in6 sin6A, sin6B, sin6C; + socklen_t len; + struct timeval tv; + struct linger l; + fd_set fds; + char buf[7]; + int fd, fd2, fd3, fd4, val, fl; + + subtest = 11; + + if ((fd = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) e(0); + + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6A, 0, sizeof(sin6A)); + sin6A.sin6_family = AF_INET6; + sin6A.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr)); + + if (bind(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + /* + * Any socket options should be inherited from the listening socket at + * connect time, and not be re-inherited at accept time, to the extent + * that they are inherited at all. TCP/IP level options are not. + */ + val = 123; + if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val)) != 0) + e(0); + val = 32768; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0) + e(0); + + if (listen(fd, 5) != 0) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + memset(&sin6B, 0, sizeof(sin6B)); + sin6B.sin6_family = AF_INET6; + sin6B.sin6_port = htons(0); + memcpy(&sin6B.sin6_addr, &in6addr_loopback, sizeof(sin6B.sin6_addr)); + + val = 1; + if (setsockopt(fd2, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) != 0) + e(0); + + if (bind(fd2, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0); + + len = sizeof(sin6B); + if (getsockname(fd2, (struct sockaddr *)&sin6B, &len) != 0) e(0); + if (len != sizeof(sin6B)) e(0); + if (sin6B.sin6_port == htons(0)) e(0); + + if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + val = 456; + if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val)) != 0) + e(0); + val = 16384; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0) + e(0); + + /* + * Obtaining the peer name should work. As always, the name should be + * inherited from the listening socket. + */ + len = sizeof(sin6C); + if (getpeername(fd2, (struct sockaddr *)&sin6C, &len) != 0) e(0); + if (sin6C.sin6_len != sizeof(sin6C)) e(0); + if (sin6C.sin6_family != AF_INET6) e(0); + if (sin6C.sin6_port != htons(TEST_PORT_A)) e(0); + if (memcmp(&sin6C.sin6_addr, &in6addr_loopback, + sizeof(sin6C.sin6_addr)) != 0) e(0); + + /* + * Sending to the socket should work, and it should be possible to + * receive the data from the other side once accepted. + */ + if (send(fd2, "Hello, ", 7, 0) != 7) e(0); + if (send(fd2, "world!", 6, 0) != 6) e(0); + + /* Shutdown settings should be visible after accepting, too. */ + if (shutdown(fd2, SHUT_RDWR) != 0) e(0); + + memset(&sin6C, 0, sizeof(sin6C)); + len = sizeof(sin6C); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6C, &len)) < 0) e(0); + if (sin6C.sin6_len != sizeof(sin6C)) e(0); + if (sin6C.sin6_family != AF_INET6) e(0); + if (sin6C.sin6_port != sin6B.sin6_port) e(0); + if (memcmp(&sin6C.sin6_addr, &in6addr_loopback, + sizeof(sin6C.sin6_addr)) != 0) e(0); + + len = sizeof(val); + if (getsockopt(fd3, SOL_SOCKET, SO_SNDLOWAT, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 123) e(0); + + len = sizeof(val); + if (getsockopt(fd3, SOL_SOCKET, SO_RCVBUF, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 32768) e(0); + + if ((fl = fcntl(fd3, F_GETFL)) == -1) e(0); + if (!(fl & O_NONBLOCK)) e(0); + if (fcntl(fd3, F_SETFL, fl & ~O_NONBLOCK) != 0) e(0); + + if (recv(fd3, buf, 7, 0) != 7) e(0); + if (memcmp(buf, "Hello, ", 7) != 0) e(0); + if (recv(fd3, buf, 7, 0) != 6) e(0); + if (memcmp(buf, "world!", 6) != 0) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0); + + /* + * Unlike in the UDS test, the other side's shutdown-for-reading is not + * visible to this side, so sending data should work just fine until we + * close or shut down the socket ourselves. The other side will simply + * discard the incoming data. + */ + if (send(fd3, "", 1, MSG_NOSIGNAL) != 1) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd3) != 0) e(0); + + /* + * If the connection pending acceptance is closed, the connection must + * remain on the queue, and the accepting party will read EOF from it. + * Try once without pending data, once with pending data. + */ + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if (close(fd2) != 0) e(0); + + len = sizeof(sin6B); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0); + + len = sizeof(val); + if (getsockopt(fd3, SOL_SOCKET, SO_SNDLOWAT, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 456) e(0); + + len = sizeof(val); + if (getsockopt(fd3, SOL_SOCKET, SO_RCVBUF, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 16384) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0); + + if (close(fd3) != 0) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if (send(fd2, "Hello!", 6, 0) != 6) e(0); + if (close(fd2) != 0) e(0); + + len = sizeof(sin6B); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != 6) e(0); + if (memcmp(buf, "Hello!", 6) != 0) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0); + + if (close(fd3) != 0) e(0); + + /* + * If the connection pending acceptance is aborted, the listening + * socket should pretend as though the connection was never there. + */ + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 0; + tv.tv_usec = 0; + if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0); + if (!FD_ISSET(fd, &fds)) e(0); + + memset(&l, 0, sizeof(l)); + l.l_onoff = 1; + l.l_linger = 0; + if (setsockopt(fd2, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0); + + if (close(fd2) != 0) e(0); + + if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0); + if (FD_ISSET(fd, &fds)) e(0); + + len = sizeof(sin6B); + if (accept(fd, (struct sockaddr *)&sin6B, &len) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + /* + * Try the same thing, but now with the connection sandwiched between + * two different pending connections, which should be left intact. + */ + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if (send(fd2, "A", 1, 0) != 1) e(0); + + if ((fd3 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd3, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if (send(fd3, "B", 1, 0) != 1) e(0); + + if ((fd4 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd4, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if (send(fd4, "C", 1, 0) != 1) e(0); + + if (setsockopt(fd3, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0); + + if (close(fd3) != 0) e(0); + + len = sizeof(sin6B); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != 1) e(0); + if (buf[0] != 'A') e(0); + + if (close(fd3) != 0) e(0); + if (close(fd2) != 0) e(0); + + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 0; + tv.tv_usec = 0; + if (select(fd + 1, &fds, NULL, NULL, &tv) != 1) e(0); + if (!FD_ISSET(fd, &fds)) e(0); + + len = sizeof(sin6B); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6B, &len)) < 0) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != 1) e(0); + if (buf[0] != 'C') e(0); + + if (close(fd3) != 0) e(0); + if (close(fd4) != 0) e(0); + + if (select(fd + 1, &fds, NULL, NULL, &tv) != 0) e(0); + if (FD_ISSET(fd, &fds)) e(0); + + len = sizeof(sin6B); + if (accept(fd, (struct sockaddr *)&sin6B, &len) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + /* + * If the listening socket was closed, the sockets pending acceptance + * should be reset. + */ + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd2, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if ((fd3 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd3, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if (close(fd) != 0) e(0); + + if (recv(fd2, buf, sizeof(buf), 0) != -1) e(0); + if (errno != ECONNRESET) e(0); + + if (recv(fd2, buf, sizeof(buf), 0) != 0) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != -1) e(0); + if (errno != ECONNRESET) e(0); + + if (recv(fd3, buf, sizeof(buf), 0) != 0) e(0); + + if (close(fd3) != 0) e(0); + + if (close(fd2) != 0) e(0); +} + +/* + * Obtain a pair of connected TCP socket. + */ +static int +get_tcp_pair(int domain, int type, int protocol, int fd[2]) +{ + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + struct sockaddr *addr; + socklen_t addr_len, len; + int lfd, val; + + if (domain == AF_INET6) { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, + sizeof(sin6.sin6_addr)); + + addr = (struct sockaddr *)&sin6; + addr_len = sizeof(sin6); + } else { + assert(domain == AF_INET); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + addr = (struct sockaddr *)&sin; + addr_len = sizeof(sin); + } + + if ((lfd = socket(domain, type, protocol)) < 0) e(0); + + if (bind(lfd, addr, addr_len) != 0) e(0); + + len = addr_len; + if (getsockname(lfd, addr, &len) != 0) e(0); + if (len != addr_len) e(0); + + if (listen(lfd, 1) != 0) e(0); + + if ((fd[0] = socket(domain, type, protocol)) < 0) e(0); + + val = 1; + if (setsockopt(fd[0], IPPROTO_TCP, TCP_NODELAY, &val, + sizeof(val)) != 0) e(0); + + if (connect(fd[0], addr, addr_len) != 0) e(0); + + len = addr_len; + if ((fd[1] = accept(lfd, addr, &len)) < 0) e(0); + if (len != addr_len) e(0); + + if (setsockopt(fd[1], IPPROTO_TCP, TCP_NODELAY, &val, + sizeof(val)) != 0) e(0); + + if (close(lfd) != 0) e(0); + + return 0; +} + +/* + * Test large transfers and MSG_WAITALL. + */ +static void +test91l(void) +{ + int fd[2]; + + subtest = 12; + + get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd); + + socklib_large_transfers(fd); + + get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd); + + socklib_large_transfers(fd); +} + +/* + * A randomized producer-consumer test for stream sockets. As part of this, + * we also perform very basic bulk functionality tests of FIONREAD, MSG_PEEK, + * MSG_DONTWAIT, and MSG_WAITALL. + */ +static void +test91m(void) +{ + int fd[2]; + + subtest = 13; + + get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd); + + socklib_producer_consumer(fd); + + get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd); + + socklib_producer_consumer(fd); +} + +/* + * Cause a receive call on the peer side of the connection of 'fd' to be + * aborted in a protocol-specific way. Return -1 to indicate that the given + * file descriptor has been closed. + */ +static int +test91_reset(int fd, const char * data __unused, size_t len __unused) +{ + struct linger l; + + l.l_onoff = 1; + l.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0); + + if (close(fd) != 0) e(0); + + return -1; +} + +/* + * Test for receiving on stream sockets. In particular, test SO_RCVLOWAT, + * MSG_PEEK, MSG_DONTWAIT, and MSG_WAITALL. + */ +static void +test91n(void) +{ + + subtest = 14; + + socklib_stream_recv(get_tcp_pair, AF_INET, SOCK_STREAM, + test91_reset); +} + +/* + * Return the send and receive buffer sizes for sockets of the given type. The + * two individual values are stored in 'sndbuf' and 'rcvbuf', for each that is + * not NULL, and the sum is returned from the call. + */ +static int +get_buf_sizes(int type, int * sndbufp, int * rcvbufp) +{ + socklen_t len; + int fd, sndbuf, rcvbuf; + + if ((fd = socket(AF_INET, type, 0)) < 0) e(0); + + len = sizeof(sndbuf); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &len) != 0) e(0); + if (len != sizeof(sndbuf)) e(0); + if (sndbufp != NULL) + *sndbufp = sndbuf; + + len = sizeof(rcvbuf); + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) != 0) e(0); + if (len != sizeof(rcvbuf)) e(0); + if (rcvbufp != NULL) + *rcvbufp = rcvbuf; + + if (close(fd) != 0) e(0); + + return sndbuf + rcvbuf; +} + +/* + * The following constant should be set to the window size used within lwIP. + * There is currently no way to obtain this constant from the LWIP service, nor + * would that be information that should ever be used by general applications, + * but we need it to fill socket receive queues in a reliable way. TODO: find + * a better solution for this general problem. + */ +#define WINDOW_SIZE 16384 /* TCP_WND in lwipopt.h, keep in sync! */ + +#define CHUNK 4096 /* base I/O chunk size */ +#define USLEEP_TIME 250000 /* increase on wimpy platforms if needed */ + +/* + * Fill the receive of socket 'rfd' with data, and if 'fill_send' is non-zero, + * also the send queue of socket 'sfd'. If 'fill_send' is zero, 'delta' may be + * a non-zero value indicating how many bytes extra (delta > 0) or fewer + * (delta < 0) should be sent compared to the receive queue size. + */ +static void +fill_tcp_bufs(int sfd, int rfd, int fill_send, int delta) +{ + unsigned char buf[CHUNK], c; + socklen_t len; + int sndbuf, rcvbuf, mss, chunk, left, res; + + assert(!fill_send || delta == 0); + + (void)get_buf_sizes(SOCK_STREAM, &sndbuf, &rcvbuf); + + len = sizeof(mss); + if (getsockopt(sfd, IPPROTO_TCP, TCP_MAXSEG, &mss, &len) != 0) e(0); + + left = rcvbuf; + if (delta < 0) + left += delta; + + memset(buf, 0, sizeof(buf)); + + /* + * In general, TCP is not designed for what we want to do here, which + * is to control the contents of the receive buffer down to the last + * byte. We already assume that the caller has disabled the Nagle + * algorithm, but we still have to deal with other algorithms that + * effectively get in the way of full control of the receive buffer. + * + * In particular, we have to work around an issue where lwIP decides to + * start shrinking the window earlier than necessary. This issue + * triggers during the transition from a fully open window to a reduced + * window. If no acknowledgement is sent when exactly that point is + * reached, the next acknowlegment will not announce the full size of + * the remainder of the window. This appears to be part of the silly + * window avoidance logic, so it is probably intentional behavior and + * thus we have to work around it. + * + * So far it appears that filling up just the window size does the job, + * as long as the last segment is a full MSS-sized segment and each + * segment is acknowledged (which is why we send data in the other + * direction). Anything short of that may trigger edge cases that, in + * some cases, show up only on slow platforms (e.g. BeagleBones). + * + * Note that while test91z also fills up receive queues using its own + * algorithm, it sets the receive queue to the window size, thereby + * avoiding the need for this more complicated algorithm. + */ + for (left = rcvbuf - WINDOW_SIZE; left > 0; left -= chunk) { + chunk = (left % mss != 0) ? (left % mss) : mss; + assert(chunk <= left); + + if (send(sfd, buf, chunk, 0) != chunk) e(0); + + if (send(rfd, ".", 1, 0) != 1) e(0); + + if (recv(sfd, &c, 1, 0) != 1) e(0); + if (c != '.') e(0); + } + + /* We are done with the hard part. Now fill up the rest. */ + if (fill_send) + delta = sndbuf; + + for (left = WINDOW_SIZE + delta; left > 0; left -= res) { + chunk = MIN(left, sizeof(buf)); + + res = send(sfd, buf, chunk, 0); + + if (res <= 0) e(0); + if (res > chunk) e(0); + } +} + +/* + * Signal handler which just needs to exist, so that invoking it will interrupt + * an ongoing system call. + */ +static void +test91_got_signal(int sig __unused) +{ + + /* Nothing. */ +} + +/* + * Test for sending on stream sockets. The quick summary here is that send() + * should basically act as the mirror of recv(MSG_WAITALL), i.e., it should + * keep suspending until all data is sent (or the call is interrupted or no + * more can possibly be sent), and, SO_SNDLOWAT, mirroring SO_RCVLOWAT, acts as + * an admission test for the send: nothing is sent until there is room in the + * send buffer (i.e., the peer's receive buffer) for at least the low send + * watermark, or the whole send request length, whichever is smaller. In + * addition, select(2) should use the same threshold. + * + * This test is a copy of test90v, and would be in socklib instead, were it not + * for the fact that TCP's segmentation and silly window avoidance make it + * impossible to perform the same exact, byte-granular test. Instead, this TCP + * implementation paints with a somewhat broader brush, using send and receive + * chunk sizes large enough to overcome the normally desirable TCP features + * that are now getting in the way. As a result, this copy of the test is not + * only somewhat less effective but also a bit more reliant on specific (TCP) + * settings, although the whole test is still way too useful to skip at all. + */ +static void +sub91o(int iroom, int istate, int slowat, int len, int bits, int act) +{ + struct sigaction sa; + struct timeval tv; + char buf[CHUNK * 4]; + fd_set fds; + pid_t pid; + int fd[2], min, flags, res, err; + int pfd[2], orig_iroom, eroom, tstate, fl, status; + + if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0); + + /* + * Set up the initial condition on the sockets. + */ + fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/); + + /* + * Receive a bit more than we send, to free up enough room (the MSS) to + * get things going again. + */ + orig_iroom = iroom; + iroom += iroom / 2; + if (iroom > 0) + if (recv(fd[1], buf, iroom, 0) != iroom) e(0); + + switch (istate) { + case 0: break; + case 1: if (shutdown(fd[0], SHUT_WR) != 0) e(0); break; + case 2: if (close(fd[1]) != 0) e(0); break; + } + + if (setsockopt(fd[0], SOL_SOCKET, SO_SNDLOWAT, &slowat, + sizeof(slowat)) != 0) e(0); + + /* SO_SNDLOWAT is always bounded by the actual send length. */ + min = MIN(len, slowat); + + flags = MSG_NOSIGNAL; + if (bits & 1) flags |= MSG_DONTWAIT; + + /* + * Do a quick select test to see if its result indeed matches whether + * the available space in the "send" buffer meets the threshold. + */ + FD_ZERO(&fds); + FD_SET(fd[0], &fds); + tv.tv_sec = 0; + tv.tv_usec = 0; + res = select(fd[0] + 1, NULL, &fds, NULL, &tv); + if (res < 0 || res > 1) e(0); + if (res != (iroom >= slowat || istate > 0)) e(0); + if (res == 1 && !FD_ISSET(fd[0], &fds)) e(0); + + /* + * Cut short a whole lot of cases, to avoid the overhead of forking, + * namely when we know the call should return immediately. This is the + * case when the socket state disallows further sending, or when all + * data could be sent, or when the call was non-blocking. The low + * send watermark only helps determine whether anything was sent here. + */ + if (istate > 0 || iroom >= len || (flags & MSG_DONTWAIT)) { + res = send(fd[0], buf, len, flags); + + if (istate > 0) { + if (res != -1) e(0); + if (errno != EPIPE && errno != ECONNRESET) e(0); + } else if (iroom >= len) { + if (res != len) e(0); + } else if (iroom >= min) { + if (res < orig_iroom || res > iroom) e(0); + } else { + if (res != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + } + + /* Early cleanup and return to avoid even more code clutter. */ + if (istate != 2 && close(fd[1]) != 0) e(0); + if (close(fd[0]) != 0) e(0); + + return; + } + + /* + * Now starts the interesting stuff: the send call should now block, + * even though if we add MSG_DONTWAIT it may not return EWOULDBLOCK, + * because MSG_DONTWAIT prevents the send from blocking after partial + * completion. As such, we can only test our expectations by letting + * the call block, in a child process, and waiting. We do test as much + * of the above assumption as we can for safety right here, but this is + * not a substitute for actually blocking even in these cases! + */ + if (iroom < min) { + if (send(fd[0], buf, len, flags | MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + } + + /* + * If (act < 9), we receive 0, 1, or 2 bytes from the receive queue + * before forcing the send call to terminate in one of three ways. + * + * If (act == 9), we use a signal to interrupt the send call. + */ + if (act < 9) { + eroom = (act % 3) * (CHUNK + CHUNK / 2 - 1); + tstate = act / 3; + } else + eroom = tstate = 0; + + if (pipe2(pfd, O_NONBLOCK) != 0) e(0); + + pid = fork(); + switch (pid) { + case 0: + errct = 0; + + if (close(fd[1]) != 0) e(0); + if (close(pfd[0]) != 0) e(0); + + if (act == 9) { + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = test91_got_signal; + if (sigaction(SIGUSR1, &sa, NULL) != 0) e(0); + } + + res = send(fd[0], buf, len, flags); + err = errno; + + if (write(pfd[1], &res, sizeof(res)) != sizeof(res)) e(0); + if (write(pfd[1], &err, sizeof(err)) != sizeof(err)) e(0); + + exit(errct); + case -1: + e(0); + } + + if (close(pfd[1]) != 0) e(0); + + /* + * Allow the child to enter the blocking send(2), and check the pipe + * to see if it is really blocked. + */ + if (usleep(USLEEP_TIME) != 0) e(0); + + if (read(pfd[0], &res, sizeof(res)) != -1) e(0); + if (errno != EAGAIN) e(0); + + if (eroom > 0) { + if (recv(fd[1], buf, eroom, 0) != eroom) e(0); + + /* + * The threshold for the send is now met if the entire request + * has been satisfied. + */ + if (iroom + eroom >= len) { + if ((fl = fcntl(pfd[0], F_GETFL, 0)) == -1) e(0); + if (fcntl(pfd[0], F_SETFL, fl & ~O_NONBLOCK) != 0) + e(0); + + if (read(pfd[0], &res, sizeof(res)) != sizeof(res)) + e(0); + if (read(pfd[0], &err, sizeof(err)) != sizeof(err)) + e(0); + + if (res != len) e(0); + + /* Bail out. */ + goto cleanup; + } + } + + if (act < 9) { + /* + * Now test various ways to terminate the send call. + * + * For other socket drivers, there should also be a case where + * a socket error is raised instead. For UDS there is no way + * to do that on stream-type sockets, not even with SO_LINGER. + */ + switch (tstate) { + case 0: if (shutdown(fd[0], SHUT_WR) != 0) e(0); break; + case 1: if (close(fd[1]) != 0) e(0); fd[1] = -1; break; + case 2: fd[1] = test91_reset(fd[1], NULL, 0); break; + } + } else + if (kill(pid, SIGUSR1) != 0) e(0); + + if ((fl = fcntl(pfd[0], F_GETFL, 0)) == -1) e(0); + if (fcntl(pfd[0], F_SETFL, fl & ~O_NONBLOCK) != 0) e(0); + + if (read(pfd[0], &res, sizeof(res)) != sizeof(res)) e(0); + if (read(pfd[0], &err, sizeof(err)) != sizeof(err)) e(0); + + /* + * If the send met the threshold before being terminate or interrupted, + * we should at least have sent something. Otherwise, the send was + * never admitted and should return EPIPE or ECONNRESET (if the send + * was terminated) or EINTR (if the child was killed). + */ + if (iroom + eroom >= min) { + if (res < MIN(orig_iroom, len)) e(0); + if (res > MIN(iroom + eroom, len)) e(0); + } else { + if (res != -1) e(0); + if (act < 9) { + if (err != EPIPE && err != ECONNRESET) e(0); + } else + if (err != EINTR) e(0); + } + +cleanup: + if (close(pfd[0]) != 0) e(0); + + if (wait(&status) != pid) e(0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0); + + if (fd[1] != -1 && close(fd[1]) != 0) e(0); + if (close(fd[0]) != 0) e(0); +} + +/* + * Test for sending on stream sockets. In particular, test SO_SNDLOWAT and + * MSG_DONTWAIT. + */ +static void +test91o(void) +{ + int iroom, istate, slowat, len, bits, act; + + subtest = 15; + + /* Insanity. */ + for (iroom = 0; iroom <= CHUNK * 2; iroom += CHUNK) + for (istate = 0; istate <= 2; istate++) + for (slowat = CHUNK; slowat <= CHUNK * 2; + slowat += CHUNK) + for (len = CHUNK; len <= CHUNK * 2; + len += CHUNK) + for (bits = 0; bits < 2; bits++) + for (act = 0; act <= 9; act++) + sub91o(iroom, istate, + slowat, len, bits, + act); +} + +/* + * Test filling up the TCP receive queue. In particular, verify that one bug I + * ran into (lwIP bug #49128) is resolved. + */ +static void +test91p(void) +{ + char buf[CHUNK]; + size_t total, left; + ssize_t res; + int fd[2]; + + subtest = 16; + + if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0); + + /* + * Fill up the sockets' queues. + */ + total = get_buf_sizes(SOCK_STREAM, NULL, NULL); + + fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/); + + /* + * Wait long enough for the zero window probing to kick in, which used + * to cause an ACK storm livelock (lwIP bug #49128). + */ + sleep(1); + + /* + * Actually sleep a bit longer, so that the polling timer kicks in and + * at least attempts to send more. This is merely an attempt to + * exercise some of the polling code, and should not have any actual + * effect on the rest of the test. + */ + sleep(5); + + /* + * Make sure all the data still arrives. + */ + for (left = total; left > 0; left -= res) { + res = recv(fd[1], buf, sizeof(buf), 0); + if (res <= 0) e(0); + if (res > left) e(0); + } + + if (recv(fd[1], buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + /* + * Attempt to shut down the socket for writing after filling up the + * send queue. The TCP FIN should then arrive after all the data. + */ + for (left = total; left > 0; left -= res) { + res = send(fd[0], buf, MIN(left, sizeof(buf)), 0); + if (res <= 0) e(0); + if (res > left) e(0); + } + + if (shutdown(fd[0], SHUT_WR) != 0) e(0); + + for (left = total; left > 0; left -= res) { + res = recv(fd[1], buf, sizeof(buf), 0); + if (res <= 0) e(0); + if (res > left) e(0); + } + + if (recv(fd[1], buf, sizeof(buf), 0) != 0) e(0); + + if (send(fd[1], "A", 1, 0) != 1) e(0); + + if (recv(fd[0], buf, sizeof(buf), 0) != 1) e(0); + if (buf[0] != 'A') e(0); + + if (close(fd[1]) != 0) e(0); + if (close(fd[0]) != 0) e(0); +} + +/* + * Attempt to fill up a TCP send queue with small amounts of data. While it + * may or may not be possible to fill up the entire send queue with small + * requests, but at least trying should not cause any problems, like the one I + * filed as lwIP bug #49218. + */ +static void +test91q(void) +{ + ssize_t res; + size_t count; + char c, c2; + int fd[2]; + + subtest = 17; + + if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0); + + count = 0; + for (c = 0; (res = send(fd[0], &c, sizeof(c), MSG_DONTWAIT)) > 0; c++) + count += res; + if (res != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + if (count < CHUNK) e(0); + + if (shutdown(fd[0], SHUT_WR) != 0) e(0); + + for (c2 = 0; count > 0; count--, c2++) { + if (recv(fd[1], &c, sizeof(c), 0) != 1) e(0); + if (c != c2) e(0); + } + + if (recv(fd[1], &c, sizeof(c), 0) != 0) e(0); + + if (close(fd[0]) != 0) e(0); + if (close(fd[1]) != 0) e(0); +} + +/* + * Test that SO_RCVLOWAT is limited to the size of the receive buffer. + */ +static void +sub91r_recv(int fill_delta, int rlowat_delta, int exp_delta) +{ + char *buf; + size_t buflen; + int fd[2], rlowat, rcvlen, res; + + if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0); + + /* + * Fill up the socket's receive queue, possibly minus one byte. + */ + (void)get_buf_sizes(SOCK_STREAM, NULL, &rcvlen); + + buflen = MAX(CHUNK, rcvlen + 1); + if ((buf = malloc(buflen)) == NULL) e(0); + + fill_tcp_bufs(fd[1], fd[0], 0 /*fill_send*/, fill_delta); + + rlowat = rcvlen + rlowat_delta; + if (setsockopt(fd[0], SOL_SOCKET, SO_RCVLOWAT, &rlowat, + sizeof(rlowat)) != 0) e(0); + + if (ioctl(fd[0], FIONREAD, &res) != 0) e(0); + if (res != rcvlen + fill_delta) e(0); + + res = recv(fd[0], buf, rcvlen + 1, MSG_DONTWAIT); + if (exp_delta < 0) { + if (res != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + } else + if (res != rcvlen - exp_delta) e(0); + + free(buf); + + if (close(fd[0]) != 0) e(0); + if (close(fd[1]) != 0) e(0); +} + +/* + * Test that SO_SNDLOWAT is limited to the size of the send buffer. + */ +static void +sub91r_send(int fill, int slowat_delta, int exp_delta) +{ + char *buf; + size_t buflen; + int fd[2], sndlen, slowat, res; + + if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0); + + /* + * Fill up the socket's receive queue, and possibly put one extra byte + * in the other socket's send queue. + */ + (void)get_buf_sizes(SOCK_STREAM, &sndlen, NULL); + + buflen = MAX(CHUNK, sndlen + 1); + if ((buf = malloc(buflen)) == NULL) e(0); + + memset(buf, 0, buflen); + + fill_tcp_bufs(fd[0], fd[1], 0 /*fill_send*/, 0 /*delta*/); + + slowat = sndlen + slowat_delta; + + if (fill > 0) { + memset(buf, 0, fill); + + if (send(fd[0], buf, fill, 0) != fill) e(0); + } + + if (setsockopt(fd[0], SOL_SOCKET, SO_SNDLOWAT, &slowat, + sizeof(slowat)) != 0) e(0); + + res = send(fd[0], buf, sndlen + 1, MSG_DONTWAIT); + if (exp_delta < 0) { + if (res != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + } else + if (res != sndlen - exp_delta) e(0); + + free(buf); + + if (close(fd[0]) != 0) e(0); + if (close(fd[1]) != 0) e(0); +} + +/* + * Test that on stream sockets, SO_RCVLOWAT and SO_SNDLOWAT are limited to + * their respective buffer sizes. This test is derived from test90w, but + * merging the two into socklib would get too messy unfortunately. + */ +static void +test91r(void) +{ + + subtest = 18; + + /* + * With the receive buffer filled except for one byte, all data should + * be retrieved unless the threshold is not met. + */ + sub91r_recv(-1, -1, 1); + sub91r_recv(-1, 0, -1); + sub91r_recv(-1, 1, -1); + + /* + * With the receive buffer filled completely, all data should be + * retrieved in all cases. + */ + sub91r_recv(0, -1, 0); + sub91r_recv(0, 0, 0); + sub91r_recv(0, 1, 0); + + /* + * With a send buffer that contains one byte, all data should be sent + * unless the threshold is not met. + */ + sub91r_send(1, -1, 1); + sub91r_send(1, 0, -1); + sub91r_send(1, 1, -1); + + /* + * With the send buffer filled completely, all data should be sent + * in all cases. + */ + sub91r_send(0, -1, 0); + sub91r_send(0, 0, 0); + sub91r_send(0, 1, 0); +} + +/* + * Test sending and receiving with bad pointers on a TCP socket. + */ +static void +sub91s_tcp(char * ptr) +{ + int fd[2]; + + memset(ptr, 'X', PAGE_SIZE); + + if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0); + + if (send(fd[0], "A", 1, 0) != 1) e(0); + + if (send(fd[0], ptr, PAGE_SIZE * 2, MSG_DONTWAIT) != -1) e(0); + if (errno != EFAULT) e(0); + + if (send(fd[0], "B", 1, 0) != 1) e(0); + + if (shutdown(fd[0], SHUT_WR) != 0) e(0); + + if (recv(fd[1], &ptr[PAGE_SIZE - 1], PAGE_SIZE, MSG_WAITALL) != -1) + e(0); + if (errno != EFAULT) e(0); + + if (recv(fd[1], ptr, 3, MSG_DONTWAIT) != 2) e(0); + if (ptr[0] != 'A') e(0); + if (ptr[1] != 'B') e(0); + + if (close(fd[0]) != 0) e(0); + if (close(fd[1]) != 0) e(0); +} + +/* + * Test sending and receiving with bad pointers on a UDP socket. + */ +static void +sub91s_udp(char * ptr) +{ + struct sockaddr_in6 sin6; + int i, fd; + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + memset(ptr, 'A', PAGE_SIZE); + + if (sendto(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0, + (struct sockaddr *)&sin6, sizeof(sin6)) != -1) e(0); + if (errno != EFAULT) e(0); + + memset(ptr, 'B', PAGE_SIZE); + + if (sendto(fd, ptr, PAGE_SIZE, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != PAGE_SIZE) e(0); + + memset(ptr, 0, PAGE_SIZE); + + if (recvfrom(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0, NULL, 0) != -1) + e(0); + if (errno != EFAULT) e(0); + + if (recvfrom(fd, ptr, PAGE_SIZE * 2, 0, NULL, 0) != PAGE_SIZE) e(0); + for (i = 0; i < PAGE_SIZE; i++) + if (ptr[i] != 'B') e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test sending and receiving with bad pointers. + */ +static void +test91s(void) +{ + char *ptr; + + subtest = 19; + + if ((ptr = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED) e(0); + + if (munmap(&ptr[PAGE_SIZE], PAGE_SIZE) != 0) e(0); + + sub91s_tcp(ptr); + sub91s_udp(ptr); + + if (munmap(ptr, PAGE_SIZE) != 0) e(0); +} + +/* + * Test closing TCP sockets and SO_LINGER. + */ +static void +test91t(void) +{ + char buf[CHUNK]; + size_t total, left; + ssize_t res; + int i, fd[2]; + + subtest = 20; + + total = get_buf_sizes(SOCK_STREAM, NULL, NULL); + + memset(buf, 0, sizeof(buf)); + + /* + * Test two cases of handling connection closure: + * + * 1) the FIN+ACK case, where the closing side finishes the close + * operation once its FIN has been acknowledged; + * 2) the FIN+FIN case, where the closing side finishes the close + * operation once it has sent its own FIN (possibly without getting + * an ACK yet) and also receives a FIN from the other side. + * + * Since lwIP prevents us from detecting #1 without polling, which + * happens twice a second, we can test #2 by shutting down the peer + * connection immediately after (i=0/2) or even before (i=4/5) closing + * this side. + */ + for (i = 0; i <= 5; i++) { + if (get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd) != 0) e(0); + + fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/); + + if (close(fd[0]) != 0) e(0); + + if (i >= 4 && shutdown(fd[1], SHUT_WR) != 0) e(0); + + for (left = total; left > 0; left -= res) { + res = recv(fd[1], buf, sizeof(buf), 0); + if (res <= 0) e(0); + if (res > left) e(0); + } + + if (recv(fd[1], buf, sizeof(buf), 0) != 0) e(0); + + sleep(i & 1); + + /* + * We can still send to the receiving end, but this will cause + * a reset. We do this only if we have not just shut down the + * writing end of this socket. Also test regular closing. + */ + if (i / 2 == 1) { + if (send(fd[1], "B", 1, 0) != 1) e(0); + + if (recv(fd[1], buf, sizeof(buf), 0) != -1) e(0); + if (errno != ECONNRESET) e(0); + } + + if (close(fd[1]) != 0) e(0); + } + + /* + * Test that closing a socket with data still in its receive queue + * causes a RST to be issued. + */ + if (get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd) != 0) e(0); + + if (send(fd[0], "C", 1, 0) != 1) e(0); + + if (recv(fd[1], buf, sizeof(buf), MSG_PEEK) != 1) e(0); + + if (close(fd[1]) != 0) e(0); + + if (recv(fd[0], buf, sizeof(buf), 0) != -1) e(0); + if (errno != ECONNRESET) e(0); + + if (close(fd[0]) != 0) e(0); +} + +/* + * Test closing a socket with a particular SO_LINGER setting. + */ +static void +sub91u(int nb, int mode, int intr, int onoff, int linger) +{ + char buf[CHUNK]; + struct timeval tv1, tv2; + struct linger l; + pid_t pid; + int fd[2], pfd[2], fl, val, res, status; + + get_tcp_pair((mode & 1) ? AF_INET6 : AF_INET, SOCK_STREAM, 0, fd); + + /* + * Set up the socket pair. + */ + fill_tcp_bufs(fd[0], fd[1], 0 /*fill_send*/, 1 /*delta*/); + + if (mode == 3 && shutdown(fd[1], SHUT_WR) != 0) e(0); + + l.l_onoff = onoff; + l.l_linger = (linger) ? (2 + intr) : 0; + if (setsockopt(fd[0], SOL_SOCKET, SO_LINGER, &l, sizeof(l)) != 0) e(0); + + if (nb) { + if ((fl = fcntl(fd[0], F_GETFL)) == -1) e(0); + if (fcntl(fd[0], F_SETFL, fl | O_NONBLOCK) != 0) e(0); + } + + /* We need two-way parent-child communication for this test. */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0) e(0); + + pid = fork(); + switch (pid) { + case 0: + errct = 0; + + if (close(pfd[1]) != 0) e(0); + + if (close(fd[1]) != 0) e(0); + + signal(SIGUSR1, test91_got_signal); + + /* + * Do not start closing the file descriptor until after the + * parent has closed its copy. + */ + if (read(pfd[0], &val, sizeof(val)) != sizeof(val)) e(0); + if (val != 0) e(0); + + if (gettimeofday(&tv1, NULL) != 0) e(0); + + /* Perform the possibly blocking close(2) call. */ + if (intr) { + if (close(fd[0]) != -1) e(0); + if (errno != EINPROGRESS) e(0); + } else + if (close(fd[0]) != 0) e(0); + + if (gettimeofday(&tv2, NULL) != 0) e(0); + + timersub(&tv2, &tv1, &tv1); + + /* Polling may take 500ms. */ + val = tv1.tv_sec + ((tv1.tv_usec > 750000) ? 1 : 0); + + if (val < 0 || val > 2) e(0); + + /* Tell the parent how long the close(2) took, in seconds. */ + if (write(pfd[0], &val, sizeof(val)) != sizeof(val)) e(0); + + exit(errct); + case -1: + e(0); + } + + /* Close file descriptors here and then let the child run. */ + if (close(pfd[0]) != 0) e(0); + + if (close(fd[0]) != 0) e(0); + + val = 0; + if (write(pfd[1], &val, sizeof(val)) != sizeof(val)) e(0); + + /* + * Wait one second until we try to close the connection ourselves, if + * applicable. If we are killing the child, we add yet another second + * to tell the difference between a clean close and a timeout/reset. + */ + sleep(1); + + if (intr) { + if (kill(pid, SIGUSR1) != 0) e(0); + + sleep(1); + } + + /* + * Trigger various ways in which the connection is closed, or not, in + * which case the linger timeout should cause a reset. + */ + switch (mode) { + case 0: /* do nothing; expect reset */ + break; + + case 1: /* FIN + rFIN */ + if (shutdown(fd[1], SHUT_WR) != 0) e(0); + + /* + * The FIN cannot yet be sent due to the zero-sized receive + * window. Make some room so that it can be sent. + */ + /* FALLTHROUGH */ + case 2: /* FIN + ACK */ + case 3: /* rFIN + FIN */ + if (recv(fd[1], buf, sizeof(buf), 0) <= 0) e(0); + break; + + case 4: /* RST */ + l.l_onoff = 1; + l.l_linger = 0; + if (setsockopt(fd[1], SOL_SOCKET, SO_LINGER, &l, + sizeof(l)) != 0) e(0); + + if (close(fd[1]) != 0) e(0); + fd[1] = -1; + break; + + default: + e(0); + } + + /* + * Make absolutely sure that the linger timer has triggered and we do + * not end up exploiting race conditions in the tests below. As a + * result this subtest takes over a minute but at least it has already + * triggered a whole bunch of bugs (and produced lwIP patch #9125). + */ + sleep(2); + + /* Get the number of seconds spent in the close(2) call. */ + if (read(pfd[1], &val, sizeof(val)) != sizeof(val)) e(0); + + /* + * See if the close(2) call took as long as expected and check that the + * other side of the connection sees either EOF or a reset as expected. + */ + if (mode == 0) { + if (nb) { + if (val != 0) e(0); + + sleep(2); + } else if (!intr) { + if (val != linger * 2) e(0); + } else + if (val != 1) e(0); + + /* See if the connection was indeed reset. */ + while ((res = recv(fd[1], buf, sizeof(buf), 0)) > 0) + ; + if (res != -1) e(0); + if (errno != ECONNRESET) e(0); + } else { + if (val != ((onoff && !nb) || intr)) e(0); + + /* Check for EOF unless we already closed the socket. */ + if (fd[1] != -1) { + while ((res = recv(fd[1], buf, sizeof(buf), 0)) > 0) + ; + if (res != 0) e(0); + } + } + + /* Clean up. */ + if (fd[1] != -1 && close(fd[1]) != 0) e(0); + + if (close(pfd[1]) != 0) e(0); + + if (wait(&status) != pid) e(0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0); +} + +/* + * Test SO_LINGER support in various configurations. It is worth noting that I + * implemented a somewhat broken version of SO_LINGER because lwIP does not + * allow for proper detection of our FIN being acknowledged in all cases (this + * is documented in the service). As a result, a close(2) call may return + * earlier than it is supposed to, namely as soon as 1) we sent a FIN, and + * 2) we received a FIN from the other side. We also test the somewhat broken + * behavior here, as above all else the aim is to make sure that the service + * code works as expected. + */ +static void +test91u(void) +{ + int nb, mode; + + subtest = 21; + + /* + * + * In all of the following scenarios, close(2) should only ever return + * success, so that the caller knows that the file descriptor has been + * closed. + */ + for (nb = 0; nb <= 1; nb++) { + /* + * SO_LINGER off: the close(2) call should return immediately, + * and the connection should be closed in the background. + */ + for (mode = 1; mode <= 4; mode++) + sub91u(nb, mode, 0, 0, 0); + + /* + * SO_LINGER on with a zero timeout: the close(2) call should + * return immediately, and the connection should be reset. + */ + sub91u(nb, 0, 0, 1, 0); + + /* + * SO_LINGER on with a non-zero timeout: the close(2) call + * should return immediately for non-blocking sockets only, and + * otherwise as soon as either the connection is closed or the + * timeout triggers, in which case the connection is reset. + */ + for (mode = 0; mode <= 4; mode++) + sub91u(nb, mode, 0, 1, 1); + } + + /* + * Test signal-interrupting blocked close(2) calls with SO_LINGER. In + * such cases, the close(2) should return EINPROGRESS to indicate that + * the file descriptor has been closed, and the original close action + * (with the original timeout) should proceed in the background. + */ + for (mode = 0; mode <= 4; mode++) + sub91u(0, mode, 1, 1, 1); +} + +/* + * Test shutdown on listening TCP sockets. + */ +static void +sub91v(int how) +{ + struct sockaddr_in sin; + socklen_t len; + char c; + int fd, fd2, fd3, fl; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + len = sizeof(sin); + if (getsockname(fd, (struct sockaddr *)&sin, &len) != 0) e(0); + if (len != sizeof(sin)) e(0); + + if (listen(fd, 1) != 0) e(0); + + if ((fd2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + if (shutdown(fd, how) != 0) e(0); + + len = sizeof(sin); + if ((fd3 = accept(fd, (struct sockaddr *)&sin, &len)) < 0) e(0); + if (len != sizeof(sin)) e(0); + + if (write(fd2, "A", 1) != 1) e(0); + if (read(fd3, &c, 1) != 1) e(0); + if (c != 'A') e(0); + + if (write(fd3, "B", 1) != 1) e(0); + if (read(fd2, &c, 1) != 1) e(0); + if (c != 'B') e(0); + + len = sizeof(sin); + if (accept(fd, (struct sockaddr *)&sin, &len) != -1) e(0); + if (errno != ECONNABORTED) e(0); + + if ((fl = fcntl(fd, F_GETFL)) == -1) e(0); + if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) != 0) e(0); + + len = sizeof(sin); + if (accept(fd, (struct sockaddr *)&sin, &len) != -1) e(0); + if (errno != ECONNABORTED) e(0); + + if (close(fd3) != 0) e(0); + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); +} + +/* + * Test shutdown on listening TCP sockets. This test is derived from test90x. + */ +static void +test91v(void) +{ + const int hows[] = { SHUT_RD, SHUT_WR, SHUT_RDWR }; + int i; + + subtest = 22; + + for (i = 0; i < __arraycount(hows); i++) + sub91v(hows[i]); +} + +/* + * Test basic sysctl(2) socket enumeration support. + */ +static void +test91w(void) +{ + struct kinfo_pcb ki; + struct sockaddr_in lsin, rsin; + struct sockaddr_in6 lsin6, rsin6; + char buf[CHUNK]; + uint16_t local_port, remote_port; + socklen_t len; + int fd[2], val, sndbuf, rcvbuf; + + subtest = 23; + + /* + * First test TCP. + */ + get_tcp_pair(AF_INET, SOCK_STREAM, 0, fd); + + val = 0; + if (setsockopt(fd[1], IPPROTO_TCP, TCP_NODELAY, &val, + sizeof(val)) != 0) e(0); + + len = sizeof(lsin); + if (getsockname(fd[0], (struct sockaddr *)&lsin, &len) != 0) e(0); + if (len != sizeof(lsin)) e(0); + local_port = ntohs(lsin.sin_port); + + if (getpeername(fd[0], (struct sockaddr *)&rsin, &len) != 0) e(0); + if (len != sizeof(rsin)) e(0); + remote_port = ntohs(rsin.sin_port); + + if (send(fd[0], "ABCDE", 5, 0) != 5) e(0); + + /* Allow the data to reach the other side and be acknowledged. */ + sleep(1); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_ESTABLISHED) e(0); + if (!(ki.ki_tflags & TF_NODELAY)) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, remote_port, + local_port, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_ESTABLISHED) e(0); + if (ki.ki_tflags & TF_NODELAY) e(0); + if (memcmp(&ki.ki_src, &rsin, sizeof(rsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &lsin, sizeof(lsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 5) e(0); + + if (recv(fd[1], buf, sizeof(buf), 0) != 5) e(0); + + if (close(fd[0]) != 0) e(0); + if (close(fd[1]) != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_TIME_WAIT) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + /* Test IPv6 sockets as well. */ + get_tcp_pair(AF_INET6, SOCK_STREAM, 0, fd); + + len = sizeof(lsin6); + if (getsockname(fd[0], (struct sockaddr *)&lsin6, &len) != 0) e(0); + if (len != sizeof(lsin6)) e(0); + local_port = ntohs(lsin6.sin6_port); + + if (getpeername(fd[0], (struct sockaddr *)&rsin6, &len) != 0) e(0); + if (len != sizeof(rsin6)) e(0); + remote_port = ntohs(rsin6.sin6_port); + + memset(buf, 0, sizeof(buf)); + + /* We fill up the queues so we do not need to sleep in this case. */ + (void)get_buf_sizes(SOCK_STREAM, &sndbuf, &rcvbuf); + + fill_tcp_bufs(fd[0], fd[1], 1 /*fill_send*/, 0 /*delta*/); + + if (send(fd[0], buf, 1, MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_ESTABLISHED) e(0); + if (!(ki.ki_tflags & TF_NODELAY)) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (ki.ki_sndq != (size_t)sndbuf) e(0); + if (ki.ki_rcvq != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, + remote_port, local_port, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_ESTABLISHED) e(0); + if (!(ki.ki_tflags & TF_NODELAY)) e(0); + if (memcmp(&ki.ki_src, &rsin6, sizeof(rsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &lsin6, sizeof(lsin6)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != (size_t)rcvbuf) e(0); + + if (close(fd[0]) != 0) e(0); + if (close(fd[1]) != 0) e(0); + + /* Bound and listening sockets should show up as well. */ + if ((fd[0] = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, 0, 0, + &ki) != 0) e(0); + + memset(&lsin, 0, sizeof(lsin)); + lsin.sin_len = sizeof(lsin); + lsin.sin_family = AF_INET; + lsin.sin_port = htons(TEST_PORT_A); + lsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (bind(fd[0], (struct sockaddr *)&lsin, sizeof(lsin)) != 0) e(0); + + memset(&rsin, 0, sizeof(rsin)); + rsin.sin_len = sizeof(rsin); + rsin.sin_family = AF_INET; + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, TEST_PORT_A, + 0, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_CLOSED) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + if (listen(fd[0], 1)) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, TEST_PORT_A, + 0, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_LISTEN) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + if (close(fd[0]) != 0) e(0); + + /* Test IPv6 sockets as well. */ + if ((fd[0] = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, 0, 0, + &ki) != 0) e(0); + + val = 1; + if (setsockopt(fd[0], IPPROTO_IPV6, IPV6_V6ONLY, &val, + sizeof(val)) != 0) e(0); + + memset(&lsin6, 0, sizeof(lsin6)); + lsin6.sin6_len = sizeof(lsin6); + lsin6.sin6_family = AF_INET6; + lsin6.sin6_port = htons(TEST_PORT_A); + memcpy(&lsin6.sin6_addr, &in6addr_loopback, sizeof(lsin6.sin6_addr)); + if (bind(fd[0], (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0); + + memset(&rsin6, 0, sizeof(rsin6)); + rsin6.sin6_len = sizeof(rsin6); + rsin6.sin6_family = AF_INET6; + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, + TEST_PORT_A, 0, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_CLOSED) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0); + + if (listen(fd[0], 1)) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, + TEST_PORT_A, 0, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_LISTEN) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0); + + if (close(fd[0]) != 0) e(0); + + /* + * I do not dare binding to ANY so we cannot test IPV6_V6ONLY properly + * here. Instead we repeat the test and ensure the IN6P_IPV6_V6ONLY + * flag accurately represents the current state. + */ + if ((fd[0] = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, 0, 0, + &ki) != 0) e(0); + + val = 0; + if (setsockopt(fd[0], IPPROTO_IPV6, IPV6_V6ONLY, &val, + sizeof(val)) != 0) e(0); + + if (bind(fd[0], (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, + TEST_PORT_A, 0, &ki) != 1) e(0); + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_CLOSED) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, TEST_PORT_A, + 0, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_TCP, TEST_PORT_A, + 0, &ki) != 0) e(0); + + if (close(fd[0]) != 0) e(0); + + /* + * Then test UDP. + */ + if ((fd[0] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, 0, 0, + &ki) != 0) e(0); + + memset(&lsin, 0, sizeof(lsin)); + lsin.sin_len = sizeof(lsin); + lsin.sin_family = AF_INET; + lsin.sin_port = htons(TEST_PORT_A); + lsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + memset(&rsin, 0, sizeof(rsin)); + rsin.sin_len = sizeof(rsin); + rsin.sin_family = AF_INET; + + if (bind(fd[0], (struct sockaddr *)&lsin, sizeof(lsin)) != 0) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_A, + 0, &ki) != 1) e(0); + if (ki.ki_type != SOCK_DGRAM) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + rsin.sin_port = htons(TEST_PORT_B); + rsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (connect(fd[0], (struct sockaddr *)&rsin, sizeof(rsin)) != 0) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_A, + TEST_PORT_B, &ki) != 1) e(0); + if (ki.ki_type != SOCK_DGRAM) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_B, + TEST_PORT_A, &ki) != 0) e(0); + + if ((fd[1] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + if (bind(fd[1], (struct sockaddr *)&rsin, sizeof(rsin)) != 0) e(0); + + if (sendto(fd[1], "ABC", 3, 0, (struct sockaddr *)&lsin, + sizeof(lsin)) != 3) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, TEST_PORT_A, + TEST_PORT_B, &ki) != 1) e(0); + if (ki.ki_type != SOCK_DGRAM) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq < 3) e(0); /* size is rounded up */ + + if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, + TEST_PORT_A, TEST_PORT_B, &ki) != 0) e(0); + + if (close(fd[0]) != 0) e(0); + if (close(fd[1]) != 0) e(0); + + /* Test IPv6 sockets as well. */ + if ((fd[0] = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, 0, 0, + &ki) != 0) e(0); + + memset(&lsin6, 0, sizeof(lsin6)); + lsin6.sin6_len = sizeof(lsin6); + lsin6.sin6_family = AF_INET6; + lsin6.sin6_port = htons(TEST_PORT_A); + memcpy(&lsin6.sin6_addr, &in6addr_loopback, sizeof(lsin6.sin6_addr)); + if (bind(fd[0], (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0); + + memset(&rsin6, 0, sizeof(rsin6)); + rsin6.sin6_len = sizeof(rsin6); + rsin6.sin6_family = AF_INET6; + + if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, + TEST_PORT_A, 0, &ki) != 1) e(0); + if (ki.ki_type != SOCK_DGRAM) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0); + + rsin6.sin6_port = htons(TEST_PORT_B); + memcpy(&rsin6.sin6_addr, &in6addr_loopback, sizeof(rsin6.sin6_addr)); + if (connect(fd[0], (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0) + e(0); + + if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, + TEST_PORT_A, TEST_PORT_B, &ki) != 1) e(0); + if (ki.ki_type != SOCK_DGRAM) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0); + + if (close(fd[0]) != 0) e(0); + + if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, + TEST_PORT_A, TEST_PORT_B, &ki) != 0) e(0); +} + +/* + * Test socket enumeration of sockets using IPv4-mapped IPv6 addresses. + */ +static void +test91x(void) +{ + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + socklen_t len; + struct kinfo_pcb ki; + unsigned short local_port, remote_port; + int fd, fd2, fd3, val; + + subtest = 24; + + /* + * Test that information from an IPv6 socket bound to an IPv4-mapped + * IPv6 address is as expected. For socket enumeration, due to lwIP + * limitations we return an IPv4 address instead of an IPv4-mapped IPv6 + * address, and that is what this test checks for various sockets. + */ + if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1) + e(0); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + len = sizeof(sin6); + if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + local_port = ntohs(sin6.sin6_port); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port, + 0, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port, + 0, &ki) != 1) e(0); + + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_CLOSED) e(0); + + memcpy(&sin, &ki.ki_src, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(local_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + memcpy(&sin, &ki.ki_dst, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(0)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0); + + if (listen(fd, 1) != 0) e(0); + + /* + * Test that information from an accepted (IPv6) socket is correct + * for a connection from an IPv4 address. + */ + if ((fd2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(local_port); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + len = sizeof(sin); + if (getsockname(fd2, (struct sockaddr *)&sin, &len) != 0) e(0); + if (len != sizeof(sin)) e(0); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + remote_port = ntohs(sin.sin_port); + + len = sizeof(sin6); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6, &len)) < 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(remote_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + len = sizeof(sin6); + if (getsockname(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(local_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + len = sizeof(sin6); + if (getpeername(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(remote_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 1) e(0); + + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_ESTABLISHED) e(0); + + memcpy(&sin, &ki.ki_src, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(local_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + memcpy(&sin, &ki.ki_dst, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(remote_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + if (close(fd3) != 0) e(0); + if (close(fd2) != 0) e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 1) e(0); + + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_TIME_WAIT) e(0); + + memcpy(&sin, &ki.ki_src, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(local_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + memcpy(&sin, &ki.ki_dst, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(remote_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + /* + * Test that information from a connected (IPv6) socket is correct + * after connecting it to an IPv4 address. + */ + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + val = 0; + if (setsockopt(fd2, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(local_port); + if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1) + e(0); + + if (connect(fd2, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + len = sizeof(sin6); + if (getsockname(fd2, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + remote_port = ntohs(sin6.sin6_port); + + len = sizeof(sin6); + if (getpeername(fd2, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(local_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, + remote_port, local_port, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, remote_port, + local_port, &ki) != 1) e(0); + + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_ESTABLISHED) e(0); + + memcpy(&sin, &ki.ki_src, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(remote_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + memcpy(&sin, &ki.ki_dst, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(local_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + len = sizeof(sin6); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6, &len)) < 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(remote_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + if (close(fd2) != 0) e(0); + if (close(fd3) != 0) e(0); + if (close(fd) != 0) e(0); + + /* + * Do one more test on an accepted socket, now without binding the + * listening socket to an IPv4-mapped IPv6 address. + */ + if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr)); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + len = sizeof(sin6); + if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + local_port = ntohs(sin6.sin6_port); + + if (listen(fd, 1) != 0) e(0); + + if ((fd2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(local_port); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (connect(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + len = sizeof(sin); + if (getsockname(fd2, (struct sockaddr *)&sin, &len) != 0) e(0); + if (len != sizeof(sin)) e(0); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + remote_port = ntohs(sin.sin_port); + + len = sizeof(sin6); + if ((fd3 = accept(fd, (struct sockaddr *)&sin6, &len)) < 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(remote_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + len = sizeof(sin6); + if (getsockname(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(local_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + len = sizeof(sin6); + if (getpeername(fd3, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port != htons(remote_port)) e(0); + if (!IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) e(0); + if (sin6.sin6_addr.__u6_addr.__u6_addr32[3] != htonl(INADDR_LOOPBACK)) + e(0); + + if (socklib_find_pcb("net.inet6.tcp6.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.tcp.pcblist", IPPROTO_TCP, local_port, + remote_port, &ki) != 1) e(0); + + if (ki.ki_type != SOCK_STREAM) e(0); + if (ki.ki_tstate != TCPS_ESTABLISHED) e(0); + + memcpy(&sin, &ki.ki_src, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(local_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + memcpy(&sin, &ki.ki_dst, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(remote_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + if (close(fd3) != 0) e(0); + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* + * Do some very simple UDP socket enumeration tests. The rest is + * already tested elsewhere. + */ + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1) + e(0); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + len = sizeof(sin6); + if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + local_port = ntohs(sin6.sin6_port); + + if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, local_port, + 0, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, local_port, + 0, &ki) != 1) e(0); + + if (ki.ki_type != SOCK_DGRAM) e(0); + if (ki.ki_tstate != 0) e(0); + + memcpy(&sin, &ki.ki_src, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(local_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + memcpy(&sin, &ki.ki_dst, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(0)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0); + + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) != 0) + e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(TEST_PORT_A); + if (inet_pton(AF_INET6, "::ffff:"LOOPBACK_IPV4, &sin6.sin6_addr) != 1) + e(0); + + if (connect(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + len = sizeof(sin6); + if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + local_port = ntohs(sin6.sin6_port); + + if (socklib_find_pcb("net.inet6.udp6.pcblist", IPPROTO_UDP, local_port, + TEST_PORT_A, &ki) != 0) e(0); + + if (socklib_find_pcb("net.inet.udp.pcblist", IPPROTO_UDP, local_port, + TEST_PORT_A, &ki) != 1) e(0); + + if (ki.ki_type != SOCK_DGRAM) e(0); + if (ki.ki_tstate != 0) e(0); + + memcpy(&sin, &ki.ki_src, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(local_port)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + memcpy(&sin, &ki.ki_dst, sizeof(sin)); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port != htons(TEST_PORT_A)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test local and remote IPv6 address handling. In particular, test scope IDs + * and IPv4-mapped IPv6 addresses. + */ +static void +test91y(void) +{ + + subtest = 25; + + socklib_test_addrs(SOCK_STREAM, 0); + + socklib_test_addrs(SOCK_DGRAM, 0); +} + +/* + * Test low-memory conditions for TCP. + */ +static void +test91z(void) +{ + struct sockaddr_in6 sin6; + socklen_t len; + unsigned char buf[CHUNK]; + struct timeval tv; + unsigned int i, j, k; + ssize_t res, left; + pid_t pid, pid2; + static int fds[OPEN_MAX]; + static size_t pos[OPEN_MAX]; + int lfd, pfd[2], val, sndlen, rcvlen, status; + + subtest = 26; + + /* + * We use custom send and receive buffer sizes, such that we can + * trigger the case that we run out of send buffers without causing + * buffers used on the receiving side to empty the buffer pool first. + * While the latter case is not unrealistic for practical scenarios, it + * is not what we want to test here. It would also cause practical + * problems for this test, as the result may be that the loopback + * interface (that we use here) starts dropping packets due to being + * unable to make copies. + * + * The aim with these two is that the ratio is such that we run into + * the 75% usage limit for the send side without using the other 25% + * for receiving purposes. Since our TCP buffer merging guarantees at + * most a 50% overhead on the receiving side, the minimum ratio of 5:1 + * translates to a worst-case ratio is 10:3 which is just above 75%. + * Thus, we should be able to use 80K:16K. Instead, we use 128K:16K, + * because otherwise we will run out of sockets before we run out of + * buffers. After all, we are not generating any traffic on the socket + * pairs in the other direction--something for which we do provision. + */ + sndlen = 131072; + rcvlen = 16384; + + /* + * Unfortunately, filling up receive queues is not easy, and for any + * size other than the window size (which is by nature also the minimum + * receive queue length that may be set) we would need to work around + * the same issue described in fill_tcp_bufs(), which would massively + * complicate the implementation of this subtest. For now, make sure + * that inconsistent internal changes will trigger this assert. + */ + assert(rcvlen == WINDOW_SIZE); + + if ((lfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + if (bind(lfd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + len = sizeof(sin6); + if (getsockname(lfd, (struct sockaddr *)&sin6, &len) != 0) e(0); + + if (listen(lfd, 1) != 0) e(0); + + /* + * Start a child process for the receiving ends. We have to use + * another process because we aim to open a total concurrent number of + * TCP sockets that exceeds OPEN_MAX. + */ + if (pipe(pfd) != 0) e(0); + + pid = fork(); + switch (pid) { + case 0: + errct = 0; + + if (close(lfd) != 0) e(0); + if (close(pfd[1]) != 0) e(0); + + /* Create socket pairs. */ + for (i = 0; ; i++) { + if (i == __arraycount(fds)) e(0); + + if ((fds[i] = socket(AF_INET6, SOCK_STREAM, 0)) < 0) + e(0); + + if (connect(fds[i], (struct sockaddr *)&sin6, + sizeof(sin6)) != 0) e(0); + + val = 1; + if (setsockopt(fds[i], IPPROTO_TCP, TCP_NODELAY, &val, + sizeof(val)) != 0) e(0); + + if (setsockopt(fds[i], SOL_SOCKET, SO_RCVBUF, &rcvlen, + sizeof(rcvlen)) != 0) e(0); + + /* Synchronization point A. */ + if (read(pfd[0], &k, sizeof(k)) != sizeof(k)) e(0); + if (k == 0) + break; + } + + /* Synchronization point B. */ + if (read(pfd[0], &k, sizeof(k)) != sizeof(k)) e(0); + if (k != 2) e(0); + + /* Receive some data from one socket. */ + pos[0] = 0; + for (left = sizeof(buf) * 2; left > 0; left -= res) { + res = recv(fds[0], buf, MIN(left, sizeof(buf)), 0); + if (res <= 0) e(0); + if (res > left) e(0); + + for (j = 0; j < res; j++) + if (buf[j] != (unsigned char)(pos[0]++)) e(0); + } + + /* Synchronization point C. */ + if (read(pfd[0], &k, sizeof(k)) != sizeof(k)) e(0); + if (k != 3) e(0); + + /* + * Receive all remaining data from all sockets. Do this in two + * steps. First enlarge the receive buffer and empty it, so + * that upon resumption, all remaining data is transferred from + * the sender to the receiver in one go. Then actually wait + * for any remaining data, and the EOF. If we do both in one + * step, this part of the test will take several minutes to + * complete. Note that the last socket needs special treatment + * because its send queue may not have been filled entirely. + */ + for (k = 0; k <= i; k++) { + if (setsockopt(fds[i], SOL_SOCKET, SO_RCVBUF, &rcvlen, + sizeof(rcvlen)) != 0) e(0); + + pos[k] = (k == 0) ? (sizeof(buf) * 2) : 0; + + for (left = sndlen + rcvlen - pos[k]; left > 0; + left -= res) { + res = recv(fds[k], buf, MIN(left, sizeof(buf)), + MSG_DONTWAIT); + if (res == -1 && errno == EWOULDBLOCK) + break; + if (res == 0 && k == i) { + pos[i] = sndlen + rcvlen; + break; + } + if (res <= 0) e(0); + if (res > left) e(0); + + for (j = 0; j < res; j++) + if (buf[j] != (unsigned char)(k + + pos[k]++)) e(0); + } + } + + for (k = 0; k <= i; k++) { + for (left = sndlen + rcvlen - pos[k]; left > 0; + left -= res) { + res = recv(fds[k], buf, MIN(left, sizeof(buf)), + 0); + if (res == 0 && k == i) + break; + if (res <= 0) e(0); + if (res > left) e(0); + + for (j = 0; j < res; j++) + if (buf[j] != (unsigned char)(k + + pos[k]++)) e(0); + } + + if (recv(fds[k], buf, 1, 0) != 0) e(0); + } + + /* Clean up. */ + do { + if (close(fds[i]) != 0) e(0); + } while (i-- > 0); + + exit(errct); + case -1: + e(0); + } + + if (close(pfd[0]) != 0) e(0); + + for (i = 0; ; i++) { + if (i == __arraycount(fds)) e(0); + + len = sizeof(sin6); + if ((fds[i] = accept(lfd, (struct sockaddr *)&sin6, &len)) < 0) + e(0); + + val = 1; + if (setsockopt(fds[i], IPPROTO_TCP, TCP_NODELAY, &val, + sizeof(val)) != 0) e(0); + + if (setsockopt(fds[i], SOL_SOCKET, SO_SNDBUF, &sndlen, + sizeof(sndlen)) != 0) e(0); + + /* + * Try to pump as much data into one end of the socket. This + * may fail at any time due to being out of buffers, so we use + * a send timeout to break the resulting blocking call. + */ + tv.tv_sec = 1; + tv.tv_usec = 0; + + if (setsockopt(fds[i], SOL_SOCKET, SO_SNDTIMEO, &tv, + sizeof(tv)) != 0) e(0); + + /* + * Since buffer corruption is most likely to be detected when + * lots of buffers are actually in use, also make sure that we + * (eventually) receive what we send. + */ + res = sizeof(buf); + pos[i] = 0; + for (left = sndlen + rcvlen; left > 0; left -= res) { + /* One byte at a time, for simplicity.. */ + for (j = sizeof(buf) - res; j < sizeof(buf); j++) + buf[j] = (unsigned char)(i + pos[i]++); + + res = send(fds[i], buf, MIN(left, sizeof(buf)), 0); + if (res == -1 && errno == EWOULDBLOCK) + break; + + if (res <= 0) e(0); + if (res > left) e(0); + + if (res < sizeof(buf)) + memmove(buf, &buf[res], sizeof(buf) - res); + } + + /* Synchronization point A. */ + k = (left == 0); + if (write(pfd[1], &k, sizeof(k)) != sizeof(k)) e(0); + + if (left > 0) + break; + } + + if (close(lfd) != 0) e(0); + + /* + * We should always be able to fill at least two socket pairs' buffers + * completely this way; in fact with a 512x512 pool it should be three, + * but some sockets may be in use in the background. With the default + * settings of the memory pool system, we should ideally be able to get + * up to 96 socket pairs. + */ + if (i < 3) e(0); + + /* + * Mix things up a bit by fully shutting down one file descriptor and + * closing another, both on the sending side. + */ + if (shutdown(fds[1], SHUT_RDWR) != 0) e(0); + if (close(fds[2]) != 0) e(0); + + /* + * Make sure that when there is buffer space available again, pending + * send() calls get woken up. We do this using a child process that + * blocks on a send() call and a parent process that frees up some + * buffer space by receiving from another socket. + */ + pid2 = fork(); + switch (pid2) { + case 0: + errct = 0; + + /* Disable the timeout again. */ + tv.tv_sec = 0; + tv.tv_usec = 0; + + if (setsockopt(fds[i], SOL_SOCKET, SO_SNDTIMEO, &tv, + sizeof(tv)) != 0) e(0); + + /* + * Try sending. This should block until there are more buffers + * available. + */ + res = send(fds[i], buf, MIN(left, sizeof(buf)), 0); + if (res <= 0) e(0); + if (res > left) e(0); + + exit(errct); + + case -1: + e(0); + } + + /* Make sure the child's send() call is indeed hanging. */ + sleep(2); + + if (waitpid(pid2, &status, WNOHANG) != 0) e(0); + + /* Then receive some data on another socket. */ + + /* Synchronization point B. */ + k = 2; + if (write(pfd[1], &k, sizeof(k)) != sizeof(k)) e(0); + + /* The send() call should now be woken up, eventually. */ + if (waitpid(pid2, &status, 0) != pid2) e(0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0); + + /* + * Shut down all (remaining) sending file descriptors for sending, so + * that we can receive until we get EOF. For all but the last socket, + * we must get the full size of what we intended to send; for the first + * socket, we have already received two buffers worth of data. Note + * that the receipt may take a while, mainly because it takes some time + * for sockets that were previously blocked to get going again. + */ + for (k = 0; k <= i; k++) { + if (k != 1 && k != 2 && shutdown(fds[k], SHUT_WR) != 0) + e(0); + } + + /* Synchronization point C. */ + k = 3; + if (write(pfd[1], &k, sizeof(k)) != sizeof(k)) e(0); + + if (close(pfd[1]) != 0) e(0); + + /* Wait for the child to receive everything and terminate. */ + if (waitpid(pid, &status, 0) != pid) e(0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0); + + /* Clean up. */ + do { + if (i != 2 && close(fds[i]) != 0) e(0); + } while (i-- > 0); +} + +/* + * Test multicast support. + */ +static void +test91aa(void) +{ + + subtest = 27; + + socklib_test_multicast(SOCK_DGRAM, 0); +} + +/* + * Test that putting an unbound TCP socket in listening mode will bind the + * socket to a port. + */ +static void +test91ab(void) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + socklen_t len; + int fd; + + subtest = 28; + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) e(0); + + if (listen(fd, 1) != 0) e(0); + + len = sizeof(sin); + if (getsockname(fd, (struct sockaddr *)&sin, &len) != 0) e(0); + if (len != sizeof(sin)) e(0); + if (sin.sin_len != sizeof(sin)) e(0); + if (sin.sin_family != AF_INET) e(0); + if (sin.sin_port == htons(0)) e(0); + if (sin.sin_addr.s_addr != htonl(INADDR_ANY)) e(0); + + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (listen(fd, 1) != 0) e(0); + + len = sizeof(sin6); + if (getsockname(fd, (struct sockaddr *)&sin6, &len) != 0) e(0); + if (len != sizeof(sin6)) e(0); + if (sin6.sin6_len != sizeof(sin6)) e(0); + if (sin6.sin6_family != AF_INET6) e(0); + if (sin6.sin6_port == htons(0)) e(0); + if (memcmp(&sin6.sin6_addr, &in6addr_any, sizeof(sin6.sin6_addr)) != 0) + e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test for connecting to the same remote TCP endpoint with the same local + * endpoint twice in a row. The second connection should fail due to the + * TIME_WAIT state left behind from the first connection, but this previously + * caused an infinite loop instead. lwIP bug #50498. + */ +static void +test91ac(void) +{ + struct sockaddr_in6 lsin6, rsin6; + socklen_t len; + int fd, fd2, fd3; + + subtest = 29; + + if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + memset(&rsin6, 0, sizeof(rsin6)); + rsin6.sin6_family = AF_INET6; + memcpy(&rsin6.sin6_addr, &in6addr_loopback, sizeof(rsin6.sin6_addr)); + + if (bind(fd, (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0) e(0); + + len = sizeof(rsin6); + if (getsockname(fd, (struct sockaddr *)&rsin6, &len) != 0) e(0); + if (len != sizeof(rsin6)) e(0); + + if (listen(fd, 1) != 0) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (connect(fd2, (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0) e(0); + + if ((fd3 = accept(fd, (struct sockaddr *)&lsin6, &len)) < 0) e(0); + if (len != sizeof(rsin6)) e(0); + + /* The server end must initiate the close for this to work. */ + if (close(fd3) != 0) e(0); + + if (close(fd2) != 0) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) e(0); + + if (bind(fd2, (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0); + + /* + * The timeout should occur almost immediately, due to a shortcut in + * lwIP (which was also the source of the problem here). The actual + * error code is not really important though. In fact, if in the + * future the connection does get established, that is still not an + * issue - in fact, it would be nice to have a working rsh(1), which is + * how this problem showed up in the first place - but at the very + * least the service should keep operating. + */ + if (connect(fd2, (struct sockaddr *)&rsin6, sizeof(rsin6)) != -1) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); +} + +/* + * Test program for LWIP TCP/UDP sockets. + */ +int +main(int argc, char ** argv) +{ + unsigned int m; + int i; + + start(91); + + if (argc == 2) + m = atoi(argv[1]); + else + m = 0xFFFFFFFF; + + for (i = 0; i < ITERATIONS; i++) { + if (m & 0x00000001) test91a(); + if (m & 0x00000002) test91b(); + if (m & 0x00000004) test91c(); + if (m & 0x00000008) test91d(); + if (m & 0x00000010) test91e(); + if (m & 0x00000020) test91f(); + if (m & 0x00000040) test91g(); + if (m & 0x00000080) test91h(); + if (m & 0x00000100) test91i(); + if (m & 0x00000200) test91j(); + if (m & 0x00000400) test91k(); + if (m & 0x00000800) test91l(); + if (m & 0x00001000) test91m(); + if (m & 0x00002000) test91n(); + if (m & 0x00004000) test91o(); + if (m & 0x00008000) test91p(); + if (m & 0x00010000) test91q(); + if (m & 0x00020000) test91r(); + if (m & 0x00040000) test91s(); + if (m & 0x00080000) test91t(); + if (m & 0x00100000) test91u(); + if (m & 0x00200000) test91v(); + if (m & 0x00400000) test91w(); + if (m & 0x00800000) test91x(); + if (m & 0x01000000) test91y(); + if (m & 0x02000000) test91z(); + if (m & 0x04000000) test91aa(); + if (m & 0x08000000) test91ab(); + if (m & 0x10000000) test91ac(); + } + + quit(); + /* NOTREACHED */ +} diff --git a/minix/tests/test92.c b/minix/tests/test92.c new file mode 100644 index 000000000..df5c71d61 --- /dev/null +++ b/minix/tests/test92.c @@ -0,0 +1,1735 @@ +/* Tests for RAW sockets (LWIP) - by D.C. van Moolenbroek */ +/* This test needs to be run as root: creating raw sockets is root-only. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "socklib.h" + +#define ITERATIONS 2 + +#define TEST_PROTO 253 /* from RFC 3692 */ +#define TEST_ICMPV6_TYPE_A 200 /* from RFC 4443 */ +#define TEST_ICMPV6_TYPE_B 201 /* from RFC 4443 */ + +static const enum state raw_states[] = { + S_NEW, S_N_SHUT_R, S_N_SHUT_W, S_N_SHUT_RW, + S_BOUND, S_CONNECTED, S_SHUT_R, S_SHUT_W, + S_SHUT_RW, +}; + +static const int raw_results[][__arraycount(raw_states)] = { + [C_ACCEPT] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, + }, + [C_BIND] = { + 0, 0, 0, 0, + 0, -EINVAL, -EINVAL, -EINVAL, + -EINVAL, + }, + [C_CONNECT] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_GETPEERNAME] = { + -ENOTCONN, -ENOTCONN, -ENOTCONN, -ENOTCONN, + -ENOTCONN, 0, 0, 0, + 0, + }, + [C_GETSOCKNAME] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_GETSOCKOPT_ERR] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_GETSOCKOPT_KA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_GETSOCKOPT_RB] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_IOCTL_NREAD] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_LISTEN] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + -EOPNOTSUPP, + }, + [C_RECV] = { + -EAGAIN, 0, -EAGAIN, 0, + -EAGAIN, -EAGAIN, 0, -EAGAIN, + 0, + }, + [C_RECVFROM] = { + -EAGAIN, 0, -EAGAIN, 0, + -EAGAIN, -EAGAIN, 0, -EAGAIN, + 0, + }, + [C_SEND] = { + -EDESTADDRREQ, -EDESTADDRREQ, -EPIPE, -EPIPE, + -EDESTADDRREQ, 1, 1, -EPIPE, + -EPIPE, + }, + [C_SENDTO] = { + 1, 1, -EPIPE, -EPIPE, + 1, 1, 1, -EPIPE, + -EPIPE, + }, + [C_SELECT_R] = { + 0, 1, 0, 1, + 0, 0, 1, 0, + 1, + }, + [C_SELECT_W] = { + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, + }, + [C_SELECT_X] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_SETSOCKOPT_BC] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_SETSOCKOPT_KA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_SETSOCKOPT_L] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_SETSOCKOPT_RA] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_SHUTDOWN_R] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_SHUTDOWN_RW] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, + [C_SHUTDOWN_W] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + }, +}; + +/* + * Set up a RAW socket file descriptor in the requested state and pass it to + * socklib_sweep_call() along with local and remote addresses and their length. + */ +static int +raw_sweep(int domain, int type, int protocol, enum state state, + enum call call) +{ + struct sockaddr_in sinA, sinB; + struct sockaddr_in6 sin6A, sin6B; + struct sockaddr *addrA, *addrB; + socklen_t addr_len; + int r, fd, fd2; + + if (domain == AF_INET) { + memset(&sinA, 0, sizeof(sinA)); + sinA.sin_family = domain; + sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + memcpy(&sinB, &sinA, sizeof(sinB)); + + addrA = (struct sockaddr *)&sinA; + addrB = (struct sockaddr *)&sinB; + addr_len = sizeof(sinA); + } else { + assert(domain == AF_INET6); + + memset(&sin6A, 0, sizeof(sin6A)); + sin6A.sin6_family = domain; + memcpy(&sin6A.sin6_addr, &in6addr_loopback, + sizeof(sin6A.sin6_addr)); + + memcpy(&sin6B, &sin6A, sizeof(sin6B)); + + addrA = (struct sockaddr *)&sin6A; + addrB = (struct sockaddr *)&sin6B; + addr_len = sizeof(sin6A); + } + + /* Create a bound remote socket. */ + if ((fd2 = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0); + + if (bind(fd2, addrB, addr_len) != 0) e(0); + + switch (state) { + case S_NEW: + case S_N_SHUT_R: + case S_N_SHUT_W: + case S_N_SHUT_RW: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + switch (state) { + case S_N_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_N_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_N_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + break; + + case S_BOUND: + case S_CONNECTED: + case S_SHUT_R: + case S_SHUT_W: + case S_SHUT_RW: + if ((fd = socket(domain, type | SOCK_NONBLOCK, + protocol)) < 0) e(0); + + if (bind(fd, addrA, addr_len) != 0) e(0); + + if (state == S_BOUND) + break; + + if (connect(fd, addrB, addr_len) != 0) e(0); + + switch (state) { + case S_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: break; + } + + break; + + default: + fd = -1; + e(0); + } + + r = socklib_sweep_call(call, fd, addrA, addrB, addr_len); + + if (close(fd) != 0) e(0); + if (fd2 != -1 && close(fd2) != 0) e(0); + + return r; +} + +/* + * Sweep test for socket calls versus socket states of RAW sockets. + */ +static void +test92a(void) +{ + + subtest = 1; + + socklib_sweep(AF_INET, SOCK_RAW, TEST_PROTO, raw_states, + __arraycount(raw_states), (const int *)raw_results, raw_sweep); + + socklib_sweep(AF_INET6, SOCK_RAW, TEST_PROTO, raw_states, + __arraycount(raw_states), (const int *)raw_results, raw_sweep); +} + +/* + * Basic I/O test for raw sockets. + */ +static void +test92b(void) +{ + struct sockaddr_in sinA, sinB, sinC; + struct sockaddr_in6 sin6A, sin6B, sin6C; + socklen_t len; + unsigned int i; + uint8_t buf[256], packet[5]; + int fd, fd2; + + subtest = 2; + + /* First test IPv4. */ + if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + memset(&sinA, 0, sizeof(sinA)); + sinA.sin_family = AF_INET; + sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + for (i = 0; i < __arraycount(packet); i++) + packet[i] = (uint8_t)(-i); + + if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sinA, + sizeof(sinA)) != sizeof(packet)) e(0); + + memset(buf, 0, sizeof(buf)); + len = sizeof(sinB); + if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sinB, + &len) != sizeof(struct ip) + sizeof(packet)) e(0); + + if (memcmp(&buf[sizeof(struct ip)], packet, sizeof(packet)) != 0) e(0); + + if (len != sizeof(sinB)) e(0); + if (sinB.sin_len != sizeof(sinB)) e(0); + if (sinB.sin_family != AF_INET) e(0); + if (sinB.sin_port != htons(0)) e(0); + if (sinB.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) e(0); + + /* + * Test two additional things: + * + * 1) a non-zero port number is ignored when sending; + * 2) multiple raw sockets may receive the same packet. + */ + sinA.sin_port = htons(22); + + if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sinA, + sizeof(sinA)) != sizeof(packet)) e(0); + + memset(buf, 0, sizeof(buf)); + len = sizeof(sinC); + if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sinC, + &len) != sizeof(struct ip) + sizeof(packet)) e(0); + + if (memcmp(&buf[sizeof(struct ip)], packet, sizeof(packet)) != 0) e(0); + + if (len != sizeof(sinC)) e(0); + if (memcmp(&sinB, &sinC, sizeof(sinB)) != 0) e(0); + + memset(buf, 0, sizeof(buf)); + len = sizeof(sinC); + if (recvfrom(fd2, buf, sizeof(buf), 0, (struct sockaddr *)&sinC, + &len) != sizeof(struct ip) + sizeof(packet)) e(0); + + if (memcmp(&buf[sizeof(struct ip)], packet, sizeof(packet)) != 0) e(0); + + if (len != sizeof(sinC)) e(0); + if (memcmp(&sinB, &sinC, sizeof(sinB)) != 0) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* Then test IPv6. */ + if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + memset(&sin6A, 0, sizeof(sin6A)); + sin6A.sin6_family = AF_INET6; + memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr)); + + if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sin6A, + sizeof(sin6A)) != sizeof(packet)) e(0); + + memset(buf, 0, sizeof(buf)); + len = sizeof(sin6B); + if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6B, + &len) != sizeof(packet)) e(0); + + if (memcmp(buf, packet, sizeof(packet)) != 0) e(0); + + if (len != sizeof(sin6B)) e(0); + if (sin6B.sin6_len != sizeof(sin6B)) e(0); + if (sin6B.sin6_family != AF_INET6) e(0); + if (sin6B.sin6_port != htons(0)) e(0); + if (memcmp(&sin6B.sin6_addr, &in6addr_loopback, + sizeof(sin6B.sin6_addr)) != 0) e(0); + + /* As above. */ + sin6A.sin6_port = htons(22); + + if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + if (sendto(fd, packet, sizeof(packet), 0, (struct sockaddr *)&sin6A, + sizeof(sin6A)) != sizeof(packet)) e(0); + + memset(buf, 0, sizeof(buf)); + len = sizeof(sin6C); + if (recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6C, + &len) != sizeof(packet)) e(0); + + if (memcmp(buf, packet, sizeof(packet)) != 0) e(0); + + if (len != sizeof(sin6C)) e(0); + if (memcmp(&sin6B, &sin6C, sizeof(sin6B)) != 0) e(0); + + memset(buf, 0, sizeof(buf)); + len = sizeof(sin6C); + if (recvfrom(fd2, buf, sizeof(buf), 0, (struct sockaddr *)&sin6C, + &len) != sizeof(packet)) e(0); + + if (memcmp(buf, packet, sizeof(packet)) != 0) e(0); + + if (len != sizeof(sin6C)) e(0); + if (memcmp(&sin6B, &sin6C, sizeof(sin6B)) != 0) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); +} + +/* + * Test the IPV6_CHECKSUM socket option. + */ +static void +test92c(void) +{ + struct sockaddr_in6 sin6; + struct icmp6_hdr icmp6_hdr; + uint8_t buf[6], buf2[6], *buf3; + socklen_t len; + unsigned int i; + int fd, fd2, val; + + subtest = 3; + + if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + if (shutdown(fd, SHUT_RD) != 0) e(0); + + /* For non-ICMPv6 sockets, checksumming is disabled by default. */ + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != -1) e(0); + + /* Test bad offsets. */ + val = -2; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + /* Now test real checksum computation. */ + val = 0; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + if (shutdown(fd2, SHUT_WR) != 0) e(0); + + memset(buf, 0, sizeof(buf)); + buf[2] = 0xfe; + buf[3] = 0x95; + buf[4] = 0x4d; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + if (sendto(fd, buf, 5, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 5) + e(0); + + if (recv(fd2, buf2, sizeof(buf2), 0) != 5) e(0); + + if (buf2[0] != 0xb3 || buf2[1] != 0x65) e(0); + if (memcmp(&buf2[2], &buf[2], 3) != 0) e(0); + + /* Turn on checksum verification on the receiving socket. */ + val = 0; + if (setsockopt(fd2, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + /* + * The current value of the checksum field should not be incorporated + * in the checksum, as that would result in an invalid checksum. + */ + buf[0] = 0xab; + buf[1] = 0xcd; + + if (sendto(fd, buf, 5, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 5) + e(0); + + if (recv(fd2, buf2, sizeof(buf2), 0) != 5) e(0); + + if (buf2[0] != 0xb3 || buf2[1] != 0x65) e(0); + if (memcmp(&buf2[2], &buf[2], 3) != 0) e(0); + + /* + * Turn off checksum computation on the sending side, so that the + * packet ends up being dropped on the receiving side. + */ + val = -1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + if (sendto(fd, buf, 5, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 5) + e(0); + + /* Send some packets that are too small to contain the checksum. */ + if (sendto(fd, buf, 0, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) + e(0); + if (sendto(fd, buf, 1, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 1) + e(0); + + /* + * If this recv call is "too soon" (it should not be) and the packets + * arrive later anyway, then we will get a failure below. + */ + if (recv(fd2, buf2, sizeof(buf2), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + buf[0] = 0; + buf[1] = 0x67; + if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != 4) e(0); + + if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0); + if (memcmp(buf, buf2, 4) != 0) e(0); + + /* + * We repeat some of the tests with a non-zero checksum offset, just to + * be sure. + */ + val = 2; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 2) e(0); + + buf[0] = 0x56; + buf[1] = 0x78; + + for (i = 0; i <= 3; i++) { + if (sendto(fd, buf, i, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != -1) e(0); + if (errno != EINVAL) e(0); + } + + val = 2; + if (setsockopt(fd2, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != 4) e(0); + + if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0); + if (memcmp(buf, buf2, 2) != 0) e(0); + if (buf2[2] != 0xa8 || buf2[3] != 0x84) e(0); + + val = -1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + buf[2] = 0xa8; + buf[3] = 0x85; /* deliberately bad checksum */ + + /* All these should be dropped on the receiver side. */ + for (i = 0; i <= 4; i++) { + if (sendto(fd, buf, i, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != i) e(0); + } + + buf[3] = 0x84; /* good checksum */ + if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 4) + e(0); + + if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0); + if (memcmp(buf, buf2, 4) != 0) e(0); + + if (recv(fd2, buf2, sizeof(buf2), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + val = -1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + if (setsockopt(fd2, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + buf[3] = 0x85; + if (sendto(fd, buf, 4, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != 4) + e(0); + + if (recv(fd2, buf2, sizeof(buf2), 0) != 4) e(0); + if (memcmp(buf, buf2, 4) != 0) e(0); + + /* + * The following is a lwIP-specific test: lwIP does not support storing + * generated checksums beyond the first pbuf. We do not know the size + * of the first pbuf until we actually send a packet, so the setsockopt + * call will not fail, but sending the packet will. Depending on the + * buffer allocation strategy, the following test may or may not + * trigger this case; simply ensure that we do not crash the service. + */ + if ((buf3 = malloc(4096)) == NULL) e(0); + + val = 4094; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != 0) e(0); + + /* This call may or may not fail, but if it fails, it yields EINVAL. */ + if (sendto(fd, buf3, 4096, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) == -1 && errno != EINVAL) e(0); + + free(buf3); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* For ICMPv6 packets, checksumming is always enabled. */ + if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 2) e(0); + + val = -1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != -1) e(0); + if (errno != EINVAL) e(0); + + memset(&icmp6_hdr, 0, sizeof(icmp6_hdr)); + icmp6_hdr.icmp6_type = TEST_ICMPV6_TYPE_A; + icmp6_hdr.icmp6_code = 123; + icmp6_hdr.icmp6_cksum = htons(0); + + len = offsetof(struct icmp6_hdr, icmp6_dataun); + if (sendto(fd, &icmp6_hdr, len, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != len) e(0); + + if (recv(fd, &icmp6_hdr, sizeof(icmp6_hdr), 0) != len) e(0); + + if (icmp6_hdr.icmp6_type != TEST_ICMPV6_TYPE_A) e(0); + if (icmp6_hdr.icmp6_code != 123) e(0); + if (ntohs(icmp6_hdr.icmp6_cksum) != 0x3744) e(0); + + if (close(fd) != 0) e(0); + + /* For IPv4 and non-RAW IPv6 sockets, the option does not work. */ + for (i = 0; i <= 2; i++) { + switch (i) { + case 0: fd = socket(AF_INET6, SOCK_DGRAM, 0); break; + case 1: fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6); break; + case 2: fd = socket(AF_INET, SOCK_RAW, TEST_PROTO); break; + } + if (fd < 0) e(0); + + val = -1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + sizeof(val)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &val, + &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + if (close(fd) != 0) e(0); + } +} + +/* + * Test the ICMP6_FILTER socket option. + */ +static void +test92d(void) +{ + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + struct icmp6_filter filter; + struct icmp6_hdr packet; + socklen_t len; + struct timeval tv; + unsigned int i; + int fd, fd2; + + subtest = 4; + + /* + * We use two different sockets to eliminate the possibility that the + * filter is also applied when sending packets--it should not be. + */ + if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0); + + if (shutdown(fd, SHUT_WR) != 0) e(0); + + len = sizeof(filter); + if (getsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, &len) != 0) + e(0); + + /* We do not aim to test the ICMP6_FILTER macros here. */ + for (i = 0; i <= UINT8_MAX; i++) + if (!ICMP6_FILTER_WILLPASS(i, &filter)) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0); + + ICMP6_FILTER_SETBLOCKALL(&filter); + if (setsockopt(fd2, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, + sizeof(filter)) != 0) e(0); + + len = sizeof(filter); + if (getsockopt(fd2, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, &len) != 0) + e(0); + + for (i = 0; i <= UINT8_MAX; i++) + if (ICMP6_FILTER_WILLPASS(i, &filter)) e(0); + + ICMP6_FILTER_SETPASSALL(&filter); + ICMP6_FILTER_SETBLOCK(TEST_ICMPV6_TYPE_A, &filter); + if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, + sizeof(filter)) != 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + memset(&packet, 0, sizeof(packet)); + packet.icmp6_type = TEST_ICMPV6_TYPE_A; + packet.icmp6_code = 12; + + if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(packet)) e(0); + + packet.icmp6_type = TEST_ICMPV6_TYPE_B; + packet.icmp6_code = 34; + + if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(packet)) e(0); + + memset(&packet, 0, sizeof(packet)); + + if (recv(fd, &packet, sizeof(packet), 0) != sizeof(packet)) e(0); + if (packet.icmp6_type != TEST_ICMPV6_TYPE_B) e(0); + if (packet.icmp6_code != 34) e(0); + + if (recv(fd, &packet, sizeof(packet), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(TEST_ICMPV6_TYPE_A, &filter); + if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, + sizeof(filter)) != 0) e(0); + + memset(&packet, 0, sizeof(packet)); + packet.icmp6_type = TEST_ICMPV6_TYPE_B; + packet.icmp6_code = 56; + + if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(packet)) e(0); + + packet.icmp6_type = TEST_ICMPV6_TYPE_A; + packet.icmp6_code = 78; + + if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(packet)) e(0); + + /* + * RFC 3542 states that setting a zero-length filter resets the filter. + * This seems like one of those things that a standardization RFC + * should not mandate: it is redundant at the API level (one can set a + * PASSALL filter, which is the required default), it relies on an edge + * case (setsockopt taking a zero-length argument), and as a "shortcut" + * it does not even cover a case that is likely to occur (no actual + * program would reset its filter on a regular basis). Presumably it + * is a way to deallocate filter memory on some platforms, but was that + * worth the RFC inclusion? Anyhow, we support it; NetBSD does not. + */ + if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, NULL, 0) != 0) e(0); + + packet.icmp6_type = TEST_ICMPV6_TYPE_B; + packet.icmp6_code = 90; + + if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(packet)) e(0); + + memset(&packet, 0, sizeof(packet)); + + if (recv(fd, &packet, sizeof(packet), 0) != sizeof(packet)) e(0); + if (packet.icmp6_type != TEST_ICMPV6_TYPE_A) e(0); + if (packet.icmp6_code != 78) e(0); + + if (recv(fd, &packet, sizeof(packet), 0) != sizeof(packet)) e(0); + if (packet.icmp6_type != TEST_ICMPV6_TYPE_B) e(0); + if (packet.icmp6_code != 90) e(0); + + if (recv(fd, &packet, sizeof(packet), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + if (recv(fd2, &packet, sizeof(packet), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + len = sizeof(filter); + if (getsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, &len) != 0) + e(0); + + for (i = 0; i <= UINT8_MAX; i++) + if (!ICMP6_FILTER_WILLPASS(i, &filter)) e(0); + + if (close(fd2) != 0) e(0); + + /* + * Let's get weird and send an ICMPv6 packet from an IPv4 socket. + * Currently, such packets are always dropped based on the rule that + * IPv6 sockets with checksumming enabled drop all IPv4 packets. As it + * happens, that is also all that is keeping this packet from arriving. + */ + if ((fd2 = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0); + + ICMP6_FILTER_SETBLOCKALL(&filter); + if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, + sizeof(filter)) != 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + memset(&packet, 0, sizeof(packet)); + packet.icmp6_type = TEST_ICMPV6_TYPE_A; + packet.icmp6_code = 123; + packet.icmp6_cksum = htons(0); /* TODO: use valid checksum */ + + if (sendto(fd2, &packet, sizeof(packet), 0, (struct sockaddr *)&sin, + sizeof(sin)) != sizeof(packet)) e(0); + + /* + * If the packet were to arrive at all, it should arrive instantly, so + * this is just an excuse to use SO_RCVTIMEO. + */ + tv.tv_sec = 0; + tv.tv_usec = 100000; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) + e(0); + + if (recv(fd, &packet, sizeof(packet), 0) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + if (close(fd2) != 0) e(0); + + if (close(fd) != 0) e(0); + + /* Make sure ICMP6_FILTER works on IPv6-ICMPv6 sockets only. */ + for (i = 0; i <= 2; i++) { + switch (i) { + case 0: fd = socket(AF_INET6, SOCK_DGRAM, 0); break; + case 1: fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO); break; + case 2: fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6); break; + } + if (fd < 0) e(0); + + if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, + sizeof(filter)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + len = sizeof(filter); + if (getsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, + &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + if (close(fd) != 0) e(0); + } +} + +/* + * Test that IPPROTO_ICMPV6 has no special value on IPv4 raw sockets. In + * particular, test that no checksum is generated or verified. By now we have + * already tested that none of the IPv6 socket options work on such sockets. + */ +static void +test92e(void) +{ + char buf[sizeof(struct ip) + sizeof(struct icmp6_hdr)]; + struct sockaddr_in sin; + struct icmp6_hdr packet; + int fd; + + subtest = 5; + + if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0); + + memset(&packet, 0, sizeof(packet)); + packet.icmp6_type = TEST_ICMPV6_TYPE_A; + packet.icmp6_code = 123; + packet.icmp6_cksum = htons(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (sendto(fd, &packet, sizeof(packet), 0, (struct sockaddr *)&sin, + sizeof(sin)) != sizeof(packet)) e(0); + + if (recv(fd, buf, sizeof(buf), 0) != sizeof(buf)) e(0); + + memcpy(&packet, &buf[sizeof(struct ip)], sizeof(packet)); + if (packet.icmp6_type != TEST_ICMPV6_TYPE_A) e(0); + if (packet.icmp6_code != 123) e(0); + if (packet.icmp6_cksum != htons(0)) e(0); + + if (close(fd) != 0) e(0); +} + +struct testpkt { + struct ip ip; + struct udphdr udp; + uint8_t data[6]; +} __packed; + +/* + * Test the IP_HDRINCL socket option. + */ +static void +test92f(void) +{ + struct sockaddr_in sin; + struct testpkt pkt, pkt2; + socklen_t len; + char buf[7]; + unsigned int i; + int fd, fd2, val; + + subtest = 6; + + /* See if we can successfully feign a UDP packet. */ + memset(&pkt, 0, sizeof(pkt)); + pkt.ip.ip_v = IPVERSION; + pkt.ip.ip_hl = sizeof(pkt.ip) >> 2; + pkt.ip.ip_tos = 123; + pkt.ip.ip_len = sizeof(pkt); /* swapped by OS */ + pkt.ip.ip_id = htons(456); + pkt.ip.ip_off = IP_DF; /* swapped by OS */ + pkt.ip.ip_ttl = 78; + pkt.ip.ip_p = IPPROTO_UDP; + pkt.ip.ip_sum = htons(0); /* filled by OS */ + pkt.ip.ip_src.s_addr = htonl(INADDR_LOOPBACK); + pkt.ip.ip_dst.s_addr = htonl(INADDR_LOOPBACK); + pkt.udp.uh_sport = htons(TEST_PORT_B); + pkt.udp.uh_dport = htons(TEST_PORT_A); + pkt.udp.uh_sum = htons(0); /* lazy.. */ + pkt.udp.uh_ulen = htons(sizeof(pkt.udp) + sizeof(pkt.data)); + memcpy(pkt.data, "Hello!", sizeof(pkt.data)); + + if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) e(0); + + if (shutdown(fd, SHUT_RD) != 0) e(0); + + /* IP_HDRINCL is never enabled by default. */ + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val)) != 0) + e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 1) e(0); + + if ((fd2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT_A); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(fd2, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + sin.sin_port = htons(0); + + if (sendto(fd, &pkt, sizeof(pkt), 0, (struct sockaddr *)&sin, + sizeof(sin)) != sizeof(pkt)) e(0); + + if (recv(fd2, &buf, sizeof(buf), 0) != sizeof(pkt.data)) e(0); + if (memcmp(buf, pkt.data, sizeof(pkt.data)) != 0) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, &len) != 0) e(0); + if (len != sizeof(val)) e(0); + if (val != 0) e(0); + + if (shutdown(fd, SHUT_RD) != 0) e(0); + + /* See if we can receive a packet for our own protocol. */ + pkt.ip.ip_p = TEST_PROTO; + + if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val)) != 0) + e(0); + + if (sendto(fd, &pkt, sizeof(pkt), 0, (struct sockaddr *)&sin, + sizeof(sin)) != sizeof(pkt)) e(0); + + if (recv(fd2, &pkt2, sizeof(pkt2), 0) != sizeof(pkt2)) e(0); + + if (pkt2.ip.ip_v != pkt.ip.ip_v) e(0); + if (pkt2.ip.ip_hl != pkt.ip.ip_hl) e(0); + if (pkt2.ip.ip_tos != pkt.ip.ip_tos) e(0); + if (pkt2.ip.ip_len != pkt.ip.ip_len) e(0); + if (pkt2.ip.ip_id != pkt.ip.ip_id) e(0); + if (pkt2.ip.ip_off != pkt.ip.ip_off) e(0); + if (pkt2.ip.ip_ttl != pkt.ip.ip_ttl) e(0); + if (pkt2.ip.ip_p != pkt.ip.ip_p) e(0); + if (pkt2.ip.ip_sum == htons(0)) e(0); + if (pkt2.ip.ip_src.s_addr != pkt.ip.ip_src.s_addr) e(0); + if (pkt2.ip.ip_dst.s_addr != pkt.ip.ip_dst.s_addr) e(0); + + /* + * Test sending packets with weird sizes to ensure that we do not crash + * the service. These packets would never arrive anyway. + */ + if (sendto(fd, &pkt, 0, 0, (struct sockaddr *)&sin, + sizeof(sin)) != -1) e(0); + if (errno != EINVAL) e(0); + if (sendto(fd, &pkt, sizeof(pkt.ip) - 1, 0, (struct sockaddr *)&sin, + sizeof(sin)) != -1) e(0); + if (errno != EINVAL) e(0); + if (sendto(fd, &pkt, sizeof(pkt.ip), 0, (struct sockaddr *)&sin, + sizeof(sin)) != sizeof(pkt.ip)) e(0); + + if (recv(fd2, &pkt2, sizeof(pkt2), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* Ensure that the socket option does not work on other types. */ + for (i = 0; i <= 1; i++) { + switch (i) { + case 0: fd = socket(AF_INET, SOCK_DGRAM, 0); break; + case 1: fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO); break; + } + if (fd < 0) e(0); + + len = sizeof(val); + if (getsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, + &len) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, + sizeof(val)) != -1) e(0); + if (errno != ENOPROTOOPT) e(0); + + if (close(fd) != 0) e(0); + } +} + +/* + * Test the IPPROTO_RAW socket protocol. This test mostly shows that the + * IPPROTO_RAW protocol is nothing special: for both IPv4 and IPv6, it sends + * and receives packets with that protocol number. We already tested earlier + * that IP_HDRINCL is disabled by default on IPPROTO_RAW sockets, too. + */ +static void +test92g(void) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + char buf[sizeof(struct ip) + 1]; + int fd; + + subtest = 7; + + if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (sendto(fd, "A", 1, 0, (struct sockaddr *)&sin, + sizeof(sin)) != 1) e(0); + + if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != sizeof(buf)) e(0); + if (buf[sizeof(struct ip)] != 'A') e(0); + + if (close(fd) != 0) e(0); + + if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + if (sendto(fd, "B", 1, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != 1) e(0); + + if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != 1) e(0); + if (buf[0] != 'B') e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test that connected raw sockets perform correct source-based filtering. + */ +static void +test92h(void) +{ + struct sockaddr_in sinA, sinB; + struct sockaddr_in6 sin6A, sin6B; + struct sockaddr sa; + socklen_t len; + char buf[sizeof(struct ip) + 1]; + int fd, fd2; + + subtest = 8; + + if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + len = sizeof(sinB); + if (getpeername(fd, (struct sockaddr *)&sinB, &len) != -1) e(0); + if (errno != ENOTCONN) e(0); + + memset(&sinA, 0, sizeof(sinA)); + sinA.sin_family = AF_INET; + sinA.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + /* + * First test that packets with the right source are accepted. + * Unfortunately, source and destination are the same in this case, so + * this test is far from perfect. + */ + if (connect(fd, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0); + + if (getpeername(fd, (struct sockaddr *)&sinB, &len) != 0) e(0); + + if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + if (sendto(fd2, "A", 1, 0, (struct sockaddr *)&sinA, + sizeof(sinA)) != 1) e(0); + + buf[0] = '\0'; + if (recv(fd2, buf, sizeof(buf), 0) != sizeof(struct ip) + 1) e(0); + if (buf[sizeof(struct ip)] != 'A') e(0); + + buf[0] = '\0'; + if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != + sizeof(struct ip) + 1) e(0); + if (buf[sizeof(struct ip)] != 'A') e(0); + + memset(&sa, 0, sizeof(sa)); + sa.sa_family = AF_UNSPEC; + + sinA.sin_addr.s_addr = htonl(INADDR_NONE); + + /* While here, test unconnecting the socket. */ + if (connect(fd, &sa, sizeof(sa)) != 0) e(0); + + if (getpeername(fd, (struct sockaddr *)&sinB, &len) != -1) e(0); + if (errno != ENOTCONN) e(0); + + /* Then test that packets with the wrong source are ignored. */ + if (connect(fd, (struct sockaddr *)&sinA, sizeof(sinA)) != 0) e(0); + + if (sendto(fd2, "B", 1, 0, (struct sockaddr *)&sinB, + sizeof(sinB)) != 1) e(0); + + buf[0] = '\0'; + if (recv(fd2, buf, sizeof(buf), 0) != sizeof(struct ip) + 1) e(0); + if (buf[sizeof(struct ip)] != 'B') e(0); + + if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + /* Repeat for IPv6, but now the other way around. */ + if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + len = sizeof(sin6B); + if (getpeername(fd, (struct sockaddr *)&sin6B, &len) != -1) e(0); + if (errno != ENOTCONN) e(0); + + memset(&sin6A, 0, sizeof(sin6A)); + sin6A.sin6_family = AF_INET6; + memcpy(&sin6A.sin6_addr, &in6addr_loopback, sizeof(sin6A.sin6_addr)); + + memcpy(&sin6B, &sin6A, sizeof(sin6B)); + if (inet_pton(AF_INET6, "::2", &sin6B.sin6_addr) != 1) e(0); + + if (connect(fd, (struct sockaddr *)&sin6B, sizeof(sin6B)) != 0) e(0); + + if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + if (sendto(fd2, "C", 1, 0, (struct sockaddr *)&sin6A, + sizeof(sin6A)) != 1) e(0); + + buf[0] = '\0'; + if (recv(fd2, buf, sizeof(buf), 0) != 1) e(0); + if (buf[0] != 'C') e(0); + + if (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + if (connect(fd, &sa, sizeof(sa)) != 0) e(0); + + if (connect(fd, (struct sockaddr *)&sin6A, sizeof(sin6A)) != 0) e(0); + + if (sendto(fd2, "D", 1, 0, (struct sockaddr *)&sin6A, + sizeof(sin6A)) != 1) e(0); + + buf[0] = '\0'; + if (recv(fd2, buf, sizeof(buf), 0) != 1) e(0); + if (buf[0] != 'D') e(0); + + buf[0] = '\0'; + if (recv(fd, buf, sizeof(buf), 0) != 1) e(0); + if (buf[0] != 'D') e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); +} + +/* + * Test sending large and small RAW packets. This test is an altered copy of + * test91e, but has been changed to IPv6 to cover a greater spectrum together. + */ +static void +test92i(void) +{ + struct sockaddr_in6 sin6; + struct msghdr msg; + struct iovec iov; + char *buf; + unsigned int i, j; + int r, fd, fd2, val; + + subtest = 9; + + if ((buf = malloc(65536)) == NULL) e(0); + + if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) != 0) e(0); + + val = 65536; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) != 0) + e(0); + + if ((fd2 = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + /* + * A maximum send buffer size of a full packet size's worth may always + * be set, although this is not necessarily the actual maximum. + */ + val = 65535; + if (setsockopt(fd2, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) != 0) + e(0); + + /* Find the largest possible packet size that can actually be sent. */ + for (i = 0; i < val; i += sizeof(int)) { + j = i ^ 0xdeadbeef; + memcpy(&buf[i], &j, sizeof(j)); + } + + for (val = 65536; val > 0; val--) { + if ((r = sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6, + sizeof(sin6))) == val) + break; + if (r != -1) e(0); + if (errno != EMSGSIZE) e(0); + } + + if (val != 65535 - sizeof(struct ip6_hdr)) e(0); + + memset(buf, 0, val); + buf[val] = 'X'; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = val + 1; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != val) e(0); + if (msg.msg_flags != 0) e(0); + + for (i = 0; i < val; i += sizeof(int)) { + j = i ^ 0xdeadbeef; + if (memcmp(&buf[i], &j, MIN(sizeof(j), val - i))) e(0); + } + if (buf[val] != 'X') e(0); + + if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != + val) e(0); + + /* + * Make sure that there are no off-by-one errors in the receive code, + * and that MSG_TRUNC is set (only) when not the whole packet was + * received. + */ + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = val; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != val) e(0); + if (msg.msg_flags != 0) e(0); + + if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != + val) e(0); + + buf[val - 1] = 'Y'; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = val - 1; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != val - 1) e(0); + if (msg.msg_flags != MSG_TRUNC) e(0); + + for (i = 0; i < val - 1; i += sizeof(int)) { + j = i ^ 0xdeadbeef; + if (memcmp(&buf[i], &j, MIN(sizeof(j), val - 1 - i))) e(0); + } + if (buf[val - 1] != 'Y') e(0); + + if (sendto(fd2, buf, val, 0, (struct sockaddr *)&sin6, sizeof(sin6)) != + val) e(0); + + buf[0] = 'Z'; + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = 0; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, 0) != 0) e(0); + if (msg.msg_flags != MSG_TRUNC) e(0); + if (buf[0] != 'Z') e(0); + + /* Make sure that zero-sized packets can be sent and received. */ + if (sendto(fd2, buf, 0, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != 0) e(0); + + /* + * Note how we currently assume that packets sent over localhost will + * arrive immediately, so that we can use MSG_DONTWAIT to avoid that + * the test freezes. + */ + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (recvmsg(fd, &msg, MSG_DONTWAIT) != 0) e(0); + if (msg.msg_flags != 0) e(0); + if (buf[0] != 'Z') e(0); + + if (recv(fd, buf, val, MSG_DONTWAIT) != -1) e(0); + if (errno != EWOULDBLOCK) e(0); + + /* + * When sending lots of small packets, ensure that fewer packets arrive + * than we sent. This sounds weird, but we cannot actually check the + * internal TCP/IP buffer granularity and yet we want to make sure that + * the receive queue is measured in terms of buffers rather than packet + * sizes. In addition, we check that older packets are favored, + * instead discarding new ones when the receive buffer is full. + */ + for (i = 0; i < 65536 / sizeof(j); i++) { + j = i; + if (sendto(fd2, &j, sizeof(j), 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(j)) e(0); + } + + for (i = 0; i < 1025; i++) { + r = recv(fd, &j, sizeof(j), MSG_DONTWAIT); + if (r == -1) { + if (errno != EWOULDBLOCK) e(0); + break; + } + if (r != sizeof(j)) e(0); + if (i != j) e(0); + } + if (i == 1025) e(0); + + if (close(fd2) != 0) e(0); + if (close(fd) != 0) e(0); + + free(buf); +} + +/* + * Test sending and receiving with bad pointers. + */ +static void +test92j(void) +{ + struct sockaddr_in sin; + char *ptr; + int i, fd; + + subtest = 10; + + if ((ptr = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0)) == MAP_FAILED) e(0); + + if (munmap(&ptr[PAGE_SIZE], PAGE_SIZE) != 0) e(0); + + if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0) e(0); + + memset(ptr, 'A', PAGE_SIZE); + + if (sendto(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0, + (struct sockaddr *)&sin, sizeof(sin)) != -1) e(0); + if (errno != EFAULT) e(0); + + memset(ptr, 'B', PAGE_SIZE); + + if (sendto(fd, ptr, PAGE_SIZE - sizeof(struct ip), 0, + (struct sockaddr *)&sin, sizeof(sin)) != + PAGE_SIZE - sizeof(struct ip)) e(0); + + memset(ptr, 0, PAGE_SIZE); + + if (recvfrom(fd, &ptr[PAGE_SIZE / 2], PAGE_SIZE, 0, NULL, 0) != -1) + e(0); + if (errno != EFAULT) e(0); + + if (recvfrom(fd, ptr, PAGE_SIZE * 2, 0, NULL, 0) != PAGE_SIZE) e(0); + for (i = sizeof(struct ip); i < PAGE_SIZE; i++) + if (ptr[i] != 'B') e(0); + + if (close(fd) != 0) e(0); + + if (munmap(ptr, PAGE_SIZE) != 0) e(0); +} + +/* + * Test basic sysctl(2) socket enumeration support. + */ +static void +test92k(void) +{ + struct kinfo_pcb ki; + struct sockaddr_in lsin, rsin; + struct sockaddr_in6 lsin6, rsin6; + int fd, fd2, val; + + subtest = 11; + + if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0, + &ki) != 0) e(0); + + if ((fd = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + memset(&lsin, 0, sizeof(lsin)); + lsin.sin_len = sizeof(lsin); + lsin.sin_family = AF_INET; + + memset(&rsin, 0, sizeof(rsin)); + rsin.sin_len = sizeof(rsin); + rsin.sin_family = AF_INET; + + if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0, + &ki) != 1) e(0); + if (ki.ki_type != SOCK_RAW) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0, + &ki) != 0) e(0); + + lsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(fd, (struct sockaddr *)&lsin, sizeof(lsin)) != 0) e(0); + + if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0, + &ki) != 1) e(0); + if (ki.ki_type != SOCK_RAW) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + if (ki.ki_pflags & INP_HDRINCL) e(0); + + rsin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (connect(fd, (struct sockaddr *)&rsin, sizeof(rsin)) != 0) e(0); + + if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0, + &ki) != 1) e(0); + if (ki.ki_type != SOCK_RAW) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + if ((fd2 = socket(AF_INET, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + if (sendto(fd2, "ABC", 3, 0, (struct sockaddr *)&lsin, + sizeof(lsin)) != 3) e(0); + + if (close(fd2) != 0) e(0); + + val = 1; + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val)) != 0) + e(0); + + if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0, + &ki) != 1) e(0); + if (ki.ki_type != SOCK_RAW) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin, sizeof(lsin)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin, sizeof(rsin)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq < 3) e(0); /* size is rounded up */ + if (!(ki.ki_pflags & INP_HDRINCL)) e(0); + + if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0, + &ki) != 0) e(0); + + if (close(fd) != 0) e(0); + + /* Test IPv6 sockets as well. */ + if ((fd = socket(AF_INET6, SOCK_RAW, TEST_PROTO)) < 0) e(0); + + memset(&lsin6, 0, sizeof(lsin6)); + lsin6.sin6_len = sizeof(lsin6); + lsin6.sin6_family = AF_INET6; + + memset(&rsin6, 0, sizeof(rsin6)); + rsin6.sin6_len = sizeof(rsin6); + rsin6.sin6_family = AF_INET6; + + if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0, + &ki) != 1) e(0); + if (ki.ki_type != SOCK_RAW) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + + memcpy(&lsin6.sin6_addr, &in6addr_loopback, sizeof(lsin6.sin6_addr)); + if (bind(fd, (struct sockaddr *)&lsin6, sizeof(lsin6)) != 0) e(0); + + if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0, + &ki) != 1) e(0); + if (ki.ki_type != SOCK_RAW) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0); + + memcpy(&rsin6.sin6_addr, &in6addr_loopback, sizeof(rsin6.sin6_addr)); + if (connect(fd, (struct sockaddr *)&rsin6, sizeof(rsin6)) != 0) + e(0); + + if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0, + &ki) != 1) e(0); + if (ki.ki_type != SOCK_RAW) e(0); + if (ki.ki_tstate != 0) e(0); + if (memcmp(&ki.ki_src, &lsin6, sizeof(lsin6)) != 0) e(0); + if (memcmp(&ki.ki_dst, &rsin6, sizeof(rsin6)) != 0) e(0); + if (ki.ki_sndq != 0) e(0); + if (ki.ki_rcvq != 0) e(0); + if (!(ki.ki_pflags & IN6P_IPV6_V6ONLY)) e(0); + + if (socklib_find_pcb("net.inet.raw.pcblist", TEST_PROTO, 0, 0, + &ki) != 0) e(0); + + if (close(fd) != 0) e(0); + + if (socklib_find_pcb("net.inet6.raw6.pcblist", TEST_PROTO, 0, 0, + &ki) != 0) e(0); +} + +/* + * Test local and remote IPv6 address handling. In particular, test scope IDs + * and IPv4-mapped IPv6 addresses. + */ +static void +test92l(void) +{ + + subtest = 12; + + socklib_test_addrs(SOCK_RAW, TEST_PROTO); +} + +/* + * Test setting and retrieving basic multicast transmission options. + */ +static void +test92m(void) +{ + + subtest = 13; + + socklib_multicast_tx_options(SOCK_RAW); +} + +/* + * Test multicast support. + */ +static void +test92n(void) +{ + + subtest = 14; + + socklib_test_multicast(SOCK_RAW, TEST_PROTO); +} + +/* + * Test small and large ICMP echo ("ping") packets. This test aims to confirm + * expected behavior resulting from the LWIP service's memory pool policies: + * lwIP should reply to ICMP echo requests that fit in a single 512-byte buffer + * (including space for ethernet headers, even on loopback interfaces), but not + * to requests exceeding a single buffer. + */ +static void +test92o(void) +{ + struct sockaddr_in6 sin6; + struct icmp6_hdr packet; + char buf[512]; + int fd; + + subtest = 15; + + /* IPv6 only for now, for simplicity reasons. */ + if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) e(0); + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &in6addr_loopback, sizeof(sin6.sin6_addr)); + + memset(&packet, 0, sizeof(packet)); + packet.icmp6_type = ICMP6_ECHO_REQUEST; + packet.icmp6_code = 0; + packet.icmp6_id = getpid(); + packet.icmp6_seq = 1; + + memset(buf, 'A', sizeof(buf)); + memcpy(buf, &packet, sizeof(packet)); + + if (sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(buf)) e(0); + + packet.icmp6_seq = 2; + + memset(buf, 'B', sizeof(buf)); + memcpy(buf, &packet, sizeof(packet)); + + if (sendto(fd, buf, sizeof(buf) - 100, 0, (struct sockaddr *)&sin6, + sizeof(sin6)) != sizeof(buf) - 100) e(0); + + do { + memset(buf, '\0', sizeof(buf)); + + if (recv(fd, buf, sizeof(buf), 0) <= 0) e(0); + + memcpy(&packet, buf, sizeof(packet)); + } while (packet.icmp6_type == ICMP6_ECHO_REQUEST); + + if (packet.icmp6_type != ICMP6_ECHO_REPLY) e(0); + if (packet.icmp6_code != 0) e(0); + if (packet.icmp6_id != getpid()) e(0); + if (packet.icmp6_seq != 2) e(0); + if (buf[sizeof(buf) - 101] != 'B') e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Test program for LWIP RAW sockets. + */ +int +main(int argc, char ** argv) +{ + int i, m; + + start(92); + + if (argc == 2) + m = atoi(argv[1]); + else + m = 0xFFFF; + + for (i = 0; i < ITERATIONS; i++) { + if (m & 0x0001) test92a(); + if (m & 0x0002) test92b(); + if (m & 0x0004) test92c(); + if (m & 0x0008) test92d(); + if (m & 0x0010) test92e(); + if (m & 0x0020) test92f(); + if (m & 0x0040) test92g(); + if (m & 0x0080) test92h(); + if (m & 0x0100) test92i(); + if (m & 0x0200) test92j(); + if (m & 0x0400) test92k(); + if (m & 0x0400) test92k(); + if (m & 0x0800) test92l(); + if (m & 0x1000) test92m(); + if (m & 0x2000) test92n(); + if (m & 0x4000) test92o(); + } + + quit(); + /* NOTREACHED */ +} diff --git a/minix/tests/test93.c b/minix/tests/test93.c new file mode 100644 index 000000000..cf0438310 --- /dev/null +++ b/minix/tests/test93.c @@ -0,0 +1,839 @@ +/* Tests for network interfaces and routing (LWIP) - by D.C. van Moolenbroek */ +/* This test needs to be run as root: it manipulates network settings. */ +/* + * TODO: due to time constraints, this test is currently absolutely minimal. + * It does not yet test by far most of the service code it is supposed to test, + * in particular interface management code, interface address assignment code, + * routing sockets code, and routing code. The second subtest (test93b) in this + * file serves as a reasonable example of how many of the future subtests + * should operate, though: by issuing interface IOCTLs and routing commands on + * a loopback interface created for the occasion. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "socklib.h" + +#define TEST_IFNAME "lo93" + +#define ITERATIONS 2 + +static const enum state rtlnk_states[] = { + S_NEW, S_N_SHUT_R, S_N_SHUT_W, S_N_SHUT_RW, +}; + +static const int rt_results[][__arraycount(rtlnk_states)] = { + [C_ACCEPT] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_BIND] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_CONNECT] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_GETPEERNAME] = { + 0, 0, 0, 0, + }, + [C_GETSOCKNAME] = { + 0, 0, 0, 0, + }, + [C_GETSOCKOPT_ERR] = { + 0, 0, 0, 0, + }, + [C_GETSOCKOPT_KA] = { + 0, 0, 0, 0, + }, + [C_GETSOCKOPT_RB] = { + 0, 0, 0, 0, + }, + [C_IOCTL_NREAD] = { + 0, 0, 0, 0, + }, + [C_LISTEN] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_RECV] = { + -EAGAIN, 0, -EAGAIN, 0, + }, + [C_RECVFROM] = { + -EAGAIN, 0, -EAGAIN, 0, + }, + [C_SEND] = { + -ENOBUFS, -ENOBUFS, -EPIPE, -EPIPE, + }, + [C_SENDTO] = { + -EISCONN, -EISCONN, -EPIPE, -EPIPE, + }, + [C_SELECT_R] = { + 0, 1, 0, 1, + }, + [C_SELECT_W] = { + 1, 1, 1, 1, + }, + [C_SELECT_X] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_BC] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_KA] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_L] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_RA] = { + 0, 0, 0, 0, + }, + [C_SHUTDOWN_R] = { + 0, 0, 0, 0, + }, + [C_SHUTDOWN_RW] = { + 0, 0, 0, 0, + }, + [C_SHUTDOWN_W] = { + 0, 0, 0, 0, + }, +}; + +static const int lnk_results[][__arraycount(rtlnk_states)] = { + [C_ACCEPT] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_BIND] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_CONNECT] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_GETPEERNAME] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_GETSOCKNAME] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_GETSOCKOPT_ERR] = { + 0, 0, 0, 0, + }, + [C_GETSOCKOPT_KA] = { + 0, 0, 0, 0, + }, + [C_GETSOCKOPT_RB] = { + -ENOPROTOOPT, -ENOPROTOOPT, -ENOPROTOOPT, -ENOPROTOOPT, + }, + [C_IOCTL_NREAD] = { + 0, 0, 0, 0, + }, + [C_LISTEN] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, -EOPNOTSUPP, + }, + [C_RECV] = { + -EOPNOTSUPP, 0, -EOPNOTSUPP, 0, + }, + [C_RECVFROM] = { + -EOPNOTSUPP, 0, -EOPNOTSUPP, 0, + }, + [C_SEND] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EPIPE, -EPIPE, + }, + [C_SENDTO] = { + -EOPNOTSUPP, -EOPNOTSUPP, -EPIPE, -EPIPE, + }, + [C_SELECT_R] = { + 1, 1, 1, 1, + }, + [C_SELECT_W] = { + 1, 1, 1, 1, + }, + [C_SELECT_X] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_BC] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_KA] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_L] = { + 0, 0, 0, 0, + }, + [C_SETSOCKOPT_RA] = { + 0, 0, 0, 0, + }, + [C_SHUTDOWN_R] = { + 0, 0, 0, 0, + }, + [C_SHUTDOWN_RW] = { + 0, 0, 0, 0, + }, + [C_SHUTDOWN_W] = { + 0, 0, 0, 0, + }, +}; + +/* + * Set up a routing or link socket file descriptor in the requested state and + * pass it to socklib_sweep_call() along with local and remote addresses and + * their lengths. + */ +static int +rtlnk_sweep(int domain, int type, int protocol, enum state state, + enum call call) +{ + struct sockaddr sa; + int r, fd; + + memset(&sa, 0, sizeof(sa)); + sa.sa_family = domain; + + if ((fd = socket(domain, type | SOCK_NONBLOCK, protocol)) < 0) e(0); + + switch (state) { + case S_NEW: break; + case S_N_SHUT_R: if (shutdown(fd, SHUT_RD)) e(0); break; + case S_N_SHUT_W: if (shutdown(fd, SHUT_WR)) e(0); break; + case S_N_SHUT_RW: if (shutdown(fd, SHUT_RDWR)) e(0); break; + default: e(0); + } + + r = socklib_sweep_call(call, fd, &sa, &sa, + offsetof(struct sockaddr, sa_data)); + + if (close(fd) != 0) e(0); + + return r; +} + +/* + * Sweep test for socket calls versus socket states of routing and link + * sockets. + */ +static void +test93a(void) +{ + + subtest = 1; + + socklib_sweep(AF_ROUTE, SOCK_RAW, 0, rtlnk_states, + __arraycount(rtlnk_states), (const int *)rt_results, rtlnk_sweep); + + /* + * Our implementation of link sockets currently serves only one + * purpose, and that is to pass on ioctl() calls issued on the socket. + * As such, the results here are not too important. The test mostly + * ensures that all calls actually complete--for example, that there is + * no function pointer NULL check missing in libsockevent. + */ + socklib_sweep(AF_LINK, SOCK_DGRAM, 0, rtlnk_states, + __arraycount(rtlnk_states), (const int *)lnk_results, rtlnk_sweep); +} + +/* + * Attempt to destroy the test loopback interface. Return 0 if destruction was + * successful, or -1 if no such interface existed. + */ +static int +test93_destroy_if(void) +{ + struct ifreq ifr; + int r, fd; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, TEST_IFNAME, sizeof(ifr.ifr_name)); + + r = ioctl(fd, SIOCIFDESTROY, &ifr); + if (r != 0 && (r != -1 || errno != ENXIO)) e(0); + + if (close(fd) != 0) e(0); + + return r; +} + +/* + * Destroy the test interface at exit. It is always safe to do so as its name + * is sufficiently unique, and we do not want to leave it around. + */ +static void +test93_destroy_if_atexit(void) +{ + static int atexit_set = 0; + + if (!atexit_set) { + (void)test93_destroy_if(); + + atexit_set = 1; + } +} + +/* + * Attempt to create a test loopback interface. Return 0 if creation was + * successful, or -1 if no more interfaces could be created. + */ +static int +test93_create_if(void) +{ + struct ifreq ifr; + int r, fd; + + (void)test93_destroy_if(); + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, TEST_IFNAME, sizeof(ifr.ifr_name)); + + r = ioctl(fd, SIOCIFCREATE, &ifr); + if (r != 0 && (r != -1 || errno != ENOBUFS)) e(0); + + if (close(fd) != 0) e(0); + + atexit(test93_destroy_if_atexit); + + return r; +} + +/* + * Set the interface-up value for an interface to the given boolean value. + */ +static void +test93_set_if_up(const char * ifname, int up) +{ + struct ifreq ifr; + int fd; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) e(0); + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, TEST_IFNAME, sizeof(ifr.ifr_name)); + + if (ioctl(fd, SIOCGIFFLAGS, &ifr) != 0) e(0); + + if (up) + ifr.ifr_flags |= IFF_UP; + else + ifr.ifr_flags &= ~IFF_UP; + + if (ioctl(fd, SIOCSIFFLAGS, &ifr) != 0) e(0); + + if (close(fd) != 0) e(0); +} + +/* + * Construct an IPv6 network mask for a certain prefix length. + */ +static void +test93_make_netmask6(struct sockaddr_in6 * sin6, unsigned int prefix) +{ + unsigned int byte, bit; + + if (prefix > 128) e(0); + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + + byte = prefix / NBBY; + bit = prefix % NBBY; + + if (byte > 0) + memset(sin6->sin6_addr.s6_addr, 0xff, byte); + if (bit != 0) + sin6->sin6_addr.s6_addr[byte] = 0xff << (NBBY - bit); +} + +/* + * Issue a modifying routing command, which must be one of RTM_ADD, RTM_CHANGE, + * RTM_DELETE, or RTM_LOCK. The destination address (IPv4 or IPv6) and netmask + * prefix are required. The flags (RTF_), interface name, and gateway are + * optional depending on the command (and flags) being issued. Return 0 on + * success, and -1 with errno set on failure. + */ +static int +test93_route_cmd(int cmd, const struct sockaddr * dest, socklen_t dest_len, + unsigned int prefix, int flags, const char * ifname, + const struct sockaddr * gw, socklen_t gw_len) +{ + static unsigned int seq = 0; + struct sockaddr_storage destss, maskss, ifpss, gwss; + struct sockaddr_in mask4; + struct sockaddr_in6 mask6; + struct sockaddr_dl ifp; + struct rt_msghdr rtm; + struct iovec iov[5]; + struct msghdr msg; + unsigned int i, iovlen; + int r, fd, err; + + memset(&rtm, 0, sizeof(rtm)); + rtm.rtm_version = RTM_VERSION; + rtm.rtm_type = cmd; + rtm.rtm_flags = flags; + rtm.rtm_addrs = RTA_DST | RTA_NETMASK; + rtm.rtm_seq = ++seq; + + iovlen = 0; + iov[iovlen].iov_base = &rtm; + iov[iovlen++].iov_len = sizeof(rtm); + + memset(&destss, 0, sizeof(destss)); + memcpy(&destss, dest, dest_len); + destss.ss_len = dest_len; + + iov[iovlen].iov_base = &destss; + iov[iovlen++].iov_len = RT_ROUNDUP(dest_len); + + /* Do this in RTA order. */ + memset(&gwss, 0, sizeof(gwss)); + if (gw != NULL) { + memcpy(&gwss, gw, gw_len); + gwss.ss_len = gw_len; + + rtm.rtm_addrs |= RTA_GATEWAY; + iov[iovlen].iov_base = &gwss; + iov[iovlen++].iov_len = RT_ROUNDUP(gwss.ss_len); + } + + memset(&maskss, 0, sizeof(maskss)); + switch (dest->sa_family) { + case AF_INET: + if (prefix > 32) e(0); + memset(&mask4, 0, sizeof(mask4)); + mask4.sin_family = AF_INET; + if (prefix < 32) + mask4.sin_addr.s_addr = htonl(0xffffffffUL << prefix); + + memcpy(&maskss, &mask4, sizeof(mask4)); + maskss.ss_len = sizeof(mask4); + + break; + + case AF_INET6: + test93_make_netmask6(&mask6, prefix); + + memcpy(&maskss, &mask6, sizeof(mask6)); + maskss.ss_len = sizeof(mask6); + + break; + + default: + e(0); + } + + iov[iovlen].iov_base = &maskss; + iov[iovlen++].iov_len = RT_ROUNDUP(maskss.ss_len); + + if (ifname != NULL) { + memset(&ifp, 0, sizeof(ifp)); + ifp.sdl_nlen = strlen(ifname); + ifp.sdl_len = offsetof(struct sockaddr_dl, sdl_data) + + ifp.sdl_nlen; + ifp.sdl_family = AF_LINK; + + memset(&ifpss, 0, sizeof(ifpss)); + memcpy(&ifpss, &ifp, ifp.sdl_len); + memcpy(&((struct sockaddr_dl *)&ifpss)->sdl_data, ifname, + ifp.sdl_nlen); + + rtm.rtm_addrs |= RTA_IFP; + iov[iovlen].iov_base = &ifpss; + iov[iovlen++].iov_len = RT_ROUNDUP(ifpss.ss_len); + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = iovlen; + + if ((fd = socket(AF_ROUTE, SOCK_RAW, 0)) < 0) e(0); + + for (i = 0; i < iovlen; i++) + rtm.rtm_msglen += iov[i].iov_len; + + r = sendmsg(fd, &msg, 0); + if (r != rtm.rtm_msglen && r != -1) e(0); + err = errno; + + /* + * We could just shut down the socket for reading, but this is just an + * extra test we can do basically for free. + */ + rtm.rtm_seq = 0; + do { + iov[0].iov_base = &rtm; + iov[0].iov_len = sizeof(rtm); + + if (recvmsg(fd, &msg, 0) <= 0) e(0); + } while (rtm.rtm_pid != getpid() || rtm.rtm_seq != seq); + + if (r == -1) { + if (rtm.rtm_errno != err) e(0); + if (rtm.rtm_flags & RTF_DONE) e(0); + } else { + if (rtm.rtm_errno != 0) e(0); + if (!(rtm.rtm_flags & RTF_DONE)) e(0); + } + + if (close(fd) != 0) e(0); + + errno = err; + return (r > 0) ? 0 : -1; +} + +/* + * Add or delete an IPv6 address to or from an interface. The interface name, + * address, and prefix length must always be given. When adding, a set of + * flags (IN6_IFF) and lifetimes must be given as well. + */ +static void +test93_ipv6_addr(int add, const char * ifname, + const struct sockaddr_in6 * sin6, unsigned int prefix, int flags, + uint32_t valid_life, uint32_t pref_life) +{ + struct in6_aliasreq ifra; + int fd; + + memset(&ifra, 0, sizeof(ifra)); + strlcpy(ifra.ifra_name, ifname, sizeof(ifra.ifra_name)); + memcpy(&ifra.ifra_addr, sin6, sizeof(ifra.ifra_addr)); + /* leave ifra_dstaddr blank */ + test93_make_netmask6(&ifra.ifra_prefixmask, prefix); + ifra.ifra_flags = flags; + ifra.ifra_lifetime.ia6t_vltime = valid_life; + ifra.ifra_lifetime.ia6t_pltime = pref_life; + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + if (ioctl(fd, (add) ? SIOCAIFADDR_IN6 : SIOCDIFADDR_IN6, &ifra) != 0) + e(0); + + if (close(fd) != 0) e(0); +} + +static const struct { + int result; /* 0..2 = prefer srcN, -1 = no preference */ + const char *dest_addr; + const char *src0_addr; + unsigned int src0_prefix; + int src0_flags; + const char *src1_addr; + unsigned int src1_prefix; + int src1_flags; + const char *src2_addr; + unsigned int src2_prefix; + int src2_flags; +} test93b_table[] = { + /* + * These are all the applicable tests from RFC 6724 Sec. 10.1, slightly + * changed not to use the default link-local address of lo0. + */ + /* Prefer appropriate scope: */ + { 0, "2001:db8:1::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 }, + /* Prefer appropriate scope: */ + { 0, "ff05::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 }, + /* Prefer same address: */ + { 0, "2001:db8:1::1", "2001:db8:1::1", 64, IN6_IFF_DEPRECATED, + "2001:db8:2::1", 64, 0 }, + /* Prefer appropriate scope: */ + { 0, "fe80::93:1", "fe80::93:2", 64, IN6_IFF_DEPRECATED, + "2001:db8:2::1", 64, 0 }, + /* Longest matching prefix: */ + { 0, "2001:db8:1::1", "2001:db8:1::2", 64, 0, "2001:db8:3::2", 64, 0 }, + /* Prefer matching label: */ + { 0, "2002:c633:6401::1", "2002:c633:6401::d5e3:7953:13eb:22e8", 64, + IN6_IFF_TEMPORARY, "2001:db8:1::2", 64, 0 }, + /* Prefer temporary address: */ + { 1, "2001:db8:1::d5e3:0:0:1", "2001:db8:1::2", 64, 0, + "2001:db8:1::d5e3:7953:13eb:22e8", 64, IN6_IFF_TEMPORARY }, + /* + * Our own additional tests. + */ + /* Prefer same address: */ + { 1, "4000:93::1", "2001:db8:3::1", 64, 0, "4000:93::1", 64, 0 }, + { 2, "2001:db8:1::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0, + "2001:db8:1::1", 64, 0 }, + /* Prefer appropriate scope: */ + { 1, "ff01::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 }, + { 1, "ff02::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 }, + { 0, "ff0e::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 }, + { 1, "fd00:93::1", "2001:db8:3::1", 64, 0, "fd00::93:2", 64, 0 }, + { 1, "fd00:93::1", "fe80::93:1", 64, 0, "fd00::93:2", 64, 0 }, + { 0, "fd00:93::1", "2001:db8:3::1", 64, 0, "fe80::93:1", 64, 0 }, + { 1, "2001:db8:1::1", "fe80::93:1", 64, 0, "fd00::93:2", 64, 0 }, + { 0, "2001:db8:1::1", "2001:db8:3::1", 64, 0, "4000:93::1", 64, 0 }, + { 0, "4000:93::2", "2001:db8:3::1", 64, 0, "4000:93::1", 64, 0 }, + { 2, "2001:db8:1::1", "fe80::93:1", 64, 0, "fd00::93:1", 64, 0, + "2001:db8:3::1", 64, 0 }, + { 2, "2001:db8:1::1", "fe80::93:1", 64, IN6_IFF_DEPRECATED, + "fe80::93:2", 64, 0, "2001:db8:3::1", 64, 0 }, + /* Avoid deprecated address: */ + { 1, "2002:c633:6401::1", "2002:c633:6401::d5e3:7953:13eb:22e8", 64, + IN6_IFF_DEPRECATED, "2001:db8:1::2", 64, 0 }, + { 2, "2001:db8:1::1", "2001:db8:1::3", 64, IN6_IFF_DEPRECATED, + "2001:db8:2::1", 64, IN6_IFF_DEPRECATED, "2001:db8:3::1", 64, 0 }, + { 2, "2001:db8:1::1", "2002:db8:1::3", 64, IN6_IFF_DEPRECATED, + "2001:db8:2::1", 64, IN6_IFF_DEPRECATED, "2001:db8:3::1", 64, 0 }, + /* Prefer matching label: */ + { 0, "2002:c633:6401::1", "2002:c633:6401::d5e3:7953:13eb:22e8", 64, 0, + "2001:db8:1::2", 64, IN6_IFF_TEMPORARY }, + { 2, "2002:c633:6401::1", "2001:db8:3::2", 64, 0, "2001:db8:1::2", 64, + IN6_IFF_TEMPORARY, "2002:c633:6401::d5e3:7953:13eb:22e8", 64, 0 }, + { 2, "2001:db8:1::1", "2003:db8::1", 64, 0, "3ffe:db8::1", 64, 0, + "2001:db8:3::1", 64, 0 }, + /* Prefer temporary address: */ + { 0, "2001:db8:1::d5e3:0:0:1", "2001:db8:1::2", 96, IN6_IFF_TEMPORARY, + "2001:db8:1::d5e3:7953:13eb:22e8", 96, 0 }, + { 2, "2002:c633:6401::1", "2001:db8:3::2", 64, 0, "2002:c633:6401::2", + 64, 0, "2002:c633:6401::d5e3:7953:13eb:22e8", 64, + IN6_IFF_TEMPORARY }, + /* Longest matching prefix: */ + { 1, "2001:db8:1::d5e3:0:0:1", "2001:db8:1::2", 96, 0, + "2001:db8:1::d5e3:7953:13eb:22e8", 96, 0 }, + { 2, "2001:db8:1:1::1", "2001:db8:2:1::2", 64, 0, "2001:db8:1:2::2", + 64, 0, "2001:db8:1:1::2", 64, 0 }, + { 0, "2001:db8:1::1", "2001:db8:1::2", 47, 0, "2001:db8:3::2", 47, 0 }, + /* No preference (a tie): */ + { -1, "2001:db8:1::1", "2001:db8:1::2", 46, 0, "2001:db8:3::2", 46, + 0 }, + { -1, "2001:db8::1:0:0:1", "2001:db8::1:0:0:2", 64, 0, + "2001:db8::2:0:0:2", 64, 0, "2001:db8::3:0:0:2", 64, 0 }, +}; + +struct src_addr { + struct sockaddr_in6 addr; + unsigned int prefix; + int flags; +}; + +/* + * Test source address selection with a particular destination address and two + * or three source addresses. + */ +static void +sub93b(int result, const struct sockaddr_in6 * dest, unsigned int ifindex, + const struct src_addr * src0, const struct src_addr * src1, + const struct src_addr * src2) +{ + struct sockaddr_in6 dest_copy, src; + socklen_t len; + int fd, rt_res; + + /* Add the candidate source addresses. */ + test93_ipv6_addr(1, TEST_IFNAME, &src0->addr, src0->prefix, + src0->flags, 0xffffffffUL, 0xffffffffUL); + + test93_ipv6_addr(1, TEST_IFNAME, &src1->addr, src1->prefix, + src1->flags, 0xffffffffUL, 0xffffffffUL); + + if (src2 != NULL) + test93_ipv6_addr(1, TEST_IFNAME, &src2->addr, src2->prefix, + src2->flags, 0xffffffffUL, 0xffffffffUL); + + /* + * We need to make sure that packets to the destination are routed to + * our test interface at all, so create a route for it. Creating the + * route may fail if the destination address is equal to either of the + * source addresses, but that is fine. We use a blackhole route here, + * but this test should not generate any traffic anyway. + */ + rt_res = test93_route_cmd(RTM_ADD, (struct sockaddr *)dest, + sizeof(*dest), 128, RTF_UP | RTF_BLACKHOLE | RTF_STATIC, + TEST_IFNAME, NULL, 0); + if (rt_res != 0 && (rt_res != -1 || errno != EEXIST)) e(0); + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) e(0); + + /* Set a scope ID if necessary. */ + memcpy(&dest_copy, dest, sizeof(dest_copy)); + dest_copy.sin6_port = 1; /* anything that is not zero */ + if (IN6_IS_ADDR_LINKLOCAL(&dest_copy.sin6_addr) || + IN6_IS_ADDR_MC_NODELOCAL(&dest_copy.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&dest_copy.sin6_addr)) + dest_copy.sin6_scope_id = ifindex; + + /* Connecting also selects a source address. */ + if (connect(fd, (struct sockaddr *)&dest_copy, sizeof(dest_copy)) != 0) + e(0); + + /* Obtain the selected source address. */ + len = sizeof(src); + if (getsockname(fd, (struct sockaddr *)&src, &len) != 0) e(0); + + /* + * If the chosen destination address has a scope ID, it must be for our + * test interface. + */ + if (src.sin6_scope_id != 0 && src.sin6_scope_id != ifindex) e(0); + + /* Is it the expected candidate source address? */ + if (!memcmp(&src.sin6_addr, &src0->addr.sin6_addr, + sizeof(src.sin6_addr))) { + if (result != 0) e(0); + } else if (!memcmp(&src.sin6_addr, &src1->addr.sin6_addr, + sizeof(src.sin6_addr))) { + if (result != 1) e(0); + } else if (src2 != NULL && !memcmp(&src.sin6_addr, + &src2->addr.sin6_addr, sizeof(src.sin6_addr))) { + if (result != 2) e(0); + } else + e(0); + + /* Clean up. */ + if (close(fd) != 0) e(0); + + if (rt_res == 0) { + if (test93_route_cmd(RTM_DELETE, (struct sockaddr *)dest, + sizeof(*dest), 128, 0, NULL, NULL, 0) != 0) e(0); + } + + if (src2 != NULL) + test93_ipv6_addr(0, TEST_IFNAME, &src2->addr, src2->prefix, 0, + 0, 0); + + test93_ipv6_addr(0, TEST_IFNAME, &src1->addr, src1->prefix, 0, 0, 0); + + test93_ipv6_addr(0, TEST_IFNAME, &src0->addr, src0->prefix, 0, 0, 0); +} + +/* + * IPv6 source address selection algorithm test. + */ +static void +test93b(void) +{ + static const int order[][3] = { + { 0, 1, 2 }, + { 1, 0, 2 }, + { 0, 2, 1 }, + { 1, 2, 0 }, + { 2, 0, 1 }, + { 2, 1, 0 } + }; + struct sockaddr_in6 dest; + struct src_addr src[3]; + unsigned int i, j, k, count, ifindex; + int result; + + subtest = 2; + + if (test93_create_if() != 0) + return; /* skip this test */ + + if ((ifindex = if_nametoindex(TEST_IFNAME)) == 0) e(0); + + test93_set_if_up(TEST_IFNAME, 1); + + for (i = 0; i < __arraycount(test93b_table); i++) { + memset(&dest, 0, sizeof(dest)); + dest.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, test93b_table[i].dest_addr, + &dest.sin6_addr) != 1) e(0); + + memset(&src[0].addr, 0, sizeof(src[0].addr)); + src[0].addr.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, test93b_table[i].src0_addr, + &src[0].addr.sin6_addr) != 1) e(0); + src[0].prefix = test93b_table[i].src0_prefix; + src[0].flags = test93b_table[i].src0_flags; + + memset(&src[1].addr, 0, sizeof(src[1].addr)); + src[1].addr.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, test93b_table[i].src1_addr, + &src[1].addr.sin6_addr) != 1) e(0); + src[1].prefix = test93b_table[i].src1_prefix; + src[1].flags = test93b_table[i].src1_flags; + + if (test93b_table[i].src2_addr != NULL) { + memset(&src[2].addr, 0, sizeof(src[2].addr)); + src[2].addr.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, test93b_table[i].src2_addr, + &src[2].addr.sin6_addr) != 1) e(0); + src[2].prefix = test93b_table[i].src2_prefix; + src[2].flags = test93b_table[i].src2_flags; + + count = 6; + } else + count = 2; + + result = test93b_table[i].result; + + /* + * Try all orders for the source addresses. The permutation + * part can be done much better, but it really does not matter. + */ + for (j = 0; j < count; j++) { + for (k = 0; k < count; k++) + if (result == -1 || order[j][k] == result) + break; + + sub93b((result != -1) ? k : 0, &dest, ifindex, + &src[order[j][0]], &src[order[j][1]], + (count > 2) ? &src[order[j][2]] : NULL); + } + } + + if (test93_destroy_if() != 0) e(0); +} + +/* + * Interface index number wrapping test. + */ +static void +test93c(void) +{ + unsigned int i; + + subtest = 3; + + /* There might not be an available loopback interface at all. */ + if (test93_create_if() != 0) + return; /* skip this test */ + + if (test93_destroy_if() != 0) e(0); + + /* + * During the development of the LWIP service, the lwIP library's + * interface index assignment was still in its infancy. This test aims + * to ensure that future changes in the library do not break our + * service. + */ + for (i = 0; i < UINT8_MAX + 1; i++) { + if (test93_create_if() != 0) e(0); + + if (test93_destroy_if() != 0) e(0); + } +} + +/* + * Test program for LWIP interface and routing management. + */ +int +main(int argc, char ** argv) +{ + int i, m; + + start(93); + + if (argc == 2) + m = atoi(argv[1]); + else + m = 0xFF; + + for (i = 0; i < ITERATIONS; i++) { + if (m & 0x01) test93a(); + if (m & 0x02) test93b(); + if (m & 0x04) test93c(); + } + + quit(); + /* NOTREACHED */ +} diff --git a/minix/tests/test94.c b/minix/tests/test94.c new file mode 100644 index 000000000..0587b7692 --- /dev/null +++ b/minix/tests/test94.c @@ -0,0 +1,2650 @@ +/* Tests for BPF devices (LWIP) - by D.C. van Moolenbroek */ +/* This test needs to be run as root: opening BPF devices is root-only. */ +/* + * We do not attempt to test the BPF filter code here. Such a test is better + * done through standardized tests and with direct use of the filter code. + * The current BPF filter implementation has been run through the FreeBSD + * BPF filter regression tests (from their tools/regression/bpf/bpf_filter), of + * which only the last test (0084 - "Check very long BPF program") failed due + * to our lower and strictly enforced BPF_MAXINSNS value. Future modifications + * of the BPF filter code should be tested against at least that test set. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ +} -- 2.44.0