乐于分享
好东西不私藏

Linux内核MPTCP路径管理器源码解析:子流何时创建、何时销毁

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 实现:

  1. 内核 PM(pm_kernel.c):默认实现,基于配置规则自动决策,本文重点
  2. 用户态 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 没有 subflow flag,PM 不会用它创建子流
  • 如果 endpoint 没有 signal flag,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++;
    }
}

决策逻辑拆解:

  1. 获取配置限制:从 pm_nl_pernet 读取三个关键限制

    • add_addr_signal_max:最多发送几个 ADD_ADDR(默认 0)
    • local_addr_max:最多使用几个本地地址(默认 0)
    • subflows_max:最多几条子流(默认 2)
  2. 决策 1:是否发送 ADD_ADDR

    • 条件:msk->pm.add_addr_signaled < add_addr_signal_max
    • 如果满足,调用 select_signal_address() 选择一个有 signal flag 的 endpoint
    • 发送 ADD_ADDR Option,通告这个地址给对端
  3. 决策 2:是否创建新子流

    • 条件:msk->pm.local_addr_used < local_addr_max && msk->pm.subflows < subflows_max
    • 如果满足,调用 select_local_address() 选择一个有 subflow flag 的 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;
}

选择逻辑:

  1. 遍历 local_addr_list 链表
  2. 筛选条件:
    • 必须有 MPTCP_PM_ADDR_FLAG_SUBFLOW flag
    • Address_ID 在 id_avail_bitmap 中标记为可用
  3. 返回第一个匹配的 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);
}

关键步骤:

  1. 创建新的 TCP socket
  2. 绑定本地地址(5G 接口的 IP)
  3. 设置子流上下文,标记为 MP_JOIN 子流
  4. 发起 MP_JOIN 三次握手(携带 Token、Nonce、Address_ID)
  5. 握手成功后,子流加入 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 配置了 signal flag
  • 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
  • 有可用的本地地址(有 subflow flag 的 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 个地址

流程:

  1. T-Box 向服务端主 IP(203.0.113.1)发起 MPTCP 连接(MP_CAPABLE)
  2. 连接建立后,服务端 PM 调用 select_signal_address(),选中备用 IP
  3. 服务端发送 ADD_ADDR Option,通告 203.0.113.2
  4. T-Box 收到 ADD_ADDR,PM 检查 add_addr_accepted < 1,决定创建子流
  5. T-Box 向 203.0.113.2 发起 MP_JOIN 握手
  6. 第二条子流建立

实战命令:

# 服务端:配置 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;
            }
        }
    }
}

关键步骤:

  1. 遍历 msk->conn_list 子流链表
  2. 找到匹配 Address_ID 的子流
  3. 发送 TCP FIN 包,关闭子流
  4. 从链表移除
  5. 释放 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 接口监听事件,下发决策

工作流程:

  1. 内核 PM 检测到事件(如新接口上线),通过 Netlink 通知用户态程序
  2. 用户态程序根据策略做决策(如检查信号强度)
  3. 用户态程序通过 Netlink 下发命令(如”创建子流到 192.168.2.2″)
  4. 内核 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 微秒内完成了决策:

  1. 检查 subflows_max:配置为 2,当前只有 1 条子流,还能创建 1 条
  2. 调用 select_local_address():找到 5G 接口的 endpoint(192.168.2.2, id=2)
  3. 调用 __mptcp_subflow_connect():发起 MP_JOIN 握手
  4. 握手成功,第二条子流建立

整个过程,应用程序毫无感知。数据继续通过 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 实现
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Linux内核MPTCP路径管理器源码解析:子流何时创建、何时销毁

猜你喜欢

  • 暂无文章