P4官方实验1 实现基础转发
介绍
交换机要实现接下来的功能
- 更新元和目的mac
- 减少IP头的TTL
- 将包转发到合适的端口
我们的P4程序使用V1Model结构写成,该结构被P4.org上的bmv2软件交换机实现。作者建议我们阅读v1model.p4文件。
我们需要使用mininet,这是斯坦福大学的一个开源项目,用于生成一个虚拟网络,包括虚拟链路、主机、交换机、路由器等,用于实验测试。
第一步:运行不完全的起始代码
在basic目录下make源代码
make run
之后使用h1 ping h2,肯定ping不通,因为我们没有对交换机进行编程,默认动作设置的是丢弃所有包。
关于控制平面的说明
转发表中的转发规则是由控制平面下发的,在这个试验中控制平面逻辑已经完成了,它们被定义在sX-runtime.json文件中,X是对应交换机的序号。
重点:我们使用P4Runtime来安装控制平面规则,sX-runtime.json文件的内容具体是指,具体的转发表名称、键、动作、就像在P4Info文件中定义的一样,P4info文件是P4编译器提供的。
第二步: 完成L3转发
我们需要补全程序中的所有TODO部分,才是完整的P4程序。
第三步:运行你的解
这一步没啥好说的,写完整个程序,再来一遍make run就行了。
如果我们写的是正确的,或者直接用了他给的solution,那么在make run之后生成一个最简单的拓扑,之后直接运行pingall可以看到是能够ping通所有连接的。
代码解析
下面是完整的程序逻辑和代码注释。
所有三反斜线之间的代码是需要我们添加的。
首先定义数据包首部HEADERS。之后是解析器PARSER,对头部进行解析和识别,对不同的协议进行不同的操作。之后是校验和验证,是一个control部件。之后是入路由处理。之后是出路由处理。之后是校验和计算。之后是去解析器。最后是将各个功能部件组合起来,形成switch。
头文件
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
const bit<16> TYPE_IPV4 = 0x800;
core.p4中包含必要的数据结构,v1model.p4中包含一些必要的函数和数据结构。
HEADERS 首部
/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/
typedef bit<9> egressSpec_t; //转发端口号定义
typedef bit<48> macAddr_t; //48位mac地址定义
typedef bit<32> ip4Addr_t; //32位ipv4地址定义
header ethernet_t { //以太网包头格式
macAddr_t dstAddr; //目的地址
macAddr_t srcAddr; //源地址
bit<16> etherType; //协议类型
}
header ipv4_t { //ipv4数据包头格式
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}
struct metadata { //元数据
/* empty */
}
struct headers { //数据链路层和网络层协议的组合头部
ethernet_t ethernet;
ipv4_t ipv4;
}
PARSER 解析器
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
\\\
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
\\\
}
解析器parser可以看作是一个抽象状态机,通过在不同的state之间设置条件转移,从而对不同的协议进行解析。
需要四个参数。
- packet_in packet:in类型的packet,表示当前传入或等待处理的包。在P4语言中in是只读类型。
- out headers hdr:out类型的headers,即out类型的包头。标准文档中说,out类型代表一个存储引用。执行完操作后,值将被写到对应的存储位置中。
- inout metadata meta:inout类型既是in类型又是out类型。metadata指在P4执行过程中生成的中间数据。
- inout standard_metadata_t standard_metadata:查找v1model.p4头文件,可以看到standard_metadata_t类型是一个别名,是从P4程序到behavioral model的一个映射,将simple switch支持的所有元数据通过standard_metadata_t暴露给用户(其实我也不懂这是啥意思)。
状态转移使用transition命令,后跟状态名;transition selelct(data)相当于C语言中的switch语句,根据data中的数据选择不同的分支转移到不同的状态。
解析处理的结束状态是accept,意为对当前包头的处理结束。最终结果是hdr中存着处理完的包头。
在这里,如果包头是ipv4类型的,会被提取进hdr.ipv4。packet_in类型见core.p4。extract操作在这个结构体重的定义是"从包中读数据到一个固定尺寸的头部中"。
CHECKSUM VERIFICATION 校验和检验
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
需要解释一下什么是control。control相当于C语言中的函数,它能够声明变量。创建流表,初始化外部对象等,它的功能在apply声明中定义。
此部分apply为空代码。
INGRESS PROCESSING 入路由
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata);
}
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;
\\\
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
drop;
NoAction;
}
size = 1024;
default_action = drop();
}
apply {
\\\
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply();
}
\\\
}
}
三个参数,头部、两个元数据。
先定义了drop动作,查标准知这是标记动作结束后要丢弃的数据。
又定义了ipv4_forward动作,这个动作的过程是需要我们手写的。了解转发过程,不难知道后三行代码是啥意思:将现在的源地址赋值为现在的目的地址,现在的目的地址赋值为给出的mac地址。但是不要忘了第一句,是设置交换机的转发端口。
再看匹配动作表。可以看到,每一个动作都由action声明指出。这里定义的动作有drop、ipv4_forward。匹配表由table声明给出,这里的匹配表名称是ipv4_lpm。table中的key,设置了hdr.ipv4.dstaddr的匹配方式为lpm最长前缀匹配,其他还有exact完全匹配,ternary三元组匹配。这三种规则写在core.p4文件中。而具体的匹配动作表项,在每个switch对应的sX-runtime.json中,X是对应的交换机编号。key后面是actions,声明了匹配后要先后做的操作。size=1024不知道啥意思,最后默认操作,就是未匹配的话,就drop。
最后是这个control模块的apply,从这里执行。先用校验和检查ipv4是否有效,再应用。
EGRESS PROCESSING 出路由
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
出路由没有任何操作。
CHECKSUM COMPUTATION 校验和计算
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
计算校验和是否有效,这个函数在v1model.p4中,但是我没有看懂。
DEPARSER 去解析器
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
\\\
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
\\\
}
}
packet_out类型在core.p4中定义,其只有一个函数,就是发送hdr。注意,这个包头的发送顺序是有先后的。
SWITCH 交换机
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;
没啥好说的,把功能模块全拼起来就行了。