P4开源Tutorials教程样例实战及对P4v1.1规范的初分析

Github链接:Github-P4Lang-Tutorials-p4v1.1

前言

本文主要对Barefoot开源教程中的p4v1.1实例simple_router的实战步骤进行记录与阐述,希望能帮助大家进一步对P4v1.1有所认识与了解。

实验环境

1.OS:Ubuntu 14.04,64bit。

2.bmv2,即behavioral-model

3.p4c-bm

4.tutorials

Hint:bmv2、p4c-bm、tutorials均在Github中开源,可以从P4Lang中git clone下来。

实验准备

1.首先修改env.sh中的脚本信息,env.sh脚本路径:tutorials/p4v1_1。

我的env.sh脚本如下:

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路径信息:

source $THIS_DIR/../env.sh

我为了方便起见,将env.sh脚本拷贝至simple_router目录下,因此我的路径信息为:

source $THIS_DIR/env.sh

所有在simple_router目录下的脚本都需要修改。

实验原理

在执行目录simple_router下有如下文件:

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交换机进行配置,具体命令如下:

./runtime_CLI.py --thrift-port [thrift服务端口]

本实验中用于控制的脚本,如register_on_off.sh,均是借助CLI来对交换机进行实时控制的,读者可以直接执行可执行脚本文件来对运行中的P4交换机进行控制。

commands.txt内容如下:

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

这里读者需要了解CLI的两种命令格式:

table_set_default <table name> <action name> <action parameters>
table_add <table name> <action name> <match fields> => <action parameters> [priority]

table_set_default命令用于设置流表的默认动作,需要指定流表名称、默认动作名称以及需要给默认动作传递的执行参数。
table_add命令用于为流表添加一条表项,需要指定流表名称、表项执行动作的名称、匹配的字段以及需要给动作传递的执行参数,可以指定该表项的优先级。

在没有给交换机添加表项之前,两个主机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.p4

/* Copyright 2013-present Barefoot Networks, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

header_type ethernet_t {
    fields {
        bit<48> dstAddr;
        bit<48> srcAddr;
        bit<16> etherType;
    }
}

header_type ipv4_t {
    fields {
        bit<4> version;
        bit<4> ihl;
        bit<8> diffserv;
        bit<16> totalLen;
        bit<16> identification;
        bit<3> flags;
        bit<13> fragOffset;
        bit<8> ttl;
        bit<8> protocol;
        bit<16> hdrChecksum;
        bit<32> srcAddr;
        bit<32> dstAddr;
    }
}

parser start {
    return parse_ethernet;
}

#define ETHERTYPE_IPV4 0x0800

header ethernet_t ethernet;

parser parse_ethernet {
    extract(ethernet);
    return select(latest.etherType) {
        ETHERTYPE_IPV4 : parse_ipv4;
        default: ingress;
    }
}

header ipv4_t ipv4;

field_list ipv4_checksum_list {
    ipv4.version;
    ipv4.ihl;
    ipv4.diffserv;
    ipv4.totalLen;
    ipv4.identification;
    ipv4.flags;
    ipv4.fragOffset;
    ipv4.ttl;
    ipv4.protocol;
    ipv4.srcAddr;
    ipv4.dstAddr;
}

field_list_calculation ipv4_checksum {
    input {
        ipv4_checksum_list;
    }
    algorithm : csum16;
    output_width : 16;
}

calculated_field ipv4.hdrChecksum  {
    verify ipv4_checksum;
    update ipv4_checksum;
}

parser parse_ipv4 {
    extract(ipv4);
    return ingress;
}


action _drop() {
    drop();
}

header_type routing_metadata_t {
    fields {
        bit<32> nhop_ipv4;
    }
}

metadata routing_metadata_t routing_metadata;

register drops_register {
    width: 32;
    static: drop_expired;
    instance_count: 16;
}

register drops_register_enabled {
    width: 1;
    static: drop_expired;
    instance_count: 16;
}

action do_drop_expired() {
    drops_register[0] = drops_register[0] + ((drops_register_enabled[0] == 1) ? (bit<32>)1 : 0);
    drop();
}

table drop_expired {
    actions { do_drop_expired; }
    size: 0;
}        

action set_nhop(in bit<32> nhop_ipv4, in bit<9> port) {
    routing_metadata.nhop_ipv4 = nhop_ipv4;
    standard_metadata.egress_spec = port;
    ipv4.ttl = ipv4.ttl - 1;
}

table ipv4_lpm {
    reads {
        ipv4.dstAddr : lpm;
    }
    actions {
        set_nhop;
        _drop;
    }
    size: 1024;
}

action set_dmac(in bit<48> dmac) {
    ethernet.dstAddr = dmac;
    // modify_field still valid
    // modify_field(ethernet.dstAddr, dmac);
}

table forward {
    reads {
        routing_metadata.nhop_ipv4 : exact;
    }
    actions {
        set_dmac;
        _drop;
    }
    size: 512;
}

action rewrite_mac(in bit<48> smac) {
    ethernet.srcAddr = smac;
}

table send_frame {
    reads {
        standard_metadata.egress_port: exact;
    }
    actions {
        rewrite_mac;
        _drop;
    }
    size: 256;
}

control ingress {
    if(valid(ipv4)) {
        if(ipv4.ttl > 1) {
            apply(ipv4_lpm);
            apply(forward);
        } else {
            apply(drop_expired);
        }
    }
}

control egress {
    apply(send_frame);
}

可以看到该程序中与P4v1.0不一样的地方:

一、header_type中的字段长度。
p4v1.1:

header_type ipv4_t {
    fields {
        bit<4> version;
        bit<4> ihl;
        bit<8> diffserv;
        bit<16> totalLen;
        bit<16> identification;
        bit<3> flags;
        bit<13> fragOffset;
        bit<8> ttl;
        bit<8> protocol;
        bit<16> hdrChecksum;
        bit<32> srcAddr;
        bit<32> dstAddr;
    }
}

p4v1.0:

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中

action do_drop_expired() {
    drops_register[0] = drops_register[0] + ((drops_register_enabled[0] == 1) ? (bit<32>)1 : 0);
    drop();
}

看见如下表达式:

(drops_register_enabled[0] == 1) ? (bit<32>)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的表现形式如下:

action set_dmac(dmac) {
    modify_field(ethernet.dstAddr, dmac);
}

使用元动作modify_field,将首部实例ethernet中的字段dstAddr值修改为传入的dmac参数。

而在p4v1.1中则可以直接使用=符号进行赋值运算:

action rewrite_mac(in bit<48> smac) {
    ethernet.srcAddr = smac;
}

P4v1.1语言规范中还有其他的细节差别,感兴趣的读者可以访问P4的官方网站P4.org下载P4v1.1的语言规范。

实验步骤

1.启动mininet虚拟网络拓扑:

./run_demo.sh

启动效果如下:

root@ubuntu:/home/wasdns/tutorials/p4v1_1/simple_router# ./run_demo.sh
WARNING: Token 'PPHASH' defined, but not used
WARNING: There is 1 unused token
Generating LALR tables
WARNING: 2 shift/reduce conflicts
parsing successful
semantic checking successful
Header type standard_metadata_t not byte-aligned, adding padding
WARNING:gen_json:The P4 program defines a checksum verification on field 'ipv4.hdrChecksum'; as of now bmv2 ignores all checksum verifications; checksum updates are processed correctly.
Generating json output to /home/wasdns/tutorials/p4v1_1/simple_router/simple_router.json
*** Creating network
*** Adding hosts:
h1 h2 
*** Adding switches:
s1 
*** Adding links:
(h1, s1) (h2, s1) 
*** Configuring hosts
h1 h2 
*** Starting controller

*** Starting 1 switches
s1 Starting P4 switch s1
/home/wasdns/bmv2/targets/simple_switch/simple_switch -i 1@s1-eth1 -i 2@s1-eth2 --thrift-port 9090 --nanolog ipc:///tmp/bm-0-log.ipc --device-id 0 simple_router.json
switch has been started

**********
h1
default interface: eth0 10.0.0.10   00:04:00:00:00:00
**********
**********
h2
default interface: eth0 10.0.1.10   00:04:00:00:00:01
**********
Ready !
*** Starting CLI:
mininet> 

此时交换机中没有任何表项,执行pingall显示主机h1和h2无法正常通信:

mininet> pingall
*** Ping: testing ping reachability
h1 -> X 
h2 -> X 
*** Results: 100% dropped (0/2 received)

2.添加表项,使主机h1和h2能够正常通信。

打开一个新的终端,运行脚本为交换机添加表项:

./add_entries.sh

添加表项效果如下:

root@ubuntu:/home/wasdns/tutorials/p4v1_1/simple_router# ./add_entries.sh 
Using JSON input simple_router.json
No Thrift port specified, using CLI default
Control utility for runtime P4 table manipulation
RuntimeCmd: Setting default action of drop_expired
action:              do_drop_expired
runtime data:        
RuntimeCmd: Setting default action of send_frame
action:              _drop
runtime data:        
RuntimeCmd: Setting default action of forward
action:              _drop
runtime data:        
RuntimeCmd: Setting default action of ipv4_lpm
action:              _drop
runtime data:        
RuntimeCmd: Adding entry to exact match table send_frame
match key:           EXACT-00:01
action:              rewrite_mac
runtime data:        00:aa:bb:00:00:00
Entry has been added with handle 0
RuntimeCmd: Adding entry to exact match table send_frame
match key:           EXACT-00:02
action:              rewrite_mac
runtime data:        00:aa:bb:00:00:01
Entry has been added with handle 1
RuntimeCmd: Adding entry to exact match table forward
match key:           EXACT-0a:00:00:0a
action:              set_dmac
runtime data:        00:04:00:00:00:00
Entry has been added with handle 0
RuntimeCmd: Adding entry to exact match table forward
match key:           EXACT-0a:00:01:0a
action:              set_dmac
runtime data:        00:04:00:00:00:01
Entry has been added with handle 1
RuntimeCmd: Adding entry to lpm match table ipv4_lpm
match key:           LPM-0a:00:00:0a/32
action:              set_nhop
runtime data:        0a:00:00:0a    00:01
Entry has been added with handle 0
RuntimeCmd: Adding entry to lpm match table ipv4_lpm
match key:           LPM-0a:00:01:0a/32
action:              set_nhop
runtime data:        0a:00:01:0a    00:02
Entry has been added with handle 1
RuntimeCmd: 

在mininet中验证h1和h2是否能够正常通信:

mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 
h2 -> h1 
*** Results: 0% dropped (2/2 received)

3.通过脚本启动计数,开始记录交换机丢弃的数据报数量。

./register_on_off.sh on
root@ubuntu:/home/wasdns/tutorials/p4v1_1/simple_router# ./register_on_off.sh onEnabling packet drop count
Using JSON input simple_router.json
No Thrift port specified, using CLI default
Control utility for runtime P4 table manipulation
RuntimeCmd: RuntimeCmd: 
Checking value...
Using JSON input simple_router.json
No Thrift port specified, using CLI default
Control utility for runtime P4 table manipulation
RuntimeCmd: drops_register_enabled[0]=  1
RuntimeCmd:

4.在mininet中,让h1向h2发送TTL字段值为1的数据报。

mininet> h1 ping h2 -t 1 

数据报在通过交换机时被丢弃,我们不会观察到ping的回复。

5.在另一个终端中运行脚本 read_register.sh 查看交换机丢弃的数据报信息。

./read_register.sh

885822-20170120220553734-124231127.jpg

参考

1.P4.org

2.Github/P4Lang/Tutorials https://github.com/p4lang/tutorials/tree/master/p4v1_1/simple_router

2017/1/20

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值