拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Tofino 2 P4 数据面:MAU 资源、ECMP 哈希与 INT 遥测排错

简介这份资源是英特尔Intel Tofino P4可编程交换芯片的技术分享PPT面向从事SDN、可编程网络与智能网卡开发的工程师及学习者用于快速建立对Tofino系列尤其是Tofino 2以太网交换架构与端到端优化能力的整体认知。资源包内仅1个pptx文件整体约4.26MB以图文幻灯片形式呈现内容涉及Intel Connectivity Group对深度定制交换架构的说明、基于P4语言的软件定义网络功能以及Intel AVX指令对处理器吞吐量的影响等要点。页面同时整理了英特尔的性能与免责声明包括测试结果受系统配置影响、实际性能随硬件软件差异而变化、性能测试软件可能仅针对英特尔微处理器优化以及无产品可保证绝对安全等提示便于读者在评估选型时把握边界条件。目前已有104人学习浏览适合需要了解可编程交换芯片原理、性能测试注意事项与架构优化思路的读者作为入门参考材料。1. 一次 P4 工程下载之后的第二小时Intel Tofino P4 switch 到底是什么下载一个 P4 工程包跑通p4_build.sh起run_switchd.sh再用 BFRT 加一条表项这四步熟手半小时能做完真正卡人的是第二小时——把ipv4_host的size从 1024 改成 64K编译器直接甩一句资源不够或者表装得下但哈希那条路径死活不命中。Intel Tofino P4 switch 就是这类问题的现场它不是插在 PCIe 槽上的智能网卡而是可以用 P4 描述数据面的以太网交换 ASIC。Tofino 2 单芯片带宽到 12.8 Tbps支持 100 个 128GbE 端口、400GbE 上行和 50G SerDes最多 20 个匹配动作单元MAU外加两级调度器做分级流量管理。搜「P4 Tofino 智能网卡」的人里有一部分其实要找的是主机侧网卡驱动那是 SmartNIC/IPU 那条产品线芯片侧的 Tofino 负责 fabric 内的转发和带内遥测两边常出现在同一套互连方案里但下载到的东西完全不同。适合往下看的人做 SDN 数据面、L4 负载均衡、DDoS 检测、INT 遥测、5G UPF/BNG 的工程师。2. Tofino 2 流水线与 P4_16 的映射从 parser 到 deparser2.1 芯片里到底有什么MAU、SRAM/TCAM 与哈希单元固定功能交换 ASIC 的问题是表和逻辑写死在硅里ACL 的 TCAM 深度、MAC 表容量都是出厂定死的跑一个只用 IPv4 转发的场景MPLS、组播那些资源就白占着。可编程 ASIC 换了个思路头部解析、表大小、包处理逻辑由软件也就是 P4 程序决定编译期把逻辑铺到 MAU 上。一个 MAU stage 里通常有 SRAM 和 TCAM 两套匹配资源SRAM 支持精确匹配功耗和面积便宜适合主机路由、MAC 表这类 key 空间大的表TCAM 支持带掩码的通配匹配适合 ACL、前缀匹配但容量小得多。stage 里还有 ALU 做算术与字段改写以及哈希单元做等价多路径ECMP和链路聚合。包在流水线里的路径是 parser → ingress MAU → 流量管理器缓冲和调度→ egress MAU → deparserINT 里那个「出队时延」就是在流量管理器出队那一刻的时间戳上取到的。资源是全局预算不是每张表各给一份。常见做法是先算清楚每张表的 key 位宽、action 数据位宽和表项数再决定哪些表能共用一个 stage。Tofino 2 增强了这一块的容量和特性深度具体每 pipe 有多少 stage、每 stage 多少 SRAM取决于 U/M/H 系列型号以编译日志和 P4 Insight 的实测报告为准别按别人博客里的数字硬套。2.2 P4_16 TNA 的最小可用骨架Tofino 用的不是 v1model 那套而是 Tofino Native ArchitectureTNA头文件在$SDE_INSTALL/share/p4include/tna.p4。下面这个骨架是最小可跑版本包含 parser、ingress control 和一个精确匹配表#include core.p4 #include tna.p4 header ethernet_t { bit48 dstAddr; bit48 srcAddr; bit16 etherType; } header ipv4_t { bit4 version; bit4 ihl; bit8 diffserv; bit16 totalLen; bit16 identification; bit16 flags; bit8 ttl; bit8 protocol; bit16 hdrChecksum; bit32 srcAddr; bit32 dstAddr; } struct headers_t { ethernet_t ethernet; ipv4_t ipv4; } struct metadata_t { bit32 hash; bit16 ecmp_gid; } parser IngressParser(packet_in pkt, out headers_t hdr, inout metadata_t meta, inout ingress_intrinsic_metadata_t ig_intr_md) { state start { pkt.extract(hdr.ethernet); transition select(hdr.ethernet.etherType) { 0x0800: parse_ipv4; default: accept; } } state parse_ipv4 { pkt.extract(hdr.ipv4); transition accept; } } control Ingress(inout headers_t hdr, inout metadata_t meta, inout ingress_intrinsic_metadata_t ig_intr_md) { action set_nhop(PortId_t port, bit48 dmac, bit48 smac) { ig_intr_md.egress_port port; hdr.ethernet.dstAddr dmac; hdr.ethernet.srcAddr smac; hdr.ipv4.ttl hdr.ipv4.ttl - 1; } action drop() { ig_intr_md.drop_ctl 1; } table ipv4_host { key { hdr.ipv4.dstAddr : exact; } actions { set_nhop; drop; } size 1024; // 这个数字直接换算成 SRAM 行数 default_action drop(); } apply { if (hdr.ipv4.isValid()) { ipv4_host.apply(); } } }key里的匹配类型决定用 SRAM 还是 TCAMsize决定占用多少行default_action决定未命中时的行为——多数排错现场就是忘了改default_action结果包全被静默丢掉。ig_intr_md.egress_port是 TNA 的出端口字段和 v1model 里的standard_metadata.egress_spec不是一回事抄网上的示例时这里最容易错。2.3 编译与资源报告p4_build.sh 交付了什么标准流程是在$SDE/pkgsrc/p4-build下走 configure makeP4 程序名会变成后面run_switchd.sh -p的入参export SDE/opt/bf-sde-9.13.0 export SDE_INSTALL$SDE/install cd $SDE/pkgsrc/p4-build ./configure --prefix$SDE_INSTALL \ --with-tofino P4_NAMElb_int P4_PATH$HOME/p4src/lb_int/main.p4 \ P4_ARCHtofino2 P4FLAGS--verbose 2 make -j$(nproc) make install编译产物不只是一份二进制--verbose 2会把每张表落在哪个 stage、用了多少 SRAM 行、ALU 指令数打出来P4 Insight 则是把这些数据可视化成 pipeline 图。数字对不上的时候先看这三个量总 stage 占用、单 stage 里 SRAM 行数、哈希单元的分布。改表大小、改 key 位宽、加一个 action 参数都会让分配结果整体挪位本地小改有时反而触发全局重排导致编译失败。3. 用 P4 写一个带 INT 遥测的 ECMP 负载均衡3.1 表设计精确匹配做主机路由哈希做 ECMP负载均衡在数据面的标准拆法是两跳表第一跳按目的地址选 ECMP 组第二跳按五元组哈希选具体成员。P4_16 里用action_selector表达成员组控制面下发时不用关心哈希算法只维护组成员增删Hashbit32(HashAlgorithm_t.CRC32, 32) five_tuple_hash; // 32 位 CRC32 action_selector(HashAlgorithm.crc32, 4096, 1024) ecmp_sel; // 最大 4096 成员 action set_gid(bit16 gid) { meta.ecmp_gid gid; } action set_member(PortId_t port, bit48 dmac) { ig_intr_md.egress_port port; hdr.ethernet.dstAddr dmac; } table ecmp_group { key { hdr.ipv4.dstAddr : exact; } actions { set_gid; drop; } size 1024; } table ecmp_member { key { meta.ecmp_gid : exact; } actions { set_member; } implementation ecmp_sel; size 4096; } apply { if (hdr.ipv4.isValid()) { five_tuple_hash.apply(meta.hash, { hdr.ipv4.srcAddr, hdr.ipv4.dstAddr, hdr.ethernet.srcAddr, hdr.ethernet.dstAddr, hdr.ipv4.protocol }); if (ecmp_group.apply().hit) { ecmp_member.apply(); } else { ipv4_host.apply(); } } }Hash这个 extern 在不同 SDE 版本里签名略有差异以tna.p4为准。哈希输入字段数直接决定哈希单元里选择器的配置多塞一个字段就多一份延展逻辑所以别把 TTL 这类每跳都变的字段放进哈希 key——局部的抖动会被放大成整条流的乱序。这条经验在 5 年以上的人那里往往是踩过一次才记住的。3.2 INT 头插入P4_14 写法与 TNA 写法的差别带内网络遥测INT的核心动作是往包里插一个 shim 头把交换机 ID 和队列时延写进去同时把后续协议头的长度字段改对。老资料里常见的 P4_14 写法是这样的// P4_14 风格来自早期的 Tofino 示例 table int_table { reads { ip.protocol; } actions { export_queue_latency; } } action export_queue_latency(sw_id) { add_header(int_header); modify_field(int_header.kind, TCP_OPTION_INT); modify_field(int_header.len, TCP_OPTION_INT_LEN); modify_field(int_header.sw_id, sw_id); modify_field(int_header.q_latency, intrinsic_metadata.deq_timedelta); add_to_field(tcp.dataOffset, 2); // TCP 选项长度按 4 字节单位 add_to_field(ipv4.totalLen, 8); // IPv4 总长增加 8 字节 subtract_from_field(ingress_metadata.tcpLength, 12); }转成 TNA 需要改三处add_header变成对headers_t里新加的int_shim_t置setValid()intrinsic_metadata.deq_timedelta换成出队内在元数据里的时间戳字段modify_field换成直接赋值。少改任何一处编译不一定报错但抓包会看到totalLen和dataOffset对不上校验和随之失真。这就是 INT 部署里最常见的「包能通、但采集端算不出时延」的原因。3.3 控制面下发三层 API 的选择Tofino 给了三层 API选哪层决定了后续维护成本API 层级面向对象改动代价典型场景SAI整机白盒方案低复用现成网络操作系统快速迁移模型驱动抽象接口需要少量自定义的团队中在标准协议模型上叠加自定义行为Switch Runtime Interface完全自定义数据面高直接按 P4 编译产物下发控制力最大自己写 P4 时基本都在最底层用 BFRT 的 Python 接口下发字段名就是 P4 里的表名和参数名import bfrt_grpc.client as gc # tbl 通过 bfrt.lb_int.ingress.ipv4_host 拿到 tbl bfrt.lb_int.ingress.ecmp_member tbl.clear() tbl.add( key tbl.make_key([gc.KeyTuple(meta.ecmp_gid, 100)]), data tbl.make_data([gc.DataTuple(port, 8), gc.DataTuple(dmac, 0x001122334455)])) tbl.dump()make_key/make_data负责把 Python 值编成硬件需要的字节序KeyTuple的字段名必须和 P4 里完全一致大小写敏感。tbl.dump()是排错第一手段下发没生效时先 dump看是表里没条目还是条目在但端口号写错了。4. 上机跑通与排错hugepages、驱动与资源超限4.1 启动顺序与内存准备顺序错了会看到一堆「设备打不开」的假故障。常见做法是按下面这个次序来每一步都能单独验证# 1. 预留大页内存switchd 与 DMA 缓冲都从这里拿 echo 4096 /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages grep HugePages_Total /proc/meminfo # 2. 加载平台驱动 $SDE/install/bin/bf_kdrv_mod_load $SDE_INSTALL lsmod | grep bf # 3. 起 switchd程序名要和 P4_NAME 一致 $SDE/run_switchd.sh -p lb_int --arch tofino2大页数量不够时switchd 会在初始化 DMA 内存池阶段失败报错信息里通常带bf_dma或hugepage字样而不是直接说内存不够。驱动没加载时bf_kdrv相关的open调用会返回权限错误容易误判成 root 权限问题。先把这两层确认掉再去看 P4 逻辑。4.2 编译期资源报错的定位套路资源类报错基本集中在 parser 状态数、单 stage SRAM 行数、哈希单元数量、deparser 总长度这四类。定位顺序是固定的先看报错点名的表和 stage再看 P4 Insight 里该 stage 的剩余余量最后才动代码。报错关键词常见原因处置Not enough stages表太多或某张表size过大合并同 key 的表或把 TCAM 表换成 SRAMTable exceeds stage capacity单 stage SRAM 行数超限下调size或拆成两张表Parser state limit协议分支过多合并状态用select复用Deparser length可插入头总量超预算减少可选头数量或缩窄字段一个反复出现的坑是「表大小按内存估算」。SRAM 的容量单位是行不是字节key 位宽越大、每行能放的表项越少size 65536在 32 位 key 上可能刚好在 128 位 key 上直接爆掉。改size之后一定要重看编译报告因为它往往牵动整条流水线的 stage 分配。4.3 运行期端口、计数器与 pktgen程序跑起来之后先确认端口状态再验证转发逻辑# 在 switchd 的 bfrt_python 控制台里读端口和计数器 bfrt.port.port.dump() bfrt.lb_int.ingress.ipv4_host.dump()port.dump()返回的PORT_UP之外还要看 FEC 和 lane 状态400GbE 端口在光模块没协商好时会显示 up 但收不到包。验证转发用内置的 pktgen 造流是最省事的办法按 P4 编译产物里的pktgen表配置包内容与端口注入后看 egress 计数器增量比接真实流量更快定位「到底哪张表没命中」。计数器是排错的第二只眼睛。给每张表加一个direct_counter命中数直接读出来能立刻区分「表没命中走了 default_action」和「命中了但 action 写错端口」。这比在采集端抓包反推快得多。5. 进阶把 INT 遥测做成可验证的闭环遥测最怕的是「采集端拿到了数据但不知道它对不对」。可验证的最小闭环是这样搭的用 pktgen 注入一条带 TCP 选项的已知报文在 Tofino 上走完 INT 插入路径采集端抓到包后校验三个不变量——ipv4.totalLen是否等于原始长度加 shim 头长度tcp.dataOffset是否按 4 字节单位加 2shim 里的sw_id是否等于该交换机的编号。# 采集侧校验示例用 scapy 解析 INT shim from scapy.all import rdpcap, IP, TCP for p in rdpcap(int_capture.pcap): if not p.haslayer(TCP): # 非 TCP 流不走选项插入路径 continue base 20 p[TCP].dataofs * 4 # 不含 shim 的 TCP 头长 assert p[IP].len len(bytes(p[IP])) , totalLen 与实长不符 assert (p[TCP].dataofs - expect_delta) * 4 base, dataOffset 未回退两个断言对应两个最常见故障totalLen不符说明add_to_field(ipv4.totalLen, 8)漏了或加了两次dataOffset不符说明 TCP 选项长度没按 4 字节单位换算。这两处一旦错中间设备的校验和卸载会把包丢掉表现为「开了 INT 之后随机丢包」而不是稳定失败。再往上一层是把 INT 数据和调度绑定。Tofino 2 的两级调度器可以按端口、队列、租户做分层限速把每级的队列时延通过 INT 带出去采集端就能区分「是链路拥塞」还是「是本级整形导致的排队」。做法是在出队路径上把两级调度器的队列号一起写进 shim采集端按队列号分组统计 P99 时延哪个租户的整形阈值定低了会立刻在时延曲线上显形。这也是可编程数据面相对固定 ASIC 最实在的收益遥测字段想要几个、放在哪一层改的是 P4 程序而不是等下一次流片。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门