☰
P4工具链环境配置指南:锁死protobuf与thrift版本,告别bmv2编译翻车
2026/10/17 10:52:03 网站建设 项目流程

简介:面向P4编程与BMv2软件交换机研究者的环境配置资源包,集中解决P4开发环境依赖组件版本匹配与安装繁琐的问题,适合需要快速搭建P4实验环境的学生、研究人员或网络工程师。整个压缩包采用gz格式,大小约147.8MB,内含behavioral-model(BMv2参考交换机)、p4c编译器、protobuf-3.2.0、thrift-0.9.2及gmock-1.7.0等工具链常用组件,均为编译运行P4程序所必需的依赖。已有609人学习下载,且作者提供配套配置教程链接,可对照步骤完成环境部署。通过此包,使用者无需再逐一寻找各组件历史版本并手动解决兼容性问题,可一次性获得已验证的安装包组合;结合教程能深入理解各依赖在P4开发流程中的角色,从而降低环境搭建门槛,将精力投入数据平面程序设计与实验验证。

1. 为什么P4环境配置容易卡壳:一份锁死版本的安装包能解决什么

第一次配P4开发环境的人,大概率不是被P4语法难住,而是被protobuf、thrift这些底层依赖拖垮的。我见过某同学在Ubuntu上编behavioral-model,configure检查protobuf版本这一步直接报错,明明装了protobuf 3.20,脚本却坚持要3.2.0,最后只能把源码包从头再编一遍。原因是bmv2、p4c这套工具链对第三方库的API极其敏感,版本差一个小版本都会编译失败,报错日志又常常不指向真正原因。

这份p4环境配置安装包,就是把behavioral-model、p4c、protobuf-3.2.0、thrift-0.9.2、gmock-1.7.0按一套验证过的组合打包。它解决的问题很具体:给你一条按顺序编译就能装完的路径,而不是让开发者在几百个版本组合里试错。适合正在学P4、需要在bmv2软件交换机上验证代码,以及被P4工具链配置反复摩擦过的开发者。

2. bmv2与p4c的依赖图谱:protobuf-3.2.0与thrift-0.9.2为何必须锁死

P4生态这套组合拆开看并不复杂:p4c负责把P4代码编译成后端描述文件,bmv2负责读取这个描述文件去模拟数据平面转发行为。真正的复杂度出在p4c和bmv2各自的构建体系上,它们把protobuf、thrift、gmock都当成编译和运行时的硬依赖,版本错一点都不行。这种感受很像当年的依赖地狱,也是如今大家在配P4环境时习惯先找现成配置包的原因。

2.1 p4c、BMv2、序列化库各自管哪一段

p4c是编译器前端,默认输出到bmv2能识别的JSON格式;behavioral-model是软件交换机,里面常见的是simple_switch和simple_switch_grpc两个可执行文件。前者走thrift通道提供CLI,后者走P4Runtime,protobuf在这条通道里做消息序列化。所以在完整环境里,p4c、bmv2、protobuf、thrift是同一个运行链路的四环:p4c编出的JSON交给bmv2加载,bmv2再靠thrift和protobuf对外提供控制接口。

gmock不是运行时依赖,它服务的是p4c的测试阶段。gmock-1.7.0属于老一代C++ Mocking框架,p4c在构建测试用例时找它的头文件和静态库来链接。如果跳过gmock直接编p4c,也能编出p4c主程序,但ctest环节会整片失败,后续想回归验证P4程序的行为就非常别扭。这也是安装包坚持把gmock-1.7.0单独列出来的原因。

2.2 为何偏偏锁protobuf-3.2.0与thrift-0.9.2

先说protobuf。protobuf-3.2.0是2017年初的版本,正好是P4Runtime方案还在快速迭代的时间窗口。bmv2源码里对protobuf的引用方式依赖旧API,和后续版本的protobuf并不兼容;新版本里部分类和函数的签名被重排,甚至直接移除,用apt源里更高的protobuf版本去编译bmv2,会在模板实例化阶段报出一大串看不懂的错误。反过来,用低于3.2.0的版本,proto文件里用到的语法特性又不齐全。所以3.2.0在很长一段时间里被当成这套工具的基准版本。

thrift那边同理。thrift-0.9.2为bmv2的simple_switch_CLI提供RPC框架。bmv2里有一批按0.9.2模板生成的C++代码,编译器生成的代码和运行时库的版本如果错位,符号就对不上。常见做法是把它装在/usr/local下,和protobuf一样统一由ldconfig管理。

2.3 安装包内的组件清单与角色对照

下表是这套包里每个组件在环境里的角色,拿到安装包后先对着看一遍再动手,比直接编译稳妥得多。

组件版本职责在运行链路中的位置
behavioral-model包内锁定BMv2软件交换机本体编译后产生simple_switch等可执行文件
p4c包内锁定P4代码编译器把P4源码转为bmv2可加载的JSON
protobuf3.2.0P4Runtime消息序列化被p4c和bmv2编译期和运行期引用
thrift0.9.2simple_switch_CLI的RPC通道运行时控制面与数据面的通信基础
gmock1.7.0p4c单元测试依赖只参与p4c的测试套件构建

有个容易被忽略的点:behavioral-model和p4c在代码里都对protobuf的头文件路径有默认假设。如果你把它们单独装到自定义目录,后面cmake找包时就要额外传一堆路径。建议严格按安装包默认的目录规划,所有组件都走/usr/local,不要自作聪明改prefix。

3. 从源码到可运行环境:五步安装顺序与成功验证

安装顺序不是随手定的。先装protobuf是因为p4c和behavioral-model都依赖它的头文件和库;再装thrift是因为bmv2的configure要检测它;gmock排在p4c之前是为了让p4c的测试套件完整构建;最后编behavioral-model。顺序乱掉就容易翻车,比如先编bmv2会因为它找不到thrift而停在configure阶段。

3.1 第零步:系统工具链先垫平

先用apt把基础工具链和常见依赖装齐。下面是我一般会用的命令:

sudo apt-get update sudo apt-get install -y autoconf automake libtool pkg-config \ g++ gcc make cmake git curl \ libssl-dev libpcre3-dev libreadline-dev \ libgmp-dev libboost-all-dev

autoconf、automake、libtool是源码包运行autogen.sh和configure的必备工具;pkg-config负责给configure提供第三方库的元信息,protobuf和thrift安装完后靠它被发现;libssl-dev和libreadline-dev分别服务于thrift的SSL支持和simple_switch_CLI的交互行读取。libboost-all-dev是新版Ubuntu里编译thrift必须的,其他工具链组件也会间接用到。

3.2 第一步:先把protobuf-3.2.0装进系统

进入安装包解压后的protobuf-3.2.0目录,依次执行:

cd ~/p4src/protobuf-3.2.0 ./autogen.sh ./configure --prefix=/usr/local make -j$(nproc) sudo make install sudo ldconfig

autogen.sh会生成configure脚本,它是protobuf源码树里自带的引导程序。--prefix=/usr/local让头文件进/usr/local/include,库文件进/usr/local/lib,这样pkg-config默认路径就能找到。make -j$(nproc)按CPU核数并行编译,能省不少时间。make install后必须执行sudo ldconfig,否则后续bmv2运行时可能找不到libprotobuf.so。装完可以用protoc --version确认版本号是3.2.0。

3.3 第二步:thrift-0.9.2与它的三个configure选项

thrift的构建脚本与protobuf略有不同,先跑bootstrap.sh生成configure,再关闭不需要的语言绑定:

cd ~/p4src/thrift-0.9.2 ./bootstrap.sh ./configure --prefix=/usr/local \ --without-python --without-ruby \ --without-nodejs --without-java make -j$(nproc) sudo make install sudo ldconfig

thrift的构建体系会连带生成很多语言的运行时,但我们只需要C++库。显式关掉python、ruby、nodejs、java的绑定,能显著缩短编译时间,也减少依赖冲突。这里有个细节:--without-python并不会影响simple_switch_CLI,简单交换机控制台走的是thrift的C++通信栈。装完thrift后,可以用thrift --version确认版本号为0.9.2。

3.4 第三步:gmock-1.7.0与p4c的CMake配置

gmock的编译相对简单,安装路径同样保持/usr/local:

cd ~/p4src/gmock-1.7.0 ./configure make -j$(nproc) sudo make install

gmock安装后会在/usr/local/include下提供gtest和gmock两套头文件,静态库则落在/usr/local/lib。p4c的CMake配置默认会去系统目录找GTest相关包,如果找不到,就手动把环境变量指过去。p4c编译我一般这么做:

cd ~/p4src/p4c export GMOCK_ROOT=/usr/local export GTEST_ROOT=/usr/local ./bootstrap.sh cd build cmake .. make -j$(nproc) sudo make install

bootstrap.sh负责生成build目录并初始化子模块,cmake ..读取顶层CMakeLists.txt。GMOCK_ROOT和GTEST_ROOT是为了让CMake的find_package能定位到gmock-1.7.0的安装位置,避免它去系统其他目录找更高版本。这里有一个常见误用:有人跳过export直接cmake,结果cmake静默降级,p4c编出来后测试套件缺了一截。所以这两个环境变量建议写进.bashrc。

3.5 第四步:behavioral-model的编译与三连验证

最后是behavioral-model本体。编译时我习惯加上--enable-debug,日志输出会更完整,排错阶段非常有用:

cd ~/p4src/behavioral-model ./autogen.sh ./configure --enable-debug make -j$(nproc) sudo make install

--enable-debug只影响bmv2自身的日志级别,不影响数据面转发性能。autogen.sh会重新生成configure和Makefile.in,如果缺了这一步,接下来多半会卡在缺少configure脚本。编译结束后,做一次三连验证:

which simple_switch which simple_switch_CLI which p4c

三条命令都能返回路径,说明核心可执行文件已经进入系统PATH。到这里,P4工具链的主干就装完了。

4. P4环境配置避坑:五条值得写进笔记的翻车记录

这五条是按真实踩坑频率排的,每一条都足够让人多耗半天。现象、原因、解决按顺序写,方便你在报错现场对照。

4.1 protobuf在gcc 9+环境下configure过了却编不过

现象:protobuf-3.2.0的configure很顺利,但make跑到一半报一堆模板错误,类似call to deleted constructor或无法将std::string转换为const char*。原因:protobuf 3.2.0是2017年的代码,现代gcc把旧的隐式类型转换当成硬错误。解决:在configure时追加旧标准兼容参数:

./configure --prefix=/usr/local \ CXXFLAGS="-O2 -fpermissive" \ CFLAGS="-O2 -fpermissive"

-fpermissive会把部分错误降级为警告,让旧代码能在新编译器下编过去。这个参数不完美,但作为兼容手段够用。

4.2 apt源里的protobuf与源码包串台,链接期才报错

现象:bmv2或p4c的configure都通过,make过程却报undefined reference,或者运行时提示protobuf library version mismatch。原因:apt源里有更高版本的libprotobuf-dev,configure找到的头文件是/usr/local/include里的3.2.0,链接时却命中了系统库的更高版本,两边ABI不一致。解决:让LD_LIBRARY_PATH优先指向/usr/local/lib:

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

可以在.bashrc里固定。更彻底的做法是把不再需要的系统protobuf库迁出/usr/lib,但动手前要确认没有其他程序依赖它。

4.3 thrift编译被boost新版本接口拦路

现象:thrift-0.9.2的configure能过,make到C++部分时却在boost头文件处崩掉,日志里出现BOOST_...未定义之类的字样。原因:新版本boost改动了一些模板接口,thrift 0.9.2生成的代码还按老接口写。解决:在configure阶段追加编译标准,并关闭不必要的库:

./configure --prefix=/usr/local \ CXXFLAGS="-std=c++11 -O1" \ --without-qt4 --without-qt5 --without-csharp

如果还不行,常见做法是装一套g++-8并指定CC和CXX环境变量编译,绕开新版gcc的兼容性问题。

4.4 simple_switch启动时找不到libprotobuf.so.15

现象:simple_switch命令能执行,但一运行就提示error while loading shared libraries: libprotobuf.so.15,进程直接退出。原因:protobuf安装后要么漏了ldconfig,要么/usr/local/lib不在动态库搜索路径里。解决:先执行sudo ldconfig,再确认导出变量:

sudo ldconfig export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

注意顺序不能反,先刷新缓存,再改环境变量。如果环境变量已写进.bashrc,记得先source ~/.bashrc再跑命令。

4.5 p4c的ctest找不到gmock,测试套件整体放弃

现象:p4c编译安装成功,但执行ctest时大量测试报错,提示无法打开gtest/gtest.h或找不到libgmock。原因:gmock-1.7.0装在/usr/local/lib,而CMake的模块没把该路径纳入搜索范围。解决:编译p4c前显式导出两个变量:

export GMOCK_ROOT=/usr/local export GTEST_ROOT=/usr/local

然后重新cmake并make。这里有个细节:GMOCK_ROOT和GTEST_ROOT要同时设置,只设一个时find_package依然可能在系统其他目录里抓到不匹配的版本。

5. 用BMv2跑通第一个P4程序:从p4c编译到CLI下表的完整闭环

环境装完不跑一个程序,永远不知道哪里还藏着缺漏。下面这个例子是最小可运行的P4十六程序,功能是IPv4查表转发,够验证整条链路。

5.1 一个能编译的最小P4程序

新建basic.p4,内容如下:

#include <core.p4> #include <v1model.p4> const bit<16> TYPE_IPV4 = 0x800; header ethernet_t { bit<48> dstAddr; bit<48> srcAddr; bit<16> etherType; } header ipv4_t { bit<4> version; bit<4> ihl; bit<8> diffserv; bit<16> totalLen; bit<16> identification; bit<3> flags; bit<13> fragOffset; bit<8> ttl; bit<8> protocol; bit<16> hdrChecksum; bit<32> srcAddr; bit<32> dstAddr; } struct headers { ethernet_t ethernet; ipv4_t ipv4; } struct meta {} parser MyParser(packet_in pkt, out headers hdr, inout meta m, inout standard_metadata_t sm) { state start { pkt.extract(hdr.ethernet); transition select(hdr.ethernet.etherType) { TYPE_IPV4: parse_ipv4; default: accept; } } state parse_ipv4 { pkt.extract(hdr.ipv4); transition accept; } } control MyIngress(inout headers hdr, inout meta m, inout standard_metadata_t sm) { action forward(bit<9> port) { sm.egress_spec = port; } action drop() { mark_to_drop(sm); } table t_fwd { key = { hdr.ipv4.dstAddr: lpm; } actions = { forward; drop; NoAction; } default_action = drop(); } apply { t_fwd.apply(); } } control MyDeparser(packet_out pkt, inout headers hdr) { apply { pkt.emit(hdr.ethernet); pkt.emit(hdr.ipv4); } } control MyVerifyChecksum(inout headers hdr, inout meta m) { apply {} } control MyComputeChecksum(inout headers hdr, inout meta m) { apply {} } V1Switch(MyParser(), MyVerifyChecksum(), MyIngress(), MyComputeChecksum(), MyDeparser()) main;

这段代码只做两件事:识别IPv4报文,再按目的地址查表转发。lpm匹配用的是最长前缀匹配,forward动作里把egress_spec设成出端口。default_action是drop,所以没下发表项前所有流量都会被丢掉,这是最安全的起点。

5.2 p4c编译与simple_switch加载

执行编译:

p4c --target bmv2 --arch v1model basic.p4

--target bmv2指定后端为BMv2,--arch v1model指定架构,两条参数缺一不可。编译成功后目录里会出现basic.json。接着启动simple_switch:

sudo simple_switch --log-console basic.json

sudo是因为simple_switch默认要绑定操作系统的网络功能,普通权限会在启动阶段报错。--log-console让报错直接打到终端,而不是静默写文件。看到类似Loading JSON and building pipeline的消息,说明BMv2已经加载了p4c生成的描述文件。

5.3 用simple_switch_CLI验证数据平面已接管

另开一个终端,用simple_switch_CLI下发一条转发规则:

echo 'table_add t_fwd forward 10.0.0.0/24 => 1' | sudo simple_switch_CLI

simple_switch_CLI通过thrift连上simple_switch,table_add命令的意思是:在t_fwd表里加一条匹配10.0.0.0/24的规则,动作是forward,参数为出端口1。命令执行后返回Adding entry to table t_fwd,就证明整条链路已经打通。从那以后我每次装完P4环境,都强制自己走一遍编译、加载、下表的闭环,而不是只看which命令的结果;少这一步,迟早会在真正跑数据包时被某个隐藏的链接问题再坑一次。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询