1、TCP选项概述linux
在前面介绍TCP头的时候,咱们说过tcp基本头下面能够带有tcp选项,其中有些选项只能在链接过程当中随着SYN包发送,有些能够延后。下表汇总了一些tcp选项算法
其中我标记为红色的部分是常见的TCP选项,咱们仅针对这些红色的TCP选项进行介绍(主要是非红色的我也不太了解~~~),另外RFC1323已经被RFC7323取代,这里给出的是TCP选项原始定义的RFC缓存
按照RFC793规定,一个TCP选项只须要单字节对齐,可是在实现上通常是两字节对齐或者会经过NOP选项实现四字节对齐,例如3bytes长的WSOPT选项,linux在添加这个tcp选项的时候,就会在这个选项前面加一个1byte的NOP选项凑成4bytes。服务器
TCP选项的格式有两种,一种是单字节长的TCP选项如EOL和NOP。另一种是包含1byte的kind,1byte的length,在加上选项的数据。除了EOL和NOP选项外,其余的TCP选项都是后一种格式,且为了兼容,协议要求后续若是扩展其余tcp选项一样须要采用后一种格式。cookie
RFC1122协议规定TCP接收端必须可以处理任意TCP包中的选项,对于不能识别的TCP选项则采起忽略该选项的办法。其中有一些选项如EOL、NOP、MSS等是协议规定必须支持的,同时协议要求未来新增的TCP选项都须要由length域,这样TCP实现不能识别这个选项的时候就能够跳过这个选项。网络
2、EOL和NOPapp
EOL格式以下
tcp
+--------+ |00000000| +--------+ Kind=0
这个选项用来指示TCP的选项列表结束,这个选项是用在全部TCP选项的后面,并非每一个TCP选项后面都须要这个选项来指示选项结束,只有在TCP选项列表结束后没有与TCP头中的Header Length字段指定的头长重合时候才须要使用EOL选项,另外这个选项并不必定放在TCP头(包括扩展头)的末尾。举个例子,假如Header Length指定的TCP头长为40bytes,其中第29-38bytes为TSOPT选项,则能够在第39byte处添加一个EOL选项指示选项列表结束,能够看到EOL并无位于TCP头的结束位置的第40byte。对于最后一个byte RFC793协议规定须要以0来填充。注意这个EOL后面填充的0已经不属于TCP选项的一部分了。布局
NOP选项格式以下性能
+--------+ |00000001| +--------+ Kind=1
这个选项可使用在选项之间或者结尾处,好比,为了使3bytes的WSOPT选项在四字节对齐的边界处结束,能够在WSOPT选项以前添加一个NOP选项,这样整个选项长度为4bytes,更容易对齐。可是按照RFC793协议规定,发送端并不保证会填充NOP选项来让其余选项达到对齐的目的,所以接收端也应该准备好接收非四字节对齐的WSOPT选项。也就是说一样的几个TCP选项能够有不一样的选项排列顺序,即便是相同的排列顺序也可能由于NOP和EOL等等而有不一样的排列布局。
最后从linux实现的角度来讲,linux自己发送TCP数据包的时候并不会添加EOL选项,而是经过添加一个或者多个NOP选项来实现整个TCP头长的四字节对齐(还记得咱们以前说过TCP头中的Header Length字段的单位是32-bit word,所以TCP的头长必定是4bytes的整数倍)。可是linux在接收数据包的时候支持解析EOL选项。另外协议虽然没有限制TCP选项的排列顺序,可是linux实现上会按照必定的顺序排列TCP选项。缘由是虽然协议没有限定options的顺序,可是互联网上有些设备对这个顺序是比较敏感的,一些特定的options顺序可能会引发问题。
3、MSS
Maximum segment size(MSS)格式以下
+--------+--------+---------+--------+ |00000010|00000100| max seg size | +--------+--------+---------+--------+ Kind=2 Length=4
Maximum segment size(MSS)是TCP指望从对端接收的最大的报文长度,天然也是对端在发送报文的时候的最大报文长度,注意MSS值仅指示TCP数据长度,并不包含关联的TCP头和IP头的长度。当链接在创建的时候,每一个endpoint一般会在对应的SYN包中经过MSS option通告对方本身的MSS,按照RFC1122规定若是没有MSS选项提供则会使用默认的536bytes做为MSS(注意原始的RFC793协议是说没有提供MSS选项的时候能够发送任意大小的包,RFC1122修正了该说法)。还有一点须要注意因为目前网卡广泛支持TSO、GSO功能,在开启这些功能的前提下,协议栈中的TCP层可能会按照MSS的整数倍发包,而后再由网卡硬件来对TCP分段,这样减轻了CPU的处理压力。后面为了方便讨论窗口管理等特性,咱们仍是按照TCP层最大包不超过MSS来讨论。
在IPV6的jumbogram中(RFC2675),若是接收端接收到的MSS值为65535时候,标识真实的MSS须要根据PMTU值来肯定。即MSS=PMTU-60。(jumbogram是IPV6中一种发送超大IP报文的协议特性,PMTU是接收端和发送端链路之间全部设备的最小MTU。)
RFC6691从新澄清了MSS选项的相关说明,并修正了以前几个RFC的错误说法。RFC6691明确规定在MSS选项中传递的MSS值为MTU减去IP基本头(ipv4为20bytes,IPV6为40bytes)和TCP基本头(20bytes)的值,不考虑扩展头。发送端负责发送数据前在这个MSS值的基础上扣除扩展头长度得出真实传输数据的长度。
4、WSOPT
WSOPT格式以下
+---------+---------+---------+ | Kind=3 |Length=3 |shift.cnt| +---------+---------+---------+
RFC1323为长肥管道提供了两个高性能扩展,一个是WSOPT选项另一个是TSOPT选项。长肥管道是指带宽时延积很大的网络。
咱们在介绍TCP头结构的时候提到过Window Size字段,这个字段占16位,最大为2^16-1,在长肥管道中,当发送端TCP须要通告更大的接收窗口的时候,就须要经过WSOPT选项了。当使用WSOPT选项的时候,接收窗口的实际大小则为Window Size<<shift.cnt,其中shift.cnt按照协议最大只能为14,当接收端接收到的shift.cnt大于14的时候,则按照14来处理Window Size。
WSOPT选项只能在SYN包中发送,所以当TCP链接创建起来后,window scale就固定了。通常在TCP实现上会有一个最大接收缓存,进而决定了最大接收窗口和window scale。WSOPT选项将原有的16位Window Size扩展到近30位大小(大约1GB)能够有效提高TCP容许使用的接收缓存,进而提高长肥网络的性能。
若是要使能window scale,须要发送端在SYN包中发送WSOPT选项,接收端在SYN-ACK包中一样发送WSOPT。注意协商window scale过程当中协议要求不能对SYN和SYN-ACK报文头中的window size应用WSopt选项。WSOPT中的shift.cnt能够为0,标识window scale factor为1(即2^0=1),即接收窗口的实际大小即为Window Size。若是发送端发送了WSOPT选项可是没有收到对端的WSOPT选项,则须要将本身的window scale factor设置为1。
发送端和接收端都各有一个接收窗口和一个发送窗口,所以总共四个窗口,共维护4个scale factor。假设发送端接收窗口的scale factor为R,发送窗口的scale factor为S,则对应的接收端的接收窗口scale factor为R,发送窗口scale factor为S。在协商好两端的scale factor后,以后接收到的数据包中的Window Size字段自动进行scale factor,发送出去的数据包中的这个字段则为实际接收窗口右移scale factor后的结果。
5、SACK-Permitted和SACK
SACK-Permitted格式
Kind: 4 +---------+---------+ | Kind=4 | Length=2| +---------+---------+
SACK格式
Kind: 5 Length: Variable +--------+--------+ | Kind=5 | Length | +--------+--------+--------+--------+ | Left Edge of 1st Block | +--------+--------+--------+--------+ | Right Edge of 1st Block | +--------+--------+--------+--------+ | | / . . . / | | +--------+--------+--------+--------+ | Left Edge of nth Block | +--------+--------+--------+--------+ | Right Edge of nth Block | +--------+--------+--------+--------+
以前咱们介绍过TCP的滑窗和ACK机制,咱们再来简单的举个例子,假设接收端依序接收到系列号为2100的byte,序列号2100以前的byte都已经按序接收到了,接着由于乱序传输或者丢包的缘由,接收端并无接收到系列号为2101的TCP数据包,而是收到了系列号为2201的TCP报文而且长度为100byte。也就是说接收端缺乏了2101-2200byte的数据,咱们称接收端这种状况在滑窗上面造成了一个洞(hole)。以下图红色部分表示接收端已经接收到的数据。
此时接收端给发送端返回ACK报文的时候,TCP头中的ack number字段只能填写2101,还记得咱们以前说过ack number表示接收端指望接收到的下一个byte的系列号吧,它是已经收到的连续报文中的最大序列号加1。注意是连续报文,由于2100和2201之间有洞,所以此时ack number只能是2101,发送端在接收到2101这个ack number后,并不能知道接收端实际上已经接收到了2201-2300byte,于是可能会在重传2101-2200byte的同时也会重传2201-2300byte的数据。那么有了SACK后,接收端就能够经过SACK来告诉发送端已经接收到了2201-2300byte的数据,这个就是一个SACK块(SACK block),同时结合ack number,发送端就能够仅仅只是重传2101-2200byte的数据,而不须要重传2201-2300byte的数据了。
一个endpoint若是在SYN包或者SYN-ACK包中解析处SACK-Permitted选项,那么就说明对端支持SACK扩展。那么本端就能够把收到的不连续报文信息发送给对端来帮助对端高效重传了。一般SACK-Permitted选项通常是在SYN包中发送,一旦收到对端SACK-Permitted选项后,SACK选项则能够在任意包中传输。Linux中能够经过/proc/sys/net/ipv4/tcp_sack控制是否使能SACK功能,设置为1时候使能,设置为0时候关闭SACK功能。由于SACK选项和TCP重传以及拥塞控制等等由比较大的关系,后面咱们讲到这些的时候再来详细介绍。
6、TSOPT
TSOPT格式以下
Kind: 8 Length: 10 bytes +-------+-------+---------------------+---------------------+ |Kind=8 | 10 | TS Value (TSval) |TS Echo Reply (TSecr)| +-------+-------+---------------------+---------------------+ 1 1 4 4
TSOPT选项也叫作timestamp选项,有时也会写为TSopt。如上面介绍WSOPT时候所说,TSOPT也是RFC1323为了改善长肥管道而提出的一个TCP扩展。当使用这个选项的时候,发送方在TSval处放置一个时间戳,接收方则会把这个时间经过TSecr返回来。由于接收端并不会处理这个TSval而只是直接从TSecr返回来,所以不须要双方时钟同步。这个时间戳通常是一个单调增的值,RFC1323建议这个时间戳每秒至少增长1。其中在初始SYN包中由于发送方没有对方时间戳的信息,所以TSecr会以0填充,TSval则填充本身的时间戳信息。
在RFC1323中,TSOPT主要有两个用途一个是RTTM(round-trip time measurement)即根据ACK报文中的这个选项测量往返时延,另一个用途是PAWS(protect against wrapped sequence numbers),即防止同一个链接的系列号重叠。另外还有一些其余的用途,如SYN-cookie、 Eifel Detection Algorithm 等等。关于RTTM咱们留到TCP重传部分进行介绍,此处咱们简单介绍一下PAWS。
PAWS假设接收到的每一个TCP包中的TSval都是随时间单调增的,基本思想就是若是接收到的一个TCP包中的TSval小于刚刚在这个链接上接收到的报文的TSval,则能够认为这个报文是一个旧的重复包而丢掉。实际上接收到的TCP报文的系列号若是落在接收窗口外面就能够丢弃,可是对于一些高速不稳定网络,可能会出现一种状况,就是系列号翻转后,以前某个无效的重传包系列号知足条件,落在了接收窗口内,这个时候仅仅依靠系列号就不足以鉴定这个TCP报文的有效性了,结合TSOPT则能够经过时间戳选项来进一步过滤旧的重复包。
相似PAWS,实际上时间戳做为了系列号的一个扩展,在同一个链接上单调增。RFC6191进一步利用这个特色,在同一个链接的不一样实例间时间戳单调增的时候,能够利用这个时间戳区分同一个链接的不一样实例的时候,即便在TIME-WAIT状态下也容许创建链接。
RFC7323明确在TCP头中的ACK标志位有效的时候TSecr字段才有效,若是ACK标志位没有置位的时候,发送端应该把TSecr置为0。当发送出去的数据包ACK标志位置位的时候,发送端必须在TSecr中回显一个最近接收到的TSval。当ACK标志位没有置位的时候,接收端必须忽视TSecr字段。
TCP能够在初始的SYN包中发送TSopt选项,可是接收端只有在接收到的初始SYN报文中解析到TSopt选项的时候才容许在SYN-ACK报文中发送TSopt选项。一旦TCP通讯的两端经过SYN报文和SYN-ACK报文协商好TSopt选项后,在这个链接随后的非RST报文中,TSopt选项必须被发送。一旦接收到一个不带由TSopt选项的非RST报文的时候,TCP应该静默的丢弃这个报文(注意是应该should,不是必须must)。TCP不能(must not)由于缺乏预期的TSopt选项而停止一个TCP链接。注意这里是协议的要求,实现上并不必定会静默的丢弃这个数据包,好比linux在协商好TSopt后,收到没有TSopt选项的数据也会正常接收,后面文章会有wireshark示例。
当在三次握手中没有协商TSopt选项而在随后的数据传输中接收到TSopt选项的时候,TCP必须忽视这个TSopt选项而后正常处理这个TCP报文。在TCP同开的时候若是一个SYN报文包含TSopt选项,另一个SYN报文不包含TSopt选项,那么两端均可以在随后的SYN-ACK报文中发送TSopt选项。
另外TSopt选项还有两个重要做用,一个是RACK重传,另一个是Eifel探测算法,后面的文章咱们会专门进行实例介绍。Linux中/proc/sys/net/ipv4/tcp_timestamps能够设置是否启用TSopt选项,这个参数设置为0的时候TCP链接就不会使用TSopt选项。
7、FOC
FOC选项的格式以下
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Kind | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Cookie ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Kind 1 byte: value = 34
Length 1 byte: range 6 to 18 (bytes); limited by
remaining space in the options field.
The number MUST be even.
Cookie 0, or 4 to 16 bytes (Length - 2)
Fast Open选项用来请求或者发送一个FOC(Fast Open Cookie),当cookie域为空的时候,client使用这个选项来从服务器请求一个FOC。当cookie域非空的时候,服务器可以使用这个选项来把cookie传递给client,或者client可使用这个选项来执行TFO。
最小的cookie大小是4byte,虽然图示中cookie是32位对齐的,但不是强制要求的,当数据包中不带SYN标志、Length值无效或者TFO功能没有打开的时候,须要忽略这个选项。
8、wireshark抓包示例
咱们看一下以前FastOpen第一次正常链接SYN包中请求FOC时候对应的tcp选项截图以下,限于篇幅再也不逐步讲解,如今咱们讲解了TCP选项,建议下载wireshark文件,对照本节对TCP选项的讲解在看一遍wireshark中的TCP选项。
补充说明
一、linux支持的tcp选项能够参考TCPOPT_NOP宏定义附近定义的选项,写入tcp选项能够参考代码tcp_options_write,本文中wireshark中TCP选项的特定顺序也是在tcp_options_write写入的。
二、linux对于MSS的处理能够参考tcp_current_mss
三、第二版TCPIP详解P609中对于TSOPT的第二个字段描述为Timestamp Echo Retry,实际应该是Timestamp Echo Reply