简介:面向P4数据平面开发初学者及BMv2实验环境搭建者的一套整合安装包,将行为模型、P4编译器、protobuf、thrift与gmock等常见依赖集中收纳,重点缓解版本匹配繁琐、依赖缺失和编译顺序易错等环境配置难题,适合需要复现P4教程实验、开展SDN课程设计或进行交换机行为模型验证的读者使用。压缩包为gz格式,整体约147.8MB,已按配置流程所需将核心组件一并打包,便于在离线或网络受限场景下完成基础环境准备,减少逐个检索安装包与反复处理依赖关系的时间。目前已有611人学习,说明其在P4入门配置场景中具备一定参考价值。包内组件覆盖编译器、运行时行为模型及测试依赖等关键环节,读者可据此搭建相对完整的P4开发与验证链路,并配合公开配置教程核对版本与安装顺序,快速定位编译报错和环境冲突,为后续编写P4程序、运行BMv2仿真及开展实验验证打下基础。
1. p4 环境配置安装包:先看版本咬合,别急着敲 make
p4 环境配置安装包这类活,最容易被低估的不是能不能装上,而是装完之后 p4c、bmv2、protobuf 三者的符号能不能对上。我见过不止一次:系统里先用包管理器装了一个 libprotobuf-dev,再源码编 behavioral-model,编译一路绿灯,一跑 simple_switch 就抛libprotobuf版本不兼容;也有人 thrift 随手拉了新版本,bmv2 链接阶段直接报 API 对不上。这套 behavioral-model + gmock-1.7.0 + p4c + protobuf-3.2.0 + thrift-0.9.2 的组合,版本是绑死的,顺序也是绑死的。这篇记录按「底座先编、楼房后盖、最后跑穿一条链」的顺序写,适合第一次搭 P4 开发环境的人,也适合装到一半翻车、想回头核对版本的人。
2. 装之前把版本清单钉死:五个组件的依赖关系
动手前先把一件事想清楚:这套东西不是并列安装,而是有严格上下层。protobuf 3.2.0 和 thrift 0.9.2 是地基,gmock 1.7.0 是测试依赖,behavioral-model(下称 bmv2)和 p4c 是盖在上面的两层。地基版本错了,上面两层编出来的二进制就是「看着能跑、一加载就崩」。所以第 2 章不写命令,只把清单、顺序和判断标准列清楚,后面几章才好对号入座。
2.1 系统底座与工具链版本
系统我一般用 Ubuntu 16.04 或 18.04 这类老一点的发行版,原因是这套源码快照偏旧,新系统自带的 GCC 10+ 会把一堆 warning 当 error,thrift 0.9.2 尤其容易在这儿翻车。GCC 用 5.x 或 7.x 比较稳,CMake 3.5 以上即可,Python 用 3 就行(老快照里个别脚本还写着 python2,遇到再单独处理)。
工具链一次装齐,省得编到一半缺东西回头补:
sudo apt-get update sudo apt-get install -y \ build-essential autoconf automake libtool pkg-config \ bison flex libfl-dev \ libboost-dev libboost-system-dev libboost-thread-dev \ libboost-filesystem-dev libboost-program-options-dev \ libjudy-dev libpcap-dev libreadline-dev libssl-dev \ libgc-dev zlib1g-dev cmake python3这段的分组逻辑:第一行是构建系统本身(autotools 全家桶 + pkg-config),第二行是 p4c 的词法语法分析和 flex 库,第三行是 bmv2 和 p4c 共用的 boost 子库,第四行是 bmv2 的表存储(Judy)、抓包(pcap)、CLI 行编辑(readline)、TLS,以及 p4c 的 Boehm GC。少装一个,configure 阶段就会停在checking for xxx... no。
注意:这一条 apt 命令里刻意没有
libprotobuf-dev、protobuf-compiler、libthrift-dev、thrift-compiler、libgtest-dev、libgmock-dev。这四个包一旦从系统源装上,后面源码编的 3.2.0 和 0.9.2 就会被/usr/lib抢先链接,典型症状是编译过、运行崩。
2.2 五个组件的版本对照表
把这五个组件的角色、装到哪、和谁有约束关系列成一张表,装的时候对着看,出问题也对着查:
| 组件 | 版本 | 安装前缀 | 在链路里的角色 | 关键约束 |
|---|---|---|---|---|
| protobuf | 3.2.0 | /usr/local | 序列化底座,bmv2 与 p4c 都链接 | 3.6+ 删了部分 API,p4c 老快照编不过 |
| thrift | 0.9.2 | /usr/local | bmv2 的运行时 RPC 接口 | 0.10+ 的 API 改名,bmv2 链接报错 |
| gmock | 1.7.0 | /usr/local | p4c 单测依赖 | 1.8 起与 gtest 合并,目录布局变了 |
| behavioral-model | 配套源码 | /usr/local | P4 软件交换机 simple_switch | 依赖 thrift 0.9.2 + protobuf 3.2.0 |
| p4c | 配套源码 | /usr/local | P4 编译器,产出 bmv2 的 JSON | 依赖 protobuf 3.2.0 + gmock 1.7.0 |
安装顺序就按表从下往上:protobuf → thrift → gmock → bmv2 → p4c。bmv2 放 p4c 前面,是因为 p4c 的 bmv2 后端在 configure 阶段会去探 bmv2 的头文件;反过来先装 p4c 也能成,但后端可能被静默关掉,后面p4c --target bmv2直接报 target 不存在。
2.3 为什么不用包管理器一把梭
新手最容易问的一句是:这些不都能 apt 装吗?能装,但装出来的版本不受控。以 Ubuntu 18.04 为例,源里的 protobuf 是 3.0.x 或 3.6.x,thrift 是 0.11,跟这套源码要的 3.2.0 / 0.9.2 都对不上。更麻烦的是包管理器会把库塞进/usr/lib/x86_64-linux-gnu,源码装的在/usr/local/lib,运行时动态链接器先找哪个,取决于ld.so.conf的顺序,排错了就出现「明明装了 3.2.0,程序却加载了 3.6」。所以合格做法是全程源码装到/usr/local,装完统一ldconfig,让/usr/local/lib稳定排在系统库前面。
3. protobuf 3.2.0 与 thrift 0.9.2:先把两个底座编出来
底座这两个包编译不难,难在「编完怎么确认没串版本」。这一章把两条编译命令、参数取舍、验证方法各写一遍,照着走完,后面 bmv2 和 p4c 的报错会少一大半。源码包按你手里安装包解压出来的目录名调整,下面统一用版本号命名。
3.1 protobuf 3.2.0 源码编译
tar -xzf protobuf-3.2.0.tar.gz cd protobuf-3.2.0 ./configure --prefix=/usr/local make -j$(nproc) sudo make install sudo ldconfig--prefix=/usr/local是这个包的落脚点,后面所有组件都从这里找头文件和库。make -j$(nproc)按核数并行,protobuf 编译本身不慢,但别开过头,老机器内存小容易 OOM。装完必须ldconfig,否则动态库缓存里没有libprotobuf.so.13,下一步 thrift 或 bmv2 运行时会报找不到库。
我不建议在这台机器上跑make check。protobuf 3.2.0 的自测在部分 GCC 版本和容器环境里会挂几条,纯属测试用例跟新编译器不对付,跟库本身能不能用无关,跑一遍十几分钟还闹心。真要验证,用下面这两条更直接:
protoc --version pkg-config --modversion protobuf第一条期望输出libprotoc 3.2.0,第二条期望输出3.2.0。两条都对上,说明头文件、库、pkg-config 描述文件三者一致。如果protoc显示 3.2.0 而pkg-config显示别的版本,基本可以断定系统里还残留着另一个 protobuf,回到 2.1 的注意项把libprotobuf-dev卸干净再重来。
提示:如果
pkg-config --modversion protobuf报Package protobuf was not found,多半是/usr/local/lib/pkgconfig不在PKG_CONFIG_PATH里。临时补一句export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH即可,别去改系统全局配置,避免影响别的项目。
3.2 thrift 0.9.2 编译与裁剪
thrift 是个多语言 RPC 框架,默认会把 C++、Java、Python、Ruby、PHP 全编一遍,这套环境其实只用它的 C++ 库给 bmv2 做运行时接口。不裁剪的话编译时间翻几倍,还容易在某个语言后端上撞见新编译器的报错。我的做法是只留 C++:
tar -xzf thrift-0.9.2.tar.gz cd thrift-0.9.2 ./configure --prefix=/usr/local \ --with-cpp \ --without-python --without-java --without-nodejs \ --without-php --without-ruby --without-perl \ --without-csharp --without-erlang --without-go \ --without-haskell --without-lua --without-qt4 make -j$(nproc) sudo make install sudo ldconfig--with-cpp明确要 C++ 支持,是 bmv2 唯一需要的部分;后面一长串--without-*是逐个关掉其它语言绑定,这些选项在 0.9.2 的 configure 里都能识别。关掉它们不是为了洁癖,而是这些后端大多依赖各语言自己的工具链,机器上没装就会在 configure 阶段报一堆checking for xxx... no,看着吓人,实际跟你要的功能无关。
如果 GCC 版本偏新(7 以上),thrift 0.9.2 常见的翻车点是某些 warning 被当成 error 中断编译。遇到时在 configure 里补一个--disable-werror,或者在make前export CXXFLAGS="-std=c++11 -Wno-error"。这不是玄学,是老代码配新编译器时的常规处理。
编完验证:
thrift --version ls -l /usr/local/lib/libthrift-0.9.2.so第一条期望输出Thrift version 0.9.2。第二条确认动态库文件名带完整版本号,bmv2 链接时找的就是这个文件名,如果只剩libthrift.so这种软链、实体文件没装上,后面链接会报cannot find -lthrift。
3.3 装完怎么确认两个底座没串版本
这一小节单独拎出来,是因为底座串版本是后面所有报错的源头,早查一分钟,后面少调两小时。三条命令一次跑完:
ldconfig -p | grep -E "libprotobuf|libthrift" which protoc thrift pkg-config --modversion protobuf thrift第一条看动态链接器缓存里注册了哪些版本,理想情况下每个库只出现一条,且路径是/usr/local/lib。如果同时出现/usr/lib/x86_64-linux-gnu/libprotobuf.so和/usr/local/lib/libprotobuf.so,说明系统包没清干净,运行时谁先谁后全看运气,属于典型的「能编不能跑」。第二条确认命令走的是/usr/local/bin下的二进制。第三条把 pkg-config 报的版本和实际装的对一遍,三者一致才算底座干净。
这一步过了再往下走。血泪经验是:底座没确认就急着编 bmv2,最后排查问题时根本分不清是 bmv2 自己的 bug 还是链接到了错的库。
4. gmock 1.7.0、bmv2、p4c:在底座上盖楼
底座干净之后,这一章按 gmock → bmv2 → p4c 的顺序往上盖。三个组件的编译命令都不长,但每个都有各自的坑:gmock 卡在目录布局,bmv2 卡在依赖探测,p4c 卡在构建系统判断。下面逐条拆。
4.1 gmock 1.7.0 的目录布局坑
gmock 1.7.0 这个版本有意思,它和 gtest 1.7.0 还是两个独立发布包,gmock 的压缩包里自带一个gtest子目录,编 gmock 的过程会把 gtest 一起带上。从 1.8 开始两者合并进 googletest 仓库,目录结构变成googletest/googletest和googletest/googlemock两层,p4c 老快照的 CMake 或 configure 脚本是按 1.7 的布局找的,布局一变就报Could NOT find GMock。所以这套环境必须用 1.7.0,不能图省事上新版。
tar -xzf gmock-1.7.0.tar.gz cd gmock-1.7.0 ./configure --prefix=/usr/local make -j$(nproc) sudo make install sudo ldconfig这三行没什么参数可调,--prefix跟前面保持一致。编完的产物是libgmock.a、libgmock_main.a和libgtest.a、libgtest_main.a四个静态库,加上/usr/local/include/gmock、/usr/local/include/gtest两套头文件。静态库不需要ldconfig,但跑一下无害,确认脚本没把.so也装出来。
验证:
ls /usr/local/lib/libgmock.a /usr/local/lib/libgmock_main.a ls -d /usr/local/include/gmock /usr/local/include/gtest四个路径都存在,说明 gmock 1.7.0 的「自带 gtest」布局完整。少任何一个,p4c 在链接单测环节会报undefined reference to testing::...,而主程序可能照样编得过,属于藏得比较深的坑。
4.2 behavioral-model 编译
bmv2 是这套环境里真正跑 P4 程序的软件交换机,simple_switch、simple_switch_CLI、simple_switch_grpc 都在这个包里。它对底座的要求最严:thrift 必须是 0.9.2,protobuf 必须是 3.2.0,boost 五个子库一个不能少。
cd behavioral-model-<配套源码目录> ./autogen.sh ./configure --prefix=/usr/local make -j$(nproc) sudo make install sudo ldconfig./autogen.sh是 autotools 项目从仓库快照生成configure的步骤,如果源码包里已经带了configure,这一步会提示跳过,正常。./configure阶段重点关注三行探测结果:checking for THRIFT...、checking for PROTOBUF...、checking for boost...,任何一个显示no都会导致后面链接失败,先回第 3 章把底座补好再继续,别硬编。
make阶段如果报undefined reference to apache::thrift::...,八成是 thrift 版本不对或者链接顺序问题;如果报undefined reference to google::protobuf::...,八成是 protobuf 串了系统版本。这两种错误的根源都在第 3 章的验证环节,不在 bmv2 本身。
老一点的 bmv2 源码快照还会依赖 nanomsg,configure 时会探libnanomsg。如果你的快照有这一项,补一条sudo apt-get install -y libnanomsg-dev即可;较新的快照已经把这块换成自己实现,不再需要。判断方法是 configure 输出里有没有checking for nn_...这类探测。
编完验证:
which simple_switch simple_switch_CLI simple_switch --help | head -20第一条要能打印两个/usr/local/bin下的路径。第二条能打印出用法说明,说明二进制本身能正常启动、动态库都找到了。如果第二条报error while loading shared libraries,回第 5 章 5.1 处理。
4.3 p4c 编译
p4c 是 P4 编译器,负责把.p4源码翻成 bmv2 能吃的 JSON。它最麻烦的地方在于:不同源码快照用的构建系统不一样,老的用 autotools,新的用 CMake,命令完全不同。我一般先看源码树里有什么文件再决定走哪条路:
cd p4c-<配套源码目录> ls configure.ac CMakeLists.txt 2>/dev/null如果输出里有configure.ac,走 autotools:
./autogen.sh ./configure --prefix=/usr/local --enable-bmv2 make -j4 sudo make install如果输出里有CMakeLists.txt,走 CMake:
mkdir -p build && cd build cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_BMV2=ON make -j4 sudo make install两条路里的--enable-bmv2和-DENABLE_BMV2=ON是一个意思:打开 bmv2 后端,让 p4c 能输出--target bmv2的结果。不同快照这个选项名可能有细微差别,configure 阶段如果提示不认识这个选项,先./configure --help | grep -i bmv2看一眼实际名字,别硬套。
make这里我故意写-j4而不是-j$(nproc)。p4c 的 C++ 模板很重,单个编译单元内存能吃到 1GB 以上,核数开满在内存小的机器上会直接 OOM 把编译器杀掉,报internal compiler error: Killed,看着像编译器 bug,实际是内存不够。4 核并行是个比较稳的折中,内存充裕再往上加。
验证:
p4c --version p4c --target bmv2 --arch v1model --help > /dev/null && echo "bmv2 backend ok"第一条能打印版本信息就算编出来了。第二条更重要,它在不编译任何程序的前提下试探 bmv2 后端是否注册上,如果报unknown target bmv2,说明--enable-bmv2那步没生效,回 configure 阶段看输出里 bmv2 相关的探测结果。
5. 踩坑排查:这五处最容易被卡
这一章的五个问题都是我在装这套环境时反复遇到的,现象、原因、解决按顺序写清楚,遇到报错先来这里对号入座,比从头重编省时间。
5.1 运行时找不到 libthrift-0.9.2.so
现象:simple_switch --help报error while loading shared libraries: libthrift-0.9.2.so: cannot open shared object file: No such file or directory,但ls /usr/local/lib明明能看到这个文件。
原因:动态链接器缓存里没有/usr/local/lib,或者缓存有但没刷新。源码装的库不会自动进/etc/ld.so.conf,必须手动登记或至少跑一次ldconfig。
解决:先sudo ldconfig试一次,多数情况这一条就够。如果还报,把/usr/local/lib写进独立配置文件:
echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/local.conf sudo ldconfig ldconfig -p | grep libthrift最后一条能看到libthrift-0.9.2.so的完整路径,才算真正登记上。
5.2 [libprotobuf FATAL] 版本不兼容
现象:程序启动瞬间打印[libprotobuf FATAL] This program was compiled against version 3.2.0 of the Protocol Buffer runtime library, which is not compatible with the installed version,然后退出。
原因:编译时链的是/usr/local/lib下的 3.2.0 头文件,运行时却加载了系统/usr/lib下的另一个版本。两个版本 ABI 不兼容,protobuf 运行时做了版本自检,直接 fatal。
解决:把系统版 protobuf 卸干净,再刷新缓存:
sudo apt-get remove -y libprotobuf-dev protobuf-compiler sudo apt-get autoremove -y sudo ldconfig卸完用ldconfig -p | grep libprotobuf确认只剩/usr/local/lib那一条。如果还有残留,检查是不是某个第三方包把libprotobuf.so拷进了别的前缀,把它清理掉。
5.3 thrift 0.9.2 编译被 warning 打断
现象:make到一半报error: 'to_string' is not a member of 'std'或大量-Werror=...类错误中断。
原因:thrift 0.9.2 发布于老 GCC 时代,代码里有些写法在新编译器下会产生 warning,而它的 Makefile 默认带-Werror,warning 升级成 error。
解决:configure 时关掉 werror,并显式指定 C++ 标准:
make clean ./configure --prefix=/usr/local --disable-werror --with-cpp make -j$(nproc) CXXFLAGS="-std=c++11" sudo make install--disable-werror让它别把 warning 当错误,CXXFLAGS="-std=c++11"解决to_string这类 C++11 才有的符号在老标准下找不到的问题。两个一起加,基本能过。
5.4 p4c 报 Could NOT find GMock
现象:p4c 的 CMake 或 configure 阶段报Could NOT find GMock (missing: GMOCK_LIBRARY GMOCK_INCLUDE_DIR),然后继续配置,最后单测目标编不出。
原因:gmock 1.7.0 装到了/usr/local,但 p4c 的查找逻辑只扫/usr,或者被系统里的新 googletest 布局干扰。
解决:显式告诉它去哪找。CMake 路线加两个变量:
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_BMV2=ON \ -DGMOCK_ROOT=/usr/local \ -DCMAKE_PREFIX_PATH=/usr/localautotools 路线则在 configure 前设环境变量CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib"。改完重新 configure,报错消失即可。主程序其实不依赖 gmock,但配置阶段探测失败会让人以为环境坏了,处理掉更省心。
5.5 bmv2 链接阶段 thrift 符号对不上
现象:bmv2make最后链接报undefined reference to apache::thrift::transport::TSocket::...或者THRIFT_SOCKET类型相关的错误。
原因:头文件是 0.9.2,库文件却是 0.10+ 的,或者反过来。thrift 在 0.9 到 0.10 之间改过一批 API 名和类型定义,混用必然对不上。
解决:先确认当前生效的是哪个版本:
pkg-config --modversion thrift ldconfig -p | grep libthrift两条都应该是 0.9.2。如果 pkg-config 报的是别的版本,说明还有另一份 thrift 的.pc文件在PKG_CONFIG_PATH里,找出来删掉或调整路径顺序;如果ldconfig -p里同时有 0.9.2 和 0.10.x,把 0.10.x 那份卸掉,回 3.2 重装 0.9.2 并ldconfig。清干净后 bmv2 要make clean重编,因为旧的.o文件里已经记了错误的符号引用。
6. 装完怎么验证:拿一个最小 P4 程序跑穿整条链
装完不验证,等于没装。我的验证方式固定是「写一个最小可编译的 P4 程序,从 p4c 编到 JSON,再让 simple_switch 加载它」,整条链走通,才算这套环境真的可用。
6.1 三步验证法
第一步,准备一个最小 P4_16 程序,只做解析以太网头和一张按入端口转发的表,不涉及复杂动作,减少版本语法差异带来的干扰:
#include <core.p4> #include <v1model.p4> header ethernet_t { bit<48> dstAddr; bit<48> srcAddr; bit<16> etherType; } struct metadata_t {} parser MyParser(packet_in packet, out headers hdr, inout metadata_t meta, inout standard_metadata_t standard_metadata) { state start { packet.extract(hdr.ethernet); transition accept; } } control MyVerifyChecksum(inout headers hdr, inout metadata_t meta) { apply { } } control MyIngress(inout headers hdr, inout metadata_t meta, inout standard_metadata_t standard_metadata) { action forward(bit<9> port) { standard_metadata.egress_spec = port; } table port_table { key = { standard_metadata.ingress_port: exact; } actions = { forward; NoAction; } default_action = NoAction(); } apply { port_table.apply(); } } control MyEgress(inout headers hdr, inout metadata_t meta, inout standard_metadata_t standard_metadata) { apply { } } control MyComputeChecksum(inout headers hdr, inout metadata_t meta) { apply { } } control MyDeparser(packet_out packet, in headers hdr) { apply { packet.emit(hdr.ethernet); } } V1Switch( MyParser(), MyVerifyChecksum(), MyIngress(), MyEgress(), MyComputeChecksum(), MyDeparser() ) main;这段里V1Switch后面六个 control 的顺序不能乱:parser、verifyChecksum、ingress、egress、computeChecksum、deparser,这是 v1model 规定的架构顺序。forward只写egress_spec而不调mark_to_drop,是为了避开这个函数在不同快照里参数个数的差异,验证阶段越少版本敏感点越好。
第二步,用 p4c 编出 JSON:
p4c --target bmv2 --arch v1model basic.p4 ls -l basic.json期望在当前目录看到basic.json。如果报unknown target bmv2,回 4.3 检查后端是否打开;如果报error: unknown type,八成是#include路径找不到v1model.p4,用p4c -I /usr/local/share/p4c/p4include ...显式指定包含目录。
第三步,让 simple_switch 加载它:
sudo ip link add veth0 type veth peer name veth1 sudo ip link set veth0 up sudo ip link set veth1 up sudo simple_switch --log-console -i 0@veth0 -i 1@veth1 basic.json启动日志里能看到 thrift 服务在 9090 端口监听、两个端口绑上,就说明 JSON 被正确解析、交换机能跑。另开一个终端验证控制面:
echo "table_dump port_table" | simple_switch_CLI能打印出空表结构、没有连接拒绝,说明 simple_switch_CLI 通过 thrift 0.9.2 跟交换机的运行时接口通上了。这一条把 bmv2、thrift、protobuf 三层全部串起来验证了一遍,比单独看哪个二进制存在有意义得多。
6.2 验证结果对照
跑完把结果对着下表核一遍,哪一栏不对就回对应章节:
| 检查项 | 期望结果 | 不对时回看 |
|---|---|---|
p4c --version | 打印版本,无报错 |
本文还有配套的精品资源,点击获取