Linux 网络协议栈开发基础篇(一)—— TCP/IP协议报文格式解析

一、传输层报文

1、TCP数据包的头

Linux 网络协议栈开发基础篇(一)—— TCP/IP协议报文格式解析_第1张图片

typedef struct _TCP_HEADER {
	USHORT nSourPort ;   // 源端口号16bit
	USHORT nDestPort ;   // 目的端口号16bit
	UINT nSequNum ;   // 序列号32bit
	UINT nAcknowledgeNum ; // 确认号32bit
	USHORT nHLenAndFlag ;  // 前4位:TCP头长度;中6位:保留;后6位:标志位16bit
	USHORT nWindowSize ;  // 窗口大小16bit
	USHORT nCheckSum ;   // 检验和16bit
	USHORT nrgentPointer ;  // 紧急数据偏移量16bit
} TCP_HEADER, *PTCP_HEADER ;

2、UDP数据包的头

IP/TCP/UDP头数据结构定义 - Jimmy - jimmyloveforever

typedef struct _UDP_HEADER {
	USHORT nSourPort ;   // 源端口号16bit
	USHORT nDestPort ;   // 目的端口号16bit
	USHORT nLength ;   // 数据包长度16bit
	USHORT nCheckSum ;   // 校验和16bit
} UDP_HEADER, *PUDP_HEADER ;

进入协议栈的过程:(从协议栈出来刚好相反)

二、网络层报文

1、IP头

     IP数据包也叫IP报文分组,传输在ISO网络7层结构中的网络层,它由IP报文头和IP报文用户数据组成,IP报文头的长度一般在20到60个字节之间,而一个IP分组的最大长度则不能超过65535个字节。 

    下图为IP分组的报文头格式,报文头的前20个字节是固定的,后面的可变。

 Linux 网络协议栈开发基础篇(一)—— TCP/IP协议报文格式解析_第2张图片

版本号 4 bit ,用来标识 IP 版本号。这个 4 位字段的值设置为二进制的 0100 表示 IPv4 ,设置为 0110 表示 IPv6 。目前使用的 IP 协议版本号是 4
首部长度 4 bit 。标识包括选项在内的 IP 头部字段的长度。
服务类型 8 bit 。服务类型字段被划分成两个子字段: 3bit 的优先级字段和 4bit TOS 字段,最后一位置为 0 4bit TOS 分别代表:最小时延,最大吞吐量,最高可靠性和最小花费。 4bit 中只能将其中一个 bit 位置 1 。如果 4 bit 均为 0 ,则代表一般服务。
 
现在大多数的 TCP/IP 实现都不支持 TOS 特性,但自 4.3BSD Reno 以后的新版系统都对它进行了设置。另外, OSPF IS-IS 都可以根据这些字段的值进行路由策略。而类似 SLIP 这样的协议虽然提供基于服务类型的排队方法,允许对交互型数据优先进行处理,但它的这种排队机制由 SLIP 自身来判断和处理。驱动程序会先查看协议字段(确定是否是 TCP 段),然后检查 TCP 信源和信宿的端口号来判断是否是一个交互服务。
 
最近, TOS 字段已经作为区分服务( Diffserv )架构的一部分被重新定义了。 Diffserv TOS 定义所允许的处理更加灵活。在 Diffserv 下,能够在一台路由器上定义服务分类( COS ),将数据包归类到这些分类中去。路由器可以根据它们的分类使用不同的优先级对数据包进行转发。每一个排序和转发处理称为一个 PHB 。这个架构也被简称为 COS
 
利用开始的 6 个位构成 DSCP 位,可以使用任意数值或根据区分服务体系结构中预先定义的服务类别,最多可以定义 64 个不同的服务类别并整理到 PHB 中。 ECN 为显式拥塞通知位。当路由器支持该特性时,这些位可以用于拥塞信号( ECN=11 )。
 
总长度字段 16 bit 。接收者用 IP 数据报总长度减去 IP 报头长度就可以确定数据包数据有效负荷的大小。 IP 数据报最长可达 65535 字节。
 
标识字段 16 bit 。唯一的标识主机发送的每一份数据报。接收方根据分片中的标识字段是否相同来判断这些分片是否是同一个数据报的分片,从而进行分片的重组。通常每发送一份报文它的值就会加 1
标志字段 3 bit 。用于标识数据报是否分片。第 1 位没有使用,第 2 位是不分段( DF )位。当 DF 位被设置为 1 时,表示路由器不能对数据包进行分段处理。如果数据包由于不能分段而未能被转发,那么路由器将丢弃该数据包并向源发送 ICMP 不可达。第 3 位是分段( MF )位。当路由器对数据包进行分段时,除了最后一个分段的 MF 位被设置为 0 外,其他的分段的 MF 位均设置为 1 ,以便接收者直到收到 MF 位为 0 的分片为止。
位偏移 13 bit 。在接收方进行数据报重组时用来标识分片的顺序。用于指明分段起始点相对于报头起始点的偏移量。由于分段到达时可能错序,所以位偏移字段可以使接收者按照正确的顺序重组数据包。
当数据包的长度超过它所要去的那个数据链路的MTU时,路由器要将它分片。数据包中的数据将被分成小片,每一片被封装在独立的数据包中。接收端使用标识符,分段偏移以及标记域的MF位来进行重组。
 
生存时间 8 bit TTL 域防止丢失的数据包在无休止的传播。该域包含一个 8 位整数,此数由产生数据包的主机设定。 TTL 值设置了数据报可以经过的最多的路由器数。 TTL 的初始值由源主机设置(通常为 32 64 ),每经过一个处理它的路由器, TTL 值减 1 。如果一台路由器将 TTL 减至 0 ,它将丢弃该数据包并发送一个 ICMP 超时消息给数据包的源地址。注意: TTL值经过PIX时不减1
协议字段 8 bit 。用来标识是哪个协议向 IP 传送数据。 ICMP 1 IGMP 2 TCP 6 UDP 17 GRE 47 ESP 50
首部校验和 :根据 IP 首部计算的校验和码。
源IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。 
目的IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
Option 选项 :是数据报中的一个可变长的可选信息。

选项字段以 32bit 为界,不足时插入值为 0 的填充字节。保证 IP 首部始终是 32bit 的整数倍。
由于Delphi里面没有位域这个概念,所以定义结构的时候只能整字节了,挺怀恋C++或者Erlang的,有位域定义出来和使用起来都很方便了 

//IP包 
  TIPHeader = packed record 
    iph_verlen: byte;           // 版本和长度 
    iph_tos: byte;              // 服务类型 
    iph_length: word;           // 总长度,2个无符号字节所以只能65535 
    iph_id: word;               // 标识 
    iph_offset: word;           // 标志和片偏移 
    iph_ttl: byte;              // 生存时间 
    iph_protocol: byte;         // 协议 
    iph_xsum: word;             // 头校验和 
    iph_src: longword;          // 源地址 
    iph_dest: longword;         // 目的地址 
  end;

这个结构体有什么用呢?其实在嗅探的时候就很有用了.

2、ICMP头和报文校验和的计算

Linux 网络协议栈开发基础篇(一)—— TCP/IP协议报文格式解析_第3张图片 

//定义ICMP包头
typedef struct _ICMP_HEADER {
	BYTE bType ;   // 类型8bit
	BYTE bCode ;   // 代码8bit
	USHORT nCheckSum ;  // 校验和16bit
	USHORT nId ;   // 标识,本进程ID16bit
	USHORT nSequence ;  // 序列号16bit
	UINT nTimeStamp ; // 可选项,这里为时间,用于计算时间32bit
} ICMP_HEADER, *PICMP_HEADER ;

发送ICMP报文时,必须由程序自己计算校验和,将它填入ICMP头部对应的域中。校验和的计算方法是:

将数据以字(16位)为单位累加到一个双字中(强转换双字类型),如果数据长度为奇数(奇数个字节),最后一个字节将被扩展到字,累加的结果是一个双字,最后将这个双字的高16位和低16位相加后取反,便得到了校验和!

// 计算ICMP包校验值
// 参数1:ICMP包缓冲区
// 参数2:ICMP包长度
USHORT GetCheckSum ( LPBYTE lpBuf, DWORD dwSize )
{
	DWORD dwCheckSum = 0 ;
	USHORT* lpWord = (USHORT*)lpBuf ;

	// 累加
	while ( dwSize > 1 )
	{
		dwCheckSum += *lpWord++ ;
		dwSize -= 2 ;
	}

	// 如果长度是奇数
	if ( dwSize == 1 )
		dwCheckSum += *((LPBYTE)lpWord) ;

	// 高16位和低16位相加
	dwCheckSum = ( dwCheckSum >> 16 ) + ( dwCheckSum & 0xFFFF ) ;

	// 取反
	return (USHORT)(~dwCheckSum ) ;
}

Linux 网络协议栈开发基础篇(一)—— TCP/IP协议报文格式解析_第4张图片

你可能感兴趣的:(Linux网络协议栈开发)