ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

P4环境搭建:bmv2与p4c老版本依赖编译避坑指南

2026/10/7 15:39:19 拓冰建站 浏览量
P4环境搭建:bmv2与p4c老版本依赖编译避坑指南 简介这份资源面向SDN与可编程数据平面方向的学习者与研究者聚焦P4开发环境的完整搭建解决behavioral-model、p4c、protobuf、thrift等组件版本匹配繁琐、编译依赖易出错的问题。包内整合了behavioral-model、gmock-1.7.0、p4c、protobuf-3.2.0与thrift-0.9.2等核心源码包覆盖P4编译器、软件交换机、单元测试框架及RPC通信库压缩包约147.8MB以gz格式分发便于在Linux环境下按需解压编译。目前已有609人学习下载说明该组合在P4入门与实验场景中具有一定参考价值。对于需要复现P4教程、搭建bmv2软件交换机或调试p4c编译流程的读者可借助这份配套安装包减少逐个查找与版本适配的时间配合配置教程快速完成环境部署为后续编写P4程序、验证数据平面行为打下基础。1. P4 环境配置为什么 2024 年还要手动编译这套老版本组合如果你最近在搜 behavioral-model、p4c、protobuf-3.2.0、thrift-0.9.2 这几个词大概率是拿到了一个 P4 相关的实验环境搭建任务或者想复现某篇论文里的 P4 数据平面实验。P4 本身是 SDN 领域里做可编程数据平面的核心工具而 behavioral-model 就是那个常被叫做 bmv2 的软件交换机p4c 是 P4 编译器protobuf 和 thrift 则是它们依赖的序列化与 RPC 框架。问题在于这套组合的版本非常老protobuf 3.2.0 和 thrift 0.9.2 放在今天的环境里跟系统自带的库、Python 版本、编译器版本几乎处处冲突。更麻烦的是p4c 和 bmv2 的构建脚本对这两个依赖的版本卡得很死你直接装最新版 protobuf 3.20 或者 thrift 0.13编译到一半就会报符号找不到或者 API 不匹配。所以这篇笔记不讲 P4 语言本身只讲怎么把这套老版本依赖在本地或者容器里跑通让你能顺利编译出 bmv2 的simple_switch和 p4c 的p4c-bm2-ss。适合谁看适合手里有 P4 实验任务、需要自己搭环境、又不想在依赖地狱里耗一整天的工程师。接下来我会按依赖安装顺序、编译参数、常见报错排查、以及一个能验证环境是否可用的最小 P4 程序来展开。2. 依赖安装顺序与版本锁定gmock、protobuf、thrift 谁先谁后2.1 为什么必须从 gmock-1.7.0 开始装gmock 是 Google Mock 的缩写bmv2 的单元测试和部分构建脚本会链接它。gmock-1.7.0 这个版本本身不依赖 protobuf但它会安装libgmock.a和对应的头文件。如果你先装了高版本 gmock后面编译 bmv2 时可能因为 C 标准差异报错。常见做法是源码编译安装到/usr/local并且不要覆盖系统包管理器里的版本。下面是我在 Ubuntu 18.04 和 20.04 上都验证过的步骤注意-DBUILD_SHARED_LIBSOFF是为了生成静态库避免运行时找不到.so。# 下载并解压 gmock-1.7.0 wget https://github.com/google/googletest/archive/release-1.7.0.tar.gz -O gmock-1.7.0.tar.gz tar -xzf gmock-1.7.0.tar.gz cd googletest-release-1.7.0 # 创建构建目录关闭共享库指定安装前缀 mkdir build cd build cmake -DBUILD_SHARED_LIBSOFF -DCMAKE_INSTALL_PREFIX/usr/local .. make -j$(nproc) sudo make install # 安装后检查头文件和库是否存在 ls /usr/local/include/gmock/gmock.h ls /usr/local/lib/libgmock.a逻辑说明gmock-1.7.0 的 CMake 脚本默认会同时编译 gtest 和 gmockBUILD_SHARED_LIBSOFF确保生成静态库因为 bmv2 的链接脚本里写的是-lgmock如果只有动态库而LD_LIBRARY_PATH没配好运行时会直接报error while loading shared libraries。参数CMAKE_INSTALL_PREFIX/usr/local是默认值但显式写出来避免被环境变量干扰。安装完一定要确认libgmock.a存在如果只有libgmock.so后面 bmv2 编译会报cannot find -lgmock。2.2 protobuf-3.2.0 的编译参数与 Python 绑定处理protobuf 3.2.0 是这套环境里最容易被系统包管理器污染的一环。Ubuntu 20.04 默认源里的 protobuf 是 3.6 以上直接apt install libprotobuf-dev会导致 p4c 编译时找不到google::protobuf::internal::LogMessage的旧接口。所以必须源码编译并且安装到独立前缀比如/usr/local/protobuf-3.2.0然后在编译 p4c 和 bmv2 时通过PKG_CONFIG_PATH和LD_LIBRARY_PATH指向它。下面命令里的--disable-shared是为了生成静态库减少运行时库路径问题。# 下载 protobuf-3.2.0 wget https://github.com/protocolbuffers/protobuf/releases/download/v3.2.0/protobuf-cpp-3.2.0.tar.gz tar -xzf protobuf-cpp-3.2.0.tar.gz cd protobuf-3.2.0 # 配置安装路径关闭共享库开启优化 ./configure --prefix/usr/local/protobuf-3.2.0 --disable-shared CXXFLAGS-O2 make -j$(nproc) sudo make install # 配置动态链接库搜索路径 echo /usr/local/protobuf-3.2.0/lib | sudo tee /etc/ld.so.conf.d/protobuf-3.2.0.conf sudo ldconfig # 验证 protoc 版本 /usr/local/protobuf-3.2.0/bin/protoc --version # 应输出 libprotoc 3.2.0逻辑说明--disable-shared生成libprotobuf.a这样 p4c 和 bmv2 链接时不会跟系统里的libprotobuf.so冲突。ld.so.conf.d里加路径是为了让运行时能找到库但如果你后面编译时显式指定了LDFLAGS这一步可以跳过。注意 protobuf 3.2.0 的 Python 绑定默认会装到系统 Python 的 site-packages如果你用 conda 或者 pyenv需要手动设置PYTHON环境变量指向正确的解释器否则make install会报Python.h not found。我一般会加--disable-python先跳过 Python 绑定因为 bmv2 和 p4c 的 C 部分不依赖 Python 版 protobuf只有p4c-bm2-ss的某些脚本会用到后面单独用 pip 装对应版本即可。2.3 thrift-0.9.2 的依赖与编译避坑thrift 0.9.2 是 bmv2 的 RPC 框架依赖它本身又依赖 boost 和 openssl。在 Ubuntu 20.04 上系统自带的 boost 版本是 1.71thrift 0.9.2 的 configure 脚本能识别但编译时可能因为std::auto_ptr被弃用而报错。解决办法是在 configure 时加CXXFLAGS-stdc03或者打补丁但更稳妥的是用-Wno-error关掉警告即错误。下面命令里--without-php和--without-ruby是为了减少不必要的语言绑定加快编译。# 安装 boost 和 openssl 开发包 sudo apt install -y libboost-dev libboost-system-dev libboost-thread-dev libssl-dev # 下载 thrift-0.9.2 wget https://archive.apache.org/dist/thrift/0.9.2/thrift-0.9.2.tar.gz tar -xzf thrift-0.9.2.tar.gz cd thrift-0.9.2 # 配置关闭不需要的语言绑定指定安装路径 ./configure --prefix/usr/local/thrift-0.9.2 --without-php --without-ruby --without-python --disable-shared make -j$(nproc) sudo make install # 验证 thrift 编译器 /usr/local/thrift-0.9.2/bin/thrift --version # 应输出 Thrift version 0.9.2逻辑说明--disable-shared同样是为了静态链接避免 bmv2 运行时找不到libthrift.so。--without-python是因为 thrift 0.9.2 的 Python 绑定在 Python 3.8 以上会编译失败而 bmv2 的 C 部分不需要它。如果你后面要用p4c-bm2-ss的 Python 脚本可以单独用 pip 装thrift0.9.2但注意 pip 版本可能跟系统 Python 版本不兼容建议在虚拟环境里操作。安装完 thrift 后把/usr/local/thrift-0.9.2/bin加到PATH否则 bmv2 的 configure 脚本找不到thrift可执行文件。3. behavioral-model 与 p4c 的编译从 configure 到生成 simple_switch3.1 bmv2 的依赖检查与编译参数behavioral-model 的源码仓库里有一个install_deps.sh但那个脚本会尝试用 apt 装依赖容易把系统里的 protobuf 和 thrift 版本搞乱。我一般手动编译先确保PKG_CONFIG_PATH指向前面装的 protobuf 和 thrift。bmv2 的 configure 脚本会检查libprotobuf、libthrift、libgmock是否存在如果找不到就报错。下面命令里的--with-pdfixed是可选项用于启用固定长度的包解析对性能有好处但如果你只是做功能验证可以不加。# 下载 bmv2 源码这里假设你已经有源码包没有的话从官方仓库克隆 git clone https://github.com/p4lang/behavioral-model.git cd behavioral-model # 设置环境变量让 configure 找到我们手动装的依赖 export PKG_CONFIG_PATH/usr/local/protobuf-3.2.0/lib/pkgconfig:/usr/local/thrift-0.9.2/lib/pkgconfig export LDFLAGS-L/usr/local/protobuf-3.2.0/lib -L/usr/local/thrift-0.9.2/lib -L/usr/local/lib export CXXFLAGS-I/usr/local/protobuf-3.2.0/include -I/usr/local/thrift-0.9.2/include -I/usr/local/include # 生成 configure 脚本 ./autogen.sh # 配置关闭 debug 信息以加快编译 ./configure --prefix/usr/local/bmv2 --disable-debug # 编译并安装 make -j$(nproc) sudo make install # 验证 simple_switch 是否生成 ls /usr/local/bmv2/bin/simple_switch逻辑说明PKG_CONFIG_PATH是关键如果 protobuf 的.pc文件不在这个路径下configure 会报Package protobuf was not found。LDFLAGS和CXXFLAGS显式指定头文件和库路径避免跟系统版本混用。--disable-debug会去掉-g编译选项减少二进制体积但如果你需要调试 bmv2 内部行为可以保留 debug。编译过程中如果报undefined reference to google::protobuf::internal::...说明链接时用了系统 protobuf 而不是我们装的 3.2.0检查LDFLAGS里的路径顺序把/usr/local/protobuf-3.2.0/lib放在最前面。3.2 p4c 的编译与 p4c-bm2-ss 后端生成p4c 是 P4 编译器它依赖 bmv2 的头文件来生成后端代码。所以必须先装好 bmv2再编译 p4c。p4c 的构建系统用 CMake需要指定BMV2_INCLUDE_DIR和BMV2_LIBRARY。另外 p4c 对 protobuf 的版本更敏感如果用了 3.2.0 以上的版本编译p4c-bm2-ss时会报google::protobuf::Any相关错误。下面命令里的-DENABLE_BMV2ON是开启 bmv2 后端-DENABLE_EBPFOFF是关掉 eBPF 后端减少编译时间。# 下载 p4c 源码 git clone https://github.com/p4lang/p4c.git cd p4c mkdir build cd build # 配置 CMake指定依赖路径 cmake .. \ -DCMAKE_INSTALL_PREFIX/usr/local/p4c \ -DCMAKE_PREFIX_PATH/usr/local/protobuf-3.2.0;/usr/local/thrift-0.9.2;/usr/local/bmv2 \ -DBMV2_INCLUDE_DIR/usr/local/bmv2/include \ -DBMV2_LIBRARY/usr/local/bmv2/lib/libbmv2.a \ -DENABLE_BMV2ON \ -DENABLE_EBPFOFF \ -DENABLE_DPDKOFF # 编译并安装 make -j$(nproc) sudo make install # 验证 p4c-bm2-ss 是否生成 ls /usr/local/p4c/bin/p4c-bm2-ss逻辑说明CMAKE_PREFIX_PATH里用分号分隔多个路径CMake 会按顺序查找。BMV2_LIBRARY指向静态库libbmv2.a如果 bmv2 编译时生成了动态库这里要改成.so。ENABLE_EBPFOFF和ENABLE_DPDKOFF是为了避免引入额外的内核依赖如果你确实需要这些后端再单独开启。编译 p4c 时如果报thrift/protocol/TProtocol.h not found检查CMAKE_PREFIX_PATH里 thrift 的路径是否正确或者手动加-DTHRIFT_INCLUDE_DIR/usr/local/thrift-0.9.2/include。3.3 环境变量与动态库路径的最终配置编译安装完成后需要把 bmv2 和 p4c 的可执行文件路径加到PATH把库路径加到LD_LIBRARY_PATH。虽然前面用了静态库但 bmv2 的simple_switch可能还依赖libboost_thread等系统库所以LD_LIBRARY_PATH里加上/usr/local/lib和 thrift 的库路径更保险。下面命令可以写进~/.bashrc但注意不要覆盖系统原有的PATH。# 追加环境变量不要覆盖 export PATH$PATH:/usr/local/bmv2/bin:/usr/local/p4c/bin:/usr/local/thrift-0.9.2/bin export LD_LIBRARY_PATH$LD_LIBRARY_PATH:/usr/local/protobuf-3.2.0/lib:/usr/local/thrift-0.9.2/lib:/usr/local/lib export PKG_CONFIG_PATH$PKG_CONFIG_PATH:/usr/local/protobuf-3.2.0/lib/pkgconfig:/usr/local/thrift-0.9.2/lib/pkgconfig # 使配置生效 source ~/.bashrc # 验证所有工具是否可用 which simple_switch which p4c-bm2-ss simple_switch --help | head -5 p4c-bm2-ss --help | head -5逻辑说明PATH追加而不是替换避免系统命令找不到。LD_LIBRARY_PATH在静态库场景下不是必须的但如果你后面用到了动态链接的第三方库加上更稳妥。PKG_CONFIG_PATH是为了后续编译其他 P4 工具时能找到依赖。验证时如果simple_switch --help报error while loading shared libraries: libthrift-0.9.2.so说明 thrift 编译时生成了动态库而LD_LIBRARY_PATH没包含它检查/usr/local/thrift-0.9.2/lib下是否有.so文件有的话确认路径已加入。4. 避坑与排查protobuf 版本冲突、thrift 符号缺失、gmock 链接失败4.1 现象编译 bmv2 时报undefined reference to google::protobuf::internal::LogMessage::LogMessage原因系统里存在多个 protobuf 版本链接器优先找到了/usr/lib/x86_64-linux-gnu/libprotobuf.so而不是我们手动装的 3.2.0 静态库。常见于 Ubuntu 20.04 默认装了libprotobuf-dev。解决用apt remove libprotobuf-dev卸载系统版本或者编译时在LDFLAGS里把/usr/local/protobuf-3.2.0/lib放在最前面并且加-Wl,-rpath,/usr/local/protobuf-3.2.0/lib强制运行时也走这个路径。我一般直接卸载系统 protobuf避免后续其他工具误链接。4.2 现象p4c 编译到 80% 时报thrift/protocol/TProtocol.h: No such file or directory原因CMake 没有正确找到 thrift 的头文件路径。虽然CMAKE_PREFIX_PATH里写了 thrift 路径但 thrift 0.9.2 的 CMake 配置文件可能不完整导致THRIFT_INCLUDE_DIR为空。解决在 cmake 命令里显式加-DTHRIFT_INCLUDE_DIR/usr/local/thrift-0.9.2/include和-DTHRIFT_LIBRARY/usr/local/thrift-0.9.2/lib/libthrift.a。如果还是找不到检查/usr/local/thrift-0.9.2/include/thrift目录是否存在没有的话说明 thrift 安装不完整重新make install。4.3 现象运行simple_switch时报error while loading shared libraries: libgmock.so.1.7.0原因gmock 编译时默认生成了动态库而 bmv2 链接时用了-lgmock运行时找不到对应的.so。解决要么在LD_LIBRARY_PATH里加上/usr/local/lib要么重新编译 gmock 时加-DBUILD_SHARED_LIBSOFF生成静态库。我推荐后者因为静态库省去运行时路径配置的麻烦。如果已经装了动态库可以手动创建软链接sudo ln -s /usr/local/lib/libgmock.so.1.7.0 /usr/lib/libgmock.so.1但不建议这么做容易跟系统库冲突。4.4 现象p4c-bm2-ss编译 P4 程序时报Unknown extern type: counter或类似错误原因p4c 的 bmv2 后端依赖 bmv2 的json文件来识别 extern 类型如果 bmv2 安装不完整或者BMV2_INCLUDE_DIR指向了错误路径p4c 会找不到bmv2/json下的定义。解决确认/usr/local/bmv2/include/bmvm2目录存在并且BMV2_INCLUDE_DIR指向/usr/local/bmv2/include而不是/usr/local/bmv2/include/bmvm2。另外p4c 编译时如果用了-DENABLE_BMV2ON但 bmv2 的版本太老也可能不匹配建议用同一时间段的 bmv2 和 p4c 源码。4.5 现象make -j$(nproc)时内存不足被 OOM Killer 杀掉原因p4c 编译时单个 C 文件可能占用 2GB 以上内存如果nproc是 8 或 16并行编译会瞬间吃光内存。解决减少并行数用make -j4或者make -j2。如果还是不够加 swap 分区sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile。这个坑在容器里尤其常见因为容器默认没有 swap。5. 验证环境可用性的最小 P4 程序与进阶技巧5.1 写一个能跑通 bmv2 的 P4_16 程序环境装好了怎么确认真的能用最直接的方法是写一个最小的 P4 程序用p4c-bm2-ss编译成 JSON再用simple_switch加载。下面这个程序只做一件事把从ingress端口进来的包从egress端口发出去不做任何修改。保存为minimal.p4。#include core.p4 #include v1model.p4 header ethernet_t { bit48 dstAddr; bit48 srcAddr; bit16 etherType; } struct metadata { } struct headers { ethernet_t ethernet; } parser MyParser(packet_in packet, out headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { state start { packet.extract(hdr.ethernet); transition accept; } } control MyIngress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { apply { // 简单转发把入端口映射到出端口 standard_metadata.egress_spec standard_metadata.ingress_port; } } control MyEgress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { apply { } } control MyDeparser(packet_out packet, in headers hdr) { apply { packet.emit(hdr.ethernet); } } V1Switch(MyParser(), MyIngress(), MyEgress(), MyDeparser()) main;逻辑说明standard_metadata.egress_spec standard_metadata.ingress_port这行让包从哪个口进来就从哪个口出去相当于一个回环。V1Switch是 v1model 的标准入口四个参数分别是 parser、ingress、egress、deparser。这个程序不依赖任何 extern所以能验证 p4c 和 bmv2 的基本功能是否正常。5.2 编译并加载到 simple_switch 的命令# 编译 P4 程序生成 JSON p4c-bm2-ss --p4v 16 --p4runtime-file minimal.p4runtime --p4runtime-format text -o minimal.json minimal.p4 # 启动 simple_switch加载 JSON监听所有端口 sudo simple_switch -i 0veth0 -i 1veth1 minimal.json # 如果不想用 veth可以用 --use-files 从 pcap 文件读包 sudo simple_switch --use-files 0input.pcap minimal.json逻辑说明--p4v 16指定 P4 语言版本为 P4_16--p4runtime-file生成 P4Runtime 的文本格式方便后续用控制器下发流表。-i 0veth0把端口 0 绑定到veth0网卡需要提前用ip link add veth0 type veth peer name veth1创建。如果只是验证编译可以不加-i直接simple_switch minimal.json它会启动但不绑定任何端口。启动后如果看到Thrift server listening on port 9090说明 thrift 也正常工作了。5.3 用 P4Runtime 下发流表的快速验证如果你需要验证控制平面可以用simple_switch_grpc而不是simple_switch它支持 P4Runtime。启动命令类似但需要额外指定--grpc-server-addr。下面是一个用 Python 脚本下发流表的片段依赖p4runtime库注意这个库的版本要跟 p4c 生成的p4runtime文件匹配。import grpc from p4.v1 import p4runtime_pb2, p4runtime_pb2_grpc # 连接 simple_switch_grpc 的 P4Runtime 端口 channel grpc.insecure_channel(localhost:50051) stub p4runtime_pb2_grpc.P4RuntimeStub(channel) # 构造流表项这里省略了具体的 table entry 构造 # 实际使用时需要根据 P4 程序里的 table 名称和 match 字段来填 req p4runtime_pb2.WriteRequest() req.device_id 1 update req.updates.add() update.type p4runtime_pb2.Update.INSERT # ... 填充 table entry ... stub.Write(req)逻辑说明这段代码只是框架实际下发流表需要根据 P4 程序里的table定义来构造TableEntry。device_id通常设为 1Update.INSERT表示插入流表项。如果你只是验证环境可以跳过这一步直接用simple_switch的 CLI 或者runtime_CLI来下发。runtime_CLI是 bmv2 自带的交互式工具用simple_switch_CLI --thrift-port 9090连接然后输入table_add命令即可。5.4 一个我踩过的坑protobuf 3.20 的诱惑网上搜 protobuf 下载安装很多人会推荐 3.20 版本说它性能好、API 稳定。但在 P4 这套环境里protobuf 3.20 跟 p4c 的旧代码完全不兼容编译时会报google::protobuf::internal::ArenaImpl相关错误。我一开始不信觉得改改 CMake 就能过结果折腾了一下午最后还是退回 3.2.0。血泪经验是P4 生态的版本锁定非常严格不要轻易升级任何一个依赖除非你确认 p4c 和 bmv2 的源码已经适配了新版本。如果你实在需要用 protobuf 3.20 做其他项目建议用容器隔离P4 环境单独一个 Docker 镜像基础镜像用 Ubuntu 18.04把所有依赖编译进去这样不会跟宿主机冲突。5.5 验证通过后的习惯保存一个可复现的 Dockerfile每次手动搭完这套环境我都会把步骤写成一个 Dockerfile下次直接docker build就能复现。关键点是在 Dockerfile 里用COPY把源码包拷进去而不是wget因为那些老版本的下载链接经常失效。另外RUN指令里每一步都加连接减少镜像层数。最后用ENTRYPOINT启动simple_switch这样镜像可以直接当软件交换机用。这个习惯帮我省了很多次重装的时间也希望帮到你。本文还有配套的精品资源点击获取