P4Runtime参照P4Runtime--基于mininet和bmv2环境的解析 - forestj - 博客园 (cnblogs.com)
目录
一 实验简介
在本练习中,我们将使用P4Runtime将流表项发送到交换机,而不是使用交换机的CLI。我们将构建与basic_tunnel练习中使用的相同的P4程序。P4程序已重命名为advanced_tunnel。P4程序中还增加了两个计数器(ingressTunnelCounter, egressTunnelCounter)和两个新动(myTunnel_ingress, myTunnel_egress)。本次实验将使用脚本文件mycontroller.py和p4runtime_lib目录中的几个辅助库来创建主机1和主机2之间的通道通信所需的表项。
二 实验步骤
1.创建拓扑
首先编译新的P4程序,启动网络,使用它来安装一些规则
make run
注:这里我使用make run命令创建好网络拓扑结构后执行h1 ping h2操作后发现是ping不通的,因为没有下发流表项的匹配规则。
2.运行脚本mycontroller.py下发流表项
P4程序定义了一个包处理管道,但是每个表中的规则由控制平面插入。在本实验中,实现我们的控制平面,而不是像在前面的练习中那样安装静态表项,P4程序还定义了开关管道和控制平面之间的接口。因此我们这里需要实现mycontroller.py中的TODO部分内容
mycontroller.py文件是一个基本的控制器平面,执行以下操作:
1.为P4Runtime服务建立到交换机的gRPC连接。
2.将P4程序推送到每个交换机。
3.为h1和h2之间的两条隧道写入隧道入和隧道出规则。
4.每2秒读取隧道入口和出口计数器。
在本练习中,还需要与p4runtime_lib目录中的一些类和方法进行交互。以下是该目录中每个文件的摘要:
- helper.py
包含用于解析p4info文件的P4InfoHelper类。
提供实体名称与ID号之间的转换方法。
构建P4Runtime表项的P4程序相关部分。
- switch.py
包含SwitchConnection类,该类获取gRPC客户端存根,并建立到交换机的连接。
提供构建P4Runtime协议缓冲区消息并进行P4Runtime gRPC服务调用的帮助器方法。
- bmv2.py
包含Bmv2SwitchConnection,它扩展了SwitchConnections,并提供了特定于bmv2的设备负载来加载P4程序。
- convert.py
提供方便的方法,将友好的字符串和数字编码和解码为协议缓冲区消息所需的字节字符串。
由helper.py使用.
导入P4Runtime库
sys.path.append(
os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../utils/'))
import p4runtime_lib.bmv2
from p4runtime_lib.error_utils import printGrpcError
from p4runtime_lib.switch import ShutdownAllSwitchConnections
import p4runtime_lib.helper
写入隧道规则
(1) Tunnel Ingress Rule 入接口规则
ipv4_lpm表中入口交换机上的隧道进入规则将流量封装到指定ID的隧道中
# 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 转发规则
由于该实验为简单拓扑,交换机1和交换机2都是使用连接到两个交换机的端口2的链路连接。因此我们在文件的顶部定义了一个变量SWITCH_TO_SWITCH_PORT,并将其赋值为2,可以将其用作此操作的输出端口。
SWITCH_TO_HOST_PORT = 1
SWITCH_TO_SWITCH_PORT = 2
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("TODO Install transit tunnel rule")
(3) Tunnel Egress Rule隧道出接口规则
对于简单拓扑,主机将始终位于SWITCH_TO_HOST_PORT(端口1)上。一般来说,只需要跟踪主机连接到的端口。
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)
交换机的创建(使用grpc)
首先在topology.json中声明交换机,在执行make run以后,会执行run_exercise.py(具体函数调用链略),之后进入P4RuntimeSwitch类的__init__()方法.
# p4runtime_switch.py
# 函数体部分只留下关注的部分,其余部分有所删减,后同
# 该类为P4RuntimeSwitch,继承了P4Switch类,P4Switch类又继承了Switch类,Switch类继承Node类(代表mininet的节点)
"BMv2 switch with gRPC support"
next_grpc_port = 50051
def __init__(self, name, sw_path = None, json_path = None,
grpc_port = None,
thrift_port = None,
pcap_dump = False,
log_console = False,
verbose = False,
device_id = None,
enable_debugger = False,
log_file = None,
**kwargs):
# name是指定的交换机名字,会在Node类的构造方法中被初始化
Switch.__init__(self, name, **kwargs)
# 这个json文件是指向p4c后p4的编译器编译出来的json文件,位于build目录下
if json_path is not None:
# make sure that the provided JSON file exists
if not os.path.isfile(json_path):
error("Invalid JSON file: {}\n".format(json_path))
exit(1)
self.json_path = json_path
else:
self.json_path = None
# 如果传递了port,就用传递的,否则是用静态变量next_grpc_port(初始值50051),并让否则是用静态变量next_grpc_port++,
if grpc_port is not None:
self.grpc_port = grpc_port
else:
self.grpc_port = P4RuntimeSwitch.next_grpc_port
P4RuntimeSwitch.next_grpc_port += 1
# 如果传递了device_id,就用传递的,否则是用静态变量P4Switch类中的静态变量device_id(初始值0),并让否则是用静态变量device_id++
if device_id is not None:
self.device_id = device_id
P4Switch.device_id = max(P4Switch.device_id, device_id)
else:
self.device_id = P4Switch.device_id
P4Switch.device_id += 1
# 几个比较重要的参数就是name用于标识名字,port标识连接端口,device_id用于标识设备
创建s1和s2的交换机连接对象
官方封装了一个Bmv2SwitchConnection类方法来帮助建立连接,这里的前三个参数和建立交换机时的要一致,最后一个参数为输出的日志文件位置。
def main(p4info_file_path, bmv2_file_path):
# Instantiate a P4Runtime helper from the p4info file
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
try:
# Create a switch connection object for s1 and s2;
# this is backed by a P4Runtime gRPC connection.
# Also, dump all P4Runtime messages sent to switch to given txt files.
s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
name='s1',
address='127.0.0.1:50051',
device_id=0,
proto_dump_file='logs/s1-p4runtime-requests.txt')
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
name='s2',
address='127.0.0.1:50052',
device_id=1,
proto_dump_file='logs/s2-p4runtime-requests.txt')
注:这里的参数要和创建交换机中设置的参数一致,其中device_id参数的设置不能随便更改,因为utils中p4_mininet.py中已经设置好了这个值,如下所示:
在交换机上安装P4程序
s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
bmv2_json_file_path=bmv2_file_path)
print("Installed P4 Program using SetForwardingPipelineConfig on s1")
s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
bmv2_json_file_path=bmv2_file_path)
print("Installed P4 Program using SetForwardingPipelineConfig on s2")
编写从h1到h2的隧道流量规则
这里的tunnel_id必须唯一
# Write the rules that tunnel traffic from h1 to h2
writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100,
dst_eth_addr="08:00:00:00:02:22", dst_ip_addr="10.0.2.2")
# Write the rules that tunnel traffic from h2 to h1
writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200,
dst_eth_addr="08:00:00:00:01:11", dst_ip_addr="10.0.1.1")
每2秒打印隧道计数器
while True:
sleep(2)
print('\n----- Reading tunnel counters -----')
printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100)
printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100)
printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200)
printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 200)
3.测试h1和h2之间连通性
执行sudo python3 mycontroller.py命令下发流表项
在mininet拓扑中执行h1 ping h2命令
可以看到能够正常连通,s1的入口隧道计数器在增加
三 实验拓展
本实验的交换机s1和s2连接的端口正好都是2,所有在写隧道转发规则时可以直接使用变量SWITCH_TO_SWITCH_PORT,如果想要实现其他交换机之间的隧道规则就需要做进一步的修改。
这里展示的是s1,s2,s3交换机之间分别建立隧道通道。
首先需要添加交换机s3的连接
s3 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
name='s3',
address='127.0.0.1:50053',
device_id=2,
proto_dump_file='logs/s3-p4runtime-requests.txt')
分别设置s1,s2,s3作为主控制器
s1.MasterArbitrationUpdate()
s2.MasterArbitrationUpdate()
s3.MasterArbitrationUpdate()
安装P4程序到s3交换机上
s3.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
bmv2_json_file_path=bmv2_file_path)
print("Installed P4 Program using SetForwardingPipelineConfig on s2")
写隧道规则,这里就需要分别添加两个字段switch_to_host_port和switch_to_switch_port来单独设置转发端口号
# Write the rules that tunnel traffic from h1 to h2
writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s2, tunnel_id=100,
dst_eth_addr="08:00:00:00:02:22", dst_ip_addr="10.0.2.2", switch_to_host_port=1, switch_to_switch_port=2)
# Write the rules that tunnel traffic from h2 to h1
writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s1, tunnel_id=200,
dst_eth_addr="08:00:00:00:01:11", dst_ip_addr="10.0.1.1", switch_to_host_port=1, switch_to_switch_port=2)
writeTunnelRules(p4info_helper, ingress_sw=s1, egress_sw=s3, tunnel_id=300,
dst_eth_addr="08:00:00:00:03:33", dst_ip_addr="10.0.3.3", switch_to_host_port=1, switch_to_switch_port=3)
writeTunnelRules(p4info_helper, ingress_sw=s3, egress_sw=s1, tunnel_id=400,
dst_eth_addr="08:00:00:00:01:11", dst_ip_addr="10.0.1.1", switch_to_host_port=1, switch_to_switch_port=2)
writeTunnelRules(p4info_helper, ingress_sw=s2, egress_sw=s3, tunnel_id=500,
dst_eth_addr="08:00:00:00:03:33", dst_ip_addr="10.0.3.3", switch_to_host_port=1, switch_to_switch_port=3)
writeTunnelRules(p4info_helper, ingress_sw=s3, egress_sw=s2, tunnel_id=600,
dst_eth_addr="08:00:00:00:02:22", dst_ip_addr="10.0.2.2", switch_to_host_port=1, switch_to_switch_port=3)
分别从s1,s2,s3中读取表项
readTableRules(p4info_helper, s1)
readTableRules(p4info_helper, s2)
readTableRules(p4info_helper, s3)
打印隧道计数器
while True:
sleep(2)
print('\n----- Reading tunnel counters -----')
printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100)
printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100)
printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200)
printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 200)
printCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 300)
printCounter(p4info_helper, s3, "MyIngress.egressTunnelCounter", 300)
printCounter(p4info_helper, s3, "MyIngress.ingressTunnelCounter", 400)
printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 400)
printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 500)
printCounter(p4info_helper, s3, "MyIngress.egressTunnelCounter", 500)
printCounter(p4info_helper, s3, "MyIngress.ingressTunnelCounter", 600)
printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 600)
修改mycontroller.py脚本后在执行pingall操作发现可以ping通