/* function: tcp_to_tcp6
 * translate ipv4 tcp to ipv6 tcp
 * fd           - tun interface fd
 * ip           - source packet ipv4 header
 * tcp          - source packet tcp header
 * 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) {
  struct ip6_hdr ip6_targ;
  struct iovec io_targ[5];
  struct tun_pi tun_header;
  uint32_t checksum;

  fill_tun_header(&tun_header,ETH_P_IPV6);

  fill_ip6_header(&ip6_targ,payload_size+options_size+sizeof(struct tcphdr),IPPROTO_TCP,ip);

  checksum = ipv6_pseudo_header_checksum(0, &ip6_targ);

  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);
}
/* function: udp_to_udp6
 * translate ipv4 udp to ipv6 udp
 * fd           - tun interface fd
 * ip           - source packet ipv4 header
 * udp          - source packet udp header
 * payload      - udp payload
 * payload_size - size of payload
 */
void udp_to_udp6(int fd, const struct iphdr *ip, const struct udphdr *udp, const char *payload, size_t payload_size) {
  struct ip6_hdr ip6_targ;
  struct iovec io_targ[4];
  struct tun_pi tun_header;
  uint32_t checksum;

  fill_tun_header(&tun_header,ETH_P_IPV6);

  fill_ip6_header(&ip6_targ,payload_size + sizeof(struct udphdr),IPPROTO_UDP,ip);

  checksum = ipv6_pseudo_header_checksum(0, &ip6_targ);

  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);

  udp_translate(fd,udp,payload,payload_size,io_targ,checksum);
}
/* function: icmp_to_icmp6
 * translate ipv4 icmp to ipv6 icmp
 * fd           - tun interface fd
 * ip           - source packet ipv4 header
 * icmp         - source packet icmp header
 * payload      - icmp payload
 * payload_size - size of payload
 */
void icmp_to_icmp6(int fd, const struct iphdr *ip, const struct icmphdr *icmp, const char *payload, size_t payload_size) {
  struct ip6_hdr ip6_targ;
  struct icmp6_hdr icmp6_targ;
  struct iovec io_targ[4];
  struct tun_pi tun_header;
  uint32_t checksum_temp;

  if(icmp->type != ICMP_ECHO && icmp->type != ICMP_ECHOREPLY) {
#if CLAT_DEBUG
    logmsg(ANDROID_LOG_WARN,"icmp_to_icmp6/unhandled icmp type: 0x%x",icmp->type);
#endif
    return;
  }

  fill_tun_header(&tun_header,ETH_P_IPV6);

  fill_ip6_header(&ip6_targ,payload_size + sizeof(icmp6_targ),IPPROTO_ICMPV6,ip);

  memset(&icmp6_targ, 0, sizeof(icmp6_targ));
  icmp6_targ.icmp6_type = (icmp->type == ICMP_ECHO) ? ICMP6_ECHO_REQUEST : ICMP6_ECHO_REPLY;
  icmp6_targ.icmp6_code = 0;
  icmp6_targ.icmp6_cksum = 0;
  icmp6_targ.icmp6_id = icmp->un.echo.id;
  icmp6_targ.icmp6_seq = icmp->un.echo.sequence;

  checksum_temp = ipv6_pseudo_header_checksum(0,&ip6_targ);
  checksum_temp = ip_checksum_add(checksum_temp,&icmp6_targ,sizeof(icmp6_targ));
  checksum_temp = ip_checksum_add(checksum_temp,payload,payload_size);
  icmp6_targ.icmp6_cksum = ip_checksum_finish(checksum_temp);

  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);
  io_targ[2].iov_base = &icmp6_targ;
  io_targ[2].iov_len = sizeof(icmp6_targ);
  io_targ[3].iov_base = (char *)payload;
  io_targ[3].iov_len = payload_size;

  writev(fd, io_targ, 4);
}
/* function: ipv4_packet
 * translates an ipv4 packet
 * out    - output packet
 * packet - packet data
 * len    - size of packet
 * returns: the highest position in the output clat_packet that's filled in
 */
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;
  uint16_t frag_flags;
  uint8_t nxthdr;
  const char *next_header;
  size_t len_left;
  uint32_t checksum;
  int iov_len;

  if(len < sizeof(struct iphdr)) {
    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/too short for an ip header");
    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;
  }

  if((size_t) header->ihl * 4 > len) { // ip header length larger than entire packet
    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set too large: %x", header->ihl);
    return 0;
  }

  if(header->version != 4) {
    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header version not 4: %x", header->version);
    return 0;
  }

  /* rfc6145 - If any IPv4 options are present in the IPv4 packet, they MUST be
   * ignored and the packet translated normally; there is no attempt to
   * translate the options.
   */

  next_header = packet + header->ihl*4;
  len_left = len - header->ihl * 4;

  nxthdr = header->protocol;
  if (nxthdr == IPPROTO_ICMP) {
    // ICMP and ICMPv6 have different protocol numbers.
    nxthdr = IPPROTO_ICMPV6;
  }

  /* Fill in the IPv6 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
   * know it yet.
   */
  fill_ip6_header(ip6_targ, 0, nxthdr, header);
  out[pos].iov_len = sizeof(struct ip6_hdr);

  // Calculate the pseudo-header checksum.
  checksum = ipv6_pseudo_header_checksum(0, ip6_targ, 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 + 1, (const struct tcphdr *) next_header, checksum, len_left);
  } else if (nxthdr == IPPROTO_UDP) {
    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 + 1, next_header, len_left);
  } else {
#if CLAT_DEBUG
    logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/unknown protocol: %x",header->protocol);
    logcat_hexdump("ipv4/protocol", packet, len);
#endif
    return 0;
  }

  // Set the length.
  ip6_targ->ip6_plen = htons(packet_length(out, pos));
  return iov_len;
}