Join WhatsApp
Join Now
Join Telegram
Join Now

Configuring bpftool for Real-Time Network Monitoring on Linux

Avatar for Noman Mohammad

By Noman Mohammad

Published on:

Your rating ?

Stop Guessing What’s on Your Wire

Last Friday I lost three hours chasing a phantom.
My web app felt sluggish. Logs? Clean. Load average? Fine.
Then I ran one tiny BPF program and saw the truth: 11,432 TCP-SYN packets per second from a single mis-configured container.
Fixed in two minutes. That’s why I’m writing this post.

In 2025 bpftool is still the fastest way to get that kind of visibility on Linux.
Below I’ll walk you through the exact steps I used—no PhD in kernel internals required.

One-Minute Checklist Before We Start

  • Kernel 6.8 or newer (check with uname -r)
  • Install the user-space bits:
    • sudo apt install linux-tools-common libbpf-dev clang-18 llvm-18 (Ubuntu/Debian)
    • sudo dnf install bpftool kernel-headers libbpf-devel clang (RHEL/Fedora)

Done? Great. Let’s build a traffic spy that runs inside the kernel.

Step 1 – A 30-Line BPF Program That Counts SYN Packets

Copy this into monitor_tcp.c:


// monitor_tcp.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, __u32);   // source IPv4
    __type(value, __u64); // counter
} syn_packets SEC(".maps");

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

    struct ethhdr *eth = data;
    if (eth + 1 > data_end) return XDP_PASS;

    if (eth->h_proto != bpf_htons(ETH_P_IP)) return XDP_PASS;

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

    if (ip->protocol != IPPROTO_TCP) return XDP_PASS;

    struct tcphdr *tcp = (void *)ip + sizeof(*ip);
    if (tcp + 1 > data_end) return XDP_PASS;

    if (tcp->syn && !tcp->ack) {
        __u32 src_ip = ip->saddr;
        __u64 *count = bpf_map_lookup_elem(&syn_packets, &src_ip);
        if (count)
            __sync_fetch_and_add(count, 1);
        else {
            __u64 one = 1;
            bpf_map_update_elem(&syn_packets, &src_ip, &one, BPF_ANY);
        }
    }
    return XDP_PASS;
}
char _license[] SEC("license") = "GPL";

Nothing fancy—just counts every TCP-SYN that reaches your NIC.

Step 2 – Turn Source Code into Kernel Byte-Code

clang -O2 -g -Wall -target bpf -D__TARGET_ARCH_x86_64 -c monitor_tcp.c -o monitor_tcp.o

Step 3 – Load It Into the Kernel

sudo bpftool prog load monitor_tcp.o /sys/fs/bpf/monitor_tcp

Step 4 – Snap It onto Your NIC

sudo bpftool net attach xdp pinned /sys/fs/bpf/monitor_tcp dev eth0

Replace eth0 with your real interface.
If you’re on Wi-Fi you might need dev wlan0 instead.

Step 5 – Watch the Numbers Roll In

Find the map ID:

MAP_ID=$(sudo bpftool map list | awk '/syn_packets/{print $1}')

Live dashboard (refreshes every second):


watch -n1 'sudo bpftool map dump id "$MAP_ID" \
  | awk "NF==4 {print \$1, \$3}" \
  | sed "s/0x//g" \
  | xargs -n2 printf "%d.%d.%d.%d  %s\n"'

You’ll see something like:

192.168.1.42   312
203.0.113.7    9
10.244.0.15    11432  ← there’s my noisy container

Finished? Clean Up

sudo bpftool net detach xdp dev eth0

Pro Tips From My Last Fire-Drill

  • Ring buffer in 2025: If you need events instead of counters use BPF_MAP_TYPE_RINGBUF—zero copy, huge throughput.
  • Kubernetes: Install kubectl-bpf and attach the same program to every node in one line:
    kubectl bpf attach xdp -f monitor_tcp.o -i eth0
  • Stuck? Always run dmesg | grep bpf first. The verifier is annoyingly good at telling you what you broke.

Still Curious?

The official docs are actually readable these days:

Grab the code, spin it up, and stop flying blind. Your next outage will thank you.

Leave a Comment