跳转到内容

MPTCP 2012 — 把一根 TCP 管道变成多条并行水管

是什么

Multipath TCP(MPTCP)是对 TCP 的一次扩展:让一个 TCP 连接同时走多条网络路径,就像一个快递员把货物分装成几个包裹,同时交给不同的快递公司运送,哪条路快就多发哪条。

日常类比:普通 TCP 是一根水管,水只能按顺序从一端流向另一端;MPTCP 是在两栋楼之间铺了几根并行的水管——可以同时打开所有水管,总流量叠加,哪根水管堵了就自动减少那根的流量。

这听起来简单,但真正难的是兼容性:2012 年的互联网里到处是 NAT、防火墙、代理、流量优化盒……这些”中间盒”(middlebox)对 TCP 有各种假设,随意拆改包头。这篇 NSDI’12 论文的核心贡献就是:在中间盒横行的现实里,把 MPTCP 做到真正可部署。最终,Apple 在 iOS 7 把它用于 Siri 的 Wi-Fi/4G 无缝切换。

为什么重要

不理解 MPTCP,下面这些事都没法解释:

  • 为什么你的 iPhone 打电话时换了 Wi-Fi 信号,Siri 没有断连——MPTCP 让连接在多条路径上同时存活
  • 为什么数据中心服务器挂两块网卡带宽并不简单翻倍——朴素的 link-bonding 会制造热点,MPTCP 的拥塞控制更聪明
  • 为什么”多路径 TCP”提案 1995 年就有了,到 2012 年才第一次在 Linux 内核真正跑起来——中间盒让协议设计难度指数级增加
  • 为什么 TCP 选项字段的设计(4 字节还是 20 字节)会影响整个协议是否能工业落地

核心要点

MPTCP 解决三大核心问题,每个都有令人意外的复杂性:

  1. 双序列号空间:子流归子流,连接归连接

    普通 TCP 只有一套序列号。MPTCP 必须给每条子流维护独立的序列号(因为 NAT 会随机重写初始序列号),同时还要用”数据序列映射”(DSM)把子流字节偏移翻译成连接级字节偏移。类比:每个快递员有自己的包裹编号,但收件方看到的是统一的货单编号——两套编号之间需要一张翻译表。

    论文测量到 10% 的路径会重写序列号、26% 的路径会丢弃”跳过了空洞”的 ACK,所以把连接级序列号暴露给子流是行不通的。

  2. DATA ACK 必须放进 TCP 选项,不能塞进 payload

    MPTCP 需要一个跨子流的累计确认(DATA ACK)来做流控和确认重传。有人提议把它编码进 payload,但论文证明这会死锁:当接收方缓冲区满时,payload 里的 DATA ACK 本身也受流控限制无法发出,导致发送方永远等不到 ACK 来释放缓冲——经典循环依赖。唯一出路:把 DATA ACK 放进 TCP 选项字段(纯 ACK 包的选项不受流控)。

  3. 缓冲区是隐藏的性能杀手,需要主动算法干预

    MPTCP 的接收缓冲需求不是各子流 BDP 之和,而是”各子流吞吐量 × 最慢子流 RTT”——如果 Wi-Fi RTT 是 20ms、3G RTT 是 150ms,MPTCP 需要的缓冲是纯 Wi-Fi TCP 的近 5 倍。缓冲不足时,MPTCP 吞吐甚至低于单路 TCP。论文提出两个修复算法:机会重传(快路径抢发慢路径还没确认的数据)+ 慢路径惩罚(压缩慢子流的拥塞窗口),组合后即使在 200KB 缓冲下也能达到单路 TCP 的 10 倍吞吐。

实践案例

案例 1:移动端 Wi-Fi/4G 无缝切换

手机同时连接 Wi-Fi 和 4G,建立 MPTCP 连接,两条子流同时工作。Wi-Fi 掉线时:

连接状态(简化):
子流 1(Wi-Fi):seqno 1→5000, DATA ACK 到 3000
子流 2(4G):seqno 1→2000, DATA ACK 到 2000
Wi-Fi 断开 → 子流 1 RST(只关闭子流,不关闭连接)
应用层:连接仍然存活,数据继续从子流 2(4G)流动
找到新 Wi-Fi → MP_JOIN 握手,新子流加入同一 MPTCP 连接

逐部分解释:

  • MP_JOIN 选项:新子流用原始连接的密钥 MAC 验证,防止攻击者劫持
  • RST 只终止子流:与普通 TCP 不同,子流 RST 不等于整个连接关闭
  • 应用完全无感知:socket API 不变,应用以为自己用的是普通 TCP

案例 2:数据中心双网卡并行

服务器挂两块 1Gbps 网卡,MPTCP 自动在两条链路上分发数据。关键点:应用代码不需要任何修改——MPTCP 在内核层透明工作,开关由 sysctl 控制:

Terminal window
# 查看当前 MPTCP 状态(Linux 5.6+)
sysctl net.ipv4.tcp_mptcp
# net.ipv4.tcp_mptcp = 1 ← 表示默认开启
# 如果未开启,手动开启:
sysctl -w net.ipv4.tcp_mptcp=1
# 应用代码完全不变:
# import socket
# server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# server.bind(('0.0.0.0', 8080))
# server.listen()
# ↑ 这就是全部——内核会自动在两块网卡上建立子流

内核透明处理的流程:

  • 收到客户端的 MP_CAPABLE 选项 SYN → 在第一条路径(1Gbps link A)建立连接
  • 探测到本机还有 link B 的地址 → 自动发出 MP_JOIN 子流握手
  • 联合拥塞控制(Linked Congestion Control)确保两条子流合计不超过外部 TCP 的公平份额

实验结果:文件 >100KB 时吞吐翻倍;比 link-bonding 更稳定——bonding 会让一条链路打满而另一条空闲,MPTCP 的拥塞控制会主动把流量迁离拥塞链路。

案例 3:中间盒检测与回退

部署 MPTCP 时需要先摸清网络里有哪些中间盒。论文提供了一个思维模型:

检测逻辑(简化):
SYN 带 MP_CAPABLE 选项
→ SYN/ACK 是否保留了选项?
✗ 没有 → 此路径上 MPTCP 无法启用(6% 的路径)
✓ 有 → 继续
数据包 DSM 选项是否被修改?
→ 检测 DSM checksum 失败
失败 → 说明路径上有"应用层网关"在改写 payload(如 FTP ALG)
→ 关闭该子流 / 回退到普通 TCP
通过 → 正常工作
序列号是否被重写?
→ DSM 使用"与初始序列号的偏移量"而非绝对值,自动适应

论文在 24 个国家 142 个接入网测试:6% 的路径丢弃新选项,10% 重写序列号,26% 会丢弃”越过空洞”的 ACK——所以每一个设计决策都有真实测量数据支撑。

踩过的坑

  1. 以为子流独立就够了,忽视了连接级流控:光有子流级 ACK 不够——当子流 RTT 差距大时,快路径可能一直在等慢路径的 DATA ACK 才能释放缓冲,结果整体吞吐被慢路径拖死,需要机会重传算法兜底。

  2. TSO 硬件会破坏 DSM 选项:NIC 的 TCP Segmentation Offload 会把一个大包拆成多个小包,并把 TCP 选项逐字复制到每个小包——如果 DSM 只写”这包从数据序列号 X 开始”,每个小包都会说”我从 X 开始”,导致映射错误。正确做法:DSM 必须写”从子流偏移 O 开始,长度 L 字节映射到数据序列号 X”,这样即使重复出现也能正确解析。

  3. DATA FIN 和 subflow FIN 语义必须分开:如果 FIN 直接映射到数据序列空间,发送方就不能在发 FIN 后再重传之前丢失的数据(重传包的序列号在 FIN 之前,但子流已经关闭)。解决方案:子流 FIN 只表示”该子流不再发数据”,真正的连接结束用单独的 DATA FIN 选项表达。

  4. 缓冲区自动调优会被 3G 的大 RTT 欺骗:现代 TCP 实现会根据测量到的 RTT 自动扩大缓冲区。3G 的 RTT 可以到 150ms+,导致 MPTCP 的自动调优算出需要几百 KB 缓冲,但实际上完全没必要——需要在缓冲已用量超过一个 BDP 时主动限制拥塞窗口。

适用 vs 不适用场景

适用

  • 移动设备同时有 Wi-Fi 和蜂窝网络,需要无缝切换或带宽叠加
  • 数据中心服务器有多块网卡,希望单个连接用满所有带宽
  • 需要路径容错:一条路径故障时连接不中断
  • 运营商网络(多宿主服务器),想在多个 ISP 上做负载均衡

不适用

  • 纯单接口设备(只有一个 IP 地址)——没有多路径可走,MPTCP 退化成普通 TCP 但有额外开销
  • 超短连接(<30KB)——建立第二条子流的握手开销反而降低性能
  • 严格延迟敏感场景——多路径带来额外的重排序延迟,不如 QUIC 的连接迁移方案
  • 需要精细控制路径选择的场景——MPTCP 的拥塞控制是自动的,不能显式指定”用路径 A 发视频,用路径 B 发控制信令”

历史小故事(可跳过)

  • 1995 年:Christian Huitema 在 IETF 提交”Multi-homed TCP”草案,提议用单一序列号跨多地址——想法对,但没考虑中间盒,无法落地。
  • 2004-2008 年:学术界提出 pTCP、MTCP、M/TCP 等方案,都在仿真中很美好,都没能在真实互联网存活——中间盒把它们挡在门外。
  • 2011 年:Raiciu 等人在 SIGCOMM 发表数据中心 MPTCP 论文,同年在 IMC 发表大规模中间盒测量研究,为 2012 年的 NSDI 论文打好了数据基础。
  • 2012 年:NSDI’12 这篇论文,10,400 行 Linux 内核补丁,第一次证明 MPTCP 在真实互联网可以工作。
  • 2013 年:RFC 6824 正式标准化 MPTCP。Apple 在 iOS 7 为 Siri 专用连接启用 MPTCP(非全量 TCP 连接),让它成为第一个大规模商用案例——用户切换 Wi-Fi 时 Siri 不再断连。普通 App 的 TCP 连接仍走单路径。
  • 2020 年:Linux 5.6 将 MPTCP 合并进内核主线;Apple 在 macOS Big Sur 进一步扩大 MPTCP 使用范围。

学到什么

  1. 协议设计不只是端到端的事:现代互联网的三次握手参与者不是两台主机,而是两台主机加上路径上所有的 NAT、防火墙、代理——设计新协议必须对”中间盒会做什么”有实测数据,而不是假设网络透明。

  2. 优雅降级比大而全更重要:MPTCP 成功的核心不是”多路径有多快”,而是”出问题时能安全回退到普通 TCP”——任何一个破坏 MPTCP 的中间盒,都只会让连接退化为单路 TCP,不会导致连接失败。

  3. 缓冲区管理是系统性能的隐藏瓶颈:论文里最有工程价值的部分不是协议设计,而是”机会重传 + 惩罚慢路径”这两个算法——它们把”缓冲区不足时 MPTCP 比单路 TCP 还慢”的反直觉现象翻转了过来。

  4. 测量驱动设计:每一个设计决策背后都有真实网络测量数据。没有对 142 个接入网的中间盒测量,就没有”DATA ACK 必须在选项字段”这个结论。先测量,再设计,是这篇论文方法论上最值得学习的地方。

延伸阅读

  • 官方实现:mptcp.io — Linux 内核 MPTCP 维护站,含内核补丁、测试工具和部署指南
  • IETF 标准:RFC 6824 — TCP Extensions for Multipath Operation — 正式协议规范,含完整选项格式和状态机
  • Apple 技术博客:Multipath TCP in iOS 7 — WWDC 2013,Apple 工程师讲 Siri 如何用 MPTCP 做无缝切换
  • 中间盒测量前驱:tcp — 理解 MPTCP 需要先清楚 TCP 的 SEQ/ACK/窗口机制
  • quic — Google 的另一条路:绕开 TCP 中间盒问题而不是与之共存

关联

  • tcp —— MPTCP 是 TCP 的超集,复用了三次握手、滑动窗口、拥塞控制的全部概念
  • quic —— 与 MPTCP 目标相似(多路径、连接迁移),但选择了在 UDP 上重建传输层而非扩展 TCP
  • tcp-vegas-1995 —— Vegas 的 RTT 测量方法被 MPTCP 借鉴来检测慢路径并触发惩罚算法
  • [[tls-1.3]] —— MPTCP 的多子流架构与 TLS 1.3 的 0-RTT 恢复都在解决同一问题:让连接跨越网络变化存活
  • http-2 —— HTTP/2 的多路复用解决的是应用层的并行问题,MPTCP 解决的是传输层的多路径问题,两者互补
  • dns —— ADD_ADDR 机制依赖主机发现自己的多地址,DNS 是多宿主服务器发布地址的基础设施
  • websocket-rfc-6455 —— WebSocket 长连接在移动场景下因网络切换频繁断连,MPTCP 是从传输层根治该问题的方案

反向链接

  • dns —— DNS — 把全球域名解析切成一棵可分布维护的树
  • http-2 —— HTTP/2 — 把 HTTP 从文本协议改造成二进制多路复用
  • lwip —— lwIP — ~40KB ROM 跑完整 TCP/IP 的嵌入式网络栈
  • quic —— QUIC — 把可靠传输从内核搬到用户空间
  • smoltcp —— smoltcp — 不依赖操作系统的 Rust TCP/IP 协议栈
  • tcp —— TCP — 在不可靠的 IP 上凿出一条 reliable 字节流
  • tcp-vegas-1995 —— TCP Vegas 1995 — 不等丢包,靠 RTT 早一步看见拥塞
  • websocket-rfc-6455 —— WebSocket RFC 6455 — 让浏览器和服务器开一条不挂断的双向电话