XDP 使用 lpm

  • XDP 使用 lpm 记录

  • 资料来源:

    • <>
  • 更新

    1
    2024.11.11 初始

导语

转载: IP防火墙 – XDP实现

  • ipblock
  • kernel 源码 例程在 tools/testing/selftests/bpf/test_lpm_map.c

需求: xdp 程序中不要拦截局域网的 pkt, 只将需要发往公网的 pkt 重定向到 xsk, 其他包放行;

基本实现:

  • xdp 程序声明 BPF_MAP_TYPE_LPM_TRIE 类型的 map:
    • 最长前缀匹配; 存入数据 是 IP 地址
    • 最长查找范围是 8-2048(8 的倍数)
  • 用户空间 set, xdp 程序 bpf_map_lookup_elem;

Xdp 程序

xdp 程序声明两个 BPF_MAP_TYPE_LPM_TRIE 类型的 map;

  • 必须声明 flag BPF_F_NO_PREALLOC

查找的键值实际上是 struct bpf_lpm_trie_key + 查找长度;

  • 对于 v4 实际长度是 4 字节 v6 是 16 字节

这里存入的值是 enum xdp_action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define MAX_NUM_RULES 8 

struct lpm_v4_key {
struct bpf_lpm_trie_key lpm;
uint32_t addr;
};

struct lpm_v6_key {
struct bpf_lpm_trie_key lpm;
struct in6_addr addr;
};

// ipv4 lpm map
struct
{
__uint(type, BPF_MAP_TYPE_LPM_TRIE); // lpm
__uint(max_entries, MAX_NUM_RULES);
__type(key, struct lpm_v4_key);
__type(value, enum xdp_action);
__uint(map_flags, BPF_F_NO_PREALLOC);
} ipv4_map SEC(".maps");

// ipv6 lpm map
struct
{
__uint(type, BPF_MAP_TYPE_LPM_TRIE); // lpm
__uint(max_entries, MAX_NUM_RULES);
__type(key, struct lpm_v6_key);
__type(value, enum xdp_action);
__uint(map_flags, BPF_F_NO_PREALLOC);
} ipv6_map SEC(".maps");

bpf_map_lookup_elem 按照查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

static __always_inline enum xdp_action lpm_lookup_ipv4(void *map, u32 addr)
{
enum xdp_action *action;
struct lpm_v4_key key = {0};

__builtin_memcpy(&key.addr, &addr, sizeof(addr));
key.lpm.prefixlen = sizeof(struct in_addr) * 8;

action = bpf_map_lookup_elem(map, &key);
if (action)
{
return *action;
}
}

SEC("xdp")
int xdp_prog(struct xdp_md *ctx)
{
int rc;
xxx
rc = lpm_lookup_ipv4(&ipv4_map, iph->saddr);
}

BPF_MAP_TYPE_LPM_TRIE

  • 最长前缀匹配, 查找匹配范围是 8 的倍数 (8-2048)
  • kernel 4.11 后加入;
  • 必须声明 flag BPF_F_NO_PREALLOC

用户空间

用户空间写入 cidr 到 map 过程与写入普通 map 差别不大, 查找到 map_fd -> bpf_map_update_elem

  • bpf_map_update_elem 的 flags 一般取 BPF_ANY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int write_v4(char *cidr, int map_fd)
{
char *addr_str = strdup(cidr);
char *prefix_str;
int prefix_len;

struct lpm_v4_key key = {0};

prefix_str = strchr(addr_str, '/');
if (!prefix_str)
{
free(addr_str);
return -1;
}
*prefix_str = '\0';
prefix_str++;

// prefix length
prefix_len = atoi(prefix_str);
if (prefix_len < 0 || prefix_len > 32)
{
free(addr_str);
return -1;
}
// parse address
struct in_addr addr;
if (inet_pton(AF_INET, addr_str, &addr) != 1)
{
free(addr_str);
return -1;
}

enum xdp_action action = XDP_PASS; // default action
key.lpm.prefixlen = prefix_len;
key.addr = addr.s_addr;

if (bpf_map_update_elem(map_fd, &key, &action, BPF_ANY) < 0)
{
free(addr_str);
return -1;
}

return 0;
}

尾声

话说为啥没有 xdp 实现的完整路由器呢 ❓