摘要

  • TCP 用户超时是每条连接各自拥有的本地上限:已发送数据在规定时间内一直未获确认,端点就可以中止连接。它和决定何时重传的计时器不是一回事。
  • RFC 5482 定义了一个四字节的 User Timeout Option,让端点通告当前时限。通告只是建议,并非有约束力的协商;对端可以忽略、按本地上下限裁剪,也不能让它覆盖应用明确设置的时限。
  • 延长时限能让连接熬过暂时断网,却会更久地占用队列和连接状态;缩短时限能较早释放资源,也可能把短暂丢包或路径延迟误判成不可恢复的故障。

一个未获确认的字节,两只不同的钟

设想连接已经建立,仍有一个字节没有得到 ACK。重传计时器到期后,TCP 可以再发一次,并重新开始计算下一次重传。用户超时回答的却是另一个问题:应用愿意让这些未获确认的数据占着整条连接等多久?

RFC 793 把用户超时定义为连接的本地参数。它一旦到期,TCP 不是再试一次,而是清空队列、向用户报告连接因超时中止、删除传输控制块并进入 CLOSED。现行的 RFC 9293 仍把两个事件分开:RETRANSMISSION TIMEOUT 负责重发,USER TIMEOUT 负责结束状态。

因此,应用控制“耐心”的能力早于 UTO 选项。1981 年的抽象接口允许应用在 OPEN 时给出时限,也可以在 SEND 时修改。RFC 1122 又以 R1 和 R2 描述过度重传:到 R1 时发出负面提示,到更大的 R2 时关闭连接。应用必须能为某一条连接设置 R2;数据段至少一百秒的建议值不是所有业务都必须接受的统一答案。

问题在于,这些决定只在本机有效。移动主机为了跨越接入切换而延长时限,对端仍可能先放弃。繁忙服务器想尽快释放沉默连接的状态,客户端也无从知道这份资源预算。

四个字节没有换来一份契约

2009 年发布的 RFC 5482 把 UTO 定为 TCP 选项 28,长度为四字节。G 位决定用秒还是分钟解释,余下十五位携带建议值;零被保留。这个字段表达的是时间,不是重传次数,也不是往返时间估计。

如果在主动或被动打开之前启用,UTO 可以出现在 SYN 和 SYN-ACK 中。第一个不带 SYN 的报文也应再次携带它。不了解该选项的 TCP 必须静默忽略。如果装有 UTO 的报文丢失,对端只会失去一次更新本地策略的机会;规范没有为它另设可靠握手,也禁止实现假定它一定送达。

控制边界写得很清楚。ADV_UTO 是本端通告的时限,REMOTE_UTO 是最近收到的建议,USER_TIMEOUT 才是本地实际采用的值。ENABLED 决定是否使用该扩展,CHANGEABLE 决定远端建议能否影响本地值。

一旦应用明确设置 USER_TIMEOUT,CHANGEABLE 就必须变为 false。对端的一只报文不能悄悄推翻应用的指令。允许调整时,RFC 5482 建议先取双方通告值中的较大者,再套用本地的下限和上限。即便如此,两端仍可能得到不同结果,也都可以自行关闭或中止。

连接活得更久,状态也留得更久

较长的用户超时有助于跨越移动切换、路由不稳定或一段暂时无连通性的时期。代价是主机必须继续保存队列和控制状态。攻击者如果完成许多握手,再建议很长的时限,就可能放大服务器维持无用连接的资源负担。

所以 RFC 5482 要求实现设置上下限。下限必须大于当前 RTO,否则一次丢包或高时延就可能让用户超时先于合理的重传机会到期。上限可以根据认证情况、单个对端的连接数、资源利用率或攻击状态而变化。本地端点接受了较长建议,也没有失去清理状态的权利。

Keep-alive 解决的是另一类问题。两者同时启用时,keep-alive 计时必须大于已采用的用户超时,避免另一套探测中止策略更早结束连接。路径上的有状态防火墙仍可以按照自己的空闲时限丢弃状态。UTO 既不探测路径,也不证明对端仍在线,更不能保证中间设备会把连接保存到通告的时刻。

TCP 选项空间只有四十字节,其他扩展还可能挤掉 UTO。因此,看不到选项并不能证明对端拒绝等待。

这项标准真正改变了什么

RFC 5482 没有发明应用设置等待时限的能力。它让本地策略变得可被对端听见,同时保留本地决定权。这也使它和邻近的 TCP 主题泾渭分明:Window Scale 在握手后固定一个字段的解释方式;PAWS 借助时间戳拒绝已经过时的序列空间含义;SACK 报告缺口之后哪些字节块已经到达。用户超时关心的是:确认迟迟不来时,本端还愿意把状态留多久。

这组 RFC 不能证明今天的部署比例,也没有列出各操作系统接口和具体应用的默认值。它能证明的历史更精确:TCP 学会了通告一项等待上限,却拒绝把它说成双方都必须履行的承诺。

来源