P4是一种高级语言,用来编写协议无关的包处理器,P4和SDN 控制协议Openflow一起工作。如何一起工作?我们可以看到SDN引入了控制平台和转发平台,而P4和OpenFlow就工作在控制平台和转发平台之间。那么都运行在其之间,OpenFlow和P4的区别在哪?OpenFlow没办法改变switch的处理逻辑,它只能通过表配置来对switch进行小小的控制,相当于OpenFlow面对的还是fix function的switch。而P4不仅可以影响switch中的某些表,而且还可以对switch的处理逻辑进行编程。可以这样说,P4提供了网络编程的抽象层次。
(1) 可以改变packet switch的处理数据包的逻辑流程
(2) 使得switch不再限制于处理特定的协议数据包,协议无关。
(3) 平台独立,编程者可以不基于底层硬件来进行数据包处理流程的编写,通过编译器再将程序映射到特定的硬件交换机上
OpenFlow通过显示地声明协议头部来表名可以处理的操作,近些年来,协议头部集合已经从12个扩展到了41个,复杂性大大提高,而且也没有提供足够的灵活性来增加新的协议。
P4可以看做是OpenFlow的一种未来演化趋势。
下图是一个抽象的switch ,它由一个可编程的parser、具有很多状态的并行或者组合的match+action构成。
上面这个模型与openflow模型的区别有三点:
(1) openflow模型假定了一个fix parser,而这个模型假定了一个可编程的parser
(2) openflow模型假定match+action的stages是顺序的,而这个模型假定它们既可以是顺序的,也可以是并列的
(3) 上面的这个模型假定action是由被switch支持的,独立于特定协议的基元(primitives)组成的
实际中的包可能会被不同的转发设备转发,比如说Ethernet switches,router等。但是我们不管这些底层硬件,只把注意力集中在P4语言如何描述在这个抽象的转发模型上的数据转发。通过这一点,程序员可以编写平台无关的程序,然后通过编译器将它映射到不同的转发设备上去。
转发模型有两种类型的控制操作:配置和填充(configure and populate),配置操作做(1)编写parser(2)设置match-action不同stage的顺序(3)声明每一个stage处理的header 域。配置操作决定了可以支持哪种协议和switch如何处理数据包。
填充操作向match+action表中增加或者删除表项(match+action中的表项是在配置过程中生成的)。填充操作可以在任何给定时间决定应用到packet上的政策。
(1)经过Parser
当有一个包到达switch后,首先被parser处理,包的头部和内容能够被分开,内容是不用于匹配的,parser从头部识别和抽取域,然后决定switch支持的协议。(parser通过分析包头部的域,来决定switch支持的协议)。这个模型对协议头的含义没有做任何假设,只是解析后的header表示定义了一个域的集合,用来匹配和执行动作。
(2)经过match-actiontables
被抽取出来的header 域被传递给match+actiontables。Match+action tables被分散在ingress和egress之间,但是ingress和egress都可以修改packet header,ingress决定egress端口和packet要被放置的队列。根据ingress的处理,packet被向前转发、复制、丢弃等。
Egress中的matchaction对packet header执行预先定义的修改操作。
Match-action tables不是有很多stage么,packet在这些stage中之间可以携带额外的信息,叫做元数据,元数据和packet hearer域一样被处理对待。元数据典型的有:ingress port、时间戳,queue等
Header:头部定义描述一些域的顺序和结构,包括声明这些域的宽度和域值的限制。
Parser:parser定义声明了如何识别头部和packet中的有效的头部序列
Table:match+actiontable是执行数据包处理的机制,P4程序定义了一个table可能匹配的域和其可能执行的动作
Actions:P4支持从简单的协议无关的基本元上构建复杂的action,这些复杂的action可以被包含在match+action 表中。
Control Programs:控制程序决定了被应用到packet上的match+action table的顺序,也就是match+action的flow顺序。
Header被声明为一系列的域名和它们的宽度,另外,可选的域声明包括:对值的范围的限制、可变大小的域的最大长度。下面举个例子,典型的ehernet和vlan头部声明如下:
P4假定了下面的switch可以实现一个状态机,将packet从start状态变到finish状态,过程中抽取出域的值,然后交给match+action table 处理。
P4直接通过从一个header到另一个header的转换集合来描述这样的状态机,每一个转换可能被header的当前值被触发。
举个例子,下面Parser从start状态开始处理,直到遇见了一个stop状态,或者一个不能处理的状态就停止了。每到一个心状态,状态机会根据header定义抽取出头部,进行处理,并且决定下一个转换。被抽取出的头部,被转向match+action表进行处理。
接下来,程序员描述定义的header域是如何匹配到match+action表的stages,和当一个匹配发生时哪个动作将会被执行。
在mTag例子中,switch匹配到L2目的地,和VLAN id ,然后选择一个mTag添加到header上。程序员定义了一个table去匹配这些域,并且增加了一个action去添加mTag header。
reads属性生命力哪个域被匹配,用匹配的类型(比如说exact,ternary)来qualified。
action属性列出了一些可能的动作,这些动作可能被应用到packet上。
max_size属性生命力这个表能支持多少个表项
表的声明允许一个编译器决定需要多少内存和内存的类型。
同样的,我们给出剩下的表:
P4定义了一些基本的action基元,可以用来构建更复杂的动作。每一个P4程序都声明一系列的动作,这些动作是由action基元构成的。这些action函数简化了表的声明和population,P4并行地执行一个action function中的action基元。上面提到的add_mTag动作被实现成下面这样:
如果一个action需要参数,那么这些参数由match table在运行时提供。
Set_field:给header中的一个域设置一个值,支持掩码设置。
Copy_field:将一个域的值复制到另一个
Add_header:初始化一个header实例
Remove_header:从一个packet中删除header
Increment:增加域的值
Decrement:减小域的值
Checksum:计算一些header域的checksum值
一旦表和action被定义了,剩下的任务就是声明从一个表到另一个表的控制流。控制流被声明地像一个程序,包括了一些function,条件,和指向表的引用。
上面这个图说明了一个mTag的控制流的图形表示,parser之后,sorce_check表验证接受到的packet和ingress port之间的一致性。
上面的那个图形化的控制流的代码如下:
我们需要一个编译器将平台无关的P4程序描述映射到一个特定switch上的硬件和软件平台。
当一个设备有可编程的parser,编译器将Parser描述翻译成parser状态机。
如果设备是fix parser的话,编译器仅仅验证一下parser描述是否和target的parser一致。
上面这个图片,当前状态是vlan,如果值等于0xaaaa,就转向状态mTag,如果值是0x800,那么就转向ipv4。是这样来看的
上面这张图片的代码(control flow)是一个方便的方式去声明一个switch的转发行为的逻辑,但是这种表现方式没有展现出表之间的相互依赖的关系,和并发的可能性。
因此我们开发了一个编译器去分析control program来是被出表之间的依赖关系,和可能存在的并行处理header域的机会。最后,编译器产生一个目标target的配置。Target可能是很多:software switch,多核软件交换机,NPU,fix function switch,或者RMT。
编译的阶段分为两个,第一个阶段将P4程序转成一个中间表示,table dependency graph,这可以用来分析表之间的依赖关系。一个与平台相关的后端然后将这个TDG映射到switch特定的资源上。
SDN的目标是一个控制平台可以控制整个网络中的switch,OpenFlow通过提供一个单一的,厂商无关的API来支持这个目标。然而,OpenFlow面向的是fix function的switch,通过一些预先定义的header field和一些预先定义的一个小集合的action。控制平台不能表达packet如何被处理来最大化地适应控制应用的需求。
程序员决定转发平台如何处理数据包,而不用考虑实现的细节。编译器将命令式的程序转变成一个tabledependency graph(TDG),TDG可以映射到多个特定的平台swtich上。