深入解析 Ciliumcilium-dbg bpf socknat:Socket NAT 反向映射的查看与调试
【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium
cilium-dbg bpf socknat是 Cilium 中用于查看Socket-Level 负载均衡(Socket Load-Balancing)反向 NAT 条目的命令组。它直接操作内核中的 BPF 映射cilium_lb4_reverse_sk与cilium_lb6_reverse_sk,帮助开发者确认一条 socket 连接从后端(backend)回程时如何被还原为前端(frontend)地址。本文围绕该命令的完整用法展开,并结合 Cilium 的 Go 客户端实现、BPF 源码与单元测试,说明其底层原理、输出格式和实际排查场景。
适用前提:本文所有命令与源码均基于当前仓库(Cilium 主分支)的真实实现;命令在安装了 Cilium agent 的节点上执行,且需要 root 权限。
命令总览
cilium-dbg bpf socknat由两个层级组成:
| 命令 | 说明 |
|---|---|
cilium-dbg bpf socknat | Socket NAT 操作命令组(父命令) |
cilium-dbg bpf socknat list | 列出 Socket-LB 反向 NAT 条目(子命令,别名ls) |
命令定义位于 cilium-dbg/cmd/bpf_socknat.go,通过 Cobra 注册到bpf命令树之下(cilium-dbg/cmd/bpf.go 中BPFCmd.AddCommand的扩展)。
父命令:socknat
cilium-dbg bpf socknat [flags]父命令本身不执行任何操作,仅承载子命令,支持一个参数:
-h, --help help for socknat子命令:list
cilium-dbg bpf socknat list [flags]list(别名ls)用于列出 socket-LB 反向 NAT 条目,是实际产生输出的命令:
-h, --help help for list -o, --output string json| yaml| jsonpath='{}'全局参数(继承自父命令)
无论执行哪个层级,命令都会继承cilium-dbg根命令的全局参数,用于控制连接方式与日志行为:
--config string Config file (default is $HOME/.cilium.yaml) -D, --debug Enable debug messages -H, --host string URI to server-side API --log-driver strings Logging endpoints to use (example: syslog) --log-opt map Log driver options (example: format=json)其中-H, --host指定与 agent 通信的 API 地址,-D, --debug用于排查时输出调试信息。注意:socknat list的实现并不依赖 daemon 的 REST API,而是直接读取节点上的 BPF 映射(见下文源码分析),因此即使 agent 的 API 暂时不可用,只要内核映射存在且具备 root 权限即可查询。
输出格式与字段解读
默认使用TablePrinter打印两列表格,列头为Socket Cookie与Backend -> Frontend:
Socket Cookie Backend -> Frontend- Socket Cookie:内核为该 socket 分配的 cookie(
bpf_get_sock_cookie返回的唯一标识),即 BPF 映射 key 中的cookie字段,用于区分不同的连接/socket 实例。 - Backend -> Frontend:每个条目按
后端地址:端口 -> 前端地址:端口 (revnat=索引)格式展示:- 前半部分是后端(backend)地址与端口;
- 后半部分是前端(frontend,即 Service 的虚拟地址)地址与端口;
revnat=N是反向 NAT 索引(RevNatIndex),指向cilium_lb4_reverse_nat/cilium_lb6_reverse_nat映射中对应的 Service 条目。
IPv6 条目以方括号包裹地址,形如[2001:db8::1]:443 -> [fd00::1]:80 (revnat=2)。多个条目按 cookie 分组,同一 cookie 下可存在多条backend -> frontend记录。
机器可读输出
通过-o参数可输出结构化格式,供脚本与自动化工具消费:
cilium-dbg bpf socknat list -o json cilium-dbg bpf socknat list -o yaml cilium-dbg bpf socknat list -o jsonpath='{range .items[*]}{.key}{end}'JSON/YAML 输出按cookie -> entries[]的 map 结构组织。jsonpath表达式可用于精确提取字段(如仅取 cookie 或仅取 backend 地址),适合在 CI 巡检脚本中做断言。
底层 BPF 映射结构
socknat list读取两个内核映射,其 Go 定义位于 pkg/loadbalancer/maps/types.go 的SockRevNat小节:
| 映射名 | BPF 映射 | 大小(最大条目数) |
|---|---|---|
cilium_lb4_reverse_sk | IPv4 Socket-LB 反向 NAT | 256 × 1024(262144) |
cilium_lb6_reverse_sk | IPv6 Socket-LB 反向 NAT | 256 × 1024(262144) |
对应的 BPF 侧定义在 bpf/lib/sock.h,映射类型为BPF_MAP_TYPE_LRU_HASH,支持LIBBPF_PIN_BY_NAME固定(pinning),并使用LRU_MEM_FLAVOR内存策略;宏LB4_REVERSE_NAT_SK_MAP_SIZE/LB6_REVERSE_NAT_SK_MAP_SIZE在 bpf/node_config.h 中定义为 262144。
Key 结构(IPv4 为例,IPv6 同理):
struct ipv4_revnat_tuple { __sock_cookie cookie; // socket cookie __be32 address; // 后端地址 __be16 port; // 后端端口 __u16 pad; };Value 结构:
struct ipv4_revnat_entry { union v6addr address; // 前端地址 __be16 port; // 前端端口 __u16 rev_nat_index; // 反向 NAT 索引 };Go 侧对应的SockRevNat4Key/SockRevNat4Value与SockRevNat6Key/SockRevNat6Value保持内存布局一致,其中端口以网络字节序存取,读取时通过byteorder.NetworkToHost16转为主机序。
源码级实现剖析
socknat list的核心逻辑在 cilium-dbg/cmd/bpf_socknat.go:
sockRevNat4Map := lbmap.NewSockRevNat4Map(256 * 1024) // Default size sockRevNat6Map := lbmap.NewSockRevNat6Map(256 * 1024) // Default size entries := make(map[string][]string) dumpReverseSKEntries(entries, sockRevNat4Map, sockRevNat6Map)关键实现细节:
- 直接创建映射对象:与依赖 daemon API 的查询不同,这里在客户端进程内直接以默认大小(
256 * 1024,与内核侧LB4_REVERSE_NAT_SK_MAP_SIZE一致)构造映射对象并读取,注释明确标注Create the maps directly。 DumpWithCallbackIfExists:遍历两个映射时使用回调节流,映射不存在时静默跳过(IfExists),因此未启用 IPv4/IPv6 或映射未创建的节点不会报错。- 按 cookie 分组:
parseEntry根据 key 的类型断言分发到 IPv4/IPv6 解析分支,将输出字符串追加到entries[cookie],最终形成按 cookie 分组的二维结构。 - 输出前校验:
common.RequireRootPrivilege("cilium bpf socknat list")强制 root 权限,因为直接读取/sys/fs/bpf下的固定映射需要对应权限。
此外,command.AddOutputOption(bpfSocknatListCmd)为list注册了-o输出选项,与command.OutputOption()配合实现 JSON/YAML/jsonpath 格式化。
与 Socket-LB 数据面的关系
这些反向 NAT 条目由 Cilium 的Socket-LB(socket-level load balancing)数据面程序写入,相关 BPF 程序集中在bpf/bpf_sock.c与bpf/bpf_sock_term.c,程序名(cil_sock4_connect、cil_sock6_sendmsg、cil_sock_release等)定义在 pkg/socketlb/socketlb.go 的cgroupProgs列表:
Connect4, SendMsg4, RecvMsg4, GetPeerName4, PostBind4, PreBind4, Connect6, SendMsg6, RecvMsg6, GetPeerName6, PostBind6, PreBind6, SockRelease这些程序以 cgroup 挂载方式在 socket 系统调用路径上执行(connect、sendmsg、recvmsg、getpeername、bind、release),实现Kubernetes 服务的零穿透负载均衡:连接建立的瞬间在 socket 层完成 DNAT/反向 NAT 映射,回程流量在recvmsg/getpeername路径上借助cilium_lb4_reverse_sk/cilium_lb6_reverse_sk恢复原始目标地址,从而让应用无感地访问 ClusterIP 服务。SockRelease程序则负责在 socket 释放时清理对应的反向 NAT 条目。
从源码结构可以推断,socknat list是验证 Socket-LB 数据面状态的关键观测手段:当排查“应用访问 ClusterIP 但实际流量未走 Socket-LB”“回程地址还原异常”等问题时,通过该命令检查反向 NAT 条目是否存在、revnat索引是否正确,可以快速定位数据面故障(例如 cgroup 程序未成功挂载、映射被清空或 agent 配置变更导致程序被 detach)。
单元测试验证
仓库提供了对条目存在性的单元测试,见 cilium-dbg/cmd/bpf_socknat_test.go。测试通过lbmap.NewFakeLBMaps()构造假映射,写入一个 IPv4 条目(cookie=1234,10.0.2.100:80)和一个 IPv6 条目(cookie=2345,2001:db8::1:443),再通过ExistsSockRevNat断言条目存在、不存在的条目(cookie=9999)返回 false。该测试直接印证了:
- key 以
(cookie, address, port)为复合元组; - IPv4/IPv6 双栈都受支持且存储在同一命令的查询范围内;
- 查询语义为精确匹配,不存在匹配即返回空。
实际使用示例
在运行 Cilium agent 且启用了 IPv4/IPv6 的节点上:
# 1. 查看全部 Socket-LB 反向 NAT 条目 sudo cilium-dbg bpf socknat list # 2. 使用别名 ls(等价) sudo cilium-dbg bpf socknat ls # 3. 以 JSON 输出便于脚本处理 sudo cilium-dbg bpf socknat list -o json # 4. 以 YAML 输出 sudo cilium-dbg bpf socknat list -o yaml # 5. 借助 jsonpath 只提取 cookie 字段 sudo cilium-dbg bpf socknat list -o jsonpath='{range .items[*]}{.key}{"\n"}{end}'典型输出示例(格式与dumpReverseSKEntries的格式化逻辑一致):
Socket Cookie Backend -> Frontend 1234 10.0.2.100:80 -> 10.96.0.1:443 (revnat=1) 2345 [2001:db8::1]:443 -> [fd00::10]:80 (revnat=2)解读:socket(cookie=1234)在访问后端10.0.2.100:80时,回程流量将被还原到前端10.96.0.1:443,即一个 ClusterIP 服务的虚拟地址;revnat=1指向cilium_lb4_reverse_nat映射中对应 Service 的索引。若此处没有任何条目,通常意味着 Socket-LB 数据面未启用或 cgroup 程序未挂载。
关联命令
- cilium-dbg bpf:BPF 映射访问入口命令组(
Direct access to local BPF maps); cilium-dbg bpf nat、cilium-dbg bpf lb等兄弟命令可分别查看常规 NAT 与 Service 负载均衡映射,用于交叉比对revnat索引是否一致。
常见问题排查
| 现象 | 可能原因与排查方向 |
|---|---|
| 输出为空但期望有条目 | Socket-LB 未启用(检查bpf-lb-external-clusterip、node-port等 KPR 相关配置);cgroup BPF 程序未挂载,可参考 pkg/socketlb/socketlb.go 中Enable的挂载逻辑与Disable的清理逻辑 |
| 提示权限不足 | 命令要求 root(源码中RequireRootPrivilege),请以 sudo 或 root 用户执行 |
| 仅显示 IPv4 条目 | 节点未启用 IPv6(EnableIPv6配置关闭),cilium_lb6_reverse_sk为空 |
| 条目长期不清理 | 检查SockRelease程序是否被启用(enabled[SockRelease]依赖EnableIPv4 || EnableIPv6)并成功挂载 |
总结
cilium-dbg bpf socknat是对 Socket-LB 反向 NAT 数据面的一线观测工具:socknat list直接读取cilium_lb4_reverse_sk/cilium_lb6_reverse_sk两个 LRU 哈希映射,按 socket cookie 分组展示后端 -> 前端的还原关系,并通过revnat索引关联 Service。理解其 key/value 结构与数据面写入路径(bpf_sock.c、pkg/socketlb),可以帮助你在 Kubernetes 集群中快速定位 Socket-LB 相关的连接建立与回程还原问题。
【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考