Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/bfcli/lexer.l
Comment thread
SkohTV marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
%s STATE_MATCHER_ICMP_TYPE
%s STATE_MATCHER_ICMP_CODE
%s STATE_MATCHER_TCP_FLAGS
%s STATE_MATCHER_META_LIMIT

int (-|(0x))?[0-9a-zA-Z]+
float [0-9]+(\.[0-9]+)?
Expand Down Expand Up @@ -186,6 +187,16 @@ meta\.flow_hash { BEGIN(STATE_MATCHER_META_FLOW_HASH); yylval.sval = strdup(
}
}

meta\.limit { BEGIN(STATE_MATCHER_META_LIMIT); yylval.sval = strdup(yytext); return MATCHER_TYPE; }
<STATE_MATCHER_META_LIMIT>{
(eq) { yylval.sval = strdup(yytext); return MATCHER_OP; }
{int}\/[a-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; }
<STATE_MATCHER_IPV4_ADDR>{
Expand Down
1 change: 1 addition & 0 deletions src/libbpfilter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ bf_target_add_elfstubs(libbpfilter
"pkt_log"
"flow_hash"
"sock_addr_log"
"limit"
)

target_compile_definitions(libbpfilter
Expand Down
42 changes: 42 additions & 0 deletions src/libbpfilter/bpf/limit.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
*/

#include <linux/bpf.h>

#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <stddef.h>

#include "cgen/runtime.h"

#define BF_TIME_S 1000000000

__u8 bf_ratelimit(void *map, const __u16 limit, __u8 letter)
{
struct bf_ratelimit *ratelimit;
__u64 current_time_ns = bpf_ktime_get_ns();
__u32 key = 0; // NTODO: should not be 0, but dynamic (WIP)

ratelimit = bpf_map_lookup_elem(map, &key);
if (!ratelimit) {
bpf_printk("failed to fetch the rule's ratelimit");
return 1;
}

switch (letter) {
case 's':
if (current_time_ns > ratelimit->last_time + BF_TIME_S) {
ratelimit->current = 0;
ratelimit->last_time = current_time_ns;
}
break;
default:
bpf_printk("the time unit '%c' is not recognized", letter);
return 1;
}

ratelimit->current++;
return (ratelimit->current > limit);
}
2 changes: 2 additions & 0 deletions src/libbpfilter/cgen/fixup.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ enum bf_fixup_type
BF_FIXUP_TYPE_STATE_MAP_FD,
/// Set a set map file descriptor in the @c BPF_LD_MAP_FD instruction.
BF_FIXUP_TYPE_SET_MAP_FD,
/// Set the limit map file descriptor in the @c BPF_LD_MAP_FD instruction.
BF_FIXUP_TYPE_LIMIT_MAP_FD,
/// Call an ELF stub.
BF_FIXUP_ELFSTUB_CALL,
_BF_FIXUP_TYPE_MAX
Expand Down
38 changes: 38 additions & 0 deletions src/libbpfilter/cgen/handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ int bf_handle_new_from_pack(struct bf_handle **handle, struct bf_lock *lock,
return bf_rpack_key_err(r, "bf_handle.smap");
}

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");
Expand Down Expand Up @@ -156,6 +165,7 @@ void bf_handle_free(struct bf_handle **handle)
bf_map_free(&(*handle)->pmap);
bf_map_free(&(*handle)->lmap);
bf_map_free(&(*handle)->smap);
bf_map_free(&(*handle)->rmap);
bf_list_clean(&(*handle)->sets);

free(*handle);
Expand Down Expand Up @@ -209,6 +219,14 @@ int bf_handle_pack(const struct bf_handle *handle, bf_wpack_t *pack)
bf_wpack_kv_nil(pack, "smap");
}

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;
Expand Down Expand Up @@ -271,6 +289,15 @@ void bf_handle_dump(const struct bf_handle *handle, prefix_t *prefix)
DUMP(prefix, "smap: 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<bf_map>[%lu]",
bf_list_size(&handle->sets));
bf_dump_prefix_push(prefix);
Expand Down Expand Up @@ -340,6 +367,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 rate limit map");
goto err_unpin_all;
}
}

bf_list_foreach (&handle->sets, set_node) {
struct bf_map *map = bf_list_node_get_data(set_node);

Expand Down Expand Up @@ -385,6 +420,8 @@ void bf_handle_unpin(struct bf_handle *handle, struct bf_lock *lock)
bf_map_unpin(handle->lmap, dir_fd);
if (handle->smap)
bf_map_unpin(handle->smap, 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);
Expand Down Expand Up @@ -515,5 +552,6 @@ void bf_handle_unload(struct bf_handle *handle)
bf_map_free(&handle->pmap);
bf_map_free(&handle->lmap);
bf_map_free(&handle->smap);
bf_map_free(&handle->rmap);
bf_list_clean(&handle->sets);
}
3 changes: 3 additions & 0 deletions src/libbpfilter/cgen/handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ struct bf_handle
* per rule. NULL if the chain has no logging rules. */
struct bf_map *smap;

/** Rate limit 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;
Expand Down
27 changes: 27 additions & 0 deletions src/libbpfilter/cgen/matcher/meta.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,31 @@ _bf_matcher_generate_meta_flow_probability(struct bf_program *program,
return 0;
}

static int _bf_matcher_generate_meta_limit(struct bf_program *program,
const struct bf_matcher *matcher)
{
uint32_t tmp = *(uint32_t *)bf_matcher_payload(matcher);
uint16_t limit = tmp;
uint8_t letter = tmp >> 16;

EMIT_LOAD_LIMIT_FD_FIXUP(program, BPF_REG_1);
EMIT(program, BPF_MOV32_IMM(BPF_REG_2, limit));
EMIT(program, BPF_MOV32_IMM(BPF_REG_3, letter));
EMIT(program,
BPF_MOV32_IMM(BPF_REG_4, bf_program_chain_counter_idx(program)));
EMIT_FIXUP_ELFSTUB(program, BF_ELFSTUB_LIMIT);

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)
{
Expand All @@ -177,6 +202,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_LIMIT:
return _bf_matcher_generate_meta_limit(program, matcher);
case BF_MATCHER_META_MARK:
case BF_MATCHER_META_FLOW_HASH:
return bf_err_r(-ENOTSUP,
Expand Down
1 change: 1 addition & 0 deletions src/libbpfilter/cgen/packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_LIMIT:
return bf_matcher_generate_meta(program, matcher);
case BF_MATCHER_META_MARK:
case BF_MATCHER_META_FLOW_HASH:
Expand Down
3 changes: 3 additions & 0 deletions src/libbpfilter/cgen/prog/map.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ static struct bf_btf *_bf_map_make_btf(const struct bf_map *map)
case BF_MAP_TYPE_SET:
case BF_MAP_TYPE_LOG:
case BF_MAP_TYPE_CTX:
case BF_MAP_TYPE_LIMIT:
// No BTF data available for these map types
return NULL;
default:
Expand Down Expand Up @@ -209,6 +210,7 @@ int bf_map_new(struct bf_map **map, const char *name, enum bf_map_type type,
[BF_MAP_TYPE_SET] = BF_BPF_MAP_TYPE_HASH,
[BF_MAP_TYPE_CTX] = BF_BPF_MAP_TYPE_ARRAY,
[BF_MAP_TYPE_STATE] = BF_BPF_MAP_TYPE_ARRAY,
[BF_MAP_TYPE_LIMIT] = BF_BPF_MAP_TYPE_ARRAY,
};

assert(map);
Expand Down Expand Up @@ -329,6 +331,7 @@ static const char *_bf_map_type_to_str(enum bf_map_type type)
[BF_MAP_TYPE_SET] = "BF_MAP_TYPE_SET",
[BF_MAP_TYPE_CTX] = "BF_MAP_TYPE_CTX",
[BF_MAP_TYPE_STATE] = "BF_MAP_TYPE_STATE",
[BF_MAP_TYPE_LIMIT] = "BF_MAP_TYPE_LIMIT",
};

static_assert_enum_mapping(type_strs, _BF_MAP_TYPE_MAX);
Expand Down
1 change: 1 addition & 0 deletions src/libbpfilter/cgen/prog/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum bf_map_type
BF_MAP_TYPE_LOG,
BF_MAP_TYPE_SET,
BF_MAP_TYPE_CTX,
BF_MAP_TYPE_LIMIT,

/** Single-entry array map holding per-rule mutable state. The value is a
* flat array of `bf_rule_state` entries, indexed by rule position. */
Expand Down
35 changes: 34 additions & 1 deletion src/libbpfilter/cgen/program.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#define _BF_PRINTER_MAP_NAME "bf_pmap"
#define _BF_LOG_MAP_NAME "bf_lmap"
#define _BF_STATE_MAP_NAME "bf_smap"
#define _BF_LIMIT_MAP_NAME "bf_rmap"

static inline size_t _bf_round_next_power_of_2(size_t value)
{
Expand Down Expand Up @@ -441,6 +442,10 @@ static int _bf_program_fixup(struct bf_program *program,
insn_type = BF_FIXUP_INSN_IMM;
value = program->handle->smap->fd;
break;
case BF_FIXUP_TYPE_LIMIT_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);
Expand Down Expand Up @@ -975,7 +980,6 @@ static int _bf_program_load_state_map(struct bf_program *program)
int r;

assert(program);

if (!(program->runtime.chain->flags & BF_FLAG(BF_CHAIN_LOG_RATELIMIT)))
return 0;

Expand All @@ -996,6 +1000,31 @@ static int _bf_program_load_state_map(struct bf_program *program)
return 0;
}

static int _bf_program_load_limit_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_LIMIT_MAP_NAME,
BF_MAP_TYPE_LIMIT, sizeof(uint32_t),
sizeof(struct bf_ratelimit), 1);
if (r)
return bf_err_r(r, "failed to create the rate limit bf_map object");

r = bf_map_set_elem(program->handle->rmap, &key, &val);
if (r)
return bf_err_r(r, "failed to set rate limit map elem");

r = _bf_program_fixup(program, BF_FIXUP_TYPE_LIMIT_MAP_FD);
if (r)
return bf_err_r(r, "failed to fixup rate limit map FD");

return 0;
}

static uint64_t _bf_dedup_hash(const void *data, void *ctx)
{
return bf_fnv1a(data, *(const size_t *)ctx, bf_fnv1a_init());
Expand Down Expand Up @@ -1156,6 +1185,10 @@ int bf_program_load(struct bf_program *prog)
if (r)
return bf_err_r(r, "failed to load the state map");

r = _bf_program_load_limit_map(prog);
if (r)
return bf_err_r(r, "failed to load the rate limit map");

if (bf_ctx_is_verbose(BF_VERBOSE_DEBUG)) {
log_buf = malloc(_BF_LOG_BUF_SIZE);
if (!log_buf) {
Expand Down
12 changes: 12 additions & 0 deletions src/libbpfilter/cgen/program.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@
return __r; \
})

#define EMIT_LOAD_LIMIT_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_LIMIT_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.
*
Expand Down
15 changes: 15 additions & 0 deletions src/libbpfilter/cgen/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,5 +179,20 @@ struct bf_runtime
static_assert(sizeof(struct bf_runtime) % 8 == 0,
"bf_runtime should be aligned to 8 bytes");

/**
* @brief Keep timestamps to allow for rate limiting
*
*/
struct bf_ratelimit
{
/** Current timestamp (at the last call of the BPF) */
__u64 current;

/** Last time the rate limit was reset */
__u64 last_time;
};

// I don't think we need a static_assert ?

extern void *bpf_dynptr_slice(const struct bpf_dynptr *, __u32, void *, __u32);
extern int bpf_dynptr_from_xdp(struct xdp_md *, __u64, struct bpf_dynptr *);
14 changes: 14 additions & 0 deletions src/libbpfilter/include/bpfilter/elfstub.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ 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, __u16 limit, __u8 letter)`
*
* **Parameters**
* - `map`: address of the rate limit map.
* - `limit`: number of packets allowed to pass in one unit of time.
* - `letter`: the unit of time to reset the rate limit.
*
* **Return** 0 if in the allowed limit (inclusive), or 1 if over the limit.
*/
BF_ELFSTUB_LIMIT,

_BF_ELFSTUB_MAX,
};

Expand Down
Loading
Loading