helloword.p4
#include <core.p4>
#include <v1model.p4>
typedef bit<48> EthernetAddress;
header Ethernet_h {
EthernetAddress dstAddr;
EthernetAddress srcAddr;
bit<16> etherType;
}
struct metadata { }
struct headers {
Ethernet_h eth;
}
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata)
{
state start {
packet.extract(hdr.eth);
transition accept;
}
}
control MyChecksum(inout headers hdr, inout metadata meta)
{
apply { }
}
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata)
{
action set_dst_addr() {
hdr.eth.dstAddr = 0xaabbccddee02;
standard_metadata.egress_spec = 0x2;
}
table mac_match_tbl {
key = {
hdr.eth.dstAddr : exact;
}
actions = {
set_dst_addr;
NoAction;
}
const entries = {(0x112233445566) : set_dst_addr(); }
//size = 1024;
default_action = NoAction();
}
apply {
mac_match_tbl.apply();
}
}
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata)
{
apply { }
}
control MyDeparserChecksum(inout headers hdr, inout metadata meta)
{
apply { }
}
control MyDeparser(packet_out packet, in headers hdr)
{
apply {
packet.emit(hdr.eth);
}
}
V1Switch(
MyParser(),
MyChecksum(),
MyIngress(),
MyEgress(),
MyDeparserChecksum(),
MyDeparser()
) main;
该程序的作用时匹配目的MAC地址为”0x112233445566″的以太网报文,然后将该报文的目的MAC地址改为”0xAABBCCEEDD02″,并通过2号端口发送出去。
配套的README.md
sudo ip link del veth0
sudo ip link del veth2
sudo ip link add veth0 type veth peer name veth1
sudo ip link add veth2 type veth peer name veth3
sudo ip link set veth0 up
sudo ip link set veth1 up
sudo ip link set veth2 up
sudo ip link set veth3 up
p4c-bm2-ss helloworld.p4 -o helloworld.json --p4runtime-files helloworld.p4.p4info.txt
sudo simple_switch_grpc helloworld.json --log-console -i 1@veth0 -i 2@veth2
simple_switch_CLI
table_dump MyIngress.mac_match_tbl
table_add MyIngress.mac_match_tbl MyIngress.set_dst_addr 0x112233445566 =>
sudo tcpdump -i veth3 -nn -XXX
sudo ./send_packet.py
1. 命令逐条解释
这几条命令是用于 编译、运行和配置 一个基于 P4 程序的软件交换机(使用 BMv2 的 simple_switch_grpc
实现)的完整流程
1.1 编译 P4 程序
p4c-bm2-ss helloworld.p4 -o helloworld.json --p4runtime-files helloworld.p4.p4info.txt
-
作用:将 P4 源代码编译为 BMv2 交换机可执行的配置。
-
参数解析:
-
p4c-bm2-ss
:P4 编译器针对 BMv2 软件交换机的后端。 -
helloworld.p4
:输入的 P4 程序文件。 -
-o helloworld.json
:输出交换机可加载的 JSON 配置文件。 -
--p4runtime-files helloworld.p4.p4info.txt
:生成 P4Runtime API 的描述文件(用于控制器动态配置)。
-
-
输出文件:
-
helloworld.json
:BMv2 交换机的运行时配置。 -
helloworld.p4.p4info.txt
:P4Runtime 的流表、计数器等元数据描述。
-
1.2 启动 BMv2 交换机
sudo simple_switch_grpc helloworld.json --log-console -i 1@veth0 -i 2@veth2
观察BMv2交换机在接收报文、执行p4程序时的日志,方便调试
-
作用:运行一个支持 gRPC 接口的 BMv2 交换机实例。
-
参数解析:
-
simple_switch_grpc
:支持 P4Runtime gRPC 接口的 BMv2 交换机版本。 -
helloworld.json
:上一步生成的配置文件。 -
--log-console
:将日志输出到控制台(便于调试)。 -
-i 1@veth0 -i 2@veth2
:将交换机的端口 1 绑定到主机虚拟接口veth0
,端口 2 绑定到veth2
。
-
-
关键点:
-
需要
sudo
权限(因为要创建虚拟网络接口)。 -
veth0
和veth2
需提前通过ip link
命令创建(如ip link add veth0 type veth peer name veth1
)。
-
从第一行开始看
- MyIngress.mac_match_tbl表默认action时NoAction
- MyIngress.mac_match_tbl表的第0项,key时112233445566,action时MyIngress.set_dst_addr
- 交换机的port 1使用veth0接口,port 2使用veth1接口
- 9559开启gRPC服务
- 9090开启Thrift服务
1.3 BMv2可编程交换机操作
1.3.1 进入交换机控制台
simple_switch_CLI
-
作用:启动交互式命令行工具,用于动态管理正在运行的交换机实例。
-
功能:
-
添加/删除流表项。
-
读取计数器/寄存器。
-
发送调试数据包等。
-
-
默认连接:自动连接到本地运行的
simple_switch_grpc
(默认端口9090
)。
1. 查看BMv2端口
show_ports
2. 查看数据面的表
1.3.2 查看流表内容
table_dump MyIngress.mac_match_tbl
-
作用:显示名为
MyIngress.mac_match_tbl
的流表所有条目。 -
字段说明:
-
MyIngress
:P4 程序中定义的control
块名称。 -
mac_match_tbl
:流表名称(通常用于匹配 MAC 地址)。
-
输出示例:
table MyIngress.mac_match_tbl
==========
TABLE ENTRIES
----------
No entries.
表目前只有一个表项,它的key是112233445566,action是MyIngress.set_dst_addr
这里已经有了条目 且不可删除 因为P4 程序中表的属性被设置为 const
或 immutable
在 P4 代码中,如果表定义包含 const
或 immutable
修饰符,编译后的流表将禁止运行时修改:
sudo simple_switch_grpc helloworld.json --log-console -i 1@veth0 -i 2@veth2
该条命令已经把p4程序编译出的配置文件给了交换机 所以尝试删除已经无法删除
删除命令
#table_delete <table_name> <entry_handle>
table_delete MyIngress.mac_match_tbl 0
1.3.3 添加流表项
table_add MyIngress.mac_match_tbl MyIngress.set_dst_addr 0x112233445566 =>
-
作用:向流表
MyIngress.mac_match_tbl
添加一条规则。 -
参数解析:
-
MyIngress.mac_match_tbl
:目标流表名称。 -
MyIngress.set_dst_addr
:匹配后执行的动作(action
)。 -
0x112233445566
:匹配字段的值(这里是 MAC 地址的十六进制形式)。 -
=>
:分隔匹配字段和动作参数(本例中动作无参数,故为空)。
-
等效 P4 逻辑:
control MyIngress {
action set_dst_addr() { ... } // 定义动作
table mac_match_tbl {
key = { hdr.ethernet.dstAddr: exact; } // 匹配目标 MAC
actions = { set_dst_addr; } // 执行动作
}
}
完整流程总结
-
编译:将 P4 代码转换为交换机可执行的 JSON 配置。
-
运行:启动交换机并绑定虚拟接口。
-
配置:通过 CLI 添加流表规则(例如匹配特定 MAC 地址并执行动作)。
-
验证:数据包进入交换机后,会根据流表规则被处理(如转发、修改字段等)。
3. 抓包
sudo tcpdump -i veth3 -nn -XXX
1. 参数说明
参数 | 作用 |
---|---|
sudo |
以管理员权限运行(抓包需要 root 权限) |
tcpdump |
经典网络抓包工具,支持过滤和解析多种协议 |
-i veth3 |
指定监听的网络接口(这里是虚拟以太网接口 veth3 ) |
-nn |
双重禁用解析: ① 不将 IP 转换为主机名 ② 不将端口转为服务名 |
-XXX |
十六进制+ASCII 混合输出:显示数据包的完整二进制内容和可读字符 |
4. 发包
下面的脚本作用是构造一个1.1.1.2发到2.2.2.2的TCP报文,源MAC为aa:bb:cc:dd:ee:01,
目的MAC为11:22:33:44:55:66,兵器从veth1端口发送出去
#!/usr/bin/env python3
import sys
from scapy.all import Ether, IP, TCP, sendp
def main():
ifname="veth1"
print("sending a packet on interface %s" % (ifname))
pkt = Ether(dst="11:22:33:44:55:66", src="aa:bb:cc:dd:ee:01") / IP(src="1.1.1.2", dst="2.2.2.2") / TCP(dport=80, sport=10000)
pkt.show2()
sendp(pkt, iface=ifname, verbose=False)
if __name__ == '__main__':
main()
会被交换机匹配并将该报文的目的MAC地址改为”0xAABBCCEEDD02″
发包后
抓包结果
MAC已被修改为0xAABBCCDDEE02
可编程交换机的结果
- 第1行:表示BMv2接收到一个报文
- 第2~7行: parser处理
- 第8~23行:ingress流水线处理。可以看到报文的目的MAC地址匹配到了表项(第13行),然后被修改为0xaabbccddee02(第21行),并且出向端口被修改为0x2(第22行)
- 第24~30行: 流水线其他阶段处理