Posted by Tomasz Osiński on June 1, 2020
Introduction
As the ecosystem around P4 continues to develop, more and more programmable targets are emerging. The P4 compiler has already support for the next-generation Linux datapath such as eBPF/XDP. However, in certain settings, it is necessary to extend user-space packet processing applications at run time. This can be achieved by using the user-space BPF (uBPF) Virtual Machine, which is a re-implementation of in-kernel eBPF VM, and provides a user-space execution environment that is extensible at runtime.
This blog post introduces p4c-ubpf
, a new back-end for the p4c
compiler that enables programming packet processing modules for the uBPF VM. p4c-ubpf
makes it possible to execute the P4 code in any solution implementing the kernel bypass including DPDK, AF_XDP, and others.
Userspace BPF for Packet Processing
Why uBPF?
The uBPF project [1] re-implements the eBPF kernel-based Virtual Machine. It provides an eBPF assembler, disassembler, interpreter, and JIT compiler for x86-64. While BPF was originally designed to enable safe execution of code in the kernel, the uBPF project enables running BPF programs in user-space. Therefore, the uBPF Virtual Machine can be easily integrated with kernel bypass (e.g. DPDK and AF_XDP) applications.
In addition, unlike the GPL-licensed eBPF implementation, uBPF uses the Apache License version 2.0, which adds additional flexibility. Finally, the userspace eBPF uses a less complex virtual machine, which means that some constructs are not supported (e.g. tail calls), but also lifts some restrictions found in eBPF, such as the 512 byte stack-size limit.
P4rt-OVS
P4rt-OVS is an extension of the widely-used Open vSwitch (OVS) that integrates the BPF virtual machine into the user-space datapath. In particular, it supports injecting BPF programs at run time, making it possible to extend the packet-processing pipeline without recompiling OVS. At a technical level, BPF programs act as programmable actions and are referenced as a new OVS action (keyword prog
) in the OpenFlow tables. The actions may read and write persistent maps (hash tables) to maintain per-flow state, and they may also write to packet headers. The p4c-ubpf
architecture model exposes a broad range of P4 features including stateful registers.
Compiling P4 to uBPF
The P4-to-uBPF compiler follows the same convention as other eBPF and XDP p4c
backends. That is, the P4 program is first translated to a representation in C, which is then compiled to BPF using clang
. This design is modular, as the P4-to-C compiler does not need to generate low-level BPF instructions.
------------ ---------
P4_16 ---> | p4c-ubpf | ---> C ----> | clang | --> uBPF
------------ ---------
The architecture model for P4c-uBPF is depicted below. It consists of a single parser, a match-action pipeline, and a deparser.
The p4c-ubpf compiler provides also a library of extern functions that implement features not directly supported in the P4 language. These functions can be called from the P4 program as a normal action. The architecture model supports operations such as hash functions, stateful registers, timestamps, and checksums. The full specification of the architecture model can be seen at this link.
Translating P4 to C
The main operation performed by p4c-ubpf is translating from P4 to C. The following tables provide a brief summary of how each P4 construct is mapped to a corresponding C construct. Note that the translation is very similar to p4c-ebpf
and p4c-xdp
.
Translating parsers
P4 Construct | C Translation |
---|---|
header | struct type with an additional valid bit |
struct | struct |
parser state | code block |
state transition | goto statement |
extract | load/shift/mask data from packet buffer |
Translating match-action pipelines
P4 Construct | C Translation |
---|---|
table | eBPF map |
table key | struct type |
table actions block | tagged union with all possible actions |
action arguments | struct |
table reads | eBPF map’s lookups |
action body | code block |
table apply | switch statement |
registers | additional eBPF map |
register reads | eBPF map’s lookups |
register writes | eBPF map’s updates |
Translating deparsers
P4 Construct | C Translation |
---|---|
apply block | code block + adjusting packet’s size |
emit operation | header’s validity check + write/shift/mask data to packet buffer |
p4c-ubpf vs. other BPF-related compilers
Compared to other compilers from P4 to BPF, p4c-ebpf
generates programs for the Linux TC subsystem and p4c-xdp
targets the XDP kernel hook, whereas p4c-ubpf
generates user-space code. There are several differences in functionality that these P4 backends support. The table below provides a brief comparison.
Comparsion of features provided by p4c-ebpf, p4c-xdp and p4c-ubpf
Feature | p4c-ebpf | p4c-xdp | p4c-ubpf |
---|---|---|---|
Packet filtering | YES | YES | YES |
Packet’s modifications & tunneling | NO | YES | YES |
Simple packet forwarding | YES | YES | YES |
Registers | NO | NO | YES |
Counters | YES | YES | NO |
Checksum computation | NO | YES | YES |
Summary
The uBPF backend for the P4 compiler supports applications that processes packets in user space. It has already been used to implement various simple applications such as a stateful firewall, a rate limiter, and GTP tunneling. When used in tandem with P4rt-OVS, p4c-ubpf
makes it possible to implement new network protocols—something that is not currently supported by Open vSwitch. See this link for more examples.
We encourage community to start playing with uBPF backend, report bugs and propose new enhancements. For questions or comments, please send an email to tomasz.osinski2@orange.com.
Quick links
P4-uBPF - presentation from Open vSwitch and OVN Fall 2019 conference