
Cilium 深度参考Linux XFRM 子系统详解——策略、状态、报文流转、ip xfrm 输出与性能结构【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium本篇技术指南以 Cilium 仓库中的 XFRM Reference Guide 为核心系统讲解 Linux XFRMIP packet transformation子系统的两大配置对象——XFRM 策略Policy与状态State、出站/入站报文在 Netfilter 栈中的 XFRM 加解密流转路径、ip xfrm输出的逐字段解读、全部 XFRM 错误计数器的语义以及内核中策略与状态的底层查找数据结构。读完后你将能够独立读懂ip xfrm policy/ip xfrm state的任意输出、诊断 IPsec 解密失败的各类错误计数并理解 Cilium 如何利用 packet mark 在 tc-bpf 钩子中标记并重路由 IPsec 流量对应0xd00/0xf00、0xcb93eXX等文档示例中出现的 mark。XFRM 是什么Linux IPsec 加解密的底层框架Linux 内核中的 IPsec 加密依赖 XFRM 子系统。XFRMIP Transforms是一个用于报文变换的 IP 框架覆盖从加密到压缩的一系列操作它通过一组policy策略和state状态对象进行配置——在 IPsec 语境下它们分别对应安全策略Security Policy与安全关联Security Association, SA。需要强调的是该参考指南面向希望深入理解 Linux XFRM 子系统的开发者和用户。阅读它有助于拓宽对 Cilium 的理解但并非使用 Cilium 的前提条件。在 Cilium 的 Linux 数据面实现中XFRM 正是节点间 IPsec 加密的承载机制其抽象封装在pkg/datapath/linux/ipsec包中包注释 明确写道该包“提供 Linux 数据面特定的抽象和有用助手以通过 Linux xfrm 管理 IPSec”。XFRM 策略与状态Policies and States从宏观上看策略定义接受哪些流量、拒绝哪些流量而状态定义如何执行加解密。策略可以按方向out、in或fwd、带 CIDR 的源/目的 IP 地址以及 packet mark 进行匹配。例如下面这条策略匹配出站报文中源地址任意、目的地址为 10.56.1.0/24、packet mark 为0xcb93eXX的流量并且默认动作是放行src 0.0.0.0/0 dst 10.56.1.0/24 dir out priority 0 mark 0xcb93e00/0xffffff00 [...]注意这里mark 0xcb93e00/0xffffff00正是“值/掩码”的匹配形式掩码0xffffff00意味着只看 mark 的高 24 位。这类 mark 值与 Cilium 源码中的常量一一对应——pkg/datapath/linux/linux_defaults/linux_defaults.go定义了RouteMarkMask 0xF00路由 mark 的位掩码、OutputMarkMask 0xFFFFFF00XFRM 状态 output-mark 使用的掩码以及IPsecMarkMaskNodeID 0xFFFF0000node ID 占高 16 位。Cilium 通过 ipsec_linux.go 中的generateEncryptMark()/generateDecryptMark()生成这些 mark加密方向 mark 由RouteMarkEncrypt置位并编码 SPI 与远端 node ID解密方向则由RouteMarkDecrypt置位。状态State与策略较为相似但与流量方向无关且只能按精确 IP 地址或 0.0.0.0 匹配所有匹配。下面这条状态作用于 10.56.0.17 → 10.56.1.238 的报文packet mark 与上文相同src 10.56.0.17 dst 10.56.1.238 proto esp spi 0x00000003 reqid 1 mode tunnel replay-window 0 mark 0xcb93e00/0xffffff00 output-mark 0xe00/0xffffff00 aead rfc4106(gcm(aes)) 0x6254fced5f7a5ea9401b9015ecf10d65eac51a69 128 anti-replay context: seq 0x0, oseq 0x36, bitmap 0x00000000 sel src 0.0.0.0/0 dst 0.0.0.0/0在隧道模式tunnel modeIPsec 中这些 IP 地址对应外层IP 地址对于入站加密报文SPI 也会参与匹配见下文。你可能会注意到这条状态并没有指明它应执行加密还是解密——因为它两者都能做。状态与流量方向无关所以同一状态在理论上可同时用于加密和解密具体做什么取决于该状态在协议栈的哪个位置被匹配到例如在入站路径匹配到时就是解密。在 Cilium 的实现中getDirFromXfrmMark() 正是通过检查 mark 中的RouteMarkDecrypt/RouteMarkEncrypt位来判定一条 XFRM 状态属于入站还是出站方向——因为状态本身没有方向字段。策略模板Policy TemplatesXFRM 策略通常还定义一个模板templatesrc 0.0.0.0/0 dst 10.56.1.0/24 dir out priority 0 mark 0xcb93e00/0xffffff00 tmpl src 10.56.0.17 dst 10.56.1.238 proto esp spi 0x00000003 reqid 1 mode tunnel模板的用法取决于方向出站流量模板定义了要执行的编码方式。例如上述模板会把报文封装 IP 头 ESP 头IP 头的源/目的地址分别为 10.56.0.17 与 10.56.1.238ESP 头的 SPI 为 3。入站与转发流量模板充当额外的过滤器。例如下面这条策略只有在报文是外层 IP 地址为 10.56.1.238 ↔ 10.56.0.17 的 ESP 报文、且 packet mark 匹配0xd00/0xf00时才放行src 0.0.0.0/0 dst 10.56.0.0/24 dir in priority 0 mark 0xd00/0xf00 tmpl src 10.56.1.238 dst 10.56.0.17 proto esp reqid 1 mode tunnel注意0xd00/0xf00与 Cilium 源码中RouteMarkDecrypt配合RouteMarkMask 0xF00的取值直接对应——这正是 Cilium 为解密流量打的特殊标记。在隧道模式下应当总是能看到与 OUT 策略模板相匹配的 XFRM 状态。因为在出站方向上状态是在模板应用之后才被匹配的IP 地址、SPI、协议、模式和 reqid 在 XFRM 状态与其对应模板之间必须全部一致。Cilium 在构造 OUT 状态时同样保证了这一点ipSecReplaceStateOut() 将state.Src/Dst设为隧道 IP、state.Reqid params.ReqID并把 mark 设置为generateEncryptMark(key.Spi, params.RemoteNodeID)——即把 SPI右移 12 位见IPsecXFRMMarkSPIShift 12与远端 node ID左移 16 位编码进 mark。XFRM 报文流转路径Packet Flows下图展示了带 XFRM 元素紫色的标准 Netfilter 报文流转出站报文流Egress出站时报文首先命中 “XFRM OUT policy” 块此时对 XFRM OUT 策略做查找。若匹配成功报文进入 “XFRM encode” 块应用模板例如封装随后再与 XFRM 状态匹配若找到状态就用其信息加密报文。加密后的报文会再次经过 OUTPUT 与 POSTROUTING 链。入站报文流Ingress入站时加密报文例如 ESP 报文在穿过 INPUT 链之后到达 “XFRM decode”。隧道模式的加密报文通常以本机 IP 之一为外层目的地址因此会被自动路由到 INPUT 链否则可能需要添加 IP 路由将报文引向 INPUT 链。Cilium 的做法是在 tc-bpf ingress 钩子中识别 IPsec 流量并打上特殊 mark再用该 mark 将报文重路由到 INPUT 链——这与入站策略示例中mark 0xd00/0xf00的匹配值相吻合。在 “XFRM decode” 处若报文匹配某个 XFRM 状态则用状态信息对其进行解码即解封装 解密。匹配依据为源/目的地址、mark、SPI 与协议。若解码出错例如密钥不对报文被丢弃且对应错误计数器递增。如图所示解码本身并不要求报文匹配某条 XFRM 策略它直接进入 “XFRM decode”但报文要进入本地进程或穿过 FORWARD 链则必须有策略放行。一条带有可选模板即level use的 XFRM 策略会放行所有解码成功的报文从未加密、因而不来自 “XFRM decode” 的流量则默认放行。报文解码后会在协议栈中重新环回recirculate表现得像是从最初接收它的接口再次进来。更具体地说报文在 tc 层之前被重新环回因此在 tc-bpf 钩子上会第二次可见解密前一次、解密后一次。重新环回时 packet mark 被保留因此可以识别并追踪已经过解密和环回的报文。逐字段解读ip xfrm输出以下输出基于 iproute2-6.1.0更新版本可能出现更多字段。例如新内核v6.10中的 XFRM 状态带有dir字段未来很可能出现在ip xfrm state输出中。在ip xfrm输出中策略按创建日期排序新策略位于顶部。这一点很重要当两条策略都能匹配同一报文且优先级相同时较新的那条会被使用。ip xfrm policy输出解读$ ip xfrm policy # - src 0.0.0.0/0 为匹配源 IP 地址的 CIDR # - dst 0.0.0.0/0 为匹配目的 IP 地址的 CIDR src 0.0.0.0/0 dst 0.0.0.0/0 uid 0 # - dir fwd 指明方向定义策略在 Linux 栈中的生效位置入站、出站或转发 # - action allow 为匹配报文执行的动作报文只能被放行默认或丢弃 # - index 18 用于区分可能具有相同或重叠选择器的不同策略。若未给出或已存在 # 则自动重新生成参见内核 xfrm_gen_index。三个 LSB 编码方向 # 例如 1 表示 XFRM_POLICY_OUTMSB 部分每次递增 1即 index 加 8直到找到空 index # - priority 2975 表示多条策略可能匹配该报文时的优先级0 为最高 # - share any 始终为 any目前未使用 # - flag (0x00000000) 为 XFRM 策略标志集合。目前仅支持 # XFRM_POLICY_ICMP (0x2)XFRM_POLICY_LOCALOK (0x1) 未实现。 # 给出 XFRM_POLICY_ICMP 时策略也适用于 payload 内报文匹配选择器的 ICMP 报文 dir fwd action allow index 18 priority 2975 share any flag (0x00000000) lifetime config: # 基于接收字节数、报文数、策略添加后的时间或最后一次被匹配后的时间的各种限制与过期时间。 # 达到软限制或软过期时间时通过 netlinkstruct xfrm_user_expire通知用户态 # 达到硬限制或硬过期时间时策略被删除 limit: soft (INF)(bytes), hard (INF)(bytes) limit: soft (INF)(packets), hard (INF)(packets) expire add: soft 0(sec), hard 0(sec) expire use: soft 0(sec), hard 0(sec) lifetime current: # 被该策略匹配的字节数与报文数计数器若设置了限制则用于计量 0(bytes), 0(packets) # 策略添加时间与最后一次被匹配的时间戳若设置了过期时间则用于计时 add 2024-06-17 11:24:49 use 2024-06-17 11:25:01 # - src 0.0.0.0 / dst 10.92.0.164 用法见“策略模板”一节 tmpl src 0.0.0.0 dst 10.92.0.164 # - proto esp、spi 0x00000000(0)、reqid 1(0x00000001)、mode tunnel # 用法均见“策略模板”一节 proto esp spi 0x00000000(0) reqid 1(0x00000001) mode tunnel # - level use 是表示该模板为“可选”的晦涩写法另一种取值是 level required。 # 若找不到匹配模板的 XFRM 状态可选模板会被跳过否则报文以 # XfrmInTmplMismatch 丢弃 # - share any 未实现始终为 any level use share any # - enc-mask ffffffff 位掩码定义允许的加密算法列表 # - auth-mask ffffffff 位掩码定义允许的认证算法列表 # - comp-mask ffffffff 未实现的位掩码可能为压缩算法定义 enc-mask ffffffff auth-mask ffffffff comp-mask ffffffffip xfrm state输出解读$ ip xfrm state # - src 10.92.1.189 为匹配报文源 IP 的地址dst 10.92.0.164 为目的 IP src 10.92.1.189 dst 10.92.0.164 # - proto esp 指明使用的 IPsec 协议 # - spi 0x00000000(0) 为安全参数索引SPI用于区分可能使用不同 # 算法/密钥的多条 IPsec 流密钥轮换时尤其有用 # - reqid 1(0x00000001) 仅用于确保 XFRM 策略模板与状态相互匹配的内核 ID # - mode tunnel 指明报文被封装tunnel还是仅添加 ESP 头transport proto esp spi 0x00000003(3) reqid 1(0x00000001) mode tunnel # - replay-window 0 为防重放检查容忍度的窗口大小 # - seq 0x00000000flag (0x00000000) 含多种标志包括 ESN 模式的 # XFRM_STATE_ESN (0x80) replay-window 0 seq 0x00000000 flag (0x00000000) # - mark 0x4db50d00/0xffff0f00 用于匹配报文 mark 的值与掩码 # - output-mark 0xd00/0xffffff00 为加解密之后施加到报文 mark 上的值与掩码 mark 0x4db50d00/0xffff0f00 output-mark 0xd00/0xffffff00 # - aead rfc4106(gcm(aes)) 为所用算法的类型与名称 # - 0x856f... (160 bits) 为密钥及其长度——这是敏感信息需妥善对待 # - 128 为 ICV 长度支持的长度取决于所用算法 aead rfc4106(gcm(aes)) 0x856f15d0ccabe682286b4286bccf5d595b88b168 (160 bits) 128 # - seq 0x0 为接收侧当前序列号用于防重放 # - oseq 0x0 为最近发出的序列号若该 32 位数溢出报文丢弃且 # XfrmOutStateSeqError 计数递增ESN 模式下序列号编码为 64 位 # - bitmap 0x00000000 跟踪重放窗口中已经出现过的序列号 anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000 # - sel src 0.0.0.0/0 dst 0.0.0.0/0 是施加于解密后报文的附加过滤器 # 确保内层报文来自/去往预期位置 # - uid 0 此字段似乎未被使用struct xfrm_selector 中的 user sel src 0.0.0.0/0 dst 0.0.0.0/0 uid 0 lifetime config: # 与策略相同软/硬限制与过期时间软限制触发 netlink 通知 # 硬限制触发状态删除 limit: soft (INF)(bytes), hard (INF)(bytes) limit: soft (INF)(packets), hard (INF)(packets) expire add: soft 0(sec), hard 0(sec) expire use: soft 0(sec), hard 0(sec) lifetime current: 20124(bytes), 83(packets) add 2024-06-17 11:15:48 use 2024-06-17 11:16:02 stats: # - replay-window 0 在收到序列号超出窗口范围的报文时递增 # - replay 0 在收到序列号在窗口内但已出现过的报文时递增 # - failed 0内核侧全名 integrity_failed在认证/加密头校验和错误时 # 递增该计数器递增时 XfrmInStateProtoError 也必然递增 replay-window 0 replay 0 failed 0值得对照的是output-mark 0xd00/0xffffff00中的值与掩码正与 Cilium 的RouteMarkDecrypt常量配合OutputMarkMask 0xFFFFFF00的用法一致报文完成解密后XFRM 内核会把该 mark 写回报文供 tc-bpf 钩子在第二次可见时识别“这是已解密的报文”。XFRM 错误计数器全景所有 XFRM 错误都对应一次报文丢弃部分错误还伴随特定状态的计数器递增。要在/proc/net/xfrm_stat中看到这些错误计数器需要开启CONFIG_XFRM_STATISTICS内核配置。错误计数器触发条件XfrmInError加密期间内核内存分配失败XfrmInBufferError报文穿过过多 XFRM 状态上限XFRM_MAX_DEPTH 6或过多 XFRM 策略模板适用于一个报文上限同为 6XfrmInHdrError报文中 SPI 部分畸形或外层 IP 头畸形XfrmInNoStates到达 INPUT 链的 AH/ESP 报文未找到任何匹配的 XFRM IN 状态XfrmInStateProtoErrorAH/ESP 校验和错误报文 IPsec 协议与状态指定协议不一致以及所有协议特定错误例如来自esp_input加解密失败如 IN 状态密钥与加密密钥不匹配、协议头/尾畸形、内存不足XfrmInStateModeError报文为隧道模式但匹配到的 XFRM 状态是传输模式XfrmInStateSeqError防重放检查拒绝报文。序列号超出窗口时递增该状态的replay-window计数序列号已出现过时递增replay计数XfrmInStateExpired状态过期硬限制到实际删除之间存在延迟该窗口内入站匹配报文被丢弃XfrmInStateMismatch状态封装协议如ip xfrm state中encap字段的espinudp与报文封装协议不匹配或解密后报文不匹配所用状态的sel选择器XfrmInStateInvalid报文匹配到正在删除或已过期expired的 XFRM 状态XfrmInTmplMismatch报文匹配了带“非可选”模板的 XFRM 策略但模板与用于解密的状态都不匹配一个报文可以被多次解码或报文上使用了mode tunnel状态但它不匹配任何策略模板XfrmInNoPols入站报文不匹配任何 XFRM 策略且默认动作为block用ip xfrm policy {get,set}default查看/设置默认动作XfrmInPolBlock报文匹配到action block的 XFRM IN 策略XfrmOutError加密期间内存分配失败某些情况下待加密报文畸形XfrmOutBundleCheckError未使用XfrmOutNoStates报文匹配了 XFRM OUT 策略但找不到匹配该策略模板的状态XfrmOutStateProtoError发生协议特定如 ESP加密错误XfrmOutStateModeError封装后报文超过 MTU 且不允许分片XfrmOutStateSeqError状态的输出序列号oseq达到最大值——非 ESN 模式下为UINT32_MAXXfrmOutStateExpired与XfrmInStateExpired对称出站方向的过期未删除状态XfrmOutPolBlock报文匹配到action block的 XFRM OUT 策略XfrmOutPolDead未使用对删除中的状态改为报告XfrmOutStateInvalidXfrmOutPolError过多策略模板适用于一个报文上限XFRM_MAX_DEPTH 6或匹配策略的非可选模板找不到任何状态XfrmFwdHdrError报文在 FWD 策略检查时畸形XfrmOutStateInvalid出站报文匹配到正在删除或已过期的状态XfrmOutStateDirError查找到的状态方向已定义且不是XFRM_SA_DIR_OUT仅 v6.10 内核XfrmInStateDirError查找到的状态方向已定义且不是XFRM_SA_DIR_IN仅 v6.10 内核排查 Cilium IPsec 问题时可结合上述表格定位XfrmInNoStates通常意味着节点间状态尚未同步完成failed计数上升则指向密钥不一致例如密钥轮换后一侧未更新。Cilium 侧的对应机制可在 xfrm_state_cache.goXFRM 状态缓存用于跟踪远端节点重启等场景下的状态一致性与 xfrm_collector.goXFRM 数据采集为cilium-dbg bpf等调试命令提供状态视图中找到线索。性能考量策略与状态的查找数据结构当你拥有成千上万条策略与状态时查找成本即使相比加解密本身也不可忽略。了解 XFRM 存放策略与状态的数据结构有助于理解何时以及如何改进索引、加速查找。XFRM 策略的数据结构XFRM 策略存储在一个由多棵红黑树与多个哈希表组成的复杂结构中。根层是一个可伸缩哈希表resizable hashtable索引键为网络命名空间、IP 族、方向以及接口若使用 XFRM 接口。每个哈希表项内部包含若干红黑树策略就存放在这些树中这些项由结构体xfrm_pol_inexact_bin表示。拿到xfrm_pol_inexact_bin依据当前 IP 族、命名空间与方向后其每棵红黑树都用源/目的 IP 进行查找root_s树按源 IP 排序root_d树按目的 IP 排序。此外root_d的叶节点还挂有一棵按源 IP 排序的子树。这使得对root_s与root_d的查找能从叶节点返回三组候选(src_ip; dst_ip)策略列表来自root_s的(src_ip; any)候选列表来自root_d的(any; dst_ip)候选列表来自root_d叶节点子树的(src_ip; dst_ip)候选列表。再补上直接存放在xfrm_pol_inexact_bin项中的(any; any)候选列表共四组候选。注意一条 XFRM 策略只根据其源/目的 CIDR 存在于其中一组列表里。随后内核线性遍历这四组列表寻找优先级最高priority数值最小且匹配报文的候选。两条策略同时匹配且优先级相同时较新者优先。mark 的比对也只发生在这段线性候选求值过程中。XFRM 状态的数据结构XFRM 状态组织在四个哈希表中索引字段与用途各不相同net-xfrm.state_bydst按源/目的 IP 地址及 reqid 索引net-xfrm.state_bysrc仅按源/目的 IP 地址索引net-xfrm.state_byspi按目的 IP、SPI 与协议索引net-xfrm.state_byseq仅按序列号索引。入站报文查找状态时使用state_byspi——这是合理的因为 RFC4301 第 4.1 节建议每个 XFRM 状态拥有独立的 SPI且加密报文本身就携带 SPI。出站加密前查找策略模板对应的状态时使用state_bydst——因为策略模板提供的恰好就是这套索引信息。遍历/清空全部状态如 flush时通常也走这张表但理论上用任意一张表都能完成。state_bysrc与state_byseq服务于其他管理任务例如查找待更新的状态、响应用户 netlink 查询、或在添加新状态前检查是否已存在。小结本指南围绕 Cilium 仓库的 XFRM Reference Guide完整覆盖了XFRM 策略/状态/模板三者的语义分工策略管放行、状态管加解密、模板出站编码入站过滤、加解密报文在 Netfilter 栈中的具体路径与 tc-bpf 二次可见的环回机制、ip xfrm policy/ip xfrm state输出的逐字段语义、全部 25 个 XFRM 错误计数器的诊断含义以及内核侧策略红黑树 可伸缩哈希表与状态四张哈希表的查找结构。在 Cilium 的实际部署中这些机制与pkg/datapath/linux/ipsec状态构建、mark 编码、远端重启恢复及pkg/datapath/linux中的路由 mark 规则RouteMarkDecrypt重路由共同构成了节点间 IPsec 加密的数据面基础。【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考