unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
configuration-managementrefactoring

bpf, datapath: move IPv6 SNAT exclusion to runtime config

Project: cilium/cilium | Language: Go | Concepts: configuration-management, refactoring | Difficulty: mid
Link: https://github.com/cilium/cilium/pull/48162

The problem

The IPv6 SNAT exclusion CIDR was previously defined as a static macro in the codebase, making it hard to change at runtime or configure per node. This limited flexibility and made it difficult to adapt to different deployment scenarios or configurations.

Why it's non-trivial

A naive fix might try to simply replace the macro with a variable, but this would not propagate the configuration cleanly throughout the BPF datapath and Go agent code. The exclusion prefix needs to be set dynamically, rendered into node configuration, and made available in the BPF programs at runtime, without breaking compatibility or introducing bugs in SNAT logic.

The approach

1. Remove static macros and add a runtime config struct for IPv6 SNAT exclusion in BPF:

-#  define IPV6_SNAT_EXCLUSION_DST_CIDR      { .addr = { 0xfa, 0xce, 0xff, 0xff, 0xff, 0x0 } }
-#  define IPV6_SNAT_EXCLUSION_DST_CIDR_MASK { .addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x0 } }

This removes the hardcoded exclusion macros from the node_config.h file, eliminating compile-time constants for SNAT exclusion.

+struct ipv6_snat_exclusion_prefix {
+	union v6addr dst_addr;
+	union v6addr dst_mask;
+	bool enabled;
+};
+
+NODE_CONFIG(struct ipv6_snat_exclusion_prefix, ipv6_snat_exclusion,
+	    "IPv6 destination prefix excluded from SNAT")

Introduces a struct for the IPv6 SNAT exclusion prefix, with address, mask, and enable flag, and registers it as a node-level config variable.

2. Use the runtime config in BPF SNAT logic:

-# ifdef IPV6_SNAT_EXCLUSION_DST_CIDR
-	{
-		union v6addr excl_cidr_mask = IPV6_SNAT_EXCLUSION_DST_CIDR_MASK;
-		union v6addr excl_cidr = IPV6_SNAT_EXCLUSION_DST_CIDR;
+	if (CONFIG(ipv6_snat_exclusion).enabled) {
+		union v6addr excl_cidr = CONFIG(ipv6_snat_exclusion).dst_addr;
+		union v6addr excl_cidr_mask = CONFIG(ipv6_snat_exclusion).dst_mask;
 
 		if (ipv6_addr_in_net(&tuple->daddr, &excl_cidr, &excl_cidr_mask))
 			return NAT_PUNT_TO_STACK;
 	}
-# endif /* IPV6_SNAT_EXCLUSION_DST_CIDR */

Replaces the macro-based check with a runtime-configured check for the SNAT exclusion prefix.

3. Populate the new config struct in Go agent code:

+		if option.Config.EnableIPv6Masquerade {
+			excludeCIDR := lnc.NativeRoutingCIDRIPv6
+			if option.Config.EnableIPMasqAgent {
+				excludeCIDR = option.Config.IPv6NativeRoutingCIDR
+			}
+
+			if excludeCIDR.IsValid() {
+				node.IPv6SNATExclusion.DstAddr.Addr = excludeCIDR.Addr().As16()
+				mask := net.CIDRMask(excludeCIDR.Bits(), excludeCIDR.Addr().BitLen())
+				copy(node.IPv6SNATExclusion.DstMask.Addr[:], mask)
+				node.IPv6SNATExclusion.Enabled = true
+			}
+		}

When IPv6 masquerading is enabled, sets the exclusion prefix and mask based on the configured CIDR, supporting both default and IP masquerade agent scenarios.

4. Update node config struct and default values:

+	// IPv6 destination prefix excluded from SNAT.
+	IPv6SNATExclusion types.IPv6SNATExclusionPrefix `config:"ipv6_snat_exclusion"`

Adds the new field to the node config struct.

+		cast[types.IPv6SNATExclusionPrefix]([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}),

Ensures the new field is initialized with zero/default values.

5. Update tests to cover the new config:

+	t.IPv6SNATExclusion.DstAddr.Addr = [16]byte{0xfa, 0xce, 0xff, 0xff, 0xff}
+	t.IPv6SNATExclusion.DstMask.Addr = [16]byte{0xff, 0xff, 0xff, 0xff, 0xff}
+	t.IPv6SNATExclusion.Enabled = true

Test setup now populates the IPv6 SNAT exclusion config for test coverage.

6. Remove obsolete macro rendering in config generator:

-			if excludeCIDR.IsValid() {
-				addr := excludeCIDR.Addr().AsSlice()
-				mask := net.CIDRMask(excludeCIDR.Bits(), excludeCIDR.Addr().BitLen())
-				extraMacrosMap["IPV6_SNAT_EXCLUSION_DST_CIDR"] = excludeCIDR.Addr().String()
-				fw.WriteString(FmtDefineAddress("IPV6_SNAT_EXCLUSION_DST_CIDR", addr))
-				extraMacrosMap["IPV6_SNAT_EXCLUSION_DST_CIDR_MASK"] = mask.String()
-				fw.WriteString(FmtDefineAddress("IPV6_SNAT_EXCLUSION_DST_CIDR_MASK", mask))
-			}

Removes the code that previously rendered the exclusion macros, as it's now handled at runtime.

From the review thread

No notable review discussion on this PR.

Takeaways

  • Prefer runtime configuration over compile-time macros for values that may need to change per deployment or node.
  • When refactoring configuration, ensure all layers (agent, BPF, tests) are updated to use the new mechanism.
  • Remove obsolete code paths and macro definitions to avoid confusion and ensure maintainability.