作者简介:陈翔,福州大学数计学院2015级计算机科学与技术(实验班)本科生 ,对软件定义网络SDN,特别是对P4语言感兴趣。
前言
相信读者在阅读此篇文章之前已经对P4(www.P4.org)语言的语法规范已经有了一定的了解和认识,但可能对P4实际编程的方法和步骤有所疑问。本篇文章是针对Barefoot在2015年SIGCOMM会议上演示的Demo:Source_Routing,以及Github上Tutorials开源教程中的P4v1.1样例Simple_Router的实战记录。
Tutorials目录下有以下几个文件夹:SIGCOMM_2015、SIGCOMM_2016、workshop_05_2016、examples 以及 p4v1_1;其教程主要分成三个部分:第一部分是Barefoot在2015年、2016年SIGCOMM会议上和2016年5月Workshop上的Demo演示;第二部分是用P4语言实现交换机某些功能的样例,如实现NAT、计数器、拷贝至CPU;第三部分是P4v1.1版本的一个基于simple_router的样例。
相比P4集成环境P4factory,开源教程Tutorials更加适用于读者学习P4语法,快速掌握P4。在大家根据样例进行P4编程的同时,提供正确的P4代码以便读者参考。此外,Tutorials的每一个样例均提供完整的实验模拟环境,读者能够依照给定的脚本自主搭建虚拟环境从而对自己的P4程序进行测试。
二、Tutorials样例实战
实验一:SICOMM_2015/Source_Routing
实验环境:
1.OS:Ubuntu 14.04,64bit。
2.bmv2,即behavioral-model
3.p4c-bm
4.获取tutorials源码:Github/P4Lang/Tutorials https://github.com/p4lang/tutorials
Hint:bmv2、p4c-bm、tutorials均在Github中开源,可以从P4Lang中git clone下来。
实验准备
1.首先修改env.sh中的脚本信息,env.sh脚本路径:tutorials/。
我的env.sh脚本如下:
1 2 3 4 5 6 7 8 |
THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) # ---------------- EDIT THIS ------------------ BMV2_PATH=/home/wasdns/bmv2 # e.g. BMV2_PATH=$THIS_DIR/../bmv2 P4C_BM_PATH=/home/wasdns/p4c-bm # e.g P4C_BM_PATH=$THIS_DIR/../p4c-bm # ---------------- END ------------------ |
读者请根据实际情况修改bmv2和p4c-bm的路径。
2.修改脚本中的env.sh路径信息。
修改脚本中的env.sh路径信息:
1 |
source $THIS_DIR/../env.sh |
我为了方便起见,将env.sh脚本拷贝至实验目录下,因此我的路径信息为:
1 |
source $THIS_DIR/env.sh |
实验原理
本实验介绍了一个非常简单的源路由协议:EasyRoute,其协议格式如下:
1 |
preamble (8 bytes) | num_valid (4 bytes) | port_1 (1 byte) | port_2 (1 byte) | ... | port_n (1 byte) | payload |
数据报中的preamble字段默认设置为0,我们可以通过这个字段来区别以太网帧和EasyRoute协议包;num_valid字段指明了在首部中合法端口字段的个数,如果数据报要经过三个交换机,那么这个字段的值就为3;当交换机收到了EasyRoute协议包的时候,根据数据报中的第一个端口字段决定其输出端口,之后将该端口字段移除并将num_valid字段值减一。详细的描述请参考:https://github.com/p4lang/tutorials/tree/master/SIGCOMM_2015#exercise-1-source-routing。
本实验要求读者使用P4语言进行编程,往给出的、不完整的P4程序(https://github.com/p4lang/tutorials/blob/master/SIGCOMM_2015/source_routing/p4src/source_routing.p4)中填入一些关键的语句,生成相应的配置部署至底层交换机,使交换机按照EasyRoute协议格式对数据报进行处理;此外,编写运行时命令填写至commands.txt中,在启动mininet虚拟拓扑环境时通过运行时CLI下发到P4交换机中,进行控制,以完成实验要求。
这里读者需要了解CLI的两种命令格式:
1 2 |
table_set_default |
table_add => [priority]
table_set_default
命令用于设置流表的默认动作,需要指定流表名称、默认动作名称以及需要给默认动作传递的执行参数。
table_add
命令用于为流表添加一条表项,需要指定流表名称、表项执行动作的名称、匹配的字段以及需要给动作传递的执行参数,可以指定该表项的优先级。
需要我们通过P4编程和下发运行时命令完成的一共有两点:
1.丢弃所有使用其他协议的数据报。
2.当交换机收到字段num_valid值为0的数据报时,丢弃该数据报。
Barefoot也提供了一整套完整的解决方案,在目录下解压solution.tar.gz即可得到完整源码和命令。建议大家先独立用P4实现,再参考所提供的解决方案。本实验使用提供的P4源码以及运行时命令进行实验。
实验步骤
1.在填写好P4程序和commands.txt之后,启动mininet虚拟网络拓扑:
1 |
> ./run_demo.sh |
2.打开h1和h3的终端:
1 |
> xterm h1 h3 |
3.在h3的终端中启动脚本receive.py,接收EasyRoute数据报:
1 |
> ./receive.py |
4.在h1的终端中启动脚本send.py,指定执行参数“h1 h3”,令h1往h3发包。
1 |
> ./send.py h1 h3 |
5.在h1的终端中写入数据报的数据并发包,在h3的终端中显示h1发送的数据报的信息:
(图一:h1发送EasyRoute数据包)
(图二:h3接收到h1发送的信息)
(图三: Demo - Source Routing)
实验二:P4v1.1/Simple_Router
实验环境
同实验一。
实验准备
1.首先修改env.sh中的脚本信息,env.sh脚本路径:tutorials/p4v1_1。
我的env.sh脚本如下:
1 2 3 4 5 6 7 8 |
THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) # ---------------- EDIT THIS ------------------ BMV2_PATH=/home/wasdns/bmv2 # e.g. BMV2_PATH=$THIS_DIR/../bmv2 P4C_BM_PATH=/home/wasdns/p4c-bm # e.g P4C_BM_PATH=$THIS_DIR/../p4c-bm # ---------------- END ------------------ |
请根据实际情况修改bmv2和p4c-bm的路径。
2.修改脚本中的env.sh路径信息。
同实验1。
注意:在simple_router目录下的所有脚本都需要修改路径信息。
实验原理
在执行目录simple_router下有如下文件:
1 2 |
add_entries.sh* README.md register_on_off.sh* commands.txt p4src/ read_register.sh* run_demo.sh* |
运行run_demo.sh脚本,先将p4src中的P4程序通过前端编译器p4c-bm转换为.json文件,启动behavioral-model中mininet目录下的1sw_demo.py脚本,建立由一个P4交换机和两个host组成的虚拟网络拓扑,并将上述生成的.json文件作为输入配置到P4交换机中。
此时,可以借助bmv2/tools目录下的runtime_CLI.py脚本来控制数据平面,启动该脚本时需要指定thrift服务端口(默认为9090)来对P4交换机进行配置,具体命令如下:
1 |
> ./runtime_CLI.py --thrift-port [thrift服务端口] |
本实验中用于控制的脚本,如register_on_off.sh,均是借助CLI来对交换机进行实时控制的,读者可以直接执行可执行脚本文件来对运行中的P4交换机进行控制。
commands.txt内容如下:
1 2 3 4 5 6 7 8 9 10 |
table_set_default drop_expired do_drop_expired table_set_default send_frame _drop table_set_default forward _drop table_set_default ipv4_lpm _drop table_add send_frame rewrite_mac 1 => 00:aa:bb:00:00:00 table_add send_frame rewrite_mac 2 => 00:aa:bb:00:00:01 table_add forward set_dmac 10.0.0.10 => 00:04:00:00:00:00 table_add forward set_dmac 10.0.1.10 => 00:04:00:00:00:01 table_add ipv4_lpm set_nhop 10.0.0.10/32 => 10.0.0.10 1 table_add ipv4_lpm set_nhop 10.0.1.10/32 => 10.0.1.10 2 |
在没有给交换机添加表项之前,两个主机h1和h2之间是不能正常通信的;运行add_entries.py
脚本,将上文中command.txt
文件中的命令通过CLI配置到运行时的交换机中,使h1和h2能够互相ping通。
在使用mininet启动虚拟拓扑并借助CLI下发命令使拓扑中的两台主机能够正常通信之后,我们能够发送TTL字段值为1的包,这些包在经过P4交换机的时候会被交换机丢弃,可以借助提供的脚本查看丢弃数据报的总量。
提供的脚本有两个:1.register_on_off.sh 2.read_register.sh
其中 register_on_off.sh 脚本用来启动和停止对丢弃数据报数量的计数,而 read_register.sh 脚本用于查看丢弃的数据报总数。
P4语言的C-like化
p4src目录下P4程序simple_router的完整源码,可以参考链接:https://github.com/p4lang/tutorials/blob/master/p4v1_1/simple_router/p4src/simple_router.p4。
该程序中与P4v1.0不一样的地方主要有以下三个方面:
一、header_type中的字段长度。
p4v1.1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
header_type ipv4_t { fields { bit version; bit ihl; bit diffserv; bit totalLen; bit identification; bit flags; bit fragOffset; bit ttl; bit protocol; bit hdrChecksum; bit srcAddr; bit dstAddr; } } |
p4v1.0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
header_type ipv4_t { fields { version : 4; ihl : 4; diffserv : 8; totalLen : 16; identification : 16; flags : 3; fragOffset : 13; ttl : 8; protocol : 8; hdrChecksum : 16; srcAddr : 32; dstAddr: 32; } } |
可以看到,p4v1.1规范中对字段长度的定义更接近C语言中的抽象数据类型。
二、更贴近C语言的语法。
我们可以在动作do_drop_expired中
1 2 3 4 |
action do_drop_expired() { drops_register[0] = drops_register[0] + ((drops_register_enabled[0] == 1) ? (bit)1 : 0); drop(); } |
看见如下表达式:
1 |
(drops_register_enabled[0] == 1) ? (bit)1 : 0 |
该表达式采用了C语言中的条件运算符:?
,在C语言中,由条件运算符组成的条件语句一般形式如下:
表达式1 ? 表达式 2 : 表达式 3。
这条P4语句首先对寄存器实例drops_register_enabled[0]
进行判断,如果该寄存器值为1,则这个表达式的值为1;否则为0。
三、使用=
取代原有元动作modify_field
simple_router.p4程序中的动作rewrite_mac是用于修改数据报中的源mac地址的。其在P4v1.0的表现形式如下:
1 2 3 |
action set_dmac(dmac) { modify_field(ethernet.dstAddr, dmac); } |
使用元动作modify_field
,将首部实例ethernet中的字段dstAddr值修改为传入的dmac参数。
而在p4v1.1中则可以直接使用=
符号进行赋值运算:
1 2 3 |
action rewrite_mac(in bit smac) { ethernet.srcAddr = smac; } |
P4v1.1语言规范中还有其他的细节差别,感兴趣的读者可以访问P4的官方网站P4.org下载P4v1.1的语言规范。
实验步骤
1.启动mininet虚拟网络拓扑:
1 |
> ./run_demo.sh |
此时交换机中没有任何表项,执行pingall显示主机h1和h2无法正常通信:
1 2 3 4 5 |
mininet> pingall *** Ping: testing ping reachability h1 -> X h2 -> X *** Results: 100% dropped (0/2 received) |
2.添加表项,使主机h1和h2能够正常通信。
打开一个新的终端,运行脚本为交换机添加表项:
1 |
> ./add_entries.sh |
在mininet中验证h1和h2是否能够正常通信:
1 2 3 4 5 |
mininet> pingall *** Ping: testing ping reachability h1 -> h2 h2 -> h1 *** Results: 0% dropped (2/2 received) |
3.通过脚本启动计数,开始记录交换机丢弃的数据报数量。
1 |
> ./register_on_off.sh on |
4.在mininet中,让h1向h2发送TTL字段值为1的数据报。
1 |
mininet> h1 ping h2 -t 1 |
数据报在通过交换机时被丢弃,我们不会观察到ping的回复。
5.在另一个终端中运行脚本 read_register.sh 查看交换机丢弃的数据报信息。
1 |
> ./read_register.sh |
(图四:Demo - Simple Router)
参考资料
1.P4.org
2.Github/P4Lang/Tutorials https://github.com/p4lang/tutorials
3.SDNLab:www.sdnlab.com