ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

P4环境配置指南:protobuf/thrift/gmock/p4c/bmv2版本组合与编译

P4环境配置指南:protobuf/thrift/gmock/p4c/bmv2版本组合与编译 简介这套资源是为P4网络数据平面编程学习者准备的依赖安装合集覆盖behavioral-modelBMV2软件交换机、p4c编译器、protobuf-3.2.0、thrift-0.9.2以及gmock-1.7.0解决P4开发环境中组件版本互不兼容、编译安装繁琐的痛点。其中protobuf与thrift负责数据序列化与RPC通信gmock用于单元测试p4c负责P4代码编译behavioral-model则提供可运行的交换机行为模型几者共同构成完整的P4开发链。压缩包以gz格式整体打包大小约147.8MB下载后可直接获得这些构建工具与库文件并配合作者提供的配置教程CSDN链接见下载页快速搭建可运行的环境。目前已有608人学习下载适合刚开始接触P4与BMV2、需要搭建本地实验环境的学生和研究人员。这些组件在P4数据平面开发中各自承担关键角色使用这份安装包能省去逐一下载与反复匹配版本的流程降低入门门槛同时为课程作业、SDN实验或科研验证提供稳定基础。1. P4 环境配置为什么总在装依赖时翻车这套安装包的定位与适用人群在 P4 数据平面开发这条路上真正劝退人的往往不是 P4 语言的语法而是环境配置。protobuf、thrift、gmock、p4c、bmv2 五个组件堆在一起版本稍微错一个configure 阶段就崩后面全是连锁反应。这套安装包的价值就是把你我从“装完 protobuf 又发现 thrift 编译不过、修完 thrift 又遇到 p4c 找不到 gtest”的循环里拉出来protobuf-3.2.0、thrift-0.9.2、gmock-1.7.0 作为固定底层配合 p4c 和 bmv2 的源码组成一个经过验证的版本组合。它适合三类人跑 P4 课程或毕设的在校生、需要反复重建 bmv2 测试环境的工程师、以及所有不想跟系统自带旧库纠缠的 SDN 研究者。2. 版本组合的兼容逻辑五个组件在 P4 编译与运行链路中的位置用 P4 开发数据平面本质上是一条“语言 → 编译器 → 执行目标”的流水线。p4c 负责把 P4-16 源码编译成特定目标能识别的配置bmv2 是这个配置的执行者而 protobuf、thrift、gmock 是这条流水线底下的基础设施。想装得干净必须先理解这些组件谁在编译期依赖谁、谁在运行期调用谁。2.1 组件分工谁是编译器谁是可执行目标P4 本身是通用数据平面语言它不能直接跑在硬件或软件设备上必须先经过编译器。p4c 就是当前主流的 P4-16 编译器它能把.p4文件编译成多种后端目标格式。当目标设为 bmv2 时p4c 会生成一个 JSON 文件这个 JSON 描述了解析器、匹配表、动作、校验和等完整数据平面逻辑。bmv2behavioral-model则是 P4 官方的软件交换机参考实现。它把那个 JSON 当作配置加载在用户态的进程里模拟真实交换机的行为。bmv2 内部包含多个交换机模型最常用的是 simple_switch它基于 v1model 架构支持从 CLI 动态增删流表。简单说p4c 是“编译器”bmv2 是“执行引擎”二者通过 JSON 这把钥匙对接。gmock 在这条链路里是“测试信任基座”。p4c 的后端代码里大量使用 gtest/gmock 写单元测试bmv2 的行为模型也有自己的测试代码。安装包里带上 gmock-1.7.0是为了保证你在编译 p4c 或 bmv2 时不缺测试头文件也能在后续开发里用同一套框架写自己的行为模型测试。2.2 底层依赖protobuf 和 thrift 如何支撑 bmv2 与 p4cprotobuf 和 thrift 是 bmv2 的骨头。bmv2 的架构里控制面与数据面是分离的CLI 进程或 P4Runtime 客户端与 simple_switch 进程之间的通信依赖分层。其中 thrift 负责传统 simple_switch_CLI 的 RPC 通信protobuf 则是 P4Runtime 规范里结构化消息的序列化格式。换句话说你在 simple_switch_CLI 里敲一条 table_add这条命令先经 thrift 编码再被交换进程解码执行而若是通过 P4Runtime 下发流表那消息格式就由 protobuf 定义。p4c 对 protobuf 的依赖同样直接它生成 P4Info 描述文件时需要 protobuf 库编译期也需要 protobuf 头文件。所以 protobuf 和 thrift 不是可选项它们是 bmv2 和 p4c 从源码构建时过不去的坎。安装顺序上必须先装底层的 protobuf 和 thrift再编译 p4c 和 bmv2否则 configure 阶段就会直接报缺依赖。2.3 为什么必须是 protobuf-3.2.0 和 thrift-0.9.2版本边界分析protobuf 现在都到 4.x/5.x 了P4 相关代码为什么还守着一个 3.2.0我一次血泪经验装新版 protobuf 后bmv2 的运行时库编译通过但 P4Runtime 消息序列化时字段编号和 wire type 全对不上抓包全是乱码。bmv2 和 p4c 的代码是按特定 protobuf API 写的老接口google::protobuf::DescriptorPool、GetReflection在不同大版本下行为有微妙差异尤其生成的.pb.h文件与新库 ABI 不兼容轻则 warn重则 segment fault。thrift 同理。thrift 0.9.2 属于 0.9.x 里较稳的版本bmv2 的simple_switch_CLI直接用 thrift 生成的simple_switch.thrift代码而这份生成代码绑定了 thrift 0.9.x 的模板实现。换到 thrift 0.13 甚至 0.17编译期会报TProcessor接口变化、TServerSocket构造签名不一致等一堆问题。gmock 1.7.0 则是和 gtest 1.7.0 绑定的版本它与 googletest 之后 split 成独立仓库的做法不同在同一个源码包内这恰好是 p4c 旧版测试代码的包容组合。下面这张表是我整理的组件关系方便对照组件版本职责谁依赖它protobuf3.2.0P4Runtime 消息序列化、p4c 生成代码bmv2、p4cthrift0.9.2simple_switch 与 CLI 之间的 RPCbmv2gmock1.7.0单元测试 mock 框架p4c、bmv2 测试代码p4c最新 masterP4-16 编译器不依赖 bmv2 但输出 bmv2 JSONbmv2最新 master软件交换机执行引擎依赖 protobuf、thrift了解完这套关系再去看安装包里面的文件就知道为什么是这些版本组合而不是随便拿最新的能跑通。版本之间不是孤立它是一条完整的编译链任何一环脱离了这个组合都可能引发链式反应。3. 安装落地protobuf、thrift、gmock、p4c、bmv2 的分步编译要点安装包解压后目录里通常能直接看到protobuf-3.2.0/、thrift-0.9.2/、gmock-1.7.0/、p4c/、behavioral-model/五块源码。我建议自己新建一个目录统一放置比如~/p4env把解压内容全部放进去。以下所有命令都假定你在这个目录的上级位置执行路径写死避免装到一半找不着包。3.1 准备工作判断系统依赖是否齐全在 Ubuntu 18.04/20.04 上先检查编译工具链是否完整。缺了 autoconf 或 libtool后面 bmv2 的autogen.sh一定会失败。我一般先跑一段依赖检查命令sudo apt update sudo apt install -y build-essential autoconf automake libtool curl \ pkg-config libssl-dev libboost-all-dev cmake python3-dev这里build-essential提供 gcc/g 和 makelibboost-all-dev是 thrift 0.9.2 编译必需的 Boost 库cmake用来构建 p4c。装完之后顺手检查一下 gcc 版本gcc --version cmake --version注意如果你的系统已经装过 protobuf 或 thrift先不要急着卸载后面配置时用--prefix/usr/local独立安装再用环境变量隔离避免破坏系统包。3.2 编译 protobuf-3.2.0configure 参数与安装验证进入 protobuf 源码目录执行标准的 autotools 流程。3.2.0 版本还比较传统不像新版用 cmakecd ~/p4env/protobuf-3.2.0 ./configure --prefix/usr/local make -j4 sudo make install sudo ldconfig--prefix/usr/local决定了头文件会被装到/usr/local/include库文件到/usr/local/lib这样不影响系统自带的/usr/lib上的旧版 protobuf。make -j4是并发编译机器核数多可以改成-j8但不建议超过物理核数避免内存不足编译被杀。安装完成后验证protoc --version ldconfig -p | grep protobuf echo #include google/protobuf/message.h | g -x c - -lprotobuf -o /dev/null第三条命令能通过说明头文件和库文件都能被编译器找到。如果最后一步报找不到头文件多半是/usr/local/include不在默认搜索路径里需要检查/etc/ld.so.conf.d是否包含/usr/local/lib。3.3 编译 thrift-0.9.2附带裁剪配置thrift 是个“全家桶”框架支持 Java、Python、PHP 等一堆语言但 bmv2 只需要 C 那一部分。编译前把不需要的语言统统裁掉能省下大量依赖和时间cd ~/p4env/thrift-0.9.2 ./configure --prefix/usr/local \ --with-boost/usr/local \ --with-opensslno \ --without-qt4 --without-qt5 \ --without-csharp --without-java --without-python \ --without-ruby --without-haskell --without-nodejs \ --without-lua --without-perl --without-php make -j4 sudo make install sudo ldconfig--with-boost/usr/local是关键。若 Boost 装的是系统包通常它在/usr/include但有些发行版会放在/usr/local/include指定路径能避免 configure 时 “Boost C Libraries were not found” 的误判。--with-opensslno是裁剪掉 TLS 传输层因为我们只在本地回环跑模拟不需要加密链路。编译完成后thrift 编译器thrift和库libthrift都会被安装验证thrift --version3.4 安装 gmock-1.7.0 与测试框架gmock-1.7.0 源码包自带 gtest二者同源编译最不容易出问题。进入目录后它没有顶层 configure需要先进入gtest目录或者直接用根目录的 Makefile。常见的做法是cd ~/p4env/gmock-1.7.0 ./configure make -j4 sudo make install这个 configure 会同时生成 gtest 和 gmock 的库。安装后/usr/local/lib下应该有libgmock.a和libgtest.a头文件在/usr/local/include/gmock和/usr/local/include/gtest。注意 gmock-1.7.0 的头文件路径是gmock/gmock.h不是新版gmock/include/gmock.h。很多编译错误都是因为这个路径差异。检查无误后可以用一个简单文件验证cat test_smoke.cpp EOF #include gmock/gmock.h int main() { ::testing::InitGoogleMock(); return 0; } EOF g test_smoke.cpp -lgmock -lgtest -lpthread -o test_smoke ./test_smoke3.5 编译 p4c 和 bmv2cmake 与 autotools 的区别p4c 使用 cmake构建前必须把子模块拉全否则会报缺少P4C相关头文件。安装包里如果已经带了完整源码直接进p4c/build构建cd ~/p4env/p4c git submodule update --init --recursive mkdir -p build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DP4C_ENABLE_ASSERTSON make -j4 sudo make install-DP4C_ENABLE_ASSERTSON可以让编译器在最测试模式下运行虽然性能会略降但调试时能捕获更多内存错误。p4c 安装后p4c可执行文件通常落在/usr/local/bin且会自动带上全家桶p4c-bm2-ss、p4c-bm2-ps。bmv2 是 autotools 体系需要先运行autogen.sh生成配置脚本cd ~/p4env/behavioral-model ./autogen.sh ./configure --prefix/usr/local --enable-debug make -j4 sudo make install--enable-debug编译时保留符号表虽然库会大不少但后续用 gdb 排查 P4 运行行为很有用。如果只是要跑程序不需要调试可以去掉它。bmv2 默认会安装simple_switch、simple_switch_CLI等多个工具装完检查which simple_switch simple_switch_CLI到这里五个组件全部安装完成。注意 p4c 与 bmv2 的先后顺序p4c 不依赖 bmv2 的库而 bmv2 的 configure 脚本会检测 p4c如果先装 bmv2 再装 p4cbmv2 也能构建只是不会启用 p4c 相关的编译测试推荐先 p4c 后 bmv2。4. 从源码到转发用 p4c 编译 L2 转发程序并跑通 bmv2环境装好下一步是让它真正动起来。这里我以一个最简单的 L2 转发程序为例完整演示编写、编译、加载、测试全过程。这一套走通说明安装包所有组件都工作正常而不是“能编译但跑不了”的花架子。4.1 编写一个可验证的 P4-16 程序用 v1model 架构写一个最小程序只提取以太网头维护一张目的 MAC 至出端口的表查表后把包送出指定端口。保存为l2_forward.p4#include core.p4 #include v1model.p4 typedef bit9 egressSpec_t; header ethernet_t { MACAddress dstAddr; MACAddress srcAddr; bit16 etherType; } struct headers { ethernet_t ethernet; } struct metadata { } parser MyParser(packet_in pkt, out headers hdr, inout metadata meta, inout standard_metadata_t sm) { state start { pkt.extract(hdr.ethernet); transition accept; } } control MyIngress(inout headers hdr, inout metadata meta, inout standard_metadata_t sm) { action forward(egressSpec_t port) { sm.egress_spec port; } table dmac { key { hdr.ethernet.dstAddr: exact; } actions { forward; NoAction(); } default_action NoAction(); size 1024; } apply { dmac.apply(); } } control MyEgress(inout headers hdr, inout metadata meta, inout standard_metadata_t sm) { apply { } } control MyVerifyChecksum(inout headers hdr, inout metadata meta) { apply { } } control MyComputeChecksum(inout headers hdr, inout metadata meta) { apply { } } control MyDeparser(packet_out pkt, inout headers hdr) { apply { pkt.emit(hdr.ethernet); } } V1Switch(MyParser(), MyVerifyChecksum(), MyIngress(), MyEgress(), MyComputeChecksum(), MyDeparser()) main;这个程序刻意不处理 IPv4 和 ARP只做纯以太网帧的查表转发。dmac表用目的 MAC 做精确匹配动作forward设置出端口。其余控制块全部空转方便聚焦在 bmv2 的加载和流表下发上。4.2 编译与生成 JSON命令参数说明用 p4c 的 bmv2 后端编译cd ~/p4env p4c --target bmv2 --arch v1model l2_forward.p4--target bmv2告诉编译器生成 bmv2 能加载的 JSON--arch v1model指定采用 v1model 架构因为我们程序里 include 的是 v1model.p4。命令默认输出l2_forward.json同时还会有l2_forward.p4info.txt等文件。检查输出ls -lh l2_forward.jsonJSON 文件大小大约几十 KB里面包含解析器状态机和 match table 定义。如果编译时报错“unexpected token”说明 p4c 与源码版本不匹配倒回--std p4-16再试。4.3 配置 veth 并启动 simple_switchbmv2 的 simple_switch 是通过操作系统网络设备收发报文的所以我们需要一对 veth 虚拟网卡把两个端口连起来。没有 vethsimple_switch 只能启动没法传包sudo ip link add veth0 type veth peer name veth1 sudo ip link set veth0 up sudo ip link set veth1 up然后启动交换机端口 0 绑定 veth0端口 1 绑定 veth1sudo simple_switch --device-id 0 -i 0veth0 -i 1veth1 l2_forward.json--device-id 0是交换机的逻辑 ID多实例交换实验时用这个区分不同交换机-i 0veth0的意思是把 veth0 作为数据面端口 0-i 1veth1同理。启动成功后终端会卡在前台打印 thrift server 启动在 9090 端口。这就是运行期与 CLI 的通信通道。此时另开一个终端检查 veth0 和 veth1 的 MAC 地址ip link show veth0记录下 veth1 的 MAC假设是aa:bb:cc:00:01:02等下流表里要用。4.4 写入流表并抓包验证启动 CLI 连到交换机用 thrift 端口下发流表simple_switch_CLI --thrift-port 9090进入 CLI 后输入table_add dmac forward aa:bb:cc:00:01:02 - 1这条命令的意思是当以太网目的 MAC 等于aa:bb:cc:00:01:02时执行forward动作参数为 1即把包从端口 1 转发出去。注意这里的 MAC 必须是 veth1 实际 MAC你可以用ip link查看然后替换。下发成功后CLI会输出Adding entry to exact match table dmac。现在从 veth0 发一个目的 MAC 为aa:bb:cc:00:01:02的原始以太网帧。我用一个小的 Python raw socket 脚本不依赖 scapysudo python3 -c import socket s socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003)) s.bind((veth0, 0)) frame bytes.fromhex(aabbcc0001020000000000010800 00 * 46) s.send(frame) 十六进制前 6 字节是目的 MAC接着 6 字节源 MAC0800是 EtherType后面 46 字节 padding 凑够最短以太网帧。发送后在另一个终端里抓包sudo tcpdump -i veth1 -nn -e -c 1如果抓到了源 MAC 为00:00:00:00:00:01的帧说明p4c 编译正确 → JSON 被 simple_switch 正确加载 → thrift CLI 成功写入流表 → 数据包按表项转发。这条链路一旦全部打通这套安装包就真正可用了。5. 避坑排查五条高频报错的现象、原因与解决路径即使版本组合固定安装过程中仍可能踩到环境相关的坑。下面是我在多台机器上复现过的五条高频问题每一条都写清楚现象、根因和处理方式遇到类似情况直接对照查。5.1 configure 找不到 protobuf现象p4c 或 bmv2 执行 configure 时日志提示checking for protobuf... no或者 cmake 报Could NOT find Protobuf。原因最常见的是/usr/local/lib没有被 ldconfig 纳入路径导致新装 protobuf 库虽然存在但链接器看不到。另一种原因是系统同时装了 protobuf 的旧版比如 2.6.1configure 脚本优先找到了旧版头文件类型定义不匹配后直接放弃。解决先执行sudo ldconfig然后用protoc --version确认当前版本。如果版本正确设置环境变量后重新配置export LD_LIBRARY_PATH/usr/local/lib:$LD_LIBRARY_PATH export PKG_CONFIG_PATH/usr/local/lib/pkgconfig如果问题依旧检查/usr/local/include/google/protobuf下是否存在旧版遗留头文件清理干净再看。5.2 thrift 编译找不到 Boost 库现象thrift-0.9.2 执行./configure时报错Error: The Boost C Libraries were not found。原因thrift 0.9.2 的老式 configure 会用BOOST_CPPFLAGS去固定路径找boost/shared_ptr.hpp有的系统把 Boost 装在/usr/local/include但反引号检查不包含这个路径。解决显式指定 Boost 路径并用BOOST_CPPFLAGS让预处理器能搜到./configure --prefix/usr/local --with-boost/usr/local \ BOOST_CPPFLAGS-I/usr/local/include/boost如果还不行确认是不是真的装了libboost-all-dev。我遇到过最隐蔽的是系统里有多个 Boost 版本configure 找到的是 1.58 的头文件但库是 1.74导致编译到一半报undefined reference。这种情况建议彻底删除系统 boost 然后重装sudo apt purge *boost* sudo apt install libboost-all-dev5.3 gmock 与系统 gtest 版本冲突现象编译 gmock-1.7.0 时make报类似cannot declare field testing::internal::...的语法错误。原因系统中预先安装了新版 gtest比如 1.8gmock-1.7.0 源码 include 时优先找到了/usr/local/include/gtest/gtest.h新旧两套代码的类内部结构不同导致 ODR 违规。解决编译 gmock 前先移除系统 gtest 头文件或者强行指定 gmock 源码自带的 gtestcd ~/p4env/gmock-1.7.0 ./configure make CXXFLAGS-I$(pwd)/gtest/include -I$(pwd)/include sudo make install注意这里$(pwd)是让编译使用源码内部的 gtest 目录而不是系统路径。装完之后最好再跑一次第 3.4 节的 smoke test确认链接的库来自本机源码。5.4 p4c 编译时缺少 gtest.h现象p4c 的cmake ..阶段报fatal error: gtest/gtest.h: No such file or directory或者googlemock/gtest not found。原因p4c 的测试框架需要 gtest 和 gmock 可见但 gmock-1.7.0 安装后头文件在/usr/local/include/gmockgtest 在/usr/local/include/gtest。如果安装包里的 gmock 只是源码包还没有make installp4c 自然找不到。解决确保 gmock 已经 install然后设置GTEST_ROOT给 p4c 指路export GTEST_ROOT/usr/local cmake .. -DGTEST_ROOT/usr/local -DGMOCK_ROOT/usr/local如果仍找不到手动把 gmock/include 和 gtest/include 下的头文件复制到/usr/local/include并把静态库复制到/usr/local/lib再重跑 cmake。5.5 simple_switch 无法绑定 veth 接口现象启动 simple_switch 时报错Cannot open socket /bind failed for veth0: Permission denied或者Invalid argument。原因这个问题一半是权限一半是 veth 设备不存在。-i 0veth0要求当前用户对网络命名空间有操作权限非 root 运行时容易拒绝另外 veth0 只是临时创建的如果之前没执行ip link addsimple_switch 无法绑定一个不存在的接口。解决用 sudo 运行并确确保 veth 设备真实存在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 ... l2_forward.json如果ip link报RTNETLINK answers: Operation not permitted说明当前 shell 没有网络管理权限检查是否在容器内运行容器需要加--privileged才能操作 veth。6. 把安装过程固化成可重试脚本断点续装与三行自检手工敲完一遍安装命令最后一件值得做的事是把整个流程写成脚本以后换机器不用再面对黑匣子。我一般采用“断点记录 重试”的脚本结构用一个.install_progress文件记录已完成步骤失败后修好问题重新执行脚本会从断点继续而不是从头再来。6.1 断点记录机制脚本骨架示例如下#!/bin/bash set -euo pipefail PROGRESS_FILE$(pwd)/.install_progress done_step() { grep -Fxq $1 $PROGRESS_FILE 2/dev/null } mark_done() { echo $1 $PROGRESS_FILE } install_protobuf() { cd protobuf-3.2.0 ./configure --prefix/usr/local make -j4 sudo make install sudo ldconfig cd - /dev/null mark_done protobuf } install_protobuf || { echo protobuf 失败修复后重跑本脚本; exit 1; } install_thrift() { cd thrift-0.9.2 ./configure --prefix/usr/local --with-boost/usr/local ... make -j4 sudo make install sudo ldconfig cd - /dev/null mark_done thrift }核心是每个函数开头先调用done_step已完成的直接跳过未完成的执行并写标记。这样即使 protobuf 反复失败也不会每次都把后面的依赖重新编译一遍。脚本里我故意没有把mark_done放在函数开头而是放在函数末尾保证只有真正成功才记账避免“假成功”。6.2 安装完成后的环境自检脚本末尾我习惯放三行验证命令可以快速判断整个环境是否可用p4c --version || echo p4c 不可用 simple_switch --version || echo simple_switch 不可用 protoc --version || echo protobuf 不可用如果 p4c 可执行但 simple_switch 缺失多半是 bmv2 的库路径没配好加上export LD_LIBRARY_PATH/usr/local/lib再脚本外验证。从那以后我每次换新机器都会先把这套脚本跑一遍跑完再进入 P4 程序开发省下的不是一小时而是连续几天的精神状态。希望这套安装包和环境配置经验也能帮到你。全文结束。本文还有配套的精品资源点击获取
返回列表