IP Header Checksum
IP Header Checksum顾名思义,只计算IP头部字段的校验和,参照《计算机网络——自顶向下方法:第四版》中的说法,IP Header Checksum的计算方法——通过IP Header生成Checksum——为:
- 计算前把Checksum字段置0;
- 将IP Header中每两个连续的字节当成一个16bit数,对所有的16bit数进行求和,在求和过程中,任何溢出16bit数范围的求和结果都需要进行回卷——将溢出的高16bit和求和结果的低16bit相加;
- 对最终的求和结果按位取反,即可得到IP Header Checksum
IP Header Checksum的验证方法为:
- 只需进行Checksum计算中的第二步,若最终结果为
0xFFFF则说明IP Header无差错。
TCP/UDP Checksum
TCP/UDP Checksum的计算方法和IP Header Checksum的计算方法类似,不同点如下:
- TCP/UDP的有效载荷(payload)会参与Checksum的计算;
- TCP/UDP会额外将一个伪首部加入计算,伪首部包括:32bit的源/目的IP地址,8bit的补零,8bit的协议号,以及16bit的TCP/UDP报文长度(头+数据)
- 在计算过程中,TCP/UDP报文总字节数可能为奇数,最后剩余的单个字节直接和中间结果相加(相当于高8bit补0)
!!!注意:计算/验证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是一致的
本文详细介绍了IP、TCP和UDP头部Checksum的计算方法。IP Header Checksum仅计算头部,而TCP/UDP Checksum则包括有效载荷和伪首部。在计算中,涉及字节的组合、溢出处理和取反操作。TCP/UDP的伪首部包含源/目的IP地址、协议号和报文长度。最后,提供了一段Checksum计算代码,并验证了其正确性。



被折叠的 条评论
为什么被折叠?



