Engineering modern VPN gateways to sustain multi-gigabit throughput demands programmable control over both incoming and outgoing packet streams. While eXpress Data Path (XDP) has earned prominence for raw packet filtering at the network driver layer, XDP has a fundamental limitation: it operates solely on ingress before the kernel allocates socket buffer metadata (struct sk_buff), rendering it unable to inspect outgoing packets, manage egress queues, or perform deep packet manipulation after socket processing.
For comprehensive, bidirectional packet manipulation, Linux network engineers rely on the Traffic Control (TC) subsystem running eBPF classifiers (BPF_PROG_TYPE_SCHED_CLS). Attached via the modern clsact queueing discipline (qdisc), eBPF TC programs execute in the kernel network stack with full access to sk_buff primitives, enabling dynamic Layer 4/Layer 7 routing, cross-interface packet redirection, and departure-time bandwidth pacing without user space context switching.
Architectural Duality: eBPF TC vs XDP in the Kernel Stack
To understand the role of eBPF TC in encrypted network tunnels, it is critical to compare its execution points with XDP.
Incoming Packet (Ingress)
│
▼
┌──────────────┐
│ XDP Hook │ <--- Operates on raw xdp_md before skb allocation
└──────┬───────┘ (Fast drop/redirect, ingress only, no egress hook)
│
▼
alloc_skb()
│
▼
┌──────────────────┐
│ eBPF TC Ingress │ <--- Operates on struct __sk_buff (clsact qdisc)
└──────┬───────────┘ (Full L3/L4 parsing, BPF maps, redirect to tunnel)
│
▼
Protocol Stack (IP/TCP/UDP) ── Socket Layer
│
▼ (Encrypted Egress)
┌──────────────────┐
│ eBPF TC Egress │ <--- Operates on struct __sk_buff before NIC ring
└──────┬───────────┘ (Departure time pacing, DSCP remarking, shaping)
│
▼
Physical NIC Driver Queue
Operating at the TC layer provides distinct functional capabilities:
- Bidirectional Coverage: TC hooks attach to both ingress and egress paths of physical interfaces (such as
eth0) and virtual tunnel devices (such aswg0). - Complete sk_buff Access: TC programs can read and mutate packet headers, transport checksums, packet timestamps (
skb->tstamp), and firewall marks (skb->mark). - Direct Action Execution: Using the
direct-action(da) flag, a single BPF classifier program directly returns traffic verdicts (TC_ACT_OK,TC_ACT_SHOT,TC_ACT_REDIRECT), avoiding clumsy multi-filter chaining.
Zero-Copy Packet Steering with bpf_redirect
In high-throughput multi-tenant VPN topologies, routing packets between physical Ethernet adapters and virtual tunnel interfaces traditionally involves traversing the kernel IP routing subsystem twice. This path traverses IP lookup tables, connection tracking tables (conntrack), and netfilter chains, generating CPU cache overhead.
With eBPF TC, administrators program direct cross-device forwarding in kernel space using the bpf_redirect() helper function:
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 65536);
__type(key, __u32); /* Client Virtual IPv4 */
__type(value, __u32); /* Target Interface ifindex */
} route_map SEC(".maps");
SEC("tc")
int tc_ingress_router(struct __sk_buff *skb)
{
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
/* Boundary check: minimum Ethernet + IPv4 headers */
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end)
return TC_ACT_OK;
struct iphdr *iph = data + sizeof(struct ethhdr);
__u32 dst_ip = iph->daddr;
/* Fast BPF map lookup for target WireGuard interface */
__u32 *ifindex = bpf_map_lookup_elem(&route_map, &dst_ip);
if (ifindex) {
/* Direct packet redirection, skipping full IP stack traversal */
return bpf_redirect(*ifindex, 0);
}
return TC_ACT_OK;
}
When attached to a physical interface, this classifier inspects incoming client traffic, verifies cryptographic destination mappings via high-speed hash tables, and redirects the packet directly to the input queue of the appropriate tunnel interface. The entire operation occurs without memory copying, reducing per-packet CPU instruction cycles by over 40%.
Early Departure Time (EDT) and Microburst Elimination
A major issue on high-speed VPN connections is packet burstiness. When multiple clients stream high-definition media or initiate concurrent bulk file transfers, TCP transmitters inject dense packet clusters into the tunnel interface. Traditional Token Bucket Filter (TBF) and Hierarchical Token Bucket (HTB) algorithms enforce rate limits by buffering excess packets in memory queues protected by global spinlocks. Under high concurrency, spinlock contention degrades multi-core scalability.
Modern Linux kernels resolve bufferbloat through Early Departure Time (EDT) scheduling. Instead of buffering packets inside locking queue structures, the system assigns a planned departure timestamp (skb->tstamp) to each packet.
Using eBPF TC on the egress path, engineers implement lock-free rate shaping:
- The eBPF egress program calculates the required inter-packet delay based on the client's provisioned bandwidth tier.
- The program updates
skb->tstamp = max(current_time, last_departure_time + packet_len / rate). - The underlying Fair Queueing (
sch_fq) qdisc releases packets onto the wire strictly according to their assigned timestamp, entirely without queue locks or software buffering stalls.
Production Setup: Loading eBPF TC Programs via clsact
Configuring eBPF TC on Linux utilizes the standard iproute2 toolchain:
# 1. Attach the clsact qdisc to the physical WAN interface
tc qdisc add dev eth0 clsact
# 2. Load the BPF classifier onto the Ingress hook with direct-action
tc filter add dev eth0 ingress bpf da obj vpn_router.o sec tc_ingress
# 3. Load the EDT rate-shaper onto the Egress hook
tc filter add dev eth0 egress bpf da obj vpn_shaper.o sec tc_egress
# 4. Inspect active filters and packet execution statistics
tc filter show dev eth0 ingress
tc filter show dev eth0 egress
Because clsact provides clean hook isolation, adding and upgrading BPF programs occurs seamlessly with zero packet loss and zero link downtime.
Enterprise Scaling in RiceGuard: Programmable Anycast Traffic Control
RiceGuard utilizes eBPF TC across its distributed Anycast network edge to deliver deterministic quality of service:
- Tenant-Aware Bandwidth Enforcing: RiceGuard attaches eBPF egress filters directly to tunnel interfaces, assigning independent EDT departure clocks to each client session and preventing noisy neighbors from starving latency-sensitive interactive traffic.
- Dynamic BPF Map Telemetry: Ingress classifiers stream per-flow telemetry (packet counts, byte rates, dropped malformed datagrams) to ring buffers, feeding real-time anomaly detection engines without user space polling overhead.
- Hardware NIC Offload Compatibility: Where smartNICs are available, RiceGuard eBPF TC rules compile directly into hardware flow engines, delivering 100 Gbps line-rate packet steering with sub-microsecond latency.
By harnessing eBPF TC for programmable packet steering and lock-free departure time shaping, WireGuard and RiceGuard achieve exceptional throughput density and robust multi-tenant traffic isolation.



