Chinaunix首页 | 论坛 | 博客
  • 博客访问: 764457
  • 博文数量: 144
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1150
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-17 14:32
个人简介

小公司研发总监,既当司令也当兵!

文章分类

全部博文(144)

分类: LINUX

2015-09-16 11:36:34

概述

    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头部:



算法:

点击(此处)折叠或打开

  1. /**********************************************************************
  2. *FUNCTION: computeTCPChecksum
  3. *
  4. *ARGUMENTS:
  5. * ipHdr -- pointer to IP header
  6. * tcpHdr -- pointer to TCP header
  7. *
  8. *RETURNS:
  9. * The computed TCP checksum
  10. ***********************************************************************/
  11. UINT16_t computeTCPChecksum(unsigned char *ipHdr, unsigned char *tcpHdr)
  12. {
  13.     UINT32_t sum = 0;

  14.     // IP报文中长度
  15.     UINT16_t count = ipHdr[2] * 256 + ipHdr[3];
  16.     UINT16_t tmp;

  17.     unsigned char *addr = tcpHdr;

  18.     // 12字节伪首部
  19.     unsigned char pseudoHeader[12];

  20.     // 计算TCP头部和数据总长度(按字节)
  21.     // IP报文总长度 - IP头部长度(按4字节计算) = IP负载长度(TCP总长度)
  22.     count -= (ipHdr[0] & 0x0F) * 4;

  23.     // 复制ip头部中原地址和目的地址信息(iphdr[12-19])到伪头部中
  24.     memcpy(pseudoHeader, ipHdr+12, 8);

  25.     // 伪头部填充
  26.     pseudoHeader[8] = 0;

  27.     // IP头部中的上层协议
  28.     pseudoHeader[9] = ipHdr[9];

  29.     // 伪头部中TCP长度
  30.     pseudoHeader[10] = (count >> 8) & 0xFF;
  31.     pseudoHeader[11] = (count & 0xFF);

  32.     // 计算伪首部累加和
  33.     sum += * (UINT16_t *) pseudoHeader;
  34.     sum += * ((UINT16_t *) (pseudoHeader+2));
  35.     sum += * ((UINT16_t *) (pseudoHeader+4));
  36.     sum += * ((UINT16_t *) (pseudoHeader+6));
  37.     sum += * ((UINT16_t *) (pseudoHeader+8));
  38.     sum += * ((UINT16_t *) (pseudoHeader+10));

  39.     // 计算TCP头部和数据累加和
  40.     while (count > 1)
  41.     {
  42.         memcpy(&tmp, addr, sizeof(tmp));
  43.         sum += (UINT32_t) tmp;
  44.         addr += sizeof(tmp);
  45.         count -= sizeof(tmp);
  46.     }
  47.     
  48.     // 如果TCP总长度为奇数,最后一个字节单独计算
  49.     if (count > 0)
  50.     {
  51.         sum += (unsigned char) *addr;
  52.     }

  53.     // 如果所有累加和超过16位,把进位继续类型,然后重新验证
  54.     // 知道累加和可以用16位表达
  55.     while(sum >> 16)
  56.     {
  57.         sum = (sum & 0xffff) + (sum >> 16);
  58.     }

  59.     // 累加和取反
  60.     return (UINT16_t) ((~sum) & 0xFFFF);
  61. }


阅读(4316) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~