p4学习-4:P4runtime实验

实验目标

使用P4Runtime而不是来交换机的CLI发送flow entries;P4程序是在basic tunnel里面的程序的基础上写的,改名叫了 advanced_tunnel.p4,并且增加了两个counters(ingressTunnelCounter, egressTunnelCounter)和两个actions(myTunnel_ingress, myTunnel_egress)

使用启动程序mycontroller.py以及一些 p4runtime_lib 库里面的函数来创建主机1和主机2之间隧道通信所需的表项。

代码部分

主要是mycontroller.py里面的writeTunnelRules函数的代码:

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
#!/usr/bin/env python2
import argparse
import grpc
import os
import sys
from time import sleep

# Import P4Runtime lib from parent utils dir
# Probably there's a better way of doing this.
sys.path.append(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../utils/'))
import p4runtime_lib.bmv2
from p4runtime_lib.switch import ShutdownAllSwitchConnections
import p4runtime_lib.helper

SWITCH_TO_HOST_PORT = 1
SWITCH_TO_SWITCH_PORT = 2

def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id,
dst_eth_addr, dst_ip_addr):
"""
Installs three rules:
1) An tunnel ingress rule on the ingress switch in the ipv4_lpm table that encapsulates traffic into a tunnel with the specified ID
ipv4_lpm表的入接口开关上的隧道入接口规则,该规则用指定的ID将流量封装到一个隧道中
2) A transit rule on the ingress switch that forwards traffic based on the specified ID
入口交换机上的一种传输规则,根据指定的ID转发流量
3) An tunnel egress rule on the egress switch that decapsulates traffic with the specified ID and sends it to the host
出口交换机上的一条隧道出口规则,将指定ID的流量解封装后发送给主机

:param p4info_helper: the P4Info helper
:param ingress_sw: the ingress switch connection
:param egress_sw: the egress switch connection
:param tunnel_id: the specified tunnel ID
:param dst_eth_addr: the destination IP to match in the ingress rule
:param dst_ip_addr: the destination Ethernet address to write in the egress rule
"""
# 1) Tunnel Ingress Rule
table_entry = p4info_helper.buildTableEntry(
table_name="MyIngress.ipv4_lpm",
match_fields={
"hdr.ipv4.dstAddr": (dst_ip_addr, 32)
},
action_name="MyIngress.myTunnel_ingress",
action_params={
"dst_id": tunnel_id,
})
ingress_sw.WriteTableEntry(table_entry)
print "Installed ingress tunnel rule on %s" % ingress_sw.name

# 2) Tunnel Transit Rule
# The rule will need to be added to the myTunnel_exact table and match on the tunnel ID (hdr.myTunnel.dst_id). Traffic will need to be forwarded using the myTunnel_forward action on the port connected to the next switch.这条规则是添加到myTunnel_exact table上面的,match的部分是tunnel ID (hdr.myTunnel.dst_id)。流量将会使用myTunnel_forward action转发到连接下一个交换机的端口,这里要观察下面的拓扑图会发现连接交换机(h1,h2)的都是二号端口,文件里面用SWITCH_TO_SWITCH_PORT表示了2号端口
#
# For our simple topology, switch 1 and switch 2 are connected using a link attached to port 2 on both switches. We have defined a variable at the top of the file, SWITCH_TO_SWITCH_PORT, that you can use as the output port for this action.
#
# We will only need a transit rule on the ingress switch because we are using a simple topology. In general, you'll need on transit rule for each switch in the path (except the last switch, which has the egress rule), and you will need to select the port dynamically for each switch based on your topology.

# TODO build the transit rule
# TODO install the transit rule on the ingress switch
#print "TODO Install transit tunnel rule"
table_entry=p4info_helper.buildTableEntry(
table_name="MyIngress.myTunnel_exact",
match_fields={
"hdr.myTunnel.dst_id":tunnel_id
},
action_name="MyIngress.myTunnel_forward",
action_params={
"port":SWITCH_TO_SWITCH_PORT
})
ingress_sw.WriteTableEntry(table_entry)
print "Installed transit tunnel rule on %s" % ingress_sw.name

# 3) Tunnel Egress Rule
# For our simple topology, the host will always be located on the SWITCH_TO_HOST_PORT (port 1).
# In general, you will need to keep track of which port the host is connected to.
table_entry = p4info_helper.buildTableEntry(
table_name="MyIngress.myTunnel_exact",
match_fields={
"hdr.myTunnel.dst_id": tunnel_id
},
action_name="MyIngress.myTunnel_egress",
action_params={
"dstAddr": dst_eth_addr,
"port": SWITCH_TO_HOST_PORT
})
egress_sw.WriteTableEntry(table_entry)
print "Installed egress tunnel rule on %s" % egress_sw.name

#### 下面的函数省略不表

实际写的时候主要依靠下面的Tunnel Egress Rule规则仿写,主要p4文件里面:

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

/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/

control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {

counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) ingressTunnelCounter;
counter(MAX_TUNNEL_ID, CounterType.packets_and_bytes) egressTunnelCounter;

action drop() {
mark_to_drop(standard_metadata);
}

action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}

action myTunnel_ingress(bit<16> dst_id) {
hdr.myTunnel.setValid();
hdr.myTunnel.dst_id = dst_id;
hdr.myTunnel.proto_id = hdr.ethernet.etherType;
hdr.ethernet.etherType = TYPE_MYTUNNEL;
ingressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id);
}

action myTunnel_forward(egressSpec_t port) {
standard_metadata.egress_spec = port;
}

action myTunnel_egress(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.dstAddr = dstAddr;
hdr.ethernet.etherType = hdr.myTunnel.proto_id;
hdr.myTunnel.setInvalid();
egressTunnelCounter.count((bit<32>) hdr.myTunnel.dst_id);
}

table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
myTunnel_ingress;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}

table myTunnel_exact {
key = {
hdr.myTunnel.dst_id: exact;
}
actions = {
myTunnel_forward;
myTunnel_egress;
drop;
}
size = 1024;
default_action = drop();
}

apply {
if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) {
// Process only non-tunneled IPv4 packets.
ipv4_lpm.apply();
}

if (hdr.myTunnel.isValid()) {
// Process all tunneled packets.
myTunnel_exact.apply();
}
}
}

match_field里面相当于action里面的key部分,具体的值由控制平面也就是函数里面的tunnel_id确定;action_name里面相当于调用了action myTunnel_forward,这个action的参数只有port,又因为题目里面的提示要求port是SWITCH_TO_SWITCH_PORT因此填进去就行了;

实验过程

1 跑未完成的代码
  1. mycontroller.py里面包含了一些隧道通信需要的规则,在没有完成的情况下先跑p4看看啥样:

    1
    make
    • 这步编译了 advanced_tunnel.p4,
    • 启动一个Mininet实例,其中三个交换机(s1, s2, s3)配置在一个三角形中,每个交换机连接到一个主机(h1, h2, h3),并且把10.0.1.1, 10.0.2.2, 10.0.3.3这些IP配置到对应的主机上
  2. 现在会看到mininet命令窗口,ping h1 和 h2:

    1
    mininet> h1 ping h2

    因为交换机上面没有规则,现在还没法接受回应,然后如果ctrl+C就如下图各种丢包;

    image-20210303234909688

    现在把这个ping留着继续跑然后开另一个shell

  3. 在另一个shell里面跑:

    1
    2
    cd ~/tutorials/exercises/p4runtime
    ./mycontroller.py

    这会安装advanced_tunnel.p4程序到switch上面并且把tunnel ingress规则推进去;这个程序每两秒打印ingress和egress的counters

    image-20210304163414071

    因为目前没有完成还有一些TODO的部分,所以下面可以看到这些包经过s1的ingress并且让计数器增加;

  4. Ctrl-c退出

    目前的交换机是根据目的IP地址将流量映射到隧道(ID)上面,要做的工作是写规则,从而让交换机能够基于隧道(tunnel) ID来转发流量

和前面的对比:之前的都是依靠一些json文件静态的设置表项,这里是动态的;要注意到p4 程序仅仅定义了一个包处理的管道,但是是不包含具体的规则的;

P4程序定义了包处理管道(规则由控制平面插入),同时定义了交互机的pipeline和控制平面的接口(这部分在advanced_tunnel.p4info)里面;在mycontroller. py里面构建的表项(table entries)会根据名称引用特定的表、键和actions;使用p4info会将名字转换程P4runtime所需要的ID。在P4程序中添加或者重命名的表、键、actions都要反映在 py程序的表项(table entries)里面

2 完善隧道转发
  1. p4runtime_lib库里面的总结,这部分代码在tutorials/utils/p4runtime_lib里面:

    • p4runtime_lib.helper

      • 包含了P4InfoHelper 类能够解析P4Info文件,包括了name,id,alias,match_field(本身/id/name/pb/value),action_param(本身/id/name/pb)
      • 提供了实体的名字到ID数字转换的方法(buildTableEnrty函数)
      • 构建P4Runtime表项的P4依赖于程序的部分。
    • p4runtime_lib.switch.py

      • 包括了SwitchConnection类,会获取gRPC的client stub以及给交换机建立连接
      • 提供了helper方法,能够构建P4Runtime协议的buffer信息以及制作P4Runtime gRPC service calls
    • p4runtime_lib.bmv2.py

      • 包含了 Bmv2SwitchConnection 类,它拓展了SwitchConnections并且提供了BMv2-specific 设备载荷来装载P4程序

        PS:实际上是重载了SwitchConnection 然后多了一个buildDeviceConfig函数能够根据bmv2_json_file来返回设备的配置

    • p4runtime_lib.convert.py

      • helper.py调用
      • 主要提供了一些把数字/字符串编码协议buffer信息需要的格式的方法以及相应的解码方法
  2. mycontroller.py是一个结能的控制平面做了以下的事情

    1. 为P4Runtime服务构建了一个到交换机的 gRPC连接
    2. 把P4程序push给各个交换机
    3. 在h1,h2之间的两个隧道编写了ingress/egress规则
    4. 每两秒读隧道的ingress/egress计数器
  3. 代码部分主要要做的事情是在 writeTunnelRules函数里面写隧道转发规则,它对应于隧道ID并且能够把包转发到下一跳

  4. 拓扑图如下:

    topology

3 实验结果

make cleanmake,然后h1 ping h2,然后开一个新端口./mycontroller.py,这个时候可以看见ingress,egress的计数都上涨了:

py_info
py_info

另外ping也能够成功的收到了:

mini_info
mini_info

额外参考——rpc,grpc学习

左转参考博客

参考博客