P4概述
P4 作为一个编程语言,有两个版本:P4_14和P4_16,P4_16 实现的模型基本等同于 PISA,叫做v1model
-
Parser:解析数据包
-
Match-Action的流水线
-
Ingress:Match-Action的流水线
-
Switching Logic:交换逻辑(此处设计有crossbar、buffer等,存放刚被上一个环节处理完,准备给下一个环节处理的包)
-
Egress:Match-Action的流水线
-
-
Deparser:把改写好的headers重新封装,然后发送出去
基础语言组件
P4程序主要由五个组件构成:Header、Parser、Table、Action和Controller
-
首部:定义报文头部格式
-
解析器:定义数据包解析流程的有限状态机
-
表:定义匹配域以及对应的执行动作
-
动作:动作指令集,包括构造查找键、根据查找键查表、执行动作等
-
流控制程序:控制程序,决定了数据包处理的流程,比如如何在不同表之间跳转等
main函数
|
|
Header
Header分为两种:一种是包头(Packet Header),一种是元数据(Metadata)
Packet Header
|
|
当收到了一个packet,要把它的Ethernet header解析出来,使用如下语句:
packet.extract(ethernetHeader);
P4定义的header中含有一个隐藏的field叫做validity:如果解析成功的话,validity 就会被自动设置为true
元数据
元数据用来携带数据和配置信息,元数据的声明与包头类似,但在实例化时不同,而且包头和元数据在字段值的约束上存在一定的差别
元数据分为两种:
-
一种是用来携带P4程序运行过程中产生的数据的用户自定义元数据(User-Defined Metadata),如首部字段的运算结果等
-
一种是固有元数据(Intrinsic Metadata),用于携带交换机自身的配置信息,如数据包进入交换机时的端口号等
有以下固定元数据
字段 | 描述 |
---|---|
ingress_port | 数据包的入端口,解析之前设置,只读 |
packet_length | 数据包的字节数,当交换机在快速转发模式下,该元数据不能在动作(action)中匹配或引用,只读 |
egress_spec | 在入端口流水线的匹配-动作过程之后设置,指定数据包出端口,可以是物理端口、逻辑端口或者多播组 |
egress_instance | 用于区分复制后数据包实例的标识符,只读 |
instance_type | 数据包实例类型:正常(Normal)、入端口复制(ingress clone)、出端口复制(egress clone)、再循环(recirculated) |
parser_sratus | 解析器解析结果,0表示无错误,其实数字代表了对应的错误类型 |
parser_error_location | 指向P4程序错误发生处 |
(该表格可能过时,以实际为准) |
注意standard_metadata
使用了struct的数据结构,该结构类似于Python中的字段,其中的成员是无序的
实例化
在首部区域定义结构后,需要进行实例化操作
|
|
Parser
==P4语言中解析器采用有限状态机的设计思路,每个解析器方法视为一种状态。==当解析器工作时,会将当前处理的数据包头字节的偏移量记录在首部实例中,并在状态迁移(调用另一个解析器)时指向包头中下一个待处理的有效字节
一个P4程序中往往定义了大量的首部和首部实例,但并不是所有的首部实例都会对数据包进行操作。解析器工作时会生成描述数据包进行哪些匹配+动作操作的中间表示,在P4中称之为==解析后表示==,这些解析后表示规定了对数据包生效的实例,是一组对数据包生效的实例的集合
parser函数的起始以start开始,以accept或reject结束。accept表示解析成功、reject表示解析失败
定义parser函数时,参数中至少有一个参数packet_in,用于表示处理的接收数据包
对于其中常用的关键字语法:
-
extract
:将目前的packet以特定的header取出来,取出来的各部分长度以header定义的为主 -
select
:类似于C语言中的switch-case语法,取决于参数通过transition关键字决定接下来的跳转方向(函数的选择操作按列表中的顺序比较字段值和左端列出的比特组对应的值,如果没有发现匹配项,则对应到default项) -
transition
:在不同的state之间切换
|
|
Match-Action
Tables
匹配方式 | 库 | 功能 | 示例 |
---|---|---|---|
exact | core.p4 | 精确匹配 | 0x01020304 |
lpm | core.p4 | 最长前缀匹配 | 0x01020304/24 |
ternary | core.p4 | 与掩码与运算 | 0x01020304 & 0x0F0F0F0F |
range | v1model.p4 | 检查是否在范围内 | 0x01020304 ~ 0x010203FF |
Actions
Action会有输入、输出的值,因此会传入带有方向性的参数
- 有向参数
- in:在一个action里是只读的,类似于Python中的函数输入值
- out:在一个action里是可以被改写的,类似于Python中的函数返回值
- inout:同时作为输入和输出值
- 无向参数:不像上面三类都是有方向性的,还有一种是从Table里查询得到的结果,这个参数不需要定义
|
|
Control Flow
控制流指:要是在Table中查询到目标,就执行一个动作。主要有三类操作:
-
使用定义好的表:
ipv4_lpm.apply()
; -
查询是否hit:
if (ipv4_lpm.apply().hit) {...} else {...}
-
查看执行的是哪一个动作:
switch (ipv4_lpm.apply().action_run) { ipv4_forward: {...} }
以网络层转发为例,一个网络包被转发的过程,可以用两个表来实现:第一个表 ipv4_lpm
用来把IP地址映射到下一跳的ID,第二个表 forward
用来把下一跳的ID映射到出口端口号
|
|
Deparser
Deparser是Parser的逆过程,使用emit
函数
|
|
附录
基本数据类型
P4不支持float与string
|
|
高级数据类型
Header
(在上文已说明)
Header stack
例如MPLS可能有多个label
|
|
Header union
例如IP header要么是v4,要么是v6
|
|
Header struct
(在上文已说明)
P4 的基本语法
P4 支持的操作类型如下:
-
算术运算(arithmetic operations):+, -, *
-
逻辑运算(logical operations):~, &, |, ^, », «
-
数列的切割(bit slicing):[m:l]
-
比特的叠加(bit concatenation):++
P4 不支持除法(division)和取余(modulo)运算
P4对于变量和常量的声明和实例化和C语言基本一致,变量不能用于维护不同网络数据包之间的状态,只能用于暂时存储数据。如果想要进行有状态的存储操作,需要用到tables或者extern objects
|
|
P4 的逻辑方面只有if-else和switch语句,并支持两个等级的终止命令:return终止当前运行的部分,exit则终止所有运行的模块
|
|