P4官方实验1. Implementing Basic Forwarding

P4官方实验1 实现基础转发

介绍

交换机要实现接下来的功能

  • 更新元和目的mac
  • 减少IP头的TTL
  • 将包转发到合适的端口

我们的P4程序使用V1Model结构写成,该结构被P4.org上的bmv2软件交换机实现。作者建议我们阅读v1model.p4文件。

我们需要使用mininet,这是斯坦福大学的一个开源项目,用于生成一个虚拟网络,包括虚拟链路、主机、交换机、路由器等,用于实验测试。

第一步:运行不完全的起始代码

在basic目录下make源代码

make run

之后使用h1 ping h2,肯定ping不通,因为我们没有对交换机进行编程,默认动作设置的是丢弃所有包。

关于控制平面的说明

转发表中的转发规则是由控制平面下发的,在这个试验中控制平面逻辑已经完成了,它们被定义在sX-runtime.json文件中,X是对应交换机的序号。

重点:我们使用P4Runtime来安装控制平面规则,sX-runtime.json文件的内容具体是指,具体的转发表名称、键、动作、就像在P4Info文件中定义的一样,P4info文件是P4编译器提供的。

第二步: 完成L3转发

我们需要补全程序中的所有TODO部分,才是完整的P4程序。

第三步:运行你的解

这一步没啥好说的,写完整个程序,再来一遍make run就行了。

如果我们写的是正确的,或者直接用了他给的solution,那么在make run之后生成一个最简单的拓扑,之后直接运行pingall可以看到是能够ping通所有连接的。

代码解析

下面是完整的程序逻辑和代码注释。

所有三反斜线之间的代码是需要我们添加的。

首先定义数据包首部HEADERS。之后是解析器PARSER,对头部进行解析和识别,对不同的协议进行不同的操作。之后是校验和验证,是一个control部件。之后是入路由处理。之后是出路由处理。之后是校验和计算。之后是去解析器。最后是将各个功能部件组合起来,形成switch。

头文件

/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>

const bit<16> TYPE_IPV4 = 0x800;

core.p4中包含必要的数据结构,v1model.p4中包含一些必要的函数和数据结构。

HEADERS 首部

/*************************************************************************
*********************** H E A D E R S  ***********************************
*************************************************************************/

typedef bit<9>  egressSpec_t;		//转发端口号定义
typedef bit<48> macAddr_t;			//48位mac地址定义
typedef bit<32> ip4Addr_t;			//32位ipv4地址定义

header ethernet_t {					//以太网包头格式
    macAddr_t dstAddr;				//目的地址
    macAddr_t srcAddr;				//源地址
    bit<16>   etherType;			//协议类型
}

header ipv4_t {						//ipv4数据包头格式
    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;
    ip4Addr_t srcAddr;
    ip4Addr_t dstAddr;
}

struct metadata {					//元数据
    /* empty */
}

struct headers {					//数据链路层和网络层协议的组合头部
    ethernet_t   ethernet;
    ipv4_t       ipv4;
}

PARSER 解析器

/*************************************************************************
*********************** P A R S E R  ***********************************
*************************************************************************/

parser MyParser(packet_in packet,
                out headers hdr,
                inout metadata meta,
                inout standard_metadata_t standard_metadata) {
	\\\
    state start {
        transition parse_ethernet;
    }

    state parse_ethernet {
        packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType) {
            TYPE_IPV4: parse_ipv4;
            default: accept;
        }
    }

    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        transition accept;
    }
	\\\
}

解析器parser可以看作是一个抽象状态机,通过在不同的state之间设置条件转移,从而对不同的协议进行解析。

需要四个参数。

  • packet_in packet:in类型的packet,表示当前传入或等待处理的包。在P4语言中in是只读类型。
  • out headers hdr:out类型的headers,即out类型的包头。标准文档中说,out类型代表一个存储引用。执行完操作后,值将被写到对应的存储位置中。
  • inout metadata meta:inout类型既是in类型又是out类型。metadata指在P4执行过程中生成的中间数据。
  • inout standard_metadata_t standard_metadata:查找v1model.p4头文件,可以看到standard_metadata_t类型是一个别名,是从P4程序到behavioral model的一个映射,将simple switch支持的所有元数据通过standard_metadata_t暴露给用户(其实我也不懂这是啥意思)。

状态转移使用transition命令,后跟状态名;transition selelct(data)相当于C语言中的switch语句,根据data中的数据选择不同的分支转移到不同的状态。

解析处理的结束状态是accept,意为对当前包头的处理结束。最终结果是hdr中存着处理完的包头。

在这里,如果包头是ipv4类型的,会被提取进hdr.ipv4。packet_in类型见core.p4。extract操作在这个结构体重的定义是"从包中读数据到一个固定尺寸的头部中"。

CHECKSUM VERIFICATION 校验和检验

/*************************************************************************
************   C H E C K S U M    V E R I F I C A T I O N   *************
*************************************************************************/

control MyVerifyChecksum(inout headers hdr, inout metadata meta) {   
    apply {  }
}

需要解释一下什么是control。control相当于C语言中的函数,它能够声明变量。创建流表,初始化外部对象等,它的功能在apply声明中定义。

此部分apply为空代码。

INGRESS PROCESSING 入路由

/*************************************************************************
**************  I N G R E S S   P R O C E S S I N G   *******************
*************************************************************************/

control MyIngress(inout headers hdr,
                  inout metadata meta,
                  inout standard_metadata_t standard_metadata) {
    action drop() {
        mark_to_drop(standard_metadata);
    }
    
    action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
    	\\\
        standard_metadata.egress_spec = port;
        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
        hdr.ethernet.dstAddr = dstAddr;
        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
    	\\\
    }
    
    table ipv4_lpm {
        key = {
            hdr.ipv4.dstAddr: lpm;
        }
        actions = {
            ipv4_forward;
            drop;
            NoAction;
        }
        size = 1024;
        default_action = drop();
    }
    
    apply {
    	\\\
        if (hdr.ipv4.isValid()) {
            ipv4_lpm.apply();
        }
        \\\
    }
}

三个参数,头部、两个元数据。

先定义了drop动作,查标准知这是标记动作结束后要丢弃的数据。

又定义了ipv4_forward动作,这个动作的过程是需要我们手写的。了解转发过程,不难知道后三行代码是啥意思:将现在的源地址赋值为现在的目的地址,现在的目的地址赋值为给出的mac地址。但是不要忘了第一句,是设置交换机的转发端口。

再看匹配动作表。可以看到,每一个动作都由action声明指出。这里定义的动作有drop、ipv4_forward。匹配表由table声明给出,这里的匹配表名称是ipv4_lpm。table中的key,设置了hdr.ipv4.dstaddr的匹配方式为lpm最长前缀匹配,其他还有exact完全匹配,ternary三元组匹配。这三种规则写在core.p4文件中。而具体的匹配动作表项,在每个switch对应的sX-runtime.json中,X是对应的交换机编号。key后面是actions,声明了匹配后要先后做的操作。size=1024不知道啥意思,最后默认操作,就是未匹配的话,就drop。

最后是这个control模块的apply,从这里执行。先用校验和检查ipv4是否有效,再应用。

EGRESS PROCESSING 出路由

/*************************************************************************
****************  E G R E S S   P R O C E S S I N G   *******************
*************************************************************************/

control MyEgress(inout headers hdr,
                 inout metadata meta,
                 inout standard_metadata_t standard_metadata) {
    apply {  }
}

出路由没有任何操作。

CHECKSUM COMPUTATION 校验和计算

/*************************************************************************
*************   C H E C K S U M    C O M P U T A T I O N   **************
*************************************************************************/

control MyComputeChecksum(inout headers  hdr, inout metadata meta) {
     apply {
	update_checksum(
	    hdr.ipv4.isValid(),
            { hdr.ipv4.version,
	      hdr.ipv4.ihl,
              hdr.ipv4.diffserv,
              hdr.ipv4.totalLen,
              hdr.ipv4.identification,
              hdr.ipv4.flags,
              hdr.ipv4.fragOffset,
              hdr.ipv4.ttl,
              hdr.ipv4.protocol,
              hdr.ipv4.srcAddr,
              hdr.ipv4.dstAddr },
            hdr.ipv4.hdrChecksum,
            HashAlgorithm.csum16);
    }
}

计算校验和是否有效,这个函数在v1model.p4中,但是我没有看懂。

DEPARSER 去解析器

/*************************************************************************
***********************  D E P A R S E R  *******************************
*************************************************************************/

control MyDeparser(packet_out packet, in headers hdr) {
    apply {
    	\\\
        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);
        \\\
    }
}

packet_out类型在core.p4中定义,其只有一个函数,就是发送hdr。注意,这个包头的发送顺序是有先后的。

SWITCH 交换机

/*************************************************************************
***********************  S W I T C H  *******************************
*************************************************************************/

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

没啥好说的,把功能模块全拼起来就行了。

上一篇:Qt+FFMPEG学习(一)视频帧转换为QImage


下一篇:使用 Packet Sender 发送TCP包