]> Zhao Yanbai Git Server - minix.git/commitdiff
test83: add test to send strange/wrong UDP and TCP packets 34/3034/1
authorErik van der Kouwe <erik@minix3.org>
Wed, 22 Jul 2015 19:39:39 +0000 (21:39 +0200)
committerErik van der Kouwe <erik@minix3.org>
Wed, 22 Jul 2015 20:25:09 +0000 (22:25 +0200)
Change-Id: I73444d2753adab140a4f8e6bee2db32282044888

distrib/sets/lists/minix/mi
minix/tests/Makefile
minix/tests/run
minix/tests/test83.c [new file with mode: 0644]

index a0fc79585f78c5f8207e62ac3ff8346859513e7b..01fa3d222b40d58e765f57e4aaea5e2e87bfcce1 100644 (file)
 ./usr/tests/minix-posix/test80         minix-sys
 ./usr/tests/minix-posix/test81         minix-sys
 ./usr/tests/minix-posix/test82         minix-sys
+./usr/tests/minix-posix/test83         minix-sys
 ./usr/tests/minix-posix/test9          minix-sys
 ./usr/tests/minix-posix/testinterp     minix-sys
 ./usr/tests/minix-posix/testisofs      minix-sys
index 6eb056fa3591ec2667c413850d36106bf49ba6a9..8a1d6af7d86c9e918b0facb68ce296ded695340e 100644 (file)
@@ -59,7 +59,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
+81 82 83
 
 .if ${MACHINE_ARCH} == "i386"
 MINIX_TESTS+= \
index d97bbe5557a6e827439d49efeb3c717d319a3b71..40b8d7f972f05c3d0619f481648257fa7b42076d 100755 (executable)
@@ -22,7 +22,7 @@ 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"
+        test69 test73 test74 test78 test83"
 # Scripts that require to be run as root
 rootscripts="testisofs testvnd testrelpol"
 
@@ -30,7 +30,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 sh1 sh2 interp mfs isofs vnd"
+        81 82 83 sh1 sh2 interp mfs isofs vnd"
 tests_no=`expr 0`
 
 # If root, make sure the setuid tests have the correct permissions
diff --git a/minix/tests/test83.c b/minix/tests/test83.c
new file mode 100644 (file)
index 0000000..35122fb
--- /dev/null
@@ -0,0 +1,1654 @@
+/*
+ * test83: test bad network packets
+ */
+
+#define DEBUG 0
+
+#if DEBUG
+#define dbgprintf(...) do {                                            \
+                               struct timeval time = { };              \
+                               gettimeofday(&time, NULL);              \
+                               fprintf(stderr, "[%2d:%.2d:%.2d.%.6d p%d %s:%d] ", \
+                                       (int) ((time.tv_sec / 3600) % 24), \
+                                       (int) ((time.tv_sec / 60) % 60), \
+                                       (int) (time.tv_sec % 60),       \
+                                       time.tv_usec,                   \
+                                       getpid(),                       \
+                                       __FUNCTION__,                   \
+                                       __LINE__);                      \
+                               fprintf(stderr, __VA_ARGS__);           \
+                               fflush(stderr);                         \
+                       } while (0)
+#else
+#define        dbgprintf(...)
+#endif
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <net/gen/ether.h>
+#include <net/gen/eth_io.h>
+#include <net/gen/in.h>
+#include <net/gen/ip_io.h>
+
+#include "common.h"
+
+int max_error = 100;
+
+/* https://tools.ietf.org/html/rfc791 */
+struct header_ip {
+       uint8_t  ver_ihl; /* Version (4 bits) + IHL (4 bits) */
+       uint8_t  tos;     /* Type of Service */
+       uint16_t len;     /* Total Length */
+       uint16_t id;      /* Identification */
+       uint16_t fl_fo;   /* Flags (3 bits) + Fragment Offset (13 bits) */
+       uint8_t  ttl;     /* Time to Live */
+       uint8_t  prot;    /* Protocol */
+       uint16_t cs;      /* Header Checksum */
+       uint32_t src;     /* Source Address */
+       uint32_t dst;     /* Destination Address */
+       uint8_t  opt[16]; /* Options  */
+};
+#define IP_FLAG_EVIL   (1 << 2)
+#define IP_FLAG_DF     (1 << 1)
+#define IP_FLAG_MF     (1 << 0)
+
+/* https://tools.ietf.org/html/rfc790 */
+#define IP_PROT_ICMP   1
+#define IP_PROT_TCP    6
+#define IP_PROT_UDP    17
+
+/* https://tools.ietf.org/html/rfc768 */
+struct header_udp {
+       uint16_t src; /* Source Port */
+       uint16_t dst; /* Destination Port */
+       uint16_t len; /* Length */
+       uint16_t cs;  /* Checksum */
+};
+
+struct header_udp_pseudo {
+       uint32_t src;
+       uint32_t dst;
+       uint8_t  zero;
+       uint8_t  prot;
+       uint16_t len;
+};
+
+/* https://tools.ietf.org/html/rfc793 */
+struct header_tcp {
+       uint16_t src;     /* Source Port */
+       uint16_t dst;     /* Destination Port */
+       uint32_t seq;     /* Sequence Number */
+       uint32_t ack;     /* Acknowledgment Number */
+       uint8_t  doff;    /* Data Offset */
+       uint8_t  fl;      /* Flags */
+       uint16_t win;     /* Window */
+       uint16_t cs;      /* Checksum */
+       uint16_t uptr;    /* Urgent Pointer */
+       uint8_t  opt[16]; /* Options  */
+};
+#define TCP_FLAG_URG   (1 << 5)
+#define TCP_FLAG_ACK   (1 << 4)
+#define TCP_FLAG_PSH   (1 << 3)
+#define TCP_FLAG_RST   (1 << 2)
+#define TCP_FLAG_SYN   (1 << 1)
+#define TCP_FLAG_FIN   (1 << 0)
+
+#define PORT_BASE      12345
+#define PORT_COUNT_TCP 4
+#define PORT_COUNT_UDP 2
+#define PORT_COUNT     (PORT_COUNT_TCP + PORT_COUNT_UDP)
+
+#define PORT_BASE_SRC  (PORT_BASE + PORT_COUNT)
+#define PORT_COUNT_SRC 79
+
+#define PAYLOADSIZE_COUNT 6
+static const size_t payloadsizes[] = {
+       0,
+       1,
+       100,
+       1024,
+       2345,
+       65535 - sizeof(struct header_ip) - sizeof(struct header_udp),
+};
+
+#define ADDR_COUNT_MAX 10
+static size_t addr_count;
+static uint32_t addrsrc = 0xc0000201; /* 192.0.2.1 (TEST-NET) */
+static uint32_t addrdst = 0x7f000001; /* 127.0.0.1 (localhost) */
+static uint32_t addrs[ADDR_COUNT_MAX] = {
+       0x00000000, /* 0.0.0.0 (INADDR_NONE) */
+       0x7f000001, /* 127.0.0.1 (localhost) */
+       0xc0000201, /* 192.0.2.1 (TEST-NET) */
+       0xffffffff, /* 255.255.255.255 (broadcast) */
+       /* local addresses will be added */
+};
+
+#define CLOSE(fd) do { assert(fd >= 0); if (close((fd)) != 0) efmt("close failed"); } while (0);
+enum server_action {
+       sa_close,
+       sa_read,
+       sa_selectr,
+       sa_selectrw,
+       sa_write,
+};
+static int server_done;
+
+static void server_alarm(int seconds);
+
+static char *sigstr_cat(char *p, const char *s) {
+       size_t slen = strlen(s);
+       memcpy(p, s, slen);
+       return p + slen;
+}
+
+static char *sigstr_itoa(char *p, unsigned long n) {
+       unsigned digit;
+       unsigned long factor = 1000000000UL;
+       int first = 1;
+
+       while (factor > 0) {
+               digit = (n / factor) % 10;
+               if (!first || digit || factor == 1) {
+                       *(p++) = digit + '0';
+                       first = 0;
+               }
+               factor /= 10;
+       }
+       return p;
+}
+
+static void dbgprintdata(const void *data, size_t size) {
+#if DEBUG
+       size_t addr;
+       const unsigned char *p = data;
+
+       for (addr = 0; addr < size; addr++) {
+               if (addr % 16 == 0) {
+                       if (addr > 0) fprintf(stderr, "\n");
+                       fprintf(stderr, "%.4zx", addr);
+               }
+               fprintf(stderr, " %.2x", p[addr]);
+       }
+       fprintf(stderr, "\n");
+       fflush(stderr);
+#endif
+}
+
+static void dbgprint_sig(const char *name) {
+#if DEBUG
+       char buf[256];
+       char *p = buf;
+
+       /* fprintf not used to be signal safe */
+       p = sigstr_cat(p, "[");
+       p = sigstr_itoa(p, getpid());
+       p = sigstr_cat(p, "] ");
+       p = sigstr_cat(p, name);
+       p = sigstr_cat(p, "\n");
+       write(STDERR_FILENO, buf, p - buf);
+#endif
+}
+
+#define SIGNAL(sig, handler) (signal_checked((sig), (handler), #sig, __FILE__, __FUNCTION__, __LINE__))
+
+static void signal_checked(int sig, void (* handler)(int), const char *signame,
+       const char *file, const char *func, int line) {
+       char buf[256];
+       char *p = buf;
+       struct sigaction sa = {
+               .sa_handler = handler,
+       };
+
+       if (sigaction(sig, &sa, NULL) == 0) return;
+
+       /* efmt not used to be signal safe */
+       p = sigstr_cat(p, "[");
+       p = sigstr_cat(p, file);
+       p = sigstr_cat(p, ":");
+       p = sigstr_itoa(p, line);
+       p = sigstr_cat(p, "] error: sigaction(");
+       p = sigstr_cat(p, signame);
+       p = sigstr_cat(p, ") failed in function ");
+       p = sigstr_cat(p, func);
+       p = sigstr_cat(p, ": ");
+       p = sigstr_itoa(p, errno);
+       p = sigstr_cat(p, "\n");
+       write(STDERR_FILENO, buf, p - buf);
+       errct++;
+}
+
+static void server_sigusr1(int signo) {
+       dbgprint_sig("SIGUSR1");
+
+       /* terminate on the first opportunity */
+       server_done = 1;
+
+       /* in case signal is caught before a blocking operation,
+        * keep interrupting
+        */
+       server_alarm(1);
+}
+
+static void server_stop(pid_t pid) {
+
+       if (pid < 0) return;
+
+       dbgprintf("sending SIGUSR1 to child %d\n", (int) pid);
+       if (kill(pid, SIGUSR1) != 0) efmt("kill failed");
+}
+
+static void server_wait(pid_t pid) {
+       int exitcode, status;
+       pid_t r;
+
+       if (pid < 0) return;
+
+       dbgprintf("waiting for child %d\n", (int) pid);
+       r = waitpid(pid, &status, 0);
+       if (r != pid) {
+               efmt("waitpid failed");
+               return;
+       }
+
+       if (WIFEXITED(status)) {
+               exitcode = WEXITSTATUS(status);
+               if (exitcode < 0) {
+                       efmt("negative exit code from child %d\n", (int) pid);
+               } else {
+                       dbgprintf("child exited exitcode=%d\n", exitcode);
+                       errct += exitcode;
+               }
+       } else if (WIFSIGNALED(status)) {
+               efmt("child killed by signal %d", WTERMSIG(status));
+       } else {
+               efmt("child has unexpected exit status 0x%x", status);
+       }
+}
+
+static void server_sigalrm(int signum) {
+       server_alarm(1);
+}
+
+static void server_alarm(int seconds) {
+       SIGNAL(SIGALRM, server_sigalrm);
+       alarm(seconds);
+}
+
+static void server_no_alarm(void) {
+       int errno_old = errno;
+       alarm(0);
+       SIGNAL(SIGALRM, SIG_DFL);
+       errno = errno_old;
+}
+
+static int server_rw(int fd, int is_write, int *success) {
+       char buf[4096];
+       ssize_t r;
+
+       /* return 0 means close connection, *success=0 means stop server */
+
+       if (is_write) {
+               /* ignore SIGPIPE */
+               SIGNAL(SIGPIPE, SIG_IGN);
+
+               /* initialize buffer */
+               memset(buf, -1, sizeof(buf));
+       }
+
+       /* don't block for more than 1s */
+       server_alarm(1);
+
+       /* perform read or write operation */
+       dbgprintf("server_rw waiting is_write=%d\n", is_write);
+       r = is_write ? write(fd, buf, sizeof(buf)) : read(fd, buf, sizeof(buf));
+
+       /* stop alarm (preserves errno) */
+       server_no_alarm();
+
+       /* handle read/write result */
+       if (r >= 0) {
+               dbgprintf("server_rw done\n");
+               *success = 1;
+               return r > 0;
+       }
+
+       switch (errno) {
+       case EINTR:
+               dbgprintf("server_rw interrupted\n");
+               *success = 1;
+               return 0;
+       case ECONNRESET:
+               dbgprintf("server_rw connection reset\n");
+               *success = 1;
+               return 0;
+       case EPIPE:
+               if (is_write) {
+                       dbgprintf("server_rw EPIPE\n");
+                       *success = 1;
+                       return 0;
+               }
+               /* fall through */
+       default:
+               efmt("%s failed", is_write ? "write" : "read");
+               *success = 0;
+               return 0;
+       }
+}
+
+static int server_select(int fd, int is_rw, int *success,
+       enum server_action *actionnext) {
+       int r;
+       fd_set readfds, writefds;
+       struct timeval timeout = { .tv_sec = 1, .tv_usec = 0 };
+
+       /* return 0 means close connection, *success=0 means stop server */
+
+       /* prepare fd sets */
+       FD_ZERO(&readfds);
+       FD_SET(fd, &readfds);
+       FD_ZERO(&writefds);
+       if (is_rw) FD_SET(fd, &writefds);
+
+       /* perform select */
+       errno = 0;
+       dbgprintf("server_select waiting\n");
+       r = select(fd + 1, &readfds, &writefds, NULL, &timeout);
+
+       /* handle result */
+       if (r < 0) {
+               switch (errno) {
+               case EINTR:
+                       dbgprintf("server_select interrupted\n");
+                       *success = 1;
+                       return 0;
+               default:
+                       efmt("select failed");
+                       *success = 0;
+                       return 0;
+               }
+       }
+       if (r == 0) {
+               dbgprintf("server_select nothing available\n");
+               *success = 1;
+               return 0;
+       }
+
+       if (FD_ISSET(fd, &readfds)) {
+               dbgprintf("server_select read available\n");
+               *actionnext = sa_read;
+               *success = 1;
+               return 1;
+       } else if (FD_ISSET(fd, &writefds)) {
+               dbgprintf("server_select write available\n");
+               *actionnext = sa_write;
+               *success = 1;
+               return 1;
+       }
+
+       *success = 0;
+       efmt("select did not set fd");
+       return 0;
+}
+
+static int server_accept(int servfd, int type, enum server_action action) {
+       enum server_action actionnext;
+       struct sockaddr addr;
+       socklen_t addrsize;
+       int connfd;
+       int success = 0;
+
+       /* if connection-oriented, accept a conmection */
+       if (type == SOCK_DGRAM) {
+               connfd = servfd;
+       } else {
+               dbgprintf("server_accept waiting for connection\n");
+               addrsize = sizeof(addr);
+               connfd = accept(servfd, &addr, &addrsize);
+               if (connfd < 0) {
+                       switch (errno) {
+                       case EINTR:
+                               dbgprintf("server_accept interrupted\n");
+                               return 1;
+                       default:
+                               efmt("cannot accept connection");
+                               return 0;
+                       }
+               }
+               dbgprintf("server_accept new connection\n");
+       }
+
+       /* perform requested action while the connection is open */
+       actionnext = action;
+       while (!server_done) {
+               switch (actionnext) {
+               case sa_close:
+                       success = 1;
+                       goto cleanup;
+               case sa_read:
+                       if (!server_rw(connfd, 0, &success)) goto cleanup;
+                       actionnext = action;
+                       break;
+               case sa_selectr:
+               case sa_selectrw:
+                       if (!server_select(connfd, actionnext == sa_selectrw,
+                               &success, &actionnext)) {
+                               goto cleanup;
+                       }
+                       break;
+               case sa_write:
+                       if (!server_rw(connfd, 1, &success)) goto cleanup;
+                       actionnext = action;
+                       break;
+               default:
+                       efmt("bad server action");
+                       success = 0;
+                       goto cleanup;
+               }
+       }
+
+       /* socket connection socket */
+cleanup:
+       dbgprintf("server_accept done success=%d\n", success);
+       if (connfd != servfd) CLOSE(connfd);
+       return success;
+}
+
+static pid_t server_start(int type, int port, enum server_action action) {
+       struct sockaddr_in addr = {
+               .sin_family = AF_INET,
+               .sin_port = htons(port),
+               .sin_addr = { htonl(INADDR_ANY) },
+       };
+       int fd;
+       pid_t pid = -1;
+
+       dbgprintf("server_start port %d\n", port);
+
+       /* create socket */
+       fd = socket(AF_INET, type, 0);
+       if (fd < 0) {
+               efmt("cannot create socket");
+               goto cleanup;
+       }
+
+       /* bind socket */
+       if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
+               efmt("cannot bind socket");
+               goto cleanup;
+       }
+
+       /* make it a server socket if needed */
+       if (type != SOCK_DGRAM) {
+               if (listen(fd, 5) != 0) {
+                       efmt("cannot listen on socket");
+                       goto cleanup;
+               }
+       }
+
+       /* intercept SIGUSR1 in case parent wants the server to stop */
+       SIGNAL(SIGUSR1, server_sigusr1);
+
+       /* fork; parent continues, child becomes server */
+       pid = fork();
+       if (pid < 0) {
+               efmt("cannot create socket");
+               goto cleanup;
+       }
+       if (pid) goto cleanup;
+
+       /* server loop */
+       dbgprintf("server_start child\n");
+       while (!server_done && server_accept(fd, type, action)) {}
+       dbgprintf("server_start child returns\n");
+
+       CLOSE(fd);
+       exit(errct);
+
+cleanup:
+       dbgprintf("server_start parent returns pid=%d\n", (int) pid);
+       if (fd >= 0) CLOSE(fd);
+       return pid;
+}
+
+static ssize_t send_packet_raw(int fd, const void *buf, size_t size) {
+       return write(fd, buf, size);
+}
+
+enum settings_ip {
+       si_bad_version   = (1 <<  0),
+       si_bad_ihl_small = (1 <<  1),
+       si_bad_ihl_big   = (1 <<  2),
+       si_bad_len_small = (1 <<  3),
+       si_bad_len_big   = (1 <<  4),
+       si_bad_len_huge  = (1 <<  5),
+       si_bad_cs        = (1 <<  6),
+       si_zero_cs       = (1 <<  7),
+
+       si_flag_evil     = (1 <<  8),
+       si_flag_df       = (1 <<  9),
+       si_flag_mf       = (1 << 10),
+
+       si_opt_end       = (1 << 11),
+       si_opt_topsec    = (1 << 12),
+       si_opt_nop       = (1 << 13),
+       si_opt_badopt    = (1 << 14),
+       si_opt_badpad    = (1 << 15),
+};
+
+enum settings_udp {
+       su_bad_len_small = (1 <<  0),
+       su_bad_len_big   = (1 <<  1),
+       su_bad_len_huge  = (1 <<  2),
+       su_bad_cs        = (1 <<  3),
+       su_zero_cs       = (1 <<  4),
+};
+
+enum fragmode_ip {
+       fi_as_needed,
+       fi_one,
+       fi_two,
+       fi_frag_tiny,
+       fi_frag_overlap,
+       fi_frag_first,
+       fi_frag_last,
+       fi_frag_repeat,
+       fi_fo_max,
+};
+
+static uint16_t checksum_ip(const void *header, size_t headersize) {
+       const uint16_t *p = header;
+       uint32_t sum = 0;
+
+       while (headersize > 0) {
+               assert(headersize >= sizeof(*p));
+               sum += ntohs(*p);
+               headersize -= sizeof(*p);
+               p++;
+       }
+       sum += sum >> 16;
+       return htons(~sum);
+}
+
+static void send_packet_ip_base(
+       int fd,
+       enum settings_ip ipsettings,
+       uint8_t  tos,
+       uint16_t id,
+       uint16_t fo,
+       uint8_t  ttl,
+       uint8_t  prot,
+       uint32_t srcip,
+       uint32_t dstip,
+       const void *payload,
+       size_t payloadsize) {
+       uint8_t ver = (ipsettings & si_bad_version) ? 3 : 4;
+       uint8_t ihl, ihl_fuzzed;
+       uint16_t fl = ((ipsettings & si_flag_evil) ? IP_FLAG_EVIL : 0) |
+                     ((ipsettings & si_flag_df)   ? IP_FLAG_DF   : 0) |
+                     ((ipsettings & si_flag_mf)   ? IP_FLAG_MF   : 0);
+       uint16_t len;
+       int optlen;
+       struct header_ip header = {
+               .tos     = tos,
+               .id      = htons(id),
+               .fl_fo   = htons((fl << 13) | fo),
+               .ttl     = ttl,
+               .prot    = prot,
+               .cs      = 0,
+               .src     = htonl(srcip),
+               .dst     = htonl(dstip),
+       };
+       char packet[6536];
+       size_t packetsize;
+       ssize_t r;
+
+       dbgprintf("sending IP packet src=%d.%d.%d.%d dst=%d.%d.%d.%d "
+               "payloadsize=%zu id=0x%.4x fragoff=%d%s\n",
+               (uint8_t) (srcip >> 24), (uint8_t) (srcip >> 16),
+               (uint8_t) (srcip >> 8), (uint8_t) (srcip >> 0),
+               (uint8_t) (dstip >> 24), (uint8_t) (dstip >> 16),
+               (uint8_t) (dstip >> 8), (uint8_t) (dstip >> 0),
+               payloadsize, id, fo, (ipsettings & si_flag_mf) ? " (MF)" : "");
+
+       optlen = 0;
+       if (ipsettings & si_opt_badpad) memset(header.opt, -1, sizeof(header.opt));
+       if (ipsettings & si_opt_nop) header.opt[optlen++] = 0x01;
+       if (ipsettings & si_opt_topsec) {
+               header.opt[optlen++] = 0x82;
+               header.opt[optlen++] = 0x0b;
+               header.opt[optlen++] = 0x6b; /* S: top secret */
+               header.opt[optlen++] = 0xc5; /* S: top secret */
+               header.opt[optlen++] = 0x00; /* C */
+               header.opt[optlen++] = 0x00; /* C */
+               header.opt[optlen++] = 'A'; /* H */
+               header.opt[optlen++] = 'B'; /* H */
+               header.opt[optlen++] = 'C'; /* TCC */
+               header.opt[optlen++] = 'D'; /* TCC */
+               header.opt[optlen++] = 'E'; /* TCC */
+       }
+       if (ipsettings & si_opt_badopt) header.opt[optlen++] = 0xff;
+       if (ipsettings & si_opt_end) header.opt[optlen++] = 0x00;
+       assert(optlen <= sizeof(header.opt));
+
+       ihl = ihl_fuzzed = (20 + optlen + 3) / 4;
+       if (ipsettings & si_bad_ihl_small) ihl_fuzzed = 4;
+       if (ipsettings & si_bad_ihl_big) ihl_fuzzed = 15;
+       header.ver_ihl = (ver << 4) | ihl_fuzzed;
+
+       len = ihl * 4 + payloadsize;
+       if (ipsettings & si_bad_len_small) len = ihl * 4 - 1;
+       if (ipsettings & si_bad_len_big) len += 1;
+       if (ipsettings & si_bad_len_huge) len = 0xffff;
+       header.len = htons(len);
+
+       packetsize = ihl * 4 + payloadsize;
+       if (packetsize > sizeof(packet)) {
+               payloadsize = sizeof(packet) - ihl * 4;
+               packetsize = sizeof(packet);
+       }
+
+       header.cs = checksum_ip(&header, ihl * 4);
+       if (ipsettings & si_zero_cs) header.cs = 0;
+       if (ipsettings & si_bad_cs) header.cs += 1;
+
+       memset(packet, 0, sizeof(packet));
+       memcpy(packet, &header, ihl * 4);
+       memcpy(packet + ihl * 4, payload, payloadsize);
+
+       errno = 0;
+       r = send_packet_raw(fd, packet, packetsize);
+       if (r == -1 && errno == EPACKSIZE &&
+               (packetsize < 60 || packetsize > 1514)) {
+               return;
+       }
+       if (r != packetsize) {
+               efmt("write to network interface failed");
+       }
+}
+
+static void send_packet_ip(
+       int fd,
+       enum settings_ip ipsettings,
+       uint8_t tos,
+       uint16_t id,
+       uint8_t ttl,
+       uint8_t prot,
+       uint32_t srcip,
+       uint32_t dstip,
+       enum fragmode_ip fragmode,
+       const void *payload,
+       size_t payloadsize) {
+       enum settings_ip flags;
+       size_t fragcount = 1;
+       size_t fragsize, fragsizecur;
+       size_t fragstart = 0;
+       size_t fragstep;
+
+       switch (fragmode) {
+       case fi_as_needed:
+               fragsize = fragstep = 1500;
+               fragcount = (payloadsize + fragsize - 1) / fragsize;
+               break;
+       case fi_one:
+       case fi_fo_max:
+               fragsize = fragstep = payloadsize;
+               break;
+       case fi_two:
+               fragcount = 2;
+               fragsize = fragstep = (payloadsize + 1) / 2;
+               break;
+       case fi_frag_tiny:
+               fragcount = (payloadsize >= 100) ? 100 :
+                       (payloadsize < 1) ? 1 : payloadsize;
+               fragsize = fragstep = (payloadsize + fragcount - 1) / fragcount;
+               break;
+       case fi_frag_overlap:
+               fragcount = 2;
+               fragsize = (payloadsize * 2 + 2) / 3;
+               fragstep = (payloadsize + 1) / 2;
+               break;
+       case fi_frag_first:
+               fragcount = 1;
+               fragsize = fragstep = (payloadsize + 1) / 2;
+               break;
+       case fi_frag_last:
+               fragcount = 1;
+               fragsize = fragstep = (payloadsize + 1) / 2;
+               break;
+       case fi_frag_repeat:
+               fragcount = 2;
+               fragsize = payloadsize;
+               fragstep = 0;
+               break;
+       }
+
+       while (fragcount > 0) {
+               if (fragstart >= payloadsize) {
+                       fragsizecur = 0;
+               } else if (payloadsize - fragstart < fragsize) {
+                       fragsizecur = payloadsize - fragstart;
+               } else {
+                       fragsizecur = fragsize;
+               }
+
+               flags = 0;
+               if (fragstart + fragsizecur < payloadsize) flags |= si_flag_mf;
+               send_packet_ip_base(
+                       fd,
+                       ipsettings | flags,
+                       tos,
+                       id,
+                       (fragmode == fi_fo_max) ? 0x1fff : fragstart,
+                       ttl,
+                       prot,
+                       srcip,
+                       dstip,
+                       (uint8_t *) payload + fragstart,
+                       fragsizecur);
+
+               fragcount--;
+               fragstart += fragstep;
+       }
+}
+
+static uint32_t checksum_udp_sum(const void *buf, size_t size) {
+       const uint16_t *p = buf;
+       uint32_t sum = 0;
+
+       while (size > 0) {
+               assert(size >= sizeof(*p));
+               sum += ntohs(*p);
+               size -= sizeof(*p);
+               p++;
+       }
+       return sum;
+}
+
+static uint16_t checksum_udp(
+       uint32_t srcip,
+       uint32_t dstip,
+       uint8_t prot,
+       const void *packet,
+       size_t packetsize) {
+       uint32_t sum = 0;
+       struct header_udp_pseudo header = {
+               .src = htonl(srcip),
+               .dst = htonl(dstip),
+               .zero = 0,
+               .prot = prot,
+               .len = htons(packetsize),
+       };
+
+       sum = checksum_udp_sum(&header, sizeof(header)) +
+               checksum_udp_sum(packet, packetsize + packetsize % 2);
+       sum += sum >> 16;
+       return ntohs(~sum);
+}
+
+static void send_packet_udp(
+       int fd,
+       enum settings_ip ipsettings,
+       uint8_t tos,
+       uint16_t id,
+       uint8_t ttl,
+       uint8_t prot,
+       uint32_t srcip,
+       uint32_t dstip,
+       enum fragmode_ip fragmode,
+       enum settings_udp udpsettings,
+       uint16_t srcport,
+       uint16_t dstport,
+       const void *payload,
+       size_t payloadsize) {
+       uint16_t len;
+       struct header_udp header = {
+               .src = htons(srcport),
+               .dst = htons(dstport),
+               .cs = 0,
+       };
+       char packet[65536];
+       size_t packetsize;
+
+       dbgprintf("sending UDP packet srcport=%d dstport=%d payloadsize=%zu\n",
+               srcport, dstport, payloadsize);
+
+       len = sizeof(struct header_udp) + payloadsize;
+       if (udpsettings & su_bad_len_small) len = sizeof(struct header_udp) - 1;
+       if (udpsettings & su_bad_len_big) len += 1;
+       if (udpsettings & su_bad_len_huge) len = 65535 - sizeof(struct header_ip);
+       header.len = htons(len);
+
+       packetsize = sizeof(header) + payloadsize;
+       assert(packetsize <= sizeof(packet));
+
+       memcpy(packet, &header, sizeof(header));
+       memcpy(packet + sizeof(header), payload, payloadsize);
+       if (packetsize % 2) packet[packetsize] = 0;
+
+       header.cs = checksum_udp(srcip, dstip, prot, packet, packetsize);
+       if (udpsettings & su_zero_cs) header.cs = 0;
+       if (udpsettings & su_bad_cs) header.cs += 1;
+
+       memcpy(packet, &header, sizeof(header));
+       send_packet_ip(
+               fd,
+               ipsettings,
+               tos,
+               id,
+               ttl,
+               prot,
+               srcip,
+               dstip,
+               fragmode,
+               packet,
+               packetsize);
+}
+
+struct send_packet_udp_simple_params {
+       int fd;
+       enum settings_ip ipsettings;
+       uint8_t tos;
+       uint16_t *id;
+       uint8_t ttl;
+       uint8_t prot;
+       uint32_t srcip;
+       uint32_t dstip;
+       enum fragmode_ip fragmode;
+       enum settings_udp udpsettings;
+       uint16_t srcport;
+       uint16_t dstport;
+       size_t payloadsize;
+};
+
+static void send_packet_udp_simple(
+       const struct send_packet_udp_simple_params *params) {
+       int i;
+       char payload[65536];
+
+       assert(params->payloadsize <= sizeof(payload));
+       for (i = 0; i < params->payloadsize; i++) {
+               payload[i] = *params->id + i;
+       }
+
+       send_packet_udp(
+               params->fd,
+               params->ipsettings,
+               params->tos,
+               *params->id,
+               params->ttl,
+               params->prot,
+               params->srcip,
+               params->dstip,
+               params->fragmode,
+               params->udpsettings,
+               params->srcport,
+               params->dstport,
+               payload,
+               params->payloadsize);
+       *params->id += 5471;
+}
+
+static void send_packets_ip_settings(
+       const struct send_packet_udp_simple_params *paramsbase) {
+       struct send_packet_udp_simple_params params;
+       int i;
+       enum settings_ip ipsettings[] = {
+               0,
+               si_bad_version,
+               si_bad_ihl_small,
+               si_bad_ihl_big,
+               si_bad_len_small,
+               si_bad_len_big,
+               si_bad_len_huge,
+               si_bad_cs,
+               si_zero_cs,
+               si_flag_evil,
+               si_flag_df,
+               si_flag_mf,
+               si_opt_end,
+               si_opt_topsec,
+               si_opt_nop,
+               si_opt_badopt,
+               si_opt_nop | si_opt_end | si_opt_badpad,
+       };
+       uint8_t ttls[] = { 0, 1, 127, 128, 255 };
+
+       /* various types of flags/options/corruptions */
+       params = *paramsbase;
+       for (i = 0; i < 17; i++) {
+               params.ipsettings = ipsettings[i];
+               send_packet_udp_simple(&params);
+       }
+
+       /* various TTL settings */
+       params = *paramsbase;
+       for (i = 0; i < 5; i++) {
+               params.ttl = ttls[i];
+               send_packet_udp_simple(&params);
+       }
+}
+
+static void send_packets_ip(int fd) {
+       enum fragmode_ip fragmode;
+       int i, j;
+       uint16_t id = 0;
+       struct send_packet_udp_simple_params params;
+       const struct send_packet_udp_simple_params paramsbase = {
+               .fd            = fd,
+               .ipsettings    = 0,
+               .tos           = 0,
+               .id            = &id,
+               .ttl           = 10,
+               .prot          = IP_PROT_UDP,
+               .srcip         = addrsrc,
+               .dstip         = addrdst,
+               .fragmode      = fi_as_needed,
+               .udpsettings   = 0,
+               .srcport       = PORT_BASE + 0,
+               .dstport       = PORT_BASE + 1,
+               .payloadsize   = 1234,
+       };
+
+       /* send packets with various payload sizes and corruptions */
+       params = paramsbase;
+       for (i = 0; i < PAYLOADSIZE_COUNT; i++) {
+               params.payloadsize = payloadsizes[i];
+               send_packets_ip_settings(&params);
+       }
+
+       /* send packets with various addresses and corruptions */
+       params = paramsbase;
+       for (i = 0; i < addr_count; i++) {
+       for (j = 0; j < addr_count; j++) {
+               params.srcip = addrs[i];
+               params.dstip = addrs[j];
+               send_packets_ip_settings(&params);
+       }
+       }
+
+       /* send valid packets with various fragmentation settings */
+       params = paramsbase;
+       for (i = 0; i < PAYLOADSIZE_COUNT; i++) {
+       for (fragmode = fi_as_needed; fragmode <= fi_fo_max; fragmode++) {
+               params.payloadsize = payloadsizes[i];
+               params.fragmode = fragmode;
+               send_packet_udp_simple(&params);
+       }
+       }
+
+       /* send a packet for each protocol */
+       params = paramsbase;
+       for (i = 0; i < 256; i++) {
+               params.prot = i;
+               send_packet_udp_simple(&params);
+       }
+
+       /* send a packet for each tos */
+       params = paramsbase;
+       for (i = 0; i < 256; i++) {
+               params.tos = i;
+               send_packet_udp_simple(&params);
+       }
+}
+
+static void send_packets_udp(int fd) {
+       int i, j, k;
+       uint16_t id = 0;
+       struct send_packet_udp_simple_params params;
+       const struct send_packet_udp_simple_params paramsbase = {
+               .fd            = fd,
+               .ipsettings    = 0,
+               .tos           = 0,
+               .id            = &id,
+               .ttl           = 10,
+               .prot          = IP_PROT_UDP,
+               .srcip         = addrsrc,
+               .dstip         = addrdst,
+               .fragmode      = fi_as_needed,
+               .udpsettings   = 0,
+               .srcport       = PORT_BASE + 0,
+               .dstport       = PORT_BASE + 1,
+               .payloadsize   = 1234,
+       };
+       uint16_t ports[] = {
+               0,
+               PORT_BASE + 0,
+               PORT_BASE + 1,
+               32767,
+               65535,
+       };
+       enum settings_udp udpsettings[] = {
+               0,
+               su_bad_len_small,
+               su_bad_len_big,
+               su_bad_len_huge,
+               su_bad_cs,
+               su_zero_cs,
+       };
+
+       /* send packets with various corruptions */
+       params = paramsbase;
+       for (i = 0; i < 6; i++) {
+               params.udpsettings = udpsettings[i];
+               send_packet_udp_simple(&params);
+       }
+
+       /* send packets with various addresses and ports */
+       params = paramsbase;
+       for (i = 0; i < addr_count; i++) {
+       for (j = 0; j < addr_count; j++) {
+       for (k = 0; k < 5; k++) {
+               params.srcip = addrs[i];
+               params.dstip = addrs[j];
+               params.dstport = ports[k];
+               send_packet_udp_simple(&params);
+       }
+       }
+       }
+       params = paramsbase;
+       for (i = 0; i < addr_count; i++) {
+       for (j = 0; j < 5; j++) {
+       for (k = 0; k < 5; k++) {
+               params.dstip = addrs[i];
+               params.srcport = ports[j];
+               params.dstport = ports[k];
+               send_packet_udp_simple(&params);
+       }
+       }
+       }
+}
+
+enum settings_tcp {
+       st_bad_doff_small  = (1 <<  0),
+       st_bad_doff_big    = (1 <<  1),
+       st_bad_doff_huge   = (1 <<  2),
+       st_bad_cs          = (1 <<  3),
+       st_zero_cs         = (1 <<  4),
+       st_opt_end         = (1 <<  5),
+       st_opt_nop         = (1 <<  6),
+       st_opt_mss_small   = (1 <<  7),
+       st_opt_mss_big     = (1 <<  8),
+       st_opt_mss_huge    = (1 <<  9),
+       st_opt_badpad      = (1 << 10),
+};
+
+static void send_packet_tcp(
+       int fd,
+       enum settings_ip ipsettings,
+       uint8_t tos,
+       uint16_t id,
+       uint8_t ttl,
+       uint8_t prot,
+       uint32_t srcip,
+       uint32_t dstip,
+       enum fragmode_ip fragmode,
+       enum settings_tcp tcpsettings,
+       uint16_t srcport,
+       uint16_t dstport,
+       uint32_t seq,
+       uint32_t ack,
+       uint8_t fl,
+       uint16_t win,
+       uint16_t uptr,
+       const void *payload,
+       size_t payloadsize) {
+       uint8_t doff, doff_fuzzed;
+       int optlen;
+       struct header_tcp header = {
+               .src  = htons(srcport),
+               .dst  = htons(dstport),
+               .seq  = htonl(seq),
+               .ack  = htonl(ack),
+               .fl   = fl,
+               .win  = htons(win),
+               .cs   = 0,
+               .uptr = htons(uptr),
+       };
+       char packet[65536];
+       size_t packetsize;
+
+       dbgprintf("sending TCP packet srcport=%d dstport=%d fl=%s%s%s%s%s%s "
+               "payloadsize=%zu\n", srcport, dstport,
+               (fl & TCP_FLAG_URG) ? "  URG" : "",
+               (fl & TCP_FLAG_ACK) ? "  ACK" : "",
+               (fl & TCP_FLAG_PSH) ? "  PSH" : "",
+               (fl & TCP_FLAG_RST) ? "  RST" : "",
+               (fl & TCP_FLAG_SYN) ? "  SYN" : "",
+               (fl & TCP_FLAG_FIN) ? "  FIN" : "",
+               payloadsize);
+
+       optlen = 0;
+       if (tcpsettings & st_opt_badpad) memset(header.opt, -1, sizeof(header.opt));
+       if (tcpsettings & st_opt_nop) header.opt[optlen++] = 0x01;
+       if (tcpsettings & st_opt_mss_small) {
+               header.opt[optlen++] = 0x02;
+               header.opt[optlen++] = 0x04;
+               header.opt[optlen++] = 0x00;
+               header.opt[optlen++] = 0x00;
+       }
+       if (tcpsettings & st_opt_mss_big) {
+               header.opt[optlen++] = 0x02;
+               header.opt[optlen++] = 0x04;
+               header.opt[optlen++] = 0x10;
+               header.opt[optlen++] = 0x00;
+       }
+       if (tcpsettings & st_opt_mss_huge) {
+               header.opt[optlen++] = 0x02;
+               header.opt[optlen++] = 0x04;
+               header.opt[optlen++] = 0xff;
+               header.opt[optlen++] = 0xff;
+       }
+       if (tcpsettings & st_opt_end) header.opt[optlen++] = 0x00;
+
+       doff = doff_fuzzed = (20 + optlen + 3) / 4;
+       if (tcpsettings & su_bad_len_small) doff_fuzzed -= 1;
+       if (tcpsettings & su_bad_len_big) doff_fuzzed += 1;
+       if (tcpsettings & su_bad_len_huge) doff_fuzzed = 15;
+       header.doff = doff_fuzzed << 4;
+
+       packetsize = doff * 4 + payloadsize;
+       assert(packetsize <= sizeof(packet));
+
+       memcpy(packet, &header, sizeof(header));
+       memcpy(packet + sizeof(header), payload, payloadsize);
+       if (packetsize % 2) packet[packetsize] = 0;
+
+       header.cs = checksum_udp(srcip, dstip, prot, packet, packetsize);
+       if (tcpsettings & su_zero_cs) header.cs = 0;
+       if (tcpsettings & su_bad_cs) header.cs += 1;
+
+       memcpy(packet, &header, sizeof(header));
+       send_packet_ip(
+               fd,
+               ipsettings,
+               tos,
+               id,
+               ttl,
+               prot,
+               srcip,
+               dstip,
+               fragmode,
+               packet,
+               packetsize);
+}
+
+struct send_packet_tcp_simple_params {
+       int fd;
+       enum settings_ip ipsettings;
+       uint8_t tos;
+       uint16_t *id;
+       uint8_t ttl;
+       uint8_t prot;
+       uint32_t srcip;
+       uint32_t dstip;
+       enum fragmode_ip fragmode;
+       enum settings_tcp tcpsettings;
+       uint16_t srcport;
+       uint16_t dstport;
+       uint32_t seq;
+       uint32_t ack;
+       uint8_t fl;
+       uint16_t win;
+       uint16_t uptr;
+       size_t payloadsize;
+};
+
+static void send_packet_tcp_simple(
+       const struct send_packet_tcp_simple_params *params) {
+       int i;
+       char payload[65536];
+
+       if (!params->srcip || !params->dstip) return; /* crashes QEMU */
+
+       assert(params->payloadsize <= sizeof(payload));
+       for (i = 0; i < params->payloadsize; i++) {
+               payload[i] = *params->id + i;
+       }
+       send_packet_tcp(
+               params->fd,
+               params->ipsettings,
+               params->tos,
+               *params->id,
+               params->ttl,
+               params->prot,
+               params->srcip,
+               params->dstip,
+               params->fragmode,
+               params->tcpsettings,
+               params->srcport,
+               params->dstport,
+               params->seq,
+               params->ack,
+               params->fl,
+               params->win,
+               params->uptr,
+               payload,
+               params->payloadsize);
+       *params->id += 5471;
+}
+
+static void send_packets_tcp(int fd) {
+       int i, j, k;
+       uint16_t id = 0;
+       const struct send_packet_tcp_simple_params paramsbase = {
+               .fd          = fd,
+               .ipsettings  = 0,
+               .tos         = 0,
+               .id          = &id,
+               .ttl         = 10,
+               .prot        = IP_PROT_TCP,
+               .srcip       = addrsrc,
+               .dstip       = addrdst,
+               .fragmode    = fi_as_needed,
+               .tcpsettings = 0,
+               .srcport     = PORT_BASE + 0,
+               .dstport     = PORT_BASE + 1,
+               .seq         = 0x12345678,
+               .ack         = 0x87654321,
+               .fl          = TCP_FLAG_SYN,
+               .win         = 4096,
+               .uptr        = 0,
+               .payloadsize = 1234,
+       };
+       uint16_t payloadsizes[] = {
+               0,
+               1,
+               999,
+               1500,
+               1600,
+               9999,
+       };
+       uint16_t ports[] = {
+               0,
+               PORT_BASE + 0,
+               PORT_BASE + 1,
+               PORT_BASE + 2,
+               PORT_BASE + 3,
+               32767,
+               65535,
+       };
+       enum settings_tcp tcpsettings[] = {
+               0,
+               st_bad_doff_small,
+               st_bad_doff_big,
+               st_bad_doff_huge,
+               st_bad_cs,
+               st_zero_cs,
+               st_opt_end,
+               st_opt_nop,
+               st_opt_mss_small,
+               st_opt_mss_big,
+               st_opt_mss_huge,
+               st_opt_badpad,
+       };
+       struct send_packet_tcp_simple_params params;
+
+       /* send packets with various corruptions */
+       params = paramsbase;
+       for (i = 0; i < 12; i++) {
+               params.tcpsettings = tcpsettings[i];
+               send_packet_tcp_simple(&params);
+       }
+
+       /* send packets with various addresses and ports */
+       params = paramsbase;
+       for (i = 0; i < addr_count; i++) {
+       for (j = 0; j < addr_count; j++) {
+       for (k = 0; k < 7; k++) {
+               params.srcip = addrs[i];
+               params.dstip = addrs[j];
+               params.dstport = ports[k];
+               send_packet_tcp_simple(&params);
+       }
+       }
+       }
+       params = paramsbase;
+       for (i = 0; i < addr_count; i++) {
+       for (j = 0; j < 7; j++) {
+       for (k = 0; k < 7; k++) {
+               params.dstip = addrs[i];
+               params.srcport = ports[j];
+               params.dstport = ports[k];
+               send_packet_tcp_simple(&params);
+       }
+       }
+       }
+
+       /* send packets with different sequence numbers */
+       params = paramsbase;
+       for (i = 0; i < 16; i++) {
+               params.seq = 0x1fffffff;
+               send_packet_tcp_simple(&params);
+       }
+
+       /* send packets with all combinations of flags */
+       params = paramsbase;
+       for (i = 0; i < 256; i++) {
+               params.fl = i;
+               send_packet_tcp_simple(&params);
+       }
+
+       /* send packets with different window sizes */
+       params = paramsbase;
+       for (i = 0; i < 6; i++) {
+               params.win = payloadsizes[i];
+               send_packet_tcp_simple(&params);
+       }
+
+       /* send packets with different payload sizes */
+       params = paramsbase;
+       for (i = 0; i < 6; i++) {
+               params.payloadsize = payloadsizes[i];
+               send_packet_tcp_simple(&params);
+       }
+}
+
+static void recv_packets_nb(int fd) {
+       char buf[4096];
+       int flags;
+       ssize_t r;
+
+       flags = fcntl(fd, F_GETFL);
+       if (flags < 0) {
+               efmt("fcntl(F_GETFL) failed");
+               return;
+       }
+
+       if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+               efmt("fcntl(F_SETFL) failed");
+               return;
+       }
+
+       for (;;) {
+               errno = 0;
+               r = read(fd, buf, sizeof(buf));
+               if (r <= 0) {
+                       if (errno != EAGAIN) efmt("nb read failed");
+                       dbgprintf("no more packets to receive\n");
+                       break;
+               }
+               dbgprintf("received packet of size %zd\n", r);
+       }
+
+       if (fcntl(fd, F_SETFL, flags) == -1) {
+               efmt("fcntl(F_SETFL) failed");
+               return;
+       }
+}
+
+static struct timeval gettimeofday_checked(void) {
+       struct timeval time = {};
+
+       if (gettimeofday(&time, NULL) != 0) {
+               efmt("gettimeofday failed");
+       }
+       return time;
+}
+
+static int timeval_cmp(const struct timeval *x, const struct timeval *y) {
+       if (x->tv_sec < y->tv_sec) return -1;
+       if (x->tv_sec > y->tv_sec) return 1;
+       if (x->tv_usec < y->tv_usec) return -1;
+       if (x->tv_usec > y->tv_usec) return 1;
+       return 0;
+}
+
+static struct timeval timeval_sub(struct timeval x, struct timeval y) {
+       struct timeval z;
+
+       /* no negative result allowed */
+       if (timeval_cmp(&x, &y) < 0) {
+               memset(&z, 0, sizeof(z));
+       } else {
+               /* no negative tv_usec allowed */
+               if (x.tv_usec < y.tv_usec) {
+                       x.tv_sec -= 1;
+                       x.tv_usec += 1000000;
+               }
+
+               /* perform subtraction */
+               z.tv_sec = x.tv_sec - y.tv_sec;
+               z.tv_usec = x.tv_usec - y.tv_usec;
+       }
+       return z;
+}
+
+static size_t recv_packet_select(
+       int fd,
+       void *buf,
+       size_t size,
+       const struct timeval *deadline) {
+       int nfds;
+       ssize_t r;
+       fd_set readfds;
+       struct timeval timeout = timeval_sub(*deadline, gettimeofday_checked());
+
+       FD_ZERO(&readfds);
+       FD_SET(fd, &readfds);
+       errno = 0;
+       nfds = select(fd + 1, &readfds, NULL, NULL, &timeout);
+       if (nfds < 0 || nfds > 1) {
+               efmt("select failed");
+               return 0;
+       }
+
+       if (nfds == 0) {
+               if (FD_ISSET(fd, &readfds)) efmt("select spuriously set fd");
+               dbgprintf("no more packets to receive\n");
+               return 0;
+       }
+
+       if (!FD_ISSET(fd, &readfds)) {
+               efmt("select did not set fd");
+               return 0;
+       }
+
+       r = read(fd, buf, size);
+       if (r <= 0) {
+               efmt("read failed");
+               return 0;
+       }
+       dbgprintf("received packet of size %zd\n", r);
+
+       return r;
+}
+
+static void recv_packets_select(int fd) {
+       char buf[4096];
+       struct timeval deadline = gettimeofday_checked();
+
+       deadline.tv_sec++;
+       while (recv_packet_select(fd, buf, sizeof(buf), &deadline)) { }
+}
+
+static int open_raw_socket(int broadcast) {
+       int fd;
+       struct nwio_ethopt opt = { };
+       struct nwio_ethstat stat = { };
+
+       fd = open("/dev/eth", O_RDWR);
+       if (fd < 0) efmt("cannot open /dev/eth");
+
+       /* test NWIOGETHOPT */
+       if (ioctl(fd, NWIOGETHOPT, &opt) != 0) {
+               efmt("ioctl(NWIOGETHOPT) failed");
+       }
+
+       /* test NWIOGETHSTAT */
+       if (ioctl(fd, NWIOGETHSTAT, &stat) != 0) {
+               efmt("ioctl(NWIOGETHSTAT) failed");
+       }
+
+       /* test invalid NWIOSETHOPT input */
+       opt.nweo_flags = NWEO_COPY << 16;
+       if (ioctl(fd, NWIOSETHOPT, &opt) != -1 && errno != EBADMODE) {
+               efmt("ioctl(NWIOSETHOPT) should have returned EBADMODE");
+       }
+
+       opt.nweo_flags = NWEO_EN_LOC | NWEO_DI_LOC;
+       if (ioctl(fd, NWIOSETHOPT, &opt) != -1 && errno != EBADMODE) {
+               efmt("ioctl(NWIOSETHOPT) should have returned EBADMODE");
+       }
+
+       /* test NWIOSETHOPT with defaults */
+       opt.nweo_flags = 0;
+       if (ioctl(fd, NWIOSETHOPT, &opt) != 0) {
+               efmt("ioctl(NWIOSETHOPT) failed");
+       }
+
+       /* test NWIOGETHSTAT right after reconfiguring */
+       opt.nweo_flags = NWEO_EN_BROAD | NWEO_EN_MULTI | NWEO_EN_PROMISC;
+       if (ioctl(fd, NWIOSETHOPT, &opt) != 0) {
+               efmt("ioctl(NWIOSETHOPT) failed");
+       }
+
+       opt.nweo_flags = NWEO_DI_BROAD | NWEO_DI_MULTI | NWEO_DI_PROMISC;
+       if (ioctl(fd, NWIOSETHOPT, &opt) != 0) {
+               efmt("ioctl(NWIOSETHOPT) failed");
+       }
+
+       if (ioctl(fd, NWIOGETHSTAT, &stat) != 0) {
+               efmt("ioctl(NWIOGETHSTAT) failed");
+       }
+
+       /* configure /dev/eth the way we want it for the rest of the test */
+       opt.nweo_flags = NWEO_COPY | NWEO_EN_LOC | NWEO_EN_BROAD |
+               NWEO_EN_MULTI | NWEO_EN_PROMISC |
+               (broadcast ? NWEO_REMANY : NWEO_REMSPEC) |
+               NWEO_TYPESPEC | NWEO_RWDATONLY;
+       opt.nweo_type = htons(ETH_IP_PROTO);
+       memcpy(&opt.nweo_rem, &stat.nwes_addr, sizeof(opt.nweo_rem));
+       if (ioctl(fd, NWIOSETHOPT, &opt) != 0) {
+               efmt("ioctl(NWIOSETHOPT) failed");
+       }
+
+       return fd;
+}
+
+static void do_packets(void) {
+       int fd;
+
+       /* test IP and UDP with broadcast */
+       fd = open_raw_socket(1 /*broadcast*/);
+       if (fd < 0) return;
+
+       send_packets_ip(fd);
+       send_packets_udp(fd);
+       recv_packets_nb(fd);
+
+       CLOSE(fd);
+
+       /* test TCP locally to avoid crashing QEMU */
+       fd = open_raw_socket(0 /*broadcast*/);
+       if (fd < 0) return;
+
+       send_packets_tcp(fd);
+       recv_packets_select(fd);
+
+       CLOSE(fd);
+}
+
+static void add_local_ip(uint32_t ip) {
+       static int first = 1;
+       int i;
+
+       for (i = 0; i < addr_count; i++) {
+               if (addrs[i] == ip) return;
+       }
+       dbgprintf("found local IP: %d.%d.%d.%d\n",
+               (uint8_t) (ip >> 24), (uint8_t) (ip >> 16),
+               (uint8_t) (ip >> 8), (uint8_t) (ip >> 0));
+       if (addr_count < ADDR_COUNT_MAX) {
+               addrs[addr_count++] = ip;
+       }
+       if (first) {
+               addrdst = ip;
+               first = 0;
+       }
+}
+
+static void get_local_ip(void) {
+       char device[16];
+       int flags;
+       int ifno;
+       nwio_ipconf_t ipconf;
+       int ip_fd;
+
+       /* inspired by ifconfig */
+       for (ifno = 0; ifno < 32; ifno++) {
+               snprintf(device, sizeof(device), "/dev/ip%d", ifno);
+               ip_fd = open(device, O_RDWR);
+               if (ip_fd < 0) {
+                       if (errno != ENOENT && errno != ENXIO) {
+                               efmt("cannot open %s", device);
+                       }
+                       continue;
+               }
+
+               flags = fcntl(ip_fd, F_GETFL);
+               if (flags == -1) {
+                       efmt("cannot get flags for %s", device);
+                       goto next;
+               }
+               if (fcntl(ip_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+                       efmt("cannot set flags for %s", device);
+                       goto next;
+               }
+
+               if (ioctl(ip_fd, NWIOGIPCONF, &ipconf) == -1) {
+                       if (errno != EAGAIN) {
+                               efmt("cannot get IP address for %s", device);
+                       }
+                       goto next;
+               }
+
+               if (fcntl(ip_fd, F_SETFL, flags) == -1) {
+                       efmt("cannot restore flags for %s", device);
+               }
+
+               add_local_ip(ntohl(ipconf.nwic_ipaddr));
+
+next:
+               CLOSE(ip_fd);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       pid_t pids[PORT_COUNT];
+
+       start(83);
+
+       /* start servers so we have someone to talk to */
+       pids[0] = server_start(SOCK_STREAM, PORT_BASE + 0, sa_close);
+       pids[1] = server_start(SOCK_STREAM, PORT_BASE + 1, sa_read);
+       pids[2] = server_start(SOCK_STREAM, PORT_BASE + 2, sa_selectrw);
+       pids[3] = server_start(SOCK_STREAM, PORT_BASE + 3, sa_write);
+       pids[4] = server_start(SOCK_DGRAM,  PORT_BASE + 0, sa_read);
+       pids[5] = server_start(SOCK_DGRAM,  PORT_BASE + 1, sa_selectr);
+
+       /* send some bogus packets */
+       get_local_ip();
+       if (get_setting_use_network()) do_packets();
+
+       /* stop the servers */
+       for (i = 0; i < PORT_COUNT; i++) server_stop(pids[i]);
+       for (i = 0; i < PORT_COUNT; i++) server_wait(pids[i]);
+
+       quit();
+       return 0;
+}