eBPF and XDP Packet Filtering: Firewall Guide - 夜莺博客

eBPF and XDP Packet Filtering: Firewall Guide

Classic iptables walks a long rule chain for every packet; eBPF programs run at hooks inside the kernel, are JIT-compiled and can drop traffic at the earliest receive point before the stack allocates anything. XDP (eXpress Data Path) is that earliest hook — it runs at the driver level and is the right place for DDoS-rate drops and IP blocklists. tc with eBPF runs later, where the full socket buffer is available, and is the right place for stateful logic, marking and shaping. This guide shows both, with the safe rollout path.

Where Each Hook Sits

NIC -> XDP -> kernel stack -> tc ingress -> routing -> tc egress -> wire
  • XDP: earliest, smallest per-packet cost, ideal for millions of packets per second. Verdicts are XDP_PASS, XDP_DROP, XDP_TX, XDP_REDIRECT.
  • tc/eBPF: full skb access, integrates with conntrack, QoS and marking; slower than XDP but far more capable.
  • AF_XDP: zero-copy handoff to a userspace fast path when you need custom per-packet processing.

XDP Program Skeleton

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

struct {
    __uint(type, BPF_MAP_TYPE_LRU_HASH);
    __uint(max_entries, 65536);
    __type(key, __u32);
    __type(value, __u64);
} blocklist SEC(".maps");

SEC("xdp")
int xdp_filter(struct xdp_md *ctx)
{
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    __u32 src = ip->saddr;
    if (bpf_map_lookup_elem(&blocklist, &src))
        return XDP_DROP;          /* line-rate drop for blacklisted source */

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

Every header read needs a bounds check before it — the verifier rejects the program otherwise, and that rejection is the single most common reason a first XDP program will not load.

Attach and Operate

clang -O2 -g -target bpf -c xdp_filter.c -o xdp_filter.o
sudo ip link set dev eth0 xdp obj xdp_filter.o sec xdp
ip link show dev eth0 | grep xdp

# XDP statistics (drops per action)
sudo bpftool prog show
sudo bpftool net show
sudo ip -s -s link show dev eth0

# manage blocklist entries from userspace
sudo bpftool map update pinned /sys/fs/bpf/blocklist key 0x0a000001 value 0x1

tc/eBPF for Stateful and Shaping Logic

tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj filter.o sec classifier
tc filter add dev eth0 egress  bpf da obj shaper.o sec classifier

# per-peer shaping for a tunnel interface
tc qdisc add dev wg0 clsact
tc filter add dev wg0 egress bpf da obj wg_shaper.o sec classifier
tc qdisc show dev wg0
tc -s filter show dev wg0 egress

Keep XDP programs small and header-focused; put queueing, marking and per-peer rate parameters in tc, where the kernel's shaping infrastructure (fq_codel, HTB) can be used instead of reimplementing it in BPF.

Rollout Safety

  • Test in a namespace first: ip netns add lab; ip link add veth0 type veth peer name veth1; ip link set veth1 netns lab, then attach the program to the veth inside the namespace.
  • Always have a detach command ready: ip link set dev eth0 xdp off. For tc, tc filter del dev eth0 ingress.
  • Watch drops, not just CPU: the counters above tell you whether the program is filtering or simply passing everything.
  • Version the programs with the rest of your config: an eBPF filter is production code and needs the same review as a firewall ruleset.

Related reading: Nmap network discovery and port scanning and tshark command-line pcap analysis.

原文链接:https://medium.com/@majidbasharat21/full-guide-to-bpf-firewalls-xdp-tc-and-ebpf-integration-81951f19354b