Skip to content
Merged
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
4 changes: 3 additions & 1 deletion doc/usage/bfcli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ Rules are defined such as:
rule
[$MATCHER...]
[$SET...]
[log [$HEADERS]]
[log [$HEADERS] [every $FREQUENCY]]
[counter]
[mark $MARK]
$VERDICT
Expand All @@ -436,6 +436,8 @@ With:

- ``log $HEADERS``: log specific packet headers. ``$HEADERS`` is a comma-separated list of ``link`` (layer 2), ``internet`` (layer 3), and/or ``transport`` (layer 4). Only supported by packet-based hooks (XDP, TC, NF, cgroup_skb).
- ``log``: log all available data for the hook type. For packet-based hooks, this is equivalent to ``log link,internet,transport``. For ``BF_HOOK_CGROUP_SOCK_ADDR_*`` hooks, this records the process ID, process name, destination address, and destination port. Sendmsg hooks additionally include the source address.

Either form accepts an optional ``every $FREQUENCY`` suffix to rate-limit log events. ``$FREQUENCY`` is a positive number (integer or decimal) followed by a unit: ``ns``, ``us``, ``ms``, or ``s`` (e.g. ``every 1s``, ``every 500ms``, ``every 1.5s``). At most one log entry is emitted per ``$FREQUENCY`` interval per rule. Without ``every``, every match is logged.
Comment thread
qdeslandes marked this conversation as resolved.
- ``counter``: optional literal. If set, the filter will count the number of events matched by the rule. For packet-based hooks, this includes both the number of packets and the total bytes. For ``BF_HOOK_CGROUP_SOCK_ADDR_*`` hooks, this counts the number of socket operations (``connect()`` or ``sendmsg()`` calls).
- ``mark``: optional, ``$MARK`` must be a valid decimal or hexadecimal 32-bits value. If set, write the packet's marker value. This marker can be used later on in a rule (see ``meta.mark``) or with a TC filter.
- ``$VERDICT``: action taken by the rule if the packet is matched against **all** the criteria: either ``ACCEPT``, ``DROP``, ``CONTINUE``, ``NEXT``, or ``REDIRECT``.
Expand Down
9 changes: 9 additions & 0 deletions src/bfcli/lexer.l
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

%s STATE_HOOK_OPTS
%s STATE_MARK_OPTS
%s STATE_LOG_EVERY
%s STATE_REDIRECT_IFACE
%s STATE_REDIRECT_DIR
%s STATE_MATCHER_SET
Expand Down Expand Up @@ -97,6 +98,14 @@ mark { BEGIN(STATE_MARK_OPTS); return MARK; }

/* Logs */
log { return LOG; }
every { BEGIN(STATE_LOG_EVERY); return EVERY; }
<STATE_LOG_EVERY>{
{float}[a-z]+ {
BEGIN(INITIAL);
yylval.sval = strdup(yytext);
return STRING;
}
}

/* 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]*\) {
Expand Down
73 changes: 72 additions & 1 deletion src/bfcli/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <linux/in6.h>
#include <linux/if_ether.h>
#include <limits.h>
#include <math.h>

#include <bpfilter/verdict.h>
#include <bpfilter/hook.h>
Expand Down Expand Up @@ -59,16 +60,49 @@
BF_RULE_OPTION_LOG = 1 << 0,
BF_RULE_OPTION_COUNTER = 1 << 1,
BF_RULE_OPTION_MARK = 1 << 2,
BF_RULE_OPTION_LOG_RATE = 1 << 3,
};

struct bf_rule_options {
uint8_t flags;

uint8_t log;
uint64_t log_rate_ns;
bool counter;
uint32_t mark;
};

static inline int _parse_log_rate(const char *s, uint64_t *ns)
{
double multiplier;
double v;
char *end;

v = strtod(s, &end);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude: suggestion: _parse_log_rate does not guard against overflow. If the user provides a very large number (e.g., 99999999999999999s), strtod returns HUGE_VAL/infinity which passes the v <= 0.0 check, and (uint64_t)(HUGE_VAL * multiplier + 0.5) is undefined behavior (casting a non-representable float to integer). Consider adding a check:

if (!isfinite(v) || v > (double)UINT64_MAX / multiplier)
    return -1;

if (end == s || v <= 0.0)
return -1;

if (strcmp(end, "ns") == 0)
multiplier = 1.0;
else if (strcmp(end, "us") == 0)
multiplier = 1000.0;
else if (strcmp(end, "ms") == 0)
multiplier = 1000000.0;
else if (strcmp(end, "s") == 0)
multiplier = 1000000000.0;
else
return -1;

if (!isfinite(v) || v > (double)UINT64_MAX / multiplier)
return -1;

*ns = (uint64_t)(v * multiplier + 0.5);
if (*ns == 0)
return -1;

return 0;
}

struct bfc_rule_verdict {
enum bf_verdict verdict;
uint32_t redirect_ifindex;
Expand All @@ -83,6 +117,7 @@
bool bval;
uint8_t u8;
uint32_t u32;
uint64_t u64;
char *sval;
enum bf_verdict verdict;
enum bf_hook hook;
Expand All @@ -101,7 +136,7 @@
%token CHAIN
%token RULE
%token SET
%token NEGATE LOG COUNTER MARK
%token NEGATE LOG COUNTER MARK EVERY
%token REDIRECT_TOKEN
%token <sval> SET_TYPE
%token <sval> SET_RAW_PAYLOAD
Expand Down Expand Up @@ -141,6 +176,7 @@
%destructor { bf_list_free(&$$); } rules

%type <u8> log_headers
%type <u64> log_rate
%type <rule_options> rule_option
%type <rule_options> rule_options
%type <rule> rule
Expand Down Expand Up @@ -299,6 +335,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->log_rate_ns = $3.flags & BF_RULE_OPTION_LOG_RATE ? $3.log_rate_ns : 0;
rule->has_counters = $3.flags & BF_RULE_OPTION_COUNTER ? 1 : 0;

if ($3.flags & BF_RULE_OPTION_MARK)
Expand Down Expand Up @@ -336,6 +373,22 @@ rule_option : LOG
.flags = BF_RULE_OPTION_LOG,
};
}
| LOG EVERY log_rate
{
$$ = (struct bf_rule_options){
.log = BF_LOG_OPT_DEFAULT,
.log_rate_ns = $3,
.flags = BF_RULE_OPTION_LOG | BF_RULE_OPTION_LOG_RATE,
};
}
| LOG log_headers EVERY log_rate
{
$$ = (struct bf_rule_options){
.log = $2,
.log_rate_ns = $4,
.flags = BF_RULE_OPTION_LOG | BF_RULE_OPTION_LOG_RATE,
};
}
| COUNTER
{
$$ = (struct bf_rule_options){
Expand Down Expand Up @@ -363,6 +416,19 @@ rule_option : LOG
};
}

log_rate : STRING
{
_cleanup_free_ const char *rate_str = $1;
uint64_t ns;

if (_parse_log_rate(rate_str, &ns) < 0)
bf_parse_err("invalid rate '%s': expected <N>ns/us/ms/s",
rate_str);

$$ = ns;
}
;

rule_options : %empty { $$ = (struct bf_rule_options){}; }
| rule_options rule_option {
if ($2.flags & BF_RULE_OPTION_LOG) {
Expand All @@ -372,6 +438,11 @@ rule_options : %empty { $$ = (struct bf_rule_options){}; }
$1.log = $2.log;
}

if ($2.flags & BF_RULE_OPTION_LOG_RATE) {
$1.flags |= BF_RULE_OPTION_LOG_RATE;
$1.log_rate_ns = $2.log_rate_ns;
}

if ($2.flags & BF_RULE_OPTION_COUNTER) {
if ($1.flags & BF_RULE_OPTION_COUNTER)
bf_parse_err("duplicate keyword \"counter\" in rule");
Expand Down
25 changes: 23 additions & 2 deletions src/bfcli/print.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct bfc_chain_opts;
#define INET6_ADDRSTRLEN 46

#define BF_TIME_S 1000000000
#define BF_TIME_MS 1000000
#define BF_TIME_US 1000

#define BF_DUMP_HEXDUMP_LEN 8
Expand Down Expand Up @@ -262,7 +263,7 @@ void bfc_chain_dump(struct bf_chain *chain, struct bf_hookopts *hookopts,

if (rule->log) {
if (rule->log == BF_LOG_OPT_DEFAULT) {
(void)fprintf(stdout, " log\n");
(void)fprintf(stdout, " log");
} else {
uint8_t log = rule->log;

Expand All @@ -274,9 +275,29 @@ void bfc_chain_dump(struct bf_chain *chain, struct bf_hookopts *hookopts,

log &= ~BF_FLAG(hdr);
(void)fprintf(stdout, "%s%s", bf_log_opt_to_str(hdr),
log ? "," : "\n");
log ? "," : "");
}
}

if (rule->log_rate_ns) {
uint64_t r = rule->log_rate_ns;

if (r % BF_TIME_S == 0) {
(void)fprintf(stdout, " every %llus",
(unsigned long long)(r / BF_TIME_S));
} else if (r % BF_TIME_MS == 0) {
(void)fprintf(stdout, " every %llums",
(unsigned long long)(r / BF_TIME_MS));
} else if (r % BF_TIME_US == 0) {
(void)fprintf(stdout, " every %lluus",
(unsigned long long)(r / BF_TIME_US));
} else {
(void)fprintf(stdout, " every %lluns",
(unsigned long long)r);
}
}

(void)fprintf(stdout, "\n");
}

if (bf_rule_mark_is_set(rule))
Expand Down
2 changes: 2 additions & 0 deletions src/libbpfilter/cgen/fixup.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 state map file descriptor in the @c BPF_LD_MAP_FD instruction.
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,
/// Call an ELF stub.
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 @@ -109,6 +109,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, "smap", &child);
if (r)
return bf_rpack_key_err(r, "bf_handle.smap");
if (!bf_rpack_is_nil(child)) {
r = bf_map_new_from_pack(&_handle->smap, dir_fd, child);
if (r)
return bf_rpack_key_err(r, "bf_handle.smap");
}

r = bf_rpack_kv_array(node, "sets", &child);
if (r)
return bf_rpack_key_err(r, "bf_handle.sets");
Expand Down Expand Up @@ -146,6 +155,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)->smap);
bf_list_clean(&(*handle)->sets);

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

if (handle->smap) {
bf_wpack_open_object(pack, "smap");
bf_map_pack(handle->smap, pack);
bf_wpack_close_object(pack);
} else {
bf_wpack_kv_nil(pack, "smap");
}

bf_wpack_kv_list(pack, "sets", &handle->sets);

return bf_wpack_is_valid(pack) ? 0 : -EINVAL;
Expand Down Expand Up @@ -244,6 +262,15 @@ void bf_handle_dump(const struct bf_handle *handle, prefix_t *prefix)
DUMP(prefix, "lmap: struct bf_map * (NULL)");
}

if (handle->smap) {
DUMP(prefix, "smap: struct bf_map *");
bf_dump_prefix_push(prefix);
bf_map_dump(handle->smap, bf_dump_prefix_last(prefix));
bf_dump_prefix_pop(prefix);
} else {
DUMP(prefix, "smap: 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 @@ -305,6 +332,14 @@ int bf_handle_pin(struct bf_handle *handle, struct bf_lock *lock)
}
}

if (handle->smap) {
r = bf_map_pin(handle->smap, dir_fd);
if (r) {
bf_err_r(r, "failed to pin BPF state 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 @@ -348,6 +383,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->smap)
bf_map_unpin(handle->smap, dir_fd);

bf_list_foreach (&handle->sets, set_node) {
struct bf_map *map = bf_list_node_get_data(set_node);
Expand Down Expand Up @@ -477,5 +514,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->smap);
bf_list_clean(&handle->sets);
}
4 changes: 4 additions & 0 deletions src/libbpfilter/cgen/handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ struct bf_handle
/** Log map. NULL if not created. */
struct bf_map *lmap;

/** Per-rule state map. Single-entry array; value holds one `bf_rule_state`
* per rule. NULL if the chain has no logging rules. */
struct bf_map *smap;

/** List of set maps. Contains at most one map for each unique key
* format. */
bf_list sets;
Expand Down
11 changes: 11 additions & 0 deletions src/libbpfilter/cgen/prog/map.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <bpfilter/helper.h>
#include <bpfilter/logger.h>

#include "cgen/runtime.h"

#define _free_bf_btf_ __attribute__((__cleanup__(_bf_btf_free)))

static void _bf_btf_free(struct bf_btf **btf);
Expand Down Expand Up @@ -111,6 +113,13 @@ static struct bf_btf *_bf_map_make_btf(const struct bf_map *map)
btf__add_field(kbtf, "count", 1, 0, 0);
btf__add_field(kbtf, "size", 1, 64, 0);
break;
case BF_MAP_TYPE_STATE:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude: suggestion: The BTF construction for BF_MAP_TYPE_STATE creates an extra btf__add_int(kbtf, "u32", 4, 0) (the one before btf->key_type_id = ...) that produces an unreferenced BTF type entry. The BF_MAP_TYPE_COUNTERS case only pre-creates types it actually references. Consider removing the extraneous call to keep the BTF section clean.

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_rule_state",
sizeof(struct bf_rule_state));
btf__add_field(kbtf, "last_log_ts", 1, 0, 0);
break;
case BF_MAP_TYPE_PRINTER:
case BF_MAP_TYPE_SET:
case BF_MAP_TYPE_LOG:
Expand Down Expand Up @@ -199,6 +208,7 @@ int bf_map_new(struct bf_map **map, const char *name, enum bf_map_type type,
* valid bf_bpf_map_type value. */
[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,
};

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

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

/** Single-entry array map holding per-rule mutable state. The value is a
* flat array of `bf_rule_state` entries, indexed by rule position. */
BF_MAP_TYPE_STATE,

_BF_MAP_TYPE_MAX,
};

Expand Down
Loading
Loading