MPTCP 与多路径传输

系统讲解多路径 TCP(MPTCP)的协议机制与工程落地:从 MP_CAPABLE/MP_JOIN 子流握手、ADD_ADDR 地址通告,到 default/redundant/backup 调度器与 LIA/OLIA 耦合拥塞控制,再到 Linux 内核参数、ip mptcp 配置与部署现状。

手机同时连着 Wi-Fi 和 4G,笔记本同时插着有线网卡和 Wi-Fi——大多数情况下,系统只会选其中一条路,另一条闲着。传统 TCP 把「一条连接 = 一个四元组」写死在协议里,链路切换意味着连接断开重建。多路径 TCP(Multipath TCP, MPTCP)要打破这个限制:让一条连接同时跑在多条路径上,聚合带宽、秒级故障切换。

本文先讲 MPTCP 在协议栈中的位置与设计目标,再拆解子流如何建立与通告地址,然后重点讲调度器与耦合拥塞控制这两个决定「多路径到底有没有用」的机制,最后落到 Linux 内核参数、ip mptcp 配置与当前部署现状。

一、MPTCP 的动机与协议栈位置

1.1 一条连接、多条路径

MPTCP 在 TCP 之上加了一层「多路径管理层」:应用看到的仍是一个普通 socket,但底层由多条 TCP 子流(subflow)承载。每条子流有自己的四元组与拥塞窗口,MPTCP 层负责把应用字节流按序重组。

协议栈位置:
  应用层(socket API,无感知)
  -----------------------------
  MPTCP 层(序号映射、调度、重传)
  -----------------------------
  TCP 子流 1    TCP 子流 2    TCP 子流 3
  (Wi-Fi)       (4G)          (有线)
  -----------------------------
  IP 层

1.2 设计目标

MPTCP(RFC 8684)有三个明确目标:吞吐聚合(多条路径同时传,总带宽相加)、可靠性(一条路径断了,流量无缝切到另一条)、向后兼容(对不支持 MPTCP 的对端,退化为普通 TCP)。

目标机制收益
吞吐聚合多子流并行传输带宽叠加
可靠性子流独立故障恢复秒级切换、零中断
向后兼容MP_CAPABLE 协商降级不破坏现有网络
资源利用同时用多张网卡不浪费空闲链路

1.3 与应用层多连接的差异

应用层也能开多条 TCP 连接做并行(如 HTTP 多连接),但那要求应用自己感知多路径并做重组,且每条连接是独立拥塞控制。MPTCP 把它下沉到传输层:应用零改动,且多子流共享同一个拥塞控制目标,对网络更友好。

应用层多连接 vs MPTCP:
  应用层:应用需自己分片、重组、处理乱序
         多条连接各自拥塞控制 → 对共享瓶颈不友好
  MPTCP :内核透明,应用无感
         耦合拥塞控制 → 公平共享瓶颈

二、子流建立与地址管理

2.1 MP_CAPABLE 握手

一条 MPTCP 连接的建立始于普通 TCP 三次握手,但在 TCP 选项里携带 MP_CAPABLE:双方交换密钥与能力标志,协商出连接级的 MPTCP 密钥,用于后续子流的身份认证。第一个子流就叫「初始子流」。

MP_CAPABLE 握手(在 TCP 选项里):
  SYN      : MP_CAPABLE + 客户端密钥 + flags
  SYN/ACK  : MP_CAPABLE + 服务端密钥 + flags
  ACK      : MP_CAPABLE + 双方密钥(确认)
  → 建立 MPTCP 连接级密钥,用于 MP_JOIN 认证

  若任一方不支持 MP_CAPABLE → 退化为普通 TCP

2.2 MP_JOIN:追加子流

要增加一条新路径(如从 Wi-Fi 换到 4G 再开一条),客户端发起带 MP_JOIN 选项的握手,用连接级密钥派生的 HMAC 证明自己属于同一条 MPTCP 连接。服务端验证通过后,新子流加入。

MP_JOIN 握手(子流 2,客户端发起):
  SYN      : MP_JOIN + token + nonce + HMAC(密钥, nonce)
  SYN/ACK  : MP_JOIN + HMAC(密钥, 双方 nonce)
  ACK      : MP_JOIN + HMAC
  → 新子流加入,共享同一个 MPTCP 连接

  token = 密钥的哈希(对端用它找到对应连接)
  HMAC  = 证明「我有连接级密钥」

2.3 ADD_ADDR:通告新地址

服务端(或客户端)可通过 ADD_ADDR 通告自己的其他地址,让对端主动来连。这在多宿主服务器上很有用:服务端有两个 IP,客户端可以同时连两个,形成两条子流。REMOVE_ADDR 则用于撤销。

ADD_ADDR 语义:
  发送方:「我还有一个地址 X,你也可以连过来」
  接收方:可主动向 X 发起 MP_JOIN → 形成新子流
  REMOVE_ADDR:某地址不再可用(如网卡 down)

  地址可以是 IPv4 或 IPv6 → MPTCP 天然支持 v4/v6 混合
# 查看某条 MPTCP 连接的子流
ip mptcp endpoint show
ss -M          # 显示 MPTCP 连接与其子流

三、调度器:包走哪条路

3.1 调度器的职责

MPTCP 层要为每个待发数据段选择一条子流。这个选择策略就是调度器(scheduler),它决定了「聚合」还是「冗余」。内核默认提供三种,各有取舍。

调度器策略特点
default优先用 RTT 最低的子流低延迟优先,易聚合
redundant同一数据在多条子流上各发一份极致可靠,浪费带宽
backup主路径优先,备路径仅在主路径不可用时启用省流量,适合计费链路
fullmesh服务端建立全互联子流多宿主服务器场景

3.2 default 调度器与聚合

default 调度器把数据优先发到「预计最早到达」的子流(用 RTT 估计)。当某条子流拥塞窗口填满时,剩余数据发到其他子流,从而实现带宽聚合。它的目标是最小化传输时间。

default 调度器逻辑:
  1. 对每条子流估计 RTT
  2. 选择「最早可发送」的子流(cwnd 有空位且 RTT 低)
  3. 若所有子流都满 → 按最小 RTT 排入最空闲的
  效果:多条高速路径叠加,吞吐接近之和

3.3 backup 与冗余的取舍

backup 模式把某些子流标记为「备用」:只要主路径可用,数据只走主路径;主路径失效才启用备用。这适合「主路径免费、备用路径计费」的场景(如 Wi-Fi 优先、蜂窝备用)。redundant 模式则相反,每条数据在所有子流上各发一份,换取极致可靠,代价是带宽浪费。

# 把某条子流设为 backup(如蜂窝链路备用)
ip mptcp endpoint add 192.0.2.1 dev wwan0 backup

# 查看 endpoint 标记
ip mptcp endpoint show

四、耦合拥塞控制

4.1 为什么不能各管各的

如果每条子流各自跑标准 TCP 拥塞控制,它们会在共享瓶颈上互相竞争,变得比单条 TCP 更「贪心」,损害其他流的公平性。MPTCP 的解法是「耦合」:让各子流的拥塞窗口联动,整体行为与一条 TCP 相当。

非耦合的问题:
  两条子流共享同一瓶颈 → 各自独立增窗 → 合计占用 2 倍带宽
  → 对同链路的普通 TCP 不公平

耦合的目标:
  多子流整体表现为「一条 TCP」
  → 与单路径流公平共享瓶颈

4.2 LIA 与 OLIA

LIA(Linked Increases Algorithm)是最早的耦合算法,以「总窗口增长不超过单路径」为约束。OLIA 在其基础上改进了窗口分配,减少某些子流被饿死的情况。两者都是内核可选算法。

# 查看/设置 MPTCP 拥塞控制算法
sysctl net.mptcp.available_congestion_control
sysctl net.mptcp.congestion_control
# 可选值通常有:lia、olia、reno(部分内核)

# 设为 OLIA
sysctl -w net.mptcp.congestion_control=olia
算法思路特点
LIA总窗口增长 ≤ 单路径公平性好,分配可能不均
OLIA优化窗口分配减少饿死,性能更均衡
Reno/CUBIC各子流独立(非耦合)仅供对比,不推荐生产

4.3 与单路径拥塞控制的关系

MPTCP 的耦合算法约束的是「跨子流的窗口总量」,而每条子流内部的丢包恢复仍遵循其底层 TCP 算法(CUBIC、BBR 等)。理解单路径算法的行为,是调优 MPTCP 的基础——若底层用 BBR,其基于带宽时延积的模型会与 LIA/OLIA 的窗口耦合产生不同互动,需要实测才能确定组合效果。

五、部署现状与内核配置

5.1 Linux 内核支持

MPTCP 自 Linux 5.6 起并入主线内核。默认对「应用透明」:可以配置成全局对所有 TCP 连接尝试 MPTCP(需对端也支持,否则退化)。生产环境更推荐用 ip mptcp limits 与 endpoint 精细控制。

# 检查内核是否支持 MPTCP
sysctl net.mptcp.enabled
# net.mptcp.enabled = 1

# 设置子流数量上限(主动发起的 / 接受的)
ip mptcp limits set subflow 4 add_addr_accepted 4

# 添加本地 endpoint(可指定是否 backup)
ip mptcp endpoint add 10.0.0.2 dev eth0
ip mptcp endpoint add 192.0.2.1 dev wwan0 backup

# 全局开关
sysctl -w net.mptcp.enabled=1

5.2 应用侧透明与 mptcpize

对大多数应用,MPTCP 是透明的——只要内核与对端支持,TCP socket 会自动用上。但有些应用显式设置了 IPPROTO_TCP,需要用 mptcpize 包装,把 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 改写成 IPPROTO_MPTCP。

# 用 mptcpize 运行一个原本不支持 MPTCP 的程序
mptcpize run curl https://example.com

# 或设置环境变量让部分程序自动启用
# 应用也可直接用 IPPROTO_MPTCP 创建 socket
/* 应用显式使用 MPTCP */
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);

5.3 中间设备的现实障碍

MPTCP 的落地难点不在两端,而在中间:不少防火墙、NAT、负载均衡器会丢弃带未知 TCP 选项的包,或改写序号导致 MPTCP 校验失败。此外,很多服务器与 CDN 尚未启用 MPTCP,导致「客户端支持、服务端不支持」而退化为普通 TCP。

障碍表现应对
中间盒丢弃 TCP 选项握手失败或降级选路径时避开;升级设备
NAT 改写序号MPTCP 校验失败现代 NAT 多数兼容
服务端未启用退化为普通 TCP服务端也开 MPTCP
计费链路被误用流量费用上升用 backup 标记
# 用 ss 确认连接是否真的跑在 MPTCP 上
ss -M -t
# 若显示 subflow 信息,说明 MPTCP 生效;否则已退化

5.4 与 QUIC 多路径的对比

QUIC 也支持多路径(Multipath QUIC),且天然带加密与连接迁移。MPTCP 的优势是「对应用完全透明、可在内核生效」,QUIC 多路径的优势是「用户态可迭代、与 HTTP/3 天然结合、加密无中间盒干扰」。二者并非替代关系,而是分别适合「改造现有 TCP 应用」与「新建基于 QUIC 的服务」。相关背景可参考 HTTP/3 与 QUIC 协议 。

维度MPTCPMultipath QUIC
层次内核传输层用户态 + UDP
透明性对应用完全透明需 QUIC 栈支持
加密无(依赖上层 TLS)内建
中间盒干扰较敏感(TCP 选项)小(UDP + 加密)
部署成熟度内核 5.6+,服务端少生态演进中

六、排错与验证

6.1 确认是否启用

MPTCP 最常见的问题是「以为启用了,其实退化了」。验证要分三层:内核开关、连接是否协商成功、子流是否建立。

# 1. 内核开关
sysctl net.mptcp.enabled

# 2. 连接是否 MPTCP(-M 只显示 MPTCP 连接)
ss -M -t

# 3. 子流与 endpoint
ip mptcp endpoint show
ip mptcp limits show

# 4. 内核统计(子流创建失败、降级次数)
nstat -az | grep -i mptcp

6.2 常见故障对照

现象原因排查
连接退化为普通 TCP对端不支持/中间盒丢选项ss -M 确认,抓包看 MP_CAPABLE
有连接无子流未配 endpoint 或 limits 为 0ip mptcp endpoint show、limits show
子流建立失败MP_JOIN HMAC 校验失败抓包看 MP_JOIN,检查密钥协商
吞吐未聚合调度器选单路径/路径 RTT 悬殊检查子流 RTT,考虑 backup 标记
蜂窝流量异常backup 未标记ip mptcp endpoint add ... backup

6.3 用抓包确认子流

当 ss -M 显示的是 MPTCP 连接但吞吐没有聚合时,抓包看子流实际行为最直接。关注三类选项:MP_CAPABLE(建连)、MP_JOIN(加子流)、ADD_ADDR(地址通告)。若只看到 MP_CAPABLE 而没有 MP_JOIN,说明子流根本没建起来。

# 抓 MPTCP 相关 TCP 选项
tcpdump -i any -nn 'tcp[tcpflags] & tcp-syn != 0' -v

# 过滤带 MPTCP 选项的握手(Wireshark 可直接解析 mptcp 选项)
tshark -i eth0 -Y 'tcp.options.mptcp' -V | head -60

# 统计子流建立失败的内核计数
nstat -az | grep -iE 'MPTCP|Mptcp'
抓包现象含义动作
无 MP_CAPABLE一端未启用或中间盒剥离检查 net.mptcp.enabled 与设备
有 MP_CAPABLE 无 MP_JOIN未配 endpoint 或路径不可达ip mptcp endpoint show
MP_JOIN 后无数据子流建立但未被调度检查 RTT 与 backup 标记
ADD_ADDR 被忽略对端不接受新地址确认对端 MPTCP 实现

6.4 部署决策清单

在决定是否上 MPTCP 之前,按下面的清单逐项确认,能避免「配了半天其实已退化」的返工。

上线前检查:
  [ ] 客户端与服务端内核均 >= 5.6 且 net.mptcp.enabled=1
  [ ] 路径上的防火墙/NAT 不丢弃未知 TCP 选项
  [ ] 需要聚合的接口都已 ip mptcp endpoint add
  [ ] 计费链路已标记 backup,避免意外流量
  [ ] 已设置子流上限(ip mptcp limits set),防止子流爆炸
  [ ] 监控 ss -M 与 nstat,确认未静默退化
  [ ] 明确底层单路径拥塞控制(CUBIC/BBR)与耦合算法的组合

七、小结

主题核心要点落地建议
定位传输层多路径,应用透明内核 5.6+,两端都要支持
握手MP_CAPABLE 建连、MP_JOIN 加子流关注中间盒兼容性
地址ADD_ADDR 通告多地址多宿主服务器配 endpoint
调度default 聚合、backup 省流量计费链路务必标 backup
拥塞LIA/OLIA 耦合保公平优先 OLIA
排错先确认没退化ss -M + nstat

MPTCP 解决的是一个根本性错配:物理世界有多个网络接口,而传统 TCP 只认一条路径。它把「聚合、冗余、无缝切换」下沉到传输层,让应用零改动获得多路径能力。落地的主要阻力不在协议本身,而在中间设备与服务端支持度。想补齐传输层基础,可阅读 TCP 协议深度剖析 ;理解多子流的窗口耦合,则要回到 TCP 拥塞控制算法 ;若面向新建服务,也可以对照 HTTP/3 与 QUIC 协议 里的多路径与连接迁移设计。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「network」更多文章

  1. 流量整形与 TC/qdisc
  2. 网络命名空间与容器网络底层
  3. WireGuard 与现代 VPN