IP/TCP/UDP中Checksum的计算

IP Header Checksum

  IP Header Checksum顾名思义,只计算IP头部字段的校验和,参照《计算机网络——自顶向下方法:第四版》中的说法,IP Header Checksum的计算方法——通过IP Header生成Checksum——为:

  1. 计算前把Checksum字段置0;
  2. 将IP Header中每两个连续的字节当成一个16bit数,对所有的16bit数进行求和,在求和过程中,任何溢出16bit数范围的求和结果都需要进行回卷——将溢出的高16bit和求和结果的低16bit相加;
  3. 对最终的求和结果按位取反,即可得到IP Header Checksum

  IP Header Checksum的验证方法为:

  1. 只需进行Checksum计算中的第二步,若最终结果为0xFFFF则说明IP Header无差错。

TCP/UDP Checksum

  TCP/UDP Checksum的计算方法和IP Header Checksum的计算方法类似,不同点如下:

  1. TCP/UDP的有效载荷(payload)会参与Checksum的计算;
  2. TCP/UDP会额外将一个伪首部加入计算,伪首部包括:32bit的源/目的IP地址,8bit的补零,8bit的协议号,以及16bit的TCP/UDP报文长度(头+数据)
  3. 在计算过程中,TCP/UDP报文总字节数可能为奇数,最后剩余的单个字节直接和中间结果相加(相当于高8bit补0)pseudo header

!!!注意:计算/验证Checksum时,都是对网络字节序表示的数据包直接进行计算

Checksum计算代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>


uint16_t calc_cksm(void *pkt, int len)
{
    uint16_t *buf = (uint16_t*)pkt;
    uint32_t cksm = 0;
    while(len > 1)
    {
        cksm += *buf++;
        cksm = (cksm >> 16) + (cksm & 0xffff);
        len -= 2;
    }
    if(len)
    {
        cksm += *((uint8_t*)buf);
        cksm = (cksm >> 16) + (cksm & 0xffff);
    }
    return (uint16_t)((~cksm) & 0xffff);
}


/** real packet: 0x0000: 0021 97aa 14f5 3822 d6bf 5100 0800 4500 0x0010: 003c 0000 4000 3e06 6ab6 9fe2 2716 0a18 0x0020: 00f6 0050 b469 e522 8dc4 8189 6718 a012 0x0030: 3890 32d5 0000 0204 05b4 0402 080a 3c62 0x0040: 232b 0091 9a24 0103 0306 IP报文总长度:60B (0x003c) TCP报文总长度:40B (60B-20B) IP Header checksum: 6ab6 TCP checksum: 32d5 **/

//真实报文
uint8_t pkt[] = 
{
    0x00, 0x21, 0x97, 0xaa, 0x14, 0xf5, 0x38, 0x22, 
    0xd6, 0xbf, 0x51, 0x00, 0x08, 0x00, 0x45, 0x00,
    0x00, 0x3c, 0x00, 0x00, 0x40, 0x00, 0x3e, 0x06, 
    0x6a, 0xb6, 0x9f, 0xe2, 0x27, 0x16, 0x0a, 0x18,
    0x00, 0xf6, 0x00, 0x50, 0xb4, 0x69, 0xe5, 0x22, 
    0x8d, 0xc4, 0x81, 0x89, 0x67, 0x18, 0xa0, 0x12,
    0x38, 0x90, 0x32, 0xd5, 0x00, 0x00, 0x02, 0x04, 
    0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0x3c, 0x62,
    0x23, 0x2b, 0x00, 0x91, 0x9a, 0x24, 0x01, 0x03, 
    0x03, 0x06
};

//加入了伪首部的tcp报文
uint8_t pseudo_hdr_tcp[] = 
{
    //pseudo header
    0x9f, 0xe2, 0x27, 0x16, 0x0a, 0x18, 0x00, 0xf6,
    0x00, 0x06, 0x00, 0x28,
    //tcp
    0x00, 0x50, 0xb4, 0x69, 0xe5, 0x22, 0x8d, 0xc4, 
    0x81, 0x89, 0x67, 0x18, 0xa0, 0x12, 0x38, 0x90, 
    0x32, 0xd5, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 
    0x04, 0x02, 0x08, 0x0a, 0x3c, 0x62, 0x23, 0x2b, 
    0x00, 0x91, 0x9a, 0x24, 0x01, 0x03, 0x03, 0x06
};

int main()
{
    uint8_t *ip_hdr = (uint8_t*)&pkt[14];

    //checksum置0
    pkt[24] = 0;
    pkt[25] = 0;
    printf("ip cksm %04x\n", calc_cksm((void*)ip_hdr, 20));

    //checksum置0
    pseudo_hdr_tcp[28] = 0;
    pseudo_hdr_tcp[29] = 0;
    //12B伪首部+40Btcp报文
    printf("tcp cksm %04x\n", calc_cksm((void*)pseudo_hdr_tcp, 52));
    return 0;
}

  上述代码的执行结果是:

ip cksm b66a 
tcp cksm d532

  按照主机字节序,6a应该在低字节,b6应该在高字节,所以,按从低字节在左、高字节在右的显示方式(如real packet注释中),两个Checksum分别为:6a b6, 32 d5,与real packet中保存的Checksum是一致的

相关文章
相关标签/搜索