Skip to content

A guide that sums up some information about eBPF for beginners

License

Notifications You must be signed in to change notification settings

O-X-L/ebpf-getting-started-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

eBPF Getting-Started Guide

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


Use Cases

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:


Concepts

See also: eBPF Docs - Concepts

Pinning

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/

Concurrency / Race Conditions

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.


Building: Raw vs eBPF-go

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:


Raw

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:

  • Traffic control:

    # 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

eBPF-go

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.


Debug Output (Print)

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)

IPv6

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]);
}

About

A guide that sums up some information about eBPF for beginners

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project