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中每一个状态被表示一个解析函数。一个解析函数也许存在在下列四种方式。

  1. return到那另外一个被定义的解析函数。return ipv4; //ipv4为已定义的解析函数名
  2. return一个被定义流控制函数。 return ingress; //ingress为已定义的流控制程序
  3. 通过关键字parse_error说明错误发生后,要执行什么情况。 //这个由程序员自己定义
  4. 在没有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;

注意点:

  1. value set被在p4项目的最高级的水平,在解析函数外。(可以看成是全局变量)。
  2. 如果有使用到他们的话,他们应该被在之前被声明,
  3. value的位宽被推断,从这个集合被使用到的地方。如果这个集合被使用在多个地方,他们对该集合里的某个值使用了用了不同的宽度,这个时候编译器会报错。
  4. 用于更新解析器值集的运行时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这个包。

这个是异常处理的汇总图:

posted @ 2016-10-12 18:13  考拉小无  阅读(1180)  评论(0)    收藏  举报