【问题标题】:Wireshark checksum does not matchWireshark 校验和不匹配
【发布时间】:2014-07-25 04:14:51
【问题描述】:

我已经编写了一个函数来计算给定 tcp 数据包的校验和。但是,当我捕获通过 ipv4 从wireshark 发送的tcp 数据包并让我的函数计算其校验和时,它与wireshark 捕获的数据包中的校验和不同。我检查了,我给 computeChecksum 函数的字节与我用wireshark捕获的tcp数据包字节完全相同。

我根据RFC 793 计算了校验和。有人看看我的代码有什么问题吗?

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;
}

如果您没有发现代码有任何问题,请也告诉我。在那种情况下,我知道要在其他地方寻找问题。

【问题讨论】:

  • 这可能是字节序问题吗?
  • 我不认为这是因为字节顺序无关紧要。最后,您只需添加所有字节,当总和溢出 16 位时,您将 1 添加到前面。改变字节序只是意味着总和在不同的时间溢出。
  • 但可能需要以不同的字节顺序放入数据包中。
  • 但是无论数据包中字节的顺序如何,校验和都不会相同吗?正如我所看到的,如果您计算数据包中字节的校验和,然后随意调整字节的顺序,然后再次计算校验和,您应该得到完全相同的校验和。还是我错了?或者你的意思是校验和需要以不同的顺序输入?我不认为是这样,因为我根本没有将校验和放回数据包中。
  • 我相信如果你重新排序所有 2 字节的集合,它会是相同的(因为你正在做 16 位加法)。所以也许这两个字节中的每一个都是不同的顺序(我相信网络使用小端,所以它应该是data = (pbuff[i] | pbuff[i+1] &lt;&lt; 8 )。另外,如果你要将校验和写入数据包,我相信你可能必须切换字节再次。

标签: java tcp wireshark checksum


【解决方案1】:

我已经测试了您的代码,它可以正常工作。我做了以下事情:

  • 将 wireshark 配置为“如果可能,验证 TCP 校验和”以避免对校验和不正确的数据包进行测试。

  • 将long类型后缀L添加到常量0xFFFFFFFF0000以避免编译时错误integer number too large(Java 8)。

  • 使用来自 wireshark 的 TCP 段的十六进制表示

    String tcpSegment = "0050dc6e5add5b4fa9bf9ad8a01243e0c67c0000020405b4010303000101080a00079999000d4e0e";
    
  • 使用方法将十六进制字符串转换为字节数组

    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;
    }
    
  • 使用 ByteBuffer 将源地址和目标地址写入int

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

这样,我得到了C67C的校验和,和wireshark一样。

P.S.:当你这样做时,你的代码中有错误

pseudoHeaderByteBuffer.putShort( (short) length );

您将长度存储在伪标头内的二进制补码中,如果长度大于 2^15,这将是一个问题。您最好使用 16 位无符号的 char

【讨论】:

    猜你喜欢
    • 2021-03-20
    • 2014-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    • 2019-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多