diff --git a/src/bfcli/lexer.l b/src/bfcli/lexer.l index 09044bc86..337de60be 100644 --- a/src/bfcli/lexer.l +++ b/src/bfcli/lexer.l @@ -31,6 +31,7 @@ %s STATE_MATCHER_META_FLOW_HASH %s STATE_MATCHER_L4_PROTO %s STATE_MATCHER_META_PROBA +%s STATE_MATCHER_META_RATELIMIT %s STATE_MATCHER_IPV4_ADDR %s STATE_MATCHER_IP4_NET %s STATE_MATCHER_IP4_DSCP @@ -177,6 +178,16 @@ meta\.flow_hash { BEGIN(STATE_MATCHER_META_FLOW_HASH); yylval.sval = strdup( } } +meta\.ratelimit { BEGIN(STATE_MATCHER_META_RATELIMIT); yylval.sval = strdup(yytext); return MATCHER_TYPE; } +{ + (eq) { yylval.sval = strdup(yytext); return MATCHER_OP; } + {int}|[0-9a-zA-Z-]+ { + BEGIN(INITIAL); + yylval.sval = strdup(yytext); + return RAW_PAYLOAD; + } +} + ip4\.saddr { BEGIN(STATE_MATCHER_IPV4_ADDR); yylval.sval = strdup(yytext); return MATCHER_TYPE; } ip4\.daddr { BEGIN(STATE_MATCHER_IPV4_ADDR); yylval.sval = strdup(yytext); return MATCHER_TYPE; } { diff --git a/src/libbpfilter/CMakeLists.txt b/src/libbpfilter/CMakeLists.txt index 13729711a..5934a1650 100644 --- a/src/libbpfilter/CMakeLists.txt +++ b/src/libbpfilter/CMakeLists.txt @@ -117,6 +117,7 @@ bf_target_add_elfstubs(libbpfilter "pkt_log" "flow_hash" "sock_addr_log" + "ratelimit" ) target_compile_definitions(libbpfilter diff --git a/src/libbpfilter/bpf/ratelimit.bpf.c b/src/libbpfilter/bpf/ratelimit.bpf.c new file mode 100644 index 000000000..8e7c22d15 --- /dev/null +++ b/src/libbpfilter/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, const __u32 limit) +{ + struct bf_ratelimit *ratelimit; + __u64 current_time = bpf_ktime_get_ns() / BF_TIME_S; + __u32 key = 0; + + 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/libbpfilter/cgen/fixup.h b/src/libbpfilter/cgen/fixup.h index 2cda63466..022311eae 100644 --- a/src/libbpfilter/cgen/fixup.h +++ b/src/libbpfilter/cgen/fixup.h @@ -37,6 +37,8 @@ enum bf_fixup_type BF_FIXUP_TYPE_PRINTER_MAP_FD, /// Set the log map file descriptor in the @c BPF_LD_MAP_FD instruction. BF_FIXUP_TYPE_LOG_MAP_FD, + /// Set the ratelimit map file descriptor in the @c BPF_LD_MAP_FD instruction. + BF_FIXUP_TYPE_RATELIMIT_MAP_FD, /// Set a set map file descriptor in the @c BPF_LD_MAP_FD instruction. BF_FIXUP_TYPE_SET_MAP_FD, /// Call an ELF stub. diff --git a/src/libbpfilter/cgen/handle.c b/src/libbpfilter/cgen/handle.c index ff70fb33f..23ef80d89 100644 --- a/src/libbpfilter/cgen/handle.c +++ b/src/libbpfilter/cgen/handle.c @@ -108,6 +108,15 @@ int bf_handle_new_from_pack(struct bf_handle **handle, struct bf_lock *lock, return bf_rpack_key_err(r, "bf_handle.lmap"); } + r = bf_rpack_kv_node(node, "rmap", &child); + if (r) + return bf_rpack_key_err(r, "bf_handle.rmap"); + if (!bf_rpack_is_nil(child)) { + r = bf_map_new_from_pack(&_handle->rmap, dir_fd, child); + if (r) + return bf_rpack_key_err(r, "bf_handle.rmap"); + } + r = bf_rpack_kv_array(node, "sets", &child); if (r) return bf_rpack_key_err(r, "bf_handle.sets"); @@ -145,6 +154,7 @@ void bf_handle_free(struct bf_handle **handle) bf_map_free(&(*handle)->cmap); bf_map_free(&(*handle)->pmap); bf_map_free(&(*handle)->lmap); + bf_map_free(&(*handle)->rmap); bf_list_clean(&(*handle)->sets); free(*handle); @@ -190,6 +200,14 @@ int bf_handle_pack(const struct bf_handle *handle, bf_wpack_t *pack) bf_wpack_kv_nil(pack, "lmap"); } + if (handle->rmap) { + bf_wpack_open_object(pack, "rmap"); + bf_map_pack(handle->rmap, pack); + bf_wpack_close_object(pack); + } else { + bf_wpack_kv_nil(pack, "rmap"); + } + bf_wpack_kv_list(pack, "sets", &handle->sets); return bf_wpack_is_valid(pack) ? 0 : -EINVAL; @@ -243,6 +261,15 @@ void bf_handle_dump(const struct bf_handle *handle, prefix_t *prefix) DUMP(prefix, "lmap: struct bf_map * (NULL)"); } + if (handle->rmap) { + DUMP(prefix, "rmap: struct bf_map *"); + bf_dump_prefix_push(prefix); + bf_map_dump(handle->rmap, bf_dump_prefix_last(prefix)); + bf_dump_prefix_pop(prefix); + } else { + DUMP(prefix, "rmap: struct bf_map * (NULL)"); + } + DUMP(bf_dump_prefix_last(prefix), "sets: bf_list[%lu]", bf_list_size(&handle->sets)); bf_dump_prefix_push(prefix); @@ -304,6 +331,14 @@ int bf_handle_pin(struct bf_handle *handle, struct bf_lock *lock) } } + if (handle->rmap) { + r = bf_map_pin(handle->rmap, dir_fd); + if (r) { + bf_err_r(r, "failed to pin BPF ratelimit map"); + goto err_unpin_all; + } + } + bf_list_foreach (&handle->sets, set_node) { struct bf_map *map = bf_list_node_get_data(set_node); @@ -347,6 +382,8 @@ void bf_handle_unpin(struct bf_handle *handle, struct bf_lock *lock) bf_map_unpin(handle->pmap, dir_fd); if (handle->lmap) bf_map_unpin(handle->lmap, dir_fd); + if (handle->rmap) + bf_map_unpin(handle->rmap, dir_fd); bf_list_foreach (&handle->sets, set_node) { struct bf_map *map = bf_list_node_get_data(set_node); @@ -429,5 +466,6 @@ void bf_handle_unload(struct bf_handle *handle) bf_map_free(&handle->cmap); bf_map_free(&handle->pmap); bf_map_free(&handle->lmap); + bf_map_free(&handle->rmap); bf_list_clean(&handle->sets); } diff --git a/src/libbpfilter/cgen/handle.h b/src/libbpfilter/cgen/handle.h index ad0c30d16..478e66748 100644 --- a/src/libbpfilter/cgen/handle.h +++ b/src/libbpfilter/cgen/handle.h @@ -52,6 +52,9 @@ struct bf_handle /** Log map. NULL if not created. */ struct bf_map *lmap; + /** Ratelimit map. NULL if not created. */ + struct bf_map *rmap; + /** List of set maps. Contains at most one map for each unique key * format. */ bf_list sets; diff --git a/src/libbpfilter/cgen/matcher/meta.c b/src/libbpfilter/cgen/matcher/meta.c index 44a4d5c32..b4794deb8 100644 --- a/src/libbpfilter/cgen/matcher/meta.c +++ b/src/libbpfilter/cgen/matcher/meta.c @@ -157,6 +157,26 @@ _bf_matcher_generate_meta_flow_probability(struct bf_program *program, return 0; } +static int _bf_matcher_generate_meta_ratelimit(struct bf_program *program, + const struct bf_matcher *matcher) +{ + uint32_t ratelimit = *(uint32_t *)bf_matcher_payload(matcher); + + EMIT_LOAD_RATELIMIT_FD_FIXUP(program, BPF_REG_1); + EMIT(program, BPF_MOV32_IMM(BPF_REG_2, ratelimit)); + EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_RATELIMIT); + + if (bf_matcher_get_negate(matcher)) { + EMIT_FIXUP_JMP_NEXT_RULE(program, + BPF_JMP32_IMM(BPF_JEQ, BPF_REG_0, 0, 0)); + } else { + EMIT_FIXUP_JMP_NEXT_RULE(program, + BPF_JMP32_IMM(BPF_JNE, BPF_REG_0, 0, 0)); + } + + return 0; +} + int bf_matcher_generate_meta(struct bf_program *program, const struct bf_matcher *matcher) { @@ -177,6 +197,8 @@ int bf_matcher_generate_meta(struct bf_program *program, return _bf_matcher_generate_meta_port(program, matcher); case BF_MATCHER_META_FLOW_PROBABILITY: return _bf_matcher_generate_meta_flow_probability(program, matcher); + case BF_MATCHER_META_RATELIMIT: + return _bf_matcher_generate_meta_ratelimit(program, matcher); case BF_MATCHER_META_MARK: case BF_MATCHER_META_FLOW_HASH: return bf_err_r(-ENOTSUP, diff --git a/src/libbpfilter/cgen/packet.c b/src/libbpfilter/cgen/packet.c index ef078c255..a58d5374f 100644 --- a/src/libbpfilter/cgen/packet.c +++ b/src/libbpfilter/cgen/packet.c @@ -376,6 +376,7 @@ int bf_packet_gen_inline_matcher(struct bf_program *program, case BF_MATCHER_META_SPORT: case BF_MATCHER_META_DPORT: case BF_MATCHER_META_FLOW_PROBABILITY: + case BF_MATCHER_META_RATELIMIT: return bf_matcher_generate_meta(program, matcher); case BF_MATCHER_META_MARK: case BF_MATCHER_META_FLOW_HASH: diff --git a/src/libbpfilter/cgen/prog/map.c b/src/libbpfilter/cgen/prog/map.c index dedee3899..7a5beba3a 100644 --- a/src/libbpfilter/cgen/prog/map.c +++ b/src/libbpfilter/cgen/prog/map.c @@ -114,6 +114,7 @@ static struct bf_btf *_bf_map_make_btf(const struct bf_map *map) case BF_MAP_TYPE_PRINTER: case BF_MAP_TYPE_SET: case BF_MAP_TYPE_LOG: + case BF_MAP_TYPE_RATELIMIT: case BF_MAP_TYPE_CTX: // No BTF data available for these map types return NULL; @@ -194,6 +195,8 @@ int bf_map_new(struct bf_map **map, const char *name, enum bf_map_type type, [BF_MAP_TYPE_COUNTERS] = BF_BPF_MAP_TYPE_ARRAY, [BF_MAP_TYPE_PRINTER] = BF_BPF_MAP_TYPE_ARRAY, [BF_MAP_TYPE_LOG] = BF_BPF_MAP_TYPE_RINGBUF, + [BF_MAP_TYPE_RATELIMIT] = + BF_BPF_MAP_TYPE_ARRAY, // Not sure if ARRAY or RINGBUF [BF_MAP_TYPE_CTX] = BF_BPF_MAP_TYPE_ARRAY, }; @@ -310,6 +313,7 @@ static const char *_bf_map_type_to_str(enum bf_map_type type) [BF_MAP_TYPE_COUNTERS] = "BF_MAP_TYPE_COUNTERS", [BF_MAP_TYPE_PRINTER] = "BF_MAP_TYPE_PRINTER", [BF_MAP_TYPE_LOG] = "BF_MAP_TYPE_LOG", + [BF_MAP_TYPE_RATELIMIT] = "BF_MAP_TYPE_RATELIMIT", [BF_MAP_TYPE_SET] = "BF_MAP_TYPE_SET", [BF_MAP_TYPE_CTX] = "BF_MAP_TYPE_CTX", }; diff --git a/src/libbpfilter/cgen/prog/map.h b/src/libbpfilter/cgen/prog/map.h index 19f98e816..bfb6156e6 100644 --- a/src/libbpfilter/cgen/prog/map.h +++ b/src/libbpfilter/cgen/prog/map.h @@ -20,6 +20,7 @@ enum bf_map_type BF_MAP_TYPE_COUNTERS, BF_MAP_TYPE_PRINTER, BF_MAP_TYPE_LOG, + BF_MAP_TYPE_RATELIMIT, BF_MAP_TYPE_SET, BF_MAP_TYPE_CTX, _BF_MAP_TYPE_MAX, diff --git a/src/libbpfilter/cgen/program.c b/src/libbpfilter/cgen/program.c index e9d29e5db..3a1ea5b45 100644 --- a/src/libbpfilter/cgen/program.c +++ b/src/libbpfilter/cgen/program.c @@ -61,6 +61,7 @@ #define _BF_COUNTER_MAP_NAME "bf_cmap" #define _BF_PRINTER_MAP_NAME "bf_pmap" #define _BF_LOG_MAP_NAME "bf_lmap" +#define _BF_RATELIMIT_MAP_NAME "bf_rmap" static inline size_t _bf_round_next_power_of_2(size_t value) { @@ -433,6 +434,10 @@ static int _bf_program_fixup(struct bf_program *program, insn_type = BF_FIXUP_INSN_IMM; value = program->handle->lmap->fd; break; + case BF_FIXUP_TYPE_RATELIMIT_MAP_FD: + insn_type = BF_FIXUP_INSN_IMM; + value = program->handle->rmap->fd; + break; case BF_FIXUP_TYPE_SET_MAP_FD: { const struct bf_set_group *group = _bf_program_find_set_group(program, fixup->attr.set_ptr); @@ -900,6 +905,32 @@ static int _bf_program_load_log_map(struct bf_program *program) return 0; } +static int _bf_program_load_ratelimit_map(struct bf_program *program) +{ + _cleanup_free_ void *pstr = NULL; + uint32_t key = 0; + struct bf_ratelimit val = {.current = 0, .last_time = 0}; + int r; + + assert(program); + + r = bf_map_new(&program->handle->rmap, _BF_RATELIMIT_MAP_NAME, + BF_MAP_TYPE_RATELIMIT, sizeof(uint32_t), + sizeof(struct bf_ratelimit), 1); + if (r) + return bf_err_r(r, "failed to create the ratelimit bf_map object"); + + r = bf_map_set_elem(program->handle->rmap, &key, &val); + if (r) + return bf_err_r(r, "failed to set ratelimit map elem"); + + r = _bf_program_fixup(program, BF_FIXUP_TYPE_RATELIMIT_MAP_FD); + if (r) + return bf_err_r(r, "failed to fixup ratelimit map FD"); + + return 0; +} + /** * @brief Load set maps, one BPF map per `bf_set_group`. * @@ -1001,6 +1032,10 @@ int bf_program_load(struct bf_program *prog) if (r) return bf_err_r(r, "failed to load the log map"); + r = _bf_program_load_ratelimit_map(prog); + if (r) + return bf_err_r(r, "failed to load the ratelimit map"); + if (bf_ctx_is_verbose(BF_VERBOSE_DEBUG)) { log_buf = malloc(_BF_LOG_BUF_SIZE); if (!log_buf) { diff --git a/src/libbpfilter/cgen/program.h b/src/libbpfilter/cgen/program.h index 0ead98162..3c8b52f29 100644 --- a/src/libbpfilter/cgen/program.h +++ b/src/libbpfilter/cgen/program.h @@ -159,6 +159,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; \ + }) + /** * Load a specific set's file descriptor. * @@ -232,6 +244,12 @@ struct bf_program #define _free_bf_program_ __attribute__((__cleanup__(bf_program_free))) +struct bf_ratelimit +{ + uint64_t current; + uint64_t last_time; +}; + /** * @brief Allocate and initialize a new `bf_program` object. * diff --git a/src/libbpfilter/include/bpfilter/elfstub.h b/src/libbpfilter/include/bpfilter/elfstub.h index 3f40334c7..437e96ec1 100644 --- a/src/libbpfilter/include/bpfilter/elfstub.h +++ b/src/libbpfilter/include/bpfilter/elfstub.h @@ -170,6 +170,19 @@ enum bf_elfstub_id */ BF_ELFSTUB_SOCK_ADDR_LOG, + /** + * Check if `limit` packets have already been seen in the last unit of time + * + * `__u8 bf_ratelimit(void *map, __u32 limit)` + * + * **Parameters** + * - `map`: address of the ratelimit map. + * - `limit`: number of packets allowed to pass in one unit of time. + * + * **Return** 0 if in the allowed limit (inclusive), or 1 if over the limit. + */ + BF_ELFSTUB_RATELIMIT, + _BF_ELFSTUB_MAX, }; diff --git a/src/libbpfilter/include/bpfilter/matcher.h b/src/libbpfilter/include/bpfilter/matcher.h index 54786e9af..5c5fb1145 100644 --- a/src/libbpfilter/include/bpfilter/matcher.h +++ b/src/libbpfilter/include/bpfilter/matcher.h @@ -67,6 +67,8 @@ enum bf_matcher_type BF_MATCHER_META_FLOW_HASH, /// Matches packets based on flow probability (consistent per flow). BF_MATCHER_META_FLOW_PROBABILITY, + /// Matches a number of packets per unit of time + BF_MATCHER_META_RATELIMIT, /// Matches IPv4 source address. BF_MATCHER_IP4_SADDR, /// Matches IPv4 source network. diff --git a/src/libbpfilter/matcher.c b/src/libbpfilter/matcher.c index cf3ba95f6..08f8c0822 100644 --- a/src/libbpfilter/matcher.c +++ b/src/libbpfilter/matcher.c @@ -408,6 +408,32 @@ static void _bf_print_probability(const void *payload) (void)fprintf(stdout, "%g%%", proba); } +static int _bf_parse_ratelimit(enum bf_matcher_type type, enum bf_matcher_op op, + void *payload, const char *raw_payload) +{ + assert(payload); + assert(raw_payload); + + int r; + + r = bf_if_index_from_str(raw_payload, (uint32_t *)payload); + if (r) { + return bf_err_r( + r, "\"%s %s\" Blabla error message something something, not '%s'", + bf_matcher_type_to_str(type), bf_matcher_op_to_str(op), + raw_payload); + } + + return 0; +} + +static void _bf_print_ratelimit(const void *payload) +{ + assert(payload); + + (void)fprintf(stdout, "%" PRIu32, *(uint32_t *)payload); +} + static int _bf_parse_mark(enum bf_matcher_type type, enum bf_matcher_op op, void *payload, const char *raw_payload) { @@ -924,6 +950,16 @@ static struct bf_matcher_meta _bf_matcher_metas[_BF_MATCHER_TYPE_MAX] = { _bf_print_probability), }, }, + [BF_MATCHER_META_RATELIMIT] = + { + .layer = BF_MATCHER_NO_LAYER, + // Not sure if we need unsupported_hooks + .ops = + { + BF_MATCHER_OPS(BF_MATCHER_EQ, sizeof(uint32_t), + _bf_parse_ratelimit, _bf_print_ratelimit), + }, + }, [BF_MATCHER_IP4_SADDR] = { .layer = BF_MATCHER_LAYER_3, @@ -1473,6 +1509,7 @@ static const char *_bf_matcher_type_strs[] = { [BF_MATCHER_META_MARK] = "meta.mark", [BF_MATCHER_META_FLOW_HASH] = "meta.flow_hash", [BF_MATCHER_META_FLOW_PROBABILITY] = "meta.flow_probability", + [BF_MATCHER_META_RATELIMIT] = "meta.ratelimit", [BF_MATCHER_IP4_SADDR] = "ip4.saddr", [BF_MATCHER_IP4_SNET] = "ip4.snet", [BF_MATCHER_IP4_DADDR] = "ip4.daddr",