Cilium’s BPF maps are the real-time, kernel-level state of your network policies, and inspecting them is like looking directly into the network traffic’s decision-making brain.

Let’s see what this looks like in action. Imagine you have a simple policy: a frontend deployment can only talk to a backend deployment on TCP port 8080.

apiVersion: cilium.io/v1alpha1
kind: CiliumNetworkPolicy
metadata:
  name: backend-access
spec:
  endpointSelector:
    matchLabels:
      app: backend
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: frontend
    toPorts:
    - ports:
      - port: "8080"
        protocol: TCP

When a pod labeled app: frontend tries to connect to a pod labeled app: backend on port 8080, Cilium, using its BPF programs, consults its maps to decide whether to allow or deny the traffic. If the traffic is unexpectedly blocked, or if you’re just curious about what Cilium thinks is allowed, you’ll be diving into these maps.

The core of Cilium’s policy enforcement lives in BPF (Berkeley Packet Filter) maps, which are specialized hash tables or arrays managed by the Linux kernel. Cilium loads BPF programs into the kernel’s networking stack (e.g., at the xdp or tc hook points). These programs, when executed for each network packet, read from and write to these BPF maps to determine policy actions. The maps store information like:

  • Policy Rules: Which pods can talk to which other pods on which ports and protocols.
  • Service Definitions: How Kubernetes Services are translated into direct pod-to-pod connections.
  • NAT Information: Source NAT (SNAT) and Destination NAT (DNAT) rules for external access and service routing.
  • Identity Information: The mapping between pod labels and Cilium’s internal security identity numbers.

The primary tool for interacting with these maps is cilium bpf. It allows you to list, get, and even set map entries.

To see the maps relevant to policy, you’d typically look at maps like policy_ingres_v4 and policy_egres_v4.

cilium bpf policy_ingres_v4 list
cilium bpf policy_egres_v4 list

These commands will dump the contents of the ingress and egress policy maps. Each entry represents a rule. For ingress, you’ll see entries that map a destination pod’s identity and port to a set of source identities that are allowed to connect. For egress, you’ll see entries mapping a source pod’s identity and port to destination identities.

Let’s break down what an entry in policy_ingres_v4 might look like. It’s a bit dense, but essential. You’ll see fields like:

  • dst_identity: The security identity of the pod the traffic is destined for.
  • src_identity: The security identity of the pod originating the traffic.
  • dport: The destination port.
  • protocol: The IP protocol (e.g., 6 for TCP, 17 for UDP).
  • action: The action to take (e.g., allow, deny).

If your frontend pod (let’s say it has identity 12345) can’t reach your backend pod (identity 67890) on port 8080, you’d first check the identity mapping:

cilium identity list

Then, you’d examine the policy_ingres_v4 map for the backend pod’s identity:

cilium bpf policy_ingres_v4 list | grep "dst_identity=67890"

You’re looking for an entry that allows traffic from src_identity=12345 to dport=8080 and protocol=6 (TCP). If that entry is missing, or if it has an action=deny, your policy isn’t being applied as expected.

The mapping between labels and identities is crucial. Cilium assigns a unique integer identity to each set of labels that represents a distinct workload or group. When you define a policy using labels (like app: frontend), Cilium translates these labels into the corresponding security identities before programming the BPF maps. This translation happens dynamically as pods are created or their labels change.

The cilium bpf policy_ingres_v4 map, for instance, stores rules in the format (dst_identity, src_identity, dport, protocol) -> action. If you have a policy allowing app: frontend to reach app: backend on TCP 8080, and frontend has identity 12345 and backend has identity 67890, you’d expect to see an entry like:

[67890, 12345, 8080, 6] -> allow

If this entry isn’t present, or if there’s a conflicting deny rule with higher precedence, traffic will be blocked. The precedence is determined by the order in which Cilium programs rules, with more specific rules generally taking precedence.

A common pitfall is not realizing that policies are applied based on security identities, not directly on labels in the BPF maps. If your pods have inconsistent labels or if Cilium hasn’t yet updated its identity map for a newly launched pod, the policy won’t apply.

The cilium bpf map_entries command is a more general way to inspect any BPF map:

cilium bpf map_entries <map_name>

For example, to see the service mapping:

cilium bpf map_entries cgroup_ipv4_service

This map is critical for understanding how Kubernetes Services are translated into direct pod-to-pod connections. When a pod outside the cluster (or even another pod) tries to reach a Service IP, Cilium’s BPF programs use this map to find the actual backend pod IPs and ports, and often perform DNAT.

The surprising thing about Cilium’s BPF maps is how granularly they expose the exact state of network policy enforcement at the kernel level, without requiring packet captures or complex tracing tools. Every allowed connection, every denied packet, is a direct result of a lookup in these kernel-resident data structures.

The cilium bpf policy_ingress_v4 map, when viewed with cilium bpf policy_ingress_v4 list, doesn’t just show what is allowed; it shows the granular mapping of destination identity, source identity, destination port, and protocol. This means you can debug issues by directly inspecting the kernel’s decision-making logic for each packet.

When you encounter an issue where traffic is blocked, the first step is to confirm the security identities of the involved pods.

cilium identity list

Then, inspect the relevant policy map for the destination pod’s identity. For ingress, this is policy_ingress_v4.

cilium bpf policy_ingress_v4 list | grep "dst_identity=<backend_identity>"

You are looking for an entry that precisely matches the source pod’s identity, the destination port, and the protocol. For example, if frontend has identity 12345 and backend has identity 67890, and you expect TCP traffic on port 8080 to be allowed, you’d search for an entry like [67890, 12345, 8080, 6] -> allow. If this entry is missing, or if there’s a deny entry with higher precedence, the traffic will be blocked.

The most subtle aspect of these maps is how they handle multiple rules for the same destination and protocol. Cilium processes rules in a specific order, and if a deny rule matches before an allow rule, the traffic is dropped. This precedence is not always obvious from the YAML definition alone and is a direct consequence of the BPF program’s execution flow and how it iterates through map entries.

When you fix a policy and re-apply it, Cilium recompiles the BPF programs and updates the maps. If you’ve had a policy issue, the next error you might hit is related to DNS resolution if your policy also implicitly blocked DNS traffic.

Want structured learning?

Take the full Cilium course →