Search code examples
javatcpwiresharkchecksum

Wireshark checksum does not match


I have written a function that computes the checksum for a given tcp packet. However, when I capture a tcp packet sent over ipv4 from wireshark and let my function compute its checksum, then its not the same checksum as in the wireshark captured packet. I checked and the bytes I give to the computeChecksum function are exactly the same as the tcp packet bytes i captured with wireshark.

I computed the checksum according to the RFC 793. Does anybody see if there's anything wrong in my code?

public long computeChecksum( byte[] buf, int src, int dst ){
    int length = buf.length; // nr of bytes of the tcppacket in total.
    int pseudoHeaderLength = 12; // nr of bytes of pseudoheader.
    int i = 0;
    long sum = 0;
    long data;
    buf[16] = (byte)0x0; // set checksum to 0 bytes
    buf[17] = (byte)0x0;


    // create the pseudoheader as specified in the rfc.
    ByteBuffer pseudoHeaderByteBuffer = ByteBuffer.allocate( 12 );
    pseudoHeaderByteBuffer.putInt( src ); 
    pseudoHeaderByteBuffer.putInt( dst );
    pseudoHeaderByteBuffer.put( (byte)0x0 );            // store the 0x0 byte
    pseudoHeaderByteBuffer.put( (byte)PROTO_NUM_TCP ); // stores the protocol number
    pseudoHeaderByteBuffer.putShort( (short) length ); // store the length of the packet.
    byte[] pbuf = pseudoHeaderByteBuffer.array();

    // loop through all 16-bit words of the psuedo header
    int bytesLeft = pseudoHeaderLength;
    while( bytesLeft > 0 ){
        // store the bytes at pbuf[i] and pbuf[i+1] in data.
        data = ( ((pbuf[i] << 8) & 0xFF00) | ((pbuf[i + 1]) & 0x00FF));
        sum += data;
            
        // Check if the sum has bit 17 or higher set by doing a binary AND with the 46 most significant bits and 0xFFFFFFFFFF0000. 
        if( (sum & 0xFFFFFFFF0000) > 0 ){
            sum = sum & 0xFFFF;     // discard all but the 16 least significant bits.
            sum += 1;   // add 1 (because we have to do a one's complement sum where you add the carry bit to the sum).
        }
        i += 2; // point to the next two bytes.
        bytesLeft -= 2;
    }
        
        
    // loop through all 16-bit words of the TCP packet (ie. until there's only 1 or 0 bytes left).
    bytesLeft = length;
    i=0;
    while( bytesLeft > 1 ){ // note that with the pseudo-header we could never have an odd byte remaining.
        // We do do exactly the same as with the pseudo-header but then for the TCP packet bytes.
        data = ( ((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0x00FF));
        sum += data;
            
        if( (sum & 0xFFFF0000) > 0 ){
            sum = sum & 0xFFFF;     
            sum += 1;   
        }
        i += 2;
        bytesLeft -= 2; 
    }
        
    // If the data has an odd number of bytes, then after adding all 16 bit words we remain with 8 bits.
    // In that case the missing 8 bits is considered to be all 0's.
    if( bytesLeft > 0 ){ // ie. there are 8 bits of data remaining.
        sum += (buf[i] << 8 & 0xFF00); // construct a 16 bit word holding buf[i] and 0x00 and add it to the sum.
        if( (sum & 0xFFFF0000) > 0) {
            sum = sum & 0xFFFF;
            sum += 1;
        }
    }
    sum = ~sum;             // Flip all bits (ie. take the one's complement as stated by the rfc)
    sum = sum & 0xFFFF;     // keep only the 16 least significant bits.
    return sum;
}

If you don't see anything wrong with the code then let me know that too. In that case I know to look somewhere else for the problem.


Solution

  • I've tested your code and it works correctly. I've done the following:

    • Configure wireshark to "Validate the TCP checksum if possible" in order to avoid to do the test with a packet with an incorrect checksum.

    • Add the long type suffix L to the constant 0xFFFFFFFF0000 in order to avoid the compile time error integer number too large (Java 8).

    • Use an hexadecimal representation of a TCP segment coming from wireshark

      String tcpSegment = "0050dc6e5add5b4fa9bf9ad8a01243e0c67c0000020405b4010303000101080a00079999000d4e0e";
      
    • Use a method to convert an hexadecimal string to a byte array

      public static byte[] toByteArray(String strPacket) {
          int len = strPacket.length();
          byte[] data = new byte[len / 2];
          for (int i = 0; i < len; i += 2) {
              data[i / 2] = (byte) ((Character.digit(strPacket.charAt(i), 16) << 4)
                      + Character.digit(strPacket.charAt(i + 1), 16));
          }
          return data;
      }
      
    • Use a ByteBuffer to write the source and destination adress into an int

      int src = ByteBuffer.wrap(toByteArray("c0a80001")).getInt();
      int dst = ByteBuffer.wrap(toByteArray("c0a8000a")).getInt();
      

    With this, I obtain a checksum of C67C, the same as in wireshark.

    P.S.: There is an error in your code when you do

    pseudoHeaderByteBuffer.putShort( (short) length );
    

    you store the length in two's-complement inside the pseudo header which will be a problem if the length is greater than 2^15. You better used char which is 16 bit unsigned.