Treat the options as part of the TCP header.

This simplifies the code and makes UDP and TCP look the same. It
will also make it easier to implement nested translation in the
future because there will only be one iovec array entry for the
transport layer header, regardless of whether we are translating
UDP or TCP and regardless of the presence of options.

Also get rid of a couple of memcpy statements by pointing to the
original data instead.

Bug: 8276725
Change-Id: I6a702aefdf3a070eedfc6f7d3ebec21880ecc22b
diff --git a/ipv4.c b/ipv4.c
index 784f10c..243f9d0 100644
--- a/ipv4.c
+++ b/ipv4.c
@@ -64,40 +64,30 @@
  * ip     - ip header
  */
 void tcp_packet(int fd, const char *packet, size_t len, struct iphdr *ip) {
-  struct tcphdr tcp;
+  const struct tcphdr *tcp = (const struct tcphdr *) packet;
   const char *payload;
-  const char *options;
-  size_t payload_size, options_size;
+  size_t payload_size, header_size;
 
   if(len < sizeof(tcp)) {
     logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/(too small)");
     return;
   }
 
-  memcpy(&tcp, packet, sizeof(tcp));
-
-  if(tcp.doff < 5) {
-    logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set to less than 5: %x",tcp.doff);
+  if(tcp->doff < 5) {
+    logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set to less than 5: %x", tcp->doff);
     return;
   }
 
-  if((size_t)tcp.doff*4 > len) {
-    logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set too large: %x",tcp.doff);
+  if((size_t) tcp->doff*4 > len) {
+    logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set too large: %x", tcp->doff);
     return;
   }
 
-  if(tcp.doff > 5) {
-    options = packet + sizeof(tcp);
-    options_size = tcp.doff*4 - sizeof(tcp);
-  } else {
-    options = NULL;
-    options_size = 0;
-  }
+  header_size = tcp->doff * 4;
+  payload = packet + header_size;
+  payload_size = len - header_size;
 
-  payload = packet + tcp.doff*4;
-  payload_size = len - tcp.doff*4;
-
-  tcp_to_tcp6(fd,ip,&tcp,payload,payload_size,options,options_size);
+  tcp_to_tcp6(fd, ip, tcp, header_size, payload, payload_size);
 }
 
 /* function: udp_packet
diff --git a/ipv6.c b/ipv6.c
index 8fc36a4..61013f2 100644
--- a/ipv6.c
+++ b/ipv6.c
@@ -66,40 +66,30 @@
  * ip6    - ip6 header
  */
 void tcp6_packet(int fd, const char *packet, size_t len, struct ip6_hdr *ip6) {
-  struct tcphdr tcp;
+  const struct tcphdr *tcp = (const struct tcphdr *) packet;
   const char *payload;
-  const char *options;
-  size_t payload_size, options_size;
+  size_t payload_size, header_size;
 
   if(len < sizeof(tcp)) {
     logmsg_dbg(ANDROID_LOG_ERROR,"tcp6_packet/(too small)");
     return;
   }
 
-  memcpy(&tcp, packet, sizeof(tcp));
-
-  if(tcp.doff < 5) {
-    logmsg_dbg(ANDROID_LOG_ERROR,"tcp6_packet/tcp header length set to less than 5: %x",tcp.doff);
+  if(tcp->doff < 5) {
+    logmsg_dbg(ANDROID_LOG_ERROR,"tcp6_packet/tcp header length set to less than 5: %x", tcp->doff);
     return;
   }
 
-  if((size_t)tcp.doff*4 > len) {
-    logmsg_dbg(ANDROID_LOG_ERROR,"tcp6_packet/tcp header length set too large: %x",tcp.doff);
+  if((size_t) tcp->doff*4 > len) {
+    logmsg_dbg(ANDROID_LOG_ERROR,"tcp6_packet/tcp header length set too large: %x", tcp->doff);
     return;
   }
 
-  if(tcp.doff > 5) {
-    options = packet + sizeof(tcp);
-    options_size = tcp.doff*4 - sizeof(tcp);
-  } else {
-    options = NULL;
-    options_size = 0;
-  }
+  header_size = tcp->doff * 4;
+  payload = packet + header_size;
+  payload_size = len - header_size;
 
-  payload = packet + tcp.doff*4;
-  payload_size = len - tcp.doff*4;
-
-  tcp6_to_tcp(fd,ip6,&tcp,payload,payload_size,options,options_size);
+  tcp6_to_tcp(fd, ip6, tcp, header_size, payload, payload_size);
 }
 
 /* function: udp6_packet
diff --git a/translate.c b/translate.c
index 89a7e7b..936c781 100644
--- a/translate.c
+++ b/translate.c
@@ -303,44 +303,41 @@
  *     array position 0 - tun header
  *     array position 1 - ipv4/ipv6 header
  *     array position 2 - empty (will be tcp header)
- *     array position 3 - empty (will be tcp options or payload)
- *     array position 4 - empty (can be payload)
+ *     array position 3 - empty (will be payload)
  * checksum     - partial checksum covering ipv4/ipv6 header
- * options      - pointer to tcp option buffer
- * options_size - size of tcp option buffer
  *
  * TODO: mss rewrite
  * TODO: hosts without pmtu discovery - non DF packets will rely on fragmentation (unimplemented)
  */
-void tcp_translate(int fd, const struct tcphdr *tcp, const char *payload, size_t payload_size, struct iovec *io_targ, uint32_t checksum, const char *options, size_t options_size) {
-  struct tcphdr tcp_targ;
-  int targ_index = 2;
+void tcp_translate(int fd, const struct tcphdr *tcp, size_t header_size, const char *payload,
+                   size_t payload_size, struct iovec *io_targ, uint32_t checksum) {
+  union {
+    // Reserve space for the maximum size of the TCP header, including options. The TCP header
+    // length field is 4 bits long and counts 4-byte words, so it can be at most 60 bytes.
+    char buf[15 * 4];
+    struct tcphdr tcp;
+  } header;
 
-  memcpy(&tcp_targ, tcp, sizeof(tcp_targ));
-  tcp_targ.check = 0;
-
-  checksum = ip_checksum_add(checksum, &tcp_targ, sizeof(tcp_targ));
-  if(options) {
-    checksum = ip_checksum_add(checksum, options, options_size);
+  if (header_size > sizeof(header.buf)) {
+    // A TCP header cannot be more than 60 bytes long, so this can never happen unless there is a
+    // bug in the caller.
+    logmsg(ANDROID_LOG_ERROR, "tcp_translate: header too long %d > %d, truncating",
+           header_size, sizeof(header.buf));
+    header_size = sizeof(header.buf);
   }
+
+  memcpy(&header, tcp, header_size);
+
+  header.tcp.check = 0;
+  checksum = ip_checksum_add(checksum, &header, header_size);
   checksum = ip_checksum_add(checksum, payload, payload_size);
-  tcp_targ.check = ip_checksum_finish(checksum);
+  header.tcp.check = ip_checksum_finish(checksum);
 
-  io_targ[targ_index].iov_base = &tcp_targ;
-  io_targ[targ_index].iov_len = sizeof(tcp_targ);
-  targ_index++;
+  io_targ[2].iov_base = &header;
+  io_targ[2].iov_len = header_size;
 
-  if(options) {
-    io_targ[targ_index].iov_base = (char *)options;
-    io_targ[targ_index].iov_len = options_size;
-    targ_index++;
-  }
-
-  io_targ[targ_index].iov_base = (char *)payload;
-  io_targ[targ_index].iov_len = payload_size;
-  targ_index++;
-
-  writev(fd, io_targ, targ_index);
+  io_targ[3].iov_base = (char *)payload;
+  io_targ[3].iov_len = payload_size;
 }
 
 /* function: tcp_to_tcp6
@@ -348,12 +345,12 @@
  * fd           - tun interface fd
  * ip           - source packet ipv4 header
  * tcp          - source packet tcp header
+ * header_size  - size of tcp header including options
  * payload      - tcp payload
  * payload_size - size of payload
- * options      - tcp options
- * options_size - size of options
  */
-void tcp_to_tcp6(int fd,const struct iphdr *ip, const struct tcphdr *tcp, const char *payload, size_t payload_size, const char *options, size_t options_size) {
+void tcp_to_tcp6(int fd, const struct iphdr *ip, const struct tcphdr *tcp, size_t header_size,
+                 const char *payload, size_t payload_size) {
   struct ip6_hdr ip6_targ;
   struct iovec io_targ[5];
   struct tun_pi tun_header;
@@ -361,16 +358,16 @@
 
   fill_tun_header(&tun_header,ETH_P_IPV6);
 
-  fill_ip6_header(&ip6_targ,payload_size+options_size+sizeof(struct tcphdr),IPPROTO_TCP,ip);
+  fill_ip6_header(&ip6_targ, header_size + payload_size, IPPROTO_TCP, ip);
 
-  checksum = ipv6_pseudo_header_checksum(0, &ip6_targ, sizeof(*tcp) + options_size + payload_size);
+  checksum = ipv6_pseudo_header_checksum(0, &ip6_targ, header_size + payload_size);
 
   io_targ[0].iov_base = &tun_header;
   io_targ[0].iov_len = sizeof(tun_header);
   io_targ[1].iov_base = &ip6_targ;
   io_targ[1].iov_len = sizeof(ip6_targ);
 
-  tcp_translate(fd,tcp,payload,payload_size,io_targ,checksum,options,options_size);
+  tcp_translate(fd, tcp, header_size, payload, payload_size, io_targ, checksum);
 }
 
 /* function: tcp6_to_tcp
@@ -378,12 +375,12 @@
  * fd           - tun interface fd
  * ip6          - source packet ipv6 header
  * tcp          - source packet tcp header
+ * header_size  - size of tcp header including options
  * payload      - tcp payload
  * payload_size - size of payload
- * options      - tcp options
- * options_size - size of options
  */
-void tcp6_to_tcp(int fd,const struct ip6_hdr *ip6, const struct tcphdr *tcp, const char *payload, size_t payload_size, const char *options, size_t options_size) {
+void tcp6_to_tcp(int fd, const struct ip6_hdr *ip6, const struct tcphdr *tcp, size_t header_size,
+                 const char *payload, size_t payload_size) {
   struct iphdr ip_targ;
   struct iovec io_targ[5];
   struct tun_pi tun_header;
@@ -391,14 +388,14 @@
 
   fill_tun_header(&tun_header,ETH_P_IP);
 
-  fill_ip_header(&ip_targ,payload_size+options_size+sizeof(struct tcphdr),IPPROTO_TCP,ip6);
+  fill_ip_header(&ip_targ, header_size + payload_size, IPPROTO_TCP, ip6);
 
-  checksum = ipv4_pseudo_header_checksum(0, &ip_targ, sizeof(*tcp) + payload_size + options_size);
+  checksum = ipv4_pseudo_header_checksum(0, &ip_targ, header_size + payload_size);
 
   io_targ[0].iov_base = &tun_header;
   io_targ[0].iov_len = sizeof(tun_header);
   io_targ[1].iov_base = &ip_targ;
   io_targ[1].iov_len = sizeof(ip_targ);
 
-  tcp_translate(fd,tcp,payload,payload_size,io_targ,checksum,options,options_size);
+  tcp_translate(fd, tcp, header_size, payload, payload_size, io_targ, checksum);
 }
diff --git a/translate.h b/translate.h
index 6fc3c79..641768e 100644
--- a/translate.h
+++ b/translate.h
@@ -24,7 +24,9 @@
 void udp_to_udp6(int fd, const struct iphdr *ip, const struct udphdr *udp, const char *payload, size_t payload_size);
 void udp6_to_udp(int fd, const struct ip6_hdr *ip6, const struct udphdr *udp, const char *payload, size_t payload_size);
 
-void tcp_to_tcp6(int fd,const struct iphdr *ip, const struct tcphdr *tcp, const char *payload, size_t payload_size, const char *options, size_t options_size);
-void tcp6_to_tcp(int fd,const struct ip6_hdr *ip6, const struct tcphdr *tcp, const char *payload, size_t payload_size, const char *options, size_t options_size);
+void tcp_to_tcp6(int fd,const struct iphdr *ip, const struct tcphdr *tcp, size_t header_size,
+                 const char *payload, size_t payload_size);
+void tcp6_to_tcp(int fd,const struct ip6_hdr *ip6, const struct tcphdr *tcp, size_t header_size,
+                 const char *payload, size_t payload_size);
 
 #endif /* __TRANSLATE_H__ */