tcp_timestamps是在RFC 1323中定义的一个TCP选项。
这篇wiki介绍一下timestamps的设计目的和相关原理,尤其强调一些比较tricky的地方。
关于RFC1323
这是一篇介绍针对High-bandwidth, Long delay链路设计的一些TCP扩展选项的资料。强烈推荐阅读!
但这篇RFC其实已经被RFC7323所取代,不过RFC1323对于了解timestamp相关的基本概念来说还是足够了的。关于RFC7323也会在后续的wiki中详细的介绍。
High-bandiwidth, Long delay链路面临的性能问题
a. Window Size limit TCP头部仅16字节用于存放receive window,这在高BDP的链路中往往是不够用的 解决办法就是引入window scale选项,然后real_rcv_wnd = rcv_wnd_in_tcp_header * (2^win_scale) b. Recovery from Losses 当BDP很大时,意味着需要更大的cwnd来充分利用带宽。如果发生网络丢包,则对这类链路的影响是巨大的。 优化办法(真的不能说解决T_T)就是引入[SACK](https://tools.ietf.org/html/rfc2018)机制,来为发送方重传提供更加准确的信息。 c. Round-Trip Measurement TCP作为可靠的传输协议,一个重要的机制就是超时重传。因此如何计算一个准确(合适)的 RTO对于TCP性能有着重要的影响。而tcp_timestamp选项正是*主要*为此而设计的。
上一句话强调”主要”是因为tcp_timestamp还被用于PAWS机制,而这一重要用途却时常被忽略。
作为一个可靠的传输协议,TCP除了考虑如何应对性能问题,还需要考虑可靠性问题。
即使这些问题发生的概率较低,PAWS就是其中一个例子。
PAWS(Protect Against Wrapped Sequence numbers)一句话解释如下,后面会详细介绍
在高带宽下,TCP序列号可能在较短的时间内就被重复使用(recycle/wrapped) 就可能导致同一条TCP流在短时间内出现序号一样的两个合法的数据包及其确认包!
补充一句:什么用wrapped形容序列号被重复使用?因为压圈了呀 :)
tcp_timestamps 的设计
tcp_timestamps的本质是记录数据包的发送时间。基本的步骤如下
1. 发送方在发送数据时,将一个timestamp(表示发送时间)放在包里面 2. 接收方在收到数据包后,在对应的ACK包中将收到的timestamp返回给发送方(echo back) 3. 发送发收到ACK包后,用当前时刻now - ACK包中的timestamp就能得到准确的RTT
当然实际运用中要考虑到RTT的波动,因此有了后续的(Round-Trip Time Measurement)RTTM机制
TCP Timestamps Option (TSopt)具体设计如下
Kind: 8 // 标记唯一的选项类型,比如window scale是3 Length: 10 bytes // 标记Timestamps选项的字节数 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | Kind=8 | Length=10 | TS Value (TSval) | TS ECho Reply (TSecr) | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 1 4 4
timestamps一个双向的选项,当一方不开启时,两方都将停用timestamps。
比如client端发送的SYN包中带有timestamp选项,但server端并没有开启该选项。
则回复的SYN-ACK将不带timestamp选项,同时client后续回复的ACK也不会带有timestamp选项。
当然,如果client发送的SYN包中就不带timestamp,双向都将停用timestamp。
为什么需要timestamp
如果没有timestamp,RTT的计算会怎样?
1. TCP层在发送出一个SKB时,使用skb->when记录发送出去的时间 2. TCP层在收到SKB数据包的确认时,使用now - skb->when来计算RTT
但上面的机制在丢包发生时会有问题,比如
1. TCP层第一次发送SKB的时间是send_time1, TCP层重传一个数据包的时间是send_time2 2. 当TCP层收到SKB的确认包的时间是recv_time
但是RTT应该是 (recv_time - send_time1)呢,还是(recv_time - send_time2)呢?
以上两种方式都不可取!因为无法判断出recv_time对应的ACK是确认第一次数据包的发送还是确认
重传数据包。因此TCP协议栈只能选择非重传数据包进行RTT采样。但是当出现严重丢包(比如整个窗口全部丢失)时,就完全没有数据包可以用于RTT采样。这样后续计算SRTT和RTO就会出现较大的偏差。
timestamp选项很好的解决了上述问题,因为ACK包里面带的TSecr值,一定是触发这个ACK的数据包在发送端发送的时间。不管数据包是否重传都能准确的计算RTT(前提是TSecr遵循RTTM中的计算原则)。
当然timestamp不仅解决了RTT计算的问题,还很好的为PAWS机制提供的信息依据。
开启timestamp会有什么负面影响?
这部分内容以后会根据更多的实际经验来补充。目前列举一些找到的分析。
1. 10字节的TCP header开销 2. The TCP Timestamp when enabled will allow you to guess the uptime of a target system (nmap v -O . Knowing how long a system has been up will enable you to determine whether security patches that require reboot has been applied or not. 引自:http://stackoverflow.com/questions/7880383/what-benefit-is-conferred-by-tcp-timestamp 注:如果通过热补丁修复bug,是否就能够避免这个问题?
什么是RTTM
RTTM规定了一些使用TSecr计算RTT的原则,具体如下
(英文水平有限,为保持原意就使用RFC中的原话了)
a. A TSecr value received in a segment is used to update the averaged RTT measurement only if the segment acknowledges some new data b. The data-sender TCP must measure the effective RTT, including the additional time due to delayed ACKs. Thus, when delayed ACKs are in use, the receiver should reply with the TSval field from the earliest c. An ACK for an out-of-order segment should therefore contain the timestamp from the most recent segment that advanced the window d. The timestamp from the latest segment (which filled the hole) must be echoed 在ACK被重传的数据时,应该使用重传数据包中的TSval进行回复
如果对以上的特殊情况有疑问,还请直接去看RFC,里面有example解释。
最后,实际上计算RTO除了以上使用TSecr的原则外,还有一些更复杂的计算方法RFC 7323。
比如对于每一个RTT采样R,
RTTVAR = (1 - beta) * RTTVAR + beta * |SRTT - R| SRTT = (1 - alpha) * SRTT + alpha * R
什么是PAWS
PAWS — Protect Againest Wrapped Sequence numbers
目的是解决在高带宽下,TCP序号可能被重复使用而带来的问题。
PAWS同样依赖于timestamp,并且假设在一个TCP流中,按序收到的所有TCP包的timestamp值
都是线性递增的。而在正常情况下,每条TCP流按序发送的数据包所带的timestamp值
也确实是线性增加的。
至于为什么要强调按序,请先自行思考。:)
首先给出几个变量的定义,之后具体介绍PAWS的工作过程
Per-Connection State Variables TS.Recent: Latest received Timestamp Last.ACK.sent: Last ACK field sent Option Fields in Current Segment SEG.TSval: TSval field from TSopt in current segment. SEG.TSecr: TSecr field from TSopt in current segment.
TS.Recent存放着按序达到的所有TCP数据包的最晚的一个时间戳,即只有在SEG.SEQ <= Last.ACK.sent < SEG.SEG + SEG.LEN
(有新的数据被按序确认了)时,
才会去更新TS.Recent的值。
假设三个数据包的*第一次*发送时间分别是A,B和C(A < B < C),但A和C含有相同的序列号。 而A数据包由于某种原因,在阻塞在了网络中,因此发送方进行了重传,重传时间为A2 PAWS要解决的主要问题就是: 当接收端在接收到A2后,又接着确认到了数据包B,下一个想接收的数据是数据包C 此时如果收到了数据包A(A从阻塞中恢复过来了,但并未真的丢失), 由于A与C的序列号是相同的。如果没有别的保护措施就会出现数据紊乱,没有做到可靠传输 PAWS的做法就是,如果收到的一个TCP数据包的timestamp值小于TS.Recnt,则会丢弃该数据包。 因此数据包A到达接收方后,接收方的TS.Recent应该是数据包B中的timestamp 而A < B,故A包就会被丢弃。而真正有效的数据C到达接收后,由于B < C,因此能被正常接收
PAWS的更多细节
1. It is recommended that RST segments NOT carry timestamps, and that RST segments be acceptable regardless of their timestamp. 2. PAWS is defined strictly within a single connection; the last timestamp is TS.Recent is kept in the connection control block, anddiscarded when a connection is closed. 3. An additional mechanism could be added to the TCP, a per-host cache of the last timestamp received from any connection. This value could then be used in the PAWS mechanism to reject old duplicate segments from earlier incarnations of theconnection, if the timestamp clock can be guaranteed to have ticked at least once since the old connection was open.
从第三点可以看到,如果针对per-host的使用PAWS中的机制,则会解决TIME-WAIT中考虑的上一个流
的数据包在下一条流中被当做有效数据包的情况,这样就没有必要等待2*MSL来结束TIME-WAIT了。
只要等待足够的RTO,解决好需要重传最后一个ACK的情况就可以了。
因此Linux就实现了这样一种机制:
当timestamp和tw_recycle两个选项同时开启的情况下,开启per-host的PAWS机制。 从而能快速回收处于TIME-WAIT状态的TCP流。
但这样真的就能完美的解决令无数人头疼的TIME-WAIT吗?答案是否定的!
因为公网中存在太多的NAT设置,当使用per-host的PAWS机制时,是无法保证timestamp是线性递增这一假设的。因为使用同一个NAT地址的两个真实的机器,他们的timestamp是不能保证同步的(其实一致也没有用,NAT就是per-host PAWS机制的死敌)。
关于这个问题也会在以后的一篇介绍TIME-WAIT的wiki中进一步详细介绍。
总结
timestamp为TCP/IP协议栈提供了两个功能: a. 更加准确的RTT测量数据,尤其是有丢包时 -- RTTM b. 保证了在极端情况下,TCP的可靠性 -- PAWS
参考资料
Documentation: ip-sysctl.txt
RFC 1323: TCP Extensions for High Performance
RFC 7323: TCP Extensions for High Performance
SACK
What benefit is conferred by TCP timestamp?