TCP协议
- Source port【16位】【2字节】:标识发送端口
- Destination port【16位】【2字节】:标识接收端口
- Sequence number【32位】【4字节】:该字段具有双重作用
- 如果Flags中的SYC位设置为1,那么这是初始序列号【initial sequence number】。实际第一个数据字节的序列号和相应ACK中的确认号就是这个序列号加1
- 如果Flags中的SYC位设置为0,那么这是当前会话的该段【segment】的第一个数据字节的累积序列号
- Acknowledgment number【32位】【4字节】:如果设置了Flags中的ACK标志,那么该字段的值就是ACK发送方所期望的下一个序列号。这表示收到了所有之前的字节(如果有的话)。每一端发送的第一个 ACK 确认另一端的初始序列号本身,但没有数据
- Data offset【4位】:指定TCP报头的大小,以32位为单位。报头的最小大小是5个字【word】,最大是15个字【word】,因此最小为20字节,最大为60字节,允许在报头中放置多达40字节的可选项【options】。这个字段的名称来源于这样一个事实:它也是从TCP段【TCP segment】开始到实际数据的偏移量
- Reserved【3位】:为将来使用,应设为零
- Flags【9位】:包含9个位标志(控制位)
- NS【1位】
- CWR【1位】
- ECE【1位】
- URG 【1位】: 指示需要关注紧急指针【Urgent pointer】字段
- ACK【1位】:指示需要关注确认【Acknowledgment 】字段。客户端在发送初始SYN报文之后所发送的所有报文都应该设置该标志
- PSH 【1位】:推送功能。 请求将缓冲的数据推送到接收应用程序
- RST 【1位】:连接复位/重置连接
- SYN【1位】:同步序列号。只有从两端发送的第一个包才应该设置这个标志。
- FIN【1位】:发送方的最后一个数据包
- Window size【16位】【2字节】:接收窗口【 receive window】的大小,指定该段【段】的发送方当前愿意接受的窗口大小(参见流量控制和§窗口缩放)
- Checksum 【16字节】【2字节】:16位的校验和【checksum】字段用于TCP报头、有效负载和IP伪报头的错误检查。伪报头由源IP地址、目的IP地址、TCP协议号(6)和TCP报头长度和有效负载(字节)组成
- Urgent pointer【16位】【2字节】:如果设置了URG标志,那么这个16位字段是表示最后一个紧急数据字节的序列号的偏移量
- 如果设置了 URG 标志,则该 16 位字段是与指示最后一个紧急数据字节的序列号的偏移量。
- Options 【0 ~ 320 位,32位对齐】
- Padding【None】:TCP 报头填充用于确保 TCP 报头在 32 位边界上结束,数据开始。填充由0组成。
TCP Flags 特辑
TCP报头中包含几个位字段,这些字段被称为Flags,用于影响TCP连接中的数据流。
- CWR、ECE是RFC 3186中为拥塞通知而引入的
- SYN、ACK、FIN、RST用于控制 TCP 连接的建立、维护和拆除,对任何执行过基本包分析的人来说都应该很熟悉。
- SYN - 发起连接
- ACK - 确认已接收的数据
- FIN - 关闭连接
- RST - 响应错误而中止连接
- PSH 用于实现实时推送功能,请求将缓冲的数据推送到接收应用程序
- URG 用于通知接收站某TCP段内的某些数据是紧急的,应该优先处理
PSH 标志位
要了解PSH标志的作用,我们首先需要了解TCP是如何缓冲数据的。 TCP 工作在 OSI 模型的第四层; 它为上层提供了一个简单的套接字,可以读取和写入,掩盖了基于包【packet-based】的通信的复杂性。 为了允许应用程序随时读取和写入此套接字,在 TCP 连接的两端都实现了双向缓冲区。
下图显示了发送方在发送数据之前以及接收方在接收数据时如何对数据进行缓冲。
当发送超过一个最大报文段长度 【MSS】 的数据(例如,传输一个大文件)时,缓冲区允许更有效地传输数据。 但是,在处理需要尽快传输数据的实时应用程序时,大缓冲区弊大于利。 考虑一下 Telnet 会话会发生什么,例如,如果 TCP 等到有足够的数据来填充数据包,然后才会发送一个数据包:在第一个数据包到达远程设备之前,您必须键入一千多个字符 。这不是很有用。
这就是 PSH 标志位的用武之地。应用程序可以将数据写入 TCP 在会话级别提供的套接字,并选择立即“推送”数据,而不是等待其他额外数据进入缓冲区。 发生这种情况时,出站 TCP 数据包中的 PSH 标志设置为 1(开启)。 在接收到设置了 PSH 标志的数据包后,连接的另一端知道立即将该段【Segment】转发给应用程序。 总结一下,TCP 的推送功能完成了两件事:
- 发送端应用程序通知TCP应该立即发送数据
- TCP报头中的PSH标志通知接收端主机应该立即将数据推送到接收应用程序。
我们可以看到在HTTP GET请求的包捕获中使用PSH标志的示例。在包#4中,我们看到初始HTTP请求设置了PSH标志,这表明客户机没有更多的数据要添加,请求应该立即发送到应用程序(在本例中是一个web守护程序)。我们还看到服务器已经在#36包上设置了PSH标志,该包包含所请求文件的最后一个字节。同样,PSH标志用于通知接收方,发送方没有进一步的数据要发送(目前)。
我们可以看到在此 HTTP GET 请求的数据包中,PSH设置为1,表明客户端没有更多数据要添加,并且应立即将请求发送到应用程序。
我们还看到服务器的响应包中,PSH设置为1。 同样,PSH 标志用于通知接收方,目前发送方没有进一步的数据要传输。
如前所述,PSH 标志还用于促进通过 TCP 的实时通信。wiresharp抓包 Telnet 会话的数据包,显示所有携带 Telnet 数据的数据包都设置了 PSH 标志,以防止按键被 TCP 缓冲。
PURG 标志位
URG标志用于通知接收站,某段【Segment】内的某些数据是紧急的,应该优先处理。如果设置了URG标志,接收站将计算TCP报头中的紧急指针【urgent pointer】。该指针指示段中有多少数据是紧急的,从第一个字节开始计数。
URG标志在现代协议中使用得不多,且各个服务器内核的实现也有歧义,这是由于当初RFC规范模糊导致的。
TCP三次握手
在客户端尝试与服务器连接之前,服务器必须首先绑定【bind】并监听【linten】一个端口以打开连接:这称为被动打开【passive open】。 一旦建立了被动打开【passive open】,客户端可以通过使用三次握手发起主动打开【active open】来建立连接:
- SYC :主动打开是由客户端向服务器发送SYN来执行的。客户端将TCP段的序列号【Sequence number】设置为一个随机值 A
- SYN-ACK :作为响应,服务器以SYN-ACK作为响应。确认号【Acknowledgment number】设置为比接收到的序列号【Sequence number】多 1,即 A+1,同时,服务器为数据包选择的序列号【Sequence number】是另一个随机数 B
- ACK:最后,客户机向服务器发送一个ACK。序列号【Sequence number】设置为接收到的确认号【Acknowledgment number】,即 A+1,并且确认号【Acknowledgment number】设置为比接收到的序列号【Sequence number】多 1,即 B +1
TCP三次握手抓包
① SYN包
我们先拆解TCP包
因为SYNC包没有数据体,所有TCP包只有报头,我们层层拆解
图中高亮部分,就是发送端口,即博主本机的端口,53620,我们请求www.baidu.com时只并未指定使用哪个端口与对端连接,此时,会默认选择一个随机可用的端口号来通信。
图下方高亮了对应于发送端口的16进制编码值:d1 74,解析为二进制为11010001 01110100,对应的10进制值则为53620
图中高亮部分,就是接收端口,即对端端口,443,报头中对应的16进制编码值为:01 bb,解析为二进制为00000001 10111011,对应的10进制值则为443
图中高亮部分,就是序列号【Sequence number】,共计32位,因为我们现在解析的是SYC包,客户端会随机初始化一个序号放到报头中。
16进制编码值为:73 9a e9 b5
2进制编码值为:01110011 10011010 11101001 10110101
10进制编码值为:1939532213
图中高亮部分,就是确认号【Acknowledgment number】,共计32位。因为我们现在解析的是SYC包,没有确认目的,所以确认号为0
16进制编码值为:00 00 00 00
2进制编码值为:00000000 00000000 00000000 00000000
10进制编码值为:0
图中高亮部分,就是数据偏移【Data offset】,共计4位,高亮的具体16进制编码值并不准确,我们拆解一下
16进制编码值为:80
2进制编码值为:1000 0000
【数据偏移取前4位】1000
【保留位取之后3位】000
【NS取末位】0
10进制编码值为:128
【数据偏移取前4位】8
【保留位取之后3位】0
【NS取末位置】0
具体什么含义呢,数据偏移标识的是从TCP段头开始,偏移多少,就是数据载荷的起始字节。
因为数据偏移【Data offset】,其真实10进制值为8,按照TCP协议规范,该字段是以32位的字为单位,亦即:32bit * 8 = 256bit = 32B
而由于SYC报文中没有数据载荷,所以该数据偏移应该等于TCP报文头总长度。
我们回头看最开始的那一张SYC报文总览接截图,高亮部分总计32字节。完美匹配!!
数据偏移【Data offset】之后尾随的是3位保留位【Reserved】,用于未来拓展,目前全部填充0
图中高亮部分,就是Flags标志位,共计9位,根据TCP协议规范,我们拆解报文编码:
16进制编码值为:80 02
2进制编码值为:1000 0000
【数据偏移取前4位】1000
【保留位取之后3位】000
【NS取末位】0
0000 0010
【CWR】0
【ECE】0
【URG】0
【ACK】0
【PSH】0
【RST】0
【SNC】1
【FIN】0
报头中Flags标识位符合TCP中协议的规定,SYNC包中,SYNC位置1,且序列号有值,确认号无值,其他置0
图中高亮部分,指定的是接收窗口大小,即TCP协议规范中的Windows Size报头。
那么什么是接收窗口?
What Window Size
简单地说,它是一个TCP接收缓冲区,用于接收应用程序尚未处理的传入数据。
TCP接收窗口的大小使用TCP报头的Windows Size值字段与链接伙伴通信。该字段告诉链接伙伴在收到确认信息之前可以在线路上发送多少数据。如果接收方不能在数据到达时快速处理数据,接收缓冲区将逐渐被填满,并且确认包【acknowledgment packets】中的 TCP 窗口将减小。这将提醒发送方它需要减少发送的数据量或允许接收方有时间清除缓冲区。
在上面的图表中,客户端和服务器在通信时发布了它们的窗口大小值。每个TCP报头将显示最近的窗口值,该值可以随着连接的进展而增长或收缩。在这个例子中,客户端有一个65,535字节的TCP接收窗口,而服务器的接收窗口是5,840字节。对于许多应用程序来说,由于客户机倾向于接收数据而不是发送数据,因此客户端通常具有较大的分配窗口大小。握手后,客户端向服务器发送一个HTTP GET请求,该请求被快速处理。来自服务器的两个响应包到达客户端,客户端发送确认包以及更新的窗口大小。 客户端能够尽快处理来自 TCP 缓冲区的数据包,因此窗口大小没有减少。 客户端仍然有一个完整的窗口可用于接收数据——65,535 字节。
在另一个示例中,客户端正在从服务器请求数据并开始接收数据。但是,在这种情况下,客户端不能快速处理传入的数据。TCP缓冲区开始被填满,正如减少的窗口值所指示的那样。
来自客户端的确认包表明窗口正在收缩。只要窗口值没有降到零,最终用户就不会注意到这种行为。虽然数量略有减少,但缓冲区中仍然有足够的空间继续进行数据传输。 在很多情况下,客户端可以追赶并处理缓冲区外的数据,清除窗口并增加窗口值。
TCP Window Scale
为窗口大小分配的TCP报头值是两个字节长。这意味着接收窗口的最大可能数值是65,535字节。在今天的网络中,这个窗口大小不足以提供最佳的流量,特别是在长而胖的网络(具有高带宽和高延迟的链接)上。在它的原生状态下,TCP不能利用这些高性能链接,因为它一次只能发送最多65,535个字节。
因此,在RFC 1323中引入了TCP Options,使TCP接收窗口以指数方式增加。具体的功能称为TCP窗口缩放【TCP Window Scaling】,在握手过程中发布。当发布其接收窗口时,客户端或服务器也将发布将在连接的生命周期中使用的缩放因子(乘数)。
在上图中,这个数据包的发送方正在发布一个64,240字节的TCP窗口,并且使用了一个8(2的8次幂,即256)的缩放因子。这意味着真正的窗口大小是64,240 x 256(16,445,440字节)。使用缩放窗口允许端点发布超过1GB的窗口大小。要使用窗口缩放,连接的双方都必须在握手过程中宣传这种功能。如果一方或另一方不能支持缩放,那么双方都不会使用这个函数。缩放因子,或乘数,只会在握手期间在SYN包中发送,并将在整个生命周期中使用。
What Is a Zero Window?
当客户端(或服务器——但通常是客户端)发布其窗口大小为零的值时,这表明TCP接收缓冲区已满,不能再接收任何数据。它可能有一个被卡住的处理器或忙于其他任务,这可能会导致TCP接收缓冲区被填满。零窗口也可能是由于应用程序中没有检索TCP缓冲区的问题造成的。
来自客户端的 TCP 零窗口将停止来自服务器端的数据传输,从而为问题站清除其缓冲区留出时间。 当客户端开始消化数据时,它会通过发送 TCP窗口更新包【TCP Window Update packet】让服务器知道恢复数据流,该包中将会通告增加的窗口大小并且流将恢复。
图中高亮部分属于TCP协议规范中的校验和部分,共计2字节,16位。
图中高亮部分属于TCP协议规范中紧急指针【Urgent pointer】部分,只有当Flags标志位中的URG标志置1时,紧急指针【Urgent pointer】才有效。
#### ????
TCP提供了将某些字节的数据标记为“紧急”的能力。该特性允许应用程序处理和转发任何必须立即处理的数据,而不必将数据放在发送队列中进行处理。相反,数据被打包成一个段,在TCP报头中设置紧急标志,并在紧急指针字段中指定一个字节偏移量,标记紧急数据的结束。
需要注意的是,紧急指针【Urgent pointer】不使用序列号来指定紧急数据的结束,而是使用偏移量,指示紧急数据结束的位置。 启用了紧急标志的 TCP 段的接收者必须将紧急指针中提供的值加到当前段的序列号字段的值上,并使用结果值来确定结束序列号。 这意味着紧急指针偏移量可以引用另一个 TCP 段中的字节位置,允许紧急数据在需要时跨越多个段。
这种机制反映了 RFC 1122 中规定的特定设计。不幸的是,这种设计并不总是很清楚,许多 TCP 实现采取了不同的解释。 最值得注意的是,许多基于 BSD 的系统使用紧急指针来引用紧急指针中指定的偏移量之后的字节,而不是指定的
#### ????
当应用程序将数据流发送到TCP时,可能会有一些字节流,应用程序希望远程主机的应用程序以某种特殊的方式来处理这些字节。因此,TCP发送端创建一个段【Segment】,并将“紧急数据”放在段的开头,然后是正常数据。URG指针字段标识“紧急数据”的结束偏移量,因此接收主机可以识别它们。
图中高亮部分,是TCP报文的Options 部分,其中比较常用的两个可选配置为:
- Windows scale 窗口缩放
- MSS
Windows scale【窗口缩放】我们再前面介绍了,主要是用于拓展Windows Size的。
下面着重介绍MSS,以及经常提到的MTU。
MTU
最大传输单元【maximum transmission unit】(MTU)是以八位字节(8位字节)指定的,可以在基于包【packet】或帧【frame】的网络(如因特网)中发送的最大数据包或帧大小。互联网的传输控制协议(TCP)使用MTU来确定任何传输中每个包的最大大小。MTU通常与以太网协议相关联,在以太网协议中,1500字节的数据包是允许的最大数据包。
与MTU相关的最常见问题之一是,有时更高级别的协议可能会创建比特定链路所支持的包更大的包,您需要进行调整以使其工作。
为了解决这个问题,IPv4允许分片【fragmentation】,将数据报【datagram】分成若干片【pieces】。使用为该接口配置的MTU参数,每一块都足够小,可以通过它正在分片的单个链路。这种分片过程发生在IP层(OSI第3层),并将它分片的包标记为IP层。这确保目标主机的IP层知道它应该将数据包重新组合成原始数据报。
应用程序有时不支持分片【fragmentation】,如果可能的话,我们应该避免这种情况。避免分片的最好方法是调整最大段大小【maximum segment size】或TCP的 MSS,以便段在到达数据链路层之前调整其大小。
如前所述,internet中MTU的常用值是1500字节。
如上图所示,MTU是由有效负载【payload】(也称为数据)、TCP头【TCP header】和IP头【IP header】构成的,其中,TCP头【TCP header】和IP头【IP header】各20个字节,共计40字节,每个包必须包含这40字节,这就为我们的数据留下了1460字节。
MSS
TCP MSS【maximum segment size】(最大报文段长度)是TCP报头Options字段中的一个参数,它指定计算机或通信设备在单个TCP段中可以接收的最大数据量(以字节为单位)。它不包括TCP报头或IP报头。这个值将指示数据包中“data”部分的最大大小。以太网链路下,默认大小为1460(因为以太网MTU为1500,TCP与IP报头均占用20字节)。
MSS 通知【MSS announcement 】(经常被误称为协商【negotiation】)是在双方的三向握手期间发送的,它说:“我可以接受大小为 x 的 TCP 段”。 该值 (x) 可能大于或小于默认值。 MSS 可以在数据流的每个方向上完全独立地使用。
由于终端设备并不总是知道将在此过程中添加到该报文中的高级协议,例如GRE报文,所以它通常不会调整TCP MSS值。因此,网络设备可以选择重写通过它们处理的TCP MSS包的值。例如,在Cisco路由器中,接口层的命令" ip tcp MSS -adjust 1436 "将重写任何通过该接口的SYN包的tcp MSS值。