TCP校验和(Checksum)的原理和实现
概述
TCP校验和(Checksum)是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。
TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。TCP校验和、IP校验和的计算方法是基本一致的,除了计算的范围不同。
TCP的校验和是必需的,而UDP的校验和是可选的。TCP和UDP计算校验和时,都要加上一个12字节的伪首部。
伪首部
首先解释下伪首部的概念,伪首部的数据都是从IP数据报头获取的。其目的是让TCP检查数据是否已经正确到达目的地,只是单纯为了做校验用的。
struct { unsigned long saddr; //源地址 unsigned long daddr; //目的地址 char mbz; //强制置空 char ptcl; //协议类型 unsigned short tcpl; //TCP长度 }psd_header;12345678
伪首部共有12字节(前96Bits),包含如下信息:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)。
伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。
校验和计算
RFC 793的TCP校验和定义: The checksum field is the 16 bit one’s complement of the one’s complement sum of all 16-bit words in the header and text. If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right with zeros to form a 16-bit word for checksum purposes. The pad is not transmitted as part of the segment. While computing the checksum, the checksum field itself is replaced with zeros.
上述的定义说得很明确: 首先,把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。把TCP报头中的校验和字段置为0(否则就陷入鸡生蛋还是蛋生鸡的问题)。 其次,用反码相加法累加所有的16位字(进位也要累加)。 最后,对计算结果取反,作为TCP的校验和。
举个例子来解释该校验方法:
1、首先将检验和置零; 2、然后将TCP伪首部部分,TCP首部部分,数据部分都划分成16位的一个个16进制数 3、将这些数逐个相加,记得溢出的部分加到最低位上,这是循环加法: 0xc0a8+ 0x0166+……+0x0402=0x9b49 4、最后将得到的结果取反,则可以得到检验和位0x64b6
校验和反码求和 的实现
发送方:原码相加 ,并将高位叠加到低位,取反 ,得到反码求和结果,放入校验和 接收方:将所有原码 相加,高位叠加, 如全为1,则正确
下面为C实现较为原始的checksum算法,代码中对于算法做了比较详细的注释:
unsigned short checksum(unsigned short * addr, int count)
{ long sum = 0; /* 计算所有数据的16bit对之和 */ while( count > 1 ) { /* This is the inner loop */ sum += *(unsigned short*)addr++;
count -= 2 }
/* 如果数据长度为奇数,在该字节之后补一个字节(0), 然后将其转换为16bit整数,加到上面计算的校验和 中。 */ if( count > 0 ) { char left_over[2] = {0}; left_over[0] = *addr; sum += * (unsigned short*) left_over; }