am 866b4af1: (-s ours) DO NOT MERGE: Support translating fragmented packets.

* commit '866b4af18d8e9ef1020adc9accbb9f9de4c873dc':
  DO NOT MERGE: Support translating fragmented packets.
diff --git a/checksum.c b/checksum.c
index 3dd1e00..a4dc9b8 100644
--- a/checksum.c
+++ b/checksum.c
@@ -49,25 +49,17 @@
   return checksum;
 }
 
-/* function: ip_checksum_fold
- * folds a 32-bit partial checksum into 16 bits
+/* function: ip_checksum_finish
+ * close the checksum
  * temp_sum - sum from ip_checksum_add
- * returns: the folded checksum in network byte order
  */
-uint16_t ip_checksum_fold(uint32_t temp_sum) {
+uint16_t ip_checksum_finish(uint32_t temp_sum) {
   while(temp_sum > 0xffff)
     temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
 
-  return temp_sum;
-}
+  temp_sum = (~temp_sum) & 0xffff;
 
-/* function: ip_checksum_finish
- * folds and closes the checksum
- * temp_sum - sum from ip_checksum_add
- * returns: a header checksum value in network byte order
- */
-uint16_t ip_checksum_finish(uint32_t temp_sum) {
-  return ~ip_checksum_fold(temp_sum);
+  return temp_sum;
 }
 
 /* function: ip_checksum
@@ -84,16 +76,16 @@
 
 /* function: ipv6_pseudo_header_checksum
  * calculate the pseudo header checksum for use in tcp/udp/icmp headers
- * ip6      - the ipv6 header
- * len      - the transport length (transport header + payload)
- * protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments
+ * current - the current checksum or 0 to start a new checksum
+ * ip6     - the ipv6 header
+ * len     - the transport length (transport header + payload)
  */
-uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr *ip6, uint16_t len, uint8_t protocol) {
+uint32_t ipv6_pseudo_header_checksum(uint32_t current, const struct ip6_hdr *ip6, uint16_t len) {
   uint32_t checksum_len, checksum_next;
-  checksum_len = htonl((uint32_t) len);
-  checksum_next = htonl(protocol);
 
-  uint32_t current = 0;
+  checksum_len = htonl((uint32_t) len);
+  checksum_next = htonl(ip6->ip6_nxt);
+
   current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr));
   current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr));
   current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len));
@@ -104,16 +96,16 @@
 
 /* function: ipv4_pseudo_header_checksum
  * calculate the pseudo header checksum for use in tcp/udp headers
+ * current - the current checksum or 0 to start a new checksum
  * ip      - the ipv4 header
  * len     - the transport length (transport header + payload)
  */
-uint32_t ipv4_pseudo_header_checksum(const struct iphdr *ip, uint16_t len) {
+uint32_t ipv4_pseudo_header_checksum(uint32_t current, const struct iphdr *ip, uint16_t len) {
   uint16_t temp_protocol, temp_length;
 
   temp_protocol = htons(ip->protocol);
   temp_length = htons(len);
 
-  uint32_t current = 0;
   current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
   current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t));
   current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t));
@@ -121,23 +113,3 @@
 
   return current;
 }
-
-/* function: ip_checksum_adjust
- * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums
- * checksum    - the header checksum in the original packet in network byte order
- * old_hdr_sum - the pseudo-header checksum of the original packet
- * new_hdr_sum - the pseudo-header checksum of the translated packet
- * returns: the new header checksum in network byte order
- */
-uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) {
-  // Algorithm suggested in RFC 1624.
-  // http://tools.ietf.org/html/rfc1624#section-3
-  checksum = ~checksum;
-  uint16_t folded_sum = ip_checksum_fold(checksum + new_hdr_sum);
-  uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
-  if (folded_sum > folded_old) {
-    return ~(folded_sum - folded_old);
-  } else {
-    return ~(folded_sum - folded_old - 1);  // end-around borrow
-  }
-}
diff --git a/checksum.h b/checksum.h
index 6195810..473f5f5 100644
--- a/checksum.h
+++ b/checksum.h
@@ -22,9 +22,7 @@
 uint16_t ip_checksum_finish(uint32_t temp_sum);
 uint16_t ip_checksum(const void *data, int len);
 
-uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr *ip6, uint16_t len, uint8_t protocol);
-uint32_t ipv4_pseudo_header_checksum(const struct iphdr *ip, uint16_t len);
-
-uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum);
+uint32_t ipv6_pseudo_header_checksum(uint32_t current, const struct ip6_hdr *ip6, uint16_t len);
+uint32_t ipv4_pseudo_header_checksum(uint32_t current, const struct iphdr *ip, uint16_t len);
 
 #endif /* __CHECKSUM_H__ */
diff --git a/clatd.c b/clatd.c
index a89c588..a0ee07d 100644
--- a/clatd.c
+++ b/clatd.c
@@ -29,6 +29,15 @@
 #include <arpa/inet.h>
 #include <fcntl.h>
 
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <linux/icmp.h>
+
 #include <sys/capability.h>
 #include <sys/uio.h>
 #include <linux/prctl.h>
@@ -51,12 +60,14 @@
 #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;
 
+struct tun_data {
+  char device6[IFNAMSIZ], device4[IFNAMSIZ];
+  int fd6, fd4;
+};
+
 /* function: set_forwarding
  * enables/disables ipv6 forwarding
  */
@@ -274,9 +285,8 @@
     Global_Clatd_Config.mtu = 1280;
   }
 
-  if(Global_Clatd_Config.ipv4mtu <= 0 ||
-     Global_Clatd_Config.ipv4mtu > Global_Clatd_Config.mtu - MTU_DELTA) {
-    Global_Clatd_Config.ipv4mtu = Global_Clatd_Config.mtu - MTU_DELTA;
+  if(Global_Clatd_Config.ipv4mtu <= 0 || (Global_Clatd_Config.ipv4mtu > Global_Clatd_Config.mtu - 20)) {
+    Global_Clatd_Config.ipv4mtu = Global_Clatd_Config.mtu-20;
     logmsg(ANDROID_LOG_WARN,"ipv4mtu now set to = %d",Global_Clatd_Config.ipv4mtu);
   }
 
@@ -295,6 +305,56 @@
   configure_tun_ip(tunnel);
 }
 
+/* function: packet_handler
+ * takes a tun header and a packet and sends it down the stack
+ * tunnel     - tun device data
+ * tun_header - tun header
+ * packet     - packet
+ * packetsize - size of packet
+ */
+void packet_handler(const struct tun_data *tunnel, struct tun_pi *tun_header, const char *packet,
+                    size_t packetsize) {
+  int fd;
+  int iov_len = 0;
+
+  // Allocate buffers for all packet headers.
+  struct tun_pi tun_targ;
+  char iphdr[sizeof(struct ip6_hdr)];
+  char transporthdr[MAX_TCP_HDR];
+  char icmp_iphdr[sizeof(struct ip6_hdr)];
+  char icmp_transporthdr[MAX_TCP_HDR];
+
+  // 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.
+    { iphdr, 0 },                     // IP header.
+    { transporthdr, 0 },              // Transport layer header.
+    { icmp_iphdr, 0 },                // ICMP error inner IP header.
+    { icmp_transporthdr, 0 },         // ICMP error transport layer header.
+    { NULL, 0 },                      // Payload. No buffer, it's a pointer to the original payload.
+  };
+
+  if(tun_header->flags != 0) {
+    logmsg(ANDROID_LOG_WARN,"packet_handler: 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);
+    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);
+  } else {
+    logmsg(ANDROID_LOG_WARN,"packet_handler: unknown packet type = %x",tun_header->proto);
+  }
+
+  if (iov_len > 0) {
+    writev(fd, out, iov_len);
+  }
+}
+
 /* function: read_packet
  * reads a packet from the tunnel fd and passes it down the stack
  * active_fd - tun file descriptor marked ready for reading
@@ -323,7 +383,7 @@
       return;
     }
 
-    translate_packet(tunnel, (struct tun_pi *) packet, packet + header_size, readlen - header_size);
+    packet_handler(tunnel, (struct tun_pi *) packet, packet + header_size, readlen - header_size);
   }
 }
 
diff --git a/clatd.conf b/clatd.conf
index 578da59..0d4b79e 100644
--- a/clatd.conf
+++ b/clatd.conf
@@ -11,7 +11,7 @@
 # get the plat_subnet from dns lookups (requires DNS64)
 plat_from_dns64 yes
 # hostname to use to lookup plat subnet. must contain only A records
-plat_from_dns64_hostname ipv4.google.com
+plat_from_dns64_hostname ipv4only.arpa
 
 # plat subnet to send ipv4 traffic to. This is a /96 subnet.
 # This setting only makes sense with: plat_from_dns64 no
diff --git a/clatd.h b/clatd.h
index 44a655e..3459b09 100644
--- a/clatd.h
+++ b/clatd.h
@@ -18,7 +18,6 @@
 #ifndef __CLATD_H__
 #define __CLATD_H__
 
-#include <linux/if.h>
 #include <linux/if_tun.h>
 
 #define MAXMTU 1500
@@ -31,9 +30,4 @@
 // how frequently (in seconds) to poll for an address change while there is no traffic
 #define NO_TRAFFIC_INTERFACE_POLL_FREQUENCY 90
 
-struct tun_data {
-  char device6[IFNAMSIZ], device4[IFNAMSIZ];
-  int fd6, fd4;
-};
-
 #endif /* __CLATD_H__ */
diff --git a/dump.c b/dump.c
index ba5fa3e..8567655 100644
--- a/dump.c
+++ b/dump.c
@@ -147,14 +147,14 @@
 /* print ipv4/udp header */
 void dump_udp(const struct udphdr *udp, const struct iphdr *ip, const char *payload, size_t payload_size) {
   uint32_t temp_checksum;
-  temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*udp) + payload_size);
+  temp_checksum = ipv4_pseudo_header_checksum(0, ip, sizeof(*udp) + payload_size);
   dump_udp_generic(udp, temp_checksum, payload, payload_size);
 }
 
 /* print ipv6/udp header */
 void dump_udp6(const struct udphdr *udp, const struct ip6_hdr *ip6, const char *payload, size_t payload_size) {
   uint32_t temp_checksum;
-  temp_checksum = ipv6_pseudo_header_checksum(ip6, sizeof(*udp) + payload_size, IPPROTO_UDP);
+  temp_checksum = ipv6_pseudo_header_checksum(0, ip6, sizeof(*udp) + payload_size);
   dump_udp_generic(udp, temp_checksum, payload, payload_size);
 }
 
@@ -203,7 +203,7 @@
 void dump_tcp(const struct tcphdr *tcp, const struct iphdr *ip, const char *payload, size_t payload_size, const char *options, size_t options_size) {
   uint32_t temp_checksum;
 
-  temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*tcp) + options_size + payload_size);
+  temp_checksum = ipv4_pseudo_header_checksum(0, ip, sizeof(*tcp) + options_size + payload_size);
   dump_tcp_generic(tcp, options, options_size, temp_checksum, payload, payload_size);
 }
 
@@ -211,7 +211,7 @@
 void dump_tcp6(const struct tcphdr *tcp, const struct ip6_hdr *ip6, const char *payload, size_t payload_size, const char *options, size_t options_size) {
   uint32_t temp_checksum;
 
-  temp_checksum = ipv6_pseudo_header_checksum(ip6, sizeof(*tcp) + options_size + payload_size, IPPROTO_TCP);
+  temp_checksum = ipv6_pseudo_header_checksum(0, ip6, sizeof(*tcp) + options_size + payload_size);
   dump_tcp_generic(tcp, options, options_size, temp_checksum, payload, payload_size);
 }
 
@@ -220,7 +220,6 @@
   char output[PACKETLEN*3+2];
   size_t i;
 
-  output[0] = '\0';
   for(i = 0; i < len && i < PACKETLEN; i++) {
     snprintf(output + i*3, 4, " %02x", (uint8_t)data[i]);
   }
diff --git a/ipv4.c b/ipv4.c
index 84dccd5..b5cbf80 100644
--- a/ipv4.c
+++ b/ipv4.c
@@ -17,6 +17,15 @@
  */
 #include <string.h>
 
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <linux/icmp.h>
+
 #include "translate.h"
 #include "checksum.h"
 #include "logging.h"
@@ -57,12 +66,11 @@
 int ipv4_packet(clat_packet out, int pos, const char *packet, size_t len) {
   const struct iphdr *header = (struct iphdr *) packet;
   struct ip6_hdr *ip6_targ = (struct ip6_hdr *) out[pos].iov_base;
-  struct ip6_frag *frag_hdr;
-  size_t frag_hdr_len;
+  uint16_t frag_flags;
   uint8_t nxthdr;
   const char *next_header;
   size_t len_left;
-  uint32_t old_sum, new_sum;
+  uint32_t checksum;
   int iov_len;
 
   if(len < sizeof(struct iphdr)) {
@@ -70,6 +78,12 @@
     return 0;
   }
 
+  frag_flags = ntohs(header->frag_off);
+  if(frag_flags & IP_MF) { // this could theoretically be supported, but isn't
+    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/more fragments set, dropping");
+    return 0;
+  }
+
   if(header->ihl < 5) {
     logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set to less than 5: %x", header->ihl);
     return 0;
@@ -106,32 +120,17 @@
   fill_ip6_header(ip6_targ, 0, nxthdr, header);
   out[pos].iov_len = sizeof(struct ip6_hdr);
 
-  /* Calculate the pseudo-header checksum.
-   * Technically, the length that is used in the pseudo-header checksum is the transport layer
-   * length, which is not the same as len_left in the case of fragmented packets. But since
-   * translation does not change the transport layer length, the checksum is unaffected.
-   */
-  old_sum = ipv4_pseudo_header_checksum(header, len_left);
-  new_sum = ipv6_pseudo_header_checksum(ip6_targ, len_left, nxthdr);
+  // Calculate the pseudo-header checksum.
+  checksum = ipv6_pseudo_header_checksum(0, ip6_targ, len_left);
 
-  // If the IPv4 packet is fragmented, add a Fragment header.
-  frag_hdr = (struct ip6_frag *) out[pos + 1].iov_base;
-  frag_hdr_len = maybe_fill_frag_header(frag_hdr, ip6_targ, header);
-  out[pos + 1].iov_len = frag_hdr_len;
-
-  if (frag_hdr_len && frag_hdr->ip6f_offlg & IP6F_OFF_MASK) {
-    // Non-first fragment. Copy the rest of the packet as is.
-    iov_len = generic_packet(out, pos + 2, next_header, len_left);
-  } else if (nxthdr == IPPROTO_ICMPV6) {
-    iov_len = icmp_packet(out, pos + 2, (const struct icmphdr *) next_header, new_sum, len_left);
+  if (nxthdr == IPPROTO_ICMPV6) {
+    iov_len = icmp_packet(out, pos + 1, (const struct icmphdr *) next_header, checksum, len_left);
   } else if (nxthdr == IPPROTO_TCP) {
-    iov_len = tcp_packet(out, pos + 2, (const struct tcphdr *) next_header, old_sum, new_sum,
-                         len_left);
+    iov_len = tcp_packet(out, pos + 1, (const struct tcphdr *) next_header, checksum, len_left);
   } else if (nxthdr == IPPROTO_UDP) {
-    iov_len = udp_packet(out, pos + 2, (const struct udphdr *) next_header, old_sum, new_sum,
-                         len_left);
+    iov_len = udp_packet(out, pos + 1, (const struct udphdr *) next_header, checksum, len_left);
   } else if (nxthdr == IPPROTO_GRE) {
-    iov_len = generic_packet(out, pos + 2, next_header, len_left);
+    iov_len = generic_packet(out, pos + 1, next_header, len_left);
   } else {
 #if CLAT_DEBUG
     logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/unknown protocol: %x",header->protocol);
diff --git a/ipv6.c b/ipv6.c
index 2e83be1..79303ec 100644
--- a/ipv6.c
+++ b/ipv6.c
@@ -17,6 +17,14 @@
  */
 #include <string.h>
 
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <linux/icmp.h>
 #include <arpa/inet.h>
 
 #include "translate.h"
@@ -77,11 +85,10 @@
 int ipv6_packet(clat_packet out, int pos, const char *packet, size_t len) {
   const struct ip6_hdr *ip6 = (struct ip6_hdr *) packet;
   struct iphdr *ip_targ = (struct iphdr *) out[pos].iov_base;
-  struct ip6_frag *frag_hdr = NULL;
   uint8_t protocol;
   const char *next_header;
   size_t len_left;
-  uint32_t old_sum, new_sum;
+  uint32_t checksum;
   int iov_len;
 
   if(len < sizeof(struct ip6_hdr)) {
@@ -113,6 +120,10 @@
   len_left = len - sizeof(struct ip6_hdr);
 
   protocol = ip6->ip6_nxt;
+  if (protocol == IPPROTO_ICMPV6) {
+    // ICMP and ICMPv6 have different protocol numbers.
+    protocol = IPPROTO_ICMP;
+  }
 
   /* Fill in the IPv4 header. We need to do this before we translate the packet because TCP and
    * UDP include parts of the IP header in the checksum. Set the length to zero because we don't
@@ -121,48 +132,20 @@
   fill_ip_header(ip_targ, 0, protocol, ip6);
   out[pos].iov_len = sizeof(struct iphdr);
 
-  // If there's a Fragment header, parse it and decide what the next header is.
-  // Do this before calculating the pseudo-header checksum because it updates the next header value.
-  if (protocol == IPPROTO_FRAGMENT) {
-    frag_hdr = (struct ip6_frag *) next_header;
-    if (len_left < sizeof(*frag_hdr)) {
-      logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for fragment header: %d", len);
-      return 0;
-    }
+  // Calculate the pseudo-header checksum.
+  checksum = ipv4_pseudo_header_checksum(0, ip_targ, len_left);
 
-    next_header += sizeof(*frag_hdr);
-    len_left -= sizeof(*frag_hdr);
-
-    protocol = parse_frag_header(frag_hdr, ip_targ);
-  }
-
-  // ICMP and ICMPv6 have different protocol numbers.
-  if (protocol == IPPROTO_ICMPV6) {
-    protocol = IPPROTO_ICMP;
-    ip_targ->protocol = IPPROTO_ICMP;
-  }
-
-  /* Calculate the pseudo-header checksum.
-   * Technically, the length that is used in the pseudo-header checksum is the transport layer
-   * length, which is not the same as len_left in the case of fragmented packets. But since
-   * translation does not change the transport layer length, the checksum is unaffected.
-   */
-  old_sum = ipv6_pseudo_header_checksum(ip6, len_left, protocol);
-  new_sum = ipv4_pseudo_header_checksum(ip_targ, len_left);
-
-  // Does not support IPv6 extension headers except Fragment.
-  if (frag_hdr && (frag_hdr->ip6f_offlg & IP6F_OFF_MASK)) {
-    iov_len = generic_packet(out, pos + 2, next_header, len_left);
-  } else if (protocol == IPPROTO_ICMP) {
-    iov_len = icmp6_packet(out, pos + 2, (const struct icmp6_hdr *) next_header, len_left);
-  } else if (protocol == IPPROTO_TCP) {
-    iov_len = tcp_packet(out, pos + 2, (const struct tcphdr *) next_header, old_sum, new_sum,
+  // does not support IPv6 extension headers, this will drop any packet with them
+  if (protocol == IPPROTO_ICMP) {
+    iov_len = icmp6_packet(out, pos + 1, (const struct icmp6_hdr *) next_header, len_left);
+  } else if (ip6->ip6_nxt == IPPROTO_TCP) {
+    iov_len = tcp_packet(out, pos + 1, (const struct tcphdr *) next_header, checksum,
                          len_left);
-  } else if (protocol == IPPROTO_UDP) {
-    iov_len = udp_packet(out, pos + 2, (const struct udphdr *) next_header, old_sum, new_sum,
+  } else if (ip6->ip6_nxt == IPPROTO_UDP) {
+    iov_len = udp_packet(out, pos + 1, (const struct udphdr *) next_header, checksum,
                          len_left);
-  } else if (protocol == IPPROTO_GRE) {
-    iov_len = generic_packet(out, pos + 2, next_header, len_left);
+  } else if (ip6->ip6_nxt == IPPROTO_GRE) {
+    iov_len = generic_packet(out, pos + 1, next_header, len_left);
   } else {
 #if CLAT_DEBUG
     logmsg(ANDROID_LOG_ERROR, "ipv6_packet/unknown next header type: %x", ip6->ip6_nxt);
diff --git a/translate.c b/translate.c
index 7585092..00ea0b9 100644
--- a/translate.c
+++ b/translate.c
@@ -16,7 +16,15 @@
  * translate.c - CLAT functions / partial implementation of rfc6145
  */
 #include <string.h>
-#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <linux/icmp.h>
 
 #include "icmp.h"
 #include "translate.h"
@@ -167,54 +175,6 @@
   ip6->ip6_dst = ipv4_addr_to_ipv6_addr(old_header->daddr);
 }
 
-/* function: maybe_fill_frag_header
- * fills a fragmentation header
- * generate an ipv6 fragment header from an ipv4 header
- * frag_hdr    - target (ipv6) fragmentation header
- * ip6_targ    - target (ipv6) header
- * old_header  - (ipv4) source packet header
- * returns: the length of the fragmentation header if present, or zero if not present
- */
-size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ,
-                              const struct iphdr *old_header) {
-  uint16_t frag_flags = ntohs(old_header->frag_off);
-  uint16_t frag_off = frag_flags & IP_OFFMASK;
-  if (frag_off == 0 && (frag_flags & IP_MF) == 0) {
-    // Not a fragment.
-    return 0;
-  }
-
-  frag_hdr->ip6f_nxt = ip6_targ->ip6_nxt;
-  frag_hdr->ip6f_reserved = 0;
-  // In IPv4, the offset is the bottom 13 bits; in IPv6 it's the top 13 bits.
-  frag_hdr->ip6f_offlg = htons(frag_off << 3);
-  if (frag_flags & IP_MF) {
-    frag_hdr->ip6f_offlg |= IP6F_MORE_FRAG;
-  }
-  frag_hdr->ip6f_ident = htonl(ntohs(old_header->id));
-  ip6_targ->ip6_nxt = IPPROTO_FRAGMENT;
-
-  return sizeof(*frag_hdr);
-}
-
-/* function: parse_frag_header
- * return the length of the fragmentation header if present, or zero if not present
- * generate an ipv6 fragment header from an ipv4 header
- * frag_hdr    - (ipv6) fragmentation header
- * ip_targ     - target (ipv4) header
- * returns: the next header value
- */
-uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ) {
-  uint16_t frag_off = (ntohs(frag_hdr->ip6f_offlg & IP6F_OFF_MASK) >> 3);
-  if (frag_hdr->ip6f_offlg & IP6F_MORE_FRAG) {
-    frag_off |= IP_MF;
-  }
-  ip_targ->frag_off = htons(frag_off);
-  ip_targ->id = htons(ntohl(frag_hdr->ip6f_ident) & 0xffff);
-  ip_targ->protocol = frag_hdr->ip6f_nxt;
-  return frag_hdr->ip6f_nxt;
-}
-
 /* function: icmp_to_icmp6
  * translate ipv4 icmp to ipv6 icmp
  * out          - output packet
@@ -248,10 +208,12 @@
     // The pseudo-header checksum was calculated on the transport length of the original IPv4
     // packet that we were asked to translate. This transport length is 20 bytes smaller than it
     // needs to be, because the ICMP error contains an IPv4 header, which we will be translating to
-    // an IPv6 header, which is 20 bytes longer. Fix it up here.
+    // an IPv6 header, which is 20 bytes longer. Fix it up here. This is simpler than the
+    // alternative, which is to always update the pseudo-header checksum in all UDP/TCP/ICMP
+    // translation functions (rather than pre-calculating it when translating the IPv4 header).
     // We only need to do this for ICMP->ICMPv6, not ICMPv6->ICMP, because ICMP does not use the
     // pseudo-header when calculating its checksum (as the IPv4 header has its own checksum).
-    checksum = checksum + htons(20);
+    checksum = htonl(ntohl(checksum) + 20);
   } else if (icmp6_type == ICMP6_ECHO_REQUEST || icmp6_type == ICMP6_ECHO_REPLY) {
     // Ping packet.
     icmp6_targ->icmp6_id = icmp->un.echo.id;
@@ -336,12 +298,10 @@
  * takes a udp packet and sets it up for translation
  * out      - output packet
  * udp      - pointer to udp header in packet
- * old_sum  - pseudo-header checksum of old header
- * new_sum  - pseudo-header checksum of new header
+ * checksum - pseudo-header checksum
  * len      - size of ip payload
  */
-int udp_packet(clat_packet out, int pos, const struct udphdr *udp,
-               uint32_t old_sum, uint32_t new_sum, size_t len) {
+int udp_packet(clat_packet out, int pos, const struct udphdr *udp, uint32_t checksum, size_t len) {
   const char *payload;
   size_t payload_size;
 
@@ -353,7 +313,7 @@
   payload = (const char *) (udp + 1);
   payload_size = len - sizeof(struct udphdr);
 
-  return udp_translate(out, pos, udp, old_sum, new_sum, payload, payload_size);
+  return udp_translate(out, pos, udp, checksum, payload, payload_size);
 }
 
 /* function: tcp_packet
@@ -364,8 +324,7 @@
  * len      - size of ip payload
  * returns: the highest position in the output clat_packet that's filled in
  */
-int tcp_packet(clat_packet out, int pos, const struct tcphdr *tcp,
-               uint32_t old_sum, uint32_t new_sum, size_t len) {
+int tcp_packet(clat_packet out, int pos, const struct tcphdr *tcp, uint32_t checksum, size_t len) {
   const char *payload;
   size_t payload_size, header_size;
 
@@ -388,21 +347,20 @@
   payload = ((const char *) tcp) + header_size;
   payload_size = len - header_size;
 
-  return tcp_translate(out, pos, tcp, header_size, old_sum, new_sum, payload, payload_size);
+  return tcp_translate(out, pos, tcp, header_size, checksum, payload, payload_size);
 }
 
 /* function: udp_translate
  * common between ipv4/ipv6 - setup checksum and send udp packet
  * out          - output packet
  * udp          - udp header
- * old_sum      - pseudo-header checksum of old header
- * new_sum      - pseudo-header checksum of new header
+ * checksum     - pseudo-header checksum
  * payload      - tcp payload
  * payload_size - size of payload
  * returns: the highest position in the output clat_packet that's filled in
  */
-int udp_translate(clat_packet out, int pos, const struct udphdr *udp, uint32_t old_sum,
-                  uint32_t new_sum, const char *payload, size_t payload_size) {
+int udp_translate(clat_packet out, int pos, const struct udphdr *udp, uint32_t checksum,
+                  const char *payload, size_t payload_size) {
   struct udphdr *udp_targ = out[pos].iov_base;
 
   memcpy(udp_targ, udp, sizeof(struct udphdr));
@@ -411,22 +369,8 @@
   out[CLAT_POS_PAYLOAD].iov_base = (char *) payload;
   out[CLAT_POS_PAYLOAD].iov_len = payload_size;
 
-  if (udp_targ->check) {
-    udp_targ->check = ip_checksum_adjust(udp->check, old_sum, new_sum);
-  } else {
-    // Zero checksums are special. RFC 768 says, "An all zero transmitted checksum value means that
-    // the transmitter generated no checksum (for debugging or for higher level protocols that
-    // don't care)." However, in IPv6 zero UDP checksums were only permitted by RFC 6935 (2013). So
-    // for safety we recompute it.
-    udp_targ->check = 0;  // Checksum field must be 0 when calculating checksum.
-    udp_targ->check = packet_checksum(new_sum, out, pos);
-  }
-
-  // RFC 768: "If the computed checksum is zero, it is transmitted as all ones (the equivalent
-  // in one's complement arithmetic)."
-  if (!udp_targ->check) {
-    udp_targ->check = 0xffff;
-  }
+  udp_targ->check = 0;  // Checksum field must be 0 when calculating checksum.
+  udp_targ->check = packet_checksum(checksum, out, pos);
 
   return CLAT_POS_PAYLOAD + 1;
 }
@@ -440,9 +384,12 @@
  * payload      - tcp payload
  * payload_size - size of payload
  * returns: the highest position in the output clat_packet that's filled in
+ *
+ * TODO: mss rewrite
+ * TODO: hosts without pmtu discovery - non DF packets will rely on fragmentation (unimplemented)
  */
 int tcp_translate(clat_packet out, int pos, const struct tcphdr *tcp, size_t header_size,
-                  uint32_t old_sum, uint32_t new_sum, const char *payload, size_t payload_size) {
+                  uint32_t checksum, const char *payload, size_t payload_size) {
   struct tcphdr *tcp_targ = out[pos].iov_base;
   out[pos].iov_len = header_size;
 
@@ -459,61 +406,8 @@
   out[CLAT_POS_PAYLOAD].iov_base = (char *)payload;
   out[CLAT_POS_PAYLOAD].iov_len = payload_size;
 
-  tcp_targ->check = ip_checksum_adjust(tcp->check, old_sum, new_sum);
+  tcp_targ->check = 0;  // Checksum field must be 0 when calculating checksum.
+  tcp_targ->check = packet_checksum(checksum, out, pos);
 
   return CLAT_POS_PAYLOAD + 1;
 }
-
-/* function: translate_packet
- * takes a tun header and a packet and sends it down the stack
- * tunnel     - tun device data
- * tun_header - tun header
- * packet     - packet
- * packetsize - size of packet
- */
-void translate_packet(const struct tun_data *tunnel, struct tun_pi *tun_header, const char *packet,
-                      size_t packetsize) {
-  int fd;
-  int iov_len = 0;
-
-  // Allocate buffers for all packet headers.
-  struct tun_pi tun_targ;
-  char iphdr[sizeof(struct ip6_hdr)];
-  char fraghdr[sizeof(struct ip6_frag)];
-  char transporthdr[MAX_TCP_HDR];
-  char icmp_iphdr[sizeof(struct ip6_hdr)];
-  char icmp_fraghdr[sizeof(struct ip6_frag)];
-  char icmp_transporthdr[MAX_TCP_HDR];
-
-  // 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.
-    { iphdr, 0 },                     // IP header.
-    { fraghdr, 0 },                   // Fragment header.
-    { transporthdr, 0 },              // Transport layer header.
-    { icmp_iphdr, 0 },                // ICMP error inner IP header.
-    { icmp_fraghdr, 0 },              // ICMP error fragmentation header.
-    { icmp_transporthdr, 0 },         // ICMP error transport layer header.
-    { 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);
-    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);
-  } else {
-    logmsg(ANDROID_LOG_WARN, "translate_packet: unknown packet type = %x",tun_header->proto);
-  }
-
-  if (iov_len > 0) {
-    writev(fd, out, iov_len);
-  }
-}
diff --git a/translate.h b/translate.h
index 5efa817..9f1ac15 100644
--- a/translate.h
+++ b/translate.h
@@ -18,29 +18,17 @@
 #ifndef __TRANSLATE_H__
 #define __TRANSLATE_H__
 
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip_icmp.h>
-#include <netinet/udp.h>
-#include <netinet/tcp.h>
-#include <netinet/ip6.h>
-#include <netinet/icmp6.h>
-#include <linux/icmp.h>
 #include <linux/if_tun.h>
 
-#include "clatd.h"
-
 #define MAX_TCP_HDR (15 * 4)   // Data offset field is 4 bits and counts in 32-bit words.
 
 // A clat_packet is an array of iovec structures representing a packet that we are translating.
 // The CLAT_POS_XXX constants represent the array indices within the clat_packet that contain
 // specific parts of the packet. The packet_* functions operate on all the packet segments past a
 // given position.
-enum clat_packet_index {
-    CLAT_POS_TUNHDR, CLAT_POS_IPHDR, CLAT_POS_FRAGHDR, CLAT_POS_TRANSPORTHDR,
-    CLAT_POS_ICMPERR_IPHDR, CLAT_POS_ICMPERR_FRAGHDR, CLAT_POS_ICMPERR_TRANSPORTHDR,
-    CLAT_POS_PAYLOAD, CLAT_POS_MAX
-};
+enum clat_packet_index { CLAT_POS_TUNHDR, CLAT_POS_IPHDR, CLAT_POS_TRANSPORTHDR,
+                         CLAT_POS_ICMPERR_IPHDR, CLAT_POS_ICMPERR_TRANSPORTHDR,
+                         CLAT_POS_PAYLOAD, CLAT_POS_MAX };
 typedef struct iovec clat_packet[CLAT_POS_MAX];
 
 // Calculates the checksum over all the packet components starting from pos.
@@ -59,19 +47,10 @@
 void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol,
                      const struct iphdr *old_header);
 
-// Translate and send packets.
-void translate_packet(const struct tun_data *tunnel, struct tun_pi *tun_header, const char *packet,
-                      size_t packetsize);
-
 // Translate IPv4 and IPv6 packets.
 int ipv4_packet(clat_packet out, int pos, const char *packet, size_t len);
 int ipv6_packet(clat_packet out, int pos, const char *packet, size_t len);
 
-// Deal with fragmented packets.
-size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ,
-                              const struct iphdr *old_header);
-uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ);
-
 // Translate ICMP packets.
 int icmp_to_icmp6(clat_packet out, int pos, const struct icmphdr *icmp, uint32_t checksum,
                   const char *payload, size_t payload_size);
@@ -82,14 +61,12 @@
 int generic_packet(clat_packet out, int pos, const char *payload, size_t len);
 
 // Translate TCP and UDP packets.
-int tcp_packet(clat_packet out, int pos, const struct tcphdr *tcp,
-               uint32_t old_sum, uint32_t new_sum, size_t len);
-int udp_packet(clat_packet out, int pos, const struct udphdr *udp,
-               uint32_t old_sum, uint32_t new_sum, size_t len);
+int tcp_packet(clat_packet out, int pos, const struct tcphdr *tcp, uint32_t checksum, size_t len);
+int udp_packet(clat_packet out, int pos, const struct udphdr *udp, uint32_t checksum, size_t len);
 
 int tcp_translate(clat_packet out, int pos, const struct tcphdr *tcp, size_t header_size,
-                  uint32_t old_sum, uint32_t new_sum, const char *payload, size_t payload_size);
-int udp_translate(clat_packet out, int pos, const struct udphdr *udp,
-                  uint32_t old_sum, uint32_t new_sum, const char *payload, size_t payload_size);
+                  uint32_t checksum, const char *payload, size_t payload_size);
+int udp_translate(clat_packet out, int pos, const struct udphdr *udp, uint32_t checksum,
+                  const char *payload, size_t payload_size);
 
 #endif /* __TRANSLATE_H__ */