Repository navigation
VPC egress ACL: end ACL_OUTBOUND with RETURN so its rules stay in order - #14355
Open
bhouse-nexthop wants to merge 2 commits into
Open
bhouse-nexthop wants to merge 2 commits into
bhouse-nexthop wants to merge 2 commits into
Conversation
CsNetfilters.compare() inserts each ACL rule ahead of the last rule in its ACL chain, which is right for ACL_INBOUND, whose last rule is its DROP. ACL_OUTBOUND (mangle) has no terminal rule, so whatever rule happens to be last is pushed behind every ACL rule: - on a tier, the 225.0.0.50 accept (conntrackd's sync multicast between redundant routers) ends up after the ACL's own deny, so with an egress deny the routers stop syncing connection state; - on a private gateway the chain is empty, so the ACL's first rule is the one pushed to the end, behind its deny. Close ACL_OUTBOUND with a RETURN the way ACL_INBOUND is closed with its DROP. RETURN is what falling off the end of the chain does already, so traffic is treated as before; the ACL rules just land ahead of it, in order. Fixes: apache#14354 Signed-off-by: Brad House <bhouse@nexthop.ai>
bhouse-nexthop
requested review from
DaanHoogland,
sureshanaparti,
vladimirpetrov and
weizhouapache
October 8, 2026 10:15
Collaborator
Author
|
@vladimirpetrov @sureshanaparti could you take a look at this one? We'd like this fix to make it into the upcoming 4.22.2 release. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 4.22 #14355 +/- ##
============================================
- Coverage 18.02% 18.02% -0.01%
Complexity 16250 16250
============================================
Files 5936 5936
Lines 535823 535823
Branches 65612 65612
============================================
- Hits 96582 96579 -3
- Misses 428242 428243 +1
- Partials 10999 11001 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
On a VPC without a public network a tier's ACL chains get no rules of their own, ACL_INBOUND no DROP either, and are only jumped to for a static route whose gateway is in the tier. Both then had the same problem: the ACL's first rule ended up behind its deny. Close both chains with RETURN in that case; it does what falling off their end did, so traffic is treated as before. Signed-off-by: Brad House <bhouse@nexthop.ai>
3 of 12 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
On a VPC virtual router, rules in the egress ACL chain
ACL_OUTBOUND_ethX(mangle) are not kept in the order the ACL list gives them, so the chain's own rules can end up behind the ACL's deny.Fixes #14354
Why it breaks
CsNetfilters.compare()puts each rule of anACL_INBOUND_*/ACL_OUTBOUND_*chain at the chain's current rule count, i.e. just ahead of the last rule already in the chain. That works forACL_INBOUND_ethX, becauseCsAddressalways closes it with a-j DROP, so every ACL rule lands ahead of that DROP, in order.ACL_OUTBOUND_ethXhas no terminal rule, so whichever rule happens to be last is pushed behind every ACL rule:CsAddressadds twofrontaccepts, for 224.0.0.18 (VRRP) and 225.0.0.50. The 225.0.0.50 accept ends up last, behind an egress deny.CsRedundant.py).PREROUTING ... -j ACL_OUTBOUND_ethXjump sends them through this chain.default_denylist has one), they are dropped, so state sync between the redundant routers breaks.CsAddressadds nothing to the chain, so it starts empty, and the ACL's first rule is the one pushed to the end, behind the deny.ACL_INBOUNDdoesn't get its DROP either, and both are jumped to only for the static route. Both then put the ACL's first rule behind its deny, ingress included.How it is fixed
CsAddressnow closesACL_OUTBOUND_ethXwith-j RETURNon a tier and on a private gateway, the same way it closesACL_INBOUND_ethXwith-j DROP. For a static route tier on a VPC without a public network, it closes bothACL_INBOUND_ethXandACL_OUTBOUND_ethXwith-j RETURN. A DROP there would newly block traffic that falls through today. RETURN at the end of a user chain does exactly what falling off its end did, so no traffic is treated differently. The ACL rules simply land ahead of it, in order.On a VR that already has a misordered chain, the next apply puts it back in order. The RETURN is appended at the end of the chain first; the ACL rules, which are always re-inserted, then land ahead of it, and their stale copies are removed.
How to reproduce the old behaviour
10.9.9.9/328.8.8.8/320.0.0.0/0iptables -t mangle -S ACL_OUTBOUND_ethXfor each interface.Tier, before (the 225.0.0.50 accept is behind the deny):
Tier, after:
Private gateway, before (rule 1 is behind the deny and never matches):
Private gateway, after:
Types of changes
Feature/Enhancement Scale or Bug Severity
Bug Severity
How Has This Been Tested?
The before/after chains above are real output, not hand-written:
AclDevice, and were fed through the unmodifiedCsNetfilters().compare()against real iptables in a network namespace.CsAddressentries for the chain were reproduced as they stand before and after this change.New tests in
systemvm/test/TestCsAddress.pycallCsIP.fw_vpcrouter()and check the last rule of the ACL chains:They fail on the current
4.22code and pass with this change.I also ran the static-route cases end to end, with the fw list built by the real
CsIP.fw_vpcrouter(), against real iptables. (Nothing in.github/workflowsrunssystemvm/test, so they run viasystemvm/test/runtests.sh.)pycodestyleandpylint(as inruntests.sh) report nothing new.The conntrackd impact is from reading
CsRedundant.pyand thePREROUTINGjump. I have not observed it on a redundant pair.How did you try to break this feature and the system with this change?
ACL_INBOUNDis unchanged wherever it has its DROP, and only gains a RETURN in the static-route case without a public network.