package
0.0.7
Repository: https://github.com/scitags/flowd-go.git
Documentation: pkg.go.dev

# README

eBPF backend

This backend will set the IPv6 flow label in response to flow events. This implies this backend is only applicable for IPv6-based traffic flows which, given the WLCG's prospects, should be a reality sooner rather than later in the context of HEP. The backed is implemented as an eBPF program adhering to the CO:RE principles so that it's portable even across kernel versions and there no need to recompile it when the program starts. Given we leverage libbpf we don't have any dependencies on netlink(7),

The flow label is derived from the flow event's experiment and activity IDs and 5 random bits to fill up the 20 bits comprising the flow label. Be sure to check Wikipedia for more information on the structure of an IPv6 header.

Please note the eBPF backend is just a stub when targetting operating systems other than Linux, namely darwin (i.e. macOS).

On section Taming eBPF you can find much more detailed and involved technical documentation regarding the eBPF technology which can be a bit complex... Be sure to check the eBPF program's source on marker.bpf.c as it's littered with informative comments providing context for cryptic lines and concepts.

Bear in mind you can take a look at how marked IPv6 datagrams look by inspecting to provided scitags.pcapng traffic capture with Wireshark. Every ICMPv6 message contains a comment describing what you're looking at.

Configuration

Please refer to the Markdown-formatted documentation at the repository's root for more information on available options. The following replicates the default configuration:

{
    "backends": {
        "ebpf": {
            "targetInterface": "lo",
            "removeQdisc": true,
            "programPath": "",
            "markingStrategy": "flowLabel",
            "debugMode": true
        }
    }
}

Taming eBPF

Our objective is simply getting an eBPF program to compile so that it can be deployed 'everywhere', simple as that!

Now, the reality is a bit more complex than one would think! Long story short, we've settled on leveraging the rather new Compile-Once Run-Everywhere (CO:RE) methodology to accomplish just that.

TL:DR

We have distilled the information below into a Makefile automating the process of building the eBPF program. One can now simply run:

$ make

And the compiled eBPF program, marker.bpf.o will be generated. Please bear in mind one can enable the program's debugging output by passing the DEBUG option when invoking make:

$ make DEBUG=yes

Debug information will be available on /sys/kernel/debug/tracing/trace_pipe.

Also, running utilities such as objdump(1) and ldd(1) on the generated program is quite informative. We recommend that you give it a go!

Achieving portability in the context of eBPF

Put simply, eBPF programs run in a Kernel VM. Now, changes in the kernel types (i.e. addition of struct fields) can really throw eBPF programs in disarray! When an eBPF program is compiled it targets the kernel it was compiled on, but that needn't be the kernel a client is running...

An initial (and rather popular) approach hinges on compiling these eBPF programs at runtime. This is exactly what the BPF Compiler Collection (BCC) does. However, this approach comes with its downsides, the main one being that the target machines need to have a clang/llvm installation capable of compiling the eBPF program at runtime. At the end of this document there's a bit more context on other subtle-yet-important problems with this approach.

Now, the other approach relies on generating relocatable eBPF object code which can then be adapted to the target kernel. This is possible thanks to the libbpf project. There is one key requirement though, the target kernel must expose BPF type information through /sys/kernel/btf/vmlinux. This will be the case for kernels compiled with the CONFIG_DEBUG_INFO_BTF=y option. This is the case on a vanilla AlmaLinux 9 installation. One can also check whether this option was used when compiling the kernel with:

$ grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r)

If a kernel has not been compiled with this option it should be recompiled to support CO:RE, so the best approach rapidly becomes going back to BCC.

What does CO:RE look like?

From the eBPF's program point of view not much changes really. One need stick to 'vanilla' primitives (i.e. no BCC are available) and the rest pretty much follows. Compilation does require some additional tooling, namely LLVM, Clang and libbpf-devel to get the necessary helpers. On AlmaLinux 9 this can be achieved with:

# Enable the CRB Repo (check https://wiki.almalinux.org/repos/Extras.html)
$ dnf install epel-release; dnf config-manager --set-enabled crb

# Install libbpf together with headers, llvm, clang and the auxiliary bpftool
$ yum install libbpf-devel libbpf-static clang llvm bpftool

With that, we can compile right away! We just need to generate the vmlinux.h header containing the definition of all kernel structs which can be easily accomplished with:

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

Bear in mind that we can leverage 'precooked' headers if we need to. This is the case for older kernels, for instance. Be sure to check this SO answer and the btfhub-archive repo fora collection of these ready-to-eat files.

At any rate, we can now generate out relocatable eBPF object code with:

$ clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I . -c marker.bpf.c -o marker.bpf.o

We're ready to load that *.o now! By the way, this section was largely based on this article.

Bundling libbpf

Given libbpf is the one in charge of making runtime transformations, it should be installed on client machines. However, the statically compiled library (i.e. libbpf.a) can be embedded into Go binaries so that there are really no external dependencies whatsoever. This last approach is the one used by tracee, for instance.

At any rate, if libbpf is not bundled the end user can always install it with:

$ yum install libbpf

That's much lighter than a full blown Clang/LLVM install!

Building with libbpf

Given libbpf is implemented in C, we need to adjust the environment we compile programs using it on so that everything works as intended. This basically translates into fixing the compiler and 'telling' it where to find the libbpf headers and libraries:

$ CC=gcc CGO_CFLAGS="-I /usr/include/bpf" CGO_LDFLAGS="/usr/lib64/libbpf.a" go build -o libbpfgo-prog

The above example leverages a statically compiled libbpf (hence the *.a extension), but the idea is equivalent when targetting shared libraries. Please bear in mind this one-liner has been extracted from the great documentation over here.

Are there any downsides?

Well, the most apparent is that one cannot rely on BCC's Macros any more, which means some additional manual implementations become necessary... However, given the functions defined in bpf-helpers(7) it's rather feasible to replicate whatever functionality's needed.

Other considered solutions

Aside from libbpf, we also considered leveraging ebpf by the Cilium team which is a great tool in its own right. However, even though it might be possible, it wasn't obvious (at least for us) from the documentation how one could leverage a CO:RE model (see the doc)... This discussion also shed some very valuable information informing this choice.

BCC was a clear contender, but the first-class API is written in Python despite gobpf being available. Again, it's use would imply 'forcing' Clang/LLVM on whoever wanted to run this!

Native Go implementations of netlink(7) such as vishvananda/netlink offer some [examples][go-netlink-bpf] on how to load eBPF programs, but they are rather bare-bones and rely on directly interacting with the kernel!

The go-tc project was also taken into consideration, but they bare-bones way in which eBPF programs are to be defined was a deal breaker too... The documentation even has an example on loading an eBPF program!

Further reading

Information on eBPF and co. is rather sparse and heavily technical. However, there are some great resources out there who deserve mentioning. These include articles by Andrii Nakryiko covering [bootstraping libbpf][libbpf-bootstrap-blog] and CO:RE. The libbpf-bootstrap repository is a treasure trove of information too!

The BPF series over at The Gray Node is also a great source!

Please note this directory's contents are largely based on this example offered by the AquaSecurity team!

We shouldn't forget that, in the end, we're trying to interact with a Linux kernel. This can be accomplished with the already existing and quite intuitive CLIs that we use on a daily basis. This example shows how all this can be 'automated' with a small shell script!

Be sure to check the eBPF docs (especially the section on BPF_PROG_TYPE_SCHED_CLS) as they contain a wealth of high-quality, nicely presented information.

# Functions

No description provided by the author

# Structs

No description provided by the author
No description provided by the author