TCP/IP校验和
1. IP 头
https://www.cnblogs.com/xiehy/p/3166953.html
IP数据包也叫IP报文分组,传输在ISO网络7层结构中的网络层,它由IP报文头和IP报文用户数据组成,IP报文头的长度一般在20到60个字节之间,而一个IP分组的最大长度则不能超过65535个字节。
下图为IP分组的报文头格式,报文头的前20个字节是固定的,后面的可变。
版本:占4位(bit) |
指IP协议的版本号。目前的主要版本为IPV4,即第4版本号,也有一些教育网和科研机构在使用IPV6。在进行通信时,通信双方的IP协议版本号必须一致,否则无法直接通信。 |
首部长度:占4位(bit) |
指IP报文头的长度。最大的长度(即4个bit都为1时)为15个长度单位,每个长度单位为4字节(TCP/IP标准,DoubleWord),所以IP协议报文头的最大长度为60个字节,最短为上图所示的20个字节。 |
服务类型:占8位(bit) |
用来获得更好的服务。其中的前3位表示报文的优先级,后面的几位分别表示要求更低时延、更高的吞吐量、更高的可靠性、更低的路由代价等。对应位为1即有相应要求,为0则不要求。 |
总长度:16位(bit) |
指报文的总长度(包括报文头)。注意这里的单位为字节,而不是4字节,所以一个IP报文的的最大长度为65535个字节。 |
标志(flag) |
该字段用于标记该报文是否为分片(有一些可能不需要分片,或不希望分片),后面是否还有分片(是否是最后一个分片)。 |
片偏移 |
指当前分片在原数据报(分片前的数据报)中相对于用户数据字段的偏移量,即在原数据报中的相对位置。 |
生存时间:TTL(Time to Live) |
该字段表明当前报文还能生存多久。每经过1ms或者一个网关,TTL的值自动减1,当生存时间为0时,报文将被认为目的主机不可到达而丢弃。使用过Ping命令的用户应该有印象,在windows中输入ping命令,在返回的结果中即有TTL的数值。 |
协议 |
该字段指出在上层(网络7层结构或TCP/IP的传输层)使用的协议,可能的协议有UDP、TCP、ICMP、IGMP、IGP等。 |
首部校验和 |
用于检验IP报文头部在传播的过程中是否出错,主要校验报文头中是否有某一个或几个bit被污染或修改了。 |
源IP地址:32位(bit) |
4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。 |
目的IP地址:32位(bit) |
4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。 |
/*IP头定义,共20个字节*/
typedef struct _IP_HEADER
{
char m_cVersionAndHeaderLen; //版本信息(前4位),头长度(后4位)
char m_cTypeOfService; // 服务类型8位
short m_sTotalLenOfPacket; //数据包长度
short m_sPacketID; //数据包标识
short m_sSliceinfo; //分片使用
char m_cTTL; //存活时间
char m_cTypeOfProtocol; //协议类型
short m_sCheckSum; //校验和
unsigned int m_uiSourIp; //源ip
unsigned int m_uiDestIp; //目的ip
} __attribute__((packed))IP_HEADER, *PIP_HEADER ;
2. IP校验和
https://www.cnblogs.com/yd1227/archive/2011/06/30/2094309.html
当发送IP包时,需要计算IP报头的校验和:
1、 把校验和字段置为0;
2、 对IP头部中的每16bit进行二进制求和;
3、 如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值;
4、 将该16bit的值取反,存入校验和字段。
3. TCP head
https://www.cnblogs.com/li-hao/archive/2011/12/07/2279912.html
源端口: 长度为16位,2个字节。
目的端口: 长度为16位,2个字节。
IP实现了点到点的数据通信,而TCP实现的是端到端的通信。
通信端用一个IP与端口号来唯一标识。(其实端口号就是用来标识同一主机中的不同进程。)
IP协议负责将数据传输到目标主机,而TCP可以根据数据报中的端口号,将数据交给相应的程序进行处理。
序列号: 长度32位,4个字节。
确认序列号:长度32位,4个字节。
头部长度:该字段占用4位,用来表示报文首部的长度,单位是4Byte。如:headLen = ((packet[12]>>4)&0x0F)*4;
预留6位:长度为6位,作为保留字段,暂时没有什么用处。
URG:长1位,表示紧急指针字段有效;
ACK:长1位,置位表示确认号字段有效;
PSH:长1位,表示当前报文需要请求推(push)操作;
RST:长1位,置位表示复位TCP连接;
SYN:长1位,用于建立TCP连接时同步序号;
FIN:长1位,用于释放TCP连接时标识发送方比特流结束;
窗口大小:长度为16位,2个字节。
校验和:长度为16位,2个字节。
紧急指针:长度为16位,2个字节。
以上是TCP包头必须要有的字段,也称固有字段,长度为20个字节。
可选项:此项是可选项(可有可无),解包时得具体分析(是TCP包头的一部分)。
/*TCP头定义,共20个字节*/
typedef struct _TCP_HEADER
{
short m_sSourPort; // 源端口号16bit
short m_sDestPort; // 目的端口号16bit
unsigned int m_uiSequNum; // 序列号32bit
unsigned int m_uiAcknowledgeNum; // 确认号32bit
short m_sHeaderLenAndFlag; // 前4位:TCP头长度;中6位:保留;后6位:标志位
short m_sWindowSize; // 窗口大小16bit
short m_sCheckSum; // 检验和16bit
short m_surgentPointer; // 紧急数据偏移量16bit
}__attribute__((packed))TCP_HEADER, *PTCP_HEADER;
/*TCP头中的选项定义
kind(8bit)+Length(8bit,整个选项的长度,包含前两部分)+内容(如果有的话)
KIND = 1表示 无操作NOP,无后面的部分
2表示 maximum segment 后面的LENGTH就是maximum segment选项的长度(以byte为单位,1+1+内容部分长度)
3表示 windows scale 后面的LENGTH就是 windows scale选项的长度(以byte为单位,1+1+内容部分长度)
4表示 SACK permitted LENGTH为2,没有内容部分
5表示这是一个SACK包 LENGTH为2,没有内容部分
8表示时间戳,LENGTH为10,含8个字节的时间戳
*/
typedef struct _TCP_OPTIONS
{
char m_ckind;
char m_cLength;
char m_cContext[32];
}__attribute__((packed))TCP_OPTIONS, *PTCP_OPTIONS;
4. TCP校验和
TCP头前加一个伪首部,其它步骤和IP类似。
unsigned short CheckSum(unsigned short *_pBuff, int _Size) { unsigned int ckSum = 0;
unsigned short *tmpBuff = _pBuff; int tmpSize = _Size;
while (tmpSize > 1) { ckSum += *tmpBuff ++; tmpSize -= sizeof(unsigned short); }
if (tmpSize > 0) { ckSum += *(unsigned char*)tmpBuff; }
ckSum = (ckSum >> 16) + (ckSum & 0xFFFF); //将高16bit与低16bit相加 ckSum += (ckSum >> 16); //将进位到高位的16bit与低16bit 再相加
return (unsigned short)(~ckSum); } |
5. 检验和更新
https://blog.csdn.net/qq_43395215/article/details/103414009
如果在报文的转发路径中遇到需要修改ip、tcp中部分报文字段,那么校验和需要重新计算。
当修改只是一小部分时,可以用下列方法更新校验和。
IP头校验和、TCP/UDP校验和更新
unsigned short updateCheckSum(unsigned short wOldCheckSum, unsigned short wOld, unsigned short wNew) { unsigned int dwCheckSum = wOldCheckSum + wOld + (~wNew & 0xFFFF); dwCheckSum = (dwCheckSum >> 16) + (dwCheckSum & 0xFFFF); dwCheckSum += (dwCheckSum >> 16); return (unsigned short)dwCheckSum; }
unsigned short updateCheckSum32(unsigned short wOldCheckSum, unsigned int dwOld, unsigned int dwNew) { return UpdateCheckSum(UpdateCheckSum(wOldCheckSum, (unsigned short) (dwOld >> 16), (unsigned short) (dwNew >> 16)), (unsigned short) dwOld, (unsigned short) dwNew); } |