2025-08-31 12:17:00
journal.hexmos.com
I. What is this eBPF? It looks scary!
Have you wanted to write programs that act as drivers for Linux? Wanted programs to run at a kernel level? Wanted to monitor events, internal resources and get better observability? All you need to know is how to make good use of Linux eBPF.
eBPF is a technology in the Linux kernel that can run sandboxed programs in a privileged context (in the OS kernel). It is used to efficiently extend the capabilities of the kernel without changing kernel source code.
An operating system kernel is hard to modify due to its central role and high requirement towards stability and security. Innovation at the operating system level is lower compared to functionality implemented outside of the operating system. And developing drivers is difficult in general (I have tried that in Windows and failed).
link : https://ebpf.io/what-is-ebpf/
eBPF changes this formula fundamentally. It allows sandboxed programs to run within the operating system, which means that application developers can run eBPF programs to add additional capabilities to the operating system at runtime. The operating system then guarantees efficiency as if natively compiled with the aid of a Just-In-Time (JIT) compiler and verification engine.
This has led to a wave of eBPF-based projects covering a wide array of use cases, improving networking, observability, and security spaces.
Let’s dive right into some practical scenario where we will build a simple firewall to block traffic from a particular ip like 8.8.8.8. And counts the incoming packets transfered each second. Follow through is you have an Ubuntu machine ready.
II. Developing with eBPF made Simple.
We need 2 files for a simple ePBF program.
- A Python user space script for interacting with eBPF
- A C code that uses eBPF functions and modules (core logic)
Let’s download the requirements and setup a python virtual environment for smooth workflow.
Initial setup for ubuntu:
sudo apt-get update && sudo apt-get install -y bpfcc-tools libbpfcc-dev
Create a python virtual environment.
➜ python3 -m venv venv
➜ source venv/bin/activate
Here’s what the 2 files that we are going to create:
-
- eBPF program that runs in the Linux kernel
- Counts all incoming packets on a network interface
- Drops packets destined for IP 8.8.8.8 (Google DNS)
-
- Python control program that:
- Loads and compiles the eBPF program
- Attaches it to a network interface
- Monitors and prints packet counts per second
- Handles graceful shutdown on SIGTERM/Ctrl+C
- Prints debug messages when packets are dropped
- Python control program that:
To find the network interface try this command.
$ ip link show | grep -Po '(?
The runner.py
script is the user-space controller for our eBPF firewall. It’s responsible for loading the eBPF program into the kernel, monitoring its activity, and cleaning up when it’s done.
First, we import the necessary Python libraries. bcc
is the core library that lets us interact with eBPF, while the others help with handling signals, time, file paths, and network data structures.
from bcc import BPF
from time import sleep
from pathlib import Path
import signal
import ctypes
import socket
import struct
To ensure the firewall can be shut down cleanly, we set up a custom signal handler. The TerminateSignal
exception and handle_sigterm
function work together to catch termination signals (like SIGTERM
), allowing the script to proceed to the cleanup steps instead of stopping abruptly.
class TerminateSignal(Exception):
pass
# Signal handler for SIGTERM
def handle_sigterm(signum, frame):
raise TerminateSignal("Received SIGTERM, terminating...")
Loading and Managing the eBPF Program
The eBPF logic itself is written in C in probe.c
. The load_bpf_program
function reads this C code, and the BCC library compiles it into eBPF bytecode and loads it into the kernel. Once loaded, attach_xdp_program
hooks the compiled code to a network interface using XDP (eXpress Data Path), allowing it to process packets at the earliest possible point in the network stack.
# Load and compile the eBPF program from the source file
def load_bpf_program():
bpf_source = Path('probe.c').read_text()
bpf = BPF(text=bpf_source)
return bpf
# Attach the eBPF program to the specified interface
def attach_xdp_program(bpf, interface):
xdp_fn = bpf.load_func("xdp_packet_counter", BPF.XDP)
bpf.attach_xdp(interface, xdp_fn, 0)
return bpf
When the script terminates, detach_xdp_program
safely removes the eBPF program from the interface, ensuring the system returns to its normal state.
# Detach the eBPF program from the specified interface
def detach_xdp_program(bpf, interface):
bpf.remove_xdp(interface, 0)
Monitoring and Event Handling
The main
function orchestrates the entire process. It starts by registering the signal handler and defining the network interface to monitor (wlp0s20f3
).
# Main function to execute the script
def main():
# Register the signal handler for SIGTERM
signal.signal(signal.SIGTERM, handle_sigterm)
# Define the network interface to monitor
INTERFACE = "wlp0s20f3"
Next, it loads and attaches the eBPF program. It then gains access to the packet_count_map
(a shared data structure for counting packets) and opens a perf_buffer
to receive real-time debug events from the kernel, such as notifications about dropped packets.
# Load the eBPF program and attach it to the network interface
bpf = load_bpf_program()
attach_xdp_program(bpf, INTERFACE)
# Access the BPF map and open the perf buffer for debug events
packet_count_map = bpf.get_table("packet_count_map")
bpf["debug_events"].open_perf_buffer(print_debug_event)
The print_debug_event
function is a callback that processes these events. When the eBPF program drops a packet, this function formats the data and prints a message to the console.
def print_debug_event(cpu, data, size):
dest_ip = ctypes.cast(data, ctypes.POINTER(ctypes.c_uint32)).contents.value
print(f"Packet to {socket.inet_ntoa(struct.pack('!L', dest_ip))} dropped")
The script then enters an infinite loop to monitor packet counts. Every second, it reads the total count from the packet_count_map
, calculates the packets-per-second rate, and prints it. It also polls for any new debug events.
try:
print("Counting packets, press Ctrl+C to stop...")
prev_total_packets = 0
while True:
sleep(1)
total_packets = sum(counter.value for counter in packet_count_map.values())
packets_per_second = total_packets - prev_total_packets
prev_total_packets = total_packets
print(f"Packets per second: {packets_per_second}")
bpf.perf_buffer_poll(1)
Graceful Shutdown
The try...except...finally
block ensures that the program can be stopped cleanly with Ctrl+C
or a SIGTERM
signal. The finally
block guarantees that the eBPF program is always detached from the network interface, preventing resource leaks.
except (KeyboardInterrupt, TerminateSignal) as e:
print(f"\n{e}. Interrupting eBPF runner.")
finally:
print("Detaching eBPF program and exiting.")
detach_xdp_program(bpf, INTERFACE)
Finally, the if __name__ == "__main__":
guard ensures the main
function runs only when the script is executed directly.
# Execute the main function when the script is run directly
if __name__ == "__main__":
main()
Next, the probe.c
file contains the eBPF program that runs inside the Linux kernel. It uses XDP (eXpress Data Path) to inspect and filter network packets at the earliest possible point—right in the network driver—making it extremely fast.
Kernel-Space Setup
First, we include kernel headers that provide access to eBPF helpers and network data structures. We then define two key BPF maps:
BPF_ARRAY
: A single-element array namedpacket_count_map
to store a global packet counter.BPF_PERF_OUTPUT
: A perf buffer nameddebug_events
to send notifications about dropped packets to the user-space script.
#include
#include
#include
#include
#include
#include
BPF_ARRAY(packet_count_map, __u64, 1);
BPF_PERF_OUTPUT(debug_events);
The Main XDP Program
The xdp_packet_counter
function is the entry point for our eBPF program. It runs for every single packet that arrives on the attached network interface.
Its first job is to increment the global packet counter. It looks up the counter from packet_count_map
and atomically increments it. Using an atomic operation is crucial to prevent race conditions when multiple CPU cores process packets simultaneously.
int xdp_packet_counter(struct xdp_md *ctx) {
__u32 key = 0;
__u64 *counter;
counter = packet_count_map.lookup(&key);
if (!counter)
return XDP_ABORTED; // Abort if map lookup fails
// Atomically increment the counter
__sync_fetch_and_add(counter, 1);
// Define the blocked IP and call the filtering function
__be32 blocked_ip = (8 24) | (8 16) | (8 8) | 8;
return drop_packet_to_destination(ctx, blocked_ip);
}
Packet Filtering Logic
The drop_packet_to_destination
function contains the firewall’s core logic. It carefully inspects the packet to decide whether to drop it or let it pass.
-
Parse Headers: It starts by getting pointers to the packet’s data and performs bounds checks to ensure the Ethernet and IP headers are safely accessible within the packet’s memory region. This prevents the eBPF verifier from rejecting the program.
-
Check Protocol: It checks if the packet is an IP packet. If not, it’s immediately passed through with
XDP_PASS
.
static int drop_packet_to_destination(struct xdp_md *ctx, __be32 blocked_ip) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
// Safety check: ensure Ethernet header is within packet bounds
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
// Pass non-IP packets
if (eth->h_proto != bpf_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *iph = (struct iphdr *)(data + ETH_HLEN);
// Safety check: ensure IP header is within packet bounds
if ((void *)(iph + 1) > data_end)
return XDP_PASS;
// If the destination IP matches the blocked IP, drop the packet
if (iph->daddr == blocked_ip) {
__be32 daddr_copy = iph->daddr;
debug_events.perf_submit(ctx, &daddr_copy, sizeof(daddr_copy));
return XDP_DROP;
}
return XDP_PASS;
}
The code for this tutorial is taken from this beautiful talk. I recommend you to check it out.
Together they form a simple eBPF firewall that counts packets and blocks traffic to a specific IP address. The Python script manages the eBPF program lifecycle while the C code does the actual packet processing in kernel space.
$ sudo python3 runner.py
Results after running the program.
Conclusion
Many tech giants Netflix, Dropbox, Yahoo, LinkedIn, Alibaba, Datadog, Shopify, DoorDash use eBPF for network observability, infrastructure debugging, pod networking/security in Kubernetes, intrusion detection. Its widely used in security monitoring and Incident Response.
It will be a big miss if you did not adopt or at least know something about it. I hope this article bridges the gap. For more articles follow the newsletter.
Keep your files stored safely and securely with the SanDisk 2TB Extreme Portable SSD. With over 69,505 ratings and an impressive 4.6 out of 5 stars, this product has been purchased over 8K+ times in the past month. At only $129.99, this Amazon’s Choice product is a must-have for secure file storage.
Help keep private content private with the included password protection featuring 256-bit AES hardware encryption. Order now for just $129.99 on Amazon!
Help Power Techcratic’s Future – Scan To Support
If Techcratic’s content and insights have helped you, consider giving back by supporting the platform with crypto. Every contribution makes a difference, whether it’s for high-quality content, server maintenance, or future updates. Techcratic is constantly evolving, and your support helps drive that progress.
As a solo operator who wears all the hats, creating content, managing the tech, and running the site, your support allows me to stay focused on delivering valuable resources. Your support keeps everything running smoothly and enables me to continue creating the content you love. I’m deeply grateful for your support, it truly means the world to me! Thank you!
BITCOIN bc1qlszw7elx2qahjwvaryh0tkgg8y68enw30gpvge Scan the QR code with your crypto wallet app |
DOGECOIN D64GwvvYQxFXYyan3oQCrmWfidf6T3JpBA Scan the QR code with your crypto wallet app |
ETHEREUM 0xe9BC980DF3d985730dA827996B43E4A62CCBAA7a Scan the QR code with your crypto wallet app |
Please read the Privacy and Security Disclaimer on how Techcratic handles your support.
Disclaimer: As an Amazon Associate, Techcratic may earn from qualifying purchases.