本文主要讲述一些P4-16的基本元素,以及相关基础架构,旨在帮助初学者快速上手P4-16。
P4开源项目
P4项目源码可以在github上直接获取(https://github.com/p4lang)。
项目关系
项目关系如下:
- 高级语言层:高级抽象的P4语言编写程序
- 前端编译器:对高级语言进行与目标无关的语义分析并生产中间表示
- 中间表示层:高级语言中间表示,可转换成多种其他语言
- 后端编译器:将中间表示转换为目标平台机器码
- 目标对象层:受控制硬件/软件设备
项目功能
P4项目由很多个单独的模块组成,每个模块就是一个子项目,各子项目功能介绍如下:
-
p4-hlir: 前端编译器。将P4代码转换成高级中间表示。目前,高级中间表示的展示形式与Python对象的层次结构相同。作用是: 使后端编译器不用关心语法分析和目标无关的语义检查。
-
p4c-bm:后端编译器。可以将高级语言或高级中间表示转为JSON格式或PD格式的配置文件。
-
behavivoral-model: 又称bmv2,P4软件交换机。使用C++语言编写。该模块主要实现三个目标,最重要的是simple_switch,即P4语言标准中抽象交换机模型。另外两个目标是simple_router, l2_switch
-
p4-build: 需要手动生成的基础设施库,为执行P4程序,编译安装PD库。
-
switch: switch示例,基本完成交换机的绝大部分功能。
-
p4factory: 快速开始,内含6个可快速启动的项目,包括basic_routing, copy_to_cpu, l2_switch, sai_p4, simple_router, switch
-
ntf (Network Test Framework): 网络测试框架。内含用以执行bmv2应用的网络测试用例。该框架中集成了mininet和docker。
-
ptf:Python测试框架,基于unittest框架实现,该框架中的大部分代码从floodlight项目中的OFTest框架移植而来。
-
tutorials:示例教程。包括Basic Forwarding, Basic Tunneling, P4Runtime, Explicit Congestion Notification, Multi-Hop Route Inspection, Source Routing, Calculator, Load Balancing。
-
scapy-vxlan: 扩展Vxlan和ERSPAN-like协议包头处理。
P4语言基础(P4-16)
相关术语
- PISA(Protocol-Independent Switch Architecture):协议无关交换机架构。
- P4 Target:特定的硬件实现。
- P4 Architecture:通过一组P4可编程、外部(externs)与固定组件,提供对P4 Target进行编程的接口。
P4语言与核心库由社区发展而来。外部(extern)库以及P4架构定义由设备上提供。
PISA
- Parser:根据程序员声明的数据报头,解析数据包,生产独立的数据包头与中间信息(MetaData);
- Match-Action Pipeline:根据解析的报头与元信息,匹配流表,对报头进行修改、添加、删除等操作;
- Deparser:数据包重新被序列化。
P4架构
- V1Model:For 芯片。 P4 v1.0 switch model实现源码,公开属性总结连接。
- SImpleSumeSwitch:For FPGA开发板。一个为NetFPGA SUME开发办定义的P4架构,simpe-sume-switch,NetFPGA Sume;
- PSA:For everithing。参考实现源码,手册文档
除了V1Model,目前还有Portable Switch Architecture和Tofino native等架构,目标是tofino native的架构比v1model有更多的功能,PSA的架构可以被更多的target (FPGA,ASIC,Software)所支持,提供从V1model到其他两个架构的translation。所以可以按照v1model的架构来写程序,之后compiler会帮助把v1model的程序转换成其他的架构的程序。
P4-16语言元素
- Parser:通过一系列状态(State)转换,提取报头字段,与一些元数据;
- Control:Tables, Actions, control flow声明;
- Expressions:基本的操作与运算符;
- Data Type:BiStrings,header, Struct,array等数据类型。
- Architecture Description:目标硬件提供的可编程的组件与接口;
- Extern Libraries:目标硬件支持的特定组件。
为一个目标硬件编写P4程序
整个过程由几部分组成,如下:
- 用户提供: P4程序与控制面;
- 厂家提供:P4架构模型与目标硬件;
- 运行时操作:控制流表,外部控制,数据包上报控制面等。
数据类型
基础类型
bit<n>
: n位的无符号整型(bitstring);bit
: 等同于bit<1>
;int<n>
:n(n >=2)位的有符号整型;varbit<n>
:最大为n位的变长bitstring。
示例:
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
typedef 定义类型别名
设置类型别名,如:
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
header 类型
header类型为有序的程序集合,特点如下:
- 可以包含
bit<n>、int<n>、varbit<n>
基础数据类型; - 必须字节对齐;
- 可以是valid或invalid,并提供几种操作测试或设置有效位:
isValid()/setValid()/setInvalid()
示例:
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}
header ipv4_t {
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 类型
struct类型为无序数据类型集合,且不需字节对齐。可用于元数据的组织。
示例:
/* Architecture */
struct standard_metadata_t {
bit<9> ingress_port;
bit<9> egress_spec;
bit<9> egress_port;
bit<32> clone_spec;
bit<32> instance_type;
bit<1> drop;
bit<16> recirculate_port;
bit<32> packet_length;
...
}
/* User program */
struct metadata {
...
}
parser
类似于C语言的function声明关键字,可以有循环。通过一系列状态(state)执行与转换,提取报头字段与元数据。
示例如下:
/* From core.p4 */
extern packet_in {
void extract<T>(out T hdr);
void extract<T>(out T variableSizeHeader,
in bit<32> variableFieldSizeInBits);
T lookahead<T>();
void advance(in bit<32> sizeInBits);
bit<32> length();
}
/* Architecture */
struct standard_metadata_t {
bit<9> ingress_port;
bit<9> egress_spec;
bit<9> egress_port;
bit<32> clone_spec;
bit<32> instance_type;
bit<1> drop;
bit<16> recirculate_port;
bit<32> packet_length;
...
}
/* User program */
struct metadata_t {
...
}
struct headers_t {
ethernet_t ethernet;
ipv4_t ipv4;
}
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t std_meta) {
state start {
packet.extract(hdr.ethernet);
transition accept;
}
}
parser的输入与输出
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t std_meta) {...}
parser
的输入与输出模型如下:
- parser输入:
packet_in
类型数据包,是由目标设备提供的extern
类型数据。metadata
: 用户自定义元数据;standard_metadata_t
:**设备产生提供的元数据。
- parser输出:
header
:用户定义的数据报头;metadata
: 用户自定义元数据;standard_metadata_t
:设备产生提供的元数据。
注意extern packet_in
提供几个接口,说明如下:
void extract<T>(out T hdr)
: 从数据报文指针开始位置,抽取T
类型数据大小的报头,存储在hdr
中,并将数据报文指针前移。可能触发PacketTooShort or StackOutOfBounds
错误,T
类型的数据的大小必须为固定值**。void extract<T>(out T variableSizeHeader, in bit<32> variableFieldSizeInBits)
:读取数据报头到可变大小的报头变量variableSizeHeader
中,variableSizeHeader
必须包含一个及以上varbit
字段。数据报指针前移。T lookahead<T>()
:读取T
类型数据大小的报头,但不前移数据报指针。void advance(in bit<32> sizeInBits)
:前移数据报头指针。bit<32> length()
:返回数据报的字节数。
parser的状态与转换
状态
parser
有三种预设状态state
,为:
start
:进入parser
功能块后的第一个状态,自动进入;accept
:若进入accept
状态,则表示数据报进入后续control流程;reject
:数据包被抛弃。
其他states
,用户可以自行定义。每个state
执行0次或以上,然后转换为其他state
。(允许循环)
转换
- 使用
transition
关键字使parser
在状态之间转换。
示例:
parser MyParser(...) {
state start {
packet.extract(hdr.ethernet);
transition accept;
}
}
- 使用
transition select(data){...}
根据data
的值转换到不同状态。
select
声明类似C语言中的switch...case
,但是其没有fall-through
行为,不用break
进行中断后续选择。
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hrd.ethernet.etherType) {
0x800: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
value_set
只能用在parser 语句块中。
emit
pkt.emit 不支持条件emit,mirror.emit支持单bit的条件emit
header stacks
头堆栈有两个属性 next
与 last
,其可以在解析器中解析时使用。
举例如下,使用如下mpls
定义表示10个MPLS头:
header Mpls_h {
bit<20> label;
bit<3> tc;
bit bos;
bit<8> ttl;
}
Mpls_h[10] mpls;
mpls.next
表示 mpls 堆栈中的一个元素的值。初始时,mpls.next
指向堆栈的第一个元素,当成功调用extract
方法后,mpls.next
将自动向前偏移,指向下一个元素。
mpls.last
指向 next 前面的那个元素(如果元素存在),即最近 extract 出来的那个元素。
注意:通过 next 或 last 属性访问头堆栈中不存在的元素将引发 transition reject 状态转换,并设置错误到 error.StackOutOfBounds
struct Pkthdr {
Ethernet_h ethernet;
Mpls_h[3] mpls;
// other headers omitted
}
parser P(packet_in b, out Pkthdr p) {
state start {
b.extract(p.ethernet);
transition select(p.ethernet.etherType) {
0x8847: parse_mpls;
0x0800: parse_ipv4;
}
}
state parse_mpls {
b.extract(p.mpls.next);
transition select(p.mpls.last.bos) {
0: parse_mpls; // This creates a loop
1: parse_ipv4;
}
}
// other states omitted
}
control
对于固定硬件,SwitchIngress 控制流程的参数固定,如下:
control SwitchIngress(
inout header_t hdr,
inout metadata_t ig_md,
in ingress_intrinsic_metadata_t ig_intr_md,
in ingress_intrinsic_metadata_from_parser_t ig_prsr_md,
inout ingress_intrinsic_metadata_for_deparser_t ig_dprsr_md,
inout ingress_intrinsic_metadata_for_tm_t ig_tm_md) {
...
}
- 类似于C语言的function声明关键字,不能有循环。
- if 语句中表达式
<, >
两侧只能有一个变量,==
两侧可以都为变量,但判断条件产生的PHV不能超过 44 bits,否则报如下错误
if (eg_md.lkp.outer_dst_mac == eg_md.lkp.outer_src_mac) {
/home/lihaifeng/test/casteni-gw/p4src/casteni_gw.p4(314): error: : condition too complex, limit of 4 bytes + 12 bits of PHV input exceeded
/home/lihaifeng/test/casteni-gw/p4src/casteni_gw.p4(314): [--Werror=legacy] error: condition too complex, one operand of > must be constant
- 在control中,可以申明变量、创建tables,以及实例化
externs
等。 - 在control中,通过
apply
声明功能; - 代表可表达为DAG的所有类型的处理,包括:
- Match-Action Pipelines;
- Deparsers;
- 其他的数据报处理,如更新checksums。
- 由用户与特定架构指定的接口,如headers和metadata。
示例:
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t std_meta) {
/* Declarations region */
bit<48> tmp;
apply {
/* Control Flow */
tmp = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = hdr.ethernet.srcAddr;
hdr.ethernet.srcAddr = tmp;
std_meta.egress_spec = std_meta.ingress_port;
}
}
上述程%
Was this helpful?
3 / 0