摘要
- TCP 同时打开是两端针对同一 socket 对分别发起主动 OPEN。两个不带 ACK 的 SYN 交叉,双方进入
SYN-RECEIVED,再以交叉的 SYN-ACK 建立一条、而非两条连接。 - 它不需要先选出客户端。每个端点只需本地验证:自己收到对方当前的初始序列号,对方也确认了自己的初始序列号。
- NAT 后来常把“出站 SYN 后应当到来入站 SYN-ACK”当作完整状态机。RFC 5382 因而明确要求:策略已经允许的连接,也必须容纳入站 SYN 这一合法路径。
三次握手图偷偷分配了角色
最常见的 TCP 图示先在左边写 client,右边写 server,再画 SYN、SYN-ACK、ACK。它非常适合入门,却容易让人误以为发起和等待是连接的固有身份。
RFC 793 同时定义主动与被动 OPEN,也明确说两个进程若在同一时间向彼此主动打开,TCP 会把它们正确连接起来。文档认为,这种灵活性对彼此异步工作的分布式组件十分重要。两端不必先争出谁有资格扮演服务器。
所以,同时打开不是后来为异常碰撞补上的退路。它从一开始就是连接同步过程的一部分。普通图示只画了一条常走的路线,并没有赋予某一端永久的接听权。
两个 SYN 交叉,状态却收敛
设 A 选择初始序列号 100,B 选择 300。A 发出 <SEQ=100><SYN> 后进入 SYN-SENT;B 也发出自己的 SYN。两包在网络中相遇。
A 收到 B 的裸 SYN 时,不会因为自己已经发过 SYN 就把它视为冲突。A 记录远端起点 300,进入 SYN-RECEIVED,并发送确认 301 的 SYN-ACK。B 镜像执行相同步骤,确认 101。两份 SYN-ACK 到达后,双方都知道对方的起点,也知道自己的起点已被对方接收,于是进入 ESTABLISHED。
现行汇总规范 RFC 9293 仍保留这条轨迹:两边都从 CLOSED 经过 SYN-SENT、SYN-RECEIVED 到 ESTABLISHED。包的视觉顺序不再是教科书那种单向问答,但逻辑证明没有变化。
加一不是格式,而是证据
SYN 在 TCP 序列空间里占一个位置。因此,序列号 300 的 SYN 要由 301 来确认。ACK 不是泛泛表示“收到了一个包”,而是表明接收进度已经越过那个带序号的控制位。空 ACK 自己不占序列空间,否则互联网会陷入对 ACK 再发 ACK 的无限递归。
同时发起也不会合并双方的发送序列。A 继续使用自己的序列空间,B 使用另一套。RFC 6528 后来又要求初始序列号包含与四元组和秘密相关的伪随机函数成分,降低旁路猜测风险。对称的是发起权,不是数字或秘密。
这种算术让协调无需中央裁判。socket 身份、当前序列和已经发生的确认足以让每一端独立判断连接是否成立。
两次 connect 仍然只是一条连接
实现者很容易把两个应用的主动调用看成两个网络对象。RFC 1122 特意提醒:同时连接只生成一条连接,不是两条,这是有意设计,不要“修复”。
从 A 看,连接由 A 的本地 socket 与 B 的远端 socket 组成;从 B 看,顺序反过来,却仍是同一对。把它复制成两条,不会保存更多真实信息,只会让应用再猜哪一条才应承载数据。
这里清楚地区分了本地动作与共享事实。可以有两次系统调用、两个等待记录,但当四元组和序列状态汇合后,协议现实中只有一条关联。
状态必须记得自己从哪里来
两个 TCB 都可能显示 SYN-RECEIVED,来历却不同:一个由被动监听进入,另一个由主动打开后收到裸 SYN 进入。RFC 1122 与 RFC 9293 要求实现记住这段历史,因为收到复位后是否回到 LISTEN 等处理取决于进入路径。
RFC 793 还指出,旧连接遗留的重复 SYN 也可能伪装成同时打开。正确做法不是禁止交叉 SYN,而是用当前序列期待、确认关系与复位验证来区分新会合和旧残片。
这说明状态名不是全部状态。若为了简化表格而删掉因果来源,机器就会在最需要恢复判断时失去证据。实现上的一个字段,可能正是协议免受旧历史污染的边界。
NAT 只背下了最常见的故事
传统客户端—服务器流量中,出站 SYN 让 NAT 创建映射,接着入站 SYN-ACK 复用该映射。可在同时打开中,接下来到达的是另一个裸 SYN。
RFC 5382 记录了一些 NAT 把它当作未经请求的入站包而丢弃,另一些则错误翻译后续出站 SYN-ACK。端点完全遵守 TCP,NAT 也可能已经依据本地 SYN 允许了这段通信,但产品把一种常见顺序实现成了协议全貌。
过滤策略当然可以拒绝连接。问题在于,一旦策略已经允许,状态跟踪就不能把标准定义的转移宣布为非法。前者是明确的权限决定,后者是藏在解析器里的协议收缩。
RFC 5382 因此要求 NAT 对获准连接处理全部合法 TCP 状态序列,特别包括同时打开。它没有取消安全策略,只要求产品忠实理解自己选择允许的协议。
六秒暴露了中间设备的无知成本
最棘手的时刻,是远端 SYN 比本地 SYN 更早到达 NAT。本地包尚未建立映射,远端包看起来确实“未经请求”。若立即返回 RST 或硬 ICMP 错误,对方会放弃;但也许几毫秒后,延迟的本地 SYN 就会证明这是一场会合。
RFC 5382 选择至少等待六秒。如果对应的出站 SYN 在此期间出现,NAT 静默丢弃早到包,让远端重传在映射就绪后通过;若没有出现,再按安全策略报告错误。
六秒不是 TCP 的自然常数,而是信息不完整的明码标价。快速报错与保留成功机会不能同时最大化。规范没有假装中间盒知道未来,而是把延迟、状态资源和可逆性之间的取舍公开出来。
点对点时代重新使用了早期的对称性
当两台设备都躲在转换器后面时,双方往往都无法直接接受无准备的入站连接。但它们可以各自创建出站状态,再让 SYN 在预先交换的地址和端口上相遇。
RFC 6544 后来把这种办法定义为 ICE TCP 的 S-O 候选,与主动、被动候选并列。它同时强调要收集多种候选和中继路径,因为操作系统、NAT、过滤规则与移动网络并不一致。
因此,同时打开不是普遍有效的 NAT 穿越魔法。它是端点拥有的一条合法路线;路径是否让它活着,是另一项事实。直连失败可能来自地址发现、端口映射、时序、API 或策略,不能简单归因于 TCP。
能验证的对称性才得以延续
同时打开展示了一种很薄但很严格的共同层:确定同一 socket 对,交换两套新序列,确认彼此收到,并保留恢复所需的历史。在此之上,应用可以自行选择主动、被动、同时尝试或中继。
后来 NAT 的问题,不是端点自治太多,而是中间设备把“多数情况下如此”偷偷变成“只能如此”。RFC 5382 重画了边界:设备可以决定准不准,却不能在已经准许时改写 TCP 语法。
两端同时拨号时,协议没有选举赢家。它让每一端证明自己发过什么、收到什么。一条连接由可验证的共同状态产生,而不是由图纸预先分配的身份产生。
来源与证据边界
本文依据 RFC 793、RFC 1122、RFC 5382、RFC 6528、RFC 6544 与 RFC 9293。这些文件不提供当前部署率、全部操作系统 API 或所有 NAT 的合规清单;丢包与重传也会改变抓包顺序,而不改变逻辑状态。
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
