WARNING: This guide is still work-in-progress.
We had some problems finding simple eBPF examples that worked out-of-the box and provided containerized build-environments.
This getting-started guide should help new eBPF-users to get started more quickly.
If you have ideas on how to extend or improve this guide => open an issue or email us
BPF can be used for many use cases.
If you are new to it, you should look into the program types to get an idea of what you can use it for.
They differ in the way they are called (some have hooks), the return values that are expected and thus the functionality the modules of this type can provide.
Examples:
- BPF_PROG_TYPE_XDP => can process network traffic very early on and thus can be used for defending against (D)DOS as described in this Cloudflare blog
- BPF_PROG_TYPE_SOCKET_FILTER => can be used to extend IPTables/NFTables rulesets as described in this Cloudflare blog
- BPF_PROG_TYPE_LWT_IN => apply route-based filters
- BPF_PROG_TYPE_LSM => can implement security-filters for specific system-events
See also: eBPF Docs - Concepts
As we've looked into the integration of eBPF modules for NFTables/IPTables - we've seen pinned-objects being mentioned.
BPF can have a filesystem (/sys/fs/bpf) that contains references to currently loaded BPF objects.
https://docs.ebpf.io/linux/concepts/pinning/
You have to be aware of possible race conditions when writing data to shared objects like maps!
The docs mention some options to work around such issues.
We would recommend using a containerized build-environment. For the examples in this repository your will have to have Docker installed.
Here we will cover two ways in which you can use eBPF:
Compile the module directly.
To run: make build_raw or bash scripts/build_raw.sh
Look into the script and docker/Dockerfile_build_raw to get to know how it is done.
Usage examples:
-
# Load the clsact qdisc on the interface (if not already loaded) tc qdisc add dev eth0 clsact # Add a BPF program as a classifier (filter) on ingress tc filter add dev eth0 ingress bpf da obj bpf_prog.o sec classifier # Add a BPF program as an action on egress tc filter add dev eth0 egress bpf da obj bpf_prog.o sec action
Load module in go user-space process and get push information from eBPF to go to process it.
For more examples see: cilium/ebpf/blob/main/examples
To run: make build_go or bash scripts/build_go_single.sh <relative-src-path>
Look into the script and docker/Dockerfile_build_go to get to know how it is done.
You can use bpf_trace_printk(fmt, sizeof(fmt)); to temporarily enable debug-output. See: eBPF Docs
You can read it via: sudo cat /sys/kernel/tracing/trace | grep BPF (if you add prefix like [eBPF] to the message)
NOTE: bpf_trace_printk can only take 3 format-parameters.
Examples:
-
Format IPv4
const char log_ip4[] = "[eBPF] IP4: %pI4\n"; bpf_trace_printk(log_ip4, sizeof(log_ip4), &ip4->saddr); // bpf_trace_printk: [eBPF] IP4: 127.0.0.1
-
Format IPv6
const char log_ip6[] = "[eBPF] IP6: %pI6\n"; bpf_trace_printk(log_ip6, sizeof(log_ip6), &ip6->saddr); // bpf_trace_printk: [eBPF] IP6: fe80:0000:0000:0000:c87f:acff:fe69:287a
-
Write hex of int
const char log_ip6_p1[] = "[eBPF] IP6: Parts 1+2 (0x%x 0x%x)\n"; bpf_trace_printk(log_ip6_p1, sizeof(log_ip6_p1), bpf_ntohl(ip6o.addr[0]), bpf_ntohl(ip6o.addr[1])); const char log_ip6_p2[] = "[eBPF] IP6: Parts 3+4 (0x%x 0x%x)\n"; bpf_trace_printk(log_ip6_p2, sizeof(log_ip6_p2), bpf_ntohl(ip6o.addr[2]), bpf_ntohl(ip6o.addr[3])); // bpf_trace_printk: [eBPF] IP6: Parts 1+2 (0xfe800000 0x0) // bpf_trace_printk: [eBPF] IP6: Parts 3+4 (0xc87facff 0xfe69287a)
We've found that you can run into issues with IPv6 addresses as they have 128-bit vs the 32-bit of IPv4.
If you want to pass it to your user-space process you might need/want to split it into 32-bit chunks:
struct ip6_addr {
__u32 addr[4];
};
static __always_inline void process_ip6(struct ethhdr *ethhdr) {
struct ipv6hdr *ip6h = (void *)(ethhdr + 1);
// create and populate
struct ip6_addr ip6_src;
__builtin_memcpy(&ip6_src.addr, &ip6h->saddr, sizeof(ip6_src.addr));
// convert network to host
ip6_src.addr[0] = bpf_ntohl(ip6_src.addr[0]);
ip6_src.addr[1] = bpf_ntohl(ip6_src.addr[1]);
ip6_src.addr[2] = bpf_ntohl(ip6_src.addr[2]);
ip6_src.addr[3] = bpf_ntohl(ip6_src.addr[3]);
// debug log
const char log_ip6_p1[] = "[eBPF] IP6: Parts 1+2 (0x%x 0x%x)\n";
bpf_trace_printk(log_ip6_p1, sizeof(log_ip6_p1), ip6o.addr[0], ip6o.addr[1]);
const char log_ip6_p2[] = "[eBPF] IP6: Parts 3+4 (0x%x 0x%x)\n";
bpf_trace_printk(log_ip6_p2, sizeof(log_ip6_p2), ip6o.addr[2], ip6o.addr[3]);
}