From e35adde820b799109076bbad37085578d7023e1e Mon Sep 17 00:00:00 2001 From: Michal Zima Date: Fri, 13 Apr 2012 20:04:01 +0200 Subject: [PATCH] Partial checksum updates for TCP and UDP --- src/checksum.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ src/checksum.h | 17 +++++++ src/ipv4.h | 7 +++ src/ipv6.h | 7 +++ src/tcp.c | 17 ++++--- src/udp.c | 23 +++++++--- 6 files changed, 178 insertions(+), 14 deletions(-) diff --git a/src/checksum.c b/src/checksum.c index 01b73b4..2d286fa 100644 --- a/src/checksum.c +++ b/src/checksum.c @@ -64,6 +64,49 @@ unsigned short checksum(const void *data, int length) return ~sum; } +/** + * General checksum update computation function. Inspired by algorithm + * in RFC3022. + * + * @param old_sum Old checksum + * @param old_data Pointer to old part of data. Must be of even + * number of octets + * @param old_len Length of old data + * @param new_data Pointer to new part of data. Must be of even + * number of octets + * @param new_len Length of new data + * + * @return Updated checksum + */ +unsigned short checksum_update(unsigned short old_sum, + unsigned short *old_data, short old_len, + unsigned short *new_data, short new_len) +{ + unsigned int sum; + + sum = ~old_sum & 0xffff; + + while (old_len) { + sum -= *old_data++; + if (sum & 0x80000000) { + sum--; + sum &= 0xffff; + } + old_len -= 2; + } + + while (new_len) { + sum += *new_data++; + if (sum & 0x00010000) { + sum++; + sum &= 0xffff; + } + new_len -= 2; + } + + return ~sum & 0xffff; +} + /** * IPv4 checksum computation function * @@ -147,3 +190,81 @@ unsigned short checksum_ipv6(struct s_ipv6_addr ip_src, return sum; } + +/** + * IPv4 checksum update computation function + * + * @param old_sum Old checksum + * @param ip6_src Original source IPv6 address + * @param ip6_dest Original destination IPv6 address + * @param old_port Original transport layer address (port) + * @param ip4_src New source IPv4 address + * @param ip4_dest New destination IPv4 address + * @param new_port New transport layer address (port) + * + * @return Checksum + */ +unsigned short checksum_ipv4_update(unsigned short old_sum, + struct s_ipv6_addr ip6_src, + struct s_ipv6_addr ip6_dest, + unsigned short old_port, + struct s_ipv4_addr ip4_src, + struct s_ipv4_addr ip4_dest, + unsigned short new_port) +{ + struct s_ipv4_pseudo_delta delta4; + struct s_ipv6_pseudo_delta delta6; + + delta4.ip_src = ip4_src; + delta4.ip_dest = ip4_dest; + delta4.port = new_port; + + delta6.ip_src = ip6_src; + delta6.ip_dest = ip6_dest; + delta6.port = old_port; + + return checksum_update(old_sum, + (unsigned short *) &delta6, + sizeof(struct s_ipv6_pseudo_delta), + (unsigned short *) &delta4, + sizeof(struct s_ipv4_pseudo_delta)); +} + +/** + * IPv6 checksum update computation function + * + * @param old_sum Old checksum + * @param ip4_src Original source IPv4 address + * @param ip4_dest Original destination IPv4 address + * @param old_port Original transport layer address (port) + * @param ip6_src New source IPv6 address + * @param ip6_dest New destination IPv6 address + * @param new_port New transport layer address (port) + * + * @return Checksum + */ +unsigned short checksum_ipv6_update(unsigned short old_sum, + struct s_ipv4_addr ip4_src, + struct s_ipv4_addr ip4_dest, + unsigned short old_port, + struct s_ipv6_addr ip6_src, + struct s_ipv6_addr ip6_dest, + unsigned short new_port) +{ + struct s_ipv4_pseudo_delta delta4; + struct s_ipv6_pseudo_delta delta6; + + delta4.ip_src = ip4_src; + delta4.ip_dest = ip4_dest; + delta4.port = old_port; + + delta6.ip_src = ip6_src; + delta6.ip_dest = ip6_dest; + delta6.port = new_port; + + return checksum_update(old_sum, + (unsigned short *) &delta4, + sizeof(struct s_ipv4_pseudo_delta), + (unsigned short *) &delta6, + sizeof(struct s_ipv6_pseudo_delta)); +} diff --git a/src/checksum.h b/src/checksum.h index 04063d3..bda5ccc 100644 --- a/src/checksum.h +++ b/src/checksum.h @@ -23,11 +23,28 @@ #include "ipv6.h" unsigned short checksum(const void *data, int length); +unsigned short checksum_update(unsigned short old_sum, + unsigned short *old_data, short old_len, + unsigned short *new_data, short new_len); unsigned short checksum_ipv4(struct s_ipv4_addr ip_src, struct s_ipv4_addr ip_dest, unsigned short length, unsigned char proto, unsigned char *payload); unsigned short checksum_ipv6(struct s_ipv6_addr ip_src, struct s_ipv6_addr ip_dest, unsigned short length, unsigned char proto, unsigned char *payload); +unsigned short checksum_ipv4_update(unsigned short old_sum, + struct s_ipv6_addr ip6_src, + struct s_ipv6_addr ip6_dest, + unsigned short old_port, + struct s_ipv4_addr ip4_src, + struct s_ipv4_addr ip4_dest, + unsigned short new_port); +unsigned short checksum_ipv6_update(unsigned short old_sum, + struct s_ipv4_addr ip4_src, + struct s_ipv4_addr ip4_dest, + unsigned short old_port, + struct s_ipv6_addr ip6_src, + struct s_ipv6_addr ip6_dest, + unsigned short new_port); #endif /* CHECKSUM_H */ diff --git a/src/ipv4.h b/src/ipv4.h index faee16a..5a6a4ee 100644 --- a/src/ipv4.h +++ b/src/ipv4.h @@ -56,6 +56,13 @@ struct s_ipv4_pseudo { unsigned short len; /* 16 b; payload length */ } __attribute__ ((__packed__)); +/* IPv4 pseudoheader structure for checksum update */ +struct s_ipv4_pseudo_delta { + struct s_ipv4_addr ip_src; /* 32 b; source address */ + struct s_ipv4_addr ip_dest; /* 32 b; destination address */ + unsigned short port; /* 16 b; transport layer address */ +} __attribute__ ((__packed__)); + int ipv4(struct s_ethernet *eth, char *packet); #endif /* IPV4_H */ diff --git a/src/ipv6.h b/src/ipv6.h index f20e2cf..7e473a9 100644 --- a/src/ipv6.h +++ b/src/ipv6.h @@ -47,6 +47,13 @@ struct s_ipv6_pseudo { unsigned char next_header; /* 8 b; next header */ } __attribute__ ((__packed__)); +/* IPv6 pseudoheader structure for checksum update */ +struct s_ipv6_pseudo_delta { + struct s_ipv6_addr ip_src; /* 128 b; source address */ + struct s_ipv6_addr ip_dest; /* 128 b; destination address */ + unsigned short port; /* 16 b; transport layer address */ +} __attribute__ ((__packed__)); + int ipv6(struct s_ethernet *eth, char *packet); #endif /* IPV6_H */ diff --git a/src/tcp.c b/src/tcp.c index 3db4a8a..438d22b 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -110,9 +110,11 @@ int tcp_ipv4(struct s_ethernet *eth, struct s_ipv4 *ip4, char *payload, tcp->port_dest = connection->ipv6_port_src; /* compute TCP checksum */ - tcp->checksum = 0x0; - tcp->checksum = checksum_ipv6(ip6->ip_src, ip6->ip_dest, payload_size, - IPPROTO_TCP, (unsigned char *) tcp); + tcp->checksum = checksum_ipv6_update(tcp->checksum, + ip4->ip_src, ip4->ip_dest, + connection->ipv4_port_src, + ip6->ip_src, ip6->ip_dest, + connection->ipv6_port_src); /* copy the payload data (with new checksum) */ memcpy(packet + sizeof(struct s_ethernet) + sizeof(struct s_ipv6), @@ -198,10 +200,11 @@ int tcp_ipv6(struct s_ethernet *eth, struct s_ipv6 *ip6, char *payload) tcp->port_src = connection->ipv4_port_src; /* compute TCP checksum */ - tcp->checksum = 0; - tcp->checksum = checksum_ipv4(ip4->ip_src, ip4->ip_dest, - htons(ip6->len), IPPROTO_TCP, - (unsigned char *) tcp); + tcp->checksum = checksum_ipv4_update(tcp->checksum, + ip6->ip_src, ip6->ip_dest, + connection->ipv6_port_src, + ip4->ip_src, ip4->ip_dest, + connection->ipv4_port_src); /* copy the payload data (with new checksum) */ memcpy(packet + sizeof(struct s_ipv4), payload, htons(ip6->len)); diff --git a/src/udp.c b/src/udp.c index 04d4dad..a64a25d 100644 --- a/src/udp.c +++ b/src/udp.c @@ -110,9 +110,17 @@ int udp_ipv4(struct s_ethernet *eth, struct s_ipv4 *ip4, char *payload, udp->port_dest = connection->ipv6_port_src; /* compute UDP checksum */ - udp->checksum = 0x0; - udp->checksum = checksum_ipv6(ip6->ip_src, ip6->ip_dest, payload_size, - IPPROTO_UDP, (unsigned char *) udp); + if (udp->checksum) { + udp->checksum = checksum_ipv6_update(udp->checksum, + ip4->ip_src, ip4->ip_dest, + connection->ipv4_port_src, + ip6->ip_src, ip6->ip_dest, + connection->ipv6_port_src); + } else { + /* if original checksum was 0x0000, we need to compute it */ + udp->checksum = checksum_ipv6(ip6->ip_src, ip6->ip_dest, payload_size, + IPPROTO_UDP, (unsigned char *) udp); + } /* copy the payload data (with new checksum) */ memcpy(packet + sizeof(struct s_ethernet) + sizeof(struct s_ipv6), @@ -198,10 +206,11 @@ int udp_ipv6(struct s_ethernet *eth, struct s_ipv6 *ip6, char *payload) udp->port_src = connection->ipv4_port_src; /* compute UDP checksum */ - udp->checksum = 0x0; - udp->checksum = checksum_ipv4(ip4->ip_src, ip4->ip_dest, - htons(ip6->len), IPPROTO_UDP, - (unsigned char *) payload); + udp->checksum = checksum_ipv4_update(udp->checksum, + ip6->ip_src, ip6->ip_dest, + connection->ipv6_port_src, + ip4->ip_src, ip4->ip_dest, + connection->ipv4_port_src); /* copy the payload data (with new checksum) */ memcpy(packet + sizeof(struct s_ipv4), payload, htons(ip6->len));