《P4语言规范》parser详解

 

本系列文章根据P4.org网站给出的《TheP4LanguageSpecificationv1.0.2》[1]内容,并通过我们的运行使用的具体实例和分析汇总,希望能为大家研究P4提供一点参考。...

前言
为深入研究P4语言相关规范及运行操作使用,本系列文章根据P4.org网站给出的《The P4 Language Specification v1.0.2》[1]内容,并通过我们的运行使用的具体实例和分析汇总,希望能为大家研究P4提供一点参考。作为大二和大三的本科生,水平和经验有限,感谢SDNLAB提供平台,希望能和大家相互学习交流。

本系列文章分为三个部分,系列一翻译和阐述 P4.org网站给出的《The P4 Language Specification v1.0.2》的第二部分首部及字段;系列二是翻译和阐述《The P4 Language Specification v1.0.2》的第三部分解析器;系列三是基于Github开源项目p4factory中的P4项目源码分析。
一、解析器介绍
p4解析器是基于有限状态机的思想来设计的。

解析器中解析的过程可以被一个解析图(parser graph)所表示,解析图中所表示的某一个状态(或者说,在P4语言中的某一个解析函数)看做是一个状态节点,每一个状态转换等同于跨越状态节点之间的边界。

下面的P4代码展示了关于mTag包处理的解析器内容中的部分解析函数。



状态图表示:



(图一:状态解析图)

从上文中的示例代码中可以看到,每一个解析节点都明确要求识别一个首部。虽然P4支持这种图表的形式,但是没有规定必须这样做。

实际上,P4的解析状态节点可以分为两种:

☘  一种是进行状态转移的选择节点,这个节点不要求进行对首部实例的识别操作。

☘  另一种状态节点可以识别一个或者多个的首部实例,并对它们进行相应的操作。
二、解析表示(Parsed Representation)
2.1 什么是 解析表示?

解析表示(Parsed Representation),是解析器针对于数据包进行一系列解析操作之后所生成的结果,它是一个合法首部实例的集合(包头实例+元数据实例);解析过程之后的匹配-动作流水线上的相关操作将在解析表示上执行。

在匹配-动作的过程中,会对解析表示中的首部实例进行更新,即更新解析表示中某个首部实例的字段值。



(图二:匹配-动作过程)

注意:针对某些特殊的操作(比如复制数据报),在数据包的处理流程中将会保存原始的数据报信息。相关内容在规范的后续章节会论述。
三、解析器操作(Parser Operation)
解析器根据数据包的第一个字节进行调控,它保存了一个指向该数据包包头中特殊单位字节的指针(current offset)。

当解析器开始对首部实例进行提取操作时,它根据作为extract函数参数的首部实例的格式进行提取,将数据包的数据更新到该首部实例中,同时更新该数据包的解析表示。在提取操作结束之后,解析器保存的指针(current offset)移向下一个要处理的位置,等同于进行一次状态转移。

可以参照下图进行理解:



(图3:开始解析时所使用的指针(current offset)指向数据包的第一个字节)



(图4:通过extract函数语句的操作,提取出对应的包头实例)



(图5:操作结束,指针current offset移向下一个位置)

在上图的step two体现了extract()函数的相关功能。

注意:

☘  extract()函数只能针对包头实例进行操作,元数据实例无法作为该函数的参数。

☘  在底层根据包头实例的格式进行数据包的提取操作之前,在P4程序中首先需要实例化该首部实例,然后再执行extract函数语句。

元数据能够影响状态转移的决定。例如,当处理某一个数据包时,会根据元数据的字段值,比如ingress端口,决定目的解析状态节点。同样的,那些进行复制操作或者执行再循环操作的数据包,也需要基于元数据进行状态选择。

在P4中每一个状态都对应于一个特定的解析函数。会以以下四种方式中的一种结束一个解析函数的相关操作:

1. return语句声明了一个已定义的目的解析函数名,该解析函数为当前状态转移的目的状态。

return ipv4; //ipv4为已定义的解析函数名

2. return语句声明了一个已定义的流控制函数名,该语句的执行意味着解析过程的结束。 return ingress; //ingress为已定义的流控制程序

3. 通过关键字parse_error说明错误发生后,将要执行的异常处理机制。

4. 在没有声明parse_error语句的情况下发生错误;该情况将在下文中的异常处理机制部分介绍。

注意:

由于上文中的头两个结束方法,解析函数名、控制函数名是共享在同一个命名空间里的。

倘若存在某一解析函数与某一控制函数重名的情况,编译器必须报错。

下图描述了以上三个部分的解析过程:



(图六:解析过程)
四、值集 (value sets)
什么是值集(value sets)

在某些情况下,在运行时(run time)需要确定一些能够决定解析过程状态转移的数值。这些为了标志状态转移的值的集合叫做值集(value sets)。值集仅包含数值,没有包含任何其他的首部类型或者状态转移的信息。通过运行时API(run time API)来对该值集进行操作(加入或者删除)。

4.1 为什么要有值集

举个例子,MPLS标签字段的值能够用来确定包头是否遵从MPLS标签,并根据判断结果执行相应的状态转移。P4通过支持值集的概念,从而支持该功能。在一个值集中,所有的数值必须服务于同一种转移。

例如,所有符合一个IPv4转移的MPLS标签值存在于一个值集;同样的,所有符合一个IPv6转移的MPLS标签存在于另一个值集。

4.2 定义方式

parser_value_set value_set_name;

注意:

1. 在P4语言中,属于最高级别(top level)的值集定义位于解析函数的定义部分之外。值集有一个独立的全局命名空间。

2. 只有已定义的值集才能在解析函数中被引用。

3. 从值集被引用的位置可以推断出值集中数值的宽度。如果这个值集被使用在多个地方,将会推断出宽度值不一的情况,这个时候编译器会报错。

4. 用于更新解析器值集的运行时API,必须支持同时定义值集中的数值和掩码对的情况。

在P4程序中的相关内容:

五、解析函数的BNF(巴科斯范式)




(图七:解析函数的巴科斯范式)

1、select函数是目的转移状态选择逻辑的主体,类比于C语言中的switch-case操作;该函数采用了逗号分隔的字段列表(a comma-separated list of fields,类似的概念可以参考CSV,这里的逗号指的是;),并且通过左端列出的具有重要意义的比特组(bits)(对应于特定字段值),比如下文中对应于field_name字段值的0xaaaa比特组,和组成该列表的成员字段值建立联系。



select函数的选择操作按列表中的顺序比较字段值和左端列出的比特组对应的值(一般为16进制值),如果没有发现与select函数的参数字段值相匹配的表项,则对应到default表项(如果有定义的话)。

根据上文给出的select函数体代码进行理解:select函数将字段field_name的值与第一个表项中的0xaaaa进行匹配,如果匹配成功则选择进入next_parser_name状态节点;

若不匹配,执行default表项,结束解析过程,进入ingress流水线

2、field_value mask field_value掩码操作。操作符mask用于三元匹配(ternary match),该类型的匹配需要定义确定的掩码值。

可以类比于IP地址与子网掩码所做的与操作。基于select函数的参数字段值和列表中的成员字段值的匹配受限于掩码的值,在进行比较之前,需要将参数字段的值与成员字段的值分别和掩码进行与运算。

表项的顺序决定了匹配的优先级:若出现该情况,则采用在列表中符合匹配条件的第一个表项。

3、select函数的参数中,经常出现latest.field_value语句,latest代表了最近一次调用extract函数所操作的对象。

在同一个解析函数中,在使用latest关键字之前如果没有进行extract语句的操作,会出现错误。

4、current(const_value, const_value),第一个参数是操作所使用的指针的偏移量(current offset),第二个参数是区域的位宽(单位为bit)。根据给出的位宽得到的调用结果视作是一个无符号的字段值;偏移量 和 (偏移量+位宽) 指明了操作区域,从该区域中提取出相应的值。

注:实际上,current函数的调用结果是根据转换规则转换而成的元数据字段。

5、在set_statement,如果调用了以下语句:

set_metadata( field_ref , metadata_expr );

作为函数参数的field_ref字段,必须存在于一个已声明的元数据实例之中。如果如果metadata_expr(该函数参数可以是一个字段的值,也可以是对数据或者字段的引用)与该字段值不一,则会发生值的替换。
六、解析器异常处理机制 (parser exceptions)
异常发生的时候有两个处理方法:(1)丢包(drop),(2)加工(process)

丢包(drop):

☘  马上丢弃掉该包,该数据包不会进行任何的后续匹配-动作操作。

☘  用计数器(counter)去记录丢包情况,用于之后的反馈。

加工(process):

解析器使用了加工处理的方法,它在遇到异常错误的时候会立即停止解析的过程,并设置一个特殊的元数据来详细说明该解析错误;随后该包被交给匹配-动作流水线的控制函数来进行处理。

就像其它正常的数据包一样,该数据包根据已安装至底层设备的匹配-动作规则来进行相关操作处理,但这些规则可能会根据该数据包所携带的元数据来检查解析错误,并采取诸如将包送至控制层面的策略。

在P4中,有很多可能会暗中触发的错误情况,这些情况列举在下文的表格中。

另外,程序员可以在解析函数中通过parser_error异常处理关键字来设置对于错误的处理方法:

parser_error parser_exception_name;

程序员们可以依据下文的BNF逻辑,来明确定义关于解析器异常处理机制的操作。

错误处理指令能够调用一些元数据集来协助处理操作,该指令要么将包交给了后续流程的控制函数,要么是丢包指令。

注意,只有在执行了调用操作之后,上文提到的为说明解析错误而设置的特殊元数据才实际有效。



图八:解析异常处理的巴科斯范式)

6.1 标准的解析异常

在下表中列举了标准的解析器异常处理机制名称。

前缀pe代表解析器异常处理机制(parser exception)。

| 名称 | 事件发生的原因 |

| ------------------------- | ---------------------- |

| 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默认处理机制的前提下,倘若出现了一个解析错误没有匹配到对应解析处理机制的情况,选择调用已定义的默认处理机制。

如果发生了上面的情况,同时也没有定义任何的默认处理机制,那么解析器选择丢包。

异常处理机制汇总图:



(图九:异常处理机制汇总图 上)



(图十:异常处理机制汇总图 下)

参考文献

[1]《The P4 Language Specification v1.0.2》 http://p4.org

作者简介:

汪培侨,福州大学数计学院2014级计算机科学与技术(实验班)本科生,目前针对软件定义网络SDN的P4语言进行研究。

陈翔,福州大学数计学院2015级计算机科学与技术(实验班)本科生 ,对软件定义网络SDN,特别是对P4语言感兴趣。

黄志文,福州大学数计学院2014级计算机科学与技术(实验班)本科生,研究侧重于数据平面可编程化。

微信ID:SDNLAB


长按左侧二维码关注


    关注 SDNLAB


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册