summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/include/ipxe/tcpip.h38
-rw-r--r--src/net/ipv4.c5
-rw-r--r--src/net/ipv6.c2
-rw-r--r--src/net/udp.c1
-rw-r--r--src/tests/tcpip_test.c19
5 files changed, 61 insertions, 4 deletions
diff --git a/src/include/ipxe/tcpip.h b/src/include/ipxe/tcpip.h
index c3528c9c..be4ac838 100644
--- a/src/include/ipxe/tcpip.h
+++ b/src/include/ipxe/tcpip.h
@@ -19,11 +19,38 @@ struct io_buffer;
struct net_device;
struct ip_statistics;
+/** Positive zero checksum value */
+#define TCPIP_POSITIVE_ZERO_CSUM 0x0000
+
+/** Negative zero checksum value */
+#define TCPIP_NEGATIVE_ZERO_CSUM 0xffff
+
/** Empty checksum value
*
- * This is the TCP/IP checksum over a zero-length block of data.
+ * All of our TCP/IP checksum algorithms will return only the positive
+ * representation of zero (0x0000) for a zero checksum over non-zero
+ * input data. This property arises since the end-around carry used
+ * to mimic one's complement addition using unsigned arithmetic
+ * prevents the running total from ever returning to 0x0000. The
+ * running total will therefore use only the negative representation
+ * of zero (0xffff). Since the return value is the one's complement
+ * negation of the running total (calculated by simply bit-inverting
+ * the running total), the return value will therefore use only the
+ * positive representation of zero (0x0000).
+ *
+ * It is a very common misconception (found in many places such as
+ * RFC1624) that this is a property guaranteed by the underlying
+ * mathematics. It is not; the choice of which zero representation is
+ * used is merely an artifact of the software implementation of the
+ * checksum algorithm.
+ *
+ * For consistency, we choose to use the positive representation of
+ * zero (0x0000) for the checksum of a zero-length block of data.
+ * This ensures that all of our TCP/IP checksum algorithms will return
+ * only the positive representation of zero (0x0000) for a zero
+ * checksum (regardless of the input data).
*/
-#define TCPIP_EMPTY_CSUM 0xffff
+#define TCPIP_EMPTY_CSUM TCPIP_POSITIVE_ZERO_CSUM
/** TCP/IP address flags */
enum tcpip_st_flags {
@@ -88,6 +115,13 @@ struct tcpip_protocol {
int ( * rx ) ( struct io_buffer *iobuf, struct net_device *netdev,
struct sockaddr_tcpip *st_src,
struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum );
+ /** Preferred zero checksum value
+ *
+ * The checksum is a one's complement value: zero may be
+ * represented by either positive zero (0x0000) or negative
+ * zero (0xffff).
+ */
+ uint16_t zero_csum;
/**
* Transport-layer protocol number
*
diff --git a/src/net/ipv4.c b/src/net/ipv4.c
index 7959cf35..8eb04a65 100644
--- a/src/net/ipv4.c
+++ b/src/net/ipv4.c
@@ -358,8 +358,11 @@ static int ipv4_tx ( struct io_buffer *iobuf,
( ( netdev->rx_stats.good & 0xf ) << 0 ) );
/* Fix up checksums */
- if ( trans_csum )
+ if ( trans_csum ) {
*trans_csum = ipv4_pshdr_chksum ( iobuf, *trans_csum );
+ if ( ! *trans_csum )
+ *trans_csum = tcpip_protocol->zero_csum;
+ }
iphdr->chksum = tcpip_chksum ( iphdr, sizeof ( *iphdr ) );
/* Print IP4 header for debugging */
diff --git a/src/net/ipv6.c b/src/net/ipv6.c
index 012ba592..bbc00d33 100644
--- a/src/net/ipv6.c
+++ b/src/net/ipv6.c
@@ -522,6 +522,8 @@ static int ipv6_tx ( struct io_buffer *iobuf,
*trans_csum = ipv6_pshdr_chksum ( iphdr, len,
tcpip_protocol->tcpip_proto,
*trans_csum );
+ if ( ! *trans_csum )
+ *trans_csum = tcpip_protocol->zero_csum;
}
/* Print IPv6 header for debugging */
diff --git a/src/net/udp.c b/src/net/udp.c
index 0f7dfb24..1fbc12d4 100644
--- a/src/net/udp.c
+++ b/src/net/udp.c
@@ -328,6 +328,7 @@ static int udp_rx ( struct io_buffer *iobuf,
struct tcpip_protocol udp_protocol __tcpip_protocol = {
.name = "UDP",
.rx = udp_rx,
+ .zero_csum = TCPIP_NEGATIVE_ZERO_CSUM,
.tcpip_proto = IP_UDP,
};
diff --git a/src/tests/tcpip_test.c b/src/tests/tcpip_test.c
index 759f886b..fac0ec26 100644
--- a/src/tests/tcpip_test.c
+++ b/src/tests/tcpip_test.c
@@ -94,6 +94,12 @@ TCPIP_TEST ( one_byte, DATA ( 0xeb ) );
/** Double byte */
TCPIP_TEST ( two_bytes, DATA ( 0xba, 0xbe ) );
+/** Positive zero data */
+TCPIP_TEST ( positive_zero, DATA ( 0x00, 0x00 ) );
+
+/** Negative zero data */
+TCPIP_TEST ( negative_zero, DATA ( 0xff, 0xff ) );
+
/** Final wrap-around carry (big-endian) */
TCPIP_TEST ( final_carry_big,
DATA ( 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ) );
@@ -126,9 +132,17 @@ TCPIP_RANDOM_TEST ( partial, 0xcafebabe, 121, 5 );
*
* This is a reference implementation taken from RFC1071 (and modified
* to fix compilation without warnings under gcc).
+ *
+ * The initial value of the one's complement @c sum is changed from
+ * positive zero (0x0000) to negative zero (0xffff). This ensures
+ * that the return value will always use the positive representation
+ * of zero (0x0000). Without this change, the return value would use
+ * negative zero (0xffff) if the input data is zero length (or all
+ * zeros) but positive zero (0x0000) for any other data which sums to
+ * zero.
*/
static uint16_t rfc_tcpip_chksum ( const void *data, size_t len ) {
- unsigned long sum = 0;
+ unsigned long sum = 0xffff;
while ( len > 1 ) {
sum += *( ( uint16_t * ) data );
@@ -142,6 +156,7 @@ static uint16_t rfc_tcpip_chksum ( const void *data, size_t len ) {
while ( sum >> 16 )
sum = ( ( sum & 0xffff ) + ( sum >> 16 ) );
+ assert ( sum != 0x0000 );
return ~sum;
}
@@ -227,6 +242,8 @@ static void tcpip_test_exec ( void ) {
tcpip_ok ( &empty );
tcpip_ok ( &one_byte );
tcpip_ok ( &two_bytes );
+ tcpip_ok ( &positive_zero );
+ tcpip_ok ( &negative_zero );
tcpip_ok ( &final_carry_big );
tcpip_ok ( &final_carry_little );
tcpip_random_ok ( &random_aligned );