1.前言
从SDN概念的提出,到openflow的问世、DPDK套件的诞生、再到后来的P4,这些年来互联网的大佬们一直在探索如何让网络的传输能更快、更多、更稳。
前几年,我曾基于P4做过一些应用开发,也体会到了P4的巧妙,用一句话来形容则是:天下武功,无坚不破,唯快不破。
最近有朋友问我有无P4的入门资料,今天整理一些资料记录一下——如何快速入门P4。
2.P4简介
P4是Programming Protocol-independent Packet Processors(与协议无关的,可编程的数据包处理器)的简写。
从我的视角来看,P4即是一门技术体系,也是一种编程语言。
与传统路由交换厂商的固有硬件(ASIC)相比,P4是一种技术颠覆,使用它可以实现任何的协议并进行处理。
P4版本说明:
在学习P4时,我们需要注意P4当前有两个版本,分别为p4_14(2014年)与p4_16(2016年)两个版本,这和python2与python3类似,也就是p4_14和p4_16的语法是不兼容的。
由于p4_14已逐渐废弃,学习时选择p4_16版本即可。
P4设备架构:
P4 Architecture是通过一组P4可编程、外部(externs)与固定组件,提供对P4 Target进行编程的接口
在体系架构层面,可编程流水线通常被称为协议独立交换架构(PISA, Protocol Independent Switching Architecture)。如上图给出了PISA概述,包括三个主要组成部分。
- 解析器(Parser),通过编程定义哪些报头字段(以及在包中的位置)将被后面的阶段识别和匹配。
- 匹配操作单元(Match-Action Unit),每个单元都被编程来匹配(并可能对其进行操作)一个或多个已标识的报头字段。
- 编码器(Deparser),将包元数据重新序列化到包中,然后在输出链路上传输。deparser根据之前处理阶段缓存在内存中的所有报头字段重新构建在链路上传输的每个包。
理想情况下,只有一套逻辑流水线,P4编译器负责将该逻辑流水线映射到各种对应的物理流水线。不幸的是,实现方面不同交换芯片可能实现了不同物理流水线,导致目前市场还没有统一的单一逻辑流水线。
现阶段的P4中存在多种架构以适应不同的设备,比较知名的有:
- Portable NIC Architecture (PNA)
- Portable Switch Architecture (PSA)
- Tofino Native Architecture (TNA),Tofino设备的特有架构
- V1Model,用于P4的学习验证,适用于Bmv2软件交换机
详细介绍可移步P4官网:p4.org,本文中使用的为V1Model架构
3. P4语法速成
P4的语法与C类似,且比C语言的关键字少很多,也可以理解为它是一个C语言的精简版。
访问p4-sandbox,通过页面右侧的示例可对P4语法有一个初步的了解。
在Sanbox示例中演示了一个数据包从进入交换机到发出数据包前后数据变化情况,值得初学时细细品读。
在了解了P4的语法后,再配合具体的目标架构,便可完成特定功能的开发。
以V1Model处理流水线为例,对应的整体示例代码片段如下:
4.P4开发环境搭建
为了快速体验P4的集成环境,P4官方仓库中也提供了方法和镜像可以快速搭建出P4的开发环境,为了方便可以直接使用官方提供的镜像。
如想体验手动搭建按照官方操作步骤使用vagrant up
命令,再根据网络情况等待一会儿脚本就会自动安装所需工具。
待P4虚拟机启动完毕后,会出现如下界面:
输入用户名密码p4/p4则可进入p4集成开发环境
如网络较慢,也可直接使用我初始化好的ova镜像导入到virtalBox中运行。下载速度10M/S以上
5.基础练习
环境就绪后,就可以按照P4 Tutorial中的题目练习感受一遍P4了。
所有示例官方也提供有对应的视频讲解教程,视频链接地址可以其GITHUB文末中找到:P4 Developer Day (P4 D2) Fall 2017,再搭配P4官方PPT则可速成
5.1 P4程序编译过程了解
编译一个p4程序到p4交换机的过程如下图所示:
结合源码文件理解:
以编译basic.p4为例,将会在build目录下产生两个文件,分别为:
- basic.json
- basic.p4.p4info.txt
Use the compiler to generate a target-specific configuration blob. - test.json in case of BMv2
其中basic.json文件的作用为P4交换机的配置文件,为P4交换机提供流表处理逻辑。
The compiler also generate a special file called P4Info that will be needed to install table entries at runtime
可以理解为P4Info文件是交换机运行时的流表,生成的json文件是流表项
用上图来理解,basic.p4info文件为P4交换机所需要安装的表项的定义文件,类似于api的定义描述文件,将会在控制面和数据面进行共享。
再次回顾一下:
- basic.p4为源码文件;
- basic.json为P4交换机中的流表规则文件;
- basic.p4info为P4交换机和控制面的共享文件,记录的是交换机可下发的表项结构信息;
- s1-runtime.json为某个交换机的表项文件,记录的是实际的表项。
5.2 基础路由实现
了解了P4的编译流程后就可以用一个小示例进行验证了。
根据Implementing Basic Forwarding中的介绍实现所有主机的正常通信。
需补全的代码为:
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
其中standard_metadata.egress_spec部分可参考此处:https://github.com/p4lang/behavioral-model/blob/main/docs/simple_switch.md
egress_spec (sm14, v1m) - Can be assigned a value in ingress code to control which output port a packet will go to.
之后进行验证,则可以发现h1、h2、h3、h4这四台主机就都可以ping通了。
其中流表项的下发过程在make run
脚本中,它会将所测试的表项写入到对应的交换机中,表项内容如下:
5.3 P4 Runtime控制面练习
上面的例子我们体验了从上向下的控制,除此之外P4也支持数据的上报,可通过p4runtime快速体验。
示例运行截图如下图所示:
通过此示例我们可以了解到:
- 在P4 Runtime实验中,数据的通信是通过GRPC进行传输的,它支持几乎所有的主流开发语言
- 上面截图中模拟的控制器使用的是python脚本
- 通过P4 Runtime则可实现ONOS对P4交换机的管控,具体可参考Try fabric.p4 with ONOS and bmv2,关于ONOS的相关资料也可看我之前的一些文章
6.实现ECMP(Load Balancing)
最后体验一下用P4实现一个简单的ECMP(Equal-Cost Multipath Forwarding,等价多路径转发)实例
6.1 拓扑与需求
详细需求描述见p4_load_balance。
拓扑如下:
核心原理为使用HASH算法实现数据包转发路径的均匀分配。
hash计算的具体方式可访问v1model.p4了解,关键代码为:
enum HashAlgorithm {
crc32,
crc32_custom,
crc16,
crc16_custom,
random,
identity,
csum16,
xor16
}
/***
* Calculate a hash function of the value specified by the data
* parameter. The value written to the out parameter named result
* will always be in the range [base, base+max-1] inclusive, if max >=
* 1. If max=0, the value written to result will always be base.
*
* Note that the types of all of the parameters may be the same as, or
* different from, each other, and thus their bit widths are allowed
* to be different.
*
* @param O Must be a type bit<W>
* @param D Must be a tuple type where all the fields are bit-fields (type bit<W> or int<W>) or varbits.
* @param T Must be a type bit<W>
* @param M Must be a type bit<W>
*/
@pure
extern void hash<O, T, D, M>(out O result, in HashAlgorithm algo, in T base, in D data, in M max);
最终完成的代码片段为:
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
action set_ecmp_select(bit<16> ecmp_base, bit<32> ecmp_count) {
hash(meta.ecmp_select,
HashAlgorithm.crc16,
ecmp_base,
{ hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr,
hdr.ipv4.protocol,
hdr.tcp.srcPort,
hdr.tcp.dstPort },
ecmp_count);
}
action set_nhop(bit<48> nhop_dmac, bit<32> nhop_ipv4, bit<9> port) {
hdr.ethernet.dstAddr = nhop_dmac;
hdr.ipv4.dstAddr = nhop_ipv4;
standard_metadata.egress_spec = port;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
table ecmp_group {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
drop;
set_ecmp_select;
}
size = 1024;
}
table ecmp_nhop {
key = {
meta.ecmp_select: exact;
}
actions = {
drop;
set_nhop;
}
size = 2;
}
apply {
if (hdr.ipv4.isValid() && hdr.ipv4.ttl > 0) {
ecmp_group.apply();
ecmp_nhop.apply();
}
}
}
即:p4交换机(bmv2)会根据hdr.ipv4.srcAddr、hdr.ipv4.dstAddr、hdr.ipv4.protocol、hdr.tcp.srcPort、hdr.tcp.dstPort计算出一个hash值赋值给meta.ecmp_select变量,之后再执行ecmp_nhop表中的 精准匹配(exact) 执行数据包转发(具体如何转发由表项json文件内容决定)
6.2 ecmp验证
cd /home/p4/tutorials/exercises/load_balance
make
xterm h1 h2 h3
在h2和h3上执行如下命令启动抓包程序
./receive.py
在h1上执行如下命令进行发包测试
./send.py 10.0.0.1 “puhaiyang”
./send.py 10.0.0.1 “haiyang”
验证效果如下:
从结果可以看出,h1发往10.0.0.1的两个不同的数据包做了负载均衡,下一跳地址在h2和h3间轮询通过。
结合s1设备的表项可看出具体转发过程:exercises/load_balance/s1-runtime.json
关键数据:
{
"action_name": "MyIngress.set_ecmp_select",
"action_params": {
"ecmp_base": 0,
"ecmp_count": 2
},
"match": {
"hdr.ipv4.dstAddr": [
"10.0.0.1",
32
]
},
"table": "MyIngress.ecmp_group"
},
{
"action_name": "MyIngress.set_nhop",
"action_params": {
"nhop_dmac": "08:00:00:00:02:02",
"nhop_ipv4": "10.0.2.2",
"port": 2
},
"match": {
"meta.ecmp_select": 0
},
"table": "MyIngress.ecmp_nhop"
},
{
"action_name": "MyIngress.set_nhop",
"action_params": {
"nhop_dmac": "08:00:00:00:03:03",
"nhop_ipv4": "10.0.3.3",
"port": 3
},
"match": {
"meta.ecmp_select": 1
},
"table": "MyIngress.ecmp_nhop"
}
set_ecmp_select部分的为负载均衡控制算法具体数据,位于ecmp_group表;
set_nhop部分的为具体下一跳数据,位于ecmp_nhop表;
7.总结
P4的快速入门没有捷径,多加练习勇于实践,必能熟练掌握
必看必练资料:https://github.com/p4lang/tutorials
Practise more. Good luck.