diff options
-rw-r--r-- | clatd.c | 52 | ||||
-rw-r--r-- | clatd.h | 1 | ||||
-rw-r--r-- | dump.c | 1 | ||||
-rw-r--r-- | ipv4.c | 173 | ||||
-rw-r--r-- | ipv4.h | 4 | ||||
-rw-r--r-- | ipv6.c | 167 | ||||
-rw-r--r-- | ipv6.h | 4 | ||||
-rw-r--r-- | translate.c | 407 | ||||
-rw-r--r-- | translate.h | 41 |
9 files changed, 383 insertions, 467 deletions
@@ -39,6 +39,7 @@ #include <linux/icmp.h> #include <sys/capability.h> +#include <sys/uio.h> #include <linux/prctl.h> #include <linux/if.h> #include <linux/if_tun.h> @@ -48,6 +49,7 @@ #include "ipv4.h" #include "ipv6.h" +#include "translate.h" #include "clatd.h" #include "config.h" #include "logging.h" @@ -306,20 +308,43 @@ void configure_interface(const char *uplink_interface, const char *plat_prefix, * 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) { - tun_header->proto = ntohs(tun_header->proto); +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]; + + // 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. + { 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(tun_header->proto == ETH_P_IP) { - ip_packet(tunnel->fd6,packet,packetsize); - } else if(tun_header->proto == ETH_P_IPV6) { - ipv6_packet(tunnel->fd4,packet,packetsize); + if(ntohs(tun_header->proto) == ETH_P_IP) { + fd = tunnel->fd6; + fill_tun_header(&tun_targ, ETH_P_IPV6); + iov_len = ipv4_packet(out, 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, 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 @@ -351,9 +376,7 @@ void read_packet(int active_fd, const struct tun_data *tunnel) { return; } - memcpy(&tun_header, packet, header_size); - - packet_handler(tunnel, &tun_header, packet+header_size, readlen-header_size); + packet_handler(tunnel, (struct tun_pi *) packet, packet + header_size, readlen - header_size); } } @@ -434,29 +457,30 @@ int main(int argc, char **argv) { } if(uplink_interface == NULL) { - logmsg(ANDROID_LOG_FATAL,"clatd called without an interface"); + logmsg(ANDROID_LOG_FATAL, "clatd called without an interface"); printf("I need an interface\n"); exit(1); } - logmsg(ANDROID_LOG_INFO,"Starting clat on %s", uplink_interface); + 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_open failed: %s",strerror(errno)); + logmsg(ANDROID_LOG_FATAL, "tun_open failed: %s", strerror(errno)); exit(1); } tunnel.fd4 = tun_open(); if(tunnel.fd4 < 0) { - logmsg(ANDROID_LOG_FATAL,"tun_open4 failed: %s",strerror(errno)); + 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)); + logmsg(ANDROID_LOG_FATAL,"open /proc/sys/net/ipv6/conf/all/forwarding failed: %s", + strerror(errno)); exit(1); } @@ -22,6 +22,7 @@ #define MAXMTU 1500 #define PACKETLEN (MAXMTU+sizeof(struct tun_pi)) +#define CLATD_VERSION "1.1" // how frequently (in seconds) to poll for an address change while traffic is passing #define INTERFACE_POLL_FREQUENCY 30 @@ -17,6 +17,7 @@ */ #include <stdio.h> #include <stdint.h> +#include <stdlib.h> #include <arpa/inet.h> #include <netinet/in.h> @@ -31,127 +31,73 @@ #include "ipv4.h" #include "logging.h" #include "debug.h" +#include "dump.h" /* function: icmp_packet - * takes an icmp packet and sets it up for translation - * fd - tun interface fd - * packet - ip payload - * len - size of ip payload - * ip - ip header + * translates an icmp packet + * out - output packet + * icmp - pointer to icmp header in packet + * checksum - pseudo-header checksum + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in */ -void icmp_packet(int fd, const char *packet, size_t len, struct iphdr *ip) { - struct icmphdr icmp; +int icmp_packet(clat_packet out, int pos, const struct icmphdr *icmp, uint32_t checksum, + size_t len) { const char *payload; size_t payload_size; - if(len < sizeof(icmp)) { - logmsg_dbg(ANDROID_LOG_ERROR,"icmp_packet/(too small)"); - return; + if(len < sizeof(struct icmphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "icmp_packet/(too small)"); + return 0; } - memcpy(&icmp, packet, sizeof(icmp)); - payload = packet + sizeof(icmp); - payload_size = len - sizeof(icmp); + payload = (const char *) (icmp + 1); + payload_size = len - sizeof(struct icmphdr); - icmp_to_icmp6(fd,ip,&icmp,payload,payload_size); + return icmp_to_icmp6(out, pos, icmp, checksum, payload, payload_size); } -/* function: tcp_packet - * takes a tcp packet and sets it up for translation - * fd - tun interface fd - * packet - ip payload - * len - size of ip payload - * ip - ip header - */ -void tcp_packet(int fd, const char *packet, size_t len, struct iphdr *ip) { - const struct tcphdr *tcp = (const struct tcphdr *) packet; - const char *payload; - size_t payload_size, header_size; - - if(len < sizeof(tcp)) { - logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/(too small)"); - return; - } - - 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); - return; - } - - header_size = tcp->doff * 4; - payload = packet + header_size; - payload_size = len - header_size; - - tcp_to_tcp6(fd, ip, tcp, header_size, payload, payload_size); -} - -/* function: udp_packet - * takes a udp packet and sets it up for translation - * fd - tun interface fd - * packet - ip payload - * len - size of ip payload - * ip - ip header - */ -void udp_packet(int fd, const char *packet, size_t len, const struct iphdr *ip) { - struct udphdr udp; - const char *payload; - size_t payload_size; - - if(len < sizeof(udp)) { - logmsg_dbg(ANDROID_LOG_ERROR,"udp_packet/(too small)"); - return; - } - - memcpy(&udp, packet, sizeof(udp)); - payload = packet + sizeof(udp); - payload_size = len - sizeof(udp); - - udp_to_udp6(fd,ip,&udp,payload,payload_size); -} - -/* function: ip_packet - * takes an ip packet and hands it off to the layer 4 protocol function - * fd - tun interface fd +/* 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 */ -void ip_packet(int fd, const char *packet, size_t len) { - struct iphdr header; +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(header)) { - logmsg_dbg(ANDROID_LOG_ERROR,"ip_packet/too short for an ip header"); - return; + if(len < sizeof(struct iphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/too short for an ip header"); + return 0; } - memcpy(&header, packet, sizeof(header)); - - frag_flags = ntohs(header.frag_off); + 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; + 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; + 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; + 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; + 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 @@ -159,19 +105,40 @@ void ip_packet(int fd, const char *packet, size_t len) { * translate the options. */ - next_header = packet + header.ihl*4; - len_left = len - header.ihl*4; + 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; + } - if(header.protocol == IPPROTO_ICMP) { - icmp_packet(fd,next_header,len_left,&header); - } else if(header.protocol == IPPROTO_TCP) { - tcp_packet(fd,next_header,len_left,&header); - } else if(header.protocol == IPPROTO_UDP) { - udp_packet(fd,next_header,len_left,&header); + /* 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 CLAT_DEBUG - logmsg_dbg(ANDROID_LOG_ERROR,"ip_packet/unknown protocol: %x",header.protocol); + 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(payload_length(out, pos)); + return iov_len; } @@ -18,6 +18,8 @@ #ifndef __IPV4_H__ #define __IPV4_H__ -void ip_packet(int fd, const char *packet, size_t len); +#include "translate.h" + +int ipv4_packet(clat_packet out, int pos, const char *packet, size_t len); #endif /* __IPV4_H__ */ @@ -25,6 +25,7 @@ #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <linux/icmp.h> +#include <arpa/inet.h> #include "translate.h" #include "checksum.h" @@ -36,84 +37,26 @@ /* function: icmp6_packet * takes an icmp6 packet and sets it up for translation - * fd - tun interface fd - * packet - ip payload - * len - size of ip payload - * ip6 - ip6 header + * out - output packet + * icmp6 - pointer to icmp6 header in packet + * checksum - pseudo-header checksum (unused) + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in */ -void icmp6_packet(int fd, const char *packet, size_t len, struct ip6_hdr *ip6) { - struct icmp6_hdr icmp6; +int icmp6_packet(clat_packet out, int pos, const struct icmp6_hdr *icmp6, uint32_t checksum, + size_t len) { const char *payload; size_t payload_size; - if(len < sizeof(icmp6)) { - logmsg_dbg(ANDROID_LOG_ERROR,"icmp6_packet/(too small)"); - return; + if(len < sizeof(struct icmp6_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "icmp6_packet/(too small)"); + return 0; } - memcpy(&icmp6, packet, sizeof(icmp6)); - payload = packet + sizeof(icmp6); - payload_size = len - sizeof(icmp6); + payload = (const char *) (icmp6 + 1); + payload_size = len - sizeof(struct icmp6_hdr); - icmp6_to_icmp(fd, ip6, &icmp6, payload, payload_size); -} - -/* function: tcp6_packet - * takes a tcp packet and sets it up for translation - * fd - tun interface fd - * packet - ip payload - * len - size of ip payload - * ip6 - ip6 header - */ -void tcp6_packet(int fd, const char *packet, size_t len, struct ip6_hdr *ip6) { - const struct tcphdr *tcp = (const struct tcphdr *) packet; - const char *payload; - size_t payload_size, header_size; - - if(len < sizeof(tcp)) { - logmsg_dbg(ANDROID_LOG_ERROR,"tcp6_packet/(too small)"); - return; - } - - 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); - return; - } - - header_size = tcp->doff * 4; - payload = packet + header_size; - payload_size = len - header_size; - - tcp6_to_tcp(fd, ip6, tcp, header_size, payload, payload_size); -} - -/* function: udp6_packet - * takes a udp packet and sets it up for translation - * fd - tun interface fd - * packet - ip payload - * len - size of ip payload - * ip6 - ip6 header - */ -void udp6_packet(int fd, const char *packet, size_t len, struct ip6_hdr *ip6) { - struct udphdr udp; - const char *payload; - size_t payload_size; - - if(len < sizeof(udp)) { - logmsg_dbg(ANDROID_LOG_ERROR,"udp6_packet/(too small)"); - return; - } - - memcpy(&udp, packet, sizeof(udp)); - payload = packet + sizeof(udp); - payload_size = len - sizeof(udp); - - udp6_to_udp(fd,ip6,&udp,payload,payload_size); + return icmp6_to_icmp(out, pos, icmp6, checksum, payload, payload_size); } /* function: log_bad_address @@ -132,53 +75,81 @@ void log_bad_address(const char *fmt, const struct in6_addr *badaddr) { /* function: ipv6_packet * takes an ipv6 packet and hands it off to the layer 4 protocol function - * fd - tun interface fd + * out - output packet * packet - packet data * len - size of packet + * returns: the highest position in the output clat_packet that's filled in */ -void ipv6_packet(int fd, const char *packet, size_t len) { - struct ip6_hdr header; +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; + uint8_t protocol; const char *next_header; size_t len_left; + uint32_t checksum; + int iov_len; int i; - if(len < sizeof(header)) { - logmsg_dbg(ANDROID_LOG_ERROR,"ipv6_packet/too short for an ip6 header"); - return; + if(len < sizeof(struct ip6_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for an ip6 header"); + return 0; } - memcpy(&header, packet, sizeof(header)); - - next_header = packet + sizeof(header); - len_left = len - sizeof(header); - - if(IN6_IS_ADDR_MULTICAST(&header.ip6_dst)) { - log_bad_address("ipv6_packet/multicast %s", &header.ip6_dst); - return; // silently ignore + if(IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + log_bad_address("ipv6_packet/multicast %s", &ip6->ip6_dst); + return 0; // silently ignore } for(i = 0; i < 3; i++) { - if(header.ip6_src.s6_addr32[i] != Global_Clatd_Config.plat_subnet.s6_addr32[i]) { - log_bad_address("ipv6_packet/wrong source address: %s", &header.ip6_src); - return; + if(ip6->ip6_src.s6_addr32[i] != Global_Clatd_Config.plat_subnet.s6_addr32[i]) { + log_bad_address("ipv6_packet/wrong source address: %s", &ip6->ip6_src); + return 0; } } - if(!IN6_ARE_ADDR_EQUAL(&header.ip6_dst, &Global_Clatd_Config.ipv6_local_subnet)) { - log_bad_address("ipv6_packet/wrong destination address: %s", &header.ip6_dst); - return; + if(!IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &Global_Clatd_Config.ipv6_local_subnet)) { + log_bad_address("ipv6_packet/wrong destination address: %s", &ip6->ip6_dst); + return 0; + } + + next_header = packet + sizeof(struct ip6_hdr); + 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 + * know it yet. + */ + fill_ip_header(ip_targ, 0, protocol, ip6); + out[pos].iov_len = sizeof(struct iphdr); + + // Calculate the pseudo-header checksum. + checksum = ipv4_pseudo_header_checksum(0, ip_targ, len_left); + // does not support IPv6 extension headers, this will drop any packet with them - if(header.ip6_nxt == IPPROTO_ICMPV6) { - icmp6_packet(fd,next_header,len_left,&header); - } else if(header.ip6_nxt == IPPROTO_TCP) { - tcp6_packet(fd,next_header,len_left,&header); - } else if(header.ip6_nxt == IPPROTO_UDP) { - udp6_packet(fd,next_header,len_left,&header); + if(protocol == IPPROTO_ICMP) { + iov_len = icmp6_packet(out, pos + 1, (const struct icmp6_hdr *) next_header, checksum, + 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(ip6->ip6_nxt == IPPROTO_UDP) { + iov_len = udp_packet(out, pos + 1, (const struct udphdr *) next_header, checksum, + len_left); } else { #if CLAT_DEBUG - logmsg(ANDROID_LOG_ERROR,"ipv6_packet/unknown next header type: %x",header.ip6_nxt); + logmsg(ANDROID_LOG_ERROR, "ipv6_packet/unknown next header type: %x",ip6->ip6_nxt); logcat_hexdump("ipv6/nxthdr", packet, len); #endif + return 0; } + + // Set the length and calculate the checksum. + ip_targ->tot_len = htons(ntohs(ip_targ->tot_len) + payload_length(out, pos)); + ip_targ->check = ip_checksum(ip_targ, sizeof(struct iphdr)); + return iov_len; } @@ -18,6 +18,8 @@ #ifndef __IPV6_H__ #define __IPV6_H__ -void ipv6_packet(int fd, const char *packet, size_t len); +#include "translate.h" + +int ipv6_packet(clat_packet out, int pos, const char *packet, size_t len); #endif /* __IPV6_H__ */ diff --git a/translate.c b/translate.c index 936c781..c0bd59a 100644 --- a/translate.c +++ b/translate.c @@ -16,7 +16,6 @@ * translate.c - CLAT functions / partial implementation of rfc6145 */ #include <string.h> -#include <sys/uio.h> #include <netinet/in.h> #include <netinet/ip.h> @@ -27,12 +26,28 @@ #include <netinet/icmp6.h> #include <linux/icmp.h> +#include "translate.h" #include "checksum.h" #include "clatd.h" #include "config.h" #include "logging.h" #include "debug.h" +/* function: payload_length + * calculates the total length of the packet components after pos + * packet - packet to calculate the length of + * pos - position to start counting from + * returns: the total length of the packet components after pos + */ +uint16_t payload_length(clat_packet packet, int pos) { + size_t len = 0; + int i; + for (i = pos + 1; i < POS_MAX; i++) { + len += packet[i].iov_len; + } + return len; +} + /* function: fill_tun_header * fill in the header for the tun fd * tun_header - tunnel header, already allocated @@ -46,6 +61,7 @@ void fill_tun_header(struct tun_pi *tun_header, uint16_t proto) { /* function: ipv6_src_to_ipv4_src * return the corresponding ipv4 address for the given ipv6 address * sourceaddr - ipv6 source address + * returns: the IPv4 address */ uint32_t ipv6_src_to_ipv4_src(const struct in6_addr *sourceaddr) { // assumes a /96 plat subnet @@ -53,29 +69,28 @@ uint32_t ipv6_src_to_ipv4_src(const struct in6_addr *sourceaddr) { } /* function: fill_ip_header - * generating an ipv4 header from an ipv6 header (called by the layer 4 protocol-specific functions) - * ip_targ - (ipv4) target packet header, source addr: original ipv4 addr, dest addr: local subnet addr + * generate an ipv4 header from an ipv6 header + * ip_targ - (ipv4) target packet header, source: original ipv4 addr, dest: local subnet addr * payload_len - length of other data inside packet * protocol - protocol number (tcp, udp, etc) - * old_header - (ipv6) source packet header, source addr: nat64 prefix, dest addr: local subnet prefix + * old_header - (ipv6) source packet header, source: nat64 prefix, dest: local subnet prefix */ -void fill_ip_header(struct iphdr *ip_targ, uint16_t payload_len, uint8_t protocol, const struct ip6_hdr *old_header) { - memset(ip_targ, 0, sizeof(ip_targ)); - - ip_targ->ihl = 5; - ip_targ->version = 4; - ip_targ->tos = 0; - ip_targ->tot_len = htons(sizeof(struct iphdr) + payload_len); - ip_targ->id = 0; - ip_targ->frag_off = htons(IP_DF); - ip_targ->ttl = old_header->ip6_hlim; - ip_targ->protocol = protocol; - ip_targ->check = 0; - - ip_targ->saddr = ipv6_src_to_ipv4_src(&old_header->ip6_src); - ip_targ->daddr = Global_Clatd_Config.ipv4_local_subnet.s_addr; - - ip_targ->check = ip_checksum(ip_targ, sizeof(struct iphdr)); +void fill_ip_header(struct iphdr *ip, uint16_t payload_len, uint8_t protocol, + const struct ip6_hdr *old_header) { + memset(ip, 0, sizeof(struct iphdr)); + + ip->ihl = 5; + ip->version = 4; + ip->tos = 0; + ip->tot_len = htons(sizeof(struct iphdr) + payload_len); + ip->id = 0; + ip->frag_off = htons(IP_DF); + ip->ttl = old_header->ip6_hlim; + ip->protocol = protocol; + ip->check = 0; + + ip->saddr = ipv6_src_to_ipv4_src(&old_header->ip6_src); + ip->daddr = Global_Clatd_Config.ipv4_local_subnet.s_addr; } /* function: ipv4_dst_to_ipv6_dst @@ -93,13 +108,14 @@ struct in6_addr ipv4_dst_to_ipv6_dst(uint32_t destination) { } /* function: fill_ip6_header - * generating an ipv6 header from an ipv4 header (called by the layer 4 protocol-specific functions) - * ip6 - (ipv6) target packet header, source addr: local subnet prefix, dest addr: nat64 prefix + * generate an ipv6 header from an ipv4 header + * ip6 - (ipv6) target packet header, source: local subnet prefix, dest: nat64 prefix * payload_len - length of other data inside packet * protocol - protocol number (tcp, udp, etc) - * old_header - (ipv4) source packet header, source addr: local subnet addr, dest addr: internet's ipv4 addr + * old_header - (ipv4) source packet header, source: local subnet addr, dest: internet's ipv4 addr */ -void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol, const struct iphdr *old_header) { +void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol, + const struct iphdr *old_header) { memset(ip6, 0, sizeof(struct ip6_hdr)); ip6->ip6_vfc = 6 << 4; @@ -113,289 +129,196 @@ void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol /* function: icmp_to_icmp6 * translate ipv4 icmp to ipv6 icmp (only currently supports echo/echo reply) - * fd - tun interface fd - * ip - source packet ipv4 header + * out - output packet * icmp - source packet icmp header + * checksum - pseudo-header checksum * payload - icmp payload * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in */ -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; +int icmp_to_icmp6(clat_packet out, int pos, const struct icmphdr *icmp, uint32_t checksum, + const char *payload, size_t payload_size) { + struct icmp6_hdr *icmp6_targ = out[pos].iov_base; uint32_t checksum_temp; if((icmp->type != ICMP_ECHO) && (icmp->type != ICMP_ECHOREPLY)) { - logmsg_dbg(ANDROID_LOG_WARN,"icmp_to_icmp6/unhandled icmp type: 0x%x",icmp->type); - return; + logmsg_dbg(ANDROID_LOG_WARN,"icmp_to_icmp6/unhandled icmp type: 0x%x", icmp->type); + return 0; } - fill_tun_header(&tun_header,ETH_P_IPV6); + memset(icmp6_targ, 0, sizeof(struct icmp6_hdr)); + icmp6_targ->icmp6_type = (icmp->type == ICMP_ECHO) ? ICMP6_ECHO_REQUEST : ICMP6_ECHO_REPLY; + icmp6_targ->icmp6_code = 0; + icmp6_targ->icmp6_id = icmp->un.echo.id; + icmp6_targ->icmp6_seq = icmp->un.echo.sequence; - 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, sizeof(icmp6_targ) + payload_size); - 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); + icmp6_targ->icmp6_cksum = 0; + checksum = ip_checksum_add(checksum, icmp6_targ, sizeof(struct icmp6_hdr)); + checksum = ip_checksum_add(checksum, payload, payload_size); + icmp6_targ->icmp6_cksum = ip_checksum_finish(checksum); - 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; + out[pos].iov_len = sizeof(struct icmp6_hdr); + out[POS_PAYLOAD].iov_base = (char *) payload; + out[POS_PAYLOAD].iov_len = payload_size; - writev(fd, io_targ, 4); + return POS_PAYLOAD + 1; } /* function: icmp6_to_icmp * translate ipv6 icmp to ipv4 icmp (only currently supports echo/echo reply) - * fd - tun interface fd - * ip6 - source packet ipv6 header + * out - output packet * icmp6 - source packet icmp6 header + * checksum - pseudo-header checksum (unused) * payload - icmp6 payload * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in */ -void icmp6_to_icmp(int fd, const struct ip6_hdr *ip6, const struct icmp6_hdr *icmp6, const char *payload, size_t payload_size) { - struct iphdr ip_targ; - struct icmphdr icmp_targ; - struct iovec io_targ[4]; - struct tun_pi tun_header; - uint32_t temp_icmp_checksum; +int icmp6_to_icmp(clat_packet out, int pos, const struct icmp6_hdr *icmp6, uint32_t checksum, + const char *payload, size_t payload_size) { + struct icmphdr *icmp_targ = out[pos].iov_base; if((icmp6->icmp6_type != ICMP6_ECHO_REQUEST) && (icmp6->icmp6_type != ICMP6_ECHO_REPLY)) { logmsg_dbg(ANDROID_LOG_WARN,"icmp6_to_icmp/unhandled icmp6 type: 0x%x",icmp6->icmp6_type); - return; + return 0; } - memset(&icmp_targ, 0, sizeof(icmp_targ)); + memset(icmp_targ, 0, sizeof(struct icmphdr)); - fill_tun_header(&tun_header,ETH_P_IP); - fill_ip_header(&ip_targ,sizeof(icmp_targ) + payload_size, IPPROTO_ICMP, ip6); + icmp_targ->type = (icmp6->icmp6_type == ICMP6_ECHO_REQUEST) ? ICMP_ECHO : ICMP_ECHOREPLY; + icmp_targ->code = 0x0; + icmp_targ->un.echo.id = icmp6->icmp6_id; + icmp_targ->un.echo.sequence = icmp6->icmp6_seq; - icmp_targ.type = (icmp6->icmp6_type == ICMP6_ECHO_REQUEST) ? ICMP_ECHO : ICMP_ECHOREPLY; - icmp_targ.code = 0x0; - icmp_targ.checksum = 0; - icmp_targ.un.echo.id = icmp6->icmp6_id; - icmp_targ.un.echo.sequence = icmp6->icmp6_seq; + icmp_targ->checksum = 0; + checksum = ip_checksum_add(0, icmp_targ, sizeof(struct icmphdr)); + checksum = ip_checksum_add(checksum, (void *)payload, payload_size); + icmp_targ->checksum = ip_checksum_finish(checksum); - temp_icmp_checksum = ip_checksum_add(0, &icmp_targ, sizeof(icmp_targ)); - temp_icmp_checksum = ip_checksum_add(temp_icmp_checksum, (void *)payload, payload_size); - icmp_targ.checksum = ip_checksum_finish(temp_icmp_checksum); + out[pos].iov_len = sizeof(struct icmphdr); + out[POS_PAYLOAD].iov_base = (char *) payload; + out[POS_PAYLOAD].iov_len = 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); - io_targ[2].iov_base = &icmp_targ; - io_targ[2].iov_len = sizeof(icmp_targ); - io_targ[3].iov_base = (char *)payload; - io_targ[3].iov_len = payload_size; - - writev(fd, io_targ, 4); + return POS_PAYLOAD + 1; } -/* function: udp_translate - * common between ipv4/ipv6 - setup checksum and send udp packet - * fd - tun interface fd - * udp - source packet udp header - * payload - udp payload - * payload_size - size of payload - * io_targ - iovec with tun and ipv4/ipv6 header (see below) - * array position 0 - tun header - * array position 1 - ipv4/ipv6 header - * array position 2 - empty (will be udp header) - * array position 3 - empty (will be payload) - * checksum - partial checksum covering ipv4/ipv6 header +/* function: udp_packet + * takes a udp packet and sets it up for translation + * out - output packet + * udp - pointer to udp header in packet + * checksum - pseudo-header checksum + * len - size of ip payload */ -void udp_translate(int fd, const struct udphdr *udp, const char *payload, size_t payload_size, struct iovec *io_targ, uint32_t checksum) { - struct udphdr udp_targ; - - memcpy(&udp_targ, udp, sizeof(udp_targ)); - udp_targ.check = 0; // reset checksum, to be calculated +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; - checksum = ip_checksum_add(checksum, &udp_targ, sizeof(struct udphdr)); - checksum = ip_checksum_add(checksum, payload, payload_size); - udp_targ.check = ip_checksum_finish(checksum); + if(len < sizeof(struct udphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR,"udp_packet/(too small)"); + return 0; + } - io_targ[2].iov_base = &udp_targ; - io_targ[2].iov_len = sizeof(udp_targ); - io_targ[3].iov_base = (char *)payload; - io_targ[3].iov_len = payload_size; + payload = (const char *) (udp + 1); + payload_size = len - sizeof(struct udphdr); - writev(fd, io_targ, 4); + return udp_translate(out, pos, udp, checksum, payload, payload_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 +/* function: tcp_packet + * takes a tcp packet and sets it up for translation + * out - output packet + * tcp - pointer to tcp header in packet + * checksum - pseudo-header checksum + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in */ -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; +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; - fill_tun_header(&tun_header,ETH_P_IPV6); + if(len < sizeof(struct tcphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/(too small)"); + return 0; + } - fill_ip6_header(&ip6_targ,payload_size + sizeof(struct udphdr),IPPROTO_UDP,ip); + if(tcp->doff < 5) { + logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set to less than 5: %x", tcp->doff); + return 0; + } - checksum = ipv6_pseudo_header_checksum(0, &ip6_targ, sizeof(*udp) + payload_size); + if((size_t) tcp->doff*4 > len) { + logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set too large: %x", tcp->doff); + return 0; + } - 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); + header_size = tcp->doff * 4; + payload = ((const char *) tcp) + header_size; + payload_size = len - header_size; - udp_translate(fd,udp,payload,payload_size,io_targ,checksum); + return tcp_translate(out, pos, tcp, header_size, checksum, payload, payload_size); } -/* function: udp6_to_udp - * translate ipv6 udp to ipv4 udp - * fd - tun interface fd - * ip6 - source packet ipv6 header - * udp - source packet udp header - * payload - udp payload +/* function: udp_translate + * common between ipv4/ipv6 - setup checksum and send udp packet + * out - output packet + * udp - udp 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 */ -void udp6_to_udp(int fd, const struct ip6_hdr *ip6, const struct udphdr *udp, const char *payload, size_t payload_size) { - struct iphdr ip_targ; - struct iovec io_targ[4]; - struct tun_pi tun_header; - uint32_t checksum; +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; - fill_tun_header(&tun_header,ETH_P_IP); + memcpy(udp_targ, udp, sizeof(struct udphdr)); + udp_targ->check = 0; // reset checksum, to be calculated - fill_ip_header(&ip_targ,payload_size + sizeof(struct udphdr),IPPROTO_UDP,ip6); - - checksum = ipv4_pseudo_header_checksum(0, &ip_targ, sizeof(*udp) + payload_size); + checksum = ip_checksum_add(checksum, udp_targ, sizeof(struct udphdr)); + checksum = ip_checksum_add(checksum, payload, payload_size); + udp_targ->check = ip_checksum_finish(checksum); - 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); + out[pos].iov_len = sizeof(struct udphdr); + out[POS_PAYLOAD].iov_base = (char *) payload; + out[POS_PAYLOAD].iov_len = payload_size; - udp_translate(fd,udp,payload,payload_size,io_targ,checksum); + return POS_PAYLOAD + 1; } /* function: tcp_translate * common between ipv4/ipv6 - setup checksum and send tcp packet - * fd - tun interface fd - * tcp - source packet tcp header + * out - output packet + * tcp - tcp header + * header_size - size of tcp header including options + * checksum - partial checksum covering ipv4/ipv6 header * payload - tcp payload * payload_size - size of payload - * io_targ - iovec with tun and ipv4/ipv6 header (see below) - * 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 payload) - * checksum - partial checksum covering ipv4/ipv6 header + * 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) */ -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; - - 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. +int tcp_translate(clat_packet out, int pos, const struct tcphdr *tcp, size_t header_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; + + if (header_size > MAX_TCP_HDR) { + // A TCP header cannot be more than MAX_TCP_HDR bytes long because it's a 4-bit field that + // counts in 4-byte words. 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); + header_size, MAX_TCP_HDR); + header_size = MAX_TCP_HDR; } - memcpy(&header, tcp, header_size); + memcpy(tcp_targ, tcp, header_size); - header.tcp.check = 0; - checksum = ip_checksum_add(checksum, &header, header_size); + tcp_targ->check = 0; + checksum = ip_checksum_add(checksum, tcp_targ, header_size); checksum = ip_checksum_add(checksum, payload, payload_size); - header.tcp.check = ip_checksum_finish(checksum); - - io_targ[2].iov_base = &header; - io_targ[2].iov_len = header_size; - - io_targ[3].iov_base = (char *)payload; - io_targ[3].iov_len = payload_size; -} - -/* function: tcp_to_tcp6 - * translate ipv4 tcp to ipv6 tcp - * 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 - */ -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; - uint32_t checksum; - - fill_tun_header(&tun_header,ETH_P_IPV6); - - fill_ip6_header(&ip6_targ, header_size + payload_size, IPPROTO_TCP, ip); - - 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, header_size, payload, payload_size, io_targ, checksum); -} - -/* function: tcp6_to_tcp - * translate ipv6 tcp to ipv4 tcp - * 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 - */ -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; - uint32_t checksum; - - fill_tun_header(&tun_header,ETH_P_IP); - - fill_ip_header(&ip_targ, header_size + payload_size, IPPROTO_TCP, ip6); - - checksum = ipv4_pseudo_header_checksum(0, &ip_targ, header_size + payload_size); + tcp_targ->check = ip_checksum_finish(checksum); - 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); + out[POS_PAYLOAD].iov_base = (char *)payload; + out[POS_PAYLOAD].iov_len = payload_size; - tcp_translate(fd, tcp, header_size, payload, payload_size, io_targ, checksum); + return POS_PAYLOAD + 1; } diff --git a/translate.h b/translate.h index 641768e..07db023 100644 --- a/translate.h +++ b/translate.h @@ -18,15 +18,40 @@ #ifndef __TRANSLATE_H__ #define __TRANSLATE_H__ -void icmp_to_icmp6(int fd, const struct iphdr *ip, const struct icmphdr *icmp, const char *payload, size_t payload_size); -void icmp6_to_icmp(int fd, const struct ip6_hdr *ip6, const struct icmp6_hdr *icmp6, const char *payload, size_t payload_size); +#include <linux/if_tun.h> -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); +#define MAX_TCP_HDR (15 * 4) // Data offset field is 4 bits and counts in 32-bit words. -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); +// A clat_packet is an array of iovec structures representing a packet that we are translating. +// The POS_XXX constants represent the array indices within the clat_packet that contain specific +// parts of the packet. +enum clat_packet_index { POS_TUNHDR, POS_IPHDR, POS_TRANSPORTHDR, POS_ICMPIPHDR, + POS_PAYLOAD, POS_MAX }; +typedef struct iovec clat_packet[POS_MAX]; + +// Returns the total length of the packet components after index. +uint16_t payload_length(clat_packet packet, int index); + +// Functions to create tun, IPv4, and IPv6 headers. +void fill_tun_header(struct tun_pi *tun_header, uint16_t proto); +void fill_ip_header(struct iphdr *ip_targ, uint16_t payload_len, uint8_t protocol, + const struct ip6_hdr *old_header); +void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol, + const struct iphdr *old_header); + +// 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); +int icmp6_to_icmp(clat_packet out, int pos, const struct icmp6_hdr *icmp6, uint32_t checksum, + const char *payload, size_t payload_size); + +// Translate TCP and UDP packets. +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 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__ */ |