在时间戳选项诞生之前,TCP有三个问题难以解决:
(1)通信延迟RTT(Round Trip Time)测量
RTT对于拥塞控制是十分重要的(比如计算多长时间重传数据)。通常,测量RTT的方法是发送一个报文,记录发送时间t1;当收到这个报文的确认时记录时间t2,t2 - t1就可以得到RTT。但TCP使用延迟确认机制,而且ACK可能会丢失,使得收到ACK时也无法确定是对哪个报文的回应。
(2)序列号快速回绕
TCP判断数据是新是旧的方法是检查数据的序列号是否位于sun.una到sun.una + 2**31的范围内,而序列号空间的总大小为2*32,即约4.29G。在万兆局域网中,4.29G字节数据回绕只需几秒钟,这时TCP就无法准确判断数据的新旧。
(3)SYN Cookie的选项信息
TCP开启SYN Cookie功能时由于Server在收到SYN请求后不保存连接,故SYN包中携带的选项(WScale、SACK)无法保存,当SYN Cookie验证通过、新连接建立之后,这些选项都无法开启。
使用时间戳选项就可以解决上述问题。
问题(1)解决方法:发送一个报文时将发送时间写入时间戳选项,在收到的ACK报文时通过其时间戳选项的回显值就能知道它确认的是什么时候发送的报文,用当前时间减去回显时间就可以得到一个RTT。
问题(2)解决方法:收到一个报文时记录选项中的时间戳值,收到下一个报文时将其中的时间戳与上次的进行对比即可。时间戳回绕的速度只与对端主机时钟频率有关。Linux以本地时钟计数(jiffies)作为时间戳的值,假设时钟计数加1需要1ms,则需要约24.8天才能回绕一半,只要报文的生存时间小于这个值的话判断新旧数据就不会出错,这个功能被称为PAWS(Protect Against Wrapped Sequence numbers)。这样虽然可以解决问题(2),但随着硬件时钟频率的提升,时间戳回绕的速度也会加快,用时间戳解决序列号回绕问题的方法早晚会遇到困境。
问题(3)解决方法:将WScale和SACK选项信息编码进32 bit的时间戳值中,建立连接时会收到ACK报文,将报文的时间戳选项的回显信息解码就可以还原WScale和SACK信息(这部分内容见《3.6 SYN Cookie》)。
时间戳选项格式:
其中,TSval是本端填写的时间戳,TSecr是回显给对端的时间戳。两端必须都分别在SYN包和SYN|ACK包中开启时间戳选项,时间戳功能才能生效。
SYN包的时间戳选项在tcp_transmit_skb调用tcp_syn_options函数时设置:
498 static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
499 struct tcp_out_options *opts,
500 struct tcp_md5sig_key **md5)
501 {
502 struct tcp_sock *tp = tcp_sk(sk);
503 unsigned int remaining = MAX_TCP_OPTION_SPACE;
504 struct tcp_fastopen_request *fastopen = tp->fastopen_req;
505
506 #ifdef CONFIG_TCP_MD5SIG
507 *md5 = tp->af_specific->md5_lookup(sk, sk);
508 if (*md5) {
509 opts->options |= OPTION_MD5;
510 remaining -= TCPOLEN_MD5SIG_ALIGNED;
511 }
512 #else
513 *md5 = NULL;
514 #endif
...
528 if (likely(sysctl_tcp_timestamps && *md5 == NULL)) { //开启MD5选项就不能使用时间戳,why?
529 opts->options |= OPTION_TS;
530 opts->tsval