导语
转载: 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; };
struct { __uint(type, BPF_MAP_TYPE_LPM_TRIE); __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");
struct { __uint(type, BPF_MAP_TYPE_LPM_TRIE); __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_len = atoi(prefix_str); if (prefix_len < 0 || prefix_len > 32) { free(addr_str); return -1; } struct in_addr addr; if (inet_pton(AF_INET, addr_str, &addr) != 1) { free(addr_str); return -1; }
enum xdp_action action = XDP_PASS; 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 实现的完整路由器呢 ❓