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.

p4c-ubpf-architecture-model.png

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 [email protected]

Quick links

BPF and XDP Reference Guide

Background on P4 and eBPF

P4-uBPF - presentation from Open vSwitch and OVN Fall 2019 conference

P4rt-OVS - Github repository