Skip to content

IPv6 except CIDRs in an IPv4 cluster produce malformed LPM keys and permanently abort egress map refreshes (including stale-entry deletion) #631

Description

@rogeriochaves

What happened:

On an IPv4-only EKS cluster, a NetworkPolicy egress rule with an IPv6 ipBlock that carries except entries permanently breaks eBPF map reconciliation for every pod the policy selects. Every egress map refresh fails with:

{"level":"error","caller":"ebpf/bpf_client.go","msg":"Failed to update kernel map during bulk refresh for key \x40\x00\x00\x00\x01\x00\x00\x00: unable to update map: invalid argument"}
{"level":"error","caller":"ebpf/bpf_client.go","msg":"BPF map update failed unable to update map: invalid argument"}
{"level":"error","caller":"controllers/policyendpoints_controller.go","msg":"Map updates failed for podIdentifier <rs>@<ns>: egress map write: unable to update map: invalid argument"}

The failing key decodes to prefix length 64 (little endian 0x40 0x00 0x00 0x00) with 4 bytes of data 0x01 0x00 0x00 0x00, which is 100::/64 (one of our v6 except entries) encoded as an IPv4 LPM trie key. A prefix length of 64 on a 4-byte-data LPM trie is what the kernel rejects with EINVAL.

Because the refresh aborts on the first bad key, the pod's egress map silently freezes at whatever it contained when the probes were first attached. Endpoint churn (deploys of the destination workloads) leaves the map pointing at stale pod IPs, and the stale-key deletion phase never runs at all, so the map also retains allow entries for IPs that have since been recycled to unrelated pods. We observed 3042 aborted refreshes over 7 days in production before finding it; removing the IPv6 ipBlock from the policy made the very next reconcile succeed and purge 10 stale entries.

Root cause (present in v1.4.0 and still on main):

In pkg/fwruleprocessor/fw_rule_processor.go, ComputeMapEntriesFromEndpointRules:

  1. The per-rule loop harvests firewallRule.Except into exceptCidrs BEFORE the f.shouldSkipRule(...) check that filters IPv6 CIDRs on IPv4 clusters. So the ::/0 rule itself is skipped correctly, but its v6 except entries survive.
  2. The later for exceptCidr := range exceptCidrs loop adds each except as a deny entry into cidrsMap with no v4/v6 filtering of its own.
  3. The final encode loop calls utils.ComputeTrieKey(*firewallMapKey, f.enableIPv6) with enableIPv6=false, producing a malformed v4-shaped key (v6 prefix length plus the first 4 bytes of the v6 address) for each v6 except.

Then in aws-ebpf-sdk-go (v1.0.15, pkg/maps/loader.go), BulkRefreshMapEntries runs BulkUpdateMapEntry first and returns on its error, so a single malformed key aborts the entire refresh including step 3, the stale-key deletion sweep. One bad key therefore wedges both additions and deletions indefinitely.

What you expected to happen:

IPv6 except CIDRs should be filtered on IPv4 clusters the same way the parent IPv6 rule is (and vice versa), so a dual-stack-defensive policy is inert rather than destructive on a single-stack cluster. Ideally the bulk refresh would also tolerate a per-key failure instead of aborting the whole refresh and the deletion phase.

How to reproduce it:

  1. IPv4-only EKS cluster with the VPC CNI network policy agent enabled (standard mode).
  2. Apply a NetworkPolicy with policyTypes: [Egress] containing normal v4 rules plus:
- to:
    - ipBlock:
        cidr: "::/0"
        except:
          - "100::/64"
          - "fe80::/10"
  ports:
    - protocol: TCP
      port: 443
  1. Watch /var/log/aws-routed-eni/network-policy-agent.log: every reconcile for the selected pods logs unable to update map: invalid argument, and the pod's egress map (via aws-eks-na-cli ebpf dump-maps) never converges with the PolicyEndpoint after endpoint churn.

Environment:

  • EKS, IPv4 cluster, network policy standard mode
  • amazon-k8s-cni v1.22.4-eksbuild.3, aws-network-policy-agent v1.4.0-eksbuild.1
  • aws-ebpf-sdk-go v1.0.15

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions