am 0c41b16c: (-s ours) am 464fb26d: Fix build.  Remove superfluous check triggering compiler error.

* commit '0c41b16cf8db8c4df06ae8829cb1f0223203f212':
  Fix build.  Remove superfluous check triggering compiler error.
diff --git a/Android.mk b/Android.mk
index f94acdf..a640e06 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,7 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:=clatd.c dump.c checksum.c translate.c icmp.c ipv4.c ipv6.c config.c dns64.c logging.c getaddr.c getroute.c netlink_callbacks.c netlink_msg.c setif.c setroute.c mtu.c
+LOCAL_SRC_FILES:=clatd.c dump.c checksum.c translate.c icmp.c ipv4.c ipv6.c config.c dns64.c logging.c getaddr.c netlink_callbacks.c netlink_msg.c setif.c mtu.c
 
 LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
 LOCAL_C_INCLUDES := external/libnl/include bionic/libc/dns/include
diff --git a/clatd.c b/clatd.c
index d8a9e72..6871129 100644
--- a/clatd.c
+++ b/clatd.c
@@ -32,9 +32,12 @@
 #include <sys/capability.h>
 #include <sys/uio.h>
 #include <linux/prctl.h>
+#include <linux/filter.h>
 #include <linux/if.h>
 #include <linux/if_tun.h>
 #include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <net/if.h>
 
 #include <private/android_filesystem_config.h>
 
@@ -44,31 +47,17 @@
 #include "logging.h"
 #include "resolv_netid.h"
 #include "setif.h"
-#include "setroute.h"
 #include "mtu.h"
 #include "getaddr.h"
 #include "dump.h"
 
-#define DEVICENAME6 "clat"
 #define DEVICENAME4 "clat4"
 
 /* 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header */
 #define MTU_DELTA 28
 
-int forwarding_fd = -1;
 volatile sig_atomic_t running = 1;
 
-/* function: set_forwarding
- * enables/disables ipv6 forwarding
- */
-void set_forwarding(int fd, const char *setting) {
-  /* we have to forward packets from the WAN to the tun interface */
-  if(write(fd, setting, strlen(setting)) < 0) {
-    logmsg(ANDROID_LOG_FATAL,"set_forwarding(%s) failed: %s", setting, strerror(errno));
-    exit(1);
-  }
-}
-
 /* function: stop_loop
  * signal handler: stop the event loop
  */
@@ -114,34 +103,51 @@
   return 0;
 }
 
-/* function: deconfigure_tun_ipv6
- * removes the ipv6 route
- * tunnel - tun device data
+/* function: configure_packet_socket
+ * Binds the packet socket and attaches the receive filter to it.
+ * sock - the socket to configure
  */
-void deconfigure_tun_ipv6(const struct tun_data *tunnel) {
-  int status;
-
-  status = if_route(tunnel->device6, AF_INET6, &Global_Clatd_Config.ipv6_local_subnet,
-      128, NULL, 1, 0, ROUTE_DELETE);
-  if(status < 0) {
-    logmsg(ANDROID_LOG_WARN,"deconfigure_tun_ipv6/if_route(6) failed: %s",strerror(-status));
+int configure_packet_socket(int sock) {
+  struct sockaddr_ll sll = {
+    .sll_family   = AF_PACKET,
+    .sll_protocol = htons(ETH_P_IPV6),
+    .sll_ifindex  = if_nametoindex((char *) &Global_Clatd_Config.default_pdp_interface),
+    .sll_pkttype  = PACKET_OTHERHOST,  // The 464xlat IPv6 address is not assigned to the kernel.
+  };
+  if (bind(sock, (struct sockaddr *) &sll, sizeof(sll))) {
+    logmsg(ANDROID_LOG_FATAL, "binding packet socket: %s", strerror(errno));
+    return 0;
   }
-}
 
-/* function: configure_tun_ipv6
- * configures the ipv6 route
- * note: routes a /128 out of the (assumed routed to us) /64 to the CLAT interface
- * tunnel - tun device data
- */
-void configure_tun_ipv6(const struct tun_data *tunnel) {
-  int status;
+  uint32_t *ipv6 = Global_Clatd_Config.ipv6_local_subnet.s6_addr32;
+  struct sock_filter filter_code[] = {
+    // Load the first four bytes of the IPv6 destination address (starts 24 bytes in).
+    // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads
+    // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it
+    // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other
+    // three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet).
+    BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  24),
+    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[0]), 0, 7),
+    BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  28),
+    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[1]), 0, 5),
+    BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  32),
+    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[2]), 0, 3),
+    BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  36),
+    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[3]), 0, 1),
+    BPF_STMT(BPF_RET | BPF_K,              PACKETLEN),
+    BPF_STMT(BPF_RET | BPF_K, 0)
+  };
+  struct sock_fprog filter = {
+    sizeof(filter_code) / sizeof(filter_code[0]),
+    filter_code
+  };
 
-  status = if_route(tunnel->device6, AF_INET6, &Global_Clatd_Config.ipv6_local_subnet,
-      128, NULL, 1, 0, ROUTE_CREATE);
-  if(status < 0) {
-    logmsg(ANDROID_LOG_FATAL,"configure_tun_ipv6/if_route(6) failed: %s",strerror(-status));
-    exit(1);
+  if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {
+    logmsg(ANDROID_LOG_FATAL, "attach packet filter failed: %s", strerror(errno));
+    return 0;
   }
+
+  return 1;
 }
 
 /* function: interface_poll
@@ -153,7 +159,8 @@
 
   interface_ip = getinterface_ip(Global_Clatd_Config.default_pdp_interface, AF_INET6);
   if(!interface_ip) {
-    logmsg(ANDROID_LOG_WARN,"unable to find an ipv6 ip on interface %s",Global_Clatd_Config.default_pdp_interface);
+    logmsg(ANDROID_LOG_WARN,"unable to find an ipv6 ip on interface %s",
+           Global_Clatd_Config.default_pdp_interface);
     return;
   }
 
@@ -165,12 +172,15 @@
     inet_ntop(AF_INET6, &interface_ip->ip6, to_addr, sizeof(to_addr));
     logmsg(ANDROID_LOG_WARN, "clat subnet changed from %s to %s", from_addr, to_addr);
 
-    // remove old route
-    deconfigure_tun_ipv6(tunnel);
-
-    // add new route, start translating packets to the new prefix
+    // Start translating packets to the new prefix.
     memcpy(&Global_Clatd_Config.ipv6_local_subnet, &interface_ip->ip6, sizeof(struct in6_addr));
-    configure_tun_ipv6(tunnel);
+
+    // Update our packet socket filter to reflect the new 464xlat IP address.
+    if (!configure_packet_socket(tunnel->read_fd6)) {
+        // Things aren't going to work. Bail out and hope we have better luck next time.
+        // We don't log an error here because configure_packet_socket has already done so.
+        exit(1);
+    }
   }
 
   free(interface_ip);
@@ -192,24 +202,10 @@
     exit(1);
   }
 
-  status = add_address(tunnel->device6, AF_INET6, &Global_Clatd_Config.ipv6_local_address,
-      64, NULL);
-  if(status < 0) {
-    logmsg(ANDROID_LOG_FATAL,"configure_tun_ip/if_address(6) failed: %s",strerror(-status));
-    exit(1);
-  }
-
-  if((status = if_up(tunnel->device6, Global_Clatd_Config.mtu)) < 0) {
-    logmsg(ANDROID_LOG_FATAL,"configure_tun_ip/if_up(6) failed: %s",strerror(-status));
-    exit(1);
-  }
-
   if((status = if_up(tunnel->device4, Global_Clatd_Config.ipv4mtu)) < 0) {
     logmsg(ANDROID_LOG_FATAL,"configure_tun_ip/if_up(4) failed: %s",strerror(-status));
     exit(1);
   }
-
-  configure_tun_ipv6(tunnel);
 }
 
 /* function: drop_root
@@ -248,6 +244,37 @@
   }
 }
 
+/* function: open_sockets
+ * opens a packet socket to receive IPv6 packets and a raw socket to send them
+ * tunnel - tun device data
+ * mark - the socket mark to use for the sending raw socket
+ */
+void open_sockets(struct tun_data *tunnel, uint32_t mark) {
+  int rawsock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
+  if (rawsock < 0) {
+    logmsg(ANDROID_LOG_FATAL, "raw socket failed: %s", strerror(errno));
+    exit(1);
+  }
+
+  int off = 0;
+  if (setsockopt(rawsock, SOL_IPV6, IPV6_CHECKSUM, &off, sizeof(off)) < 0) {
+    logmsg(ANDROID_LOG_WARN, "could not disable checksum on raw socket: %s", strerror(errno));
+  }
+  if (mark != MARK_UNSET && setsockopt(rawsock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+    logmsg(ANDROID_LOG_ERROR, "could not set mark on raw socket: %s", strerror(errno));
+  }
+
+  tunnel->write_fd6 = rawsock;
+
+  int packetsock = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6));
+  if (packetsock < 0) {
+    logmsg(ANDROID_LOG_FATAL, "packet socket failed: %s", strerror(errno));
+    exit(1);
+  }
+
+  tunnel->read_fd6 = packetsock;
+}
+
 /* function: configure_interface
  * reads the configuration and applies it to the interface
  * uplink_interface - network interface to use to reach the ipv6 internet
@@ -282,12 +309,6 @@
     logmsg(ANDROID_LOG_WARN,"ipv4mtu now set to = %d",Global_Clatd_Config.ipv4mtu);
   }
 
-  error = tun_alloc(tunnel->device6, tunnel->fd6);
-  if(error < 0) {
-    logmsg(ANDROID_LOG_FATAL,"tun_alloc failed: %s",strerror(errno));
-    exit(1);
-  }
-
   error = tun_alloc(tunnel->device4, tunnel->fd4);
   if(error < 0) {
     logmsg(ANDROID_LOG_FATAL,"tun_alloc/4 failed: %s",strerror(errno));
@@ -304,12 +325,10 @@
  */
 void read_packet(int active_fd, const struct tun_data *tunnel) {
   ssize_t readlen;
-  uint8_t packet[PACKETLEN];
+  uint8_t buf[PACKETLEN], *packet;
+  int fd;
 
-  // in case something ignores the packet length
-  memset(packet, 0, PACKETLEN);
-
-  readlen = read(active_fd,packet,PACKETLEN);
+  readlen = read(active_fd, buf, PACKETLEN);
 
   if(readlen < 0) {
     logmsg(ANDROID_LOG_WARN,"read_packet/read error: %s", strerror(errno));
@@ -317,16 +336,37 @@
   } else if(readlen == 0) {
     logmsg(ANDROID_LOG_WARN,"read_packet/tun interface removed");
     running = 0;
-  } else {
+    return;
+  }
+
+  if (active_fd == tunnel->fd4) {
     ssize_t header_size = sizeof(struct tun_pi);
 
-    if(readlen < header_size) {
+    if (readlen < header_size) {
       logmsg(ANDROID_LOG_WARN,"read_packet/short read: got %ld bytes", readlen);
       return;
     }
 
-    translate_packet(tunnel, (struct tun_pi *) packet, packet + header_size, readlen - header_size);
+    struct tun_pi *tun_header = (struct tun_pi *) buf;
+    uint16_t proto = ntohs(tun_header->proto);
+    if (proto != ETH_P_IP) {
+      logmsg(ANDROID_LOG_WARN, "%s: unknown packet type = 0x%x", __func__, proto);
+      return;
+    }
+
+    if(tun_header->flags != 0) {
+      logmsg(ANDROID_LOG_WARN, "%s: unexpected flags = %d", __func__, tun_header->flags);
+    }
+
+    fd = tunnel->write_fd6;
+    packet = buf + header_size;
+    readlen -= header_size;
+  } else {
+    fd = tunnel->fd4;
+    packet = buf;
   }
+
+  translate_packet(fd, (fd == tunnel->write_fd6), packet, readlen);
 }
 
 /* function: event_loop
@@ -340,7 +380,7 @@
   // start the poll timer
   last_interface_poll = time(NULL);
 
-  wait_fd[0].fd = tunnel->fd6;
+  wait_fd[0].fd = tunnel->read_fd6;
   wait_fd[0].events = POLLIN;
   wait_fd[0].revents = 0;
   wait_fd[1].fd = tunnel->fd4;
@@ -377,6 +417,18 @@
   printf("-i [uplink interface]\n");
   printf("-p [plat prefix]\n");
   printf("-n [NetId]\n");
+  printf("-m [socket mark]\n");
+}
+
+/* function: parse_unsigned
+ * parses a string as a decimal/hex/octal unsigned integer
+ * str - the string to parse
+ * out - the unsigned integer to write to, gets clobbered on failure
+ */
+int parse_unsigned(const char *str, unsigned *out) {
+    char *end_ptr;
+    *out = strtoul(str, &end_ptr, 0);
+    return *str && !*end_ptr;
 }
 
 /* function: main
@@ -385,13 +437,13 @@
 int main(int argc, char **argv) {
   struct tun_data tunnel;
   int opt;
-  char *uplink_interface = NULL, *plat_prefix = NULL, *net_id_str = NULL;
+  char *uplink_interface = NULL, *plat_prefix = NULL, *net_id_str = NULL, *mark_str = NULL;
   unsigned net_id = NETID_UNSET;
+  uint32_t mark = MARK_UNSET;
 
-  strcpy(tunnel.device6, DEVICENAME6);
   strcpy(tunnel.device4, DEVICENAME4);
 
-  while((opt = getopt(argc, argv, "i:p:n:h")) != -1) {
+  while((opt = getopt(argc, argv, "i:p:n:m:h")) != -1) {
     switch(opt) {
       case 'i':
         uplink_interface = optarg;
@@ -402,49 +454,46 @@
       case 'n':
         net_id_str = optarg;
         break;
-      case 'h':
-      default:
-        print_help();
-        exit(1);
+      case 'm':
+        mark_str = optarg;
         break;
+      case 'h':
+        print_help();
+        exit(0);
+      default:
+        logmsg(ANDROID_LOG_FATAL, "Unknown option -%c. Exiting.", (char) optopt);
+        exit(1);
     }
   }
 
   if(uplink_interface == NULL) {
     logmsg(ANDROID_LOG_FATAL, "clatd called without an interface");
-    printf("I need an interface\n");
-    exit(1);
-  }
-  if (net_id_str != NULL) {
-    char *end_ptr;
-    net_id = strtoul(net_id_str, &end_ptr, 0);
-    if (*net_id_str == 0 || *end_ptr != 0) {
-      logmsg(ANDROID_LOG_FATAL, "clatd called with invalid NetID %s", net_id_str);
-      exit(1);
-    }
-  }
-  logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s", CLATD_VERSION, uplink_interface);
-
-  // open the tunnel device before dropping privs
-  tunnel.fd6 = tun_open();
-  if(tunnel.fd6 < 0) {
-    logmsg(ANDROID_LOG_FATAL, "tun_open6 failed: %s", strerror(errno));
     exit(1);
   }
 
+  if (net_id_str != NULL && !parse_unsigned(net_id_str, &net_id)) {
+    logmsg(ANDROID_LOG_FATAL, "invalid NetID %s", net_id_str);
+    exit(1);
+  }
+
+  if (mark_str != NULL && !parse_unsigned(mark_str, &mark)) {
+    logmsg(ANDROID_LOG_FATAL, "invalid mark %s", mark_str);
+    exit(1);
+  }
+
+  logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s netid=%s mark=%s",
+         CLATD_VERSION, uplink_interface,
+         net_id_str ? net_id_str : "(none)",
+         mark_str ? mark_str : "(none)");
+
+  // open the tunnel device and our raw sockets before dropping privs
   tunnel.fd4 = tun_open();
   if(tunnel.fd4 < 0) {
     logmsg(ANDROID_LOG_FATAL, "tun_open4 failed: %s", strerror(errno));
     exit(1);
   }
 
-  // open the forwarding configuration before dropping privs
-  forwarding_fd = open("/proc/sys/net/ipv6/conf/all/forwarding", O_RDWR);
-  if(forwarding_fd < 0) {
-    logmsg(ANDROID_LOG_FATAL,"open /proc/sys/net/ipv6/conf/all/forwarding failed: %s",
-           strerror(errno));
-    exit(1);
-  }
+  open_sockets(&tunnel, mark);
 
   // run under a regular user
   drop_root();
@@ -455,7 +504,10 @@
 
   configure_interface(uplink_interface, plat_prefix, &tunnel, net_id);
 
-  set_forwarding(forwarding_fd,"1\n");
+  if (!configure_packet_socket(tunnel.read_fd6)) {
+    // We've already logged an error.
+    exit(1);
+  }
 
   // Loop until someone sends us a signal or brings down the tun interface.
   if(signal(SIGTERM, stop_loop) == SIG_ERR) {
@@ -464,7 +516,6 @@
   }
   event_loop(&tunnel);
 
-  set_forwarding(forwarding_fd,"0\n");
   logmsg(ANDROID_LOG_INFO,"Shutting down clat on %s", uplink_interface);
 
   return 0;
diff --git a/clatd.h b/clatd.h
index 29c9ace..0f4809d 100644
--- a/clatd.h
+++ b/clatd.h
@@ -23,7 +23,7 @@
 
 #define MAXMTU 1500
 #define PACKETLEN (MAXMTU+sizeof(struct tun_pi))
-#define CLATD_VERSION "1.2"
+#define CLATD_VERSION "1.3"
 
 // how frequently (in seconds) to poll for an address change while traffic is passing
 #define INTERFACE_POLL_FREQUENCY 30
@@ -32,8 +32,8 @@
 #define NO_TRAFFIC_INTERFACE_POLL_FREQUENCY 90
 
 struct tun_data {
-  char device6[IFNAMSIZ], device4[IFNAMSIZ];
-  int fd6, fd4;
+  char device4[IFNAMSIZ];
+  int read_fd6, write_fd6, fd4;
 };
 
 #endif /* __CLATD_H__ */
diff --git a/clatd_test.cpp b/clatd_test.cpp
index 5172d3f..b35bf70 100644
--- a/clatd_test.cpp
+++ b/clatd_test.cpp
@@ -418,23 +418,26 @@
   udp->check = ip_checksum_finish(ip_checksum_add(pseudo_checksum, udp, ntohs(udp->len)));
 }
 
+// Testing stub for send_rawv6. The real version uses sendmsg() with a
+// destination IPv6 address, and attempting to call that on our test socketpair
+// fd results in EINVAL.
+extern "C" void send_rawv6(int fd, clat_packet out, int iov_len) {
+    writev(fd, out, iov_len);
+}
+
 void do_translate_packet(const uint8_t *original, size_t original_len, uint8_t *out, size_t *outlen,
                          const char *msg) {
   int fds[2];
   if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, fds)) {
     abort();
   }
-  struct tun_data tunnel = {
-    "clat", "clat4",
-    fds[0], fds[1]
-  };
   struct tun_pi tun_header = { 0, 0 };
 
   char foo[512];
   snprintf(foo, sizeof(foo), "%s: Invalid original packet", msg);
   check_packet(original, original_len, foo);
 
-  int read_fd;
+  int read_fd, write_fd;
   uint16_t expected_proto;
   int version = ip_version(original);
   switch (version) {
@@ -442,32 +445,40 @@
       tun_header.proto = htons(ETH_P_IP);
       expected_proto = htons(ETH_P_IPV6);
       read_fd = fds[1];
+      write_fd = fds[0];
       break;
     case 6:
       tun_header.proto = htons(ETH_P_IPV6);
       expected_proto = htons(ETH_P_IP);
       read_fd = fds[0];
+      write_fd = fds[1];
       break;
     default:
       FAIL() << msg << ": Unsupported IP version " << version << "\n";
       break;
   }
 
-  translate_packet(&tunnel, &tun_header, original, original_len);
+  translate_packet(write_fd, (version == 4), original, original_len);
 
-  struct tun_pi new_tun_header;
-  struct iovec iov[] = {
-    { &new_tun_header, sizeof(new_tun_header) },
-    { out, *outlen }
-  };
-  int len = readv(read_fd, iov, 2);
-  if (len > (int) sizeof(new_tun_header)) {
-    ASSERT_LT((size_t) len, *outlen) << msg << ": Translated packet buffer too small\n";
-    EXPECT_EQ(expected_proto, new_tun_header.proto) << msg << "Unexpected tun proto\n";
-    *outlen = len - sizeof(new_tun_header);
+  if (version == 6) {
+    // Translating to IPv4. Expect a tun header.
+    struct tun_pi new_tun_header;
+    struct iovec iov[] = {
+      { &new_tun_header, sizeof(new_tun_header) },
+      { out, *outlen }
+    };
+    int len = readv(read_fd, iov, 2);
+    if (len > (int) sizeof(new_tun_header)) {
+      ASSERT_LT((size_t) len, *outlen) << msg << ": Translated packet buffer too small\n";
+      EXPECT_EQ(expected_proto, new_tun_header.proto) << msg << "Unexpected tun proto\n";
+      *outlen = len - sizeof(new_tun_header);
+    } else {
+      FAIL() << msg << ": Packet was not translated";
+      *outlen = 0;
+    }
   } else {
-    FAIL() << msg << ": Packet was not translated";
-    *outlen = 0;
+    // Translating to IPv6. Expect raw packet.
+    *outlen = read(read_fd, out, *outlen);
   }
 }
 
diff --git a/config.c b/config.c
index 3d39ff0..d8bec80 100644
--- a/config.c
+++ b/config.c
@@ -31,7 +31,6 @@
 #include "logging.h"
 #include "getaddr.h"
 #include "clatd.h"
-#include "setroute.h"
 
 struct clat_config Global_Clatd_Config;
 
diff --git a/dump.c b/dump.c
index 0fda4e7..27b75d1 100644
--- a/dump.c
+++ b/dump.c
@@ -162,7 +162,7 @@
 }
 
 /* print tcp header */
-void dump_tcp_generic(const struct tcphdr *tcp, const char *options, size_t options_size, uint32_t temp_checksum, const char *payload, size_t payload_size) {
+void dump_tcp_generic(const struct tcphdr *tcp, const uint8_t *options, size_t options_size, uint32_t temp_checksum, const uint8_t *payload, size_t payload_size) {
   uint16_t my_checksum;
 
   temp_checksum = ip_checksum_add(temp_checksum, tcp, sizeof(struct tcphdr));
@@ -205,7 +205,7 @@
 /* print ipv4/tcp header */
 void dump_tcp(const struct tcphdr *tcp, const struct iphdr *ip,
               const uint8_t *payload, size_t payload_size,
-              const char *options, size_t options_size) {
+              const uint8_t *options, size_t options_size) {
   uint32_t temp_checksum;
 
   temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*tcp) + options_size + payload_size);
@@ -215,7 +215,7 @@
 /* print ipv6/tcp header */
 void dump_tcp6(const struct tcphdr *tcp, const struct ip6_hdr *ip6,
                const uint8_t *payload, size_t payload_size,
-               const char *options, size_t options_size) {
+               const uint8_t *options, size_t options_size) {
   uint32_t temp_checksum;
 
   temp_checksum = ipv6_pseudo_header_checksum(ip6, sizeof(*tcp) + options_size + payload_size, IPPROTO_TCP);
diff --git a/getroute.c b/getroute.c
deleted file mode 100644
index a615a4f..0000000
--- a/getroute.c
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2012 Daniel Drown
- *
- * 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.
- *
- * getroute.c - get an ip route
- */
-#include <string.h>
-#include <errno.h>
-
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <arpa/inet.h>
-
-#include <netlink/handlers.h>
-#include <netlink/msg.h>
-
-#include "getroute.h"
-#include "netlink_callbacks.h"
-#include "netlink_msg.h"
-
-/* function: get_default_route_cb
- * finds the default route with the request family and out interface and saves the gateway
- * msg  - netlink message
- * data - (struct default_route_data) requested filters and response storage
- */
-static int get_default_route_cb(struct nl_msg *msg, void *data) {
-  struct rtmsg *rt_p;
-  struct rtattr *rta_p;
-  int rta_len;
-  struct default_route_data *default_route = data;
-  union anyip *this_gateway = NULL;
-  ssize_t this_gateway_size;
-  int this_interface_id = -1;
-
-  if(default_route->reply_found_route) { // we already found our route
-    return NL_OK;
-  }
-
-  rt_p = (struct rtmsg *)nlmsg_data(nlmsg_hdr(msg));
-  if(rt_p->rtm_dst_len != 0) { // not a default route
-    return NL_OK;
-  }
-  if((rt_p->rtm_family != default_route->request_family) || (rt_p->rtm_table != RT_TABLE_MAIN)) { // not a route we care about
-    return NL_OK;
-  }
-
-  rta_p = (struct rtattr *)RTM_RTA(rt_p);
-  rta_len = RTM_PAYLOAD(nlmsg_hdr(msg));
-  for(; RTA_OK(rta_p, rta_len); rta_p = RTA_NEXT(rta_p, rta_len)) {
-    switch(rta_p->rta_type) {
-      case RTA_GATEWAY:
-        this_gateway = RTA_DATA(rta_p);
-        this_gateway_size = RTA_PAYLOAD(rta_p);
-        break;
-      case RTA_OIF:
-        this_interface_id = *(int *)RTA_DATA(rta_p);
-        break;
-      default:
-        break;
-    }
-  }
-
-  if(this_interface_id == default_route->request_interface_id) {
-    default_route->reply_found_route = 1;
-    if(this_gateway != NULL) {
-      memcpy(&default_route->reply_gateway, this_gateway, this_gateway_size);
-      default_route->reply_has_gateway = 1;
-    } else {
-      default_route->reply_has_gateway = 0;
-    }
-  }
-  return NL_OK;
-}
-
-/* function: error_handler
- * error callback for get_default_route
- * nla  - where the message came from
- * err  - netlink message
- * arg  - (int *) storage for the error number
- */
-static int error_handler(__attribute__((unused)) struct sockaddr_nl *nla,
-                         struct nlmsgerr *err, void *arg) {
-  int *retval = arg;
-  if(err->error < 0) { // error_handler called even on no error (NLMSG_ERROR reply type used)
-    *retval = err->error;
-  }
-  return NL_OK;
-}
-
-/* function: get_default_route
- * finds the first default route with the given family and interface, returns the gateway (if it exists) in the struct
- * default_route - requested family and interface, and response storage
- */
-int get_default_route(struct default_route_data *default_route) {
-  struct rtmsg msg;
-  struct nl_cb *callbacks = NULL;
-  struct nl_msg *nlmsg = NULL;
-  int retval = 0;
-
-  default_route->reply_has_gateway = 0;
-  default_route->reply_found_route = 0;
-
-  memset(&msg,'\0',sizeof(msg));
-  msg.rtm_family = default_route->request_family;
-  msg.rtm_table = RT_TABLE_MAIN;
-  msg.rtm_protocol = RTPROT_KERNEL;
-  msg.rtm_scope = RT_SCOPE_UNIVERSE;
-
-  callbacks = nl_cb_alloc(NL_CB_DEFAULT);
-  if(!callbacks) {
-    retval = -ENOMEM;
-    goto cleanup;
-  }
-  // get_default_route_cb sets the response fields in default_route
-  nl_cb_set(callbacks, NL_CB_VALID, NL_CB_CUSTOM, get_default_route_cb, default_route);
-  nl_cb_err(callbacks, NL_CB_CUSTOM, error_handler, &retval);
-
-  nlmsg = nlmsg_alloc_rtmsg(RTM_GETROUTE, NLM_F_REQUEST | NLM_F_ROOT, &msg);
-  if(!nlmsg) {
-    retval = -ENOMEM;
-    goto cleanup;
-  }
-  send_netlink_msg(nlmsg, callbacks);
-
-cleanup:
-  if(callbacks)
-    nl_cb_put(callbacks);
-  if(nlmsg)
-    nlmsg_free(nlmsg);
-
-  return retval;
-}
diff --git a/getroute.h b/getroute.h
deleted file mode 100644
index e7b8670..0000000
--- a/getroute.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2012 Daniel Drown
- *
- * 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.
- *
- * getroute.h - get an ip route
- */
-#ifndef __GETROUTE_H__
-#define __GETROUTE_H__
-
-// for union anyip
-#include "getaddr.h"
-
-struct default_route_data {
-  int request_interface_id;
-  int request_family;
-
-  union anyip reply_gateway;
-  int reply_has_gateway;
-  int reply_found_route;
-};
-
-int get_default_route(struct default_route_data *default_route);
-
-#endif
diff --git a/setroute.c b/setroute.c
deleted file mode 100644
index cffee9f..0000000
--- a/setroute.c
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2012 Daniel Drown <dan-android@drown.org>
- *
- * 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.
- *
- * setroute.c - network route configuration
- */
-#include <errno.h>
-#include <netinet/in.h>
-#include <net/if.h>
-
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <netlink/handlers.h>
-#include <netlink/msg.h>
-
-#include "netlink_msg.h"
-#include "setroute.h"
-#include "logging.h"
-#include "getroute.h"
-
-/* function: if_route
- * create/replace/delete a route
- * ifname      - name of the outbound interface
- * family      - AF_INET or AF_INET6
- * destination - pointer to a struct in_addr or in6_addr for the destination network
- * prefixlen   - bitlength of the network address (example: 24 for AF_INET's 255.255.255.0)
- * gateway     - pointer to a struct in_addr or in6_addr for the gateway to use or NULL for an interface route
- * metric      - route metric (lower is better)
- * mtu         - route-specific mtu or 0 for the interface mtu
- * change_type - ROUTE_DELETE, ROUTE_REPLACE, or ROUTE_CREATE
- */
-int if_route(const char *ifname, int family, const void *destination, int prefixlen, const void *gateway, int metric, int mtu, int change_type) {
-  int retval;
-  struct nl_msg *msg = NULL;
-  struct rtmsg rt;
-  uint16_t type, flags = 0;
-  size_t addr_size;
-  uint32_t ifindex;
-
-  addr_size = inet_family_size(family);
-  if(addr_size == 0) {
-    retval = -EAFNOSUPPORT;
-    goto cleanup;
-  }
-
-  if (!(ifindex = if_nametoindex(ifname))) {
-    retval = -ENODEV;
-    goto cleanup;
-  }
-
-  memset(&rt, 0, sizeof(rt));
-  rt.rtm_family = family;
-  rt.rtm_table = RT_TABLE_MAIN;
-  rt.rtm_dst_len = prefixlen;
-  switch(change_type) {
-    case ROUTE_DELETE:
-      rt.rtm_scope = RT_SCOPE_NOWHERE;
-      type = RTM_DELROUTE;
-      break;
-
-    case ROUTE_REPLACE:
-      flags = NLM_F_REPLACE;
-    case ROUTE_CREATE:
-      type = RTM_NEWROUTE;
-      flags |= NLM_F_CREATE;
-      if(gateway == NULL) {
-        rt.rtm_scope = RT_SCOPE_LINK;
-      } else {
-        rt.rtm_scope = RT_SCOPE_UNIVERSE;
-      }
-      rt.rtm_type = RTN_UNICAST;
-      //RTPROT_STATIC = from administrator's configuration
-      //RTPROT_BOOT = from an automatic process
-      rt.rtm_protocol = RTPROT_BOOT;
-      break;
-
-    default:
-      retval = -EINVAL;
-      goto cleanup;
-  }
-
-  flags |= NLM_F_REQUEST | NLM_F_ACK;
-
-  msg = nlmsg_alloc_rtmsg(type, flags, &rt);
-  if(!msg) {
-    retval = -ENOMEM;
-    goto cleanup;
-  }
-
-  if(nla_put(msg, RTA_DST, addr_size, destination) < 0) {
-    retval = -ENOMEM;
-    goto cleanup;
-  }
-  if(gateway != NULL)
-    if(nla_put(msg, RTA_GATEWAY, addr_size, gateway) < 0) {
-      retval = -ENOMEM;
-      goto cleanup;
-    }
-  if(nla_put(msg, RTA_OIF, 4, &ifindex) < 0) {
-    retval = -ENOMEM;
-    goto cleanup;
-  }
-  if(nla_put(msg, RTA_PRIORITY, 4, &metric) < 0) {
-    retval = -ENOMEM;
-    goto cleanup;
-  }
-  if(mtu > 0 && change_type != ROUTE_DELETE) {
-    // MTU is inside an RTA_METRICS nested message
-    struct nlattr *metrics = nla_nest_start(msg, RTA_METRICS);
-    if(metrics == NULL) {
-      retval = -ENOMEM;
-      goto cleanup;
-    }
-
-    if(nla_put(msg, RTAX_MTU, 4, &mtu) < 0) {
-      retval = -ENOMEM;
-      goto cleanup;
-    }
-
-    nla_nest_end(msg, metrics);
-  }
-
-  retval = netlink_sendrecv(msg);
-
-cleanup:
-  if(msg)
-    nlmsg_free(msg);
-
-  return retval;
-}
diff --git a/setroute.h b/setroute.h
deleted file mode 100644
index 58f61cf..0000000
--- a/setroute.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2012 Daniel Drown <dan-android@drown.org>
- *
- * 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.
- *
- * setroute.h - network route configuration
- */
-
-#ifndef __SETROUTE_H__
-#define __SETROUTE_H__
-
-#define ROUTE_DELETE 0
-#define ROUTE_REPLACE 1
-#define ROUTE_CREATE 2
-int if_route(const char *ifname, int family, const void *destination, int cidr, const void *gateway, int metric, int mtu, int change_type);
-
-#endif
diff --git a/translate.c b/translate.c
index e93a93a..487468b 100644
--- a/translate.c
+++ b/translate.c
@@ -465,16 +465,38 @@
   return CLAT_POS_PAYLOAD + 1;
 }
 
+void send_tun(int fd, clat_packet out, int iov_len) {
+  writev(fd, out, iov_len);
+}
+
+// Weak symbol so we can override it in the unit test.
+void send_rawv6(int fd, clat_packet out, int iov_len) __attribute__((weak));
+
+void send_rawv6(int fd, clat_packet out, int iov_len) {
+  // A send on a raw socket requires a destination address to be specified even if the socket's
+  // protocol is IPPROTO_RAW. This is the address that will be used in routing lookups; the
+  // destination address in the packet header only affects what appears on the wire, not where the
+  // packet is sent to.
+  static struct sockaddr_in6 sin6 = { AF_INET6, 0, 0, { { { 0, 0, 0, 0 } } }, 0 };
+  static struct msghdr msg = {
+    .msg_name = &sin6,
+    .msg_namelen = sizeof(sin6),
+  };
+
+  msg.msg_iov = out,
+  msg.msg_iovlen = iov_len,
+  sin6.sin6_addr = ((struct ip6_hdr *) out[CLAT_POS_IPHDR].iov_base)->ip6_dst;
+  sendmsg(fd, &msg, 0);
+}
+
 /* function: translate_packet
- * takes a tun header and a packet and sends it down the stack
- * tunnel     - tun device data
- * tun_header - tun header
+ * takes a packet, translates it, and writes it to fd
+ * fd         - fd to write translated packet to
+ * to_ipv6    - true if translating to ipv6, false if translating to ipv4
  * packet     - packet
  * packetsize - size of packet
  */
-void translate_packet(const struct tun_data *tunnel, struct tun_pi *tun_header,
-                      const uint8_t *packet, size_t packetsize) {
-  int fd;
+void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize) {
   int iov_len = 0;
 
   // Allocate buffers for all packet headers.
@@ -488,7 +510,7 @@
 
   // iovec of the packets we'll send. This gets passed down to the translation functions.
   clat_packet out = {
-    { &tun_targ, sizeof(tun_targ) },  // Tunnel header.
+    { &tun_targ, 0 },                 // Tunnel header.
     { iphdr, 0 },                     // IP header.
     { fraghdr, 0 },                   // Fragment header.
     { transporthdr, 0 },              // Transport layer header.
@@ -498,23 +520,17 @@
     { NULL, 0 },                      // Payload. No buffer, it's a pointer to the original payload.
   };
 
-  if(tun_header->flags != 0) {
-    logmsg(ANDROID_LOG_WARN, "translate_packet: unexpected flags = %d", tun_header->flags);
-  }
-
-  if(ntohs(tun_header->proto) == ETH_P_IP) {
-    fd = tunnel->fd6;
-    fill_tun_header(&tun_targ, ETH_P_IPV6);
+  if (to_ipv6) {
     iov_len = ipv4_packet(out, CLAT_POS_IPHDR, packet, packetsize);
-  } else if(ntohs(tun_header->proto) == ETH_P_IPV6) {
-    fd = tunnel->fd4;
-    fill_tun_header(&tun_targ, ETH_P_IP);
-    iov_len = ipv6_packet(out, CLAT_POS_IPHDR, packet, packetsize);
+    if (iov_len > 0) {
+      send_rawv6(fd, out, iov_len);
+    }
   } else {
-    logmsg(ANDROID_LOG_WARN, "translate_packet: unknown packet type = %x",tun_header->proto);
-  }
-
-  if (iov_len > 0) {
-    writev(fd, out, iov_len);
+    iov_len = ipv6_packet(out, CLAT_POS_IPHDR, packet, packetsize);
+    if (iov_len > 0) {
+      fill_tun_header(&tun_targ, ETH_P_IP);
+      out[CLAT_POS_TUNHDR].iov_len = sizeof(tun_targ);
+      send_tun(fd, out, iov_len);
+    }
   }
 }
diff --git a/translate.h b/translate.h
index 6d4f126..46e178b 100644
--- a/translate.h
+++ b/translate.h
@@ -60,8 +60,7 @@
                      const struct iphdr *old_header);
 
 // Translate and send packets.
-void translate_packet(const struct tun_data *tunnel, struct tun_pi *tun_header,
-                      const uint8_t *packet, size_t packetsize);
+void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize);
 
 // Translate IPv4 and IPv6 packets.
 int ipv4_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len);