Skip to main content
In this lesson we step through the network interfaces created by Cilium and then perform a packet walk: as packets traverse a node and the cluster network, we explain the routing decisions and where eBPF is involved. This is a high-level conceptual overview to show the big picture — it does not dive into eBPF implementation details.
This article simplifies many details for clarity. The exact behaviour may vary with Cilium versions and configuration (for example: kube-proxy replacement, encapsulation mode, or network policy settings).

Node interfaces and pod addressing

Assume a two-node Kubernetes cluster with Cilium installed. On a node (Node1) the relevant host interfaces might look like:
Table: common interfaces and their roles To confirm the node CIDR/allocated pool used by Cilium, inspect the agent debug output:
Node2 in this example has a different allocation:
When a pod is scheduled on Node1 (for example, a frontend pod), it receives an isolated network namespace with an eth0 and an IP from the node’s pool:
On the host you’ll see the matching veth peer:
The pod’s default route points to the local cilium_host IP (the per-node gateway):
Pod ARP entry confirms the host-side veth MAC:
Other pods on the same node follow the same pattern (default route via cilium_host, host-side veth entries with unique MACs). Node2 shows the same set of interfaces but with a different cilium_host IP (10.0.1.48 in the example):
Example backend pods on Node2 and their host-side veths:

Services and backends

Create a ClusterIP Service that selects backend pods:
Check the Service in Kubernetes:
Cilium tracks services and maps them to endpoints. Example from the Cilium agent shows the service IP and active backends:
Table: Service mapping (example)

High-level eBPF attachments

Cilium attaches eBPF programs to multiple interfaces (per-pod veths, host interfaces, VXLAN device, etc.). Inspect currently attached programs with bpftool from within the Cilium agent:
Common responsibilities of these eBPF programs when a packet is seen on an attached interface:
  1. Validate and classify the packet
  2. Perform service load balancing
  3. Apply destination NAT (service translation)
  4. Enforce network policy (accept or drop)
A stylized bee logo labeled "ebpf" is on the left, and on the right is a numbered list of four eBPF packet-processing steps: 01 Validate the packet, 02 Service load balancing, 03 Perform destination NAT, and 04 Network policy checking.

Packet walk: pod-to-pod (same node)

Scenario: frontend pod (10.0.2.172) on Node1 sends to backend service (10.96.71.79:80). Example flow tuple:
  • sourceIP: 10.0.2.172
  • destIP: 10.96.71.79
  • sourcePort: ephemeral (e.g., 5578)
  • destPort: 80
Flow steps (same-node backend selected):
  1. The frontend routes via the node gateway (cilium_host 10.0.2.241) — the packet travels over the pod-host veth.
  2. The eBPF program attached to the pod veth (cil_from_container) runs. It looks up the service and selects a backend endpoint.
  3. Cilium performs DNAT to the chosen backend pod IP (for example, 10.0.2.183) and creates connection-tracking entries for both directions so that return traffic is correctly translated.
  4. The eBPF program looks up the destination in the cilium_lxc map to obtain destination MAC and host veth info, then forwards the packet out the matching host veth toward the destination pod.
You can inspect BPF maps to debug how Cilium maps pod IPs to host egress interfaces and endpoint IDs. Example lookup (hex key shown) for destination 10.0.2.183 in the cilium_lxc map:
Within that value, part encodes the destination MAC and the endpoint ID (for example 0x0252 -> decimal 594). Confirm the endpoint mapping:
Return traffic for the connection is handled by conntrack and the reverse NAT is applied as responses traverse back through the agent.

Packet walk: pod-to-pod (reverse direction)

When the backend (10.0.2.183) responds, tuple values flip:
  • sourceIP: 10.0.2.183
  • destIP: 10.0.2.172
  • sourcePort: 80
  • destPort: ephemeral (e.g., 5578)
Process:
  1. The backend’s default route sends traffic to its cilium_host (10.0.2.241).
  2. The pod’s host veth ingress trigger runs (cil_from_container), which checks conntrack for the original flow.
  3. Conntrack state is used to rewrite addresses back to the service IP if required and to identify the frontend endpoint (for example endpoint ID 3959).
  4. The eBPF program forwards the packet to the frontend pod’s veth.
Example debug lookups for the frontend endpoint:
This confirms Cilium’s mapping and the MAC/interface used to forward the packet to the frontend pod.

Packet walk: pod-to-pod (different nodes)

Now consider the frontend (Node1, 10.0.2.172) contacting the service but Cilium selects a backend on Node2 (10.0.1.105). Example initial tuple:
  • sourceIP: 10.0.2.172
  • destIP: 10.96.71.79
  • sourcePort: ephemeral
  • destPort: 80
Flow for remote backend:
  1. The frontend sends the packet to local cilium_host (10.0.2.241).
  2. The local eBPF program resolves a backend endpoint (10.0.1.105) that belongs to Node2. Cilium looks up the ipcache (cilium_ipcache) mapping for that pod to obtain the node-level host address (the node’s eth0 IP).
  3. Example ipcache lookup shows the host IP for Node2 (172.19.0.2):
  1. Because the backend is remote and the cluster is using VXLAN encapsulation, Cilium encapsulates the packet. The outer packet uses Node1 and Node2 host IPs as the VXLAN outer source/destination and egresses via cilium_vxlan.
  2. On Node2, the VXLAN decapsulated packet arrives on cilium_vxlan. The eBPF program attached to that device (cil_from_overlay) decapsulates and then performs an inner-packet lookup in cilium_lxc to find the destination MAC and host veth to forward to the pod.
Example lookup on Node2 for pod 10.0.1.105:
Verify the backend pod interface and MAC:
Decode the endpoint identity from the map value (e.g., 0x9e04 -> endpoint ID 1182) and confirm:
Node2 forwards the inner packet to the correct veth and the destination pod receives it. Return traffic reverses this process: pod -> host veth -> cilium_host -> VXLAN encapsulate -> Node1 -> decapsulate -> forward to frontend pod. Conntrack ensures NAT and correct service semantics.
Two tables titled "Services" and "Connection Tracking" listing service and endpoint IPs, ports, IDs and connection types. The Services table shows service IP 10.96.71.79:80 mapped to endpoints like 10.0.2.170, 10.0.1.128, 10.0.2.183 and 10.0.1.105, and the Connection Tracking table shows flows from 10.0.2.172 with source ports to those destinations labeled as "svc" or "Egress."

Wrapping up

This lesson summarized the higher-level packet flow when using Cilium with eBPF:
  • Pods connect to the host via per-pod veth pairs; the per-node cilium_host acts as the pod gateway.
  • eBPF programs are attached to many interfaces (pod veths, host, vxlan) and perform packet validation, load balancing, DNAT, and policy enforcement.
  • Cilium uses BPF maps (for example cilium_lxc and cilium_ipcache) for fast lookups of MACs, endpoint IDs and node mappings.
  • For remote endpoints Cilium encapsulates traffic (VXLAN in this example) using node IPs as outer addresses, decapsulates on the destination node and forwards to the pod.
  • Connection tracking carries state for DNAT and reverse translation so services behave as expected.
A network diagram of two Kubernetes nodes showing frontend and backend pods, their pod IPs, host namespaces and Cilium components (Cilium_vxlan, Cilium_host) with eBPF and LXC interfaces. It also shows a backend service (10.96.71.79:80) and an example flow with source/destination IPs and ports across the 172.19.0.x host network.
Conceptual reminder: the ordering of eBPF hooks and specific packet transformations can vary with Cilium configuration (encapsulation vs. direct routing, kube-proxy replacement, etc.), kernel versions and user-space tooling. Use the Cilium debugging tools and bpftool to inspect live behaviour in your environment.

Watch Video