P4语言基础组件
P4语言的基础语言组件
本文主要引自
http://www.sdnlab.com/17882.html
一、基础语言组件
首部(Headers)
首部类型是由成员字段组成的列表,每个字段都有对应的名称和长度,每一种首部类型都有对应的首部实例来存储具体数据,有点类似C语言的结构体。
首部分为两种包头、元数据
包头
包头可以用来描述数据包的结构,以IPV4为例子

用P4语言可以这样表示:
//包头类型声明
header_type ipv4_t{
fields{
version:4;
ihl:4;
diffserv:8;
totalLen:16;
identification:16;
flags:3;
fragOffset:13;
ttl:8;
protocol:4;
hdrChecksum:16;
srcAddr:32;
dstADDr:32;
options:*;//字段长度不确定
}
length:(ihl<<2); //根据ihl计算值
max_length:60;
}
//包头实例化
header_ipv4_t ipv4;
“包头“与”报头“区别
包头指的是P4语言中的术语;
报头值得是数据包的报文头部。
元数据
元数据用来携带数据和配置信息,元数据的声明与包头类似,但实例化不同,包头和元数据在字段值的约束上存在一定的区别。
元数据分为两种
- 一种是用来携带P4程序运行过程中产生的数据的用户自定义元数据(User-Defined Metadata),如首部字段的运算结果等。
- 一种是固有元数据(Intrinsic Metadata),用于携带配置交换机自身的配置信息,如数据包进入交换机的端口号。
//生命入端口元数据类型
header_type ingress_metadata_t{
fields{
/* 输入 ×/
ingress_port:9;//在解析前有效
packet_length:16;
instance_type:2;//普通、克隆、再循环等
ingress_global_tstamp:48;
parser_status:8;//解析错误
/*入口流水线输出*/
egress_spec:16;
queue_id:9;
}
}
//实例化元数据
metadata ingress_metadata_t ingress_metadata;
8种固有元数据
| 字段 | 描述 |
|---|---|
| ingress_port | 数据包的入端口,解析之前设置。只读 |
| packet_length | 数据包的字节数,当交换机在快速转发模式下,g该元数据不能在动作中匹配或引用。只读 |
| egress_spec | 在入端口流水线的匹配-动作过程之后设置,指定数据包的出端口,可以是物理端口、逻辑端口或者多播组 |
| egress_port | 指定数据包的物理出端口,区别于egress_spec,只能用于物理端口。只读 |
| egress_instance | 用于区分复制后数据包实例的标识符。只读 |
| instance_type | 数据包实例类型:正常(Normal)、入端口复制(ingress clone)、出端口复制(egress clone)、再循环(recirculated) |
| parser_status | 解析器解析结果,0表示无错误,其实数字代表了对应的错误类型 |
| parser_error_location | 指向P4程序错误发生处 |
在P4语言中定义首部类型有几点需要注意
- 包头类型的长度需要与字节对齐,即长度必须是8bit的整数倍
- 包头中字段长度可以是可变值(该特性在P4语言规范中规定,但当前编译器版本并未实现,后续版本会支持)也可以是首部中其他字段值计算后的值。而元数据中的字段长度只能是定值。
- 只有包头能实例化成数据,元数据不行。
- 实例化时,首部中已定义名称的字段的值会被初始化程序中的指定值,如果首部中只定义了字段名称而未指定值,字段的值将会被初始化成0.
解析器
一个P4程序定义了许多的首部和首部实例,但并不是所有的首部实例都会对数据包进行操作。
解析器工作时会生成 描述数据包进行哪些匹配 +动作操作的中间表示(intermediate Representation),在P4中称之为解析后表示(Parsed Representation),这些解析后表示规定了对数据包生效的实例,是一组对数据包生效的实例的集合。
(一个理解,生成两个部分,一个用于以后match 一个用于以后的Action 只是Action部分生成的是中间表示,感觉有点类似于运行时API,方便上层的应用,让上层屏蔽底层细节。)
P4语言中解析器采用有限状态机的设计思路,每个解析器方法视为一种状态。当解析器工作时,会将当前处理的数据包头字节的偏移量记录在首部实例中,并在状态迁移(调用另一个解析器)时指向包头中的下一个待处理的有效字节。(有点类似传统wangluo解包的过程)
以以太网帧的解析器为例子,用数据包类型代对应解析器,将每个解析器作为一种状态,用箭头表示状态迁移,则可以构建出如下图的 以太网帧的解析器的状态迁移图。

以太网帧和IPV4数据包解析器定义示例
//解析开始
parser start{
return parse_ethernet;
}
//以太网解析逻辑
parser parse_ethernet{
extract(ethernet)
return switch(latest.ethertype){
//latest代表最近提取的首部实例,这里指ethernet的实例
case 0x8100:parse_vlan;
case 0x0800:parse_ipv4;
//other case;
}
}
//ipv4包头解析逻辑
parser parse_ipv4{
extract(ipv4);
return select(latest.protocol){
IP_PROTOCOLS_IPHL_ICMP:parse_icmp;
IP_PROTOCOLS_IPHL_TCP:parse_tcp;
IP_PROTOCOLS_IPHL_UPD:parse_udp;
IP_PROTOCOLS_IPHL_GRE:parse_gre;
IP_PROTOCOLS_IPHL_IPV4:parse_ipv4_in_ip;
IP_PROTOCOLS_IPHL_IPV6:parse_ipv6_in_ip;
//其他情况
default:ingress;
}
}
个人感觉这个编译过不了,因为不了解swtich 以及 select的用法,switch是C语言的基础
一个解析方法/状态,可以以下面四种方式结束:
- return 一个流控制程序名
- return 一个解析器名
- 发生显式错误
- 发生隐式错误
P4语言中流控制程序和解析器的命名空间是共用的,所以在定义解析器和流控制程序的时候需要注意不能重名。变量前面最好要 像这种形式 parse_XXX.
动作
P4语言动作也是分为两种,基本动作(Primitive Actions)和复合动作(Compound Actions).基本动作包括:数据包处理运算符(如添加、删除、修改包头)、基本的算数运算符、哈希运算符和统计跟踪运算符(如计量、测量)。复合动作由基本动作组合而成,由用户自行定义。表8中展示了P4中定义的基本动作。
| 动作 | 描述 |
|---|---|
| no_op | 占位符动作,不做任何操作 |
| drop | 在入口流水线中将数据包丢弃 |
| modify_field | 修改解析后表示中的包头字段值 |
| modify_field_with_hash_based_index | 使用字段列表索引计算一个值并使用该值生成偏移量 |
| add_header | 为数据包的解析后表示添加包头 |
| remove_header | 为数据包的解析后表示删除包头 |
| copy_header | 复制首部实例 |
| push | 将所有首部实例亚茹一个数据,并在顶部添加一个新首部 |
| pop | 在实例数据顶部的元素淡出,后续元素向顶部移位 |
| count | 更新计数器 |
| meter | 执行计量操作 |
| generate_digest | 生成一个保温摘要并发送到接收机 |
| truncate | 在出口处截断数据包 |
| resubmit | 将原始数据包和元数据重新发送给解析器 |
| Recirculate | 在数据包完成出口修改操作后重新发送 |
| clone_ingress_pkt_to_ingress | 复制原始数据包并发送到解析器 |
| clone_egress_pkt_to_ingress | 复制出口数据包并发送到解析器 |
| clone_ingress_pkt_to_egress | 复制原始数据包并发送到缓存区 |
| clone_egress_pkt_to_egress | 复制出口数据包并发送到缓存区 |
这些动作高度抽象且协议无关,以实现P4语言处理数据的协议无关性。同时,复杂的操作以及流程可以通过组合的不同基本操作完成,从而保障完善了P4语言对各种协议的支持以及扩展性。
下面给出复合动作定义的示例
//定义IPV4路由动作
action route_ipv4(dst_port,dst_mac,src_mac,vid){
//复合动作由多个基本动作复合
modify_field(standard_metadata.egress_spec,dst_port); //修改元数据中的
//出端口
modify_field(ethernet.dst_addr,dst_mac);//修改目的mac
modify_field(vlan_tag.vid,vid);//修改vlan的vid
modify_field(ipv4.ttl,-1); //使ttl-1
}
匹配-动作表
p4语言中的匹配-动作表定义了匹配字段、动作及一些相关属性(如表容量),当匹配-动作表中定义的字段与数据包匹配成功时,则执行对应的动作;若不匹配则标记为"失配"(miss),并执行默认操作。看个定义
#define IPV4_LPM_TABLE_SIZE 65536
table ipv4_fib_lpm{
reads{
metadata.vrf:exact;
ipv4:dstAddr:lpm;
}
actions{
on_miss;
fib_hit_nexthop;
}
size:IPV4_LPM_TABLE_SIZEL;
}
action on_miss(){
//noop
}
action fib_hit_nexthop(){
modify_field(metadata.fib_hit,TRUE);
modify_field(metadata.fib_nexthop,nexthop_index);
modify_field(ipv4.ttl,-1);
}
//一个类似与路由表匹配,匹配对应执行对应的操作
P4语言的匹配-动作表支持多种匹配类型,如精确匹配、最长前缀匹配、范围匹配等。如表9所示,展示了动作-匹配表支持的匹配类型。
| 匹配类型 | 描述 |
|---|---|
| excat | 精确匹配 |
| ternary | 三重匹配,动作-匹配表的每一个表项都有一个掩码,将掩码和字段值进行逻辑与运算,在执行匹配,为了避免导致多条表项匹配成功,每条表项都需要设定一个优先级 |
| lpm | 这是三重匹配的一种特殊情况,当多个表项匹配成功时,选择掩码最长的最高优先级的进行匹配 |
| index | 字段值作为表项索引 |
| range | 表项中确定一个范围,字段值在此范围内都能成功匹配 |
| valid | 仅用于包头字段匹配,表项值只能为true/false |
流控制程序
p4语言中匹配-动作表中规定需要匹配的字段和需要执行的操作,流控制程序则用来规定匹配-动作表的执行顺序。
以P4语言定义二层转发流程为例子,数据包首先进行L2转发表(l2_fwd)匹配,然后根据数据包的以太网目的地址是否匹配路由器自身的mac地址(通过查找所属的router_mac表)决定是否要经过l3路由表(ipv4_fib_lpm和ipv6_fib_lpm),在根据包头类型(IPV4或IPV6),数据包匹配不同的L3路由表,最后通过访问控制列表来控制数据包是否通过。
control ingress{
//二层转发
apply(l2_fwd);
//三层路由
apply(router_mac){
hit{
if(valid(ipv4)){
apply(ipv4_fib_lpm);
}else{
if(valid(ipv6)){
apply(ipv6_fib_lpm);
}
}
}
//ACL
apply(acl);//控制列表
}
}
至此了解所有的P4语言的基础语言组件,并了解他们是用来干什么的。
首先定义一个首部,在来用解析器去读对应的包,然后产生解析后表示,这些解析后表示在流控制程序的控制下按一定的顺序方法去匹配 “匹配动作表”,然后执行相应的动作。
浙公网安备 33010602011771号