Data plane 中有三个重要的部分:
- Parser
- Tables (Match + Action)
- Control program
本篇主要介绍Parser部分,以及相关的Header。
Header是定义哪些数据报文的header可以使用,Parser是对headers里面的信息解析出来变成P4理解的数据结构。解析过程中存在若干个状态,状态之间可以转换。
Header Types
数据包通常包括一系列headers,每个header都是一系列字段。例如,以太网数据包具有以下结构:
P4提供一种表示header的内置类型
可以使用标准的“.”符号访问header的每个组件,例如,如果可变以太网的类型为ethernet_t,则为ethernet.dstAddr表示目标地址。
header值可以处于有效或者无效状态,最初是无效状态。读取无效header的字段会生成未定义的值。
header要被设置成有效的,可以使用 isValid()、setValid()操作,或者通过在解析器中提取头来使其有效。
Typedefs and Structs
为了支持为常用类型提供便捷的名称,P4提供了类型定义:
在这个声明中,类型bit<48>和macAddr_t是同义词,被视为等价的。
P4还提供标准的C样式结构,其定义如下:
与header不同,struct没有内置的有效性概念,也不意味着字段之间有任何顺序。
Header Stacks and Unions
P4为header堆栈和联合提供派生类型。header堆栈类似于数组,但支持解析数据包时可以使用的其他操作。
如果header是header类型,那么类型header[N]表示header堆栈类型,其中N必须是整数文本。如果堆栈是类型为header堆栈的表达式,则:
- stack[i]: 表示索引i处的header,
- stack.size: 表示头堆栈的大小,
- stack.push_front(n): 将堆栈“向右”移动n,使堆栈前面的n个条目无效,以及
- stack.pop_front(n):将堆栈“左”移n,使堆栈末尾的n个条目无效。
此外,在解析器(如下所述)中,可以使用以下表达式:
- hs.next 表示未使用extract(…)调用填充的header堆栈的下一个元素,下文将对此进行举例,以及
- hs.last 对header堆栈最后一个元素的引用,该元素以前是通过调用extract(…)填充的。
header union 对两个header之间可选性进行编码:
在运行时,只有一个header可能有效。可以使用标准的“.”符号访问和修改header union的组件。
Parsers
P4程序的第一部分通常是一个解析器,它将实际数据包中的位映射为类型化表示。典型的解析器可能声明如下:
这里,packet参数是一个封装正在解析的数据包的对象。它有一个通用的方法extract,可以用来填充headers。
headers、meta和std_meta 这三个参数是表示解析的Header的数据结构,以及特定于程序和标准的元数据。通常,header和meta的类型是程序员定义的结构,而std_meta的类型是标准库中定义的结构。
方向out和inout分别表示只读和读写的参数。还有一个in代表只读参数。
在内部,解析器描述一个状态机,其中的状态可能是:
从数据包header中提取位,
关于数据值的分支,以及
过渡到下一个状态
例如,下面的解析器识别以太网和IPv4数据包。
解析器有几个特定状态,包括必须明确定义的初始状态(start),以及接受(accept)和拒绝(reject)最终状态。
举例
接下来,让我们看看如何解析具有可变结构的数据包。我们将使用一个涉及源路由的简单示例:每个数据包要么是标准IPv4数据包,要么是源路由数据包。
首先,我们将定义表示以太网报头的类型
和源路由的header
port字段对数据包应该转发到的端口进行编码,而bos字段是堆栈底部的标记,在堆栈的最后一个元素设置为1。
我们还定义了一个struct来表示所有的headers:
请注意,srcRoutes是一个最多包含8个元素的堆栈。
通过这些类型定义,我们可以定义解析器本身:
从start状态开始,
transition 到parse_ethernet,提取ethernet header,如果etherType是0x1234则转到parse_srcRouting,默认accept,
最有趣的状态是parse_srcRouting,它反复提取hdr的下一个元素。srcRoutes会一直堆叠,直到堆栈中的空间耗尽,或者找到bos设置为1的元素。
bos为1,转到parse_ipv4。
下篇将会介绍 Table (Match + Action) 。