Add utility program to help run fuzzers against isoping.

host-isoping_fuzz reads packets from standard input, then sends the data
to the isoping server over a socket.  This lets fuzzers like afl easily
pass in modified data, while covering as much of the real code as
possible.  It also overrides the non-deterministic parts of the server
such as periodic secret rotation and cookie validation so as to not
confuse the fuzzers.

Change-Id: I991add34d65e159036e7279932dd15c6c68a200d
diff --git a/cmds/Makefile b/cmds/Makefile
index 43362dc..b8ff87e 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -56,7 +56,7 @@
 	wait-until-created \
 	watch-dir
 
-HOST_TARGETS=$(addprefix host-,$(TARGETS))
+HOST_TARGETS=$(addprefix host-,$(TARGETS)) host-isoping_fuzz
 LIB_TARGETS=\
 	stdoutline.so
 HOST_TEST_TARGETS=\
@@ -188,8 +188,9 @@
 host-isoping: host-isoping.o host-isoping_main.o
 host-isoping_test.o: CXXFLAGS += -D WVTEST_CONFIGURED -I ../wvtest/cpp
 host-isoping_test.o: isoping.cc
-host-isoping_test: LIBS+=$(HOST_LIBS) -lm -lstdc++ -lcrypto
+host-isoping_test host-isoping_fuzz: LIBS+=$(HOST_LIBS) -lm -lstdc++ -lcrypto
 host-isoping_test: host-isoping_test.o host-isoping.o host-wvtestmain.o host-wvtest.o
+host-isoping_fuzz: host-isoping.o host-isoping_fuzz.o
 host-isostream isostream: LIBS+=$(RT)
 host-diskbench diskbench: LIBS+=-lpthread $(RT)
 host-dnsck: LIBS+=$(HOST_LIBS) -lcares $(RT)
diff --git a/cmds/isoping.cc b/cmds/isoping.cc
index 18d73f4..48999c3 100644
--- a/cmds/isoping.cc
+++ b/cmds/isoping.cc
@@ -159,6 +159,19 @@
   return p.first;
 }
 
+Sessions::Sessions()
+    : md(EVP_sha256()),
+      rng(std::random_device()()),
+      cookie_epoch(0),
+      last_secret_update_time(0) {
+  NewRandomCookieSecret();
+  EVP_MD_CTX_init(&digest_context);
+}
+
+Sessions::~Sessions() {
+  EVP_MD_CTX_cleanup(&digest_context);
+}
+
 bool Sessions::CalculateCookie(Packet *p, struct sockaddr_storage *remoteaddr,
                                size_t remoteaddr_len) {
   return CalculateCookieWithSecret(p, remoteaddr, remoteaddr_len,
@@ -206,13 +219,13 @@
   Packet golden;
   golden.packet_type = PACKET_TYPE_HANDSHAKE;
   golden.usec_per_pkt = p->usec_per_pkt;
-  if (p->data.handshake.cookie_epoch == cookie_epoch) {
-    CalculateCookieWithSecret(&golden, addr, addr_len, cookie_secret,
-                              sizeof(cookie_secret));
-  } else {
-    CalculateCookieWithSecret(&golden, addr, addr_len, prev_cookie_secret,
-                              sizeof(prev_cookie_secret));
+  unsigned char *secret = cookie_secret;
+  size_t secret_len = sizeof(cookie_secret);
+  if (p->data.handshake.cookie_epoch == prev_cookie_epoch) {
+    secret = prev_cookie_secret;
+    secret_len = sizeof(prev_cookie_secret);
   }
+  CalculateCookieWithSecret(&golden, addr, addr_len, secret, secret_len);
   DLOG("Handshake: cookie epoch=%d, cookie=0x",
        p->data.handshake.cookie_epoch);
   debug_print_hex(p->data.handshake.cookie, sizeof(p->data.handshake.cookie));
@@ -229,12 +242,15 @@
   return true;
 }
 
-void Sessions::MaybeRotateCookieSecrets() {
-  // Round off the unix timestamp to 64 seconds as an epoch, so we don't have to
-  // track which ones we've already used.
-  uint32_t new_epoch = time(NULL) >> 6;
-  if (new_epoch != cookie_epoch) {
-    RotateCookieSecrets(new_epoch);
+void Sessions::MaybeRotateCookieSecrets(uint32_t now, int is_server) {
+  if (is_server && (now - last_secret_update_time) > 1000000) {
+    // Round off the unix timestamp to 64 seconds as an epoch, so we don't have
+    // to track which ones we've already used.
+    uint32_t new_epoch = time(NULL) >> 6;
+    if (new_epoch != cookie_epoch) {
+      RotateCookieSecrets(new_epoch);
+    }
+    last_secret_update_time = now;
   }
 }
 
@@ -897,10 +913,11 @@
   s->last_rxtime = rxtime;
 }
 
-int isoping_main(int argc, char **argv) {
+int isoping_main(int argc, char **argv, Sessions *sessions, int extrasock) {
+  assert(sessions != NULL);
+
   struct sockaddr_in6 listenaddr;
   struct addrinfo *ai = NULL;
-  int sock = -1;
 
   setvbuf(stdout, NULL, _IOLBF, 0);
 
@@ -943,7 +960,7 @@
     }
   }
 
-  sock = socket(PF_INET6, SOCK_DGRAM, 0);
+  int sock = socket(PF_INET6, SOCK_DGRAM, 0);
   if (sock < 0) {
     perror("socket");
     return 1;
@@ -951,7 +968,6 @@
 
   int is_server;
   uint32_t now = ustime();       // current time
-  Sessions sessions;
 
   if (argc - optind == 0) {
     is_server = 1;
@@ -988,8 +1004,9 @@
       perror("connect");
       return 1;
     }
-    sessions.NewSession(now, 1e6 / packets_per_sec,
-                        (struct sockaddr_storage *)ai->ai_addr, ai->ai_addrlen);
+    sessions->NewSession(now, 1e6 / packets_per_sec,
+                         (struct sockaddr_storage *)ai->ai_addr,
+                         ai->ai_addrlen);
   } else {
     usage_and_die(argv[0]);
   }
@@ -1018,24 +1035,29 @@
   act.sa_flags = SA_RESETHAND;
   sigaction(SIGINT, &act, NULL);
 
-  uint32_t last_secret_update_time = 0;
-
   while (!want_to_die) {
     fd_set rfds;
     FD_ZERO(&rfds);
     FD_SET(sock, &rfds);
+    if (extrasock > 0) {
+      FD_SET(extrasock, &rfds);
+    }
     struct timeval tv;
     tv.tv_sec = 0;
 
     now = ustime();
-    if (sessions.next_sends.size() == 0 ||
-        DIFF(sessions.next_send_time(), now) < 0) {
+    if (sessions->next_sends.size() == 0 ||
+        DIFF(sessions->next_send_time(), now) < 0 ||
+        extrasock > 0) {
       tv.tv_usec = 0;
     } else {
-      tv.tv_usec = DIFF(sessions.next_send_time(), now);
+      tv.tv_usec = DIFF(sessions->next_send_time(), now);
     }
-    int nfds = select(sock + 1, &rfds, NULL, NULL,
-                      sessions.next_sends.size() > 0 ? &tv : NULL);
+    struct timeval *tvp = NULL;
+    if (sessions->next_sends.size() > 0 || extrasock > 0) {
+      tvp = &tv;
+    }
+    int nfds = select(std::max(sock, extrasock) + 1, &rfds, NULL, NULL, tvp);
     now = ustime();
     if (nfds < 0 && errno != EINTR) {
       perror("select");
@@ -1043,29 +1065,32 @@
     }
 
     // Periodically check if the cookie secrets need updating.
-    if (is_server && (now - last_secret_update_time) > 1000000) {
-      sessions.MaybeRotateCookieSecrets();
-      last_secret_update_time = now;
-    }
+    sessions->MaybeRotateCookieSecrets(now, is_server);
 
-    int err = send_waiting_packets(&sessions, sock, now, is_server);
+    int err = send_waiting_packets(sessions, sock, now, is_server);
     if (err != 0) {
       return err;
     }
 
     if (nfds > 0) {
-      err = read_incoming_packet(&sessions, sock, now, is_server);
+      int s = FD_ISSET(sock, &rfds) ? sock : extrasock;
+      err = read_incoming_packet(sessions, s, now, is_server);
       if (!is_server && err == ECONNREFUSED) return 2;
       if (err != 0) {
         continue;
       }
     }
+
+    if (extrasock > 0 && nfds == 0) {
+      DLOG("read all data from extrasock, exiting\n");
+      exit(0);
+    }
   }
 
   // TODO(pmccurdy): Separate out per-client and global stats, print stats for
   // the server when each client disconnects.
   if (!is_server) {
-    Session &s = sessions.session_map.begin()->second;
+    Session &s = sessions->session_map.begin()->second;
     printf("\n---\n");
     printf("tx: min/avg/max/mdev = %.2f/%.2f/%.2f/%.2f ms\n",
            s.lat_tx_min / 1000.0,
diff --git a/cmds/isoping.h b/cmds/isoping.h
index 33a4438..8ff819d 100644
--- a/cmds/isoping.h
+++ b/cmds/isoping.h
@@ -128,22 +128,13 @@
                   const SessionMap::iterator &rhs);
 };
 
-struct Sessions {
+class Sessions {
  public:
-  Sessions()
-      : md(EVP_sha256()),
-        rng(std::random_device()()),
-        cookie_epoch(0) {
-    NewRandomCookieSecret();
-    EVP_MD_CTX_init(&digest_context);
-  }
-
-  ~Sessions() {
-    EVP_MD_CTX_cleanup(&digest_context);
-  }
+  Sessions();
+  ~Sessions();
 
   // Rotates the cookie secrets if they haven't been changed in a while.
-  void MaybeRotateCookieSecrets();
+  virtual void MaybeRotateCookieSecrets(uint32_t now, int is_server);
 
   // Rotate the cookie secrets using the given epoch directly.  Only for use in
   // unit tests.
@@ -156,8 +147,8 @@
                        size_t remoteaddr_len);
 
   // Returns true if the packet contains a handshake packet with a valid cookie.
-  bool ValidateCookie(Packet *p, struct sockaddr_storage *addr,
-                      socklen_t addr_len);
+  virtual bool ValidateCookie(Packet *p, struct sockaddr_storage *addr,
+                              socklen_t addr_len);
 
   SessionMap::iterator NewSession(uint32_t first_send,
                                   uint32_t usec_per_pkt,
@@ -178,7 +169,7 @@
   std::priority_queue<SessionMap::iterator, std::vector<SessionMap::iterator>,
       CompareNextSend> next_sends;
 
- private:
+ protected:
   void NewRandomCookieSecret();
   bool CalculateCookieWithSecret(Packet *p, struct sockaddr_storage *remoteaddr,
                                  size_t remoteaddr_len, unsigned char *secret,
@@ -189,6 +180,7 @@
   const EVP_MD *md;
   std::mt19937_64 rng;
   uint32_t cookie_epoch;
+  uint32_t last_secret_update_time;
   unsigned char cookie_secret[COOKIE_SECRET_SIZE];
   uint32_t prev_cookie_epoch;
   unsigned char prev_cookie_secret[COOKIE_SECRET_SIZE];
@@ -231,7 +223,8 @@
 void set_packets_per_sec(double new_pps);
 
 // Parses arguments and runs the main loop.  Distinct from main() for unit test
-// purposes.
-int isoping_main(int argc, char **argv);
+// and fuzz test purposes.  extrasock is an additional socket to read from, used
+// for tests.  Set to -1 if unused.
+int isoping_main(int argc, char **argv, Sessions *sessions, int extrasock);
 
 #endif
diff --git a/cmds/isoping_fuzz.cc b/cmds/isoping_fuzz.cc
new file mode 100644
index 0000000..17f30da
--- /dev/null
+++ b/cmds/isoping_fuzz.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* A version of isoping_main.cc to be used when fuzzing.  Reads the fuzz test
+ * case from standard input, writes it to the socket, then exits once processing
+ * is finished.*/
+
+#include "isoping.h"
+
+#include <netdb.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <thread>
+#include <time.h>
+#include <unistd.h>
+
+// Removes sources of nondeterminism from the Sessions code, so fuzzers can
+// detect which code paths are affected by inputs.
+class DeterministicSessions : public Sessions {
+ public:
+  DeterministicSessions() : Sessions() {
+    // Make the cookie secret data deterministic.
+    prev_cookie_epoch = 1;
+    cookie_epoch = 2;
+    memset(&prev_cookie_secret, 0, sizeof(prev_cookie_secret));
+    memset(&cookie_secret, 0, sizeof(cookie_secret));
+    prev_cookie_secret[0] = 1;
+    cookie_secret[0] = 2;
+  }
+  ~DeterministicSessions() {}
+
+  // Don't rotate the cookie secrets, it confuses the fuzzer.
+  virtual void MaybeRotateCookieSecrets(uint32_t now, int is_server) {
+  }
+
+  // Force the incoming cookie to be valid, then call the real validation
+  // routine.  This ensures we test the real routine, without the fuzzer having
+  // to generate valid cookies on its own.
+  virtual bool ValidateCookie(Packet *p, struct sockaddr_storage *addr,
+                              socklen_t addr_len) {
+    Packet golden;
+    golden.packet_type = PACKET_TYPE_HANDSHAKE;
+    golden.usec_per_pkt = p->usec_per_pkt;
+    p->data.handshake.cookie_epoch = cookie_epoch;
+    CalculateCookieWithSecret(&golden, addr, addr_len, cookie_secret,
+                              sizeof(cookie_secret));
+    memcpy(p->data.handshake.cookie, golden.data.handshake.cookie, COOKIE_SIZE);
+    return Sessions::ValidateCookie(p, addr, addr_len);
+  }
+};
+
+int main(int argc, char **argv) {
+  DeterministicSessions dsessions;
+  // Read data from stdin
+  fprintf(stderr, "Running fuzz code.\n");
+  unsigned char buf[10 * sizeof(Packet)];
+  memset(buf, 0, sizeof(buf));
+  int insize = read(0, buf, sizeof(buf));
+  fprintf(stderr, "Read %d bytes from stdin.\n", insize);
+
+  struct addrinfo hints;
+  struct addrinfo *res;
+
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_INET6;
+  hints.ai_socktype = SOCK_DGRAM;
+  hints.ai_flags = AI_PASSIVE | AI_V4MAPPED;
+  int err = getaddrinfo(NULL, "0", &hints, &res);
+  if (err != 0) {
+    perror("getaddrinfo");
+    return 1;
+  }
+
+  int sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+  if (sock < 0) {
+    perror("socket");
+    return 2;
+  }
+
+  if (bind(sock, res->ai_addr, res->ai_addrlen)) {
+    perror("bind");
+    return 3;
+  }
+
+  // Figure out the local port we got.
+  struct sockaddr_storage listenaddr;
+  socklen_t listenaddr_len = sizeof(listenaddr);
+  memset(&listenaddr, 0, listenaddr_len);
+  if (getsockname(sock, (struct sockaddr *)&listenaddr, &listenaddr_len)) {
+    perror("getsockname");
+    return 4;
+  }
+
+  // Send each incoming packet from a different client port.
+  while (insize > 0) {
+    int csock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+    if (csock < 0) {
+      perror("client socket");
+      return 5;
+    }
+    if (connect(csock, (struct sockaddr *)&listenaddr, listenaddr_len)) {
+      perror("connect");
+      return 6;
+    }
+
+    int packet_len = std::min((unsigned long)insize, sizeof(Packet));
+    sendto(csock, buf, packet_len, 0, (struct sockaddr *)&listenaddr,
+           listenaddr_len);
+    insize -= packet_len;
+    close(csock);
+  }
+
+  isoping_main(argc, argv, &dsessions, sock);
+  close(sock);
+  freeaddrinfo(res);
+}
diff --git a/cmds/isoping_fuzz/testcases/connection_established b/cmds/isoping_fuzz/testcases/connection_established
new file mode 100644
index 0000000..9842b91
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/connection_established
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/connection_packet b/cmds/isoping_fuzz/testcases/connection_packet
new file mode 100644
index 0000000..0bbbc38
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/connection_packet
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/handshake_packet b/cmds/isoping_fuzz/testcases/handshake_packet
new file mode 100644
index 0000000..52b3d76
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/handshake_packet
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/two_connections b/cmds/isoping_fuzz/testcases/two_connections
new file mode 100644
index 0000000..572d917
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/two_connections
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/unittest_min b/cmds/isoping_fuzz/testcases/unittest_min
new file mode 100644
index 0000000..b9ddc8d
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/unittest_min
Binary files differ
diff --git a/cmds/isoping_fuzz/testcases/zero_pkt b/cmds/isoping_fuzz/testcases/zero_pkt
new file mode 100644
index 0000000..19ee605
--- /dev/null
+++ b/cmds/isoping_fuzz/testcases/zero_pkt
Binary files differ
diff --git a/cmds/isoping_main.cc b/cmds/isoping_main.cc
index 521b5e1..78e323a 100644
--- a/cmds/isoping_main.cc
+++ b/cmds/isoping_main.cc
@@ -1,5 +1,22 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 #include "isoping.h"
 
 int main(int argc, char **argv) {
-  return isoping_main(argc, argv);
+  Sessions sessions;
+  return isoping_main(argc, argv, &sessions, -1);
 }
diff --git a/cmds/isoping_test.cc b/cmds/isoping_test.cc
index 9ab5cd9..acf96fd 100644
--- a/cmds/isoping_test.cc
+++ b/cmds/isoping_test.cc
@@ -501,13 +501,13 @@
   Sessions s;
   uint32_t usec_per_pkt = 100 * 1000;
 
-  s.MaybeRotateCookieSecrets();
-  // TODO(pmccurdy): Remove +1?
-  c.NewSession(cbase + 1, usec_per_pkt, &listenaddr, listenaddr_len);
-
   int is_server = 1;
   int is_client = 0;
 
+  s.MaybeRotateCookieSecrets(sbase, is_server);
+  // TODO(pmccurdy): Remove +1?
+  c.NewSession(cbase + 1, usec_per_pkt, &listenaddr, listenaddr_len);
+
   // Send the initial handshake packet.
   Session &cSession = c.session_map.begin()->second;
   uint32_t t = cSession.next_send - cbase;