DPDK上的P4应用实例

科技   科技   2023-06-21 12:00   上海  

随着P4编程语言的日益普及,由于其灵活的可编程性,使其在软件定义网络中占据一席之地。

本文主要简介如何用P4写一个简单的vxlan转发并在DPDK上运行,涉及到P4 代码的基本模板,DPDK backend的P4编译方法以及DPDK pipeline example用例三个部分。


一、如何编写一个P4 pipeline

P4是由斯坦福大学的Nick Mckeown教授提出, 最新的规范为2016年发布的P4_16, 随着规范一起发布的还有PSA体系架构(P4~16~ Portable Switch Architecture (PSA))。PSA定义了一个类型库,externs以及一系列的包路径。PSA和P4_16语言的关系类似于C语言标准库和C语言的关系。

图1      

图1展示了一个基于PSA架构实现的数据路径,分别是:

    1. ingress parser: 检查数据包头,识别每个包里都有什么,每个字节表示什么

    2. ingress: 一系列match-action的pipeline,检查是否有某些字节符合某个定义表项的关键字, 然后执行该关键字对应的action

    3. Ingress deparser: 重组包头和数据格式,然后将报文送出

    4. egress parser: 类似Ingress parser

    5. egress: 类似Ingress

    6. egress deparser: 类似Ingress deparser packet buffer: 用来存放上一个环节处理完的包

其中1-6为可编程模块,参照这个结构,我们可以实现一个简单的vxlan 报文转发。

/*引用必要的文件,可以直接调用一些已经写好的库函数*/

#include<core.p4>

#include<psa.p4>


/*定义相关协议的结构体*/

struct empty_metadata_t {

}

 

typedef bit<48> ethernet_addr_t;

 

header ethernet_t {

    ethernet_addr_t dst_addr;

    ethernet_addr_t src_addr;

    bit<16>         ether_type;

}


header ipv4_t {

    bit<8> ver_ihl;

    bit<8> diffserv;

    bit<16> total_len;

    bit<16> identification;

    bit<16> flags_offset;

    bit<8> ttl;

    bit<8> protocol;

    bit<16> hdr_checksum;

    bit<32> src_addr;

    bit<32> dst_addr;

}

 

header udp_t {

    bit<16> src_port;

    bit<16> dst_port;

    bit<16> length;

    bit<16> checksum;

}

 

header vxlan_t {

    bit<8> flags;

    bit<24> reserved;

    bit<24> vni;

    bit<8> reserved2;

}


/*在这个示例中,我们需要定义一个完整的vxlan报文的包头格式*/

struct headers_t {

    ethernet_t ethernet;

    ipv4_t ipv4;

    vxlan_t vxlan;

    ethernet_t outer_ethernet;

    ipv4_t outer_ipv4;

    udp_t outer_udp;

    vxlan_t outer_vxlan;

}

 

struct local_metadata_t {

    ethernet_addr_t  dst_addr;

    ethernet_addr_t  src_addr;

}


/*ingress parser解析进入pipeline的报文,在这里剥离了mac和ipv4的包头,所有ipv4的报文可以进入下一个过程*/

parser packet_parser(packet_in packet, out headers_t headers, inout local_metadata_t local_metadata, in psa_ingress_parser_input_metadata_t standard_metadata, in empty_metadata_t resub_meta, in empty_metadata_t recirc_meta) {

    state start {

        transition parse_ethernet;

    }

    state parse_ethernet {

        packet.extract(headers.ethernet);

        transition parser ipv4;

    }

    state parser_ipv4 {

        packet.extract(headers.ipv4);

        transition accept;

    }

}


/*ingress match-action实现了一个名为vxlan的table,当进入pipeline的报文目的MAC地址符合规则时,给它打包对应参数的vxlan包头,不命中的报文则直接丢弃*/

control ingress(inout headers_t headers, inout local_metadata_t local_metadata1, in psa_ingress_input_metadata_t standard_metadata, inout psa_ingress_output_metadata_t ostd) {

    InternetChecksum() csum;

    action vxlan_encap(

        bit<48> ethernet_dst_addr,

        bit<48> ethernet_src_addr,

        bit<16> ethernet_ether_type,

        bit<8> ipv4_ver_ihl,

        bit<8> ipv4_diffserv,

        bit<16> ipv4_total_len,

        bit<16> ipv4_identification,

        bit<16> ipv4_flags_offset,

        bit<8> ipv4_ttl,

        bit<8> ipv4_protocol,

        bit<16> ipv4_hdr_checksum,

        bit<32> ipv4_src_addr,

        bit<32> ipv4_dst_addr,

        bit<16> udp_src_port,

        bit<16> udp_dst_port,

        bit<16> udp_length,

        bit<16> udp_checksum,

        bit<8> vxlan_flags,

        bit<24> vxlan_reserved,

        bit<24> vxlan_vni,

        bit<8> vxlan_reserved2,

        bit<32> port_out

    ) {

        headers.outer_ethernet.src_addr = ethernet_src_addr;

        headers.outer_ethernet.dst_addr = ethernet_dst_addr;

 

        headers.outer_ethernet.ether_type = ethernet_ether_type;

        headers.outer_ipv4.ver_ihl = ipv4_ver_ihl;

        headers.outer_ipv4.diffserv = ipv4_diffserv;

        headers.outer_ipv4.total_len = ipv4_total_len;

        headers.outer_ipv4.identification = ipv4_identification;

        headers.outer_ipv4.flags_offset = ipv4_flags_offset;

        headers.outer_ipv4.ttl = ipv4_ttl;

        headers.outer_ipv4.protocol = ipv4_protocol;

        headers.outer_ipv4.hdr_checksum = ipv4_hdr_checksum;

        headers.outer_ipv4.src_addr = ipv4_src_addr;

        headers.outer_ipv4.dst_addr = ipv4_dst_addr;

        headers.outer_udp.src_port = udp_src_port;

        headers.outer_udp.dst_port = udp_dst_port;

        headers.outer_udp.length = udp_length;

        headers.outer_udp.checksum = udp_checksum;

        headers.vxlan.flags = vxlan_flags;

        headers.vxlan.reserved = vxlan_reserved;

        headers.vxlan.vni = vxlan_vni;

        headers.vxlan.reserved2 = vxlan_reserved2;

        ostd.egress_port = (PortId_t)port_out;

        csum.add({headers.outer_ipv4.hdr_checksum, headers.ipv4.total_len});

        headers.outer_ipv4.hdr_checksum = csum.get();

        headers.outer_ipv4.total_len = headers.outer_ipv4.total_len + headers.ipv4.total_len;

        headers.outer_udp.length = headers.outer_udp.length + headers.ipv4.total_len;

    }

    action drop(){

        ostd.egress_port = (PortId_t)4;

    }

    table vxlan {

        key = {

            headers.ethernet.dst_addr: exact;

        }

        actions = {

            vxlan_encap;

            drop;

        }

        const default_action = drop;

        size =  1024 * 1024;

    }

 

    apply {

        vxlan.apply();

    }

}


/* ingress deparser将剥离并处理过后的包头重新封装*/

control packet_deparser(packet_out packet, out empty_metadata_t clone_i2e_meta, out empty_metadata_t resubmit_meta, out empty_metadata_t normal_meta, inout headers_t headers, in local_metadata_t local_metadata, in psa_ingress_output_metadata_t istd) {

    apply {

        packet.emit(headers.outer_ethernet);

        packet.emit(headers.outer_ipv4);

        packet.emit(headers.outer_udp);

        packet.emit(headers.outer_vxlan);

        packet.emit(headers.ethernet);

        packet.emit(headers.ipv4);

    }

}

 

/*目前DPDK后端只支持ingress模块,所以这里的egress没有定义match-action,直接让报文pass-through*/

parser egress_parser(packet_in buffer, out headers_t headers, inout local_metadata_t local_metadata, in psa_egress_parser_input_metadata_t istd, in empty_metadata_t normal_meta, in empty_metadata_t  clone_i2e_meta, in empty_metadata_t clone_e2e_meta) {

    state start {

        transition accept;

    }

}

 

control egress(inout headers_t headers, inout local_metadata_t local_metadata, in psa_egress_input_metadata_t istd, inout  psa_egress_output_metadata_t ostd) {

    apply {

    }

}

 

control egress_deparser(packet_out packet, out empty_metadata_t clone_e2e_meta, out empty_metadata_t recirculate_meta, inout headers_t headers, in local_metadata_t local_metadata, in psa_egress_output_metadata_t istd, in psa_egress_deparser_input_metadata_t edstd) {

    apply {

    }

}

 

/*main函数的定义,即整个pipeline的运行流程*/

IngressPipeline(packet_parser(), ingress(), packet_deparser()) ip;

EgressPipeline(egress_parser(), egress(), egress_deparser()) ep;

PSA_Switch(ip, PacketReplicationEngine(), ep,  BufferingQueueingEngine()) main;


二、编译器P4c

P4c是P4语言的编译器,能够支持P4-14和P4-16标准,将P4源代码通过不同的后端编译器生成适用不同平台的代码。针对DPDK的编译后端名为p4c-dpdk backend, 将符合P4-16规范的P4代码编译成能够配置DPDK SWX pipeline的 .spec文件。关于pipeline example的使用,下一章节会讲到。P4c-dpdk编译器目前能支持PSA和PNA两种架构。那么如何编译我们刚才实现的p4文件?很简单,一条命令就够了,

p4c-dpdk --arch psa vxlan.p4 -o vxlan.spec

第一个参数为代码使用的架构,第二个参数即为源p4文件,编译后的输出名为vxlan.spec。


三、DPDK pipeline 

为了将DPDK的高性能与P4语言的灵活性相结合,DPDK提供了一套Software Switch (SWX) API,它们可以被调用来实现完整的软件交换机或者数据面应用程序。DPDK基于SWX API实现了一个名为pipeline的sample application,通过这个application,DPDK可以直接运行p4c编译出来的spec文件。

以上一章节编译生成的vxlan.spec为例,想要在DPDK实现我们所期望的vxlan报文转发规则,我们还需要配置以下三个文件:

1.ethdev.io: 端口配置文件,包括了BDF,收发包队列号,burst size等。

2.vxlan_table.txt: 规则配置文件,适配前文中我们实现的vxlan_encap table,给命中不同mac地址的报文打包不同的vxlan包头。

3.vxlan.cli: 定义了pipeline的command顺序以及引用文件路径,包括使用的spec文件,端口配置文件和规则配置文件等。

三个文件都配置好之后,调用Pipeline example的启动参数即可运行。

Dpdk-pipeline[EAL_ARGS] – [-s SCRIPT_FILE] [-h HOST] [-p PORT]

以下是运行示例:

看到pipeline enable的字样,此时在其中一个端口发送可以命中规则的报文时,就可以观察到对应端口上可收到按照所定规则封装的报文。

以上就是基于P4和DPDK pipeline example实现的简单vxlan转发全部过程。


更多内容请参考链接:

1. PSA架构及语法

https://p4.org/p4-spec/docs/PSA-v1.1.0.html

2. P4C源码

https://github.com/p4lang/p4c/tree/main/backends/dpdk

3. DPDK pipeline example user guide

https://doc.dpdk.org/guides/sample_app_ug/pipeline.html?highlight=pipeline

转载须知

DPDK与SPDK开源社区

公众号文章转载声明

推荐阅读

利用DPU/IPU 卸载容器镜像以及文件系统的相关操作

Intel® 以太网800 系列网络适配器 – DPDK上的性能演进

点点“赞”“在看”,给我充点儿电吧~

DPDK与SPDK开源社区
最权威,最贴近DPDK/SPDK技术专家的社区。
 最新文章