Open-source project
p4lang/behavioral-model avatar
p4lang/behavioral-model

bmv2 interprets your P4 program, which is exactly why it is fast enough to debug on

The reference P4 software switch

658 stars401 forksC++NOASSERTION

At a glance

What is it?
p4lang/behavioral-model, known as bmv2, is the reference P4 software switch: it takes the JSON a P4 compiler emits and interprets it, rather than compiling packets to native code. That makes it slower than a production switch by design, and it makes it the right tool for developing and debugging P4 data planes and their control planes.
Who is it for?
Adopt bmv2 when you are writing a P4 program or the control plane that drives it and you need a switch you can step through, inspect and reset in seconds. Do not put it in a path where throughput matters, because the project states plainly that its performance is significantly less than a production grade software switch.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

An interpreter, not a compiler, and that is the whole design

The first thing to understand about bmv2 is what it does not do. The project states that the software switch takes as input a JSON file generated from your P4 program by a P4 compiler, and interprets it to implement the packet processing behaviour specified by that P4 program.

That single sentence defines the project's place in the toolchain. A production data plane compiler turns your P4 into native code or into a hardware specific configuration, and then that thing runs packets. bmv2 takes the intermediate JSON and interprets it, executing the match action logic instruction by instruction as packets arrive.

The consequence for you is a change in workflow rather than in results. The behaviour under test is the behaviour you wrote, but you can inspect it, set breakpoints in it, dump its internal state and reset it between test cases, because there is no compiled artefact to work around. For developing, testing and debugging P4 data planes and the control plane software written for them, that is the point.

The switch is written in C++17, and the repository contains several variations of the behavioural model, for example simple_switch, simple_switch_grpc and psa_switch. Those names are not arbitrary: they encode which target architecture and which control plane API the interpreter is exposing.

The audience is therefore network engineers and researchers with a P4 program in hand, not operators looking for a switch to deploy.

The packaged install is one apt command

bmv2 has package support for several Ubuntu and Debian distributions, and that is the route most people should take.

For Ubuntu 20.04 and newer, the repository is added with a few commands:

bash
. /etc/os-release
echo "deb http://download.opensuse.org/repositories/home:/p4lang/xUbuntu_${VERSION_ID}/ /" | sudo tee /etc/apt/sources.list.d/home:p4lang.list
curl -fsSL "https://download.opensuse.org/repositories/home:p4lang/xUbuntu_${VERSION_ID}/Release.key" | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/home_p4lang.gpg > /dev/null
sudo apt update
sudo apt install p4lang-bmv2

The pattern is worth internalising rather than copying blindly. The distribution identifier is read from /etc/os-release, the repository line is written into apt's sources directory under a home:p4lang name, and the release key is fetched and dearmoured into the trusted keys directory. Debian 11 follows the same shape with a Debian_11 repository path.

If you cannot use a repository, the documentation points at the OpenSUSE build service page for home:p4lang/p4lang-bmv2, where you pick your operating system version and download the .deb file, then install it with:

bash
    git clone https://github.com/p4lang/behavioral-model.git

The caveat stated there is worth remembering: you need to download a new file each time you want to upgrade bmv2. There is no upgrade path in the manual route, so a repository is much more convenient if your environment allows one.

Building from source means thrift, nanomsg and a decision between two build systems

Source builds are documented for Ubuntu 20.04, Fedora and macOS, and the dependency list is long enough that the project ships helpers for it.

The first step is the clone:

bash
    sudo apt-get install -y automake cmake libgmp-dev \
        libpcap-dev libboost-dev libboost-test-dev libboost-program-options-dev \
        libboost-system-dev libboost-filesystem-dev libboost-thread-dev \
        libevent-dev libtool flex bison pkg-config g++ libssl-dev

On Ubuntu 20.04 the build dependencies come from apt:

bash
    ./autogen.sh
    ./configure
    make
    sudo make install  # if you need to install bmv2

Two libraries have to be installed from source as well: thrift 0.11.0 or later, tested up to 0.13, and nanomsg 1.0.0 or later. The repository ships install scripts under ci/ for this, and the top level install_deps.sh script installs everything the Ubuntu 20.04 path needs. The CLI additionally requires the pynng Python package at version 0.9.0, with ci/install-pynng.sh provided for that.

The CI configuration runs on Ubuntu 22.04 and on ubuntu-latest, which the documentation notes is 24.04 as of 2025-Mar-05.

Two build systems are offered. The autoconf based build is the recommended one:

On Linux you may then need to run sudo ldconfig to refresh the shared library cache. The CMake build exists and is covered by CI, and its documented difference is that it produces a single version of each library while the autoconf build produces both static and shared versions for every library.

Debug logging is on by default, and that changes what you measure

A detail that catches people out on their first run is that debug logging is enabled by default in bmv2.

The documentation says that if you want to disable it for performance reasons you can pass the --disable-logging-macros flag to the configure script.

And in debug mode, where you probably want to disable compiler optimisation and keep symbols in the binary, the documentation gives a configure line with CXXFLAGS set to -O0 -g.

There is also a new bmv2 debugger, enabled by passing --enable-debugger to configure.

This is worth pausing on, because it interacts with the performance question. The project is explicit that bmv2 is significantly slower than a production grade software switch in throughput and latency, and points to docs/performance.md for detail. Debug logging being on by default means that the default build is optimised for visibility rather than for speed, which is the correct default for a debugging tool and the wrong default if you are quietly measuring throughput.

The CMake build exposes the same knobs as configuration options, and the list begins with the two you are most likely to want: disabling nanomsg support with cmake -DWITH_NANOMSG=OFF .. and disabling thrift support with cmake -DWITH_THRIFT=.

If you are comparing bmv2 against another switch for a paper or a design review, turning off the logging macros first is not optional bookkeeping, it changes the thing you are measuring.

Choosing between simple_switch, simple_switch_grpc and psa_switch

The repository contains code for several variations of the behavioural model, and the difference between them is not cosmetic: it determines which control plane API your program talks to.

The names in the documentation are simple_switch, simple_switch_grpc and psa_switch, with the details in targets/README.md. The gRPC suffix indicates a control plane exposed over gRPC rather than the older interface, and psa_switch refers to the Portable Switch Architecture, the portable target that other implementations compile against.

This matters because a P4 program is written against a target architecture and a control plane contract. The architecture decides which externs and which intrinsic functions are available, and the control plane API decides how tables get populated. Choosing the wrong variation means writing a program that will not compile against the target you actually intend to ship.

The repository layout backs up the scale of the thing. Alongside src/, include/ and tests/ there is a proto/ directory for the control plane interface definitions, a thrift_src/ directory, a third_party/ directory, a PI/ directory, a pdfixed/ directory for the portable switch architecture fixed structures, a services/ directory, and a mininet/ directory for network emulation. There are also build files for Bazel, including BUILD.bazel, MODULE.bazel and a .bazelversion, alongside the autoconf and CMake builds.

The examples/ directory contains a custom_extern sample, which is the typical way to learn how to extend the model with your own externs.

The one thing the overview does not spell out is a recommendation for a newcomer. targets/README.md is where that comparison lives, and it is the first document to read before writing a line of P4 against this switch.

Not a production switch, and the comparison is in the repository

The project is unusually direct about this, and it is the single most important sentence for anyone evaluating the project: bmv2 is not meant to be a production grade software switch. It is meant to be used as a tool for developing, testing and debugging P4 data planes and control plane software written for them.

The performance consequence is stated in the same breath. In terms of throughput and latency, bmv2 is significantly less than a production grade software switch like Open vSwitch, and the README refers to docs/performance.md for more information. That reference to Open vSwitch is the comparison the project itself draws, and it is worth reading that document before quoting any number.

Two things follow for an evaluator. If you are trying to decide whether to deploy bmv2, the answer is already in the first line of the README, and no benchmark changes it. If you are trying to decide whether to develop against bmv2, the same sentence is a feature: the reference model is slow enough that your test harness will not saturate it, and fast enough to run thousands of packets per second.

The alternative to bmv2 is not another P4 switch for the same job, because there is no production grade P4 switch in this position. The alternative is writing your own interpreter, which no one should do, or testing against a real hardware target, which costs money and turns every bug into a flash cycle. Between those, an interpreted reference model is the pragmatic choice, and the performance gap is the price of the debugging convenience.

Two licence signals that do not obviously agree

The licence situation needs a careful reading, and it is the one place where this repository is ambiguous.

The source headers carry an SPDX identifier. The project overview opens with a comment block naming 2014 Dan Talayco and the SPDX-License-Identifier of Apache-2.0, and the Dockerfile in the repository carries the same pattern with a 2017 attribution to Seth Fowler. Individual files are therefore marked as Apache-2.0.

The repository as a whole is recorded as using a licence that GitHub cannot classify, which is the marker for a custom licence, and there is both a LICENSE file at the root and a LICENSES/ directory. The recommended practice with a custom licence is to read it, and this review cannot substitute for that.

What can be said without legal advice is that the licensing is not a simple single licence file, and that the per file SPDX identifiers and the repository level terms need to be read together before you vendor a copy into a product.

The release history in the meantime is steady. The tags are 1.15.7 from 2026-10-02, 1.15.6 from 2026-09-02 and 1.15.5 from 2026-08-02, so patch releases arrive about once a month. The repository is not archived and the last push was on 2026-10-16.

For deployment, the container route is available. The Dockerfile builds from a p4lang/pi base image, has an IMAGE_TYPE argument that selects a build image optimised for size or a test image that keeps source and build dependencies, and takes CC, CXX, GCOV and sswitch_grpc arguments. A separate Dockerfile.noPI exists for builds without the PI framework, and the build stage sets the cmake options for pdfixed, PI, stress tests, the debugger, coverage and warnings as errors.

Editorial conclusion

Adopt bmv2 when you are writing a P4 program or the control plane that drives it and you need a switch you can step through, inspect and reset in seconds. Do not put it in a path where throughput matters, because the project states plainly that its performance is significantly less than a production grade software switch. Before you build, install the packaged version from the p4lang repository rather than compiling, read targets/README.md to choose between simple_switch, simple_switch_grpc and psa_switch, and confirm which licence the LICENSE file actually grants, since the source headers carry Apache-2.0 identifiers while the repository is recorded as using a custom licence.

Frequently asked questions

What is bmv2 in p4lang/behavioral-model?

It is the reference P4 software switch, written in C++17. It takes the JSON file generated from your P4 program by a P4 compiler and interprets it to implement the packet processing behaviour that program specifies.

How do I install bmv2 on Ubuntu?

Add the p4lang repository from download.opensuse.org for your distribution, import the release key, then run sudo apt install p4lang-bmv2. Packages are available for Ubuntu 20.04 and newer, and there is a Debian 11 repository as well.

Is bmv2 suitable for production traffic?

No. The project states that bmv2 is not meant to be a production grade software switch and that its throughput and latency are significantly less than a production grade switch such as Open vSwitch. It is a tool for developing, testing and debugging P4 programs.

What is the difference between simple_switch and simple_switch_grpc?

The repository contains several variations of the behavioural model, named simple_switch, simple_switch_grpc and psa_switch, and the details of their differences are in targets/README.md. The gRPC variant exposes a gRPC control plane interface.

How do I build bmv2 from source?

Clone the repository, install the build dependencies including thrift and nanomsg from source, then run ./autogen.sh, ./configure and make, with sudo make install if you want it on the system. A CMake build is also available and is covered by CI.

What licence does bmv2 use?

Source files carry SPDX Apache-2.0 identifiers, while the repository itself is recorded as using a custom licence with a LICENSE file at the root and a LICENSES directory. Both need to be read before vendoring the code.

Official sources

  1. Issues
  2. p4lang/behavioral-model on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/p4lang-behavioral-model.svg)](https://hysenlabs.com/projects/p4lang-behavioral-model)

Was this useful?