Conversation
Claude review of PR #493 (683d9a7)Previous must-fix issues (NULL dereferences in Suggestions
Nits
|
8a5b67b to
1def2a9
Compare
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.
1def2a9 to
557e9ea
Compare
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.
557e9ea to
e70a6ab
Compare
| - Notes | ||
| * - :rspan:`1` Source address | ||
| - :rspan:`1` ``ip6.saddr`` | ||
| * - :rspan:`2` Source address |
There was a problem hiding this comment.
drive-by fix (document ip6.d/saddr works in sets)
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.
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.
e70a6ab to
683d9a7
Compare
| static int _bf_program_check_proto(struct bf_program *program, | ||
| enum bf_matcher_type type, | ||
| uint32_t *checked_layers) | ||
| { |
There was a problem hiding this comment.
Claude: suggestion: Missing assert(program) and assert(checked_layers) in _bf_program_check_proto. Every other static function in this file that takes a program pointer asserts on it (14 instances: _bf_program_fixup, _bf_program_generate_rule, _bf_program_generate_elfstubs, etc.). Since checked_layers is also a pointer parameter, both should be asserted per the style guide's parameter validation convention.
| { | |
| 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); |
| return offsetof(struct bpf_sock_addr, user_ip6); | ||
| case BF_MATCHER_TCP_DPORT: | ||
| case BF_MATCHER_UDP_DPORT: | ||
| case BF_MATCHER_META_DPORT: |
There was a problem hiding this comment.
Claude: suggestion: _bf_cgroup_sock_addr_ctx_offset does not handle BF_MATCHER_IP4_PROTO, which supports BF_MATCHER_IN (so it can be a set key component) and whose unsupported_hooks does not exclude IPv4 cgroup_sock_addr hooks. A set like (ip4.proto) in { 6; 17 } would pass the _bf_chain_check_rule component validation but hit the default branch here, returning (size_t)-1 and failing late at codegen with -ENOTSUP.
The error message is clear so this is not silent corruption, but it is a validation/codegen mismatch. Consider either:
- Adding
BF_MATCHER_IP4_PROTOto the switch (reading frombpf_sock_addr.protocol), or - Adding cgroup_sock_addr hooks to
BF_MATCHER_IP4_PROTO'sunsupported_hooksinmatcher.cso the mismatch is caught at chain creation time.
|
|
||
| # (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) |
There was a problem hiding this comment.
Claude: suggestion: The dry-run section tests a standalone (tcp.dport) in { 80; 443 } set, but the behavioral tests only exercise ports as part of a multi-component key ((ip4.daddr, udp.dport)). Since the port code path uses a 2-byte narrow context read from the 4-byte user_port field (as documented in the code comment at cgroup_sock_addr.c:288), a standalone port-only behavioral test would exercise this narrow-read path in isolation and increase confidence that the verifier rewriting produces the expected NBO value.
|
|
||
| if (set->use_trie) { | ||
| const struct bf_matcher_meta *meta = bf_matcher_get_meta(set->key[0]); | ||
|
|
There was a problem hiding this comment.
Claude: nit: Error code inconsistency: _bf_matcher_pkt_generate_set returns -ENOENT for the !meta case here, while _bf_cgroup_sock_addr_generate_set at cgroup_sock_addr.c:253 returns -EINVAL for the same condition. The _bf_rule_check_layer helper in chain.c also uses -ENOENT. Consider aligning on one errno and message pattern across all three paths.
| case BF_MATCHER_IP6_DADDR: | ||
| case BF_MATCHER_IP6_DNET: | ||
| return offsetof(struct bpf_sock_addr, user_ip6); | ||
| case BF_MATCHER_TCP_DPORT: |
There was a problem hiding this comment.
Claude: nit: BF_MATCHER_META_DPORT cannot appear as a set key component: bf_set_new only accepts matcher types that have BF_MATCHER_IN in their ops, and BF_MATCHER_META_DPORT only defines EQ, NE, and RANGE. This case is dead code. Consider removing it or adding a comment explaining it is kept for future-proofing.
| @@ -204,6 +208,98 @@ static int _bf_cgroup_sock_addr_generate_port(struct bf_program *program, | |||
| bf_matcher_payload(matcher), 2, BPF_REG_1); | |||
| } | |||
There was a problem hiding this comment.
Claude: nit: _bf_cgroup_sock_addr_ctx_offset is a non-trivial function mapping matcher types to bpf_sock_addr field offsets, with a sentinel return convention ((size_t)-1 for unsupported types). A brief Doxygen comment documenting the purpose, the return convention, and the handled matcher types would help maintainability. Other comparable helpers in this file (e.g., _bf_cgroup_sock_addr_generate_port) have documentation.
| } | ||
| assert(program); | ||
| assert(matcher); | ||
|
|
There was a problem hiding this comment.
Claude: nit: (uint32_t)(addr_size * 8) is safe only if addr_size is at most ~536 million, but the real constraint is addr_size <= 16 (IPv6). This invariant is implicit. A brief inline comment or assert(addr_size <= 16) would document the assumption and prevent future misuse if bf_set_generate_trie_lookup is called with unexpected sizes.
Summary
Before this change, sets (hash maps and LPM tries used for bulk matching) were only supported on packet-based hooks. This meant users who wanted to filter against a list of IPs or ports on a
CONNECT/SENDMSGhook had to create one rule per value, which scales poorly.This change adds set support for all four
CGROUP_SOCK_ADDRhooks, reusing the same BPF map infrastructure (hash maps for exact match, LPM tries for prefix match) that packet hooks already use.Refactoring: make set codegen composable/generic
Set codegen previously lived entirely in
set.cand was tightly coupled to packet-based header loading. To reuse it forCGROUP_SOCK_ADDRhooks, we extracted the generic pieces (copy field to scratch, map/trie lookup) into shared helpers and moved the packet-specific logic intopacket.c. Protocol checks and layer conflict detection were also hoisted out of set codegen intoprogram.c's existing dedup loop, so they now cover set components the same way they cover individual matchers.As a drive-by, set components are now validated individually against the hook's supported matchers.
CGROUP_SOCK_ADDRset codegenWith the shared helpers in place,
CGROUP_SOCK_ADDRset codegen maps each key component to itsbpf_sock_addrcontext field offset, copies the field to the scratch buffer, and calls the shared map lookup. No header parsing needed —r6already points to the context.Why can't we use the current set codegen?
Packet-based hooks receive a pointer to raw packet memory. The BPF program parses headers layer by layer (L2 -> L3 -> L4), and
r6points to the current header being inspected.CGROUP_SOCK_ADDRhooks receive abpf_sock_addrcontext struct instead. The kernel pre-extracts socket metadata into typed fields:This difference has two consequences for set codegen:
No header parsing needed.
r6already points to the context. Instead of loading a header pointer and extracting fields from parsed packet data, we read directly from context field offsets (r6 + offsetof(bpf_sock_addr, field)).BPF verifier constraints on access width. Packet memory can be read at any width. Context struct fields must be read at widths that respect field alignment. The verifier rejects misaligned reads from context pointers. The new
bf_stub_loadfunction handles this by picking the largest safe access width based on both source and destination alignment.The diagram below shows the two paths:
Test plan
CGROUP_SOCK_ADDRhook has dry-run parsing tests and behavioral tests covering hash sets, trie sets, and multi-component sets