TCP 超时管理 "TCP/IP详解" 第十四章 读书笔记
连接建立时发生失败或传输数据超时
连接发起方会以指数级等待, 并尝试重新建立连接
R1: TCP认为超时过多, 应该报告IP层(重新路由)
R2 : TCP判定连接建立失败(报告应用层)
R1R2可以在系统层面修改, 数值代表发送失败的次数而不是具体秒数, 秒数根据RTO测定
超时重传
RTO = rtt_mean + 4 * rtt_var
在Linux实现中将测量周期从50ms变到了1ms, 虽然测量更加准确, 但是有以下几个问题
- 大量的数据会导致rtt_var变化不明显(大数定理)
- 当网络突然通畅的时候也会导致rtt_var剧烈变化, 可能导致RTO变大, 因此在Linux的实现上又加入了rtt_var的最大最小限
RTO测量基于Timestamp特性(如果没有Timestamp则普遍只维护一个测量样本, 使用后可同时测量多个样本), 返回的ack包包括了SYN发送过来的时间, 那么发送端根据自己的时钟就能测量出链路RTT
RTT测量会忽略0大小的包, 如KeepAlive包等, 同时这些包的特征是"不提供可靠交付"
这种Linux的测量方法相比于传统方法更加鲁棒:
- 后面的报文选择确认后只是确认了当前起始窗口的ACK, 导致RTO增大(失序到达不应该认为是丢包, 降低重传积极性)
- 成功重传后, 窗口前移, ACK的时间来自于最新窗口起始的报文(尽快更新TimeStamp, 而不是使用当前确认的, 可以更加准确估算RTT)
超时重传是tcp的"兜底策略", 因为RTO一般是2*RTT, 多数情况都是使用快速重传完成了重传的过程.
快速重传
当失序报文到达的时候, 立刻返回ACK. 如果采用了SACK, 那么也会携带SACK信息来得知多个空缺.
对于发送端, 收到多个ack可能是丢失了, 也可能只是延迟到达, 所以发送端需要累积多个ack才决定重发.
如果不采用SACK, 至多只能重传一个片段. 直到收到了出现重传状态前应该收到的ACK包时才认为恢复了(newReno算法), 只要收到了任何一个可接受的ACK则认为恢复了(老Reno算法)
每个SACK信息需要32位来描述ACK位置, 即每个区间需要2*4个字节来描述, tcp扩展有40个字节, 使用TimeStamp用掉10字节, 2个字节标志位, 因此最多能够标识3个SACK区间
但是在SACK中, 发送端也只能重传一个片段, 直到得知这个区间已经被确认.
SACK核心思想: 在一个RTT内填补更多的空缺区间
伪重传现象
由于ACK报文传输时间过长导致的发送端认定应当重传的现象
会导致发生了回退N, 浪费带宽.
Eifel检测算法或者DSACK. DSACK复用了SACK的字段来告知发送端有的报文重复了. Eifel算法使用了TimeStamp, 发送端发送了重传后, 如果收到的TimeStamp是对于前一个报文的确认, 则认为是伪重传.
Eifel算法能够更早发现伪重传, 因为DSACK需要在接收端也感知到伪重传并构造DSACK
F-RTO算法: 能够避免超时重传引起的伪重传: 发生超时重传时, 仍然坚持发送之后的报文, 如果能收到这个报文的ACK, 证明接收方窗口移动了, 说明不需要重传.
还有一种是"重复"现象: 在传输过程中由于数据链路层退避算法导致数据包被复制传输, 接收方收到了多个重传但是发送方并不之情. 接收方会回复多个确认, 可能会引起重传. 如果使用了DSACK可以有效避免.(接收方的回复中报告了自己已经成功接收)
重新组包
TCP通过字节号来识别数据而不是报文段或者包
因此在SACK重传的时候允许同时在这个包携带下一个要发送的数据, 组成一个更大的数据包, 甚至这两篇数据顺序都不重要.
攻击
预测重传时间, 在要重传的时候发动DDOS攻击, 让对方发不出去
伪造ACK, 提前确认, 让发送方一位RTO很小, 从而浪费带宽提前发送很多无效数据