概述
TCP校验和是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。
TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。
TCP的校验和是必需的,而UDP的校验和是可选的。
TCP和UDP计算校验和时,都要加上一个12字节的伪首部。
伪首部
伪首部共有12字节,包含如下信息:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)。伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。
定义
(1) 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的校验和。
实现
下面两图展示了IP头部和TCP头部内容和字节序号对应关系,方便后续代码阅读:
图一,IP头部
图二, TCP头部:
算法:
-
/**********************************************************************
-
*FUNCTION: computeTCPChecksum
-
*
-
*ARGUMENTS:
-
* ipHdr -- pointer to IP header
-
* tcpHdr -- pointer to TCP header
-
*
-
*RETURNS:
-
* The computed TCP checksum
-
***********************************************************************/
-
UINT16_t computeTCPChecksum(unsigned char *ipHdr, unsigned char *tcpHdr)
-
{
-
UINT32_t sum = 0;
-
-
// IP报文中长度
-
UINT16_t count = ipHdr[2] * 256 + ipHdr[3];
-
UINT16_t tmp;
-
-
unsigned char *addr = tcpHdr;
-
-
// 12字节伪首部
-
unsigned char pseudoHeader[12];
-
-
// 计算TCP头部和数据总长度(按字节)
-
// IP报文总长度 - IP头部长度(按4字节计算) = IP负载长度(TCP总长度)
-
count -= (ipHdr[0] & 0x0F) * 4;
-
-
// 复制ip头部中原地址和目的地址信息(iphdr[12-19])到伪头部中
-
memcpy(pseudoHeader, ipHdr+12, 8);
-
-
// 伪头部填充
-
pseudoHeader[8] = 0;
-
-
// IP头部中的上层协议
-
pseudoHeader[9] = ipHdr[9];
-
-
// 伪头部中TCP长度
-
pseudoHeader[10] = (count >> 8) & 0xFF;
-
pseudoHeader[11] = (count & 0xFF);
-
-
// 计算伪首部累加和
-
sum += * (UINT16_t *) pseudoHeader;
-
sum += * ((UINT16_t *) (pseudoHeader+2));
-
sum += * ((UINT16_t *) (pseudoHeader+4));
-
sum += * ((UINT16_t *) (pseudoHeader+6));
-
sum += * ((UINT16_t *) (pseudoHeader+8));
-
sum += * ((UINT16_t *) (pseudoHeader+10));
-
-
// 计算TCP头部和数据累加和
-
while (count > 1)
-
{
-
memcpy(&tmp, addr, sizeof(tmp));
-
sum += (UINT32_t) tmp;
-
addr += sizeof(tmp);
-
count -= sizeof(tmp);
-
}
-
-
// 如果TCP总长度为奇数,最后一个字节单独计算
-
if (count > 0)
-
{
-
sum += (unsigned char) *addr;
-
}
-
-
// 如果所有累加和超过16位,把进位继续类型,然后重新验证
-
// 知道累加和可以用16位表达
-
while(sum >> 16)
-
{
-
sum = (sum & 0xffff) + (sum >> 16);
-
}
-
-
// 累加和取反
-
return (UINT16_t) ((~sum) & 0xFFFF);
-
}
阅读(4225) | 评论(0) | 转发(0) |