Internet Checksum

Stu 2007-11-01 Created page.

The Internet Checksum is a checksum used in (nearly?) every IP packet crossing the Internet or any IP-based network, therefore the speed of of the calculation is very important; oftentimes it is performed in hardware.

Many RFC's described it thusly:

The checksum field is the 16 bit one's complement of the one's complement sum of all 16 bit words in the header.

The description of the checksum and how to calculate it are found in RFC 1071 [L1 ] Computing the Internet Checksum, with additional notes in RFC 1141 [L2 ] Incremental Updating of the Internet Checksum, and RFC 1624 [L3 ] Computation of the Internet Checksum via Incremental Update.

I've written a few Tcl procs to calculate the Internet Checksum using 8,16,32 or 64 bit quantities/operations. These procs assume that the input data is binary and in network byte order aka big-endian, which is the usual representation of an IP packet.

# inet_cksum8 --
#
#       Compute Internet Checksum
#       using 8 bit quantities/operations
#
# Arguments:
#       data    binary data to be checksummed
#
# Results:
#       cksum   16 bit Internet Checksum
#
proc inet_cksum8 {data} { 
        set sum 0
        set x {}
        set shift 8
        binary scan $data c* x
        foreach v $x {
                set sum [expr {$sum + (($v & 0xff) << $shift)}]
                if {$shift == 8} { set shift 0 } else { set shift 8 }
        }
        return [Inet_cksum_fold_and_complement $sum]
}
###
# inet_cksum16 --
#
#       Compute Internet Checksum
#       using 16 bit quantities/operations
#
# Arguments:
#       data    binary data to be checksummed
#
# Results:
#       cksum   16 bit Internet Checksum
#
proc inet_cksum16 {data} {
        set sum 0
        set x {}
        append data [binary format c 0]
        binary scan $data S* x
        foreach v $x {
                set sum [expr {$sum + ($v & 0xffff)}]
        }
        return [Inet_cksum_fold_and_complement $sum]
}
###
# inet_cksum32 --
#
#       Compute Internet Checksum
#       using 32 bit quantities/operations
#
# Arguments:
#       data    binary data to be checksummed
#
# Results:
#       cksum   16 bit Internet Checksum
#
proc inet_cksum32 {data} {
        set sum 0
        set x {}
        set c [set b [set a 0]]

        set n [binary scan $data I*ccc x a b c]

        set a [expr {$a & 0xff}]
        set b [expr {$b & 0xff}]
        set c [expr {$c & 0xff}]

        if {$n > 1} { set a [expr {$a << 8}] }
        if {$n > 3} { set c [expr {$c << 8}] }

        lappend x $a $b $c

        foreach v $x {
                set sum [expr {$sum + ($v & 0xffffffff)}]
        }

        return [Inet_cksum_fold_and_complement $sum]
}
###
# inet_cksum64 --
#
# Does not work with tcl 8.4.16, works with tcl 8.5
#
#       Compute Internet Checksum
#       using 64 bit quantities/operations
#
# Arguments:
#       data    binary data to be checksummed
#
# Results:
#       cksum   16 bit Internet Checksum
#
proc inet_cksum64 {data} {
        set sum 0
        set x {}
        set g [set f [set e [set d [set c [set b [set a 0]]]]]]

        set n [binary scan $data W*ccccccc x a b c d e f g]
 
        set a [expr {$a & 0xff}]
        set b [expr {$b & 0xff}]
        set c [expr {$c & 0xff}] 
        set d [expr {$d & 0xff}]
        set e [expr {$e & 0xff}]
        set f [expr {$f & 0xff}]
        set g [expr {$g & 0xff}]
 
        if {$n > 1} { set a [expr {$a << 8}] }
        if {$n > 3} { set c [expr {$c << 8}] }
        if {$n > 5} { set e [expr {$e << 8}] }
        if {$n > 7} { set g [expr {$g << 8}] }

        lappend x $a $b $c $d $e $f $g

        foreach v $x {
                set sum [expr {$sum + ($v & 0xffffffffffffffff)}]
        }
        
        return [Inet_cksum_fold_and_complement $sum]
}
###

This performs the last 2 steps in the calculation and is called by all the other procs.

# Inet_cksum_fold_and_complement --
#
#       Fold back checksum (apply carry) into 16 bits and complement
#
# Arguments:
#       sum     Summed data, may be more than 16 bits wide
#
# Results:
#       cksum   16 bit Internet Checksum
#
proc Inet_cksum_fold_and_complement {sum} {
        while {$sum > 0xffff} {
                set sum [expr {($sum & 0xffff) + ($sum >> 16)}]
        }
        return [expr {~$sum & 0xffff}]
}
###

Hideous test code, the result of the last column is the checksum of (checksum+data) which should always be 0.

proc go {} {
        set msg [string repeat [binary format H* 0102030405060708090a0b0c0d0e0f] 99]
        set times 1000
        puts Tcl:\ [info patch]
        puts "Which         Time     Checksum  0"
        puts inet_cksum8\ :\ [format %-10s [lindex [time {inet_cksum8 $msg} $times] 0]]\ ([format %04x [inet_cksum8 $msg]])\ ([format %x [inet_cksum8 [binary format S [inet_cksum8 $msg]]$msg]])
        puts inet_cksum16:\ [format %-10s [lindex [time {inet_cksum16 $msg} $times] 0]]\ ([format %04x [inet_cksum16 $msg]])\ ([format %x [inet_cksum16 [binary format S [inet_cksum16 $msg]]$msg]])
        puts inet_cksum32:\ [format %-10s [lindex [time {inet_cksum32 $msg} $times] 0]]\ ([format %04x [inet_cksum32 $msg]])\ ([format %x [inet_cksum32 [binary format S [inet_cksum32 $msg]]$msg]])
        puts inet_cksum64:\ [format %-10s [lindex [time {inet_cksum64 $msg} $times] 0]]\ ([format %04x [inet_cksum64 $msg]])\ ([format %x [inet_cksum64 [binary format S [inet_cksum64 $msg]]$msg]])
}

Testing. The 64 bit proc gives an incorrect result in Tcl 8.4.? which is too bad since it's the fastest. Tcl8.5b2 is slower on the whole with the 64 bit proc being really slow. The 32 bit proc is the overall winner.

Tcl: 8.4.7
Which         Time     Checksum  0
inet_cksum8 : 1889       (b0b8) (0)
inet_cksum16: 649        (b0b8) (0)
inet_cksum32: 341        (b0b8) (0)
inet_cksum64: 191        (0b27) (fd09)
Tcl: 8.5b2
Which         Time     Checksum  0
inet_cksum8 : 2414.965   (b0b8) (0)
inet_cksum16: 838.68     (b0b8) (0)
inet_cksum32: 448.847    (b0b8) (0)
inet_cksum64: 3125.231   (b0b8) (0)