summaryrefslogtreecommitdiffstats
path: root/translate.c
diff options
context:
space:
mode:
Diffstat (limited to 'translate.c')
-rw-r--r--translate.c186
1 files changed, 153 insertions, 33 deletions
diff --git a/translate.c b/translate.c
index fc70f3d..7585092 100644
--- a/translate.c
+++ b/translate.c
@@ -16,15 +16,7 @@
* translate.c - CLAT functions / partial implementation of rfc6145
*/
#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 <sys/uio.h>
#include "icmp.h"
#include "translate.h"
@@ -149,7 +141,7 @@ void fill_ip_header(struct iphdr *ip, uint16_t payload_len, uint8_t protocol,
// Third-party ICMPv6 message. This may have been originated by an native IPv6 address.
// In that case, the source IPv6 address can't be translated and we need to make up an IPv4
// source address. For now, use 255.0.0.<ttl>, which at least looks useful in traceroute.
- if (ip->saddr == (uint32_t) INADDR_NONE) {
+ if ((uint32_t) ip->saddr == INADDR_NONE) {
ttl_guess = icmp_guess_ttl(old_header->ip6_hlim);
ip->saddr = htonl((0xff << 24) + ttl_guess);
}
@@ -175,6 +167,54 @@ void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol
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
@@ -208,12 +248,10 @@ int icmp_to_icmp6(clat_packet out, int pos, const struct icmphdr *icmp, uint32_t
// 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. 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).
+ // an IPv6 header, which is 20 bytes longer. Fix it up here.
// 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 = htonl(ntohl(checksum) + 20);
+ checksum = checksum + htons(20);
} else if (icmp6_type == ICMP6_ECHO_REQUEST || icmp6_type == ICMP6_ECHO_REPLY) {
// Ping packet.
icmp6_targ->icmp6_id = icmp->un.echo.id;
@@ -236,16 +274,14 @@ int icmp_to_icmp6(clat_packet out, int pos, const struct icmphdr *icmp, uint32_t
* translate ipv6 icmp to ipv4 icmp
* 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
*/
-int icmp6_to_icmp(clat_packet out, int pos, const struct icmp6_hdr *icmp6, uint32_t checksum,
+int icmp6_to_icmp(clat_packet out, int pos, const struct icmp6_hdr *icmp6,
const char *payload, size_t payload_size) {
struct icmphdr *icmp_targ = out[pos].iov_base;
uint8_t icmp_type;
- int ttl;
int clat_packet_len;
memset(icmp_targ, 0, sizeof(struct icmphdr));
@@ -280,14 +316,32 @@ int icmp6_to_icmp(clat_packet out, int pos, const struct icmp6_hdr *icmp6, uint3
return clat_packet_len;
}
+/* function: generic_packet
+ * takes a generic IP packet and sets it up for translation
+ * out - output packet
+ * pos - position in the output packet of the transport header
+ * payload - pointer to IP payload
+ * len - size of ip payload
+ * returns: the highest position in the output clat_packet that's filled in
+ */
+int generic_packet(clat_packet out, int pos, const char *payload, size_t len) {
+ out[pos].iov_len = 0;
+ out[CLAT_POS_PAYLOAD].iov_base = (char *) payload;
+ out[CLAT_POS_PAYLOAD].iov_len = len;
+
+ return CLAT_POS_PAYLOAD + 1;
+}
+
/* 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
+ * old_sum - pseudo-header checksum of old header
+ * new_sum - pseudo-header checksum of new header
* len - size of ip payload
*/
-int udp_packet(clat_packet out, int pos, const struct udphdr *udp, uint32_t checksum, 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) {
const char *payload;
size_t payload_size;
@@ -299,7 +353,7 @@ int udp_packet(clat_packet out, int pos, const struct udphdr *udp, uint32_t chec
payload = (const char *) (udp + 1);
payload_size = len - sizeof(struct udphdr);
- return udp_translate(out, pos, udp, checksum, payload, payload_size);
+ return udp_translate(out, pos, udp, old_sum, new_sum, payload, payload_size);
}
/* function: tcp_packet
@@ -310,7 +364,8 @@ int udp_packet(clat_packet out, int pos, const struct udphdr *udp, uint32_t chec
* 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 checksum, size_t len) {
+int tcp_packet(clat_packet out, int pos, const struct tcphdr *tcp,
+ uint32_t old_sum, uint32_t new_sum, size_t len) {
const char *payload;
size_t payload_size, header_size;
@@ -333,20 +388,21 @@ int tcp_packet(clat_packet out, int pos, const struct tcphdr *tcp, uint32_t chec
payload = ((const char *) tcp) + header_size;
payload_size = len - header_size;
- return tcp_translate(out, pos, tcp, header_size, checksum, payload, payload_size);
+ return tcp_translate(out, pos, tcp, header_size, old_sum, new_sum, payload, payload_size);
}
/* function: udp_translate
* common between ipv4/ipv6 - setup checksum and send udp packet
* out - output packet
* udp - udp header
- * checksum - pseudo-header checksum
+ * old_sum - pseudo-header checksum of old header
+ * new_sum - pseudo-header checksum of new header
* 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 checksum,
- 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) {
struct udphdr *udp_targ = out[pos].iov_base;
memcpy(udp_targ, udp, sizeof(struct udphdr));
@@ -355,8 +411,22 @@ int udp_translate(clat_packet out, int pos, const struct udphdr *udp, uint32_t c
out[CLAT_POS_PAYLOAD].iov_base = (char *) payload;
out[CLAT_POS_PAYLOAD].iov_len = payload_size;
- udp_targ->check = 0; // Checksum field must be 0 when calculating checksum.
- udp_targ->check = packet_checksum(checksum, out, pos);
+ 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;
+ }
return CLAT_POS_PAYLOAD + 1;
}
@@ -370,12 +440,9 @@ int udp_translate(clat_packet out, int pos, const struct udphdr *udp, uint32_t c
* 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 checksum, const char *payload, size_t payload_size) {
+ uint32_t old_sum, uint32_t new_sum, const char *payload, size_t payload_size) {
struct tcphdr *tcp_targ = out[pos].iov_base;
out[pos].iov_len = header_size;
@@ -392,8 +459,61 @@ int tcp_translate(clat_packet out, int pos, const struct tcphdr *tcp, size_t hea
out[CLAT_POS_PAYLOAD].iov_base = (char *)payload;
out[CLAT_POS_PAYLOAD].iov_len = payload_size;
- tcp_targ->check = 0; // Checksum field must be 0 when calculating checksum.
- tcp_targ->check = packet_checksum(checksum, out, pos);
+ tcp_targ->check = ip_checksum_adjust(tcp->check, old_sum, new_sum);
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);
+ }
+}