Explains high level packet traversal in a Cilium Kubernetes cluster showing eBPF attachments, service DNAT, conntrack, BPF maps and VXLAN encapsulation for pod to pod traffic.
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).
Assume a two-node Kubernetes cluster with Cilium installed. On a node (Node1) the relevant host interfaces might look like:
Copy
# On Kubernetes Node1› ip add9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue link/ether 02:42:ac:13:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.19.0.3/16 brd 172.19.255.255 scope global eth0 valid_lft forever preferred_lft forever3: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 link/ether 1e:cc:8e:cf:40:b6 brd ff:ff:ff:ff:ff:ff inet 10.0.2.241/32 scope global cilium_host valid_lft forever preferred_lft forever4: cilium_vxlan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue link/ether aa:6f:d9:14:65:3b brd ff:ff:ff:ff:ff:ff15: lxc0ce577f8ba27@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue link/ether d6:8c:de:15:e1:7a brd ff:ff:ff:ff:ff:ff
Table: common interfaces and their roles
Interface
Role / Purpose
Example IP
eth0
Physical host interface (cluster node network)
172.19.0.3
cilium_host
Per-node Cilium host gateway (node CIDR IP)
10.0.2.241/32
cilium_vxlan
VXLAN device used for overlay (when tunnelling)
n/a
lxc… (veth host-side)
Host-side veth that pairs with pod eth0
n/a
To confirm the node CIDR/allocated pool used by Cilium, inspect the agent debug output:
Copy
# On Node1 (exec into the cilium agent pod)› kubectl exec cilium-v5flq -n kube-system -- cilium debuginfo | grep IPAMIPAM: IPv4: 6/254 allocated from 10.0.2.0/24
Node2 in this example has a different allocation:
Copy
# On Node2› kubectl exec cilium-65r9f -n kube-system -- cilium debuginfo | grep IPAMIPAM: IPv4: 6/254 allocated from 10.0.1.0/24
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:
Copy
# On the frontend pod› kubectl exec frontend-5f44ddcfd6-dxmsc -- ip add1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 inet 127.0.0.1/8 scope host lo14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 2e:91:24:0d:66:2b brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.2.172/32 scope global eth0
On the host you’ll see the matching veth peer:
Copy
# On Kubernetes Node1› ip add15: lxc0ce577f8ba27@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether d6:8c:de:15:e1:7a brd ff:ff:ff:ff:ff:ff link-netns cni-23fe68b7-891f-c523-5bcd-69d915414682
The pod’s default route points to the local cilium_host IP (the per-node gateway):
Copy
# On the frontend pod› kubectl exec frontend-5f44ddcfd6-dxmsc -- ip routedefault via 10.0.2.241 dev eth0 mtu 145010.0.2.241 dev eth0 scope link
Pod ARP entry confirms the host-side veth MAC:
Copy
› kubectl exec frontend-5f44ddcfd6-dxmsc -- arp -a? (10.0.2.241) at d6:8c:de:15:e1:7a [ether] on eth0
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):
Copy
# On Kubernetes Node2› ip add3: cilium_host@cilium_net: <BROADCAST,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 link/ether ca:8c:0a:ae:98:9a brd ff:ff:ff:ff:ff:ff inet 10.0.1.48/32 scope global cilium_host4: cilium_vxlan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff inet 172.19.0.2/16 scope global eth0
Example backend pods on Node2 and their host-side veths:
Copy
# Pod interfaces on Node2› kubectl exec backend-7d965dd744-mvcfl -- ip add10: eth0@if11: ... inet 10.0.1.105/32 scope global eth0› kubectl exec backend-7d965dd744-zhlld -- ip add12: eth0@if13: ... inet 10.0.1.128/32 scope global eth0# Host-side veth entries on Node2› ip add11: lxce2a18cced893@if10: ... link/ether d2:10:a4:a5:82:9e13: lxc6fcb99267c0e@if12: ... link/ether 92:67:3b:3a:79:b7
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:
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):
The frontend routes via the node gateway (cilium_host 10.0.2.241) — the packet travels over the pod-host veth.
The eBPF program attached to the pod veth (cil_from_container) runs. It looks up the service and selects a backend endpoint.
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.
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:
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:
The frontend sends the packet to local cilium_host (10.0.2.241).
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).
Example ipcache lookup shows the host IP for Node2 (172.19.0.2):
Copy
# Look up ipcache for endpoint IP (example)› kubectl exec cilium-v5flq -n kube-system -- bpftool map lookup pinned /sys/fs/bpf/tc/globals/cilium_ipcache key hex 40 00 00 00 00 00 00 01 0a 00 01 69 00 00 00 00 00 00 00 00 00 00...value:97 7a 00 00 ac 13 00 02 00 00 00 00# ac 13 00 02 -> 172.19.0.2 (host IP of Node2)
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.
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:
Copy
# On Node2: lookup cilium_lxc for the remote-destination pod 10.0.1.105› kubectl exec cilium-65r9f -n kube-system -- bpftool map lookup pinned /sys/fs/bpf/tc/globals/cilium_lxc key hex 0A 00 01 69 00 00 00 00 00 00 00 00 00 01 00 00 00value:0b 00 00 00 00 9e 04 00 00 00 ...f2 68 f8 83 61 0e 00 00 d2 10 a4 a5 82 9e 00 00# the value includes destination MAC f2:68:f8:83:61:0e and host-side veth info
Verify the backend pod interface and MAC:
Copy
> kubectl exec backend-7d965dd744-mvcfl -- ip add10: eth0@if11: ... inet 10.0.1.105/32 scope global eth0 link/ether f2:68:f8:83:61:0e
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.
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.
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.