From a87479a089ae28ed667282d781d980b01fea035f Mon Sep 17 00:00:00 2001 From: SkohTV Date: Wed, 8 Oct 2025 16:18:13 -0400 Subject: [PATCH 1/5] cli: add `ratelimit` keyword --- src/bfcli/lexer.l | 11 +++++++++++ src/bfcli/parser.y | 24 ++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/bfcli/lexer.l b/src/bfcli/lexer.l index a262a4e0..b5cacc14 100644 --- a/src/bfcli/lexer.l +++ b/src/bfcli/lexer.l @@ -22,6 +22,7 @@ %s STATE_HOOK_OPTS %s STATE_LOG_OPTS +%s STATE_RATELIMIT_OPTS %s STATE_MARK_OPTS %s STATE_REDIRECT_IFACE %s STATE_REDIRECT_DIR @@ -104,6 +105,16 @@ log { BEGIN(STATE_LOG_OPTS); return LOG; } } } + /* Rate limit */ +ratelimit { BEGIN(STATE_RATELIMIT_OPTS); return RATELIMIT; } +{ + [0-9]+ { + BEGIN(INITIAL); + yylval.sval = strdup(yytext); + return RATELIMIT_VAL; + } +} + /* Sets */ \([ \t\n\r\f\v]*([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)([ \t\n\r\f\v]*,[ \t\n\r\f\v]*([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+))*[ \t\n\r\f\v]*\) { BEGIN(STATE_MATCHER_SET); diff --git a/src/bfcli/parser.y b/src/bfcli/parser.y index ffa17709..a9fffc16 100644 --- a/src/bfcli/parser.y +++ b/src/bfcli/parser.y @@ -56,9 +56,10 @@ }) enum bf_rule_option_flag { - BF_RULE_OPTION_LOG = 1 << 0, - BF_RULE_OPTION_COUNTER = 1 << 1, - BF_RULE_OPTION_MARK = 1 << 2, + BF_RULE_OPTION_LOG = 1 << 0, + BF_RULE_OPTION_COUNTER = 1 << 1, + BF_RULE_OPTION_RATELIMIT = 1 << 2, + BF_RULE_OPTION_MARK = 1 << 3, }; struct bf_rule_options { @@ -66,6 +67,7 @@ uint8_t log; bool counter; + uint32_t ratelimit; uint32_t mark; }; @@ -101,9 +103,10 @@ %token CHAIN %token RULE %token SET -%token LOG COUNTER MARK +%token LOG COUNTER RATELIMIT MARK %token REDIRECT_TOKEN %token LOG_HEADERS +%token RATELIMIT_VAL %token SET_TYPE %token SET_RAW_PAYLOAD %token STRING @@ -297,6 +300,7 @@ rule : RULE matchers rule_options rule_verdict bf_parse_err("failed to create a new bf_rule\n"); rule->log = $3.flags & BF_RULE_OPTION_LOG ? $3.log : 0; + rule->ratelimit = $3.flags & BF_RULE_OPTION_RATELIMIT ? $3.ratelimit : 0; rule->counters = $3.flags & BF_RULE_OPTION_COUNTER ? $3.counter : false; if ($3.flags & BF_RULE_OPTION_MARK) @@ -351,6 +355,18 @@ rule_option : LOG LOG_HEADERS .flags = BF_RULE_OPTION_COUNTER, }; } + | RATELIMIT RATELIMIT_VAL + { + _cleanup_free_ char *in = $2; + char *tmp = in; + uint32_t limit = atoi(tmp); + + printf("Got %d\n", limit); + $$ = (struct bf_rule_options){ + .ratelimit = limit, + .flags = BF_RULE_OPTION_RATELIMIT, + }; + } | MARK STRING { _cleanup_free_ const char *raw_mark = $2; From 8917eb8390ed2c50b7057aeadb0c7524edc3f2dd Mon Sep 17 00:00:00 2001 From: SkohTV Date: Wed, 8 Oct 2025 16:19:49 -0400 Subject: [PATCH 2/5] lib: add `bf_ratelimit` struct --- src/libbpfilter/include/bpfilter/ratelimit.h | 67 +++++++++++++++++++ src/libbpfilter/include/bpfilter/rule.h | 1 + src/libbpfilter/ratelimit.c | 70 ++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/libbpfilter/include/bpfilter/ratelimit.h create mode 100644 src/libbpfilter/ratelimit.c diff --git a/src/libbpfilter/include/bpfilter/ratelimit.h b/src/libbpfilter/include/bpfilter/ratelimit.h new file mode 100644 index 00000000..ad8cc307 --- /dev/null +++ b/src/libbpfilter/include/bpfilter/ratelimit.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Meta Platforms, Inc. and affiliates. + */ + +#pragma once + +#include + +#include + +/** + * @struct bf_ratelimit + * + * Rate limit assigned to each rule + * + * @var bf_ratelimit::limit + * Number of times the rule can be matched before starting to drop packets. + */ +struct bf_ratelimit +{ + uint64_t limit; +}; + +#define _free_bf_ratelimit_ __attribute__((__cleanup__(bf_ratelimit_free))) + +/** + * Create a new @ref bf_ratelimit with the given limit. + * + * On success, @p ratelimit is set to the newly allocated @ref bf_ratelimit, + * owned by the caller. + * + * @param ratelimit Output pointer for the new @ref bf_ratelimit. Can't be NULL. + * @param limit Number of matches allowed. + * @return 0 on success, negative errno on error. + */ +int bf_ratelimit_new(struct bf_ratelimit **ratelimit, int64_t limit); + +/** + * @brief Allocate and initialize a new ratelimit from serialized data. + * + * @param ratelimit Rate limit object to allocate and initialize from the serialized + * data. The caller will own the object. On failure, `*ratelimit` is + * unchanged. Can't be NULL. + * @param node Node containing the serialized ratelimit. Can't be NULL. + * @return 0 on success, or a negative errno value on failure. + */ +int bf_ratelimit_new_from_pack(struct bf_ratelimit **ratelimit, + bf_rpack_node_t node); + +/** + * Free a @ref bf_ratelimit structure. + * + * If @p ratelimit is NULL, nothing is done. + * + * @param ratelimit Rate limit to free. Can't be NULL. + */ +void bf_ratelimit_free(struct bf_ratelimit **ratelimit); + +/** + * @brief Serialize a ratelimit. + * + * @param ratelimit Rate limit to serialize. Can't be NULL. + * @param pack `bf_wpack_t` object to serialize the ratelimit into. Can't be NULL. + * @return 0 on success, or a negative error value on failure. + */ +int bf_ratelimit_pack(const struct bf_ratelimit *ratelimit, bf_wpack_t *pack); diff --git a/src/libbpfilter/include/bpfilter/rule.h b/src/libbpfilter/include/bpfilter/rule.h index 7ab1331e..2eb544ff 100644 --- a/src/libbpfilter/include/bpfilter/rule.h +++ b/src/libbpfilter/include/bpfilter/rule.h @@ -57,6 +57,7 @@ struct bf_rule uint32_t index; bf_list matchers; uint8_t log; + uint32_t ratelimit; /** Mark to set to the packet's `sk_buff`. Only support for some hooks. * The leftmost 32 bits are set to 1 if a mark is defined, or 0 otherwise. diff --git a/src/libbpfilter/ratelimit.c b/src/libbpfilter/ratelimit.c new file mode 100644 index 00000000..1faf9463 --- /dev/null +++ b/src/libbpfilter/ratelimit.c @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2025 Meta Platforms, Inc. and affiliates. + */ +#include "bpfilter/ratelimit.h" + +#include +#include +#include +#include + +#include "bpfilter/helper.h" + +int bf_ratelimit_new(struct bf_ratelimit **ratelimit, int64_t limit) +{ + _cleanup_free_ struct bf_ratelimit *_ratelimit = NULL; + + bf_assert(ratelimit); + + _ratelimit = malloc(sizeof(*_ratelimit)); + if (!_ratelimit) + return -ENOMEM; + + _ratelimit->limit = limit; + + *ratelimit = TAKE_PTR(_ratelimit); + + return 0; +} + +int bf_ratelimit_new_from_pack(struct bf_ratelimit **ratelimit, + bf_rpack_node_t node) +{ + _free_bf_ratelimit_ struct bf_ratelimit *_ratelimit = NULL; + int r; + + bf_assert(ratelimit); + + r = bf_ratelimit_new(&_ratelimit, 0); + if (r) + return r; + + r = bf_rpack_kv_u64(node, "bytes", &_ratelimit->limit); + if (r) + return bf_rpack_key_err(r, "bf_ratelimit.bytes"); + + *ratelimit = TAKE_PTR(_ratelimit); + + return 0; +} + +void bf_ratelimit_free(struct bf_ratelimit **ratelimit) +{ + bf_assert(ratelimit); + + if (!*ratelimit) + return; + + freep((void *)ratelimit); +} + +int bf_ratelimit_pack(const struct bf_ratelimit *ratelimit, bf_wpack_t *pack) +{ + bf_assert(ratelimit); + bf_assert(pack); + + bf_wpack_kv_u64(pack, "bytes", ratelimit->limit); + + return bf_wpack_is_valid(pack) ? 0 : -EINVAL; +} From e431f889fec686d87eb94c7371b0492f3d465d11 Mon Sep 17 00:00:00 2001 From: SkohTV Date: Wed, 8 Oct 2025 16:21:32 -0400 Subject: [PATCH 3/5] daemon: add ratelimit into `bf_map` --- src/bpfilter/cgen/fixup.c | 2 ++ src/bpfilter/cgen/fixup.h | 2 ++ src/bpfilter/cgen/prog/map.c | 3 ++ src/bpfilter/cgen/prog/map.h | 1 + src/bpfilter/cgen/program.c | 59 ++++++++++++++++++++++++++++++++++++ src/bpfilter/cgen/program.h | 14 +++++++++ 6 files changed, 81 insertions(+) diff --git a/src/bpfilter/cgen/fixup.c b/src/bpfilter/cgen/fixup.c index 99e94924..fd04ead0 100644 --- a/src/bpfilter/cgen/fixup.c +++ b/src/bpfilter/cgen/fixup.c @@ -44,6 +44,7 @@ static const char *_bf_fixup_type_to_str(enum bf_fixup_type type) static const char *str[] = { [BF_FIXUP_TYPE_JMP_NEXT_RULE] = "BF_FIXUP_TYPE_JMP_NEXT_RULE", [BF_FIXUP_TYPE_COUNTERS_MAP_FD] = "BF_FIXUP_TYPE_COUNTERS_MAP_FD", + [BF_FIXUP_TYPE_RATELIMIT_MAP_FD] = "BF_FIXUP_TYPE_RATELIMIT_MAP_FD", [BF_FIXUP_TYPE_PRINTER_MAP_FD] = "BF_FIXUP_TYPE_PRINTER_MAP_FD", [BF_FIXUP_TYPE_SET_MAP_FD] = "BF_FIXUP_TYPE_SET_MAP_FD", [BF_FIXUP_ELFSTUB_CALL] = "BF_FIXUP_ELFSTUB_CALL", @@ -70,6 +71,7 @@ void bf_fixup_dump(const struct bf_fixup *fixup, prefix_t *prefix) switch (fixup->type) { case BF_FIXUP_TYPE_JMP_NEXT_RULE: case BF_FIXUP_TYPE_COUNTERS_MAP_FD: + case BF_FIXUP_TYPE_RATELIMIT_MAP_FD: case BF_FIXUP_TYPE_PRINTER_MAP_FD: // No specific value to dump break; diff --git a/src/bpfilter/cgen/fixup.h b/src/bpfilter/cgen/fixup.h index bf43a846..11babc75 100644 --- a/src/bpfilter/cgen/fixup.h +++ b/src/bpfilter/cgen/fixup.h @@ -32,6 +32,8 @@ enum bf_fixup_type BF_FIXUP_TYPE_JMP_NEXT_RULE, /// Set the counters map file descriptor in the @c BPF_LD_MAP_FD instruction. BF_FIXUP_TYPE_COUNTERS_MAP_FD, + /// Set the ratelimit map file descriptor in the @c BPF_LD_MAP_FD instruction. + BF_FIXUP_TYPE_RATELIMIT_MAP_FD, /// Set the printer map file descriptor in the @c BPF_LD_MAP_FD instruction. BF_FIXUP_TYPE_PRINTER_MAP_FD, /// Set the log map file descriptor in the @c BPF_LD_MAP_FD instruction. diff --git a/src/bpfilter/cgen/prog/map.c b/src/bpfilter/cgen/prog/map.c index ac4bc23b..97083616 100644 --- a/src/bpfilter/cgen/prog/map.c +++ b/src/bpfilter/cgen/prog/map.c @@ -62,6 +62,7 @@ int bf_map_new(struct bf_map **map, const char *name, enum bf_map_type type, { static enum bf_bpf_map_type _map_type_to_bpf[_BF_MAP_TYPE_MAX] = { [BF_MAP_TYPE_COUNTERS] = BF_BPF_MAP_TYPE_ARRAY, + [BF_MAP_TYPE_RATELIMIT] = BF_BPF_MAP_TYPE_ARRAY, [BF_MAP_TYPE_PRINTER] = BF_BPF_MAP_TYPE_ARRAY, [BF_MAP_TYPE_LOG] = BF_BPF_MAP_TYPE_RINGBUF, }; @@ -172,6 +173,7 @@ static const char *_bf_map_type_to_str(enum bf_map_type type) { static const char *type_strs[] = { [BF_MAP_TYPE_COUNTERS] = "BF_MAP_TYPE_COUNTERS", + [BF_MAP_TYPE_RATELIMIT] = "BF_MAP_TYPE_RATELIMIT", [BF_MAP_TYPE_PRINTER] = "BF_MAP_TYPE_PRINTER", [BF_MAP_TYPE_LOG] = "BF_MAP_TYPE_LOG", [BF_MAP_TYPE_SET] = "BF_MAP_TYPE_SET", @@ -310,6 +312,7 @@ static struct bf_btf *_bf_map_make_btf(const struct bf_map *map) btf__add_field(kbtf, "packets", 1, 0, 0); btf__add_field(kbtf, "bytes", 1, 64, 0); break; + case BF_MAP_TYPE_RATELIMIT: case BF_MAP_TYPE_PRINTER: case BF_MAP_TYPE_SET: case BF_MAP_TYPE_LOG: diff --git a/src/bpfilter/cgen/prog/map.h b/src/bpfilter/cgen/prog/map.h index 6e911780..b3f6ee54 100644 --- a/src/bpfilter/cgen/prog/map.h +++ b/src/bpfilter/cgen/prog/map.h @@ -18,6 +18,7 @@ enum bf_map_type { BF_MAP_TYPE_COUNTERS, + BF_MAP_TYPE_RATELIMIT, BF_MAP_TYPE_PRINTER, BF_MAP_TYPE_LOG, BF_MAP_TYPE_SET, diff --git a/src/bpfilter/cgen/program.c b/src/bpfilter/cgen/program.c index 0eb372a6..ccddf6cc 100644 --- a/src/bpfilter/cgen/program.c +++ b/src/bpfilter/cgen/program.c @@ -118,6 +118,11 @@ int bf_program_new(struct bf_program **program, const struct bf_chain *chain) if (r < 0) return bf_err_r(r, "failed to create the counters bf_map object"); + r = bf_map_new(&_program->rmap, "ratelimit_map", BF_MAP_TYPE_RATELIMIT, + sizeof(uint32_t), sizeof(struct bf_counter), 1); + if (r < 0) + return bf_err_r(r, "failed to create the ratelimit bf_map object"); + r = bf_map_new(&_program->pmap, "printer_map", BF_MAP_TYPE_PRINTER, sizeof(uint32_t), BF_MAP_VALUE_SIZE_UNKNOWN, 1); if (r < 0) @@ -186,6 +191,14 @@ int bf_program_new_from_pack(struct bf_program **program, if (r) return r; + bf_map_free(&_program->rmap); + r = bf_rpack_kv_obj(node, "rmap", &child); + if (r) + return bf_rpack_key_err(r, "bf_program.rmap"); + r = bf_map_new_from_pack(&_program->rmap, dir_fd, child); + if (r) + return r; + bf_map_free(&_program->pmap); r = bf_rpack_kv_obj(node, "pmap", &child); if (r) @@ -270,6 +283,7 @@ void bf_program_free(struct bf_program **program) closep(&(*program)->runtime.prog_fd); bf_map_free(&(*program)->cmap); + bf_map_free(&(*program)->rmap); bf_map_free(&(*program)->pmap); bf_map_free(&(*program)->lmap); bf_list_clean(&(*program)->sets); @@ -289,6 +303,10 @@ int bf_program_pack(const struct bf_program *program, bf_wpack_t *pack) bf_map_pack(program->cmap, pack); bf_wpack_close_object(pack); + bf_wpack_open_object(pack, "rmap"); + bf_map_pack(program->rmap, pack); + bf_wpack_close_object(pack); + bf_wpack_open_object(pack, "pmap"); bf_map_pack(program->pmap, pack); bf_wpack_close_object(pack); @@ -329,6 +347,11 @@ void bf_program_dump(const struct bf_program *program, prefix_t *prefix) bf_map_dump(program->cmap, bf_dump_prefix_last(prefix)); bf_dump_prefix_pop(prefix); + DUMP(prefix, "rmap: struct bf_map *"); + bf_dump_prefix_push(prefix); + bf_map_dump(program->rmap, bf_dump_prefix_last(prefix)); + bf_dump_prefix_pop(prefix); + DUMP(prefix, "pmap: struct bf_map *"); bf_dump_prefix_push(prefix); bf_map_dump(program->pmap, bf_dump_prefix_last(prefix)); @@ -455,6 +478,10 @@ static int _bf_program_fixup(struct bf_program *program, insn_type = BF_FIXUP_INSN_IMM; value = program->cmap->fd; break; + case BF_FIXUP_TYPE_RATELIMIT_MAP_FD: + insn_type = BF_FIXUP_INSN_IMM; + value = program->rmap->fd; + break; case BF_FIXUP_TYPE_PRINTER_MAP_FD: insn_type = BF_FIXUP_INSN_IMM; value = program->pmap->fd; @@ -913,6 +940,26 @@ static int _bf_program_load_printer_map(struct bf_program *program) return 0; } +static int _bf_program_load_ratelimit_map(struct bf_program *program) +{ + _cleanup_close_ int _fd = -1; + int r; + + bf_assert(program); + + r = bf_map_create(program->rmap); + if (r < 0) + return r; + + r = _bf_program_fixup(program, BF_FIXUP_TYPE_RATELIMIT_MAP_FD); + if (r < 0) { + bf_map_destroy(program->rmap); + return bf_err_r(r, "failed to fixup ratelimit map FD"); + } + + return 0; +} + static int _bf_program_load_counters_map(struct bf_program *program) { _cleanup_close_ int _fd = -1; @@ -1041,6 +1088,10 @@ int bf_program_load(struct bf_program *prog) if (r) return r; + r = _bf_program_load_ratelimit_map(prog); + if (r) + return r; + r = _bf_program_load_printer_map(prog); if (r) return r; @@ -1104,6 +1155,7 @@ void bf_program_unload(struct bf_program *prog) closep(&prog->runtime.prog_fd); bf_link_detach(prog->link); bf_map_destroy(prog->cmap); + bf_map_destroy(prog->rmap); bf_map_destroy(prog->pmap); bf_map_destroy(prog->lmap); bf_list_foreach (&prog->sets, map_node) @@ -1155,6 +1207,12 @@ int bf_program_pin(struct bf_program *prog, int dir_fd) goto err_unpin_all; } + r = bf_map_pin(prog->rmap, dir_fd); + if (r) { + bf_err_r(r, "failed to pin BPF ratelimit map for '%s'", name); + goto err_unpin_all; + } + r = bf_map_pin(prog->pmap, dir_fd); if (r) { bf_err_r(r, "failed to pin BPF printer map for '%s'", name); @@ -1196,6 +1254,7 @@ void bf_program_unpin(struct bf_program *prog, int dir_fd) assert(prog); bf_map_unpin(prog->cmap, dir_fd); + bf_map_unpin(prog->rmap, dir_fd); bf_map_unpin(prog->pmap, dir_fd); bf_map_unpin(prog->lmap, dir_fd); diff --git a/src/bpfilter/cgen/program.h b/src/bpfilter/cgen/program.h index e4361f31..def96c2e 100644 --- a/src/bpfilter/cgen/program.h +++ b/src/bpfilter/cgen/program.h @@ -146,6 +146,18 @@ return __r; \ }) +#define EMIT_LOAD_RATELIMIT_FD_FIXUP(program, reg) \ + ({ \ + const struct bpf_insn ld_insn[2] = {BPF_LD_MAP_FD(reg, 0)}; \ + int __r = bf_program_emit_fixup( \ + (program), BF_FIXUP_TYPE_RATELIMIT_MAP_FD, ld_insn[0], NULL); \ + if (__r < 0) \ + return __r; \ + __r = bf_program_emit((program), ld_insn[1]); \ + if (__r < 0) \ + return __r; \ + }) + #define EMIT_LOAD_LOG_FD_FIXUP(program, reg) \ ({ \ const struct bpf_insn ld_insn[2] = {BPF_LD_MAP_FD(reg, 0)}; \ @@ -200,6 +212,8 @@ struct bf_program /// Counters map struct bf_map *cmap; + /// Rate limit map + struct bf_map *rmap; /// Printer map struct bf_map *pmap; /// Log map From 8bd3fd7d47d4380acabe1474b4503b856d306a85 Mon Sep 17 00:00:00 2001 From: SkohTV Date: Wed, 8 Oct 2025 16:21:46 -0400 Subject: [PATCH 4/5] WIP --- src/bfcli/lexer.l | 3 +- src/bfcli/parser.y | 27 +++++++++++++--- src/bpfilter/CMakeLists.txt | 1 + src/bpfilter/bpf/ratelimit.bpf.c | 34 ++++++++++++++++++++ src/bpfilter/cgen/elfstub.h | 3 ++ src/bpfilter/cgen/prog/map.c | 7 +++- src/bpfilter/cgen/program.c | 20 +++++++++++- src/libbpfilter/include/bpfilter/ratelimit.h | 15 ++++++--- src/libbpfilter/ratelimit.c | 19 +++++++---- src/libbpfilter/rule.c | 6 ++++ 10 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 src/bpfilter/bpf/ratelimit.bpf.c diff --git a/src/bfcli/lexer.l b/src/bfcli/lexer.l index b5cacc14..3b671cae 100644 --- a/src/bfcli/lexer.l +++ b/src/bfcli/lexer.l @@ -108,7 +108,8 @@ log { BEGIN(STATE_LOG_OPTS); return LOG; } /* Rate limit */ ratelimit { BEGIN(STATE_RATELIMIT_OPTS); return RATELIMIT; } { - [0-9]+ { + /* -?[0-9]+(\.[0-9]+)*\/[a-zA-z]+ { */ /* ex: -13.99/abc */ + -?[0-9]+\/[a-zA-z]+ { /* ex: 13/abcd */ BEGIN(INITIAL); yylval.sval = strdup(yytext); return RATELIMIT_VAL; diff --git a/src/bfcli/parser.y b/src/bfcli/parser.y index a9fffc16..e9436a37 100644 --- a/src/bfcli/parser.y +++ b/src/bfcli/parser.y @@ -58,8 +58,8 @@ enum bf_rule_option_flag { BF_RULE_OPTION_LOG = 1 << 0, BF_RULE_OPTION_COUNTER = 1 << 1, - BF_RULE_OPTION_RATELIMIT = 1 << 2, - BF_RULE_OPTION_MARK = 1 << 3, + BF_RULE_OPTION_MARK = 1 << 2, + BF_RULE_OPTION_RATELIMIT = 1 << 3, }; struct bf_rule_options { @@ -359,9 +359,21 @@ rule_option : LOG LOG_HEADERS { _cleanup_free_ char *in = $2; char *tmp = in; - uint32_t limit = atoi(tmp); + char *saveptr; + + if (tmp[0] == '-') + bf_parse_err("ratelimit should be positive"); + + errno = 0; + uint32_t limit = strtoul(strtok_r(tmp, "/", &saveptr), NULL, 0); + if (errno != 0) + bf_parse_err("ratelimit value is too large"); + + $$ = (struct bf_rule_options){ + .ratelimit = limit, + .flags = BF_RULE_OPTION_RATELIMIT, + }; - printf("Got %d\n", limit); $$ = (struct bf_rule_options){ .ratelimit = limit, .flags = BF_RULE_OPTION_RATELIMIT, @@ -403,6 +415,13 @@ rule_options : %empty { $$ = (struct bf_rule_options){}; } $1.counter = $2.counter; } + if ($2.flags & BF_RULE_OPTION_RATELIMIT) { + if ($1.flags & BF_RULE_OPTION_RATELIMIT) + bf_parse_err("duplicate keyword \"ratelimit\" in rule"); + $1.flags |= BF_RULE_OPTION_RATELIMIT; + $1.ratelimit = $2.ratelimit; + } + if ($2.flags & BF_RULE_OPTION_MARK) { if ($1.flags & BF_RULE_OPTION_MARK) bf_parse_err("duplicate keyword \"mark\" in rule"); diff --git a/src/bpfilter/CMakeLists.txt b/src/bpfilter/CMakeLists.txt index 610605f8..1aaffb5e 100644 --- a/src/bpfilter/CMakeLists.txt +++ b/src/bpfilter/CMakeLists.txt @@ -62,6 +62,7 @@ bf_target_add_elfstubs(bpfilter "parse_ipv6_nh" "update_counters" "log" + "ratelimit" ) target_compile_definitions(bpfilter diff --git a/src/bpfilter/bpf/ratelimit.bpf.c b/src/bpfilter/bpf/ratelimit.bpf.c new file mode 100644 index 00000000..7695a56c --- /dev/null +++ b/src/bpfilter/bpf/ratelimit.bpf.c @@ -0,0 +1,34 @@ +#include + +#include +#include +#include + +#define BF_TIME_S 1000000000 + +struct bf_ratelimit +{ + __u64 current; + __u64 last_time; +}; + +__u8 bf_ratelimit(void *map, __u64 key, __u64 limit) +{ + struct bf_ratelimit *ratelimit; + __u64 current_time = bpf_ktime_get_ns() / BF_TIME_S; + + ratelimit = bpf_map_lookup_elem(map, &key); + if (!ratelimit) { + bpf_printk("failed to fetch the rule's ratelimit"); + return 1; + } + + if (current_time != ratelimit->last_time) { + ratelimit->current = 0; + } + + ratelimit->current++; + ratelimit->last_time = current_time; + + return (ratelimit->current > limit); +} diff --git a/src/bpfilter/cgen/elfstub.h b/src/bpfilter/cgen/elfstub.h index c628c842..1853b4c4 100644 --- a/src/bpfilter/cgen/elfstub.h +++ b/src/bpfilter/cgen/elfstub.h @@ -135,6 +135,9 @@ enum bf_elfstub_id */ BF_ELFSTUB_LOG, + // Return 0 on ACCEPT, 1 on DROP + BF_ELFSTUB_RATELIMIT, + _BF_ELFSTUB_MAX, }; diff --git a/src/bpfilter/cgen/prog/map.c b/src/bpfilter/cgen/prog/map.c index 97083616..d09cc26b 100644 --- a/src/bpfilter/cgen/prog/map.c +++ b/src/bpfilter/cgen/prog/map.c @@ -312,7 +312,12 @@ static struct bf_btf *_bf_map_make_btf(const struct bf_map *map) btf__add_field(kbtf, "packets", 1, 0, 0); btf__add_field(kbtf, "bytes", 1, 64, 0); break; - case BF_MAP_TYPE_RATELIMIT: + case BF_MAP_TYPE_RATELIMIT: // I have no clue what I'm doing here + btf__add_int(kbtf, "u64", 8, 0); + btf->key_type_id = btf__add_int(kbtf, "u32", 4, 0); + btf->value_type_id = btf__add_struct(kbtf, "bf_", 16); + btf__add_field(kbtf, "limit", 1, 0, 0); + break; case BF_MAP_TYPE_PRINTER: case BF_MAP_TYPE_SET: case BF_MAP_TYPE_LOG: diff --git a/src/bpfilter/cgen/program.c b/src/bpfilter/cgen/program.c index ccddf6cc..8a6ec49d 100644 --- a/src/bpfilter/cgen/program.c +++ b/src/bpfilter/cgen/program.c @@ -36,8 +36,11 @@ #include #include +#include "bpf/bpf_helpers.h" +#include "bpfilter/ratelimit.h" #include "cgen/cgroup.h" #include "cgen/dump.h" +#include "cgen/elfstub.h" #include "cgen/fixup.h" #include "cgen/jmp.h" #include "cgen/matcher/icmp.h" @@ -119,7 +122,7 @@ int bf_program_new(struct bf_program **program, const struct bf_chain *chain) return bf_err_r(r, "failed to create the counters bf_map object"); r = bf_map_new(&_program->rmap, "ratelimit_map", BF_MAP_TYPE_RATELIMIT, - sizeof(uint32_t), sizeof(struct bf_counter), 1); + sizeof(uint32_t), sizeof(struct bf_ratelimit), 1); if (r < 0) return bf_err_r(r, "failed to create the ratelimit bf_map object"); @@ -634,6 +637,21 @@ static int _bf_program_generate_rule(struct bf_program *program, EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_UPDATE_COUNTERS); } + if (rule->ratelimit) { + EMIT_LOAD_RATELIMIT_FD_FIXUP(program, BPF_REG_1); + EMIT(program, BPF_MOV32_IMM(BPF_REG_2, rule->index)); + EMIT(program, BPF_MOV32_IMM(BPF_REG_3, rule->ratelimit)); + EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_RATELIMIT); + + _clean_bf_jmpctx_ struct bf_jmpctx ctx = + bf_jmpctx_get(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 0)); + + EMIT(program, + BPF_MOV64_IMM(BPF_REG_0, + program->runtime.ops->get_verdict(BF_VERDICT_DROP))); + EMIT(program, BPF_EXIT_INSN()); + } + switch (rule->verdict) { case BF_VERDICT_ACCEPT: case BF_VERDICT_DROP: diff --git a/src/libbpfilter/include/bpfilter/ratelimit.h b/src/libbpfilter/include/bpfilter/ratelimit.h index ad8cc307..eeb83f70 100644 --- a/src/libbpfilter/include/bpfilter/ratelimit.h +++ b/src/libbpfilter/include/bpfilter/ratelimit.h @@ -14,12 +14,15 @@ * * Rate limit assigned to each rule * - * @var bf_ratelimit::limit - * Number of times the rule can be matched before starting to drop packets. + * @var bf_ratelimit::current + * Packets allowed to pass in the current unit of time. + * @var bf_ratelimit::last_time + * Timestamp of the last packet. */ struct bf_ratelimit { - uint64_t limit; + uint64_t current; + uint64_t last_time; }; #define _free_bf_ratelimit_ __attribute__((__cleanup__(bf_ratelimit_free))) @@ -31,10 +34,12 @@ struct bf_ratelimit * owned by the caller. * * @param ratelimit Output pointer for the new @ref bf_ratelimit. Can't be NULL. - * @param limit Number of matches allowed. + * @param current Packets allowed to pass in the current unit of time. + * @param last_time Timestamp of the last packet. * @return 0 on success, negative errno on error. */ -int bf_ratelimit_new(struct bf_ratelimit **ratelimit, int64_t limit); +int bf_ratelimit_new(struct bf_ratelimit **ratelimit, uint64_t current, + uint64_t last_time); /** * @brief Allocate and initialize a new ratelimit from serialized data. diff --git a/src/libbpfilter/ratelimit.c b/src/libbpfilter/ratelimit.c index 1faf9463..4833977d 100644 --- a/src/libbpfilter/ratelimit.c +++ b/src/libbpfilter/ratelimit.c @@ -11,7 +11,8 @@ #include "bpfilter/helper.h" -int bf_ratelimit_new(struct bf_ratelimit **ratelimit, int64_t limit) +int bf_ratelimit_new(struct bf_ratelimit **ratelimit, uint64_t current, + uint64_t last_time) { _cleanup_free_ struct bf_ratelimit *_ratelimit = NULL; @@ -21,7 +22,8 @@ int bf_ratelimit_new(struct bf_ratelimit **ratelimit, int64_t limit) if (!_ratelimit) return -ENOMEM; - _ratelimit->limit = limit; + _ratelimit->current = current; + _ratelimit->last_time = last_time; *ratelimit = TAKE_PTR(_ratelimit); @@ -36,13 +38,17 @@ int bf_ratelimit_new_from_pack(struct bf_ratelimit **ratelimit, bf_assert(ratelimit); - r = bf_ratelimit_new(&_ratelimit, 0); + r = bf_ratelimit_new(&_ratelimit, 0, 0); if (r) return r; - r = bf_rpack_kv_u64(node, "bytes", &_ratelimit->limit); + r = bf_rpack_kv_u64(node, "current", &_ratelimit->current); if (r) - return bf_rpack_key_err(r, "bf_ratelimit.bytes"); + return bf_rpack_key_err(r, "bf_ratelimit.current"); + + r = bf_rpack_kv_u64(node, "last_time", &_ratelimit->last_time); + if (r) + return bf_rpack_key_err(r, "bf_ratelimit.last_time"); *ratelimit = TAKE_PTR(_ratelimit); @@ -64,7 +70,8 @@ int bf_ratelimit_pack(const struct bf_ratelimit *ratelimit, bf_wpack_t *pack) bf_assert(ratelimit); bf_assert(pack); - bf_wpack_kv_u64(pack, "bytes", ratelimit->limit); + bf_wpack_kv_u64(pack, "current", ratelimit->current); + bf_wpack_kv_u64(pack, "last_time", ratelimit->last_time); return bf_wpack_is_valid(pack) ? 0 : -EINVAL; } diff --git a/src/libbpfilter/rule.c b/src/libbpfilter/rule.c index 70eb4f73..a1ed2afa 100644 --- a/src/libbpfilter/rule.c +++ b/src/libbpfilter/rule.c @@ -89,6 +89,10 @@ int bf_rule_new_from_pack(struct bf_rule **rule, bf_rpack_node_t node) if (r) return bf_rpack_key_err(r, "bf_rule.counters"); + r = bf_rpack_kv_u32(node, "ratelimit", &_rule->ratelimit); + if (r) + return bf_rpack_key_err(r, "bf_rule.ratelimit"); + r = bf_rpack_kv_u64(node, "mark", &_rule->mark); if (r) return bf_rpack_key_err(r, "bf_rule.mark"); @@ -150,6 +154,7 @@ int bf_rule_pack(const struct bf_rule *rule, bf_wpack_t *pack) bf_wpack_kv_u32(pack, "index", rule->index); bf_wpack_kv_u8(pack, "log", rule->log); bf_wpack_kv_bool(pack, "counters", rule->counters); + bf_wpack_kv_u32(pack, "ratelimit", rule->ratelimit); bf_wpack_kv_u64(pack, "mark", rule->mark); bf_wpack_kv_int(pack, "verdict", rule->verdict); bf_wpack_kv_u32(pack, "redirect_ifindex", rule->redirect_ifindex); @@ -186,6 +191,7 @@ void bf_rule_dump(const struct bf_rule *rule, prefix_t *prefix) DUMP(prefix, "log: %02x", rule->log); DUMP(prefix, "counters: %s", rule->counters ? "yes" : "no"); + DUMP(prefix, "ratelimit: 0x%" PRIx32, rule->ratelimit); DUMP(prefix, "mark: 0x%" PRIx64, rule->mark); DUMP(prefix, "verdict: %s", bf_verdict_to_str(rule->verdict)); if (bf_rule_has_redirect(rule)) { From 699137e1380351fb57a3fe4130d95b0235814f1b Mon Sep 17 00:00:00 2001 From: SkohTV Date: Tue, 3 Feb 2026 15:10:20 -0500 Subject: [PATCH 5/5] needs a rebase --- src/bfcli/parser.y | 8 ++------ src/bpfilter/bpf/ratelimit.bpf.c | 3 +-- src/bpfilter/cgen/elfstub.h | 13 ++++++++++++- src/bpfilter/cgen/prog/map.c | 7 +------ src/bpfilter/cgen/program.c | 18 ++++++++++-------- src/libbpfilter/ratelimit.c | 10 +++++----- 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/bfcli/parser.y b/src/bfcli/parser.y index e9436a37..262b719c 100644 --- a/src/bfcli/parser.y +++ b/src/bfcli/parser.y @@ -360,12 +360,13 @@ rule_option : LOG LOG_HEADERS _cleanup_free_ char *in = $2; char *tmp = in; char *saveptr; + uint32_t limit; if (tmp[0] == '-') bf_parse_err("ratelimit should be positive"); errno = 0; - uint32_t limit = strtoul(strtok_r(tmp, "/", &saveptr), NULL, 0); + limit = strtoul(strtok_r(tmp, "/", &saveptr), NULL, 0); if (errno != 0) bf_parse_err("ratelimit value is too large"); @@ -373,11 +374,6 @@ rule_option : LOG LOG_HEADERS .ratelimit = limit, .flags = BF_RULE_OPTION_RATELIMIT, }; - - $$ = (struct bf_rule_options){ - .ratelimit = limit, - .flags = BF_RULE_OPTION_RATELIMIT, - }; } | MARK STRING { diff --git a/src/bpfilter/bpf/ratelimit.bpf.c b/src/bpfilter/bpf/ratelimit.bpf.c index 7695a56c..2b3ad15c 100644 --- a/src/bpfilter/bpf/ratelimit.bpf.c +++ b/src/bpfilter/bpf/ratelimit.bpf.c @@ -23,9 +23,8 @@ __u8 bf_ratelimit(void *map, __u64 key, __u64 limit) return 1; } - if (current_time != ratelimit->last_time) { + if (current_time != ratelimit->last_time) ratelimit->current = 0; - } ratelimit->current++; ratelimit->last_time = current_time; diff --git a/src/bpfilter/cgen/elfstub.h b/src/bpfilter/cgen/elfstub.h index 1853b4c4..291eb12f 100644 --- a/src/bpfilter/cgen/elfstub.h +++ b/src/bpfilter/cgen/elfstub.h @@ -135,7 +135,18 @@ enum bf_elfstub_id */ BF_ELFSTUB_LOG, - // Return 0 on ACCEPT, 1 on DROP + /** + * Drop packets when `limit` number of packets have already been seen in the last unit of time + * + * `__u8 bf_ratelimit(void *map, __u64 key, __u64 limit)` + * + * **Parameters** + * - `map`: address of the ratelimit map. + * - `key`: key of the map to update. + * - `limit`: number of packets allowed to pass in one unit of time. + * + * **Return** 0 on accept, or 1 on drop. + */ BF_ELFSTUB_RATELIMIT, _BF_ELFSTUB_MAX, diff --git a/src/bpfilter/cgen/prog/map.c b/src/bpfilter/cgen/prog/map.c index d09cc26b..97083616 100644 --- a/src/bpfilter/cgen/prog/map.c +++ b/src/bpfilter/cgen/prog/map.c @@ -312,12 +312,7 @@ static struct bf_btf *_bf_map_make_btf(const struct bf_map *map) btf__add_field(kbtf, "packets", 1, 0, 0); btf__add_field(kbtf, "bytes", 1, 64, 0); break; - case BF_MAP_TYPE_RATELIMIT: // I have no clue what I'm doing here - btf__add_int(kbtf, "u64", 8, 0); - btf->key_type_id = btf__add_int(kbtf, "u32", 4, 0); - btf->value_type_id = btf__add_struct(kbtf, "bf_", 16); - btf__add_field(kbtf, "limit", 1, 0, 0); - break; + case BF_MAP_TYPE_RATELIMIT: case BF_MAP_TYPE_PRINTER: case BF_MAP_TYPE_SET: case BF_MAP_TYPE_LOG: diff --git a/src/bpfilter/cgen/program.c b/src/bpfilter/cgen/program.c index 8a6ec49d..4687bda9 100644 --- a/src/bpfilter/cgen/program.c +++ b/src/bpfilter/cgen/program.c @@ -643,13 +643,15 @@ static int _bf_program_generate_rule(struct bf_program *program, EMIT(program, BPF_MOV32_IMM(BPF_REG_3, rule->ratelimit)); EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_RATELIMIT); - _clean_bf_jmpctx_ struct bf_jmpctx ctx = - bf_jmpctx_get(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 0)); - - EMIT(program, - BPF_MOV64_IMM(BPF_REG_0, - program->runtime.ops->get_verdict(BF_VERDICT_DROP))); - EMIT(program, BPF_EXIT_INSN()); + { + _clean_bf_jmpctx_ struct bf_jmpctx ctx = + bf_jmpctx_get(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 0)); + + EMIT(program, + BPF_MOV64_IMM(BPF_REG_0, program->runtime.ops->get_verdict( + BF_VERDICT_DROP))); + EMIT(program, BPF_EXIT_INSN()); + } } switch (rule->verdict) { @@ -963,7 +965,7 @@ static int _bf_program_load_ratelimit_map(struct bf_program *program) _cleanup_close_ int _fd = -1; int r; - bf_assert(program); + assert(program); r = bf_map_create(program->rmap); if (r < 0) diff --git a/src/libbpfilter/ratelimit.c b/src/libbpfilter/ratelimit.c index 4833977d..422304b5 100644 --- a/src/libbpfilter/ratelimit.c +++ b/src/libbpfilter/ratelimit.c @@ -16,7 +16,7 @@ int bf_ratelimit_new(struct bf_ratelimit **ratelimit, uint64_t current, { _cleanup_free_ struct bf_ratelimit *_ratelimit = NULL; - bf_assert(ratelimit); + assert(ratelimit); _ratelimit = malloc(sizeof(*_ratelimit)); if (!_ratelimit) @@ -36,7 +36,7 @@ int bf_ratelimit_new_from_pack(struct bf_ratelimit **ratelimit, _free_bf_ratelimit_ struct bf_ratelimit *_ratelimit = NULL; int r; - bf_assert(ratelimit); + assert(ratelimit); r = bf_ratelimit_new(&_ratelimit, 0, 0); if (r) @@ -57,7 +57,7 @@ int bf_ratelimit_new_from_pack(struct bf_ratelimit **ratelimit, void bf_ratelimit_free(struct bf_ratelimit **ratelimit) { - bf_assert(ratelimit); + assert(ratelimit); if (!*ratelimit) return; @@ -67,8 +67,8 @@ void bf_ratelimit_free(struct bf_ratelimit **ratelimit) int bf_ratelimit_pack(const struct bf_ratelimit *ratelimit, bf_wpack_t *pack) { - bf_assert(ratelimit); - bf_assert(pack); + assert(ratelimit); + assert(pack); bf_wpack_kv_u64(pack, "current", ratelimit->current); bf_wpack_kv_u64(pack, "last_time", ratelimit->last_time);