介绍
BPF_MAP_TYPE_LPM_TRIE是BPF众多MAP的一种,主要功能为最长前缀匹配(Longest Prefix Matching),如IP匹配等。
Linux Kernel文档见 BPF_MAP_TYPE_LPM_TRIE — The Linux Kernel documentation
源码见lpm_trie.c - kernel/bpf/lpm_trie.c - Linux source code (v5.13) - Bootlin
原理
BPF_MAP_TYPE_LPM_TRIE提供了一个最长前缀匹配算法,可用于将IP地址与存储的前缀集匹配。在内部,数据存储在一个使用prefixlen, data对作为键的不平衡trie中。data按网络字节顺序(即大端序)存储,因此data[0]存储最高字节。
在创建LPM_TRIE时,可以以8的倍数作为最长前缀长度,实际范围为8-2048。用于查询和更新MAP的键是一个结构struct bpf_lpm_trie_key,包含u32的值prefixlen和max_prefixlen/8字节的数据。
Note1:
BPF_MAP_TYPE_LPM_TRIE是在内核版本4.11中引入的。
Note2:
创建BPF_MAP_TYPE_LPM_TRIE类型的映射时,必须设置BPF_F_NO_PREALLOC标志。
在实现中,用一个trie存储前缀和值,内核中的结构如下
struct lpm_trie_node {
struct rcu_head rcu;
struct lpm_trie_node __rcu *child[2];
u32 prefixlen;
u32 flags;
u8 data[];
};
struct lpm_trie {
struct bpf_map map;
struct lpm_trie_node __rcu *root;
size_t n_entries;
size_t max_prefixlen;
size_t data_size;
spinlock_t lock;
};
举例说明trie的构建过程,以ipv4地址前缀匹配为例,假设初始情况trie为空,此时插入一个前缀192.168.0.0/16,对应值为1。如下图,建立一个新的节点(1),存储该前缀和值
接下来插入另一个前缀192.168.0.0/24,值为2,由于已经存在一个data相同但长度更短的前缀,新前缀将作为节点(1)的子节点存储。新前缀(192.168.0.0/24)在旧前缀(192.168.0.0/16)之后的下一位为0,因此将成为孩子0。
接下来插入192.168.128.0/24,值为3,与上面过程类似。
再插入一个前缀192.168.1.0/24,值为4。该前缀明显不能放入节点(2)或节点(3)的子节点,那么我们引入一个辅助节点(4)。
不难看出节点(4)存储了节点(2)和(5)的最长公共前缀,按照下一位(第24位)的不同存储两个子节点(2)和(5),且这个节点不存储值,搜索也不会返回该类节点,而是返回搜索路径上经过的有效最长前缀。
用法
Kernel端
查询:使用helper
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
即可,需要特别注意的是,其中key同样使用struct bpf_lpm_trie_key结构,且prefixlen设置为最长前缀长度,如在IPv4匹配中将设置为32。
更新:使用helper
long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)
注意flags参数必须设置为BPF_ANY, BPF_NOEXIST 或 BPF_EXIST其中之一,但由于BPF_ANY的语义,值会被忽略。
删除:使用helper
long bpf_map_delete_elem(struct bpf_map *map, const void *key)
User端
可以使用helper
int bpf_map_get_next_key (int fd, const void *cur_key, void *next_key)
遍历所有key,第一个键可以通过调用bpf_map_get_next_key()并将cur_key设置为NULL来获取。随后的调用将获取当前键之后的下一个键。bpf_map_get_next_key()成功时返回0,如果cur_key是树中的最后一个键则返回-ENOENT,如果失败则返回负值表示错误。
bpf_map_get_next_key()将首先从最左边的叶开始遍历LPM trie。这意味着迭代将先返回更具体的前缀,再返回不那么具体的前缀。
User端也可以调用bpf_map_update_elem()向map中插入前缀,此时prefixlen即为想要插入的前缀长度。