jimyag's Blog

MPTCP 能做什么:多路径、子流与 CVE-2026-98432 的监听端口

最后更新:

手机从 Wi-Fi 走到 5G 覆盖边缘,正在跑的 SSH 会断。TCP 连接绑在源地址、源端口、目的地址、目的端口上,源地址一变,原来的连接就失效;应用要么重连,要么在传输层解决。

MPTCP 把这件事放到传输层解决。它仍然是 TCP,只是在 TCP 选项里加了一套多路径协商:一条 MPTCP 连接可以有多条普通 TCP 子流,每条子流走一条路径。应用读写的是一个 socket,看到的是同一条可靠字节流。

2026 年 10 月,MPTCP 因为另一个原因被提起。CVE-2026-98432 是 Linux IPv4 TCP 的 use-after-free,和 MPTCP 协议本身无关;但 Anthropic 公开的远程 root 利用打的是一个 MPTCP 监听端口。关掉 MPTCP 能去掉这条利用路径,底层的 TCP 问题还在。

一条连接,多条路径

MPTCP 是 TCP 的扩展,协议由 RFC 8684(MPTCPv1,2020 年)定义,废弃了 RFC 6824。它不改变 TCP 的可靠字节流语义,只是把一条字节流拆到多条路径上传输。

MPTCP 的选项和状态使用下面这些术语:

概念作用
MPTCP 连接应用看到的 socket,由双方的 key/token 标识
子流一条普通 TCP 连接,承载 MPTCP 数据的一部分
DSNMPTCP 层的数据序列号,用于跨子流重组
DSS把子流的 TCP 序列号映射到 DSN
MP_CAPABLE初始握手,交换 key,确认双方支持 MPTCP
MP_JOIN为已有连接增加一条子流
ADD_ADDR / REMOVE_ADDR通告或撤销可用地址
MP_PRIO调整子流优先级,例如标记 backup
MP_FAIL子流级故障,进入恢复流程
MP_FASTCLOSE快速关闭整条 MPTCP 连接

初始子流仍然是一次 TCP 三次握手,只是 SYN、SYN/ACK、ACK 里带了 MP_CAPABLE。双方各有一个 key,token 是 key 的哈希,后续 MP_JOIN 用它标识连接,不需要在网络上暴露 key。连接建立后,服务端可以用 ADD_ADDR 通告自己的另一个地址,客户端据此发起 MP_JOIN,形成第二条子流。

  sequenceDiagram
    autonumber
    participant C as 客户端
    participant S as 服务端

    C->>S: SYN + MP_CAPABLE(key_C)
    S->>C: SYN/ACK + MP_CAPABLE(key_S)
    C->>S: ACK + MP_CAPABLE
    Note over C,S: 双方由 key 计算 token,初始子流建立

    S->>C: ADD_ADDR(10.0.2.2)
    C->>S: MP_JOIN(token_S, nonce_C),新子流
    S->>C: SYN/ACK + MP_JOIN(HMAC)
    C->>S: ACK + MP_JOIN(HMAC)
    Note over C,S: 两条子流属于同一条 MPTCP 连接

    C-->>S: 数据走子流 1
    C-->>S: 数据走子流 2
    Note over C,S: 子流 1 失效,未确认数据在子流 2 重传

每条子流是独立的 TCP 连接,有自己的拥塞控制和重传。MPTCP 层用 DSN 把子流上的数据拼回一条字节流;一条子流丢包,可以在另一条子流上重传。Path Manager 负责子流的创建、删除和地址通告,Packet Scheduler 决定下一个报文走哪条子流。

对端或中间设备不支持 MPTCP 时,SYN/ACK 里没有 MP_CAPABLE,连接会降级成普通 TCP,应用不会收到错误。服务端可以先打开 MPTCP,客户端愿意用再用。

MPTCP 的几种用法

最常见的用法是换网。手机同时有 Wi-Fi 和蜂窝时,MPTCP 把两条路径都握在手里;Wi-Fi 消失后,未确认的数据从蜂窝重传,应用不需要重连。Apple 从 2013 年起就在智能手机上主要用它做这类切换,具体场景写在 mptcp.dev 的用例说明里。对 SSH、视频、游戏这类长连接,这比断线重连更接近用户想要的行为。

两条路径也可以同时用。固网加移动、专线加 5G,都可以让一条连接同时从两条路径发送数据。它不能简单当成链路捆绑:MPTCP 按连接调度,受接收窗口、拥塞控制和路径 RTT 差异影响。两条路径差距太大时,调度器未必能把带宽直接相加,所以它适合两条路径都能稳定到达对端的场景。

只需要备用路径时,可以给 endpoint 加 backup。默认调度器会优先使用非 backup 子流,只有其他子流不可用时才把数据放到 backup 上。服务端也可以通告多个地址,让客户端决定走哪条。这里的“选路”是连接内的子流选择,不是替换系统的路由表。

服务端通常是收益较小的一方。机房链路稳定、带宽充足,MPTCP 的主要价值在客户端侧;但服务端打开 MPTCP 后,客户端才有机会用。普通 TCP 客户端连接 MPTCP listener 时,内核在 accept() 时给应用一个普通 TCP socket,额外开销很小。麻烦的是 L4 负载均衡:ADD_ADDR 通告的地址和端口必须能把后续子流送回同一台后端,否则第二条子流可能落到另一台机器。常见做法是服务端设置 net.mptcp.allow_join_initial_addr_port=0,并用 signal endpoint 通告每台后端专属的地址或端口,负载均衡器按这个地址和端口转发。细节见 mptcp.dev 的 load balancer 说明。

MPTCP 不会自动生效。应用要显式创建 MPTCP socket,或者用 mptcpize(LD_PRELOAD)、eBPF 的 mptcpify 强制已有程序使用,这两个工具都只用于 Linux。它要求两端都实现,中间设备可能剥离 TCP options;也不是所有 TCP socket option 都支持,例如 Linux 上的 MPTCP 目前不兼容 KTLS。

Linux 里怎么用

Linux 从 5.6 开始支持 MPTCP,内核文档在 networking/mptcp。应用通过 IPPROTO_MPTCP(值为 262)创建 socket:

1
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);

如果内核不支持,socket() 会返回 EINVAL(5.6 之前)、EPROTONOSUPPORT(未编译 MPTCP)或 ENOPROTOOPT(被 net.mptcp.enabled=0 关闭)。这个 socket 是 MPTCP 元 socket,内核为它创建第一条 TCP 子流,后续子流由 path manager 创建。

net.mptcp 下有几个常用的 sysctl:

1
2
3
4
sysctl net.mptcp.enabled
sysctl net.mptcp.pm_type
sysctl net.mptcp.scheduler
sysctl net.mptcp.allow_join_initial_addr_port

这里的 sysctl <键名> 只读取当前值,没有修改配置。各键含义是:

  • enabled(启用):MPTCP 开关,按 network namespace(网络命名空间,netns)分别生效,默认 1。
  • pm_type(Path Manager type,路径管理器类型):0 表示内核 path manager,1 表示交给用户态 daemon。
  • scheduler(调度器):选择发送数据使用的 MPTCP 调度器。在 6.12.113 上只有 default,/proc/sys/net/mptcp/available_schedulers 可以看到内核实际加载了哪些。
  • allow_join_initial_addr_port:是否允许对端向初始子流的地址和端口发起 MP_JOIN,默认 1。

路径用 ip mptcp 配置:

1
2
3
4
5
6
7
# 服务端:通告 10.0.2.2,允许两条额外子流,接受两个 ADD_ADDR
ip mptcp limits set subflow 2 add_addr_accepted 2
ip mptcp endpoint add 10.0.2.2 dev eth2 signal

# 客户端:把 10.0.2.1 作为新子流的源地址
ip mptcp limits set subflow 2 add_addr_accepted 2
ip mptcp endpoint add 10.0.2.1 dev eth2 subflow

这些参数有的控制整条连接,有的只描述一个地址:

  • limits set(设置上限):修改当前网络命名空间的 MPTCP 连接限制。
  • subflow 2:在 limits set 中表示每条连接最多允许 2 条额外子流,不包含初始子流。
  • add_addr_accepted 2:允许接受对端通过 ADD_ADDR 通告的 2 个额外地址。
  • endpoint add <地址>(添加端点):把地址加入本机 MPTCP path manager 的端点配置。
  • dev eth2(device,设备):把这个端点关联到网卡 eth2。
  • signal(通告):用 ADD_ADDR 向对端通告这个端点。
  • subflow(子流):在 endpoint add 中表示使用这个本地地址发起额外子流,区别于前面的数量上限。
  • backup(备用):把使用该端点的子流标为备用。
  • fullmesh(全互连):与 subflow 配合,向已知的各个对端地址建立子流。

完整语义见 ip-mptcp(8)。

Go 1.21 开始支持 MPTCP,net.Dialer.SetMultipathTCP 控制拨号;Go 1.24 起监听默认开启 MPTCP(GODEBUG=multipathtcp=2,只影响 listener)。可以用 GODEBUG=multipathtcp=0 或 go.mod 里的 godebug multipathtcp=0 关闭默认值,也可以用 net.ListenConfig.SetMultipathTCP(false) 按 listener 覆盖。取值和默认行为见 Go 的 GODEBUG 文档。

看 MPTCP 状态要分两层:

1
2
3
4
5
ss -Mani                          # MPTCP 元 socket
ss -tani | grep tcp-ulp-mptcp     # 子流,标记为 tcp-ulp-mptcp
nstat -asz | grep MPTcpExt        # 协议计数器
ip -ts mptcp monitor              # 子流和地址事件
ss -Ml                            # MPTCP listener

ss 的短选项可以合写,-Mani 对应 -M -a -n -i,-tani 对应 -t -a -n -i:

  • -M / --mptcp:选择 MPTCP 元 socket。
  • -t / --tcp:选择 TCP socket,用来检查子流。
  • -a / --all(all,全部):同时显示监听和非监听 socket。
  • -n / --numeric(numeric,数字形式):用数字显示端口,不转换成服务名。
  • -i / --info(information,信息):显示内部连接信息。
  • -l / --listening(listening,监听中):只显示 listener。

不加 -a 或 -l 时,ss -M 默认看已建立连接。只看 ss -ltn 会把 MPTCP listener 显示成普通 TCP,容易漏掉。其余两条观察命令的参数是:

  • nstat -a(absolute,绝对值助记):输出累计计数,不计算相对上次查询的增量。
  • nstat -s:不更新查询历史,避免影响下次增量统计。
  • nstat -z / --zeros(zero,零):连值为 0 的计数器也显示。-asz 是这三个选项的合写。
  • ip -ts / -tshort(short timestamp,短时间戳):为监控输出添加简短时间戳;-ts 是一个选项的缩写,不是 -t -s。
  • mptcp monitor:持续观察 MPTCP 地址和子流事件。
  • | grep ...:把输出交给 grep,只保留包含指定文本的行。

参数细节见 ss(8)、nstat(8) 和 ip(8)。括号中的英文用于理解和助记,不代表所有短选项都有同名长选项。

两条子流的实验

在一台 6.12.113 的 Linux 上,用两个 network namespace 和两对 veth 建了两条路径:c1/s1 是 10.0.1.0/24,c2/s2 是 10.0.2.0/24。服务端在 10.0.1.2:5201 监听 MPTCP,通告 10.0.2.2;客户端从 10.0.1.1 连过去,并把 10.0.2.1 配成 subflow endpoint。

连接建立后,客户端看到的元 socket 是:

1
2
ESTAB 0 0 10.0.1.1:... 10.0.1.2:5201
    subflows:1 add_addr_accepted:1 subflows_max:2 ... subflows_total:2

subflows_total:2 表示一条初始子流加一条额外子流。ss -tani 里能看到两条 ESTAB,都带 tcp-ulp-mptcp 和各自的 token/id;一条来自 10.0.1.1,另一条来自 10.0.2.1。

传 500 MiB 数据,在传输中途把第二条路径 c2 down 掉:

1
2
3
4
5
6
T=1  path1 bytes_acked 122.7 MB
     path2 bytes_acked  74.8 MB
T=2  ip -n c link set c2 down
T=3  path1 bytes_acked 336.9 MB
     path2 bytes_acked 154.1 MB(有 unacked/lost,等待重传)
client: read 500.0 MiB
  • ip -n c / -netns c(network namespace):在名为 c 的网络命名空间中执行操作;这里的 -n 与 ss -n 含义不同。
  • link set c2 down:把该命名空间中的网卡 c2 设为关闭状态,用来模拟路径中断。

两条子流都在承载数据;第二条路径断开后,连接没有断,客户端仍然读完 500 MiB。这些数字来自同一台机器上的 veth,不能当作真实带宽,只能说明调度器会把数据分到两条子流,以及一条路径消失后 MPTCP 能在另一条上继续。

如果客户端连的是普通 TCP listener,MPTCP 会降级:服务端 accept() 出来的是普通 TCP socket,ss -tani 里没有 tcp-ulp-mptcp;客户端 nstat 里的 MPCapableFallbackSYNACK 增加。应用代码不需要处理这个差异。

CVE-2026-98432

2026 年 10 月,Linux IPv4 TCP 的 ireq_opt 生命周期问题被公开为 CVE-2026-98432。

时间事件
2026-10-01修复补丁发出,提交 e3f33b0a1d89
2026-10-05合入上游
2026-10-15CVE 公开,稳定分支回移
2026-10-27Anthropic 公开利用细节
2026-10-28Innora 发布独立复现

CVE 公开日期取自 Ubuntu 页面,Anthropic 的日期是其文章发布日期;补丁发出、合入和 Innora 复现的日期来自 Innora 的 writeup。

Ubuntu 的 CVE 页面记录 CVSS 为 9.8,描述为可导致远程代码执行;Red Hat 把它标为 Critical。按 Ubuntu 的描述,问题始于提交 079096f103fa(tcp/dccp: install syn_recv requests into ehash table),Innora 的 writeup 把它对应到 Linux 4.4;此后所有未打补丁的 IPv4 TCP 内核都受影响。触发需要让 ireq_opt 非空。最常见的是 SYN 带 IPv4 options;SELinux 或 Smack 的 CIPSO 映射也能走到同一条路径。

ireq_opt 的生命周期

SYN 如果带 IPv4 options(IHL > 5),tcp_v4_save_options() 会分配一个 ip_options_rcu:大小是 sizeof(struct ip_options_rcu) + optlen。40 字节 options 时是 32 + 40 = 72 字节,落到 kmalloc-96。这个对象归请求套接字 reqsk 的 ireq_opt 所有。

ACK 完成握手时,tcp_v4_syn_recv_sock() 把 ireq->ireq_opt 直接赋给子套接字的 newinet->inet_opt。这是指针移交,不拷贝;子套接字随后被 inet_ehash_nolisten() 放进 ehash,reqsk 从 ehash 摘除,如果 own_req 为真,再把 ireq->ireq_opt 置 NULL。

问题在另一个 CPU。自 4.4 起,reqsk 在 ehash 里可以被无 listener 锁地查到。一个重传的 SYN 命中同一个 reqsk,会走 tcp_check_req() → tcp_v4_send_synack() → inet_csk_route_req() / ip_options_build(),只在 rcu_read_lock() 下读 ireq->ireq_opt。与此同时,ACK 路径上的子套接字可能已经被销毁,inet_sock_destruct() 用普通 kfree() 释放 inet_opt,没有等 RCU 宽限期。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
CPU0:ACK 完成握手                         CPU1:重传 SYN
─────────────────────────────            ─────────────────────────────
tcp_v4_syn_recv_sock()
  inet_opt = ireq->ireq_opt
  (指针移交,不拷贝)
  inet_ehash_nolisten()
    reqsk 从 ehash 摘除
                                           tcp_check_req()
                                             已在 ehash 中查到 reqsk
                                             持有引用
                                           tcp_v4_send_synack()
                                             inet_csk_route_req()
                                               rcu_dereference(ireq_opt)
子套接字被销毁                              ip_options_build()
  inet_sock_destruct()                        读取 opt->optlen
    kfree(inet_opt)  ← 无 RCU 宽限          memcpy(opt->__data, optlen)

ip_options_build() 先 memcpy 整个 struct ip_options 到 IPCB(skb)->opt,再 memcpy(iph + 20, opt->__data, opt->optlen)。两次读之间,对象可能已经被回收;如果回收者能控制新对象的内容,第二次读到的 optlen 和 __data 就是攻击者的数据。optlen 变大可以越界读,srr 被控制可以越界写。

上游修复只改了两处 kfree:把 inet_sock_destruct() 和 tcp_v4_reqsk_destructor() 里的 kfree 改成 kfree_rcu,等所有 RCU 读者退出后再释放。提交见 e3f33b0a1d89。同一个修复还覆盖了 reqsk 迁移路径(net.ipv4.tcp_migrate_req=1 或 BPF reuseport),那是另一种共享 ireq_opt 的方式。IPv6 的主路径不受影响,因为 tcp_v6_syn_recv_sock() 会复制 options;IPv6 的 CALIPSO 迁移问题另行处理。

MPTCP 监听的释放时机

CVE-2026-98432 不是 MPTCP 协议漏洞。Red Hat 的公告明确写了 “Multipath TCP is not a required condition for this flaw”,默认关闭 MPTCP 的 OpenShift 节点仍然受影响。Go 安全公告也说,演示的利用需要目标暴露 MPTCP listener,但底层 bug 不是 MPTCP 特有的。

公开利用选 MPTCP listener,和子套接字的销毁时机有关。MPTCP listener 在内核里是一个元 socket 加一个 TCP 子流 listener。收到 SYN 时,icsk_af_ops->syn_recv_sock 指向 subflow_syn_recv_sock(),它先调用底层的 tcp_v4_syn_recv_sock(),也就是前面说的 ireq_opt 移交点。然后它检查 MPTCP 选项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fallback_is_fatal = tcp_rsk(req)->is_mptcp && subflow_req->mp_join;

if (child && *own_req) {
    if (!ctx || fallback) {
        if (fallback_is_fatal) {
            subflow_add_reset_reason(skb, MPTCP_RST_EMPTCP);
            goto dispose_child;
        }
        goto fallback;
    }
}

走到 dispose_child,子套接字会被立即关闭:

1
2
3
4
5
6
7
dispose_child:
    mptcp_subflow_drop_ctx(child);
    tcp_rsk(req)->drop_req = true;
    inet_csk_prepare_for_destroy_sock(child);
    tcp_done(child);
    ...
    return child;

tcp_done() 把子套接字置为 CLOSE;drop_req 让 tcp_check_req() 走 if (own_req && rsk_drop_req(req)),丢掉 reqsk 并返回子套接字;tcp_v4_rcv() 随后调用 tcp_child_process(),最后 sock_put(child) 触发 inet_sock_destruct(),把 inet_opt 释放掉。整个过程都在 ACK 的处理路径里,不需要等应用 accept(),也不需要额外的 RST。

MP_JOIN 可以走到这条路径:攻击者先正常建立一条 MPTCP 连接,从 SYN/ACK 里拿到服务端的 key,算出 token;再向 listener 的初始地址和端口发一个带 IPv4 options 和 MP_JOIN 的 SYN(allow_join_initial_addr_port 默认是 1),然后回一个不带有效 MPJ_ACK 的 ACK。subflow_req->mp_join 为真,fallback_is_fatal 为真,子套接字走 dispose_child。这时另一个 CPU 上的重传 SYN 如果已经查到 reqsk 并持有引用,就可能在 ip_options_build() 里读到刚被释放的 inet_opt。

普通 TCP 没有这条由 MPTCP 选项错误直接触发的 dispose 路径。子套接字建好后进入 accept 队列,要等应用关闭或 RST 才会释放;重传 SYN 的 SYNACK 构造通常早就结束了。攻击者也可以用 RST 提前释放,但 RST 要等子套接字进入 ehash 或 accept 队列之后才能命中,释放时机比 MPTCP 的 dispose 晚,时序窗口更难对上。

Anthropic 的文章没有展开这层原因,上面的路径是我根据 6.12 源码推出来的。它能解释公开利用为什么选 MPTCP;普通 TCP listener 同样受影响,只是需要不同的触发方式。

利用链和复现边界

Anthropic 的公开描述把利用分成两段:第一段用越界读把内核指针带回用户态,破 KASLR;第二段用越界写覆盖回调指针,触发一条已经布置在内核里的 payload,最后执行 root 命令。文章给的数字是 48 分钟拿到内核指针、再 5 分钟拿到 shell;输掉竞态只会让这次握手失败,不会崩系统,所以可以一直重试。文章里没有附利用代码。

Innora 的仓库把原语拆得更细:kmalloc-96 回收、TOCTOU、越界读、越界写、把 skb_shared_info->nr_frags 改成 8、在 frags 遍历里解引用攻击者给的 page 指针。每一步都有 trace 级实验,仓库在 sgInnora/innora-cve-research 的 CVE-2026-98432 目录。

这套仓库没有走到完整的远程利用。他们的实验跑在 QEMU 里的 6.12.112 arm64 插桩内核上,插桩补丁在 inet_opt 移交窗口里提前 kfree 并做 spray。writeup 也写明,在他们这套触发结构里,SYNACK 读取发生在释放之前,只要 reqsk 指针还有效,子套接字的销毁就排不到前面;在未插桩的内核上,要靠拉长构建窗口才可能重叠,每轮命中的概率很低。所以它证明的是漏洞和利用原语。公开的第三方 PoC 大多也是 proof-only:验证 UAF 或内存泄漏 oracle,不包含 root 链;在非隔离环境跑竞态触发可能直接把内核打崩。我没有在真实环境跑利用,CVE 部分依据公开文章、修复提交和 6.12.112 源码。

补丁、检查和临时措施

先打补丁

带 e3f33b0a1d89 的稳定版本有:

分支修复版本
6.126.12.113
6.66.6.159
6.16.1.190
5.155.15.223
5.105.10.272
6.186.18.56

这张表取自 Innora 仓库的 README,我没有逐个对照 kernel.org 的变更日志;Ubuntu 和 Red Hat 只列各自的包版本。截至 2026 年 11 月初,7.2.10 和 7.3 也已包含修复。发行版内核可能用不同的版本号回移,先看厂商的 CVE tracker,不要只比对 uname -r 的主版本。升级内核后需要重启;不要假设 Livepatch 一定覆盖这个函数,是否覆盖以厂商公告为准。

  • uname -r(kernel release,内核发行版本):打印当前正在运行的内核版本,不是已经安装但尚未启动的内核版本。

检查 MPTCP 监听

如果服务是 Go 1.24 及以上编译、跑在支持 MPTCP 的 Linux 上,监听默认会尝试 MPTCP(multipathtcp=2)。用 ss -Ml 看:

1
2
ss -Ml
# LISTEN 0 2 10.0.1.2:5201 0.0.0.0:*

ss -ltn 会把它显示成普通 TCP,不看 -M 容易漏。其他语言或框架要按各自的创建方式检查:C 里看是否用了 IPPROTO_MPTCP,已有二进制看是否被 mptcpize 或 eBPF 强制。

如果确认不需要 MPTCP,可以关掉 Go 的默认值:

1
GODEBUG=multipathtcp=0 ./server

或者写在 go.mod 里:

1
godebug multipathtcp=0

也可以在单个 listener 上用 net.ListenConfig.SetMultipathTCP(false) 覆盖。改完要重启服务;已经在运行的 MPTCP listener 不会因为环境变量变化而改变。

临时措施覆盖不到的地方

net.mptcp.enabled=0 能阻止新的 MPTCP socket,但它是 per-netns 的,只影响新 socket;已有的 listener 和连接不变,要重启服务才生效。它移除的是公开利用走过的那条 MPTCP 路径,不是底层 IPv4 TCP 的 UAF。

net.mptcp.allow_join_initial_addr_port=0 会拒绝向初始地址和端口的 MP_JOIN,能收窄上面那条 MP_JOIN 路线,但同样不是修复;如果服务依赖这个行为做多路径接入,还会破坏正常功能。

更直接的临时措施是过滤带 IPv4 options 的 TCP 报文。Red Hat 公告给的 nftables 规则是:

1
2
3
4
5
6
table inet cve_2026_98432 {
  chain input {
    type filter hook input priority -310; policy accept;
    ip protocol tcp ip hdrlength > 5 counter drop
  }
}

它挡的是带 IPv4 header options 的 TCP 报文,也就是 ireq_opt 非空的主要来源。普通 TCP options(timestamp、window scale)在 TCP 头里,不受影响。这条规则来自 Red Hat 的 RHSB-2026-016,部署前要按自己的流量测试,它不是补丁的替代品。

规则还有几个覆盖不到的地方。SELinux 或 Smack 的 NetLabel CIPSO 映射也可能让 ireq_opt 非空,只按 IP options 过滤不一定覆盖。容器有自己的 network namespace,host 上的规则不一定管到容器内部,要在对应 netns 或 CNI 层处理。转发、NodePort、桥接流量的 hook 点和 host input 不同,不能只在一处加规则就认为全覆盖。

容器共享宿主内核,所以宿主内核必须打补丁;VM 有独立的 guest 内核,宿主机的 Livepatch 不会补 guest。对多租户集群,这两类边界要分开评估。

#MPTCP #Linux #内核 #TCP #网络安全