Correct TCP/IP checksum generation.
authorMichael Brown <mcb30@etherboot.org>
Wed, 19 Jul 2006 23:38:05 +0000 (23:38 +0000)
committerMichael Brown <mcb30@etherboot.org>
Wed, 19 Jul 2006 23:38:05 +0000 (23:38 +0000)
src/include/gpxe/tcpip_if.h
src/net/ipv4.c
src/net/tcpip_if.c
src/net/udp.c

index ce095ef..df63541 100644 (file)
@@ -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 );
index 19e5644..6594af4 100644 (file)
@@ -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 );
index 80d6926..98d6480 100644 (file)
@@ -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.
+ */
+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.
  */
-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_chksum ( const void *data, size_t len ) {
+       return tcpip_continue_chksum ( 0xffff, data, len );
 }
index 87795d4..d04c94b 100644 (file)
@@ -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;