BPF_PROG_TYPE_FLOW_DISSECTOR

概述

流分解器(Flow dissector)是一个从数据包中解析元数据的例程。它在网络子系统的各个地方(RFS、流哈希等)都有使用。

BPF 流分解器试图在 BPF 中重新实现基于 C 的流分解器逻辑,以获得 BPF 验证器的所有好处(即指令数量和尾调用的限制)。

API

BPF 流分解器程序在 __sk_buff 上操作。然而,只允许使用有限的字段集:datadata_endflow_keysflow_keysstruct bpf_flow_keys,包含流分解器的输入和输出参数。

输入参数为:
  • nhoff - 网络头部的初始偏移量

  • thoff - 传输头部的初始偏移量,初始化为 nhoff

  • n_proto - L3 协议类型,从 L2 头部解析

  • flags - 可选标志

流分解器 BPF 程序应填充 struct bpf_flow_keys 的其余字段。输入参数 nhoff/thoff/n_proto 也应进行相应调整。

BPF 程序的返回码要么是 BPF_OK 表示成功分解,要么是 BPF_DROP 表示解析错误。

__sk_buff->data

在没有 VLAN 的情况下,BPF 流分解器的初始状态如下:

+------+------+------------+-----------+
| DMAC | SMAC | ETHER_TYPE | L3_HEADER |
+------+------+------------+-----------+
                            ^
                            |
                            +-- flow dissector starts here
skb->data + flow_keys->nhoff point to the first byte of L3_HEADER
flow_keys->thoff = nhoff
flow_keys->n_proto = ETHER_TYPE

在 VLAN 的情况下,流分解器可以以两种不同的状态被调用。

VLAN 解析前

+------+------+------+-----+-----------+-----------+
| DMAC | SMAC | TPID | TCI |ETHER_TYPE | L3_HEADER |
+------+------+------+-----+-----------+-----------+
                      ^
                      |
                      +-- flow dissector starts here
skb->data + flow_keys->nhoff point the to first byte of TCI
flow_keys->thoff = nhoff
flow_keys->n_proto = TPID

请注意,TPID 可以是 802.1AD,因此 BPF 程序对于双标签数据包可能需要解析两次 VLAN 信息。

VLAN 解析后

+------+------+------+-----+-----------+-----------+
| DMAC | SMAC | TPID | TCI |ETHER_TYPE | L3_HEADER |
+------+------+------+-----+-----------+-----------+
                                        ^
                                        |
                                        +-- flow dissector starts here
skb->data + flow_keys->nhoff point the to first byte of L3_HEADER
flow_keys->thoff = nhoff
flow_keys->n_proto = ETHER_TYPE

在这种情况下,VLAN 信息已在流分解器之前处理,BPF 流分解器无需处理它。

这里的要点如下:BPF 流分解器程序可以在包含可选 VLAN 头的情况下被调用,并且应该优雅地处理两种情况:存在单个或双个 VLAN 以及不存在 VLAN。同一个程序可以用于这两种情况,并且必须仔细编写以处理这两种情况。

标志

flow_keys->flags 可能包含可选的输入标志,其作用如下:

  • BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG - 告诉 BPF 流分解器继续解析第一个分片;默认的预期行为是流分解器在发现数据包已分片后立即返回;由 eth_get_headlen 用于估算 GRO 的所有头部的长度。

  • BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL - 告诉 BPF 流分解器在到达 IPv6 流标签时停止解析;由 ___skb_get_hash 用于获取流哈希。

  • BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP - 告诉 BPF 流分解器在到达封装头部时停止解析;由路由基础设施使用。

参考实现

请参阅 tools/testing/selftests/bpf/progs/bpf_flow.c 获取参考实现,并参阅 tools/testing/selftests/bpf/flow_dissector_load.[hc] 获取加载器。bpftool 也可以用于加载 BPF 流分解器程序。

参考实现组织如下:
  • jmp_table 映射,包含每个支持的 L3 协议的子程序。

  • _dissect 例程 - 入口点;它进行输入 n_proto 解析并执行 bpf_tail_call 到相应的 L3 处理器。

由于 BPF 目前不支持循环(或任何跳回),因此使用 jmp_table 来处理多层封装(和 IPv6 选项)。

当前限制

BPF 流分解器不支持导出所有内核中基于 C 的实现可以导出的元数据。值得注意的例子是单 VLAN (802.1Q) 和双 VLAN (802.1AD) 标签。请参阅 struct bpf_flow_keys 以获取当前可以从 BPF 上下文导出的信息集。

当 BPF 流分解器附加到根网络命名空间(机器范围策略)时,用户无法在其子网络命名空间中覆盖它。