设计目的
tcp_timestamps设计目的是为了记录数据包的发送时间,过程如下
- 发送方在发送数据包时,在TCP协议中的TSopt选项记录当前的发送的时间戳TSval中
- 接收方收到数据包,进行拆封并把发送的时间戳TSval记录在TSecr返回给发送方一个ack
- 发送包收接收方的ack包,用当前时间戳 - ack中的TSecr时间戳就可以得到精确的RTT
数据结构
- 数据包结构
Kind: 8 // 标记唯一的选项类型,比如window scale是3 Length: 10 bytes // 标记Timestamps选项的字节数 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | Kind=8 | Length=10 | TS Value (TSval) | TS ECho Reply (TSecr) | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 1 4 4
值得注意,tcp_timestamps必须需要双方都要开启方可生效,这是前提条件,如果有一方没有开启则双方进行数据发送接收时该功能不起作用(比如client端发送的SYN包中带有timestamp选项,但server端并没有开启该选项。则回复的SYN-ACK将不带timestamp选项,同时client后续回复的ACK也不会带有timestamp选项。当然,如果client发送的SYN包中就不带timestamp,双向都将停用timestamp)
- 该参数内核默认是启用的,即为 1
功能作用
摘抄网络
如果没有tcp_timestamps,看一下RTT是如何计算的
- TCP层在发送出一个SKB时,使用skb->when记录发送出去的时间
- TCP层在收到SKB数据包的确认时,使用now - skb->when来计算RTT
但如果发生丢包事件后,该如何计算
- TCP层第一次发送SKB的时间是send_time1, TCP层重传一个数据包的时间是send_time2
- 当TCP层收到SKB的确认包的时间是recv_time
但是RTT应该是 (recv_time - send_time1)呢,还是(recv_time - send_time2)呢?
timestamp选项很好的解决了上述问题,因为ACK包里面带的TSecr值,一定是触发这个ACK的数据包在发送端发送的时间。不管数据包是否重传都能准确的计算RTT(前提是TSecr遵循RTTM中的计算原则)。
什么是 RTT & RTO
- RTO (Rerrtransmission TimeOut)即,数据包重传超时时间
官方RFC2988文中定义
TCP超时与重传中的一个相当重要的部分,是对一个已知连接的RTT时间的测量,由于网络波动则会根据实际的情况相应的调整RTO时间
- RTT (Round Trip Time)即数据报文在整个链路的传播时间(从发送开始到返回的时间的差值)
RTT由三个部分组成
- 链路的传播时间
- 末端系统处理时间
- 路由器缓存队列及处理时间
具体测量方法,实际测量方法有二种(tcp_timestamps & 重传队列中数据包TCP控制块),只介绍tcp_timestamps
RTT= 数据包返回的当前时间 - 返回数据包中的TSecr中的echo时间戳
什么是PAWS
PAWS(Protect Againest Wrapped Sequence numbers)
中文理解,目的是解决在高带宽,高流速情况下,解决TCP序号重复排列带来的问题(默认情况下60s内同一源ip主机的socket connect请求中的timestamp必须是递增的)
PAWS同样也依赖于tcp_timestamps,假设在一个TCP传输流中,按序列接收到所有报文的中timestamp值也是线性递增的,在正常情况下,每个数据报文都是按序发送携带的timestamp值同样是线性增加的
- 字段名词解释
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,因此能被正常接收
英文解释
-
It is recommended that RST segments NOT carry timestamps, and thatRST segments be acceptable regardless of their timestamp.
- PAWS is defined strictly within a single connection; the last timestamp is TS.Recent is kept in the connection control block, and discarded when a connection is closed.
-
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 the connection, if the timestamp clock can be guaranteed to have ticked at least once since the old connection was open.
第三点说明,如果per-host使用PAWS机制的话,可以很好的解决在TIME_WAIT上一次数据报文在下一次传输在被视为有效的报文,只要等待足够的RTO,处理好需要重传最后一个ack包,于是衍生出下列机制
同时启用tw_recycle & tcp_timestamps时,可以达到快速回收TIME_WAIT的连接,但是这样并没有真正的有效解决,在互联网中,由其主动访问的终端(办公室这种环境,很多情况下都是通过NAT上网,这样无法确保发送的TCP报文中的时间戳是线性递增的,也无法确保终端的时间是一一致的,如果发送时间戳不是线性递增的,A B C 很有可能C的时间戳小于B,则被服务端丢弃,不符合PAWS的运行机制)
总结
- tcp_timestamps主要作用如下更精准的度量TCP连接数据报文传输过程中的RTT值,尤其在丢包情况下(RTTM在ACK被重传的数据时,应该使用重传数据包中的TSval进行回复)
- 加强PAWS在特殊情况的下(时间戳非线性递增,seq重复使用)确保TCP可靠传输
参考文献
https://perthcharles.github.io/2015/08/27/timestamp-intro/
https://blog.csdn.net/sinat_20184565/article/details/105000868
https://blog.csdn.net/zhangskd/article/details/7196707