P4 04-L2_Flooding
P4示例程序-04 L2组播与泛洪
功能
在L2基本交换机的基础上实现的泛洪与组播,当L2交换机在表中无法查询到MAC地址或目的MAC地址为广播地址时,从各端口泛洪出去
拓扑结构
代码
All Port
p4app-all-ports.json1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24{
"p4_src": "p4src/l2_flooding.p4",
"cli": true,
"pcap_dump": true,
"enable_log": true,
"topology": {
"assignment_strategy": "l2",
"default": {
"auto_arp_tables": false
},
"links": [["h1", "s1"], ["h2", "s1"], ["h3", "s1"], ["h4","s1"]],
"hosts": {
"h1": { },
"h2": { },
"h3": { },
"h4": { }
},
"switches": {
"s1": {
"cli_input": "s1-commands-all-ports.txt"
}
}
}
}- Headers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17typedef bit<9> egressSpec_t; // 定义 egressSpec_t 类型,9 位,用于指定输出端口
typedef bit<48> macAddr_t; // 定义 macAddr_t 类型,48 位,用于表示 MAC 地址
typedef bit<32> ip4Addr_t; // 定义 ip4Addr_t 类型,32 位,用于表示 IPv4 地址
header ethernet_t { // 定义以太网头部 ethernet_t
macAddr_t dstAddr; // 目的 MAC 地址
macAddr_t srcAddr; // 源 MAC 地址
bit<16> etherType; // 协议类型字段
}
struct metadata { // 定义元数据结构
/* empty */ // 当前为空,后续可扩展
}
struct headers { // 定义 headers 结构
ethernet_t ethernet; // 包含一个以太网头部
} - Parser
1
2
3
4
5
6
7
8
9parser MyParser(packet_in packet, // 解析器 MyParser,输入报文 packet
out headers hdr, // 输出解析后的头部 hdr
inout metadata meta, // 输入输出元数据
inout standard_metadata_t standard_metadata) { // 标准元数据
state start { // 起始状态
packet.extract(hdr.ethernet); // 提取以太网头部
transition accept; // 解析完成,跳转 accept
}
} - Checksum Verification
1
2
3control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
} - Ingress Processing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16control MyIngress(inout headers hdr, // 入方向处理
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() { mark_to_drop(standard_metadata); } // 丢弃报文
action forward(bit<9> egress_port) { standard_metadata.egress_spec = egress_port; } // 转发到指定端口
action broadcast() { standard_metadata.mcast_grp = 1; } // 设置多播组 ID=1,实现广播
table dmac { // 定义 dmac 表
key = { hdr.ethernet.dstAddr: exact; } // 匹配目的 MAC,精确匹配
actions = { forward; broadcast; NoAction; } // 表可执行的动作
size = 256; // 表容量 256
default_action = NoAction; // 默认 NoAction
}
apply { dmac.apply(); } // 应用 dmac 表
} - Egress Processing
1
2
3
4
5control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { } // 出方向处理,此处为空
} - Checksum Computation
1
2
3control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply { } // 校验和计算,此处为空
} - DE parser
1
2
3
4
5control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet); // 输出以太网头部
}
} - Switch
1
2
3
4
5
6
7
8V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main; s1-commands-all-ports.txt1
2
3
4
5
6
7
8
9
10table_add dmac forward 00:00:0a:00:00:01 => 1
table_add dmac forward 00:00:0a:00:00:02 => 2
table_add dmac forward 00:00:0a:00:00:03 => 3
table_add dmac forward 00:00:0a:00:00:04 => 4
table_set_default dmac broadcast
mc_mgrp_create 1
mc_node_create 0 1 2 3 4
mc_node_associate 1 0controller-all-ports.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31from p4utils.utils.helper import load_topo
from p4utils.utils.sswitch_p4runtime_API import SimpleSwitchP4RuntimeAPI
topo = load_topo('topology.json')
controllers = {}
sw_name = 's1'
for switch, data in topo.get_p4rtswitches().items():
controllers[switch] = SimpleSwitchP4RuntimeAPI(data['device_id'], data['grpc_port'],
p4rt_path=data['p4rt_path'],
json_path=data['json_path'])
controller = controllers[sw_name]
# Populate table dmac
controller.table_add("dmac", "forward", ['00:00:0a:00:00:01'], ['1'])
controller.table_add("dmac", "forward", ['00:00:0a:00:00:02'], ['2'])
controller.table_add("dmac", "forward", ['00:00:0a:00:00:03'], ['3'])
controller.table_add("dmac", "forward", ['00:00:0a:00:00:04'], ['4'])
controller.table_set_default("dmac", "broadcast", [])
# Get port list
interfaces_to_port = topo.get_node_intfs(fields=['port'])[sw_name].copy()
# Filter lo and cpu port
interfaces_to_port.pop('lo', None)
interfaces_to_port.pop(topo.get_cpu_port_intf(sw_name), None)
port_list = list(interfaces_to_port.values())
# Add broadcast group
controller.mc_mgrp_create(1, port_list)
Other Port
p4app-other-ports.json1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24{
"p4_src": "p4src/l2_flooding.p4",
"cli": true,
"pcap_dump": true,
"enable_log": true,
"topology": {
"assignment_strategy": "l2",
"default": {
"auto_arp_tables": false
},
"links": [["h1", "s1"], ["h2", "s1"], ["h3", "s1"], ["h4","s1"]],
"hosts": {
"h1": { },
"h2": { },
"h3": { },
"h4": { }
},
"switches": {
"s1": {
"cli_input": "s1-commands-other-ports.txt"
}
}
}
}- Headers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17typedef bit<9> egressSpec_t; // 定义 9 位输出端口类型
typedef bit<48> macAddr_t; // 定义 48 位 MAC 地址类型
typedef bit<32> ip4Addr_t; // 定义 32 位 IPv4 地址类型
header ethernet_t { // 定义以太网头部
macAddr_t dstAddr; // 目的 MAC 地址
macAddr_t srcAddr; // 源 MAC 地址
bit<16> etherType; // 以太类型字段
}
struct metadata { // 定义元数据结构
/* empty */ // 当前为空
}
struct headers { // 定义头部集合
ethernet_t ethernet; // 包含以太网头部
} - Parser
1
2
3
4
5
6
7
8
9
10parser MyParser(packet_in packet, // 定义解析器,输入为 packet
out headers hdr, // 输出解析的头部 hdr
inout metadata meta, // 输入输出元数据
inout standard_metadata_t standard_metadata) { // 标准元数据
state start { // 解析器起始状态
packet.extract(hdr.ethernet); // 从报文中提取以太网头部
transition accept; // 跳转到 accept 状态,解析结束
}
} - Checksum Verification
1
2
3control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
} - Ingress Processing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67control MyIngress(inout headers hdr, // 入方向处理控制块
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() { // 定义丢弃动作
mark_to_drop(standard_metadata); // 标记丢弃报文
}
action forward(bit<9> egress_port) { // 定义转发动作
standard_metadata.egress_spec = egress_port; // 设置输出端口
}
action broadcast() { // 定义广播动作
//Empty action that was not necessary, we just call it when there is a table miss // 该动作为空,仅在表项未命中时调用
}
table dmac { // 定义 dmac 表,用于目的 MAC 匹配
key = {
hdr.ethernet.dstAddr: exact; // 精确匹配目的 MAC 地址
}
actions = {
forward; // 转发动作
broadcast; // 广播动作
NoAction; // 无动作
}
size = 256; // 表大小 256 项
default_action = NoAction; // 默认 NoAction
}
action set_mcast_grp(bit<16> mcast_grp) { // 定义设置多播组动作
standard_metadata.mcast_grp = mcast_grp; // 设置多播组 ID
}
table select_mcast_grp { // 定义 select_mcast_grp 表,根据 ingress_port 选择多播组
key = {
standard_metadata.ingress_port : exact; // 匹配入口端口
}
actions = {
set_mcast_grp; // 设置多播组动作
NoAction; // 无动作
}
size = 32; // 表大小 32
default_action = NoAction; // 默认 NoAction
}
apply {
switch (dmac.apply().action_run) { // 根据 dmac 表执行的动作结果分支
broadcast: {
select_mcast_grp.apply(); // 如果动作是 broadcast,则执行 select_mcast_grp 表
}
}
/* Alternative Solution (even easier) // 另一种更简单的解决方案
if (dmac.apply().hit){ // 如果 dmac 表命中
}
else {
select_mcast_grp.apply(); // 未命中则应用 select_mcast_grp 表
}
End of Alternative solution // 另一种解决方案结束
*/
}
} - Egress Processing
1
2
3
4
5
6
7
8control MyEgress(inout headers hdr, // 出方向处理控制块
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply {
} // 当前为空
} - Checksum Computation
1
2
3
4control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
} // 当前为空
} - DE parser
1
2
3
4
5control MyDeparser(packet_out packet, in headers hdr) {
apply {
// 解析的头部需要重新封装回报文
packet.emit(hdr.ethernet); // 输出以太网头部
} - Switch
1
2
3
4
5
6
7
8V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main; s1-commands-other-ports.txt1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31table_add dmac forward 00:00:0a:00:00:01 => 1
table_add dmac forward 00:00:0a:00:00:02 => 2
table_add dmac forward 00:00:0a:00:00:03 => 3
table_add dmac forward 00:00:0a:00:00:04 => 4
table_set_default dmac broadcast
#define broadcasting port groups
mc_node_create 0 2 3 4
mc_node_create 1 1 3 4
mc_node_create 2 1 2 4
mc_node_create 3 1 2 3
#associate node group with mcast group
mc_mgrp_create 1
mc_node_associate 1 0
mc_mgrp_create 2
mc_node_associate 2 1
mc_mgrp_create 3
mc_node_associate 3 2
mc_mgrp_create 4
mc_node_associate 4 3
#fill table selector
table_add select_mcast_grp set_mcast_grp 1 => 1
table_add select_mcast_grp set_mcast_grp 2 => 2
table_add select_mcast_grp set_mcast_grp 3 => 3
table_add select_mcast_grp set_mcast_grp 4 => 4controller-other-ports.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30from p4utils.mininetlib.network_API import NetworkAPI
net = NetworkAPI()
# Network general options
net.setLogLevel('info')
net.setCompiler(p4rt=True)
net.execScript('python controller-other-ports.py', reboot=True)
net.disableArpTables()
# Network definition
net.addP4RuntimeSwitch('s1')
net.setP4Source('s1','./p4src/l2_flooding_other_ports.p4')
net.addHost('h1')
net.addHost('h2')
net.addHost('h3')
net.addHost('h4')
net.addLink('s1', 'h1')
net.addLink('s1', 'h2')
net.addLink('s1', 'h3')
net.addLink('s1', 'h4')
# Assignment strategy
net.l2()
# Nodes general options
net.enablePcapDumpAll()
net.enableLogAll()
net.enableCli()
net.startNetwork()
P4仿真(以other-port为例)
- 建立配置匹配表
s1-commands-other-ports.txt
- 启动网络拓扑
- Ping操作前,由于各主机没有被自动配置ARP表,因此各主机的ARP表为空
- Ping操作后,各主机学习到ARP表,表明交换机的泛洪操作生效且正确
P4 04-L2_Flooding
http://example.com/2025/07/28/P4 04-L2_Flooding/