Repository navigation
Allow Unconditional Terminal Rules - #607
yaakov-stein wants to merge 4 commits into
Conversation
|
@claude review |
Claude review of PR #607 (8cfc64c)Suggestions
Nits
Errors
|
49fd335 to
cb6b1db
Compare
|
@claude review |
| if (r) | ||
| return bf_err_r(r, "failed to generate next rule fixups"); | ||
|
|
||
| if (!policy_reachable) |
There was a problem hiding this comment.
Claude: nit: Rules after an unconditional terminal rule are never emitted, but _bf_chain_check_rule() still uses them to set BF_CHAIN_LOG, BF_CHAIN_LOG_RATELIMIT and BF_CHAIN_STORE_NEXTHDR. For example, rule DROP rule log every 1s DROP still creates the log and state maps and runs the state-map lookup in the prologue for every packet. An unreachable ip6.nexthdr rule still turns on IPv6 extension-header parsing. The program still works, but is this extra per-packet work intended? Or should unreachable rules stop contributing to the chain flags?
cb6b1db to
f93e286
Compare
f93e286 to
8cfc64c
Compare
|
@claude review |
c4f0f59 to
57084d1
Compare
57084d1 to
ab9d28e
Compare
| test "$(get_counter c 6)" -gt 0 | ||
|
|
||
| # Comparison no-ops also terminate without emitting the suffix or policy. | ||
| for matcher in '(meta.sport) not in {}' 'meta.probability eq 100%'; do |
There was a problem hiding this comment.
(meta.sport) not in {} isn't a no-op: meta.sport not 9 and (meta.sport) not in {9} never match ICMP (ports exist only for TCP/UDP), so an empty set shouldn't either.
Treating it as unconditional makes this rule drop ping and skip the rest of the chain.
Check REDIRECT support, matcher operators and verdicts for every rule, including disabled rules. Unreachable rules must not bypass validation.
Omit subsequent rules and default-policy code after unconditional terminal verdicts, avoiding verifier rejection of unreachable instructions. Document support for unconditional rules in the C API.
Allow matcherless rules in bfcli and document the syntax, exposing the library's unconditional-rule support. Add XDP regressions for unconditional rule behavior.
Treat non-negated meta.probability 100 as a no-op, avoiding unnecessary random sampling and allowing terminal rules to omit unreachable code. Cover it in the existing XDP regression.
ab9d28e to
334148c
Compare
qdeslandes
left a comment
There was a problem hiding this comment.
Few nits. Also, in the last commit "Cover it in the existing XDP regression." is probably not necessary, we usually don't mention the tests in the commit messages unless the commit is only about the tests.
| NULL, &rules)); | ||
| bf_list_clean(&rules); | ||
|
|
||
| // Membership requires a SET matcher, not a scalar IN matcher. |
There was a problem hiding this comment.
This comment is not very clear to me, do you mind rephrasing it?
| if (rule->verdict < 0 || rule->verdict >= _BF_VERDICT_MAX) { | ||
| return bf_err_r(-EINVAL, "unknown verdict %d in rule %u", rule->verdict, | ||
| rule->index); | ||
| } | ||
|
|
||
| if (rule->verdict == BF_VERDICT_REDIRECT) { | ||
| enum bf_flavor flavor = bf_hook_to_flavor(chain->hook); | ||
|
|
||
| if (flavor != BF_FLAVOR_XDP && flavor != BF_FLAVOR_TC) { | ||
| return bf_err_r(-ENOTSUP, "redirect is not supported by %s hook", | ||
| bf_hook_to_str(chain->hook)); | ||
| } | ||
|
|
||
| if (flavor == BF_FLAVOR_XDP && | ||
| rule->redirect_dir != BF_REDIRECT_EGRESS) { | ||
| return bf_err_r(-ENOTSUP, | ||
| "XDP redirect only supports 'out' direction"); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
All good for now, but we'll eventually need to define a better model to handle flavor-level and matcher-level requirements and constraints.
| n_fixups = bf_list_size(&program->fixups); | ||
| r = _bf_program_fixup(program, BF_FIXUP_TYPE_JMP_NEXT_RULE); | ||
| if (r) | ||
| return bf_err_r(r, "failed to generate next rule fixups"); | ||
|
|
||
| /* Resolving next-rule jumps removes their fixups and leaves all other | ||
| * fixups unchanged. If the list shrinks, the rule has a jump to the | ||
| * next rule. CONTINUE also reaches the next rule by falling through. */ | ||
| next_reachable = rule->verdict == BF_VERDICT_CONTINUE || | ||
| bf_list_size(&program->fixups) < n_fixups; |
There was a problem hiding this comment.
Make the check more explicit, e.g. _bf_program_has_fixup(program, BF_FIXUP_TYPE_JMP_NEXT_RULE), with _bf_program_has_fixup properly documented.
| ; | ||
|
|
||
| matchers : matcher | ||
| matchers : %empty |
There was a problem hiding this comment.
I had to think hard about this, it definitely doesn't behave as I thought it would!
|
|
||
| . "$(dirname "$0")"/../e2e_test_util.sh | ||
|
|
||
| make_sandbox |
| # Confirm connectivity and populate neighbor entries before testing DROP. | ||
| ping -c 1 -W 0.1 ${NS_IP_ADDR} | ||
|
|
||
| # Set-map, logging and counter fixups remain valid when the suffix is omitted. |
There was a problem hiding this comment.
"suffix" here is the rules after the unconditional one? It's confusing IMO
| if (bf_matcher_get_type(matcher) == BF_MATCHER_META_PROBABILITY) { | ||
| const float proba = *(const float *)bf_matcher_payload(matcher); | ||
|
|
||
| return proba == 100.0F && !bf_matcher_get_negate(matcher); | ||
| } |
There was a problem hiding this comment.
meta.probability not eq 0% should also be handled here. Currently, 0% has a probability of 1 in 2^32 to match because we use BPF_JLE for the comparison. This is a good opportunity to fix one of those two legs (the other one being that meta.probability eq 0% should never match, but that's another thing).
Summary
Currently, chains like these:
will fail to load (referring to chains like these being used through
libbpfilter, not viabfcli), as the verifier will complain that BPF instructions are unreachable. Even when no rules follow an unconditional terminal rule, the default-policy block for the chain will still cause this problem.I see two paths we can take to fix this:
libbpfiltercan check if rules are unconditional and, if so, reject the ruleset with a clear error (as opposed to a verifier splat + error).libbpfiltercan check if rules are unconditional and prevent any unreachable code from being added to the program.As more users use bpfilter programmatically (via C API usage of
libbpfilter), I believe the second approach is the correct one. When programmatically defining a ruleset or populating a set, having zero matchers or having an empty set can easily occur. Requiring the user to ensure those cases don't happen passes unnecessary work onto callers oflibbpfilter.Also, the third commit adds the no-op case of a non-negated
meta.probability 100, as that will always be true.Related issue
#561 - Allow a rule to match all the traffic
Testing
New testing file (
unconditional.sh) + some manual testingNotes for the reviewer
AI disclosure
Worked with AI for planning and coding
Checklist
make -C $BUILD test_bin testpasses and the code follows the style guidecomponent: subcomponent: short description