一、以太网帧格式

1.1 MAC 地址头

  • https://juejin.cn/post/7087218595159080968

MAC帧是数据帧的一种。而所谓数据帧,就是数据链路层的协议数据单元,它包括三部分:帧头,数据部分,帧尾。其中,帧头和帧尾包含一些必要的控制信息,比如同步信息、地址信息、差错控制信息等;数据部分则包含网络层传下来的数据,比如ip数据包。

目前,有四种不同格式的以太网帧在使用,它们分别是:
Ethernet II即DIX 2.0:Xerox与DEC、Intel在1982年制定的以太网标准帧格式。Cisco名称为:ARPA。
Ethernet 802.3 raw:Novell在1983年公布的专用以太网标准帧格式。Cisco名称为:Novell-Ether。
Ethernet 802.3 SAP:IEEE在1985年公布的Ethernet 802.3的SAP版本以太网帧格式。Cisco名称为:SAP。
Ethernet 802.3 SNAP:IEEE在1985年公布的Ethernet 802.3的SNAP版本以太网帧格式。Cisco名称为:SNAP。
Ethernet II和IEEE802.3的帧格式比较类似,主要的不同点在于前者定义的2字节为包类型,而后者定义的2字节为的长度;所幸的是,后者定义的有效长度值与前者定义的有效类型值无一相同,这样就容易区分两种帧格式了。如果值大于 1500(0x05DC),说明是以太网类型字段,EthernetII 帧格式。如果值小于等于1500,说明是长度字段,IEEE802.3 帧格式。因此类型字段值最小的是 0x0600。而长度最大为 1500。

/*数据帧定义,头14个字节,尾4个字节*/
typedef struct _MAC_FRAME_HEADER
{
 char m_cDstMacAddress[6];    //目的mac地址
 char m_cSrcMacAddress[6];    //源mac地址
 short m_cType;            //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp
}__attribute__((packed))MAC_FRAME_HEADER,*PMAC_FRAME_HEADER;

 

typedef struct _MAC_FRAME_TAIL
{
 unsigned int m_sCheckSum;    //数据帧尾校验和
}__attribute__((packed))MAC_FRAME_TAIL, *PMAC_FRAME_TAIL;
  • 以太类帧型:占16个比特,表示协议类型。
    • 0800:IP协议
    • 0806:ARP 协议
    • 86DD:IPv6
    • 0000-05DC:IEEE 802.3

1.2 IP头结构的定义

  • 固定头:20 个字节
  • 变长头:20 + 额外最多40字节

在这里插入图片描述
/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 ;

IP 头字段说明:

  • 版本(Version)字段:占4比特。用来表明IP协议实现的版本号,当前一般为IPv4,即0100。
  • 首部长度(Internet Header Length,IHL)字段:占4比特。因为头部长度不固定(Option可选部分不固定),所以需要标识该分组的头部长度多少,用4bit表示,以4byte为单位,取值范围:5-15,即20(54)-60(154)byte(其他字段也是类似的计算方式,因为bit位是不够表示该字段的值)
  • 服务类型(Type of Service ,TOS)字段:占8比特。其中前3比特为优先权子字段(Precedence,现已被忽略)。第8比特保留未用。第4至第7比特分别代表延迟、吞吐量、可靠性和花费。当它们取值为1时分别代表要求最小时延、最大吞吐量、最高可靠性和最小费用。这4比特的服务类型中只能置其中1比特为1。可以全为0,若全为0则表示一般服务。服务类型字段声明了数据报被网络系统传输时可以被怎样处理。例如:TELNET协议可能要求有最小的延迟,FTP协议(数据)可能要求有最大吞吐量,SNMP协议可能要求有最高可靠性,NNTP(Network News Transfer Protocol,网络新闻传输协议)可能要求最小费用,而ICMP协议可能无特殊要求(4比特全为0)。实际上,大部分主机会忽略这个字段,但一些动态路由协议如OSPF(Open Shortest Path First Protocol)、IS-IS(Intermediate System to Intermediate System Protocol)可以根据这些字段的值进行路由决策。RFC2474的ToS取消了IP precedence字段而使用了DSCP,QoS里有描述,给QoS用来打标签。TOS字段历史:
  • 总长度:占16比特。指明整个数据报的长度(以字节为单位,含头长度)。最大长度为65535字节。可用总长度减去头部长度获得实际报文数据的长度,取值范围0-65535byte,链路只允许1500byte,所以一般都需要MTU分片 。
  • 标志字段:占16比特。用来唯一地标识主机发送的每一份数据报。通常每发一份报文,它的值会加1。通常与标记字段和分片偏移字段一起用于IP报文的分片。当原始报文大小超过MTU,那么就必须将原始数据进行分片。每个被分片的报文大小不得超过MTU,而这个字段还将在同一原始文件被分片的报文上打上相同的标记,以便接收设备可以识别出属于同一个报文的分片,“类似于进程号”,有时候电信会用他来识别流量是否是同一台主机(因为做了PAT后源ip都是一样的,鸡贼!)
    • 注意:Ethernet以太网跟802.3以太网有所区别,802.3是由IEEE指定的标准,比较复杂用的比较少,网卡一般两种都支持。IP数据包的MTU值在各种物理线路环境下对应的MTU取值:(注意:不包含帧头和尾)
  • 标志位字段:占3比特。标志一份数据报是否要求分段。
    • 第1位没有被使用。
    • 第2位D是不分片位(DF),Do not fragment,顾名思义,不要分片,当DF位设置为1时,表示路由器不能对报文进行分片处理。
    • 第3位M表示还有后继分片(MF),More fragment,多分片,当路由器对报分进行分片时,除了最后一个分片的MF位设置为0外,其他所有分片的MF位均设置1,以便接收者直到收到MF位为0的分片为止
  • 段偏移字段:占13比特。如果一份数据报要求分段的话,此字段指明该段偏移距原始数据报开始的位置。
  • 生存期(TTL:Time to Live)字段:占8比特。用来设置数据报最多可以经过的路由器数。由发送数据的源主机设置,通常为32、64(win7)、128、255(linux)等。每经过一个路由器,其值减1,直到0时该数据报被丢弃。
  • 协议字段:占8比特。指明IP层所封装的上层协议类型,如ICMP(1)、IGMP(2) 、TCP(6)、UDP(17)等。
  • 头部校验和字段:占16比特。内容是根据IP头部计算得到的校验和码。计算方法是:对头部中每个16比特进行二进制反码求和。(和ICMP、IGMP、TCP、UDP不同,IP不对头部后的数据进行校验)。
  • 源IP地址、目标IP地址字段:各占32比特。用来标明发送IP数据报文的源主机地址和接收IP报文的目标主机地址。
  • 可选项字段:占32比特。用来定义一些任选项:如记录路径、时间戳等。这些选项很少被使用,同时并不是所有主机和路由器都支持这些选项。可选项字段的长度必须是32比特的整数倍,如果不足,必须填充0以达到此长度要求。

1.2.2 抓包图示

在这里插入图片描述

1.3 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 = 1表示 无操作NOP,无后面的部分
kind = 2表示 maximum segment,后面的Length就是maximum segment选项的长度(以byte为单位,1+1+内容部分长度)
kind =  3表示 windows scale,后面的Length就是 windows scale选项的长度(以byte为单位,1+1+内容部分长度)
kind =  4表示 SACK permitted    Length为2,没有内容部分
kind =  5表示这是一个SACK包     Length为2,没有内容部分
kind =  8表示时间戳,Length为10,含8个字节的时间戳
*/
typedef struct _TCP_OPTIONS
{
 char m_ckind;            //kind(8bit)
 char m_cLength;          //Length(8bit,整个选项的长度,包含前两部分)
 char m_cContext[32];     //内容(如果有的话)
}__attribute__((packed))TCP_OPTIONS, *PTCP_OPTIONS;
  • 源端口:源端口和IP地址的作用是标识报文的返回地址。

  • 目的端口:端口指明接收方计算机上的应用程序接口。TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。

  • 序号和确认号:是TCP可靠传输的关键部分。

    • 序号是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每一个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。
    • 确认号,即ACK,指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。
  • 数据偏移/首部长度:4bits。由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。

  • 保留:为将来定义新的用途保留,现在一般置0。

  • 控制位:URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。

    • URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
    • ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
    • PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
    • RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
    • SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
    • FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
  • 窗口:滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,因而窗口大小最大为65535。

  • 校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。

  • 紧急指针:只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。

  • 选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。

  • 数据部分: TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。

1.4 UDP头结构定义

在这里插入图片描述

/*UDP头定义,共8个字节*/
typedef struct _UDP_HEADER 
{
 unsigned short m_usSourPort;       // 源端口号16bit
 unsigned short m_usDestPort;       // 目的端口号16bit
 unsigned short m_usLength;        // 数据包长度16bit
 unsigned short m_usCheckSum;      // 校验和16bit
}__attribute__((packed))UDP_HEADER, *PUDP_HEADER;
  • 端口号:用来表示发送和接受进程。由于 IP层已经把I P数据报分配给TCP或UDP(根据I P首部中协议字段值),因此TCP端口号由TCP来查看,而 UDP端口号由UDP来查看。TCP端口号与UDP端口号是相互独立的。
  • 长度:UDP长度字段指的是UDP首部和UDP数据的字节长度。该字段的最小值为 8字节(发送一份0字节的UDP数据报是 OK)
  • 检验和:UDP检验和是一个端到端的检验和。它由发送端计算,然后由接收端验证。其目的是为了发现UDP首部和数据在发送端到接收端之间发生的任何改动。整个UDP报文头和UDP所带的数据的校验和(也包括伪报文头)。伪报文头不包括在真正的UDP报文头中,但是它可以保证UDP数据被正确的主机收到了。因在校验和中加入了伪头标,故ICMP除能防止单纯数据差错之外,对IP分组也具有保护作用。

二、wireshark TCP常见异常报文分析

2.1 Tcp Previous segment not captured

Wireshark抓包时,除了TCP协议的三次握手建立连接、数据收发和四次握手断开连接外,还经常能看到如下几种不太常见的报文,具体包括:

1.Tcp Previous Segment Not captured
2.Tcp Out-Of-Order
3.Tcp Dup Ack 12345#1
4.Tcp Spurious Retransmissiion
5.Tcp Retransmission

其中1、2、3会相伴出现,3、4、5会相伴出现。

  • 问题:乱序、丢包、重传
  • 对应第一种情况是由于由于TCP数据被分块后,传输过程中经过不同的路径,到达目的端时乱序,出现后发而先至的情况,此时目的端会显示【Tcp Previous Segment Not captured】,并且用【Tcp Dup Ack 12345#1】对前一个包再次进行确认,先发而后至的包到达目的端时,会显示【Tcp Out-Of-Order】;
  • 对应第二种情况是由于网络不稳定,源端发送数据后,目的端进行了确认,但源端并未在规定的时间内收到确认,所以会重新发送,目的端对重新发送的报文会显示为【Tcp Spurious Retransmissiion】,并且用【Tcp Dup Ack 12345#1】确认。
  • 这两种情况出现属于正常情况,TCP的超时重传机制和乱序重排可以保证TCP的按序可靠传输。

第一种情况乱序
在这里插入图片描述

乱序与丢包

1、[TCP Previous segment not captured]
[TCP Previous segment not captured]报文指的是在TCP发送端传输过程中,该Seq前的报文缺失了。一般在网络拥塞的情况下,造成TCP报文乱序、丢包时,会出现该标志。
需要注意的是,[TCP Previous segment not captured]解析文字是wireshark添加的标记,并非TCP报文内容

例子:
流媒体服务器39.135.135.81,端口80,发送序号Seq=147154的包,长度Len=1360,那么下一个数据包序号应该为Seq=147154+1360=148514,可以看到客户端请求的也是Ack=148514。而服务器下一个包序号为Seq=149874,中间的包丢失了。

在这里插入图片描述
2、[TCP Out-Of-Order]
[TCP Out-Of-Order]指的是TCP发送端传输过程中报文乱序了。

例子:
继续上面的包分析,因为208142包序号为Seq=148514,而前一个序号为Seq=149874,故有此错误标志。
Seq=148514实际是208139包的响应,因为网络拥塞的情况下,TCP包不能按顺序到达,所以出现[TCP Previous segment not captured] 和 [TCP Out-Of-Order]标志。

3、[TCP dup ack XXX#X]
[TCP dup ack XXX#X]表示第几次重新请求某一个包,#前XXX表示第几个包(不是Seq),#后的X表示第几次请求。丢包或者乱序的情况下,会出现该标志。

例子:下图表示客户端一直请求101261的包。
在这里插入图片描述

2.3 TCP Window (TCP ZeroWindow 与 TCP window Full)

问题:

  1. 一直处于 TCP ZeroWindow 状态会怎样?

1、[TCP ZeroWindow]
作为接收方发出现的标志,表示接收缓冲区已经满了,此时发送方不能再发送数据,一般会做流控调整。接收窗口,也就是接收缓冲区win=xxx ,告诉对方接收窗口大小。

例子:
传输过程中,接收方TCP窗口满了,win=0,wireshark会打上[TCP ZeroWindow]标签。
在这里插入图片描述
TCP ZeroWindow 是因为缓存超过65536字节了吗
不完全正确。TCP ZeroWindow 是指接收方窗口大小为0,表示接收方暂时无法接收更多的数据。这可能是因为接收方的接收缓冲区已满,但不一定是因为缓存超过了65536字节。接收方窗口大小取决于接收方的可用缓冲区大小,这个大小是由接收方主机的操作系统决定的,并不一定固定为65536字节。

当接收方窗口为0时,发送方会停止发送数据,以避免数据堆积在网络中。一旦接收方的缓冲区有足够的空间,它会发送窗口更新通知给发送方,以便发送方可以继续发送数据。

需要注意的是,TCP ZeroWindow 并不一定意味着数据丢失,只是暂时的数据阻塞。一旦接收方的窗口大小恢复正常,数据传输就会继续进行。

2、[TCP window update]
当接收端接收窗口大小发生变化,可以接收数据了,则有该标志。

例子:
接收方消耗缓冲数据后,更新TCP窗口,,可以看到从win=0逐渐变大,这时wireshark会打上[TCP window update]标签。

3、[TCP window Full]
作为发送方的标识,当前发送包的大小已经超过了接收端窗口大小,wireshark会打上此标识,标识不能在发送。

例子:
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐