P4C
The P4 Compiler
Loading...
Searching...
No Matches
uBPF Backend

The p4c-ubpf compiler allows to translate P4 programs into the uBPF programs. We use the uBPF implementation provided by the P4rt-OVS switch. The uBPF VM is based on the open-source implementation provided by IOVisor.

The P4-to-uBPF compiler accepts only the P4_16 programs written for the ubpf_model.p4 architecture model.

The backend for uBPF is mostly based on P4-to-eBPF compiler. In fact, it implements the same concepts, but generates C code, which is compatible with the user space BPF implementation.

Background

P4

Please, refer to the overview of P4 written in eBPF Backend.

uBPF

Why uBPF? The uBPF Virtual Machine can be used in any solution implementing the kernel bypass (e.g. DPDK apps).

The uBPF project re-implements the eBPF kernel-based Virtual Machine. While the BPF programs are intented to run in the kernel, the uBPF project enables running the BPF programs in user-space applications. It contains eBPF assembler, disassembler, interpreter, and JIT compiler for x86-64.

Moreover, contrary to the eBPF implementation, uBPF is not licensed under GPL. The uBPF implementation is licensed under Apache License, version 2.0.

Compiling P4 to uBPF

The scope of the uBPF backend is wider than the scope of the eBPF backend. Except for simple packet filtering the P4-to-uBPF compiler supports also P4 registers and programmable actions including packet's modifications and tunneling. For further details refer to uBPF architecture model.

Note! Due to the reason that the standard_metadata has been introduced to the uBPF model at 15th of May 2020 the old P4 programs will not work anymore. You should update your P4 program to the latest architecture model. Alternatively, you can also specify the old version of uBPF model: #define UBPF_MODEL_VERSION 20200304 before #include <ubpf_model.p4>.

The current version of the P4-to-uBPF compiler translates P4_16 programs to programs written in the C language. This program is compatible with the uBPF VM and the clang compiler can be used to generate uBPF bytecode.

Translation between P4 and C

We follow the convention of the P4-to-eBPF compiler so the parser translation is presented in the table Translating parsers from P4-to-eBPF. The translation of match-action pipelines is presented in the Translating match-action pipelines table from P4-to-eBPF.

However, we introduced some modifications, which are listed below:

  • The generated code uses user-level data types (e.g. uint8_t, etc.).
  • Methods to extract packet fields (e.g. load_dword, etc.) have been re-implemented to use user-space data types.
  • The uBPF helpers are imported into the C programs.
  • We have added mark_to_drop() extern to the ubpf model, so that packets to drop are marked in the P4-native way.
  • We have added support for P4 registers implemented as BPF maps

How to use?

The sample P4 programs are located in the examples/ directory. We have tested them with the P4rt-OVS switch - the Open vSwitch that can be extended with BPF programs at runtime. See the detailed tutorial on how to run and test those examples.

In order to generate the C code use the following command:

p4c-ubpf PROGRAM.p4 -o out.c

This command will generate out.c and the corresponding out.h file containing definitions of the packet structures and BPF maps.

Once the C program is generated it can be compiled using:

clang -O2 -target bpf -c out.c -o /tmp/out.o

The output file (out.o) can be injected to the uBPF VM.

uBPF Backend test programs

This Section contains description of the basic P4 programs, which were used to test the functionality of the P4-to-uBPF compiler. All tests have been run on the P4rt-OVS switch.

You can use Vagrantfile to set up a test environment.

Before any experiment the following commands need to be invoked:

bash @section compile-p4-program-to-c-code compile P4 program to C code $ p4c-ubpf -o test.c PROGRAM.p4 $ sudo ovs-ofctl del-flows br0 @section compile-testc-to-bpf-machine-code compile test.c to BPF machine code $ clang-6.0 -O2 -I .. -target bpf -c test.c -o /tmp/test.o @section load-filter-bpf-program Load filter BPF program $ sudo ovs-ofctl load-bpf-prog br0 1 /tmp/test.o @section setup-rules-to-forward-traffic-bidirectional Setup rules to forward traffic (bidirectional) $ sudo ovs-ofctl add-flow br0 in_port=2,actions=prog:1,output:1 $ sudo ovs-ofctl add-flow br0 in_port=1,actions=prog:1,output:2

Note! The P4-uBPF compiler works properly with clang-6.0. We noticed some problems when using older versions of clang (e.g. 3.9).

Examples

This section presents how to run and test the P4-uBPF compiler.

Packet modification

This section presents a P4 program, which modifies the packet's fields.

IPv4 + MPLS (simple-actions.p4)

Key: Source IPv4 address

Actions:

  • mpls_decrement_ttl
  • mpls_set_label
  • mpls_set_label_decrement_ttl
  • mpls_modify_tc
  • mpls_set_label_tc
  • mpls_modify_stack
  • change_ip_ver
  • ip_swap_addrs
  • ip_modify_saddr
  • Reject

Sample usage:

bash @section template-ovs-ofctl-update-bpf-map-bridge-program-id-map-id-key-key-data-value-value-data Template: ovs-ofctl update-bpf-map <BRIDGE> <PROGRAM-ID> <MAP-ID> key <KEY-DATA> value <VALUE-DATA> $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 0 0 0 0 0 0 0 0 0 0 0 0 # decrements MPLS TTL $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 1 0 0 0 24 0 0 0 0 0 0 0 # sets MPLS label to 24 $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 2 0 0 0 24 0 0 0 0 0 0 0 # sets MPLS label to 24 and decrements TTL $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 3 0 0 0 3 0 0 0 0 0 0 0 # modifies MPLS TC (set value to 3) $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 4 0 0 0 24 0 0 0 1 0 0 0 # sets MPLS label to 24 and TC to 1 $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 5 0 0 0 1 0 0 0 0 0 0 0 # modifies stack value of MPLS header $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 6 0 0 0 6 0 0 0 0 0 0 0 # changes IP version to 6. $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 7 0 0 0 0 0 0 0 0 0 0 0 # swaps IP addresses $ sudo ovs-ofctl update-bpf-map br0 1 0 key 14 0 16 172 value 8 0 0 0 1 0 16 172 0 0 0 0 # sets source IP address to 172.16.0.1

IPv6 (ipv6-actions.p4)

The aim of this example is to test modification of wider packet's fields. Thus, we have used the IPv6 headers.

The match key is source IPv6 address. The P4 program implements three actions:

  • ipv6_modify_dstAddr - modifies IPv6 destination address;
  • ipv6_swap_addr - swaps IPv6 addresses.
  • set_flowlabel - tests modification of custom-length field, where length % 8 != 0.

Sample usage: bash @section changes-destination-ipv6-address-from-fe80a0027fffe7eb95-to-e00-simple-random-value Changes destination IPv6 address from fe80::a00:27ff:fe7e:b95 to e00::: (simple, random value) $ sudo ovs-ofctl update-bpf-map br0 1 0 key 254 128 0 0 0 0 0 0 10 0 39 255 254 21 180 17 value 0 0 0 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 @section swaps-source-and-destination-ipv6-addresses Swaps source and destination IPv6 addresses $ sudo ovs-ofctl update-bpf-map br0 1 0 key 254 128 0 0 0 0 0 0 10 0 39 255 254 21 180 17 value 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 @section sets-flow-label-to-1 Sets Flow Label to 1. $ sudo ovs-ofctl update-bpf-map br0 1 0 key 254 128 0 0 0 0 0 0 10 0 39 255 254 21 180 17 value 2 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Registers

This section presents P4 programs, which use registers. Register can be declared this way:

Register<value_type, key_type>(number_of_elements) register_t;

The parameters are as follows:

  • value_type - is bit array type (i.e. bit<32>) or struct like type
  • key_type - is bit array type (i.e. bit<32>) or struct like type
  • number_of_elements - the maximum number of key-value pairs

Currently, the ubpf architecture model does not allow to initialize registers with default values. Initialization has to be done by a control plane.

Rate limiter (rate-limiter.p4)

The rate limiter uses two registers. First which counts the number of packets and second which holds timestamps.

This rate limiter limits the number of packets per second. Responsible for that are two variables BUCKET_SIZE and WINDOW_SIZE placed in rate-limiter.p4 file. For instance now BUCKET_SIZE has value of 10 and WINDOW_SIZE has value of 100. It means that 10 packets are passed in 100 ms window. It also means 100 packets per second. If you send 1470 Bytes width packets the bit rate should not exceed 1.176 Mbit/s (1470B * 8 * (10/100ms)).

Due to registers limitation before starting your own tests initialize rate limiter registers with zeros:

bash @section initalizes-timestamp_r-register Initalizes timestamp_r register $ sudo ovs-ofctl update-bpf-map br0 1 0 key 0 0 0 0 value 0 0 0 0 @section initalizes-count_r-register Initalizes count_r register $ sudo ovs-ofctl update-bpf-map br0 1 1 key 0 0 0 0 value 0 0 0 0

To measure the bandwidth use the iperf tool:

Start a iperf UDP server

bash $ iperf -s -u

Then, run iperf client:

bash $ iperf -c <server_ip> -b 10M -l 1470

Rate limiter (rate-limiter-structs.p4)

The same rate limiter as above, but implemented using structs.

Packet counter (packet-counter.p4)

The packet counter counts every packet passed via the program. Before tests initialize packet counter register with zeros:

bash @section initalizes-packet_counter_reg-register Initalizes packet_counter_reg register $ sudo ovs-ofctl update-bpf-map br0 1 0 key 0 0 0 0 value 0 0 0 0

Then generate any network traffic. To check if the program counts packets use i.e.

bash $ watch sudo ovs-ofctl dump-bpf-map br0 1 0

Simple firewall (simple-firewall.p4)

This is very simple example of stateful firewall. Every TCP packet is analyzed to track the state of the TCP connection. If the traffic belongs to known connection it is passed. Otherwise, it is dropped.
Notice that the example program uses hash function which is constrained to hash only 64 bit values - that's why TCP connection is identified via IP source and destination address. This is the known limitation of the uBPF backend used in P4rt-OVS (to be fixed in the future).

Due to registers limitation before starting your own tests initialize simple firewall registers with zeros:

bash @section initalizes-conn_state-register-key-is-a-output-from-a-hash-function-for-client192168110-and-server-19216811 Initalizes conn_state register (key is a output from a hash function for client(192.168.1.10) and server (192.168.1.1)) $ sudo ovs-ofctl update-bpf-map br0 1 0 key 172 192 20 5 value 0 0 0 0 @section initalizes-conn_srv_addr-register-key-is-a-output-from-a-hash-function-for-client192168110-and-server-19216811 Initalizes conn_srv_addr register (key is a output from a hash function for client(192.168.1.10) and server (192.168.1.1)) $ sudo ovs-ofctl update-bpf-map br0 1 1 key 172 192 20 5 value 0 0 0 0

To test simple firewall you can use as an example ptf/simple-firewall-test.py test.

Tunneling

This section presents more complex examples of packet tunneling operations. There are two P4 programs used:

  • tunneling.p4, which implements MPLS tunneling,
  • vxlan.p4, which implements more complex packet tunneling: VXLAN.

VXLAN

To run example compile vxlan.p4 with p4c and then clang-6.0.

Sample usage:

bash @section sets-action-vxlan_decap-value-0-for-packets-matching-rule-vni25-key-25 Sets action vxlan_decap() (value 0) for packets matching rule VNI=25 (key 25) @section handled-by-the-table-upstream_tbl-map-id-0-and-bpf-prog-1 handled by the table upstream_tbl (map id 0) and BPF prog 1. sudo ovs-ofctl update-bpf-map br0 1 0 key 25 0 0 0 value 0 0 0 0 @section sets-action-vxlan_encap-value-0-for-packets-matching-rule-ip-dstaddr17216014-key-14-0-16-172 Sets action vxlan_encap() (value 0) for packets matching rule IP dstAddr=172.16.0.14 (key 14 0 16 172) @section handled-by-the-table-downstream_tbl-map-id-1-and-bpf-prog-1 handled by the table downstream_tbl (map id 1) and BPF prog 1. sudo ovs-ofctl update-bpf-map br0 1 1 key 14 0 16 172 value 0 0 0 0

GPRS Tunneling Protocol (GTP)

To run example compile gtp.p4 with p4c and then clang-6.0.

To test encapsulation:

bash @section for-downstream_tbl-id-1-sets-action-gtp_encap-value-0-and-gtp-teid3-for-packets-with-destination-ip-address-17216014 For downstream_tbl (ID 1) sets action gtp_encap() (value 0) and GTP TEID=3 for packets with destination IP address 172.16.0.14. $ sudo ovs-ofctl update-bpf-map br0 1 1 key 14 0 16 172 value 0 0 0 0 3 0 0 0

To test decapsulation:

bash @section for-upstream_tbl-id-0-sets-action-gtp_decap-for-packets-matching-gtp-teid3 For upstream_tbl (ID 0) sets action gtp_decap() for packets matching GTP TEID=3. $ sudo ovs-ofctl update-bpf-map br0 1 0 key 3 0 0 0 value 0 0 0 0

Scapy can be used to easily test GTP protocol:

python >>> load_contrib('gtp') >>> p = Ether(dst='08:00:27:7e:0b:95', src='08:00:27:15:b4:11')/IP(dst='172.16.0.14', src='172.16.0.12')/UDP(sport=2152,dport=2152)/GTPHeader(teid=3)/IP(dst='172.16.0.14', src='172.16.0.12')/ICMP() >>> sendp(p, iface='eth1')

uBPF Backend testing

Tests use two VMs:

  • switch - on this VM we run PTF tests
  • generator - expose two interfaces to P4rt-OVS switch installed on switch VM

Note. As P4rt-OVS (the test uBPF target) is built on top of DPDK the tests require to be run in virtual environment with two VMs (generator + switch).

Steps to Run Tests:

  1. Install Virtualbox and Vagrant on you machine:
    `sudo apt install -y virtualbox vagrant`
    
  1. Install and configure environment for tests

    bash $ cd environment $ vagrant up

  2. Run PTF agent on generator machine

    bash $ vagrant ssh generator $ cd ptf/ptf_nn/ $ sudo python ptf_nn_agent.py --device-socket 0@tcp://192.168.100.20:10001 -i 0-1@enp0s8 -i 0-2@enp0s9 -v

  3. Compile P4 programs on switch machine

    bash $ vagrant ssh switch $ cd p4c/backends/ubpf/tests $ sudo python compile_testdata.py

  4. Run tests on switch machine

    bash $ vagrant ssh switch $ cd p4c/backends/ubpf/tests $ sudo ptf --failfast --test-dir ptf/ --device-socket 0-{1-2}@tcp://192.168.100.20:10001 --platform nn

    Important

    After any system's reboot the below scripts have to be run again:

    bash $ cd /vagrant $ ./configure_dpdk.sh #This script configures dpdk interfaces that p4rt-ovs switch use $ ./run_switch.sh # This script runs p4rt-switch

    In case of tests errors please check if the p4rt-ovs switch is up and running (ie. ps aux).

-->

Custom C extern functions

The P4 to uBPF compiler allows to define custom C extern functions and call them from P4 program as P4 action.

The design of this feature is identical to p4c-ebpf. See the P4 to eBPF documentation to learn how to use this feature. Note that the C extern function written for p4c-ubpf must be compatible with userspace BPF VM.

Known limitations

  • No support for some P4 constructs (meters, counters, etc.)

Contact

Tomasz OsiƄski <tomas.nosp@m.z.os.nosp@m.inski.nosp@m.2@or.nosp@m.ange..nosp@m.com>

Mateusz Kossakowski <mateu.nosp@m.sz.k.nosp@m.ossak.nosp@m.owsk.nosp@m.i@ora.nosp@m.nge..nosp@m.com>