BPF_PROG_TYPE_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 时,以及当它不存在时。同一个程序可以为这两种情况调用,并且必须仔细编写以处理这两种情况。

标志

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.ctools/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 流解析器附加到根网络命名空间(机器范围的策略)时,用户无法在其子网络命名空间中覆盖它。