Repository navigation
lib: cgen: detach the old Netfilter links on chain update - #613
Conversation
|
@claude review |
Claude review of PR #613 (863d3c9)The fix detaches the old links before closing them, as Suggestions
Nits
|
| * unpinned, so closing them only drops a reference: they would stay | ||
| * attached until bpffs releases the pinned inode, the old program would | ||
| * keep filtering, and its priority would stay busy. */ | ||
| (void)bf_bpf_link_detach(link->fd); |
There was a problem hiding this comment.
Claude: nit: Casting the detach result to (void) matches bf_handle_detach(). On this path, though, a failed detach produces no log, and its only symptom comes later: the old program keeps filtering and the next chain update fails with -EBUSY, with nothing pointing back to the detach. 414b9b2, which this restores, logged the failure with bf_warn_r(r, "failed to detach existing BPF_NETFILTER link"). Should we keep that warning here, for both fd and fd_extra, so the later EBUSY can be traced? The update should still succeed, since the new links are already attached.
There was a problem hiding this comment.
I kept (void) like bf_handle_detach(): a Netfilter link's detach always returns 0 (bpf_nf_link_detach()), and link->fd is valid here, so the warning could never fire.
| * unpinned, so closing them only drops a reference: they would stay | ||
| * attached until bpffs releases the pinned inode, the old program would | ||
| * keep filtering, and its priority would stay busy. */ | ||
| (void)bf_bpf_link_detach(link->fd); |
There was a problem hiding this comment.
Claude: nit: This detach sequence (detach fd, then detach fd_extra if it is >= 0, ignoring errors) is the same as the one in bf_handle_detach() (src/libbpfilter/cgen/handle.c). Could it move into a small bf_link helper, for example a static _bf_link_detach() in link.c with a public wrapper for handle.c? Then a later refactor of the link code can't drop it from one caller and keep it in the other, which is what 2b1e12d did to the logic from 414b9b2. Optional, since there are only two call sites.
| * attached until bpffs releases the pinned inode, the old program would | ||
| * keep filtering, and its priority would stay busy. */ | ||
| (void)bf_bpf_link_detach(link->fd); | ||
| closep(&link->fd); |
There was a problem hiding this comment.
Claude: suggestion: This PR doesn't introduce this, but it affects whether the update path is complete. When bf_link_update() fails, bf_cgen_update() handles it: the half-created new link is closed and the old handle is re-pinned. After bf_link_update() succeeds there is no rollback. If bf_handle_pin() or _bf_cgen_persist() then fails in bf_cgen_update(), the new links stay unpinned and are released when the process exits, while the old links are already gone. With this patch the old links go away synchronously here; before it, they went once bpffs released the inode. Either way the hook ends up with no program, and the persisted context may still list the old priorities. Is that worth a follow-up, for example detaching the old links only after the new handle is pinned and persisted?
qdeslandes
left a comment
There was a problem hiding this comment.
Ok for the fix, but small comment about the test.
| # The old program must stop filtering as soon as the update returns | ||
| ${FROM_NS} ${BFCLI} chain update --name nf_dual_0 --from-str "chain nf_dual_0 BF_HOOK_NF_LOCAL_IN ACCEPT" | ||
| ping -c 1 -W 0.1 ${NS_IP_ADDR} | ||
|
|
||
| # An immediate update reuses the priority the previous one released | ||
| ${FROM_NS} ${BFCLI} chain update --name nf_dual_0 --from-str "chain nf_dual_0 BF_HOOK_NF_LOCAL_IN ACCEPT" |
There was a problem hiding this comment.
Triggering the actual issue is out of our control, so the error might not show up even if the code is not fixed. I would not introduce a non deterministic test.
There was a problem hiding this comment.
Agreed, I dropped the test change: the PR now only contains the fix.
fb3701a to
0e24592
Compare
_bf_link_update_nf() attaches the new program at the chain's other priority, then closes the old links. bf_cgen_update() unpinned them just before, so closing them only drops a reference: they stay attached until bpffs releases the pinned inode. Until then the old program keeps filtering, so a rule the update removed still applies, and its priority is still in use, so another update right after fails with -EBUSY. Detach the old links before closing them, as bf_handle_detach() does. 414b9b2 did this for the previous Netfilter update code; 2b1e12d dropped it when the links moved to bf_link.
0e24592 to
c3ad02a
Compare
Summary
After
bfcli chain update(orupdate-set) on a Netfilter chain returns, the old program keeps filtering for a short while, and an update right after fails:_bf_link_update_nf()attaches the new program at the chain's other priority, then closes the old links.bf_cgen_update()unpinned them just before, so closing them only drops a reference: they stay attached until bpffs releases the pinned inode. Until then the old program still runs, and its priority is still taken, so the next update can't attach there. This detaches the old links before closing them, asbf_handle_detach()does. 414b9b2 did the same for the previous Netfilter update code; 2b1e12d dropped it when the links moved tobf_link.Related issue
None.
Testing
CMake release build (
-DNO_DOCS=1) on CentOS Stream 9, kernel 6.16.1, gcc 11.5, clang 22.1.3, libbpf 1.6.3, thenmake -C $BUILD test_binandctest(withoutfuzzing.parser.infinite, which runs until stopped):check.iwyudidn't run becauseiwyu_tool.pyisn't installed here (it only checkscgen/program.c). Thecheck.astgreptests aren't registered becauseast-grepisn't installed either.No test is added: on main the old links go away once bpffs frees the unpinned inode, so whether a test catches the bug depends on timing.
Checked by hand, each run in a fresh network and mount namespace, same
link.con main and with this PR, with the chain above: a ping right afterchain updateis dropped in 10/10 runs on main and passes in 10/10 with this PR, and an immediate second update fails withEBUSYin 9/10 runs on main and 0/10 with this PR.Notes for the reviewer
bf_cgen_update()to re-pin.AI disclosure
Claude Code found the issue, drafted the change, the commit message and this description under my direction, and ran the tests listed under Testing. I reviewed the change and the results.
Checklist
make -C $BUILD test_bin testpasses (check.iwyuandcheck.astgrepnot run, see Testing) and the code follows the style guidecomponent: subcomponent: short description