Blog
Thoughts on engineering, design, and building great products.
Writing a tc Program Yourself: __sk_buff and the tcx Chain
Article 12 read Cilium's tc datapath from the outside. This article writes a tc program ourselves — counting egress packets by protocol — to understand __sk_buff from the inside. The core difference from XDP: tc sees the sk_buff with metadata already filled in (skb->protocol, skb->len), not the raw packet. We attach it with tcx on a real interface, get correct counts, then hit a lesson: attached after Cilium on the NIC it never runs, because of how the tcx chain terminates.
eBPF From Scratch: Running Programs Inside the Linux Kernel
Right now, on a worker of the Kubernetes cluster we built in the previous series, 140 eBPF programs are running inside the Linux kernel — routing every packet, controlling device access, collecting metrics. eBPF lets you load code into the kernel and run it safely at hooks, without changing kernel source and without loading a module. This opening article explains what eBPF is, why it changes how the kernel is extended, and how a program goes from code to native machine code.
Case Study: A Packet Through Cilium's eBPF Datapath
Nineteen articles dissected each piece: verifier, maps, XDP, tc, tail call, perf ring, identity. This article assembles them into one seamless story — following a single packet as a pod calls the cluster's DNS Service, from leaving the source pod to reaching the CoreDNS pod, through every eBPF program and every BPF map it touches. No new concepts; just seeing the whole machine run as one unified thing, with real data from the same cluster used throughout the series.
tc/sched_cls and Dissecting a Live Cilium Datapath
After XDP comes tc — the hook where the packet already has an sk_buff, where both ingress and egress are visible, and where Cilium puts almost its entire datapath. This article dissects the 74 sched_cls programs actually running on a cluster node: where they attach (NIC, each pod), how they call each other via tail calls, and which BPF maps they look up to load-balance a Service or apply a NetworkPolicy. kube-proxy-less load balancing is one map lookup.
XDP: Processing Packets at the Earliest Point, Writing a Firewall
XDP attaches an eBPF program to the network driver, running on every incoming packet before the kernel even allocates an sk_buff — the earliest point you can touch a packet. It returns a verdict: PASS, DROP, TX, REDIRECT. This post builds a small XDP firewall that drops ICMP on a real interface, attaches it to a node's NIC with bpftool, then watches ping fall from 0% to 100% loss while SSH stays alive — and sees how it sits ahead of Cilium's tc datapath on the same interface.
LB IPAM and Traffic Policy
Articles 48 and 49 both stopped where the LoadBalancer Service and Gateway hung with external-IP <pending> — nobody hands out addresses on a self-built cluster. This article fills the gap with Cilium's LB IPAM: define an IP range, let Cilium assign it, and the previous Gateway flips to Programmed=True. Then externalTrafficPolicy — Cluster or Local decides whether the client's source IP survives. With a clear line between assigning an IP and advertising it.
Gateway API: The Successor to Ingress
Ingress is frozen at the basics. Gateway API is Kubernetes' new API for inbound traffic, splitting infrastructure and application roles into separate objects, and doing what Ingress can't: weighted traffic splitting, header matching, multi-protocol routing. This article enables Gateway API on Cilium, stands up a Gateway with an HTTPRoute routing by host/path, then splits traffic 80/20 between two versions — tested for real on the EC2 cluster.