From 02b41ef5f8ba240889262ba90232d857b12429fd Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 17:12:25 -0700 Subject: [PATCH 1/8] lib: cgen: stub: add bf_stub_load Extract the copy-to-scratch logic from bf_stub_stx_payload into a new function with explicit parameters. bf_stub_load copies size bytes from R6 + src_offset to scratch[dst_scratch_offset], using min(src, dst) alignment for access size selection. For each chunk, the largest access width is picked where both src_off and dst_off are aligned and remaining >= width. This ensures optimal width for packet access (R6 = memory pointer) and verifier-safe width for context access (R6 = ctx pointer). bf_stub_stx_payload is rewritten as a thin wrapper around bf_stub_load. --- src/libbpfilter/cgen/stub.c | 40 +++++++++++++++++++++++-------------- src/libbpfilter/cgen/stub.h | 17 ++++++++++++++++ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/libbpfilter/cgen/stub.c b/src/libbpfilter/cgen/stub.c index e76d7112a..8c0dcfd76 100644 --- a/src/libbpfilter/cgen/stub.c +++ b/src/libbpfilter/cgen/stub.c @@ -431,40 +431,50 @@ int bf_stub_load_header(struct bf_program *program, return 0; } -int bf_stub_stx_payload(struct bf_program *program, - const struct bf_matcher_meta *meta, size_t offset) +int bf_stub_load(struct bf_program *program, size_t src_offset, size_t size, + size_t dst_offset) { assert(program); - assert(meta); - size_t remaining_size = meta->hdr_payload_size; - size_t src_offset = 0; - size_t dst_offset = offset; + size_t src_off = src_offset; + size_t dst_off = dst_offset; + size_t remaining_size = size; while (remaining_size) { int bpf_size = BPF_B; size_t copy_bytes = 1; - if (BF_ALIGNED_64(offset) && remaining_size >= 8) { + if (BF_ALIGNED_64(src_off) && BF_ALIGNED_64(dst_off) && + remaining_size >= 8) { bpf_size = BPF_DW; copy_bytes = 8; - } else if (BF_ALIGNED_32(offset) && remaining_size >= 4) { + } else if (BF_ALIGNED_32(src_off) && BF_ALIGNED_32(dst_off) && + remaining_size >= 4) { bpf_size = BPF_W; copy_bytes = 4; - } else if (BF_ALIGNED_16(offset) && remaining_size >= 2) { + } else if (BF_ALIGNED_16(src_off) && BF_ALIGNED_16(dst_off) && + remaining_size >= 2) { bpf_size = BPF_H; copy_bytes = 2; } - EMIT(program, BPF_LDX_MEM(bpf_size, BPF_REG_1, BPF_REG_6, - meta->hdr_payload_offset + src_offset)); - EMIT(program, BPF_STX_MEM(bpf_size, BPF_REG_10, BPF_REG_1, - BF_PROG_SCR_OFF(dst_offset))); + EMIT(program, BPF_LDX_MEM(bpf_size, BPF_REG_1, BPF_REG_6, src_off)); + EMIT(program, BPF_STX_MEM(bpf_size, BPF_REG_10, BPF_REG_1, dst_off)); remaining_size -= copy_bytes; - src_offset += copy_bytes; - dst_offset += copy_bytes; + src_off += copy_bytes; + dst_off += copy_bytes; } return 0; } + +int bf_stub_stx_payload(struct bf_program *program, + const struct bf_matcher_meta *meta, size_t offset) +{ + assert(program); + assert(meta); + + return bf_stub_load(program, meta->hdr_payload_offset, + meta->hdr_payload_size, BF_PROG_SCR_OFF(offset)); +} diff --git a/src/libbpfilter/cgen/stub.h b/src/libbpfilter/cgen/stub.h index e10a0b58b..e1682c72f 100644 --- a/src/libbpfilter/cgen/stub.h +++ b/src/libbpfilter/cgen/stub.h @@ -117,5 +117,22 @@ int bf_stub_rule_check_protocol(struct bf_program *program, int bf_stub_load_header(struct bf_program *program, const struct bf_matcher_meta *meta, int reg); +/** + * @brief Copy bytes from `R6 + src_offset` to `R10 + dst_offset`. + * + * The access size per iteration is determined by checking alignment of both + * source and destination offsets (both advance each iteration), picking the + * largest width where both are aligned and remaining >= width. + * + * @param program Program to emit the instructions into. Can't be NULL. + * @param src_offset Byte offset from `R6` to start reading from. + * @param size Number of bytes to copy. + * @param dst_offset Byte offset from `R10` (stack pointer) to write to. Use + * `BF_PROG_SCR_OFF()` for scratch area offsets. + * @return 0 on success, or negative error value on error. + */ +int bf_stub_load(struct bf_program *program, size_t src_offset, size_t size, + size_t dst_offset); + int bf_stub_stx_payload(struct bf_program *program, const struct bf_matcher_meta *meta, size_t offset); From c2412112032e8ccdc71ea0bd803ecee7e8baece4 Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 17:18:48 -0700 Subject: [PATCH 2/8] lib: cgen: set: extract shared set codegen helpers Extract reusable set codegen helpers from the existing packet-specific set code: - bf_set_generate_map_lookup: the 5-instruction map-lookup tail shared by both trie and hash paths (load set FD, compute key pointer, call bpf_map_lookup_elem, jump to next rule on NULL). - bf_set_generate_trie_lookup: complete LPM trie key assembly and lookup. Writes prefixlen at scratch[4], copies the address to scratch[8] via bf_stub_load, then calls bf_set_generate_map_lookup. Replaces ~30 lines of IPv4/IPv6-branching trie bytecode that would otherwise be duplicated between packet and cgroup_sock_addr flavors. The trie path in bf_matcher_generate_set is inlined since it reduces to three calls with the new helpers. The hash path calls bf_set_generate_map_lookup instead of inlining the sequence. --- src/libbpfilter/cgen/matcher/set.c | 104 +++++++++++------------------ src/libbpfilter/cgen/matcher/set.h | 39 +++++++++++ 2 files changed, 79 insertions(+), 64 deletions(-) diff --git a/src/libbpfilter/cgen/matcher/set.c b/src/libbpfilter/cgen/matcher/set.c index 2dde26f20..b7f44fc84 100644 --- a/src/libbpfilter/cgen/matcher/set.c +++ b/src/libbpfilter/cgen/matcher/set.c @@ -12,72 +12,45 @@ #include "cgen/program.h" #include "cgen/stub.h" -static int _bf_matcher_generate_set_trie(struct bf_program *program, - const struct bf_matcher *matcher) +int bf_set_generate_map_lookup(struct bf_program *program, + const struct bf_matcher *matcher, + size_t key_offset) { assert(program); assert(matcher); - const struct bf_set *set = - bf_chain_get_set_for_matcher(program->runtime.chain, matcher); - enum bf_matcher_type type = set->key[0]; - const struct bf_matcher_meta *meta = bf_matcher_get_meta(type); - int r; - - if (!set) { - return bf_err_r(-ENOENT, "set #%u not found in %s", - *(uint32_t *)bf_matcher_payload(matcher), - program->runtime.chain->name); - } - - r = bf_stub_rule_check_protocol(program, meta); - if (r) - return bf_err_r(r, "failed to check for protocol"); - - r = bf_stub_load_header(program, meta, BPF_REG_6); - if (r) - return bf_err_r(r, "failed to load protocol header into BPF_REG_6"); - - if (BF_FLAG(type) & (BF_FLAGS(BF_MATCHER_IP4_SNET, BF_MATCHER_IP4_DNET))) { - EMIT(program, BPF_MOV64_IMM(BPF_REG_1, 32)); - EMIT(program, - BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_1, BF_PROG_SCR_OFF(4))); - EMIT(program, BPF_LDX_MEM(BPF_W, BPF_REG_1, BPF_REG_6, - meta->hdr_payload_offset)); - EMIT(program, - BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_1, BF_PROG_SCR_OFF(8))); - } else if (BF_FLAG(type) & - (BF_FLAGS(BF_MATCHER_IP6_SNET, BF_MATCHER_IP6_DNET))) { - EMIT(program, BPF_MOV64_IMM(BPF_REG_1, 128)); - EMIT(program, - BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_1, BF_PROG_SCR_OFF(4))); - - EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6, - meta->hdr_payload_offset)); - EMIT(program, - BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_1, BF_PROG_SCR_OFF(8))); - - EMIT(program, BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6, - meta->hdr_payload_offset + 8)); - EMIT(program, - BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_1, BF_PROG_SCR_OFF(16))); - } else { - return bf_err_r(-EINVAL, - "set key '%s' (%d) should not use a LPM trie map", - bf_matcher_type_to_str(type), type); - } - EMIT_LOAD_SET_FD_FIXUP(program, BPF_REG_1, *(uint32_t *)bf_matcher_payload(matcher)); EMIT(program, BPF_MOV64_REG(BPF_REG_2, BPF_REG_10)); - EMIT(program, BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, BF_PROG_SCR_OFF(4))); + EMIT(program, BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, key_offset)); EMIT(program, BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem)); // Jump to the next rule if map_lookup_elem returned 0 EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 0)); + return 0; } +int bf_set_generate_trie_lookup(struct bf_program *program, + const struct bf_matcher *matcher, + size_t src_offset, size_t addr_size) +{ + int r; + + assert(program); + assert(matcher); + + EMIT(program, BPF_MOV64_IMM(BPF_REG_1, (uint32_t)(addr_size * 8))); + EMIT(program, + BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_1, BF_PROG_SCR_OFF(4))); + + r = bf_stub_load(program, src_offset, addr_size, BF_PROG_SCR_OFF(8)); + if (r) + return r; + + return bf_set_generate_map_lookup(program, matcher, BF_PROG_SCR_OFF(4)); +} + int bf_matcher_generate_set(struct bf_program *program, const struct bf_matcher *matcher) { @@ -95,8 +68,20 @@ int bf_matcher_generate_set(struct bf_program *program, program->runtime.chain->name); } - if (set->use_trie) - return _bf_matcher_generate_set_trie(program, matcher); + if (set->use_trie) { + const struct bf_matcher_meta *meta = bf_matcher_get_meta(set->key[0]); + + r = bf_stub_rule_check_protocol(program, meta); + if (r) + return bf_err_r(r, "failed to check for protocol"); + + r = bf_stub_load_header(program, meta, BPF_REG_6); + if (r) + return bf_err_r(r, "failed to load protocol header into BPF_REG_6"); + + return bf_set_generate_trie_lookup( + program, matcher, meta->hdr_payload_offset, meta->hdr_payload_size); + } // Ensure the packet uses the required protocols for (size_t i = 0; i < set->n_comps; ++i) { @@ -136,14 +121,5 @@ int bf_matcher_generate_set(struct bf_program *program, offset += meta->hdr_payload_size; } - EMIT_LOAD_SET_FD_FIXUP(program, BPF_REG_1, - *(uint32_t *)bf_matcher_payload(matcher)); - EMIT(program, BPF_MOV64_REG(BPF_REG_2, BPF_REG_10)); - EMIT(program, BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, BF_PROG_SCR_OFF(0))); - EMIT(program, BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem)); - - // Jump to the next rule if map_lookup_elem returned 0 - EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 0)); - - return 0; + return bf_set_generate_map_lookup(program, matcher, 0); } diff --git a/src/libbpfilter/cgen/matcher/set.h b/src/libbpfilter/cgen/matcher/set.h index ecfaa8713..aaf03e7be 100644 --- a/src/libbpfilter/cgen/matcher/set.h +++ b/src/libbpfilter/cgen/matcher/set.h @@ -5,8 +5,47 @@ #pragma once +#include + struct bf_matcher; struct bf_program; +/** + * @brief Generate the map-lookup sequence shared by all set codegen paths. + * + * Emits: load set FD, compute key pointer, call `bpf_map_lookup_elem`, + * jump to next rule if the lookup returns NULL. + * + * @param program Program to emit into. Can't be NULL. + * @param matcher Set matcher (carries the set index in its payload). + * Can't be NULL. + * @param key_offset Byte offset from `R10` (stack pointer) where the key + * starts. Use `BF_PROG_SCR_OFF()` for scratch area offsets. + * @return 0 on success, or negative errno on error. + */ +int bf_set_generate_map_lookup(struct bf_program *program, + const struct bf_matcher *matcher, + size_t key_offset); + +/** + * @brief Generate a complete LPM trie key and map lookup. + * + * Writes prefixlen (`addr_size * 8`) at `scratch[4]`, copies `addr_size` bytes + * from `R6 + src_offset` to `scratch[8]` via `bf_stub_load`, then calls + * `bf_set_generate_map_lookup` with key at `scratch[4]`. + * + * `R6` must already point to the base of the data (header pointer for + * packet flavors, ctx pointer for `cgroup_sock_addr`). + * + * @param program Program to emit into. Can't be NULL. + * @param matcher Set matcher (carries the set index). Can't be NULL. + * @param src_offset Byte offset from `R6` where the address starts. + * @param addr_size Size of the address in bytes (4 for IPv4, 16 for IPv6). + * @return 0 on success, or negative errno on error. + */ +int bf_set_generate_trie_lookup(struct bf_program *program, + const struct bf_matcher *matcher, + size_t src_offset, size_t addr_size); + int bf_matcher_generate_set(struct bf_program *program, const struct bf_matcher *matcher); From f88971ed79e96a2d2e185a954cf916ea318849f1 Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 17:53:04 -0700 Subject: [PATCH 3/8] lib: cgen: program: hoist set protocol checks into dedup loop For BF_MATCHER_SET, iterate set key components through the same checked_layers dedup logic as regular matchers. Extract _bf_program_check_proto to share the check between both paths. --- src/libbpfilter/cgen/matcher/set.c | 19 --------- src/libbpfilter/cgen/program.c | 64 +++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/libbpfilter/cgen/matcher/set.c b/src/libbpfilter/cgen/matcher/set.c index b7f44fc84..63538525f 100644 --- a/src/libbpfilter/cgen/matcher/set.c +++ b/src/libbpfilter/cgen/matcher/set.c @@ -71,10 +71,6 @@ int bf_matcher_generate_set(struct bf_program *program, if (set->use_trie) { const struct bf_matcher_meta *meta = bf_matcher_get_meta(set->key[0]); - r = bf_stub_rule_check_protocol(program, meta); - if (r) - return bf_err_r(r, "failed to check for protocol"); - r = bf_stub_load_header(program, meta, BPF_REG_6); if (r) return bf_err_r(r, "failed to load protocol header into BPF_REG_6"); @@ -83,21 +79,6 @@ int bf_matcher_generate_set(struct bf_program *program, program, matcher, meta->hdr_payload_offset, meta->hdr_payload_size); } - // Ensure the packet uses the required protocols - for (size_t i = 0; i < set->n_comps; ++i) { - enum bf_matcher_type type = set->key[i]; - const struct bf_matcher_meta *meta = bf_matcher_get_meta(type); - - if (!meta) { - return bf_err_r(-ENOENT, "meta for '%s' not found", - bf_matcher_type_to_str(type)); - } - - r = bf_stub_rule_check_protocol(program, meta); - if (r) - return bf_err_r(r, "failed to check for protocol"); - } - // Generate the bytecode to build the set key for (size_t i = 0; i < set->n_comps; ++i) { enum bf_matcher_type type = set->key[i]; diff --git a/src/libbpfilter/cgen/program.c b/src/libbpfilter/cgen/program.c index 84f8d7b5f..6aeadffce 100644 --- a/src/libbpfilter/cgen/program.c +++ b/src/libbpfilter/cgen/program.c @@ -283,11 +283,38 @@ static int _bf_program_fixup(struct bf_program *program, return 0; } +static int _bf_program_check_proto(struct bf_program *program, + enum bf_matcher_type type, + uint32_t *checked_layers) +{ + assert(program); + assert(checked_layers); + + const struct bf_matcher_meta *meta = bf_matcher_get_meta(type); + + if (!meta) + return bf_err_r(-EINVAL, "missing meta for matcher type %d", type); + + if (*checked_layers & BF_FLAG(meta->layer)) + return 0; + + if (meta->layer == BF_MATCHER_LAYER_2 || + meta->layer == BF_MATCHER_LAYER_3 || + meta->layer == BF_MATCHER_LAYER_4) { + int r = bf_stub_rule_check_protocol(program, meta); + if (r) + return r; + *checked_layers |= BF_FLAG(meta->layer); + } + + return 0; +} + static int _bf_program_generate_rule(struct bf_program *program, struct bf_rule *rule) { uint32_t checked_layers = 0; - int r; + int r = 0; assert(program); assert(rule); @@ -298,28 +325,27 @@ static int _bf_program_generate_rule(struct bf_program *program, bf_list_foreach (&rule->matchers, matcher_node) { struct bf_matcher *matcher = bf_list_node_get_data(matcher_node); - const struct bf_matcher_meta *meta = - bf_matcher_get_meta(bf_matcher_get_type(matcher)); - if (bf_matcher_get_type(matcher) == BF_MATCHER_SET) - continue; + if (bf_matcher_get_type(matcher) == BF_MATCHER_SET) { + const struct bf_set *set = + bf_chain_get_set_for_matcher(program->runtime.chain, matcher); - if (!meta) { - return bf_err_r(-EINVAL, "missing meta for matcher type %d", - bf_matcher_get_type(matcher)); - } - - if (checked_layers & BF_FLAG(meta->layer)) - continue; + if (!set) { + return bf_err_r(-ENOENT, "rule %u references non-existent set", + rule->index); + } - if (meta->layer == BF_MATCHER_LAYER_2 || - meta->layer == BF_MATCHER_LAYER_3 || - meta->layer == BF_MATCHER_LAYER_4) { - r = bf_stub_rule_check_protocol(program, meta); - if (r) - return r; - checked_layers |= BF_FLAG(meta->layer); + for (size_t i = 0; i < set->n_comps && !r; ++i) { + r = _bf_program_check_proto(program, set->key[i], + &checked_layers); + } + } else { + r = _bf_program_check_proto(program, bf_matcher_get_type(matcher), + &checked_layers); } + + if (r) + return r; } bf_list_foreach (&rule->matchers, matcher_node) { From a0ce260c9b7600ccc516faed9cf76c8191100550 Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 18:10:23 -0700 Subject: [PATCH 4/8] lib: chain: validate set components per hook and layer Check each set key component against the chain's hook and track set components in the per-layer compatibility check. Extract _bf_rule_check_layer to share the logic between regular matchers and set components. --- src/libbpfilter/chain.c | 103 ++++++++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 20 deletions(-) diff --git a/src/libbpfilter/chain.c b/src/libbpfilter/chain.c index 4c7f47059..964cc82e3 100644 --- a/src/libbpfilter/chain.c +++ b/src/libbpfilter/chain.c @@ -60,6 +60,37 @@ static int _bf_rule_references_empty_set(const struct bf_chain *chain, return 0; } +/** + * @brief Check a single matcher type against per-layer header tracking. + * + * @param type Matcher type to check. + * @param layer_hdr_id Per-layer header ID tracking array. + * @return 0 if compatible, 1 if incompatible, negative errno on error. + */ +static int _bf_rule_check_layer(enum bf_matcher_type type, + uint32_t layer_hdr_id[_BF_MATCHER_LAYER_MAX]) +{ + const struct bf_matcher_meta *meta = bf_matcher_get_meta(type); + + if (!meta) { + return bf_err_r(-EINVAL, "missing meta for '%s'", + bf_matcher_type_to_str(type)); + } + + if (meta->layer <= BF_MATCHER_NO_LAYER) + return 0; + + if (layer_hdr_id[meta->layer] == UINT32_MAX) { + layer_hdr_id[meta->layer] = meta->hdr_id; + return 0; + } + + if (layer_hdr_id[meta->layer] != meta->hdr_id) + return 1; + + return 0; +} + /** * @brief Check if a rule has matchers on the same layer but different * protocols. @@ -68,14 +99,17 @@ static int _bf_rule_references_empty_set(const struct bf_chain *chain, * address can never match a packet (a packet is either IPv4 or IPv6, not * both). Same for layer 4 (e.g. TCP vs UDP). Such rules should be disabled. * + * @param chain Chain containing the sets list. * @param rule Rule to check. * @return 0 if no conflict, 1 if the rule contains incompatible matchers, or * a negative errno value on failure. */ -static int _bf_rule_has_incompatible_matchers(const struct bf_rule *rule) +static int _bf_rule_has_incompatible_matchers(const struct bf_chain *chain, + const struct bf_rule *rule) { uint32_t layer_hdr_id[_BF_MATCHER_LAYER_MAX]; + assert(chain); assert(rule); for (int i = 0; i < _BF_MATCHER_LAYER_MAX; ++i) @@ -83,26 +117,27 @@ static int _bf_rule_has_incompatible_matchers(const struct bf_rule *rule) bf_list_foreach (&rule->matchers, matcher_node) { struct bf_matcher *matcher = bf_list_node_get_data(matcher_node); - const struct bf_matcher_meta *meta; - - if (bf_matcher_get_type(matcher) == BF_MATCHER_SET) - continue; - - meta = bf_matcher_get_meta(bf_matcher_get_type(matcher)); - if (!meta) { - return bf_err_r( - -ENOENT, "missing bf_matcher_meta for %s", - bf_matcher_type_to_str(bf_matcher_get_type(matcher))); - } - if (meta->layer <= BF_MATCHER_NO_LAYER) - continue; - - if (layer_hdr_id[meta->layer] == UINT32_MAX) { - layer_hdr_id[meta->layer] = meta->hdr_id; - continue; + int r = 0; + + if (bf_matcher_get_type(matcher) == BF_MATCHER_SET) { + const struct bf_set *set = + bf_chain_get_set_for_matcher(chain, matcher); + + if (!set) { + return bf_err_r(-ENOENT, "rule %u references non-existent set", + rule->index); + } + + for (size_t i = 0; i < set->n_comps && !r; ++i) + r = _bf_rule_check_layer(set->key[i], layer_hdr_id); + } else { + r = _bf_rule_check_layer(bf_matcher_get_type(matcher), + layer_hdr_id); } - if (layer_hdr_id[meta->layer] != meta->hdr_id) { + if (r < 0) + return r; + if (r) { bf_warn( "rule %u has incompatible matchers on the same layer, rule will be disabled", rule->index); @@ -125,7 +160,7 @@ int _bf_chain_check_rule(struct bf_chain *chain, struct bf_rule *rule) rule->disabled = r; if (!rule->disabled) { - r = _bf_rule_has_incompatible_matchers(rule); + r = _bf_rule_has_incompatible_matchers(chain, rule); if (r < 0) return r; @@ -170,6 +205,34 @@ int _bf_chain_check_rule(struct bf_chain *chain, struct bf_rule *rule) bf_matcher_type_to_str(bf_matcher_get_type(matcher)), bf_hook_to_str(chain->hook)); } + + if (bf_matcher_get_type(matcher) == BF_MATCHER_SET) { + const struct bf_set *set = + bf_chain_get_set_for_matcher(chain, matcher); + + if (!set) { + return bf_err_r(-ENOENT, "rule %u references non-existent set", + rule->index); + } + + for (size_t i = 0; i < set->n_comps; ++i) { + const struct bf_matcher_meta *comp_meta = + bf_matcher_get_meta(set->key[i]); + + if (!comp_meta) { + return bf_err_r(-EINVAL, + "missing meta for set component '%s'", + bf_matcher_type_to_str(set->key[i])); + } + + if (comp_meta->unsupported_hooks & BF_FLAG(chain->hook)) { + return bf_err_r(-ENOTSUP, + "set component '%s' not compatible with %s", + bf_matcher_type_to_str(set->key[i]), + bf_hook_to_str(chain->hook)); + } + } + } } return 0; From 9a79cd3b49c0c2efdc3bd866ed283095f9e4d3ab Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 19:01:17 -0700 Subject: [PATCH 5/8] lib: cgen: packet: move set codegen from set.c to packet.c --- src/libbpfilter/cgen/matcher/packet.c | 62 ++++++++++++++++++++++++++- src/libbpfilter/cgen/matcher/set.c | 56 ------------------------ src/libbpfilter/cgen/matcher/set.h | 3 -- 3 files changed, 61 insertions(+), 60 deletions(-) diff --git a/src/libbpfilter/cgen/matcher/packet.c b/src/libbpfilter/cgen/matcher/packet.c index 3974edba9..8ea8526ef 100644 --- a/src/libbpfilter/cgen/matcher/packet.c +++ b/src/libbpfilter/cgen/matcher/packet.c @@ -14,9 +14,11 @@ #include #include +#include #include #include #include +#include #include "cgen/matcher/cmp.h" #include "cgen/matcher/meta.h" @@ -296,6 +298,64 @@ static int _bf_matcher_pkt_generate_ip6_dscp(struct bf_program *program, return 0; } +static int _bf_matcher_pkt_generate_set(struct bf_program *program, + const struct bf_matcher *matcher) +{ + assert(program); + assert(matcher); + + const struct bf_set *set = + bf_chain_get_set_for_matcher(program->runtime.chain, matcher); + size_t offset = 0; + int r; + + if (!set) { + return bf_err_r(-ENOENT, "set #%u not found in %s", + *(uint32_t *)bf_matcher_payload(matcher), + program->runtime.chain->name); + } + + if (set->use_trie) { + const struct bf_matcher_meta *meta = bf_matcher_get_meta(set->key[0]); + + if (!meta) { + return bf_err_r(-EINVAL, "missing meta for '%s'", + bf_matcher_type_to_str(set->key[0])); + } + + r = bf_stub_load_header(program, meta, BPF_REG_6); + if (r) + return bf_err_r(r, "failed to load protocol header into BPF_REG_6"); + + return bf_set_generate_trie_lookup( + program, matcher, meta->hdr_payload_offset, meta->hdr_payload_size); + } + + for (size_t i = 0; i < set->n_comps; ++i) { + enum bf_matcher_type type = set->key[i]; + const struct bf_matcher_meta *meta = bf_matcher_get_meta(type); + + if (!meta) { + return bf_err_r(-EINVAL, "missing meta for '%s'", + bf_matcher_type_to_str(type)); + } + + r = bf_stub_load_header(program, meta, BPF_REG_6); + if (r) + return bf_err_r(r, "failed to load protocol header into BPF_REG_6"); + + r = bf_stub_stx_payload(program, meta, offset); + if (r) { + return bf_err_r(r, + "failed to generate bytecode to load packet data"); + } + + offset += meta->hdr_payload_size; + } + + return bf_set_generate_map_lookup(program, matcher, BF_PROG_SCR_OFF(0)); +} + int bf_matcher_generate_packet(struct bf_program *program, const struct bf_matcher *matcher) { @@ -349,7 +409,7 @@ int bf_matcher_generate_packet(struct bf_program *program, case BF_MATCHER_IP6_DSCP: return _bf_matcher_pkt_generate_ip6_dscp(program, matcher, meta); case BF_MATCHER_SET: - return bf_matcher_generate_set(program, matcher); + return _bf_matcher_pkt_generate_set(program, matcher); default: return bf_err_r(-EINVAL, "unknown matcher type %d", bf_matcher_get_type(matcher)); diff --git a/src/libbpfilter/cgen/matcher/set.c b/src/libbpfilter/cgen/matcher/set.c index 63538525f..1ec9d40ba 100644 --- a/src/libbpfilter/cgen/matcher/set.c +++ b/src/libbpfilter/cgen/matcher/set.c @@ -5,9 +5,7 @@ #include "cgen/matcher/set.h" -#include #include -#include #include "cgen/program.h" #include "cgen/stub.h" @@ -50,57 +48,3 @@ int bf_set_generate_trie_lookup(struct bf_program *program, return bf_set_generate_map_lookup(program, matcher, BF_PROG_SCR_OFF(4)); } - -int bf_matcher_generate_set(struct bf_program *program, - const struct bf_matcher *matcher) -{ - assert(program); - assert(matcher); - - const struct bf_set *set = - bf_chain_get_set_for_matcher(program->runtime.chain, matcher); - size_t offset = 0; - int r; - - if (!set) { - return bf_err_r(-ENOENT, "set #%u not found in %s", - *(uint32_t *)bf_matcher_payload(matcher), - program->runtime.chain->name); - } - - if (set->use_trie) { - const struct bf_matcher_meta *meta = bf_matcher_get_meta(set->key[0]); - - r = bf_stub_load_header(program, meta, BPF_REG_6); - if (r) - return bf_err_r(r, "failed to load protocol header into BPF_REG_6"); - - return bf_set_generate_trie_lookup( - program, matcher, meta->hdr_payload_offset, meta->hdr_payload_size); - } - - // Generate the bytecode to build the set key - for (size_t i = 0; i < set->n_comps; ++i) { - enum bf_matcher_type type = set->key[i]; - const struct bf_matcher_meta *meta = bf_matcher_get_meta(type); - - if (!meta) { - return bf_err_r(-ENOENT, "meta for '%s' not found", - bf_matcher_type_to_str(type)); - } - - r = bf_stub_load_header(program, meta, BPF_REG_6); - if (r) - return bf_err_r(r, "failed to load protocol header into BPF_REG_6"); - - r = bf_stub_stx_payload(program, meta, offset); - if (r) { - return bf_err_r(r, - "failed to generate bytecode to load packet data"); - } - - offset += meta->hdr_payload_size; - } - - return bf_set_generate_map_lookup(program, matcher, 0); -} diff --git a/src/libbpfilter/cgen/matcher/set.h b/src/libbpfilter/cgen/matcher/set.h index aaf03e7be..a85bd41fb 100644 --- a/src/libbpfilter/cgen/matcher/set.h +++ b/src/libbpfilter/cgen/matcher/set.h @@ -46,6 +46,3 @@ int bf_set_generate_map_lookup(struct bf_program *program, int bf_set_generate_trie_lookup(struct bf_program *program, const struct bf_matcher *matcher, size_t src_offset, size_t addr_size); - -int bf_matcher_generate_set(struct bf_program *program, - const struct bf_matcher *matcher); From 392e24899be3dc6a017b2bd5bb6cde17ebee4483 Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 19:13:58 -0700 Subject: [PATCH 6/8] lib: cgen: add cgroup_sock_addr set codegen --- src/libbpfilter/cgen/cgroup_sock_addr.c | 99 +++++++++++++++++++++++++ src/libbpfilter/matcher.c | 1 - 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/libbpfilter/cgen/cgroup_sock_addr.c b/src/libbpfilter/cgen/cgroup_sock_addr.c index 7d4c4d841..321bf394d 100644 --- a/src/libbpfilter/cgen/cgroup_sock_addr.c +++ b/src/libbpfilter/cgen/cgroup_sock_addr.c @@ -14,15 +14,19 @@ #include #include +#include #include #include #include #include +#include #include #include "cgen/matcher/cmp.h" #include "cgen/matcher/meta.h" +#include "cgen/matcher/set.h" #include "cgen/program.h" +#include "cgen/stub.h" #include "cgen/swich.h" #include "filter.h" @@ -204,6 +208,99 @@ static int _bf_cgroup_sock_addr_generate_port(struct bf_program *program, bf_matcher_payload(matcher), 2, BPF_REG_1); } +static size_t _bf_cgroup_sock_addr_ctx_offset(enum bf_matcher_type type) +{ + switch (type) { + case BF_MATCHER_IP4_SADDR: + case BF_MATCHER_IP4_SNET: + return offsetof(struct bpf_sock_addr, msg_src_ip4); + case BF_MATCHER_IP4_DADDR: + case BF_MATCHER_IP4_DNET: + return offsetof(struct bpf_sock_addr, user_ip4); + case BF_MATCHER_IP6_SADDR: + case BF_MATCHER_IP6_SNET: + return offsetof(struct bpf_sock_addr, msg_src_ip6); + case BF_MATCHER_IP6_DADDR: + case BF_MATCHER_IP6_DNET: + return offsetof(struct bpf_sock_addr, user_ip6); + case BF_MATCHER_IP4_PROTO: + return offsetof(struct bpf_sock_addr, protocol); + case BF_MATCHER_TCP_DPORT: + case BF_MATCHER_UDP_DPORT: + return offsetof(struct bpf_sock_addr, user_port); + default: + return (size_t)-1; + } +} + +static int _bf_cgroup_sock_addr_generate_set(struct bf_program *program, + const struct bf_matcher *matcher) +{ + assert(program); + assert(matcher); + + const struct bf_set *set = + bf_chain_get_set_for_matcher(program->runtime.chain, matcher); + size_t offset = 0; + int r; + + if (!set) { + return bf_err_r(-ENOENT, "set #%u not found in %s", + *(uint32_t *)bf_matcher_payload(matcher), + program->runtime.chain->name); + } + + if (set->use_trie) { + const struct bf_matcher_meta *meta = bf_matcher_get_meta(set->key[0]); + size_t ctx_off = _bf_cgroup_sock_addr_ctx_offset(set->key[0]); + + if (!meta) { + return bf_err_r(-EINVAL, "missing meta for set component '%s'", + bf_matcher_type_to_str(set->key[0])); + } + + if (ctx_off == (size_t)-1) { + return bf_err_r( + -ENOTSUP, + "set component '%s' not supported for cgroup_sock_addr", + bf_matcher_type_to_str(set->key[0])); + } + + return bf_set_generate_trie_lookup(program, matcher, ctx_off, + meta->hdr_payload_size); + } + + for (size_t i = 0; i < set->n_comps; ++i) { + enum bf_matcher_type type = set->key[i]; + const struct bf_matcher_meta *meta = bf_matcher_get_meta(type); + size_t ctx_off = _bf_cgroup_sock_addr_ctx_offset(type); + + if (!meta) { + return bf_err_r(-EINVAL, "missing meta for set component '%s'", + bf_matcher_type_to_str(type)); + } + + if (ctx_off == (size_t)-1) { + return bf_err_r( + -ENOTSUP, + "set component '%s' not supported for cgroup_sock_addr", + bf_matcher_type_to_str(type)); + } + + /* For ports, `hdr_payload_size` is 2 but `user_port` is a + * 4-byte `__u32`. The BPF verifier rewrites this 2-byte narrow + * ctx read to extract the correct NBO port value. */ + r = bf_stub_load(program, ctx_off, meta->hdr_payload_size, + BF_PROG_SCR_OFF(offset)); + if (r) + return r; + + offset += meta->hdr_payload_size; + } + + return bf_set_generate_map_lookup(program, matcher, BF_PROG_SCR_OFF(0)); +} + static int _bf_cgroup_sock_addr_gen_inline_matcher(struct bf_program *program, const struct bf_matcher *matcher) @@ -248,6 +345,8 @@ _bf_cgroup_sock_addr_gen_inline_matcher(struct bf_program *program, case BF_MATCHER_TCP_DPORT: case BF_MATCHER_UDP_DPORT: return _bf_cgroup_sock_addr_generate_port(program, matcher); + case BF_MATCHER_SET: + return _bf_cgroup_sock_addr_generate_set(program, matcher); default: return bf_err_r(-ENOTSUP, "matcher '%s' not supported for cgroup_sock_addr", diff --git a/src/libbpfilter/matcher.c b/src/libbpfilter/matcher.c index 0b9d645fc..eefab6a25 100644 --- a/src/libbpfilter/matcher.c +++ b/src/libbpfilter/matcher.c @@ -1299,7 +1299,6 @@ static struct bf_matcher_meta _bf_matcher_metas[_BF_MATCHER_TYPE_MAX] = { [BF_MATCHER_SET] = { .layer = BF_MATCHER_NO_LAYER, - .unsupported_hooks = BF_FLAGS(_BF_HOOKS_CGROUP_SOCK_ADDR_ALL), }, }; From 73c0dee21d8ff24efeed8aeec45fb16f5d0d3415 Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 20:01:13 -0700 Subject: [PATCH 7/8] tests: add cgroup_sock_addr set tests --- tests/e2e/hooks/cgroup_sock_addr_connect4.sh | 26 ++++++++ tests/e2e/hooks/cgroup_sock_addr_connect6.sh | 25 ++++++++ tests/e2e/hooks/cgroup_sock_addr_sendmsg4.sh | 27 +++++++++ tests/e2e/hooks/cgroup_sock_addr_sendmsg6.sh | 25 ++++++++ tests/unit/libbpfilter/chain.c | 62 ++++++++++++++++++++ 5 files changed, 165 insertions(+) diff --git a/tests/e2e/hooks/cgroup_sock_addr_connect4.sh b/tests/e2e/hooks/cgroup_sock_addr_connect4.sh index 009e425f4..3d5c3609f 100755 --- a/tests/e2e/hooks/cgroup_sock_addr_connect4.sh +++ b/tests/e2e/hooks/cgroup_sock_addr_connect4.sh @@ -36,6 +36,16 @@ ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_C (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule ip6.daddr eq ::1 counter DROP") (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule ip6.dnet eq 2001:db8::/32 counter DROP") +# Supported sets +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule (ip4.daddr) in { 1.1.1.1; 2.2.2.2 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule (ip4.dnet) in { 192.168.1.0/24; 10.0.0.0/8 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule (tcp.dport) in { 80; 443 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule (ip4.daddr, tcp.dport) in { 1.1.1.1, 80; 2.2.2.2, 443 } counter DROP" + +# Unsupported set components +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule (ip4.saddr) in { 1.1.1.1 } counter DROP") +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4 ACCEPT rule (tcp.sport) in { 80 } counter DROP") + make_sandbox CGROUP_PATH=/sys/fs/cgroup/bftest_${_TEST_NAME} @@ -112,3 +122,19 @@ ${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNE udp4_connect ${HOST_IP_ADDR} 9990 (! udp4_connect ${HOST_IP_ADDR} 9991) ${FROM_NS} ${BFCLI} ruleset flush + +# ip4.daddr hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4{cgpath=${CGROUP_PATH}} ACCEPT rule (ip4.daddr) in { ${HOST_IP_ADDR} } DROP" +(! udp4_connect ${HOST_IP_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush + +# ip4.dnet trie set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4{cgpath=${CGROUP_PATH}} ACCEPT rule (ip4.dnet) in { 10.0.0.0/8 } DROP" +(! udp4_connect ${HOST_IP_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush + +# (ip4.daddr, udp.dport) multi-component hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4{cgpath=${CGROUP_PATH}} ACCEPT rule (ip4.daddr, udp.dport) in { ${HOST_IP_ADDR}, 9990 } DROP" +(! udp4_connect ${HOST_IP_ADDR} 9990) +udp4_connect ${HOST_IP_ADDR} 9991 +${FROM_NS} ${BFCLI} ruleset flush diff --git a/tests/e2e/hooks/cgroup_sock_addr_connect6.sh b/tests/e2e/hooks/cgroup_sock_addr_connect6.sh index 8676596b3..49c3c0689 100755 --- a/tests/e2e/hooks/cgroup_sock_addr_connect6.sh +++ b/tests/e2e/hooks/cgroup_sock_addr_connect6.sh @@ -32,6 +32,15 @@ ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_C (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6 ACCEPT rule ip4.dnet eq 10.0.0.0/8 counter DROP") (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6 ACCEPT rule ip4.proto eq tcp counter DROP") +# Supported sets +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6 ACCEPT rule (ip6.daddr) in { ::1; ::2 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6 ACCEPT rule (ip6.dnet) in { 2001:db8::/32; fd00::/8 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6 ACCEPT rule (tcp.dport) in { 80; 443 } counter DROP" + +# Unsupported set components +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6 ACCEPT rule (ip6.saddr) in { ::1 } counter DROP") +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6 ACCEPT rule (tcp.sport) in { 80 } counter DROP") + make_sandbox # Add IPv6 addresses on the veth pair @@ -125,3 +134,19 @@ ${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNE udp6_connect ${HOST_IP6_ADDR} 9990 (! udp6_connect ${HOST_IP6_ADDR} 9991) ${FROM_NS} ${BFCLI} ruleset flush + +# (ip6.daddr, udp.dport) multi-component hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6{cgpath=${CGROUP_PATH}} ACCEPT rule (ip6.daddr, udp.dport) in { ${HOST_IP6_ADDR}, 9990 } DROP" +(! udp6_connect ${HOST_IP6_ADDR} 9990) +udp6_connect ${HOST_IP6_ADDR} 9991 +${FROM_NS} ${BFCLI} ruleset flush + +# ip6.daddr hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6{cgpath=${CGROUP_PATH}} ACCEPT rule (ip6.daddr) in { ${HOST_IP6_ADDR} } DROP" +(! udp6_connect ${HOST_IP6_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush + +# ip6.dnet trie set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6{cgpath=${CGROUP_PATH}} ACCEPT rule (ip6.dnet) in { fd00::/64 } DROP" +(! udp6_connect ${HOST_IP6_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush diff --git a/tests/e2e/hooks/cgroup_sock_addr_sendmsg4.sh b/tests/e2e/hooks/cgroup_sock_addr_sendmsg4.sh index 1779919fb..f3e0426cd 100755 --- a/tests/e2e/hooks/cgroup_sock_addr_sendmsg4.sh +++ b/tests/e2e/hooks/cgroup_sock_addr_sendmsg4.sh @@ -32,6 +32,17 @@ ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_S (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule ip6.saddr eq ::1 counter DROP") (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule ip6.snet eq 2001:db8::/32 counter DROP") +# Supported sets +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule (ip4.daddr) in { 1.1.1.1; 2.2.2.2 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule (ip4.dnet) in { 10.0.0.0/8 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule (ip4.saddr) in { 10.0.0.1 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule (ip4.snet) in { 10.0.0.0/8 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule (udp.dport) in { 53; 443 } counter DROP" + +# Unsupported set components +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule (tcp.dport) in { 80 } counter DROP") +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4 ACCEPT rule (tcp.sport) in { 80 } counter DROP") + make_sandbox CGROUP_PATH=/sys/fs/cgroup/bftest_${_TEST_NAME} @@ -114,3 +125,19 @@ ${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDM udp4_sendmsg ${HOST_IP_ADDR} 9990 (! udp4_sendmsg ${HOST_IP_ADDR} 9991) ${FROM_NS} ${BFCLI} ruleset flush + +# ip4.daddr hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4{cgpath=${CGROUP_PATH}} ACCEPT rule (ip4.daddr) in { ${HOST_IP_ADDR} } DROP" +(! udp4_sendmsg ${HOST_IP_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush + +# ip4.dnet trie set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4{cgpath=${CGROUP_PATH}} ACCEPT rule (ip4.dnet) in { 10.0.0.0/8 } DROP" +(! udp4_sendmsg ${HOST_IP_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush + +# (ip4.saddr, udp.dport) multi-component hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4{cgpath=${CGROUP_PATH}} ACCEPT rule (ip4.saddr, udp.dport) in { ${NS_IP_ADDR}, 9990 } DROP" +(! udp4_sendmsg ${HOST_IP_ADDR} 9990) +udp4_sendmsg ${HOST_IP_ADDR} 9991 +${FROM_NS} ${BFCLI} ruleset flush diff --git a/tests/e2e/hooks/cgroup_sock_addr_sendmsg6.sh b/tests/e2e/hooks/cgroup_sock_addr_sendmsg6.sh index 06cc1141f..ba316d1e9 100755 --- a/tests/e2e/hooks/cgroup_sock_addr_sendmsg6.sh +++ b/tests/e2e/hooks/cgroup_sock_addr_sendmsg6.sh @@ -32,6 +32,15 @@ ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_S (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6 ACCEPT rule ip4.saddr eq 10.0.0.1 counter DROP") (! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6 ACCEPT rule ip4.snet eq 10.0.0.0/8 counter DROP") +# Supported sets +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6 ACCEPT rule (ip6.daddr) in { ::1; ::2 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6 ACCEPT rule (ip6.dnet) in { fd00::/64 } counter DROP" +${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6 ACCEPT rule (ip6.saddr, udp.dport) in { ::1, 53 } counter DROP" + +# Unsupported set components +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6 ACCEPT rule (tcp.dport) in { 80 } counter DROP") +(! ${BFCLI} ruleset set --dry-run --from-str "chain test BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6 ACCEPT rule (tcp.sport) in { 80 } counter DROP") + make_sandbox # Add IPv6 addresses on the veth pair @@ -115,3 +124,19 @@ ${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDM udp6_sendmsg ${HOST_IP6_ADDR} 9990 (! udp6_sendmsg ${HOST_IP6_ADDR} 9991) ${FROM_NS} ${BFCLI} ruleset flush + +# ip6.daddr hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6{cgpath=${CGROUP_PATH}} ACCEPT rule (ip6.daddr) in { ${HOST_IP6_ADDR} } DROP" +(! udp6_sendmsg ${HOST_IP6_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush + +# ip6.dnet trie set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6{cgpath=${CGROUP_PATH}} ACCEPT rule (ip6.dnet) in { fd00::/64 } DROP" +(! udp6_sendmsg ${HOST_IP6_ADDR} 9990) +${FROM_NS} ${BFCLI} ruleset flush + +# (ip6.saddr, udp.dport) multi-component hash set +${FROM_NS} ${BFCLI} chain set --from-str "chain c BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6{cgpath=${CGROUP_PATH}} ACCEPT rule (ip6.saddr, udp.dport) in { ${NS_IP6_ADDR}, 9990 } DROP" +(! udp6_sendmsg ${HOST_IP6_ADDR} 9990) +udp6_sendmsg ${HOST_IP6_ADDR} 9991 +${FROM_NS} ${BFCLI} ruleset flush diff --git a/tests/unit/libbpfilter/chain.c b/tests/unit/libbpfilter/chain.c index 61c75352e..1367699f9 100644 --- a/tests/unit/libbpfilter/chain.c +++ b/tests/unit/libbpfilter/chain.c @@ -186,6 +186,38 @@ static void incompatible_matchers_disable_rule(void **state) assert_true(rule->disabled); } + // L3 conflict via set: ip4.daddr set + ip6.daddr matcher. + { + _free_bf_chain_ struct bf_chain *chain = NULL; + _clean_bf_list_ bf_list sets = + bf_list_default(bf_set_free, bf_set_pack); + _clean_bf_list_ bf_list rules = + bf_list_default(bf_rule_free, bf_rule_pack); + _free_bf_set_ struct bf_set *set = NULL; + struct bf_rule *rule = NULL; + + enum bf_matcher_type key[] = {BF_MATCHER_IP4_DADDR}; + + uint32_t set_index = 0; + uint8_t ip6_addr[16] = {}; + + assert_ok(bf_set_new(&set, "s", key, ARRAY_SIZE(key))); + assert_ok(bf_set_add_elem(set, (uint8_t[4]) {10, 0, 0, 1})); + assert_ok(bf_list_add_tail(&sets, set)); + set = NULL; + + assert_ok(bf_rule_new(&rule)); + assert_ok(bf_rule_add_matcher(rule, BF_MATCHER_SET, BF_MATCHER_IN, + &set_index, sizeof(set_index))); + assert_ok(bf_rule_add_matcher(rule, BF_MATCHER_IP6_DADDR, BF_MATCHER_EQ, + ip6_addr, sizeof(ip6_addr))); + assert_ok(bf_list_add_tail(&rules, rule)); + + assert_ok(bf_chain_new(&chain, "test", BF_HOOK_TC_EGRESS, + BF_VERDICT_ACCEPT, &sets, &rules)); + assert_true(rule->disabled); + } + // No conflict: same protocol at same layer (TCP sport + TCP dport). { _free_bf_chain_ struct bf_chain *chain = NULL; @@ -228,6 +260,35 @@ static void incompatible_matchers_disable_rule(void **state) } } +static void set_component_unsupported_hook(void **state) +{ + (void)state; + + // Set component unsupported for hook: ip4.saddr on CONNECT4. + _free_bf_chain_ struct bf_chain *chain = NULL; + _clean_bf_list_ bf_list sets = bf_list_default(bf_set_free, bf_set_pack); + _clean_bf_list_ bf_list rules = bf_list_default(bf_rule_free, bf_rule_pack); + _free_bf_set_ struct bf_set *set = NULL; + struct bf_rule *rule = NULL; + + enum bf_matcher_type key[] = {BF_MATCHER_IP4_SADDR}; + + uint32_t set_index = 0; + + assert_ok(bf_set_new(&set, "s", key, ARRAY_SIZE(key))); + assert_ok(bf_set_add_elem(set, (uint8_t[4]) {10, 0, 0, 1})); + assert_ok(bf_list_add_tail(&sets, set)); + set = NULL; + + assert_ok(bf_rule_new(&rule)); + assert_ok(bf_rule_add_matcher(rule, BF_MATCHER_SET, BF_MATCHER_IN, + &set_index, sizeof(set_index))); + assert_ok(bf_list_add_tail(&rules, rule)); + + assert_err(bf_chain_new(&chain, "test", BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4, + BF_VERDICT_ACCEPT, &sets, &rules)); +} + static void get_set_by_name(void **state) { _free_bf_chain_ struct bf_chain *chain = bft_chain_dummy(true); @@ -247,6 +308,7 @@ int main(void) cmocka_unit_test(get_set_from_matcher), cmocka_unit_test(mixed_enabled_disabled_log_flag), cmocka_unit_test(incompatible_matchers_disable_rule), + cmocka_unit_test(set_component_unsupported_hook), cmocka_unit_test(get_set_by_name), }; From 5ab9f3584640ad34cdf03623ce3784fca16f1257 Mon Sep 17 00:00:00 2001 From: yaakov-stein Date: Mon, 30 Mar 2026 20:14:17 -0700 Subject: [PATCH 8/8] doc: bfcli: document cgroup_sock_addr set support and ip6 set keys --- doc/usage/bfcli.rst | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/doc/usage/bfcli.rst b/doc/usage/bfcli.rst index 2db7090e5..c72933710 100644 --- a/doc/usage/bfcli.rst +++ b/doc/usage/bfcli.rst @@ -360,7 +360,7 @@ With: .. note:: - ``BF_HOOK_CGROUP_SOCK_ADDR_*`` hooks operate on socket metadata rather than packet data. Supported matchers: ``meta.l3_proto``, ``meta.l4_proto``, ``meta.probability``, ``meta.dport``, ``ip4.daddr``, ``ip4.dnet``, ``ip4.proto``, ``ip6.daddr``, ``ip6.dnet``, ``udp.dport``. Connect hooks additionally support ``tcp.dport``. Sendmsg hooks additionally support ``ip4.saddr``, ``ip4.snet``, ``ip6.saddr``, and ``ip6.snet``. + ``BF_HOOK_CGROUP_SOCK_ADDR_*`` hooks operate on socket metadata rather than packet data. Supported matchers: ``meta.l3_proto``, ``meta.l4_proto``, ``meta.probability``, ``meta.dport``, ``ip4.daddr``, ``ip4.dnet``, ``ip4.proto``, ``ip6.daddr``, ``ip6.dnet``, ``udp.dport``. Connect hooks additionally support ``tcp.dport``. Sendmsg hooks additionally support ``ip4.saddr``, ``ip4.snet``, ``ip6.saddr``, and ``ip6.snet``. Sets are supported with the same per-component restrictions: each set key component must be a matcher supported by the chain's hook. - ``$POLICY``: action taken if no rule matches the packet, either ``ACCEPT`` forward the packet to the kernel, or ``DROP`` to discard it. Note while ``CONTINUE`` is a valid verdict for rules, it is not supported for chain policy. @@ -647,16 +647,21 @@ IPv6 - Operator - Payload - Notes - * - :rspan:`1` Source address - - :rspan:`1` ``ip6.saddr`` + * - :rspan:`2` Source address + - :rspan:`2` ``ip6.saddr`` - ``eq`` - - :rspan:`3` ``$ADDR`` - - :rspan:`3` ``$ADDR`` must be an IPv6 address composed of 8 hexadecimal numbers (abbreviations are supported). To filter on an IPv6 network (using an IPv6 address and a subnet mask), see ``ip6.snet`` or ``ip6.dnet``. + - :rspan:`1` ``$ADDR`` + - :rspan:`5` ``$ADDR`` must be an IPv6 address composed of 8 hexadecimal numbers (abbreviations are supported). To filter on an IPv6 network (using an IPv6 address and a subnet mask), see ``ip6.snet`` or ``ip6.dnet``. * - ``not`` - * - :rspan:`1` Destination address - - :rspan:`1` ``ip6.daddr`` + * - ``in`` + - ``{$ADDR[,...]}`` + * - :rspan:`2` Destination address + - :rspan:`2` ``ip6.daddr`` - ``eq`` + - :rspan:`1` ``$ADDR`` * - ``not`` + * - ``in`` + - ``{$ADDR[,...]}`` * - :rspan:`2` Source network - :rspan:`2` ``ip6.snet`` - ``eq``