{"id":20115515,"url":"https://github.com/asphaltt/iptables-in-bpf","last_synced_at":"2025-05-06T13:32:49.890Z","repository":{"id":138614646,"uuid":"578627201","full_name":"Asphaltt/iptables-in-bpf","owner":"Asphaltt","description":"An iptables-like ACL implementation with eBPF.","archived":false,"fork":false,"pushed_at":"2022-12-18T15:40:57.000Z","size":743,"stargazers_count":7,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-09T12:42:48.671Z","etag":null,"topics":["acl","bpf","ebpf","ebpf-co-re","iptables"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Asphaltt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-15T13:59:46.000Z","updated_at":"2025-03-21T15:11:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"ad79d640-8dbb-4d53-a62a-32f147d68440","html_url":"https://github.com/Asphaltt/iptables-in-bpf","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asphaltt%2Fiptables-in-bpf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asphaltt%2Fiptables-in-bpf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asphaltt%2Fiptables-in-bpf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asphaltt%2Fiptables-in-bpf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Asphaltt","download_url":"https://codeload.github.com/Asphaltt/iptables-in-bpf/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252693728,"owners_count":21789749,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acl","bpf","ebpf","ebpf-co-re","iptables"],"created_at":"2024-11-13T18:35:32.249Z","updated_at":"2025-05-06T13:32:49.884Z","avatar_url":"https://github.com/Asphaltt.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 低性能 eBPF ACL\n\n[高性能 eBPF ACL](https://github.com/Asphaltt/xdp_acl) 中的 ACL 规则匹配算法比较复杂，晦涩难懂；相对于 iptables 而言，该实现就比较难维护了。这就是为了性能而牺牲了可维护性。\n\n所以，有没有类似 iptables 遍历匹配规则的可维护性高的 eBPF ACL 的实现呢？\n\n有，在 eBPF 里可以使用 `bpf_for_each_map_elem()` 遍历匹配规则。\n\n## `bpf_for_each_map_elem()`\n\neBPF helpers 中有遍历 bpf map 的帮助函数， `bpf_for_each_map_elem()`。当用来遍历 bpf map 时，不就可以用来遍历 ACL 规则了；从而就可以一条一条地匹配 ACL 规则了。\n\n\u003e 该帮助函数要求 5.13 及以上的内核才支持（[BPF Features by Linux Kernel Version](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md)）。\n\n该帮助函数的目标场景之一就是防火墙。\n\n该帮助函数的函数签名及使用说明如下：\n\n```C\n/*\n * bpf_for_each_map_elem\n *\n * For each element in **map**, call **callback_fn** function with\n * **map**, **callback_ctx** and other map-specific parameters.\n * The **callback_fn** should be a static function and\n * the **callback_ctx** should be a pointer to the stack.\n * The **flags** is used to control certain aspects of the helper.\n * Currently, the **flags** must be 0.\n *\n * The following are a list of supported map types and their\n * respective expected callback signatures:\n *\n * BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_PERCPU_HASH,\n * BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH,\n * BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_PERCPU_ARRAY\n *\n * long (\\*callback_fn)(struct bpf_map \\*map, const void \\*key, void \\*value, void \\*ctx);\n *\n * For per_cpu maps, the map_value is the value on the cpu where the\n * bpf_prog is running.\n *\n * If **callback_fn** return 0, the helper will continue to the next\n * element. If return value is 1, the helper will skip the rest of\n * elements and return. Other return values are not used now.\n *\n *\n * Returns\n * The number of traversed map elements for success, **-EINVAL** for\n * invalid **flags**.\n */\nstatic long (*bpf_for_each_map_elem)(void *map, void *callback_fn, void *callback_ctx, __u64 flags) = (void *) 164;\n```\n\n\u003e 注：网络上搜索到的 `man 7 bpf-helpers` 里不一定有该帮助函数，可以根据内核版本去查看内核源代码里的 `include/uapi/linux/bpf.h` 头文件；该头文件里就包含了内核所支持的所有 bpf 帮助函数的函数列表，以及函数的使用说明。\n\n## 遍历匹配 ACL 规则\n\n![iptables in bpf](./iptables%20in%20bpf.jpg)\n\n如上图，使用 6 个数组类型的 bpf map 保存 ACL 规则。每条 ACL 规则的五元组以及规则动作使用同一个数组索引保存到那 6 个 bpf map 中。\n\n在 `bpf_for_each_map_elem()` 遍历其中一个 bpf map 的时候，就可以拿着遍历中的 `key` （数组索引）去查询另外 5 个 bpf map。\n\n\u003e 简化代码后的 `bpf_for_each_map_elem()` 的用法如下。\n\n```C\nstatic int\nmatching_rule(struct bpf_map *map, const __u32 *key, struct rule_policy *value, struct rule_matching *match) {\n    if (*key \u003e= ACL_RULE_NUM)\n        return 1;\n\n    // protocol\n    proto = (typeof(proto))bpf_map_lookup_elem(\u0026acl_protocol, key);\n\n    // sport\n    pr = (typeof(pr))bpf_map_lookup_elem(\u0026acl_sport, key);\n\n    // dport\n    pr = (typeof(pr))bpf_map_lookup_elem(\u0026acl_dport, key);\n\n    // saddr\n    m = (typeof(m))bpf_map_lookup_elem(\u0026acl_saddr, key);\n    val = (typeof(val))bpf_map_lookup_elem(m, \u0026k);\n\n    // daddr\n    m = (typeof(m))bpf_map_lookup_elem(\u0026acl_daddr, key);\n    val = (typeof(val))bpf_map_lookup_elem(m, \u0026k);\n\n    __builtin_memcpy(\u0026match-\u003epolicy, value, sizeof(*value));\n    match-\u003ematched = 1;\n    return 1;\n}\n\nstatic __always_inline int\nmatch_acl_rules(struct xdp_md *ctx) {\n    struct rule_matching match = {};\n\n    bpf_for_each_map_elem(\u0026acl_rule_policy, matching_rule, \u0026match, 0);\n\n    if (match.matched == 0)\n        return XDP_PASS;\n\n    return match.policy.action;\n}\n```\n\n\u003e 详细代码请查看 [github.com/Asphaltt/iptables-in-bpf](https://github.com/Asphaltt/iptables-in-bpf)。\n\n## iptables VS `iptables in bpf`\n\n相比于 iptables 每次增删规则时都刷一遍规则，`iptables in bpf` 里的实现能够做到无损更新规则。\n\n![iptables in bpf](./iptables%20in%20bpf%20entry.jpg)\n\n如上图，每次增删规则时，为所有规则使用一份全新的 bpf map 和 ACL bpf prog；将规则数据保存到 bpf map 后，将新的 ACL bpf prog 更新到那个 bpf prog 数组的 bpf map 中。而在 XDP 程序的入口里，直接 `bpf_tail_call()` 跳到 ACL bpf prog 即可；如下。\n\n```C\nstruct {\n    __uint(type, BPF_MAP_TYPE_PROG_ARRAY);\n    __type(key, __u32);\n    __type(value, __u32);\n    __uint(max_entries, 1);\n} acl_progs SEC(\".maps\");\n\nSEC(\"xdp_acl\")\nint xdp_acl_func(struct xdp_md *ctx) {\n    bpf_tail_call_static(ctx, \u0026acl_progs, 0);\n\n    return XDP_PASS;\n}\n```\n\n## 小结\n\n纸上得来终觉浅，绝知此事要躬行。\n\n即使是 `bpf_for_each_map_elem()` 看似简单的帮助函数，只有实践起来才知道：为什么 `callback_ctx` 要指向 bpf 程序运行时所在的栈空间。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasphaltt%2Fiptables-in-bpf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasphaltt%2Fiptables-in-bpf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasphaltt%2Fiptables-in-bpf/lists"}