简介:这份资源面向SDN与可编程数据平面方向的学习者与开发者,尤其是正在搭建P4实验环境、准备相关课程设计或科研验证的人群。它整合了behavioral-model、p4c、protobuf-3.2.0、thrift-0.9.2与gmock-1.7.0等关键组件,用于解决P4编译与软件交换机运行环境依赖复杂、版本匹配困难的问题。压缩包为gz格式,整体约147.8MB,包含源码与依赖库等文件,便于一次性获取并离线部署。目前已有609人学习下载,说明该组合在P4入门与实验场景中具有一定参考价值。借助这份安装包,读者可省去逐个检索与编译依赖的时间,快速完成bmv2与p4c的配套环境搭建,为后续编写P4程序、验证数据包处理逻辑及开展网络实验提供基础支撑。
1. 从一次编译翻车说起:这套 P4 全家桶到底解决了什么
如果你在 Ubuntu 上从零编译过 P4 的参考软件交换机,大概率经历过这样的场景:./configure报 protobuf 版本不对,换了个版本后 thrift 又链接失败,好不容易把 bmv2 编出来,p4c编译 P4 程序时又提示 gmock 找不到。这不是你操作有问题,而是 P4 工具链的版本耦合出了名的紧——behavioral-model、p4c、protobuf、thrift、gmock 这五个组件之间存在严格的版本对应关系,任何一个版本漂移都会导致编译中断。
这套安装包把 behavioral-model(bmv2)、gmock-1.7.0、p4c、protobuf-3.2.0、thrift-0.9.2 五个组件的源码和依赖打包在一起,解决的正是「版本对齐」这个最耗时的环节。它适合两类人:一是刚接触 P4 语言、想在本地跑通simple_switch做转发实验的学生或转岗工程师;二是需要复现某个 P4 论文实验、但被环境卡住的开发者。你不需要再去 GitHub 上逐个 clone 再猜版本,包里的组合是经过验证能编过的。
我见过太多人在这里耗掉两三天,最后放弃改用 Docker 镜像——但 Docker 镜像里同样是这套版本组合,只是别人替你编好了。理解这套依赖链怎么咬合,比直接跑镜像更有价值,因为一旦你要改 bmv2 源码加自定义 extern,还是得回到本地编译。
2. 五个组件的依赖链:谁先编、谁依赖谁
2.1 为什么版本必须锁死
P4 工具链的编译顺序不是随意的,它由头文件引用和库链接关系决定。protobuf-3.2.0 是最底层的基础库,bmv2 和 p4c 都依赖它做序列化;thrift-0.9.2 提供 RPC 通信层,bmv2 的simple_switch通过 thrift 暴露运行时接口(比如往表里插表项);gmock-1.7.0 是 Google Mock 框架,p4c 的部分单元测试和 bmv2 的测试用例依赖它。
关键约束在于:protobuf 3.2.0 的libprotobufABI 与 thrift 0.9.2 的编译假设是匹配的,而 p4c 的某些版本对 protobuf 的DescriptorAPI 有特定调用方式,3.2.0 之后的版本改了接口签名,直接编会报no matching function。thrift 0.9.2 则是 bmv2 官方在那个时期指定的版本,0.10 以后 thrift 的 C++ 生成代码结构变了,bmv2 的SimpleSwitchThriftServer会编译失败。
所以顺序是:先编 protobuf 和 gmock(互不依赖),再编 thrift(依赖 protobuf 可选),然后编 bmv2(依赖 thrift + protobuf + gmock),最后编 p4c(依赖 protobuf + gmock,运行时调用 bmv2)。
2.2 编译顺序与依赖关系表
| 组件 | 版本 | 依赖 | 编译顺序 | 典型编译耗时 |
|---|---|---|---|---|
| protobuf | 3.2.0 | 无(需 zlib) | 1 | 8-15 分钟 |
| gmock | 1.7.0 | 无 | 1(可并行) | 2-3 分钟 |
| thrift | 0.9.2 | protobuf(可选) | 2 | 10-20 分钟 |
| behavioral-model | 随包 | thrift + protobuf + gmock | 3 | 15-30 分钟 |
| p4c | 随包 | protobuf + gmock | 4 | 20-40 分钟 |
这张表里的耗时是基于 4 核虚拟机、机械硬盘的参考值,SSD 上会快不少。注意 thrift 对 protobuf 的依赖是可选的——如果你不编 thrift 的 protobuf 插件,可以先编 thrift 再编 protobuf,但为了省事建议按表里顺序来。
2.3 环境准备:别在依赖库上栽跟头
在动手之前,系统级依赖必须先装齐。以下是 Ubuntu 16.04/18.04 上验证过的包列表(20.04 也能用,但 gmock 可能需要额外处理,后面避坑章会讲):
# 基础编译工具链 sudo apt-get update sudo apt-get install -y \ build-essential \ automake \ libtool \ pkg-config \ flex \ bison \ git \ curl # protobuf 编译依赖 sudo apt-get install -y \ zlib1g-dev # thrift 编译依赖 sudo apt-get install -y \ libboost-dev \ libboost-test-dev \ libboost-program-options-dev \ libboost-system-dev \ libboost-filesystem-dev \ libevent-dev \ libssl-dev # bmv2 额外依赖 sudo apt-get install -y \ libjudy-dev \ libgmp-dev \ libpcap-dev \ libreadline-dev # p4c 额外依赖 sudo apt-get install -y \ libboost-graph-dev \ libboost-iostreams-dev \ libgc-dev \ python3-dev这段脚本的逻辑是按组件分组安装,方便你定位问题。libjudy-dev是 bmv2 里JudyArray的依赖,缺了会在链接阶段报undefined reference to judy_lalloc;libgc-dev是 p4c 的垃圾回收依赖,缺了会在编译前端时失败。libboost-graph-dev用于 p4c 的控制流图分析,这个包名在 Ubuntu 18.04 之后有变化,如果装不上就换成libboost-graph1.65-dev之类的具体版本包。
提示:如果你在容器里编译,注意
libpcap-dev需要内核头文件,某些精简镜像里没有,会报net/bpf.h not found,补装linux-libc-dev即可。
3. 逐个击破:从 protobuf 到 p4c 的编译实操
3.1 protobuf-3.2.0 编译与安装路径
protobuf 是整条链的地基,它的安装路径会直接影响后面所有组件的configure参数。我一般把它装到/usr/local下,避免和系统自带的 protobuf 冲突。
# 进入安装包中的 protobuf 目录 cd protobuf-3.2.0 # 生成 configure 脚本 ./autogen.sh # 配置:指定安装前缀,关闭 64 位文件偏移检查(避免某些系统报错) ./configure --prefix=/usr/local \ --disable-shared \ CXXFLAGS="-fPIC" # 编译,-j 后跟 CPU 核心数 make -j$(nproc) # 安装 sudo make install # 刷新动态库缓存 sudo ldconfig这里有几个参数值得说明。--disable-shared是只编静态库,因为 bmv2 和 p4c 链接时用静态库更省心,避免运行时找不到.so。CXXFLAGS="-fPIC"是位置无关代码,静态库被链接进动态库时必须加,否则 bmv2 编libbmpi.so时会报relocation R_X86_64_32 against ... can not be used when making a shared object。
编完后验证:
protoc --version # 应输出 libprotoc 3.2.0如果输出的是系统自带的版本(比如 2.6.1),说明/usr/local/bin不在 PATH 前面,或者系统里装了protobuf-compiler包。用which protoc确认路径,必要时sudo apt-get remove protobuf-compiler卸掉系统包。
3.2 gmock-1.7.0 的编译与路径处理
gmock-1.7.0 是个比较老的版本,它的编译方式和后来的独立 gtest 不同,需要先编 gtest 再编 gmock。
# 进入 gmock 目录 cd gmock-1.7.0 # 先编 gtest cd gtest ./configure --prefix=/usr/local make -j$(nproc) sudo make install cd .. # 再编 gmock ./configure --prefix=/usr/local make -j$(nproc) sudo make install sudo ldconfiggmock 1.7.0 的configure脚本会自动检测 gtest 是否已安装,所以顺序不能反。安装后头文件在/usr/local/include/gmock和/usr/local/include/gtest,库文件在/usr/local/lib。如果后面 bmv2 编译时报gmock/gmock.h: No such file or directory,检查/usr/local/include是否在编译器的默认搜索路径里——通常g++会默认搜/usr/local/include,但某些交叉编译环境需要显式加-I/usr/local/include。
3.3 thrift-0.9.2 编译:最容易卡住的一环
thrift 0.9.2 的编译是这套流程里最容易出问题的,因为它对 boost 版本和 openssl 版本都有要求。
cd thrift-0.9.2 # 生成 configure ./bootstrap.sh # 配置:关闭不需要的语言绑定,只留 C++ 和 Python ./configure --prefix=/usr/local \ --without-java \ --without-php \ --without-ruby \ --without-perl \ --without-python \ --without-go \ --without-nodejs \ --with-cpp \ --with-c_glib=no make -j$(nproc) sudo make install sudo ldconfig--without-*那一串是为了加快编译速度,thrift 默认会尝试编译所有语言绑定,Java 和 Node.js 的绑定会拖慢很多。如果你后续要用 Python 写控制平面(比如用p4runtime的 Python 库),把--without-python去掉即可。
编译过程中如果报error: 'SSLv3_method' was not declared,说明 openssl 版本太新(1.1.0+),thrift 0.9.2 还在用 SSLv3 的 API。解决办法是打补丁或者降级 openssl,但更简单的做法是在configure时加--without-openssl,因为 P4 实验环境里 thrift 的 SSL 功能基本用不到。
3.4 behavioral-model 编译:链接顺序有讲究
bmv2 的编译依赖前面三个组件全部就位。进入 bmv2 目录后:
cd behavioral-model # 生成 configure ./autogen.sh # 配置:指定 thrift 和 protobuf 路径 ./configure --prefix=/usr/local \ --with-thrift=/usr/local \ --with-protobuf=/usr/local \ --enable-debugger make -j$(nproc) sudo make install sudo ldconfig--enable-debugger会编出simple_switch的调试版本,支持--debugger参数,方便你在运行时查看表项和寄存器状态。如果不需要调试功能,去掉这个参数可以省几分钟编译时间。
编完后验证:
simple_switch --help # 应输出 usage 信息,包含 --interface、--pcap 等参数如果报error while loading shared libraries: libthrift-0.9.2.so: cannot open shared object file,说明/usr/local/lib不在动态链接器的搜索路径里。检查/etc/ld.so.conf.d/下是否有包含/usr/local/lib的配置文件,没有就手动加一个再sudo ldconfig。
3.5 p4c 编译:最后一块拼图
p4c 的编译依赖 protobuf 和 gmock,不直接依赖 thrift(它通过 bmv2 的运行时接口通信)。
cd p4c # p4c 用 cmake 构建 mkdir -p build cd build cmake .. \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_BUILD_TYPE=Release \ -DENABLE_GTESTS=ON make -j$(nproc) sudo make install sudo ldconfig-DENABLE_GTESTS=ON会编译单元测试,如果你只想用编译器本身,可以设为 OFF 省时间。-DCMAKE_BUILD_TYPE=Release开启优化,编译出的p4c-bm2-ss运行更快。
验证:
p4c-bm2-ss --version # 应输出版本信息到这里五个组件全部就位。你可以写一个最简单的 P4 程序测试整条链路:
// minimal.p4 - 最简单的 P4 程序,只做转发 #include <core.p4> #include <v1model.p4> header ethernet_t { bit<48> dstAddr; bit<48> srcAddr; bit<16> 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 { // 空 apply,默认行为是丢弃 } } 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); } } control MyChecksum(inout headers hdr, inout metadata meta) { apply { } } V1Switch(MyParser(), MyIngress(), MyEgress(), MyChecksum(), MyDeparser()) main;编译这个程序:
p4c-bm2-ss --target bmv2 --arch v1model minimal.p4 -o minimal.json如果生成了minimal.json,说明 p4c 和 bmv2 的 target 后端都正常工作。然后用 bmv2 加载:
sudo simple_switch -i 0@veth0 -i 1@veth1 minimal.json-i 0@veth0表示把端口 0 绑定到veth0网卡。这一步需要你先创建 veth pair,属于网络命名空间的操作,不在本文展开。
4. 避坑指南:五个让编译中断的典型问题
4.1 protobuf 版本冲突:系统包和源码包打架
现象:protoc --version显示 3.2.0,但 bmv2 编译时报undefined reference to google::protobuf::internal::...,符号名带版本后缀。
原因:系统里同时存在 apt 安装的libprotobuf-dev和源码编译的 protobuf,链接器优先找到了系统库。Ubuntu 16.04 自带的 protobuf 是 2.6.1,ABI 和 3.2.0 不兼容。
解决:sudo apt-get remove libprotobuf-dev protobuf-compiler卸掉系统包,然后sudo ldconfig刷新缓存。如果卸不掉(被其他包依赖),在 bmv2 的configure里显式指定--with-protobuf=/usr/local,并在LDFLAGS里加-L/usr/local/lib -Wl,-rpath,/usr/local/lib。
4.2 thrift 编译报 SSLv3 错误
现象:error: 'SSLv3_method' was not declared in this scope,出现在TSslSocket.cpp或类似文件。
原因:openssl 1.1.0 移除了 SSLv3 的 API,thrift 0.9.2 的代码还在调用。
解决:在configure时加--without-openssl,P4 实验环境不需要 thrift 的 SSL 加密。如果必须用 SSL,需要手动改 thrift 源码里的TSSLSocket.cpp,把SSLv3_method()替换为TLS_method()。
4.3 bmv2 链接时找不到 Judy 库
现象:undefined reference to 'judy_lalloc'或cannot find -lJudy。
原因:libjudy-dev没装,或者装在了非标准路径。
解决:sudo apt-get install libjudy-dev。如果已经装了还报错,用dpkg -L libjudy-dev | grep libJudy找到库文件路径,然后在configure时加LDFLAGS="-L/那个路径"。
4.4 p4c 编译时 gmock 头文件找不到
现象:fatal error: gmock/gmock.h: No such file or directory,但/usr/local/include/gmock/gmock.h确实存在。
原因:p4c 的 cmake 脚本默认在/usr/include下找 gmock,没搜/usr/local/include。
解决:在 cmake 命令里加-DCMAKE_CXX_FLAGS="-I/usr/local/include",或者把 gmock 装到/usr而不是/usr/local(不推荐,容易和系统包冲突)。
4.5 simple_switch 运行时找不到动态库
现象:simple_switch --help报error while loading shared libraries: libbmpi.so: cannot open shared object file。
原因:/usr/local/lib不在ld.so的搜索路径里。
解决:创建/etc/ld.so.conf.d/local.conf,内容写/usr/local/lib,然后sudo ldconfig。验证:ldconfig -p | grep bmpi应能看到库条目。
5. 验证与进阶:用 table 转发确认整条链路真的通了
编译通过只是第一步,真正要确认的是「P4 程序能控制交换机行为」。我一般用一个带 table 的最小程序做端到端验证,比空 apply 更有说服力。
// forward.p4 - 带 table 的转发程序 #include <core.p4> #include <v1model.p4> header ethernet_t { bit<48> dstAddr; bit<48> srcAddr; bit<16> 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) { action drop() { mark_to_drop(standard_metadata); } action forward(bit<9> port) { standard_metadata.egress_spec = port; } table ipv4_dst { key = { hdr.ethernet.dstAddr : exact; } actions = { forward; drop; } default_action = drop(); size = 64; } apply { ipv4_dst.apply(); } } 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); } } control MyChecksum(inout headers hdr, inout metadata meta) { apply { } } V1Switch(MyParser(), MyIngress(), MyEgress(), MyChecksum(), MyDeparser()) main;编译并加载:
p4c-bm2-ss --target bmv2 --arch v1model forward.p4 -o forward.json sudo simple_switch -i 0@veth0 -i 1@veth1 forward.json然后用 bmv2 的运行时命令行(simple_switch_CLI)往 table 里插表项:
# 另开一个终端 simple_switch_CLI --thrift-port 9090 # 在 CLI 里执行 table_add ipv4_dst forward 00:00:00:00:00:01 => 1 table_add ipv4_dst forward 00:00:00:00:00:02 => 2 table_dump ipv4_dsttable_add的参数格式是「表名 动作名 匹配键 => 动作参数」。这里表示:目的 MAC 为00:00:00:00:00:01的包从端口 1 转发,00:00:00:00:00:02从端口 2 转发。table_dump能列出当前所有表项,确认插入成功。
验证方法:用ping或scapy从veth0发一个目的 MAC 为00:00:00:00:00:01的包,在veth1上抓包应能看到。如果没抓到,检查simple_switch的日志(启动时加--log-console),看包是否被 parser 正确解析、table 是否命中。
这套验证跑通,说明 protobuf 的序列化、thrift 的 RPC、bmv2 的数据平面、p4c 的编译后端全部正常工作。之后你要加自定义 header、改 parser 状态机、或者用 P4Runtime 替代 thrift CLI,都是在这个基础上做增量。
从那以后我每次配新环境,都会先用这个带 table 的程序跑一遍端到端,确认表项能插、包能转,再开始做真正的实验。因为编译通过不代表运行时没问题,thrift 端口没起来、table 名字对不上、端口绑定顺序反了,这些坑只有实际发一个包才能暴露。希望帮到你。
本文还有配套的精品资源,点击获取