views:

143

answers:

2

I need to calculate the checksum of an IP packet as described in http://www.faqs.org/rfcs/rfc1071.html.

I have already the following code:

#!/usr/bin/python
import struct

data = "45 00 00 47 73 88 40 00 40 06 a2 c4 83 9f 0e 85 83 9f 0e a1"

# a test for the checksum calculation

def _checksum(data):
    #calculate the header sum
    ip_header_sum = sum(struct.unpack_from("6H", data))
    #add the carry
    ip_header_sum = (ip_header_sum & 0xFFFF) + (ip_header_sum >> 16 & 0xFFFF)
    #invert the sum, python does not support inversion (~a is -a + 1) so we have to do
    #little trick: ~a is the same as 0xFFFF & ~a
    ip_header_sum = ~ip_header_sum & 0xFFFF

    return ip_header_sum #should return 0 if correct

data = data.split()
data = map(lambda x: int(x,16), data)
data = struct.pack("%dB" % len(data), *data)

print " ".join(map(lambda x: "0x%02x" % ord(x), data))
print "Checksum: 0x%04x" % _checksum(data)

It works with a package that I have captured with wireshark and which should have the correct checksum and should therefore evaluate to 0

Unfortunately the result is 0x6524. It is also interesting, that the result is always 0x6524 for every correct packet...

Who spots the error?

Edited to make the error more clear *Edited a second time*

+4  A: 

You've got two issues here.

First, your call to struct.unpack_from only unpacks 4 16-bit values (that is, 8 bytes) from the buffer. If you want to unpack the whole header, you'll need to do something like struct.unpack_from("!nH"), where n is the number of shorts you want to unpack. You can generate the appropriate format string with stuct.unpack_from("!%dH"%(len(data)/2), data), assuming that 'data' contains nothing but the IP header.

Second, once you do that, you'll find that the checksum now works out to 0. This is the correct result for a packet that already has its checksum set, as this one does. (You highlighted the ac and c4 bytes in the packet above.) To calculate the correct checksum for a packet from scratch, you need to set the checksum bytes to 0. (See the start of step 2 in RFC1071: "To generate a checksum, the checksum field itself is cleared".)

nickm
*You highlighted the ac and c4 bytes in the packet above* - i don't think Simon intentionally highlighted any bytes of the sample packet, StackOverflow engine did, thinking it's some code
mykhal
The data parameter contains the whole packet. Also I do not unpack the checksum so therefore it should give the checksum (but maybe byte swapped )
Simon
I have done what nickm has told, but still this does not work, see the updated question
Simon
+1  A: 

You can use the solution directly from http://stackoverflow.com/questions/1767910/checksum-udp-calculation-python, which results in the expected checksum value of zero.

import struct

data = "45 00 00 47 73 88 40 00 40 06 a2 c4 83 9f 0e 85 83 9f 0e a1"

def carry_around_add(a, b):
    c = a + b
    return (c & 0xffff) + (c >> 16)

def checksum(msg):
    s = 0
    for i in range(0, len(msg), 2):
        w = ord(msg[i]) + (ord(msg[i+1]) << 8)
        s = carry_around_add(s, w)
    return ~s & 0xffff

data = data.split()
data = map(lambda x: int(x,16), data)
data = struct.pack("%dB" % len(data), *data)

print ' '.join('%02X' % ord(x) for x in data)
print "Checksum: 0x%04x" % checksum(data)

Results:

45 00 00 47 73 88 40 00 40 06 A2 C4 83 9F 0E 85 83 9F 0E A1
Checksum: 0x0000
Kevin Jacobs
Doesn't work, too. It also gives the result 0x6524. I believe the error is not in the calculation, but in the unpacking. But the checksum is calculated out of the first 96 bits of the package, isn't it?
Simon
Edited to demonstrate that it does indeed work, providing a checksum result of zer0.
Kevin Jacobs