From 2c0eb6eb1dc2b3828adad19ff46aeffae95b0102 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 19 Jul 2006 23:38:05 +0000 Subject: [PATCH] Correct TCP/IP checksum generation. --- src/include/gpxe/tcpip_if.h | 4 ++- src/net/ipv4.c | 10 +++--- src/net/tcpip_if.c | 65 ++++++++++++++++++++++++++++--------- src/net/udp.c | 9 ++--- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/include/gpxe/tcpip_if.h b/src/include/gpxe/tcpip_if.h index ce095efa..df635417 100644 --- a/src/include/gpxe/tcpip_if.h +++ b/src/include/gpxe/tcpip_if.h @@ -83,7 +83,9 @@ extern void trans_rx ( struct pk_buff *pkb, uint8_t trans_proto, extern int trans_tx ( struct pk_buff *pkb, struct tcpip_protocol *tcpip, struct sockaddr *dest ); -extern uint16_t calc_chksum ( void *b, int len ); +extern unsigned int tcpip_continue_chksum ( unsigned int partial, + const void *data, size_t len ); +extern unsigned int tcpip_chksum ( const void *data, size_t len ); extern struct tcpip_protocol * find_tcpip_protocol ( uint8_t trans_proto ); extern struct tcpip_net_protocol * find_tcpip_net_protocol ( sa_family_t sa_family ); diff --git a/src/net/ipv4.c b/src/net/ipv4.c index 19e56440..6594af46 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -238,9 +238,8 @@ void ipv4_tx_csum ( struct pk_buff *pkb, struct tcpip_protocol *tcpip ) { struct iphdr *iphdr = pkb->data; struct ipv4_pseudo_header pshdr; - void *csum_offset = iphdr + sizeof ( *iphdr ) + tcpip->csum_offset; - uint16_t partial_csum = *( ( uint16_t* ) csum_offset ); - uint16_t csum; + uint16_t *csum = ( ( ( void * ) iphdr ) + sizeof ( *iphdr ) + + tcpip->csum_offset ); /* Calculate pseudo header */ pshdr.src = iphdr->src; @@ -250,8 +249,7 @@ void ipv4_tx_csum ( struct pk_buff *pkb, struct tcpip_protocol *tcpip ) { pshdr.len = htons ( pkb_len ( pkb ) - sizeof ( *iphdr ) ); /* Update the checksum value */ - csum = partial_csum + calc_chksum ( &pshdr, sizeof ( pshdr ) ); - memcpy ( csum_offset, &csum, 2 ); + *csum = tcpip_continue_chksum ( *csum, &pshdr, sizeof ( pshdr ) ); } /** @@ -407,7 +405,7 @@ int ipv4_tx ( struct pk_buff *pkb, struct tcpip_protocol *tcpip, /* Calculate header checksum, in network byte order */ iphdr->chksum = 0; - iphdr->chksum = htons ( calc_chksum ( iphdr, sizeof ( *iphdr ) ) ); + iphdr->chksum = tcpip_chksum ( iphdr, sizeof ( *iphdr ) ); /* Print IP4 header for debugging */ ipv4_dump ( iphdr ); diff --git a/src/net/tcpip_if.c b/src/net/tcpip_if.c index 80d69268..98d64804 100644 --- a/src/net/tcpip_if.c +++ b/src/net/tcpip_if.c @@ -105,21 +105,56 @@ int trans_tx ( struct pk_buff *pkb, struct tcpip_protocol *tcpip, } /** - * Calculate internet checksum + * Calculate continued TCP/IP checkum * - * @v b Pointer to the data - * @v len Length of data to be checksummed - * @ret result 16 bit internet checksum + * @v partial Checksum of already-summed data, in network byte order + * @v data Data buffer + * @v len Length of data buffer + * @ret cksum Updated checksum, in network byte order + * + * Calculates a TCP/IP-style 16-bit checksum over the data block. The + * checksum is returned in network byte order. + * + * This function may be used to add new data to an existing checksum. + * The function assumes that both the old data and the new data start + * on even byte offsets; if this is not the case then you will need to + * byte-swap either the input partial checksum, the output checksum, + * or both. Deciding which to swap is left as an exercise for the + * interested reader. */ -uint16_t calc_chksum(void *b, int len) { - uint16_t *buf = b, result; - uint16_t sum=0; - for ( sum = 0; len > 1; len -= 2 ) /* Sum all 16b words */ - sum += *buf++; - if ( len == 1 ) /* If any stray bytes, */ - sum += *(unsigned char*)buf; /* add to sum */ - sum = (sum >> 16) + (sum & 0xffff); /* Add the carry */ - sum += (sum >> 16); /* (again) */ - result = ~sum; /* Take the one's complement */ - return result; /* Return 16b value */ +unsigned int tcpip_continue_chksum ( unsigned int partial, const void *data, + size_t len ) { + unsigned int cksum = ( ( ~partial ) & 0xffff ); + unsigned int value; + unsigned int i; + + for ( i = 0 ; i < len ; i++ ) { + value = * ( ( uint8_t * ) data + i ); + if ( i & 1 ) { + /* Odd bytes: swap on little-endian systems */ + value = be16_to_cpu ( value ); + } else { + /* Even bytes: swap on big-endian systems */ + value = le16_to_cpu ( value ); + } + cksum += value; + if ( cksum > 0xffff ) + cksum -= 0xffff; + } + + return ( ( ~cksum ) & 0xffff ); +} + +/** + * Calculate TCP/IP checkum + * + * @v data Data buffer + * @v len Length of data buffer + * @ret cksum Checksum, in network byte order + * + * Calculates a TCP/IP-style 16-bit checksum over the data block. The + * checksum is returned in network byte order. + */ +unsigned int tcpip_chksum ( const void *data, size_t len ) { + return tcpip_continue_chksum ( 0xffff, data, len ); } diff --git a/src/net/udp.c b/src/net/udp.c index 87795d4d..d04c94bb 100644 --- a/src/net/udp.c +++ b/src/net/udp.c @@ -142,11 +142,8 @@ int udp_sendto ( struct udp_connection *conn, struct sockaddr *peer, udphdr->dest_port = *dest; udphdr->source_port = conn->local_port; udphdr->len = htons ( pkb_len ( conn->tx_pkb ) ); - /** - * Calculate the partial checksum. Note this is stored in host byte - * order. - */ - udphdr->chksum = calc_chksum ( udphdr, sizeof ( *udphdr ) + len ); + udphdr->chksum = 0; + udphdr->chksum = tcpip_chksum ( udphdr, sizeof ( *udphdr ) + len ); /** * Dump the contents of the UDP header @@ -238,7 +235,7 @@ void udp_rx ( struct pk_buff *pkb, struct in_addr *src_net_addr __unused, } /* Verify the checksum */ - chksum = calc_chksum ( pkb->data, pkb_len ( pkb ) ); + chksum = tcpip_chksum ( pkb->data, pkb_len ( pkb ) ); if ( chksum != 0xffff ) { DBG ( "Bad checksum %d\n", chksum ); return;