P4 06-MPLS
P4示例程序-06 MPLS
功能
MPLS是一种高效且可扩展的数据转发机制,它不再根据IP包头里的IP地址来做路由查找,而是根据给数据包加上的短标签来快速转发。
拓扑结构
代码
p4app.json1
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"topology": {
"assignment_strategy": "l3",
"links": [["h1", "s1"],
["s1", "s2"],
["s1", "s3"],
["s2", "s4"],
["s3", "s4"],
["s4", "s5"],
["s4", "s6"],
["s5", "s7"],
["s6", "s7"],
["s7", "h2"],
["s7", "h3"]],
"hosts": {
"h1": {
},
"h2": {
},
"h3": {
}
},
"switches": {
"s1": {
},
"s2": {
},
"s3": {
},
"s4": {
},
"s5": {
},
"s6": {
},
"s7": {
}
}
}- Headers
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
42typedef bit<9> egressSpec_t; // 定义egress端口位宽
typedef bit<48> macAddr_t; // MAC地址类型
typedef bit<32> ip4Addr_t; // IPv4地址类型
header ethernet_t {
macAddr_t dstAddr; // 目标MAC地址
macAddr_t srcAddr; // 源MAC地址
bit<16> etherType; // 以太网类型
}
header mpls_t {
bit<20> label; // MPLS标签字段
bit<3> exp; // 实验字段
bit<1> s; // 最底层标签标志位
bit<8> ttl; // MPLS TTL
}
header ipv4_t {
bit<4> version; // IP版本
bit<4> ihl; // 首部长度
bit<8> diffserv; // 差异化服务
bit<16> totalLen; // 总长度
bit<16> identification; // 标识符
bit<3> flags; // 标志位
bit<13> fragOffset; // 分段偏移
bit<8> ttl; // TTL
bit<8> protocol; // 上层协议
bit<16> hdrChecksum; // 首部校验和
ip4Addr_t srcAddr; // 源IP地址
ip4Addr_t dstAddr; // 目的IP地址
}
struct metadata {
bit<1> is_ingress_border; // 是否是入边界口
bit<1> is_egress_border; // 是否是出边界口
}
struct headers {
ethernet_t ethernet; // 以太网头部
mpls_t mpls; // MPLS头部
ipv4_t ipv4; // IPv4头部
} - Parser
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
28parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet; // 跳转到以太网解析状态
}
state parse_ethernet {
packet.extract(hdr.ethernet); // 提取以太网头
transition select(hdr.ethernet.etherType) {
TYPE_MPLS: parse_mpls; // MPLS类型则转mpls解析
TYPE_IPV4: parse_ipv4; // IPv4类型则转ipv4解析
default: accept; // 其他则直接接受
}
}
state parse_mpls {
packet.extract(hdr.mpls); // 提取MPLS头部
transition parse_ipv4; // 继续提取IPv4头
}
state parse_ipv4 {
packet.extract(hdr.ipv4); // 提取IPv4头部
transition accept; // 完成解析
}
} - Checksum Verification
1
2
3
4control 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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata); // 标记丢包
}
action set_is_ingress_border(){
meta.is_ingress_border = (bit<1>)1; // 标记为入边界口
}
table check_is_ingress_border {
key = {
standard_metadata.ingress_port: exact; // 根据入端口判断
}
actions = {
NoAction; // 无操作
set_is_ingress_border; // 设置入边界标志
}
default_action = NoAction; // 默认无操作
size = CONST_MAX_PORTS; // 表大小为32
}
action add_mpls_header(bit<20> tag) {
hdr.mpls.setValid(); // 设置MPLS头有效
hdr.mpls.label = tag; // 指定标签
hdr.mpls.s = 0; // 非最底层
hdr.mpls.ttl = 255; // 初始TTL
hdr.ethernet.etherType = TYPE_MPLS; // 修改以太类型为MPLS
}
table fec_to_label {
key = {
hdr.ipv4.dstAddr: lpm; // 匹配目标IP前缀
}
actions = {
NoAction; // 无操作
add_mpls_header; // 添加MPLS标签
}
default_action = NoAction; // 默认不处理
size = CONST_MAX_LABELS; // 表大小为10
}
action mpls_forward(macAddr_t dstAddr, egressSpec_t port) {
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; // 设置源地址为本机
hdr.ethernet.dstAddr = dstAddr; // 设置目标地址
standard_metadata.egress_spec = port; // 指定出端口
hdr.mpls.ttl = hdr.mpls.ttl - 1; // TTL减一
}
table mpls_tbl {
key = {
hdr.mpls.label: exact; // 匹配MPLS标签
}
actions = {
mpls_forward; // MPLS转发
drop; // 丢弃
}
default_action = drop; // 默认丢弃
size = CONST_MAX_LABELS; // 表大小为10
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; // 设置源地址为本机
hdr.ethernet.dstAddr = dstAddr; // 设置目标地址
standard_metadata.egress_spec = port; // 指定出端口
hdr.ipv4.ttl = hdr.ipv4.ttl - 1; // TTL减一
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm; // 匹配目标IP前缀
}
actions = {
ipv4_forward; // IPv4转发
drop; // 丢弃
}
default_action = drop; // 默认丢弃
size = 128; // 表大小
}
apply {
// We check if it is an ingress border port
check_is_ingress_border.apply(); // 判断是否入边界口
if(meta.is_ingress_border == 1){
// We need to check if the header is valid since mpls label is based on dst ip
if(hdr.ipv4.isValid()){
// We add the label based on the destination
fec_to_label.apply(); // 添加MPLS标签
}
}
// We select the egress port based on the mpls label
if(hdr.mpls.isValid()){
mpls_tbl.apply(); // MPLS转发表
}
// We implement normal forwarding
else if (hdr.ipv4.isValid())
{
ipv4_lpm.apply(); // 普通IPv4转发
}
}
} - Egress 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
31control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action is_egress_border(){
// We remove the mpls header
hdr.mpls.setInvalid(); // 删除MPLS头
hdr.ethernet.etherType = TYPE_IPV4; // 恢复以太类型
hdr.ipv4.ttl = hdr.ipv4.ttl - 1; // TTL减一
}
table check_is_egress_border {
key = {
standard_metadata.egress_port: exact; // 根据出端口判断
}
actions = {
NoAction; // 无操作
is_egress_border; // 设置为出边界口
}
default_action = NoAction; // 默认无操作
size = CONST_MAX_PORTS; // 表大小为32
}
apply {
// We check if it is an egress border port
if (hdr.mpls.isValid()){
check_is_egress_border.apply(); // 判断并处理出边界口
}
}
} - Checksum Computation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(), // 检查IPv4有效性
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16); // 计算IPv4首部校验和
}
} - DE parser
1
2
3
4
5
6
7control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet); // 发送以太网头部
packet.emit(hdr.mpls); // 发送MPLS头部(如果有效)
packet.emit(hdr.ipv4); // 发送IPv4头部(如果有效)
}
} - Switch
1
2
3
4
5
6
7
8V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main; s1-commands.txt1
2
3
4
5
6
7
8
9# S1交换机
# 针对发完主机H2的IPv4数据包添加MPLS标签
table_add FEC_tbl mpls_ingress_4_hop 10.7.2.0/24 => 2 3 2 2
# 针对发完主机H3的IPv4数据包添加MPLS标签
table_add FEC_tbl mpls_ingress_4_hop 10.7.3.0/24 => 2 3 2 2
# MPLS标签指示从端口2转发,将到达交换机S2
table_add mpls_tbl mpls_forward 2 0 => 00:00:00:02:01:00 2
# 正常的IPv4数据包转发,从端口1转发进而到达主机H1
table_add FEC_tbl ipv4_forward 10.1.1.0/24 => 00:00:0a:01:01:02 1s2-commands.txt1
2
3# S2交换机
# 剥离顶层MPLS标签,并从端口2转发至下一跳交换机S4
table_add mpls_tbl mpls_forward 2 0 => 00:00:00:04:01:00 2s3-commands.txt1
2
3# S3交换机
# 剥离全部MPLS标签、恢复为IPv4数据包,并从1端口转发至交换机S1
table_add mpls_tbl penultimate 1 1 => 00:00:00:01:03:00 1s4-commands.txt1
2
3
4
5# S4交换机
# 剥离顶层MPLS标签,并从端口3转发至下一跳交换机S5
table_add mpls_tbl mpls_forward 3 0 => 00:00:00:05:01:00 3
# 剥离顶层MPLS标签,并从端口2转发至下一跳交换机S3
table_add mpls_tbl mpls_forward 2 0 => 00:00:00:03:02:00 2s5-commands.txt1
2
3# S5交换机
# 剥离全部MPLS标签、恢复为IPv4数据包,并从2端口转发至交换机S7
table_add mpls_tbl penultimate 2 1 => 00:00:00:07:01:00 2s6-commands.txt1
2
3# S6交换机
# 剥离顶层MPLS标签,并从端口1转发至下一跳交换机S4
table_add mpls_tbl mpls_forward 1 0 => 00:00:00:04:04:00 1s7-commands.txt1
2
3
4
5
6
7
8
9# S7交换机
# 针对发完主机H1的IPv4数据包添加MPLS标签
table_add FEC_tbl mpls_ingress_4_hop 10.1.1.0/24 => 1 2 1 2
# MPLS标签指示从端口2转发,将到达交换机S6
table_add mpls_tbl mpls_forward 2 0 => 00:00:00:06:02:00 2
# 正常的IPv4数据包转发,从端口3转发进而到达主机H2
table_add FEC_tbl ipv4_forward 10.7.2.0/24 => 00:00:0a:07:02:02 3
# 正常的IPv4数据包转发,从端口4转发进而到达主机H3
table_add FEC_tbl ipv4_forward 10.7.3.0/24 => 00:00:0a:07:03:02 4controller.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
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102from p4utils.utils.helper import load_topo
from p4utils.utils.sswitch_p4runtime_API import SimpleSwitchP4RuntimeAPI
from p4utils.utils.sswitch_thrift_API import SimpleSwitchThriftAPI
# 加载 topology.json 拓扑配置文件
topo = load_topo('topology.json')
# 用于保存每台交换机的 P4Runtime 控制器对象
controllers = {}
# 遍历拓扑中所有 P4Runtime 交换机,初始化对应的控制器对象
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']
)
# -------------------------------
# 配置 s1:入口边界交换机
# -------------------------------
controller = controllers['s1']
# 设置端口 1 为入口边界端口
controller.table_add('check_is_ingress_border', 'set_is_ingress_border', ['1'])
# 配置目的地址为 10.7.2.2/32 时添加 MPLS 标签 2(发往 h2)
controller.table_add('fec_to_label', 'add_mpls_header', ['10.7.2.2/32'], ['2'])
# 配置目的地址为 10.7.3.2/32 时添加 MPLS 标签 3(发往 h3)
controller.table_add('fec_to_label', 'add_mpls_header', ['10.7.3.2/32'], ['3'])
# 根据标签 2 进行转发,出端口 2,目的 MAC 为下一跳 s2
controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:02:01:00', '2'])
# 根据标签 3 进行转发,出端口 3,目的 MAC 为下一跳 s3
controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:03:01:00', '3'])
# 标签 1 用于回程流量(h2/h3 → h1),出端口 1
controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:0a:01:01:02', '1'])
# 设置端口 1 为出口边界(处理回程 MPLS 流量)
controller.table_add('check_is_egress_border', 'is_egress_border', ['1'])
# -------------------------------
# 配置 s2:中间 MPLS 节点
# -------------------------------
controller = controllers['s2']
# 标签 2 → 发往 s4,端口 2
controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:04:02:00', '2'])
# 标签 1(回程)→ 发往 s1,端口 1
controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:01:02:00', '1'])
# -------------------------------
# 配置 s3:中间 MPLS 节点
# -------------------------------
controller = controllers['s3']
# 标签 3 → 发往 s4,端口 2
controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:04:03:00', '2'])
# -------------------------------
# 配置 s4:汇聚 MPLS 节点
# -------------------------------
controller = controllers['s4']
# 标签 2 → 发往 s6,端口 4
controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:06:04:00', '4'])
# 标签 3 → 发往 s5,端口 3
controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:05:04:00', '3'])
# 标签 1(回程)→ 发往 s2,端口 1
controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:02:04:00', '1'])
# -------------------------------
# 配置 s5:通往 s7 的一条路径
# -------------------------------
controller = controllers['s5']
# 标签 3 → 发往 s7,端口 2
controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:07:05:00', '2'])
# -------------------------------
# 配置 s6:另一条路径,最终到达 s7
# -------------------------------
controller = controllers['s6']
# 标签 2 → 发往 s7,端口 2
controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:07:06:00', '2'])
# 标签 1(回程)→ 发往 s4,端口 1
controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:04:06:00', '1'])
# -------------------------------
# 配置 s7:出口边界交换机(h2、h3)
# -------------------------------
controller = controllers['s7']
# 标签 2 → 发往 h2,端口 3
controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:0a:07:02:02', '3'])
# 标签 3 → 发往 h3,端口 4
controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:0a:07:03:02', '4'])
# 设置端口 3 和 4 为 ingress border(回程流量起始点)
controller.table_add('check_is_ingress_border', 'set_is_ingress_border', ['3'])
controller.table_add('check_is_ingress_border', 'set_is_ingress_border', ['4'])
# 设置端口 3 和 4 为出口边界(解封装流量)
controller.table_add('check_is_egress_border', 'is_egress_border', ['3'])
controller.table_add('check_is_egress_border', 'is_egress_border', ['4'])
# 配置回程流量(目的为 h1)的 FEC → 标签 1
controller.table_add('fec_to_label', 'add_mpls_header', ['10.1.1.2/32'], ['1'])
# 标签 1(回程)→ 发往 s6,端口 2
controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:06:07:00', '2'])
# 本地 IP 转发规则:不使用 MPLS,直接发往 h2、h3
controller.table_add('ipv4_lpm', 'ipv4_forward', ['10.7.2.2/32'], ['00:00:0a:07:02:02', '3'])
controller.table_add('ipv4_lpm', 'ipv4_forward', ['10.7.3.2/32'], ['00:00:0a:07:03:02', '4'])network.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
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
56from p4utils.mininetlib.network_API import NetworkAPI
# 创建一个 NetworkAPI 实例,用于定义网络拓扑
net = NetworkAPI()
# 设置网络日志级别为 'info',可输出适量调试信息
net.setLogLevel('info')
# 指定编译器使用 P4Runtime 模式
net.setCompiler(p4rt=True)
# 执行名为 controller.py 的控制器脚本,并在每次运行时重新启动该脚本
net.execScript('python controller.py', reboot=True)
# 添加七个基于 P4Runtime 的交换机,分别命名为 s1 至 s7
net.addP4RuntimeSwitch('s1')
net.addP4RuntimeSwitch('s2')
net.addP4RuntimeSwitch('s3')
net.addP4RuntimeSwitch('s4')
net.addP4RuntimeSwitch('s5')
net.addP4RuntimeSwitch('s6')
net.addP4RuntimeSwitch('s7')
# 所有交换机使用名为 basics.p4 的 P4 程序作为数据面逻辑
net.setP4SourceAll('basics.p4')
# 添加三台主机 h1、h2、h3
net.addHost('h1')
net.addHost('h2')
net.addHost('h3')
# 连接主机 h1 到交换机 s1,连接到 s1 的端口 1
net.addLink("h1", "s1", port2=1)
# 以下构建的是一个多路径菱形结构的拓扑,支持负载均衡和路径冗余
# 连接 s1 的端口 2 到 s2 的端口 1
net.addLink("s1", "s2", port1=2, port2=1)
# 连接 s1 的端口 3 到 s3 的端口 1
net.addLink("s1", "s3", port1=3, port2=1)
# 连接 s2 的端口 2 到 s4 的端口 1
net.addLink("s2", "s4", port1=2, port2=1)
# 连接 s3 的端口 2 到 s4 的端口 2
net.addLink("s3", "s4", port1=2, port2=2)
# s4 是拓扑的中间节点,连接两个子路径分支:s5 和 s6
net.addLink("s4", "s5", port1=3, port2=1)
net.addLink("s4", "s6", port1=4, port2=1)
# s5 和 s6 都连向汇聚节点 s7
net.addLink("s5", "s7", port1=2, port2=1)
net.addLink("s6", "s7", port1=2, port2=2)
# s7 是终点交换机,连接主机 h2(端口 3)和 h3(端口 4)
net.addLink("s7", "h2", port1=3)
net.addLink("s7", "h3", port1=4)
# 启用三层自动地址分配与默认路由配置
net.l3()
# 启用对所有节点的 pcap 抓包功能,便于后续数据包分析
net.enablePcapDumpAll()
# 启用所有节点的日志记录
net.enableLogAll()
# 启动命令行交互界面 CLI,便于调试与测试网络行为
net.enableCli()
# 启动整个网络,包括交换机、主机和控制器
net.startNetwork()
P4仿真
- 配置匹配表
- 启动网络拓扑
- 启动
pingall命令,发现主机间成功通信
P4 06-MPLS
http://example.com/2025/07/31/P4 06-MPLS/