• 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 {
         32;
        static: drop_expired;
        instance_count: 16;
    }
    
    register drops_register_enabled {
         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
    

    参考

    1.P4.org

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

    2017/1/20

  • 相关阅读:
    Flask目录结构
    RHSA-2019:1880-低危: curl 安全和BUG修复更新 及 RHSA-2019:1884-中危: libssh2 安全更新
    ELK+Logback进行业务日志分析查看
    Maven编译过程中出现的问题
    Zabbix监控服务器磁盘I/O
    创建readonly只读用户脚本
    Zabbix监控多个JVM进程
    redis命令
    docker配置Nginx
    docker基本命令
  • 原文地址:https://www.cnblogs.com/qq952693358/p/6329493.html
Copyright © 2020-2023  润新知