p4 parser
p4 parser
引言介绍
p4 parser 可以看做是一个状态机。
这个可以被称作是一个解析图,解析图的每一个节点可以看成一个状态,状态间的转换可以看成是边。
下列展示了一个非常简单的例子关于mTag解析包的解析器内容的部分解析函数。
parser ethernet {
extract(ethernet); // Start with the ethernet header
return select(latest.ethertype) {
0x8100: vlan;
0x800: ipv4;
default: ingress;
}
}
parser vlan {
extract(vlan);
return select(latest.ethertype) {
0xaaaa: mtag;0x800: ipv4;
default: ingress;
}
}
parser mtag {
extract(mtag);
return select(latest.ethertype) {
0x800: ipv4;
default: ingress;
}
}
该图为为上述代码的状态图表示

上面这个示例代码,可以看到每一个解析器都明确定义了识别一个首部。P4会支持这个方法,但是没有强行规定要这么做。
真正P4的状态解析器状态节点可以分为两种:
- 一种是指示用于决定进行状态转移的选择节点,这个节点没有必要明确去识别某个首部。
- 另一种是可以识别某个首部,并且可以识别首部,并进行相应操作的状态节点,识别的首部可以是一个也可以是多个。
解析后表示(Parsed Representation)
什么是解析后表示?
由解析器针对于数据包所生成的一种表示,目的是为后面的match+action服务,这个叫做这个数据包的解析后表示,一个合法的首部实例的集合(包头实例+首部实例)。
在match+action的过程中,会更新这些首部实例,也就是解析后表示的某些值。

注意:
- 在这个无论是解析或者match+action的过程中,原始的数据包会被保存起来,始终不变,为了后面有可能出现的clone动作。
解析器操作(Parser Operation)
解析器是根据packet的第一个字节进行调控。他保存了一个指针,叫做current offset,指向了该数据包包头的某个特殊字节位置。
当他开始提取首部实例的时候,他会根据这个实例化的首部实例大小进行提取,把数据包的数据更新到该首部实例中,更新该数据包的解析后表示。然后解析器的current offset指针在移向下一个要处理的位置,做一次状态转移。
为方便理解可看下图:


如上图所示:
extract()函数功能就如上图的step two功能,值得注意的:
-
extract()函数只能针对包头实例进行操作,而不能通过元数据实例进行操作。
-
对应要被提取的包头实例,在extract()函数执行的时候也要提前实例化出来。
元数据也会对状态转移的决定有影响。例如,一个数据包进来,会通过元数据(ingress port)判断要进入的初始解析节点是哪一个。同样的,那些clone或者再循环的数据包,也是通过元数据判断要从那个节点开始解析。
在P4中每一个状态被表示一个解析函数。一个解析函数也许存在在下列四种方式。
- return到那另外一个被定义的解析函数。
return ipv4; //ipv4为已定义的解析函数名 - return一个被定义流控制函数。
return ingress; //ingress为已定义的流控制程序 - 通过关键字
parse_error说明错误发生后,要执行什么情况。 //这个由程序员自己定义 - 在没有
parse_error发生错误的情况,下面异常处理会介绍到。
值得注意的是因为前面两点,解析函数名,控制函数名共享在同一个命名空间。如果两个像这样的函数重名,这个编译器发出错误。
下面是一个大体解析过程的图(针对上面3个部分):
****
value sets
什么是value sets
有一些值是用来确定状态转移时,从一个状态转向另一个状态节点的。这些为了状态转移的集合叫做value sets。这个集合中,仅仅只有包含值,没有包含其他信息。这个集合的值是由运行时API去操作,加入或者删除。
为什么要有value sets
例子,MPLS标签域的值可以用来确定哪种包头是否遵从MPLS tag,然后根据这个结果去执行相应的状态转移工作。为了支持这个功能,P4支持了value sets这个概念。
全部的值在一个集合中中必须服务于同一种转移。
例如,全部的MPLS标签必须符合一个IPV4的转移将会存在在一个集合,同样的全部的MPLS标签符合全部IPV6转移将会存在在另一个value set。

定义方式
parser_value_set value_set_name;
注意点:
- value set被在p4项目的最高级的水平,在解析函数外。(可以看成是全局变量)。
- 如果有使用到他们的话,他们应该被在之前被声明,
- value的位宽被推断,从这个集合被使用到的地方。如果这个集合被使用在多个地方,他们对该集合里的某个值使用了用了不同的宽度,这个时候编译器会报错。
- 用于更新解析器值集的运行时API,必须支持同时定义值集中的数值和掩码对的情况。
实际P4程序的使用情况
parser parser_name {
return select(latest.field_name) {
value_set_name: xxx; //引用了名为value_set_name的值集
}
}
parser function BNF
arser_function_declaration ::=
parser parser_state_name { parser_function_body }
parser_function_body ::=
extract_or_set_statement*
return_statement
extract_or_set_statement ::= extract_statement | set_statement
extract_statement ::= extract ( header_extract_ref );
header_extract_ref ::=
instance_name |
instance_name "[" header_extract_index "]"
header_extract_index ::= const_value | next
set_statement ::= set_metadata ( field_ref, metadata_expr ) ;
metadata_expr ::= field_value | field_or_data_ref
return_statement ::=
return_value_type |
return select ( select_exp ) { case_entry + }
return_value_type ::=
return parser_state_name ; |
return control_function_name ; |
parse_error parser_exception_name ;
case_entry ::= value_list : case_return_value_type ;
value_list ::= value_or_masked [ , value_or_masked ]* | default
case_return_value_type ::=
parser_state_name |
control_function_name |
parse_error parser_exception_name
value_or_masked ::=
field_value | field_value mask field_value | value_set_name
select_exp ::= field_or_data_ref [, field_or_data_ref] *
field_or_data_ref ::=
field_ref |
latest.field_name |
current( const_value , const_value )
1、select()有点像是C语言中的switch操作,可以用这个概念来理解。
return select(latest.field_name) {
xaaaa : next_parser_name;
default : ingress;
}
2、field_value mask field_value掩码操作。可以类比于IP地址与子网掩码所做的与操作。如果出现多个符合的条目,选择第一条作为匹配
3、对于经常出现的latest.xxx,这个latest代表了最近一次调用extract函数所操作的对象
4、current(first,second),first就是相对于current offset指针的偏移位,second就是位宽。就是用来如果没有指定相应的field-value的时候,可以根据这个去寻找的相应的值,寻找方式就是 在current offset + first,提取second位宽的值。
5、在set_statement,如果调用了如下的语句
set_metadata( field_ref , metadata_expr );
作为参数field_ref必须是在一个已经声明的元数据实例之中。如果如果metadata_expr(该函数参数可以是一个字段的值,也可以是对数据或者字段的引用)与该字段值不一,则会发生值的替换(精度的转换工作)。
解析器异常处理机制parser exceptions
异常发生的时候有两个处理方法:1 drop 2 process
drop:
- 马上丢弃掉该包,没有match+action。
- 用计数器counter去记录丢包情况,用来后面的反馈。
process:
1、可参见下图:

这个就是通过流控制程序匹配对应的异常,进行相应的处理,最后转发给控制层面
2、也可以可以在解析函数中通过parse_error进行处理
标准的解析异常
| 名称 | 事件发生的原因 |
|---|---|
| p4_pe_index_out_of_bounds | 在指定array header[]时索引越界 |
| p4_pe_out_of_packet | 没有足够长的字节完成extract操作 |
| p4_pe_header_too_long | 数据包太长,超出了定义的最大值 |
| p4_pe_header_too_short | 数据包太短,小于定义的最小值 |
| p4_pe_unhandled_select | select操作找不到对应的那个域值 |
| p4_pe_checksum | 检验和与数据包校验和不匹配 |
| p4_pe_default | 可由程序员,自行定义,写出默认异常的处理过程 |
默认的异常处理方式
如果一个异常发生,没有对应它的异常处理发放,也没有定义p4_pe_default处理方法时,那就drop这个包。
这个是异常处理的汇总图:

浙公网安备 33010602011771号