
简介这份资源面向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-modelbmv2、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 编译顺序与依赖关系表组件版本依赖编译顺序典型编译耗时protobuf3.2.0无需 zlib18-15 分钟gmock1.7.0无1可并行2-3 分钟thrift0.9.2protobuf可选210-20 分钟behavioral-model随包thrift protobuf gmock315-30 分钟p4c随包protobuf gmock420-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_lalloclibgc-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_glibno 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.0thrift 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_TYPERelease \ -DENABLE_GTESTSON make -j$(nproc) sudo make install sudo ldconfig-DENABLE_GTESTSON会编译单元测试如果你只想用编译器本身可以设为 OFF 省时间。-DCMAKE_BUILD_TYPERelease开启优化编译出的p4c-bm2-ss运行更快。验证p4c-bm2-ss --version # 应输出版本信息到这里五个组件全部就位。你可以写一个最简单的 P4 程序测试整条链路// minimal.p4 - 最简单的 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 { // 空 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 0veth0 -i 1veth1 minimal.json-i 0veth0表示把端口 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.1ABI 和 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 的 APIthrift 0.9.2 的代码还在调用。解决在configure时加--without-opensslP4 实验环境不需要 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 { 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) { action drop() { mark_to_drop(standard_metadata); } action forward(bit9 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 0veth0 -i 1veth1 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 名字对不上、端口绑定顺序反了这些坑只有实际发一个包才能暴露。希望帮到你。本文还有配套的精品资源点击获取