随着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
转载须知
推荐阅读
点点“赞”和“在看”,给我充点儿电吧~