Why touch the Egress Gateway at all?

Cilium’s Egress Gateway gives pods a predictable, stable source IP when they talk to the outside world. Current gateway implementation, at least the community version

  • All Nat convertion is handled at the gateway, this creates a single point of conntracking for port/address translation and overloading.

The idea: Split-Stage Deterministic NAT

Using;

  • MAP-T (RFC 7599) — stateless, algorithmic address+port mapping.
  • A+P (RFC 6346) — giving an edge device a slice of the port range instead of a whole address.
  • Deterministic CGNAT (RFC 7422) — derive the public identity arithmetically from the subscriber, so you can log/reverse without per-flow state.

Instead of one stateful hop, the pipeline is split into two eBPF stages:

Stage-1 — Worker / CE node (A+P clamp). The worker acts as the Customer Edge. It compresses the pod’s random outbound ephemeral source port into the node’s assigned deterministic port block (port_lo .. port_hi). The worker’s own conntrack remembers ephemeral ↔ block_port for that pod — this is the only state, and it lives on the source node where it belongs.

Stage-2 — Gateway / Border Relay (pure arithmetic). The gateway acts as a stateless Border Relay. Forward SNAT and reverse de-NAT are a bit-shift:

Thus instead of directing traffic from all nodes to the gateway for NAT, simply each node handles the NAT overlading and uses conntrack.

Gateways simply used for return traffic to appropriate node and then using the connt track to the right POD.

Example translation flow

Real capture from the lab (blockSize = 2048 → shift = 11, NAT pool 172.16.99.0/28):

# forward — pod on a worker (CE)
10.1.0.33:57906  ── Stage-1 (A+P clamp) ─▶  10.1.0.33:9139        # ephemeral → node port block; node CT keeps 57906 ↔ 9139
                 ── Stage-2 (BR SNAT)   ─▶  172.16.99.0:9139 ─▶ 255.255.0.1:8080
                                            # node_index = 9139 >> 11 = 4 → NAT IP + block derived arithmetically

# return — may hit ANY gateway (active-active ECMP)
255.255.0.1:8080 ─▶ 172.16.99.0:9139  ── Stage-2 reverse (stateless) ─▶ node[ 9139 >> 11 ]
                                          # gateway holds no CT — it recomputes the owner node from the port
                 ── node CT reverse ──▶  10.1.0.33:57906            # delivered back to the pod

It’s a contained addition to the existing Egress Gateway stack — and crucially, no new eBPF maps on the gateway:

  • CRD — egressGatewayMode: deterministic plus a deterministicConfig (natIPv4Pool, blockSize, podIPv4CIDRs, podIPv4CIDRMaskSize) on CiliumEgressGatewayPolicy, and an egressGateways list for active-active.
  • eBPF datapath — the two-stage clamp / SNAT / stateless reverse in bpf/lib/egress_gateway.h, wired into the from-pod and from-overlay hooks (bpf/bpf_host.c, bpf/bpf_overlay.c, reply path in bpf/lib/nodeport.h).
  • Control plane / operator — deterministic pool sizing and fail-fast policy validation in pkg/egressgateway/ and operator/pkg/egressgateway/.
  • Observability — a new DROP_NAT_BLOCK_EXHAUSTED drop reason, surfaced in cilium monitor and Hubble flows.

The content of this post was created by the author; AI was used for editing and restructuring.