2023年11月29日发(作者:)
P4官⽅实验3.P4Runtime
Implementing a Control Plane using P4Runtime
使⽤P4Runtime来发送流表项到交换机,⽽不是使⽤交换机命令⾏接⼝。
在中,我们定义明确的流表、键、动作的名字,我们使⽤P4Info_helper来将这些名字转化成IDs,P4Runtime需要这些
IDs来执⾏。
任何表、键、动作的改变都必须在表项中被反映。
代码解析
头⽂件
⾸先是引⼊了⼀堆库,和需要⽤到的p4runtime_lib。
#!/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.error_utils import printGrpcError
from p4runtime_lib.switch import ShutdownAllSwitchConnections
import p4runtime_lib.helper
SWITCH_TO_HOST_PORT = 1
SWITCH_TO_SWITCH_PORT = 2 //指定了交换机端⼝号
writeTunnelRules
之后是定义写隧道规则。
def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, dst_eth_addr, dst_ip_addr):
注释中说明了这个函数要实现的三个规则:
1. ⼊路由中的ipv4_lpm表的⼊隧道规则,将流量封装进指定ID的隧道中
2. **(TODO)**⼊路由中的运输规则,基于指定的隧道ID转发流量
3. 在出路由中的规则,使⽤特定的ID对流量解封装,并且发送流量到主机
⾸先是第⼀个规则
# 1) Tunnel Ingress Rule
table_entry = p4info_helper.buildTableEntry(
table_name="4_lpm",
match_fields={
"r": (dst_ip_addr, 32)
},
action_name="el_ingress",
action_params={
"dst_id": tunnel_id,
})
ingress_sw.WriteTableEntry(table_entry)
print "Installed ingress tunnel rule on %s" % ingress_sw.name
表项 = p4info_ableEntry 上⽂说过,需要使⽤p4info_helper这个解析器来将规则转化为P4Runtime能够识别的形式。
定义表名,设置匹配域。如果包头对应的r字段与参数中的dst_ip_addr匹配,就执⾏这⼀条表项的对应动作。后⾯的32
是掩码,表⽰前32bit都是⽹络号。
设置匹配成功对应的动作名,该动作参数为传⼊的tunnel_id
最后,ingress_sw调⽤WriteTableEntry,将⽣成的匹配动作表项加⼊交换机。
第⼆、三个规则跟这个很像,先不说了。
readTableRules
def readTableRules(p4info_helper, sw):
"""
Reads the table entries from all tables on the switch.
:param p4info_helper: the P4Info helper
:param sw: the switch connection
"""
print 'n----- Reading tables rules for %s -----' % sw.name
for response in sw.ReadTableEntries():
for entity in response.entities:
entry = entity.table_entry
# TODO For extra credit, you can use the p4info_helper to translate
# the IDs in the entry to names
print entry
print '-----'
虽然代码部分牵扯某些函数不⼤好懂,但能明⽩⼤致的意思是将交换机中所有流表所有条⽬全部读出来,打印出来。
printCounter
从交换机中读具体的索引对应的计数器。在我们的程序中,这个索引是隧道ID号。如果这个索引是0,就会从计数器中返回所有的值。
def printCounter(p4info_helper, sw, counter_name, index):
"""
Reads the specified counter at the specified index from the switch. In our
program, the index is the tunnel ID. If the index is 0, it will return all
values from the counter.
:param p4info_helper: the P4Info helper
:param sw: the switch connection
:param counter_name: the name of the counter from the P4 program
:param index: the counter index (in our case, the tunnel ID)
"""
for response in sw.ReadCounters(p4info_helper.get_counters_id(counter_name), index):
for entity in response.entities:
counter = entity.counter_entry
print "%s %s %d: %d packets (%d bytes)" % (
sw.name, counter_name, index,
counter.data.packet_count, counter.data.byte_count
)
可以看到,counter能够计数数据包个数,数据总⽐特位数。
main函数
⾸先初始化p4info_helper对象,⽤于接下来的解析。
p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)
之后,创建⼀个在s1和s2之间的交换机链接,这由gRPC提供⽀持;将所有发送给交换机的P4Runtime信息转存到给出的txt⽂件中。
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/')
s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
name='s2',
address='127.0.0.1:50052',
device_id=1,
proto_dump_file='logs/')
设置这个controller是master,这⼀步需要P4Runtime在运⾏所有写操作之前执⾏。(但我暂时没明⽩为什么要有这⼀步)。
# Send master arbitration update message to establish this controller as
# master (required by P4Runtime before performing any other write operation)
s1.MasterArbitrationUpdate()
s2.MasterArbitrationUpdate()
将P4程序安装进switch中。
# Install the P4 program on the switches
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"
写转发隧道的规则。
# 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")
**(TODO)**完成接下来的两⾏,从S1和S2读表项的操作。上⾯已定义了函数,所以很好填。
# TODO Uncomment the following two lines to read table entries from s1 and s2
readTableRules(p4info_helper, s1)
readTableRules(p4info_helper, s2)
每两秒读⼀次隧道计数器,读关于包数、bit数的信息。
# Print the tunnel counters every 2 seconds
while True:
sleep(2)
print 'n----- Reading tunnel counters -----'
printCounter(p4info_helper, s1, "sTunnelCounter", 100)
printCounter(p4info_helper, s2, "TunnelCounter", 100)
printCounter(p4info_helper, s2, "sTunnelCounter", 200)
printCounter(p4info_helper, s1, "TunnelCounter", 200)
如果上⾯的这些try操作出错,就⽤except来接收这些错误。最后,Shut Down All Switch Connections。
if __name__ == ‘__main__’:
if __name__ == '__main__':
这⼀句的意思是,如果这个python⽂件是被当做脚本直接执⾏,那么就会执⾏下⾯的语句,如果这个⽂件是被当做包import,那么下⾯的
语句就不会执⾏。
每个模块都包含内置变量__name__,当模块被直接执⾏时,__name__等于⽂件名.py,如果这个模块import到其他模块中,那么该模块
__name__等于模块名称(⽆后缀py)。
__main__始终指当前执⾏模块的名称.py,当模块被直接执⾏时,__name__ == __main__为真。
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='P4Runtime Controller')
parser.add_argument('--p4info', help='p4info proto in text format from p4c',
type=str, action="store", required=False,2
default='./build/advanced_')
parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c',
type=str, action="store", required=False,
default='./build/advanced_')
args = parser.parse_args()
if not os.path.exists(args.p4info):
parser.print_help()
print "np4info file not found: %snHave you run 'make'?" % args.p4info
parser.exit(1)
if not os.path.exists(args.bmv2_json):
parser.print_help()
print "nBMv2 JSON file not found: %snHave you run 'make'?" % args.bmv2_json
parser.exit(1)
main(args.p4info, args.bmv2_json)
其实没怎么看懂,但是隐约感觉这是配置解析器参数,最后调⽤上⾯的main函数。


发布评论