Skip to content

ACL with multiple CIDRs does not honor rule id #12668

Description

@bradh352

problem

If you have a CIDR list like:

rule 1: [ "1.2.3.4/32", "2.3.4.5/32"] tcp allow port 22
rule 65535: [ "0.0.0.0/0"] deny port any

What you end up with when inspecting the VR is:

-A ACL_OUTBOUND_eth12 -d 1.2.3.4/32 -p tcp -m tcp --dport 22 -j ACCEPT
-A ACL_OUTBOUND_eth12 -j DROP
-A ACL_OUTBOUND_eth12 -d 2.3.4.5/32 -p tcp -m tcp --dport 22 -j ACCEPT

This is clearly not the desired behavior.

versions

Cloudstack 4.22.0

The steps to reproduce the bug

See description

What to do about it?

Don't use more than one CIDR per rule

Activity

  1. added this to the 4.22.1 milestone on Feb 23, 2026
  2. added theissue type on Feb 23, 2026
  3. modified the milestones: 4.22.1, 4.22.2 on Apr 24, 2026
  4. weizhouapache commented on May 21, 2026

    @weizhouapache
    Member

    @bradh352
    I tested with the same CIDR

    Image

    iptables rules look ok

    @@ -71,6 +71,8 @@
     -A ACL_INBOUND_eth2 -j DROP
     -A ACL_INBOUND_eth3 -d 225.0.0.50/32 -j ACCEPT
     -A ACL_INBOUND_eth3 -d 224.0.0.18/32 -j ACCEPT
    +-A ACL_INBOUND_eth3 -s 2.3.4.5/32 -p tcp -m tcp --dport 22:23 -j ACCEPT
    +-A ACL_INBOUND_eth3 -s 1.2.3.4/32 -p tcp -m tcp --dport 22:23 -j ACCEPT
     -A ACL_INBOUND_eth3 -j DROP
     -A NETWORK_STATS_eth1 -s 172.16.0.0/20 -o eth1
     -A NETWORK_STATS_eth1 -d 172.16.0.0/20 -i eth1
    
  5. fermosan commented on Oct 7, 2026

    @fermosan
    Contributor

    I can reproduce this on a VPC virtual router (CloudStack 4.23.0.0, VR template <4.22.0>, KVM, iptables v1.8.9 (nf_tables)).

    Why a single multi-CIDR rule looks fine: it only goes wrong when the multi-CIDR rule is followed by other rules in the same ACL list. With just one rule there is nothing after it, so the order cannot be seen to be wrong.

    Setup: a tier ACL list with ingress rules such as 10 (allow tcp 80, 10.10.0.0/24,10.10.7.0/24), 12 (allow tcp 135, three CIDRs), 13 (allow tcp 443, 0.0.0.0/0), more single- and multi-CIDR rules, and a deny at 9998.

    Result on the VR (iptables -L ACL_INBOUND_eth9 -n -v --line-numbers, and the same for ACL_OUTBOUND_* in the mangle table):
    3 ACCEPT tcp 10.10.7.0/24 tcp dpt:80 <- only the LAST cidr of rule 10
    5 ACCEPT tcp 10.10.4.0/24 tcp dpt:135 <- only the LAST cidr of rule 12
    6 ACCEPT tcp 0.0.0.0/0 tcp dpt:443
    21 DROP <- explicit deny (rule 9998)
    29 ACCEPT tcp 10.10.2.0/24 tcp dpt:135 <- other CIDRs of rule 12, behind the DROP
    30 ACCEPT tcp 10.10.0.10 tcp dpt:135
    31 ACCEPT tcp 10.10.0.0/24 tcp dpt:80 <- other CIDR of rule 10, behind the DROP
    32 DROP <- the VR's own final DROP
    In every multi-CIDR rule only the last CIDR lands in the right place. The rest are appended after the explicit deny, so they never match. It happens in both ingress (filter) and egress (mangle). Single-CIDR rules are fine.

    Impact: allow rules silently stop working for all but one CIDR. A deny with several CIDRs (for example "block private ranges" placed before an internet allow) would end up after the allow and stop denying.

    Possible cause (from reading configure.py, not tested): AclDevice.process() raises the insert index by 1 per ACL rule, and AclRule.create() emits one -A -s a,b,c ....

    iptables expands the comma-separated source into several rules, so the tracked index drifts after every multi-CIDR rule, and CsNetfilter inserts "right before the DROP all" at the wrong position.

    Workaround: one CIDR per rule, as @bradh352 wrote.

  6. fermosan commented on Oct 7, 2026

    @fermosan
    Contributor

    Follow-up: the IPv6 side of the same problem is worse, those rules are not misplaced, they are not applied at all.

    On the same router (4.23.0.0, VR template 4.22.0), the ACL list has 20 IPv6 ingress rules. nft list chain ip6 ip6_acl eth9_ingress_policy contains 15 of them. The 5 that are missing are exactly the rules whose cidrlist has two or more IPv6 CIDRs (tcp 4172, tcp 4521, tcp 6283-6284, tcp 52000-60000 and an ICMPv6 rule). Rules with exactly one IPv6 CIDR, including mixed IPv4+IPv6 rules, are applied correctly. Nothing is reported in the UI or API.

    Cause: AclDevice.__process_ip6 in configure.py builds the rule from the whole comma-separated string (addr = "ip6 saddr " + cidr), and nft does not accept a bare comma list. Checked on the VR with nft -c (nothing applied):

    $ nft -c add rule ip6 ip6_acl eth9_ingress_policy ip6 saddr 2001:db8:a1:2009::/64,2001:db8:a1:afd2::/64 tcp dport 4172 accept
    Error: syntax error, unexpected /, expecting end of file or newline or semicolon
    
    $ nft -c add rule ip6 ip6_acl eth9_ingress_policy ip6 saddr '{ 2001:db8:a1:2009::/64, 2001:db8:a1:afd2::/64 }' tcp dport 4172 accept
    (no error)
    

    cloud.log shows each IPv6 ACL rule being added one at a time with nft add rule ip6 ip6_acl ..., so only the failing rules disappear and the rest of the list loads.

  7. added a commit that references this issue on Oct 8, 2026
    c397683
  8. bhouse-nexthop commented on Oct 8, 2026

    @bhouse-nexthop
    Collaborator

    Fix in PR #14350

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions