使用P4Runtime来发送流表项到交换机,而不是使用交换机命令行接口。
在mycontroller.py中,我们定义明确的流表、键、动作的名字,我们使用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 //指定了交换机端口号
之后是定义写隧道规则。
def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id, dst_eth_addr, dst_ip_addr):
注释中说明了这个函数要实现的三个规则:
入路由中的ipv4_lpm表的入隧道规则,将流量封装进指定ID的隧道中
**(TODO)**入路由中的运输规则,基于指定的隧道ID转发流量
在出路由中的规则,使用特定的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
表项 = p4info_helper.buildTableEntry 上文说过,需要使用p4info_helper这个解析器来将规则转化为P4Runtime能够识别的形式。
定义表名,设置匹配域。如果包头对应的hdr.ipv4.dstAddr字段与参数中的dst_ip_addr匹配,就执行这一条表项的对应动作。后面的32是掩码,表示前32bit都是网络号。
设置匹配成功对应的动作名,该动作参数为传入的tunnel_id
最后,ingress_sw调用WriteTableEntry,将生成的匹配动作表项加入交换机。
第二、三个规则跟这个很像,先不说了。
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 '-----'
虽然代码部分牵扯某些函数不大好懂,但能明白大致的意思是将交换机中所有流表所有条目全部读出来,打印出来。
从交换机中读具体的索引对应的计数器。在我们的程序中,这个索引是隧道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能够计数数据包个数,数据总比特位数。
首先初始化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/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')
设置这个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, "MyIngress.ingressTunnelCounter", 100)
printCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100)
printCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 200)
printCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 200)
如果上面的这些try操作出错,就用except来接收这些错误。最后,Shut Down All Switch Connections。
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_tunnel.p4.p4info.txt')
parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c',
type=str, action="store", required=False,
default='./build/advanced_tunnel.json')
args = parser.parse_args()
if not os.path.exists(args.p4info):
parser.print_help()
print "\np4info file not found: %s\nHave 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: %s\nHave you run 'make'?" % args.bmv2_json
parser.exit(1)
main(args.p4info, args.bmv2_json)
其实没怎么看懂,但是隐约感觉这是配置解析器参数,最后调用上面的main函数。