Linux内核MPTCP路径管理器源码解析:子流何时创建、何时销毁
MPTCP 深度解析系列(6/20),本文基于 Linux 内核主线代码(net/mptcp/pm_kernel.c)编写
T-Box 正在 4G 网络上跑 MPTCP 连接,稳定上传车辆数据。车辆驶入高速公路,5G 基站信号覆盖,5G 接口瞬间获得了 IP 地址。
这时,一个关键问题摆在 Linux 内核面前:要不要用这个新接口建立第二条子流?
如果建,能提升带宽,数据上传更快,但也会增加功耗和连接复杂度。如果不建,可能错过更好的网络路径,白白浪费 5G 的高带宽。
谁来做这个决策?
不是应用程序——它只管调用 send(),根本不知道有几个网络接口。不是 TCP 层——每条 TCP 子流只管自己的传输,不知道 MPTCP 的全局状态。
答案是 **Path Manager(路径管理器)**——MPTCP 的”大脑”,负责决定何时创建子流、何时销毁子流、何时向对端通告新地址。
这篇文章,我们就跟着 T-Box 的移动,从连接建立到子流创建、销毁,完整看一遍 Path Manager 的工作流程。每个决策点,我们都会深入内核源码,看看那 100 微秒里到底发生了什么。
一、Path Manager 的职责:MPTCP 的”交通指挥中心”
在深入源码之前,我们需要先理解 Path Manager 在 MPTCP 架构中的位置和职责。
PM 在架构中的位置
应用层 [App: send()/recv()]
|
MPTCP 层 [mptcp_sock]
/ | \
PM(大脑) Scheduler(调度) 拥塞控制
|
子流管理(创建/销毁)
|
TCP 层 [tcp_sock × N]
Path Manager 不在数据路径上,它在控制路径上。数据包不经过 PM,PM 只负责管理”路”本身——哪些路可以用、什么时候开新路、什么时候关闭旧路。
打个比方:如果把 MPTCP 比作一个物流系统,那么:
-
Path Manager = 交通指挥中心,决定开哪些车道、何时开、何时关 -
Scheduler = 调度员,决定每个包裹走哪条车道 -
TCP 子流 = 实际的运输车辆,负责把包裹送到目的地
PM 的两大核心职责
职责 1:endpoint 管理
endpoint 是本地网络接口的抽象,包含:
-
IP 地址(IPv4 或 IPv6) -
端口(可选) -
Address_ID(1 字节的逻辑标识,1-255) -
flags(signal / subflow / backup / fullmesh)
PM 维护一个 endpoint 列表,决定哪些接口可以用于 MPTCP。用户通过 ip mptcp endpoint 命令配置,PM 根据配置做决策。
职责 2:子流生命周期决策
-
何时创建子流:连接建立时、新接口上线时、收到 ADD_ADDR 时 -
何时销毁子流:接口失效时、收到 REMOVE_ADDR 时、超过 subflows_max 限制时
PM 的两种实现
Linux 内核提供两种 PM 实现:
-
内核 PM(pm_kernel.c):默认实现,基于配置规则自动决策,本文重点 -
用户态 PM(pm_userspace.c):内核 6.x+ 支持,通过 Netlink 接口让用户态程序控制
大多数场景用内核 PM 就够了。只有在需要根据信号强度、网络成本等复杂策略动态调整时,才需要用户态 PM。
实战命令:
# 查看当前 endpoint 配置
ip mptcp endpoint show
# 查看 limits 配置(subflows_max 等)
ip mptcp limits show
# 输出示例:
# subflows 2 add_addr_accepted 0
二、endpoint 配置:PM 的”地图”
要理解 PM 的决策,必须先理解 endpoint。endpoint 是 PM 的”地图”——没有地图,指挥中心无从决策。
endpoint 的内核数据结构
源码位置:net/mptcp/pm_kernel.c 第 17-29 行
structpm_nl_pernet {
spinlock_t lock; // 保护并发访问
structlist_headlocal_addr_list;// endpoint 链表
unsignedint addrs; // 当前 endpoint 数量
unsignedint add_addr_signal_max; // 最多发送几个 ADD_ADDR
unsignedint add_addr_accept_max; // 最多接受几个 ADD_ADDR
unsignedint local_addr_max; // 最多使用几个本地地址
unsignedint subflows_max; // 最多几条子流
unsignedint next_id; // 下一个 Address_ID
DECLARE_BITMAP(id_bitmap, MPTCP_PM_MAX_ADDR_ID + 1);
};
这个结构体存储在网络命名空间(network namespace)中,每个命名空间一份。关键字段:
-
local_addr_list:所有配置的 endpoint 链表,PM 决策时遍历这个链表 -
subflows_max:全局子流数量上限,默认 2,可通过ip mptcp limits修改 -
add_addr_signal_max:最多向对端通告几个地址,默认 0(不通告) -
add_addr_accept_max:最多接受对端通告的几个地址,默认 0(不接受)
endpoint 配置命令详解
回到 T-Box 的场景。工程师在 T-Box 上配置两个 endpoint:
# 配置 4G 接口(eth0)
ip mptcp endpoint add 192.168.1.2 dev eth0 subflow id 1
# 配置 5G 接口(eth1)
ip mptcp endpoint add 192.168.2.2 dev eth1 subflow backup id 2
# 查看配置
ip mptcp endpoint show
# 输出:
# 192.168.1.2 id 1 subflow dev eth0
# 192.168.2.2 id 2 subflow backup dev eth1
flags 含义:
-
subflow:允许用这个地址主动建立子流(必须有这个 flag,PM 才会用它创建子流) -
signal:向对端发送 ADD_ADDR,通告这个地址(服务端通常配置这个) -
backup:标记为备份子流,优先级低,只在主子流失效时使用 -
fullmesh:与所有对端地址建立子流,全网格模式(数据中心场景)
注意:
-
如果 endpoint 没有 subflowflag,PM 不会用它创建子流 -
如果 endpoint 没有 signalflag,PM 不会向对端通告它 -
一个 endpoint 可以同时有多个 flags,如 signal subflow
Netlink 接口实现
ip mptcp 命令通过 Netlink 与内核通信。
源码位置:net/mptcp/pm_netlink.c 第 31-89 行
staticintmptcp_pm_parse_pm_addr_attr(struct nlattr *tb[],
const struct nlattr *attr,
struct genl_info *info,
struct mptcp_addr_info *addr,
bool require_family){
// 解析用户配置的地址
if (tb[MPTCP_PM_ADDR_ATTR_ID])
addr->id = nla_get_u8(tb[MPTCP_PM_ADDR_ATTR_ID]);
addr->family = nla_get_u16(tb[MPTCP_PM_ADDR_ATTR_FAMILY]);
if (addr->family == AF_INET)
addr->addr.s_addr = nla_get_in_addr(tb[addr_addr]);
else
addr->addr6 = nla_get_in6_addr(tb[addr_addr]);
if (tb[MPTCP_PM_ADDR_ATTR_PORT])
addr->port = htons(nla_get_u16(tb[MPTCP_PM_ADDR_ATTR_PORT]));
return0;
}
用户配置的 endpoint 最终存储在 pm_nl_pernet->local_addr_list 链表中,PM 决策时遍历这个链表。
endpoint 与 Address_ID 的映射
Address_ID 是 MPTCP 中的关键概念——它是 1 字节的逻辑标识(1-255),在 MP_JOIN、ADD_ADDR、REMOVE_ADDR 中使用。
为什么需要 Address_ID?因为 IP 地址可能因为 NAT 而改变,但 Address_ID 是双方约定的不变标识。类比:手机号(Address_ID)vs 家庭地址(IP)——换了住址,手机号不变。
PM 维护一个 id_bitmap,确保每个 Address_ID 唯一分配。
实战命令:
# 添加 endpoint
ip mptcp endpoint add 192.168.1.2 dev eth0 subflow
# 删除 endpoint
ip mptcp endpoint delete id 1
# 修改 endpoint flags
ip mptcp endpoint change id 1 backup
# 查看 endpoint
ip mptcp endpoint show
三、子流创建决策:核心函数 mptcp_pm_create_subflow_or_signal_addr()
现在,T-Box 进入 5G 覆盖区,5G 接口获得 IP 地址。PM 检测到新接口,开始决策。
决策触发时机
PM 的决策在三个时机触发:
时机 1:连接建立时
源码位置:net/mptcp/pm_kernel.c 第 370-373 行
staticvoidmptcp_pm_nl_fully_established(struct mptcp_sock *msk){
mptcp_pm_create_subflow_or_signal_addr(msk);
}
当 MPTCP 连接完成 MP_CAPABLE 握手后,mptcp_pm_nl_fully_established() 被调用,触发 PM 决策。此时 PM 会检查:是否需要创建额外的子流?是否需要向对端通告地址?
时机 2:新子流建立时
源码位置:net/mptcp/pm_kernel.c 第 375-378 行
staticvoidmptcp_pm_nl_subflow_established(struct mptcp_sock *msk){
mptcp_pm_create_subflow_or_signal_addr(msk);
}
当一条新子流完成 MP_JOIN 握手后,PM 再次决策——也许还能创建更多子流。
时机 3:endpoint 配置变化时
用户通过 ip mptcp endpoint add 添加新 endpoint 后,PM 会遍历所有已建立的 MPTCP 连接,检查是否需要创建新子流。
源码位置:net/mptcp/pm_kernel.c 第 740-770 行
staticintmptcp_nl_add_subflow_or_signal_addr(struct net *net,
struct mptcp_addr_info *addr){
structmptcp_sock *msk;
// 遍历所有 MPTCP 连接
while ((msk = mptcp_token_iter_next(net, &s_slot, &s_num)) != NULL) {
lock_sock(sk);
spin_lock_bh(&msk->pm.lock);
mptcp_pm_create_subflow_or_signal_addr(msk); // 触发决策
spin_unlock_bh(&msk->pm.lock);
release_sock(sk);
}
return0;
}
核心决策函数:mptcp_pm_create_subflow_or_signal_addr()
这是 PM 的大脑,所有决策逻辑都在这里。
源码位置:net/mptcp/pm_kernel.c 第 256-368 行
staticvoidmptcp_pm_create_subflow_or_signal_addr(struct mptcp_sock *msk){
structsock *sk = (structsock *)msk;
unsignedint add_addr_signal_max;
bool signal_and_subflow = false;
unsignedint local_addr_max;
structpm_nl_pernet *pernet;
structmptcp_pm_locallocal;
unsignedint subflows_max;
pernet = pm_nl_get_pernet(sock_net(sk));
// 获取配置限制
add_addr_signal_max = mptcp_pm_get_add_addr_signal_max(msk);
local_addr_max = mptcp_pm_get_local_addr_max(msk);
subflows_max = mptcp_pm_get_subflows_max(msk);
// 决策 1:是否发送 ADD_ADDR
while (msk->pm.add_addr_signaled < add_addr_signal_max) {
// 选择一个有 signal flag 的 endpoint
if (!select_signal_address(pernet, msk, &local))
break;
// 发送 ADD_ADDR Option
mptcp_pm_announce_addr(msk, &local.addr, false);
msk->pm.add_addr_signaled++;
}
// 决策 2:是否创建新子流
while (msk->pm.local_addr_used < local_addr_max &&
msk->pm.subflows < subflows_max) {
// 选择一个有 subflow flag 的 endpoint
if (!select_local_address(pernet, msk, &local))
break;
// 填充远程地址列表
nr = fill_remote_addresses_vec(msk, &local.addr, fullmesh, addrs);
if (nr == 0)
continue;
// 创建子流
for (i = 0; i < nr; i++)
__mptcp_subflow_connect(sk, &local, &addrs[i]);
msk->pm.local_addr_used++;
}
}
决策逻辑拆解:
-
获取配置限制:从
pm_nl_pernet读取三个关键限制 -
add_addr_signal_max:最多发送几个 ADD_ADDR(默认 0) -
local_addr_max:最多使用几个本地地址(默认 0) -
subflows_max:最多几条子流(默认 2) -
决策 1:是否发送 ADD_ADDR
-
条件: msk->pm.add_addr_signaled < add_addr_signal_max -
如果满足,调用 select_signal_address()选择一个有signalflag 的 endpoint -
发送 ADD_ADDR Option,通告这个地址给对端 -
决策 2:是否创建新子流
-
条件: msk->pm.local_addr_used < local_addr_max && msk->pm.subflows < subflows_max -
如果满足,调用 select_local_address()选择一个有subflowflag 的 endpoint -
调用 __mptcp_subflow_connect()创建子流
关键计数器:
-
msk->pm.add_addr_signaled:已发送的 ADD_ADDR 数量 -
msk->pm.local_addr_used:已使用的本地地址数量 -
msk->pm.subflows:当前子流数量
本地地址选择:select_local_address()
PM 决定创建子流后,需要选择一个本地地址。
源码位置:net/mptcp/pm_kernel.c 第 102-129 行
staticboolselect_local_address(const struct pm_nl_pernet *pernet,
const struct mptcp_sock *msk,
struct mptcp_pm_local *new_local){
structmptcp_pm_addr_entry *entry;
bool found = false;
msk_owned_by_me(msk);
rcu_read_lock();
// 遍历 endpoint 链表
list_for_each_entry_rcu(entry, &pernet->local_addr_list, list) {
// 必须有 subflow flag
if (!(entry->flags & MPTCP_PM_ADDR_FLAG_SUBFLOW))
continue;
// Address_ID 必须可用(未被使用)
if (!test_bit(entry->addr.id, msk->pm.id_avail_bitmap))
continue;
new_local->addr = entry->addr;
new_local->flags = entry->flags;
new_local->ifindex = entry->ifindex;
found = true;
break;
}
rcu_read_unlock();
return found;
}
选择逻辑:
-
遍历 local_addr_list链表 -
筛选条件: -
必须有 MPTCP_PM_ADDR_FLAG_SUBFLOWflag -
Address_ID 在 id_avail_bitmap中标记为可用 -
返回第一个匹配的 endpoint
回到 T-Box 的场景:
-
4G 接口(192.168.1.2, id=1)已用于主子流, id_avail_bitmap中 bit 1 = 0 -
5G 接口(192.168.2.2, id=2)未使用, id_avail_bitmap中 bit 2 = 1 -
select_local_address()返回 5G 接口
子流建立:__mptcp_subflow_connect()
PM 选定本地地址后,调用 __mptcp_subflow_connect() 创建子流。
源码位置:net/mptcp/subflow.c
void __mptcp_subflow_connect(struct sock *sk,
const struct mptcp_pm_local *local,
const struct mptcp_addr_info *remote) {
structmptcp_sock *msk = mptcp_sk(sk);
structsocket *sf_sock;
structsock *ssk;
// 创建新的 TCP socket
err = sock_create_kern(net, local->addr.family, SOCK_STREAM,
IPPROTO_TCP, &sf_sock);
ssk = sf_sock->sk;
// 绑定本地地址
err = kernel_bind(sf_sock, (struct sockaddr *)&local_addr, addrlen);
// 设置 MPTCP 子流上下文
subflow = mptcp_subflow_ctx(ssk);
subflow->request_join = 1; // 标记为 MP_JOIN 子流
subflow->local_id = local->addr.id;
// 发起 MP_JOIN 握手(SYN + Token + Nonce)
err = kernel_connect(sf_sock, (struct sockaddr *)&remote_addr,
addrlen, O_NONBLOCK);
// 握手成功后,子流加入 msk->conn_list
mptcp_subflow_add_to_list(msk, subflow);
}
关键步骤:
-
创建新的 TCP socket -
绑定本地地址(5G 接口的 IP) -
设置子流上下文,标记为 MP_JOIN 子流 -
发起 MP_JOIN 三次握手(携带 Token、Nonce、Address_ID) -
握手成功后,子流加入 msk->conn_list链表
实战命令:
# 查看当前子流数量
ss -Mtin | grep subflows
# 输出示例:
# subflows:2 add_addr_signal:0
# 查看子流详细信息
ss -Mtin sport = :8080
# 输出示例:
# ESTAB 0 0 192.168.1.2:8080 203.0.113.1:443
# mptcp subflows:2 ...
# subflow #1: 192.168.1.2:8080 -> 203.0.113.1:443
# subflow #2: 192.168.2.2:54321 -> 203.0.113.1:443
# 查看 PM 统计
nstat -az | grep Mptcp
# 关注:
# MPTCPMPJoinSynTx ← MP_JOIN SYN 发送次数
四、ADD_ADDR 信号触发机制
T-Box 的场景中,客户端(T-Box)主动创建子流。但在服务端有多个 IP 的场景中,服务端需要通过 ADD_ADDR 通告地址,让客户端来连。
ADD_ADDR 发送决策:select_signal_address()
源码位置:net/mptcp/pm_kernel.c 第 131-160 行
staticboolselect_signal_address(struct pm_nl_pernet *pernet,
const struct mptcp_sock *msk,
struct mptcp_pm_local *new_local){
structmptcp_pm_addr_entry *entry;
bool found = false;
rcu_read_lock();
// 遍历 endpoint 链表
list_for_each_entry_rcu(entry, &pernet->local_addr_list, list) {
// Address_ID 必须可用
if (!test_bit(entry->addr.id, msk->pm.id_avail_bitmap))
continue;
// 必须有 signal flag
if (!(entry->flags & MPTCP_PM_ADDR_FLAG_SIGNAL))
continue;
new_local->addr = entry->addr;
found = true;
break;
}
rcu_read_unlock();
return found;
}
发送条件:
-
endpoint 配置了 signalflag -
msk->pm.add_addr_signaled < add_addr_signal_max
ADD_ADDR 接收处理
客户端收到 ADD_ADDR Option 后,PM 决定是否创建新子流。
源码位置:net/mptcp/pm.c
voidmptcp_pm_nl_add_addr_received(struct mptcp_sock *msk,
const struct mptcp_addr_info *addr){
unsignedint add_addr_accept_max;
add_addr_accept_max = mptcp_pm_get_add_addr_accept_max(msk);
// 检查是否超过接受限制
if (msk->pm.add_addr_accepted >= add_addr_accept_max)
return;
// 决定创建新子流
if (select_local_address(pernet, msk, &local)) {
__mptcp_subflow_connect(sk, &local, addr);
msk->pm.add_addr_accepted++;
}
}
决策条件:
-
msk->pm.add_addr_accepted < add_addr_accept_max -
有可用的本地地址(有 subflowflag 的 endpoint)
典型场景:服务端通告备用 IP
# 服务端配置(有两个公网 IP)
ip mptcp endpoint add 203.0.113.1 dev eth0 subflow # 主 IP
ip mptcp endpoint add 203.0.113.2 dev eth1 signal # 备用 IP
ip mptcp limits set add_addr_signal 1 # 允许通告 1 个地址
# 客户端配置(T-Box)
ip mptcp endpoint add 192.168.1.2 dev eth0 subflow
ip mptcp limits set add_addr_accepted 1 # 允许接受 1 个地址
流程:
-
T-Box 向服务端主 IP(203.0.113.1)发起 MPTCP 连接(MP_CAPABLE) -
连接建立后,服务端 PM 调用 select_signal_address(),选中备用 IP -
服务端发送 ADD_ADDR Option,通告 203.0.113.2 -
T-Box 收到 ADD_ADDR,PM 检查 add_addr_accepted < 1,决定创建子流 -
T-Box 向 203.0.113.2 发起 MP_JOIN 握手 -
第二条子流建立
实战命令:
# 服务端:配置 add_addr_signal
ip mptcp limits set add_addr_signal 2
# 客户端:配置 add_addr_accepted
ip mptcp limits set add_addr_accepted 2
# 监控 ADD_ADDR 事件(需要内核 5.12+)
ip monitor mptcp
# 查看 ADD_ADDR 统计
nstat -az | grep -i addaddr
# 关注:
# MPTCPMPAddAddrTx ← 发送的 ADD_ADDR 数量
# MPTCPMPAddAddrRx ← 接收的 ADD_ADDR 数量
五、子流销毁决策
T-Box 离开高速公路,驶出 5G 覆盖区。5G 接口失去信号,IP 地址失效。PM 检测到这个变化,决定销毁 5G 子流。
销毁触发时机
时机 1:接口失效
网络接口 down 时,内核通知 PM。PM 遍历所有使用该接口的子流,逐一关闭。
时机 2:收到 REMOVE_ADDR
对端发送 REMOVE_ADDR Option,通知某个 Address_ID 不再可用。
源码位置:net/mptcp/pm.c
voidmptcp_pm_nl_rm_addr_received(struct mptcp_sock *msk, u8 rm_id){
structmptcp_rm_listlist = { .nr = 0 };
list.ids[list.nr++] = rm_id;
// 关闭该 Address_ID 对应的所有子流
mptcp_pm_rm_subflow(msk, &list);
}
时机 3:超过限制
用户修改 subflows_max,PM 检测到当前子流数超过新限制,主动关闭多余子流。
子流关闭流程
源码位置:net/mptcp/pm.c
voidmptcp_pm_rm_subflow(struct mptcp_sock *msk,
const struct mptcp_rm_list *rm_list){
structmptcp_subflow_context *subflow, *tmp;
// 遍历子流链表
list_for_each_entry_safe(subflow, tmp, &msk->conn_list, node) {
// 检查 Address_ID 是否在删除列表中
for (i = 0; i < rm_list->nr; i++) {
if (subflow->local_id == rm_list->ids[i] ||
subflow->remote_id == rm_list->ids[i]) {
// 发送 TCP FIN 包
mptcp_subflow_shutdown(sk, ssk, how);
// 从链表移除
list_del(&subflow->node);
// 释放 Address_ID
__clear_bit(subflow->local_id, msk->pm.id_avail_bitmap);
break;
}
}
}
}
关键步骤:
-
遍历 msk->conn_list子流链表 -
找到匹配 Address_ID 的子流 -
发送 TCP FIN 包,关闭子流 -
从链表移除 -
释放 Address_ID,标记为可用(在 id_avail_bitmap中设置对应 bit)
实战命令:
# 手动删除 endpoint(会触发子流销毁)
ip mptcp endpoint delete id 2
# 查看子流状态
ss -Mtin | grep -A 5 MPTCP
# 如果子流正在关闭,会看到 FIN-WAIT 状态
# 修改 subflows_max(会触发多余子流关闭)
ip mptcp limits set subflow 1
# 查看销毁统计
nstat -az | grep -i remove
# 关注:
# MPTCPMPRemoveAddrTx ← 发送的 REMOVE_ADDR 数量
六、用户态 PM 扩展:更灵活的决策
内核 PM 基于配置规则,决策逻辑固定。但在某些场景中,需要更复杂的策略:
-
只在 5G 信号强度 > -90dBm 时创建子流 -
根据云端下发的策略动态调整子流 -
根据网络成本(流量费用)选择路径
这时就需要用户态 PM。
用户态 PM 架构
源码位置:net/mptcp/pm_userspace.c(内核 6.x+)
用户态 PM 的核心思想:
-
内核只执行,不决策:内核 PM 变成”执行器”,只负责创建/销毁子流 -
决策在用户态:用户态程序通过 Netlink 接口监听事件,下发决策
工作流程:
-
内核 PM 检测到事件(如新接口上线),通过 Netlink 通知用户态程序 -
用户态程序根据策略做决策(如检查信号强度) -
用户态程序通过 Netlink 下发命令(如”创建子流到 192.168.2.2″) -
内核 PM 执行命令
使用场景
场景 1:信号强度感知
T-Box 场景中,只在 5G 信号强度 > -90dBm 时创建 5G 子流:
# 用户态程序(伪代码)
defon_new_interface(interface):
if interface.name == "eth1": # 5G 接口
signal_strength = get_5g_signal_strength()
if signal_strength > -90:
# 通过 Netlink 下发命令
mptcp_pm_create_subflow(local="192.168.2.2", remote="203.0.113.1")
场景 2:网络成本感知
根据流量费用选择路径:
defon_subflow_request(local_addr):
if is_expensive_network(local_addr):
# 只在 WiFi 断开时才使用 4G
ifnot wifi_available():
mptcp_pm_create_subflow(local_addr, remote)
else:
mptcp_pm_create_subflow(local_addr, remote)
切换到用户态 PM
# 切换到用户态 PM(需要内核 6.x+)
echo userspace > /proc/sys/net/mptcp/pm_type
# 查看当前 PM 类型
cat /proc/sys/net/mptcp/pm_type
# 输出:userspace
注意:切换到用户态 PM 后,内核 PM 的所有自动决策都会停止,必须由用户态程序接管。
七、写在最后
回到开头那个场景。T-Box 进入 5G 覆盖区,Path Manager 在 100 微秒内完成了决策:
-
检查 subflows_max:配置为 2,当前只有 1 条子流,还能创建 1 条 -
调用 select_local_address():找到 5G 接口的 endpoint(192.168.2.2, id=2) -
调用 __mptcp_subflow_connect():发起 MP_JOIN 握手 -
握手成功,第二条子流建立
整个过程,应用程序毫无感知。数据继续通过 send() 发送,Scheduler 会自动把数据分配到两条子流。
这就是 Path Manager 的价值:在幕后做出最优决策,让 MPTCP “自动驾驶”。
但 PM 只是决定”开哪些路”,数据包具体走哪条路,是由另一个组件决定的——**Packet Scheduler(包调度器)**。
下一篇,我们将深入 Scheduler 的源码,看看它如何在多条子流之间分配数据,如何避免 HoL 阻塞,以及 BLEST 算法的 linger_time 计算公式。
下一篇预告:《MPTCP 包调度器(Packet Scheduler)源码解析:数据该走哪条路》
实战工具速查:
# endpoint 管理
ip mptcp endpoint add <IP> dev <interface> [signal] [subflow] [backup]
ip mptcp endpoint delete id <ID>
ip mptcp endpoint show
# limits 配置
ip mptcp limits set subflow <N>
ip mptcp limits set add_addr_signal <N>
ip mptcp limits set add_addr_accepted <N>
ip mptcp limits show
# 连接状态查看
ss -Mtin # 查看 MPTCP 连接
ss -Mtin sport = :8080 # 查看指定端口的 MPTCP 连接
# 统计信息
nstat -az | grep -i mptcp # 查看 MPTCP 统计
ip monitor mptcp # 监控 MPTCP 事件(内核 5.12+)
# 切换 PM 类型
echo userspace > /proc/sys/net/mptcp/pm_type # 切换到用户态 PM
echo kernel > /proc/sys/net/mptcp/pm_type # 切换回内核 PM
关键源码位置:
-
net/mptcp/pm_kernel.c:256– mptcp_pm_create_subflow_or_signal_addr() -
net/mptcp/pm_kernel.c:102– select_local_address() -
net/mptcp/pm_kernel.c:131– select_signal_address() -
net/mptcp/subflow.c– __mptcp_subflow_connect() -
net/mptcp/pm_netlink.c– Netlink 接口实现 -
net/mptcp/pm_userspace.c– 用户态 PM 实现
夜雨聆风