diff options
-rw-r--r-- | checksum.c | 38 | ||||
-rw-r--r-- | checksum.h | 2 | ||||
-rw-r--r-- | ipv4.c | 13 | ||||
-rw-r--r-- | ipv6.c | 9 | ||||
-rw-r--r-- | translate.c | 49 | ||||
-rw-r--r-- | translate.h | 12 |
6 files changed, 87 insertions, 36 deletions
@@ -49,19 +49,27 @@ uint32_t ip_checksum_add(uint32_t current, const void *data, int len) { return checksum; } -/* function: ip_checksum_finish - * close the checksum +/* function: ip_checksum_fold + * folds a 32-bit partial checksum into 16 bits * temp_sum - sum from ip_checksum_add + * returns: the folded checksum in network byte order */ -uint16_t ip_checksum_finish(uint32_t temp_sum) { +uint16_t ip_checksum_fold(uint32_t temp_sum) { while(temp_sum > 0xffff) temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF); - temp_sum = (~temp_sum) & 0xffff; - return temp_sum; } +/* 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); +} + /* function: ip_checksum * combined ip_checksum_add and ip_checksum_finish * data - data to checksum @@ -113,3 +121,23 @@ uint32_t ipv4_pseudo_header_checksum(uint32_t current, const struct iphdr *ip, u 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 + } +} @@ -25,4 +25,6 @@ uint16_t ip_checksum(const void *data, int len); 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); +uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum); + #endif /* __CHECKSUM_H__ */ @@ -70,7 +70,7 @@ int ipv4_packet(clat_packet out, int pos, const char *packet, size_t len) { uint8_t nxthdr; const char *next_header; size_t len_left; - uint32_t checksum; + uint32_t old_sum, new_sum; int iov_len; if(len < sizeof(struct iphdr)) { @@ -121,14 +121,17 @@ int ipv4_packet(clat_packet out, int pos, const char *packet, size_t len) { out[pos].iov_len = sizeof(struct ip6_hdr); // Calculate the pseudo-header checksum. - checksum = ipv6_pseudo_header_checksum(0, ip6_targ, len_left); + old_sum = ipv4_pseudo_header_checksum(0, header, len_left); + new_sum = 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); + iov_len = icmp_packet(out, pos + 1, (const struct icmphdr *) next_header, new_sum, len_left); } else if (nxthdr == IPPROTO_TCP) { - iov_len = tcp_packet(out, pos + 1, (const struct tcphdr *) next_header, checksum, len_left); + iov_len = tcp_packet(out, pos + 1, (const struct tcphdr *) next_header, old_sum, new_sum, + len_left); } else if (nxthdr == IPPROTO_UDP) { - iov_len = udp_packet(out, pos + 1, (const struct udphdr *) next_header, checksum, len_left); + iov_len = udp_packet(out, pos + 1, (const struct udphdr *) next_header, old_sum, new_sum, + len_left); } else if (nxthdr == IPPROTO_GRE) { iov_len = generic_packet(out, pos + 1, next_header, len_left); } else { @@ -88,7 +88,7 @@ int ipv6_packet(clat_packet out, int pos, const char *packet, size_t len) { uint8_t protocol; const char *next_header; size_t len_left; - uint32_t checksum; + uint32_t old_sum, new_sum; int iov_len; if(len < sizeof(struct ip6_hdr)) { @@ -133,16 +133,17 @@ int ipv6_packet(clat_packet out, int pos, const char *packet, size_t len) { out[pos].iov_len = sizeof(struct iphdr); // Calculate the pseudo-header checksum. - checksum = ipv4_pseudo_header_checksum(0, ip_targ, len_left); + old_sum = ipv6_pseudo_header_checksum(0, ip6, len_left); + new_sum = ipv4_pseudo_header_checksum(0, ip_targ, len_left); // 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, + iov_len = tcp_packet(out, pos + 1, (const struct tcphdr *) next_header, old_sum, new_sum, len_left); } else if (ip6->ip6_nxt == IPPROTO_UDP) { - iov_len = udp_packet(out, pos + 1, (const struct udphdr *) next_header, checksum, + iov_len = udp_packet(out, pos + 1, (const struct udphdr *) next_header, old_sum, new_sum, len_left); } else if (ip6->ip6_nxt == IPPROTO_GRE) { iov_len = generic_packet(out, pos + 1, next_header, len_left); diff --git a/translate.c b/translate.c index 00ea0b9..9a0f1b5 100644 --- a/translate.c +++ b/translate.c @@ -208,12 +208,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; @@ -298,10 +296,12 @@ int generic_packet(clat_packet out, int pos, const char *payload, size_t len) { * 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; @@ -313,7 +313,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 @@ -324,7 +324,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; @@ -347,20 +348,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)); @@ -369,8 +371,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; } @@ -389,7 +405,7 @@ int udp_translate(clat_packet out, int pos, const struct udphdr *udp, uint32_t c * 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; @@ -406,8 +422,7 @@ 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; } diff --git a/translate.h b/translate.h index 9f1ac15..cfb7bbb 100644 --- a/translate.h +++ b/translate.h @@ -61,12 +61,14 @@ int icmp6_to_icmp(clat_packet out, int pos, const struct icmp6_hdr *icmp6, 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 checksum, size_t len); -int udp_packet(clat_packet out, int pos, const struct udphdr *udp, 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); +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_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); + 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); #endif /* __TRANSLATE_H__ */ |