P4学习笔记(二)一个简单P4交换机实现
P4学习笔记(二)一个简单P4交换机实现文章目录P4学习笔记(二)一个简单P4交换机实现1、 架构模型2、预定义模块详细描述2.1 Arbiter 模块2.2 Parser runtime 模块2.3 Demux 模块3、代码声明文件4、代码实现文件本节主要讲诉利用P4实现一个最简单的交换机。首先会讲一下交换机的架构,然后给出具体的P4代码实现。1、 架构模型简单P4交换机(VSS:very si
本节主要讲诉利用P4实现一个最简单的交换机。首先会讲一下交换机的架构,然后给出具体的P4代码实现。
1、 架构模型
简单P4交换机(VSS:very simple switch)它只是一个教学示例,说明了可编程交换机如何利用P4实现和代码编写。VSS具有许多固定功能块(在我们的示例中以浅蓝色显示),具体功能在下一小节描述。 白色块为P4代码实现模块(parse、match-action pipeline、 deparse)。
VSS通过8个输入以太网端口之一,通过再循环通道或从直接连接到CPU的端口接收数据包。 VSS有一个单一的Parser,解析后输出到match-action pipline,pipline处理后输出到Deparse模块,经过Deparser后,数据包通过8个输出以太网端口之一或3个“特殊”端口之一发出:
- 发送到“ CPU端口”的数据包将发送到控制平面
- 发送到“丢弃端口”的数据包将被丢弃
- 发送到“再循环端口”的数据包通过特殊的输入端口重新注入到交换机中
图中的白色块是可编程的,用户必须提供相应的P4程序来指定每个此类块的行为。红色箭头指示用户定义的数据流。浅蓝色块是固定功能组件。绿色箭头是数据平面接口,用于在固定功能块和可编程块之间传递信息,这些功能在P4程序中作为内部元数据(译者注:类似于全局变量的概念)。
2、预定义模块详细描述
P4仅能实现部分模块功能,部分功能还是需要预定义模块来实现。
2.1 Arbiter 模块
- 从物理输入以太网端口之一,控制平面或输入再循环端口接收数据包。
- 对于从以太网端口接收的数据包,该模块计算以太网尾部校验和并进行验证。 如果校验和不匹配,则丢弃数据包。 如果校验和确实匹配,它已从数据包payload中删除。
- 如果有多个数据包,则接收数据包涉及运行仲裁算法。(译者注:没有太明白啥意思)
- 如果Arbiter 模块正在忙于处理先前的数据包,并且没有队列空间可用,则输入端口可能会丢弃到达的数据包,且不会提示。
- 接收到数据包后,Arbiter 块将inCtrl、inputPort值设置为match-action pipline输入,该值是数据包起源的输入端口的标识。 物理以太网端口的编号为0到7,而输入再循环端口的编号为13,CPU端口的编号为14。
2.2 Parser runtime 模块
Parser runtime模块与Parser协同工作。 它基于Parser的结果match-action pipline提供错误代码,并且向demux 模块提供关于数据包payload的信息(例如,剩余payload数据的大小)。 parser完成数据包的处理后,将使用关联的元数据作为输入(数据包头和用户定义的元数据)调用match-action pipline。
2.3 Demux 模块
Demux模块的核心功能是从deparser接收输出数据包的包头,从parser接收数据包的payload,将它们组合成新的数据包,然后将结果发送到正确的输出端口。 输出端口由outCtrl.ouputPort的值指定,该值由match-action pipline设置。
- 丢弃报文:将数据包发送到丢弃端口会让数据包消失。
- 转发报文:将数据包发送到编号在0到7之间的输出以太网端口会导致它在相应的物理接口上发出。 如果输出接口已经在忙于发出另一个数据包,则可以将该数据包放入队列中。 发出数据包时,物理接口会计算正确的以太网校验和尾部并将其附加到数据包中。
- 上升到CPU:将数据包发送到输出CPU端口会使数据包传输到控制平面。 在这种情况下,发送到CPU的数据包是原始输入数据包,而不是从Parser接收的数据包,从deparser接受后的包会被丢弃。
- 循环处理包:将数据包发送到输出再循环端口会使它出现在输入再循环端口。 当无法单次完成数据包处理时,循环很有用。
- 非法包: 如果outputPort的值非法(例如9),则数据包将被丢弃。
- 业务繁忙丢包:如果Demux 模块正忙于处理先前的数据包,并且没有能力将来自deparser的数据包排队,则解复用器可能会丢弃该数据包,而与指示的输出端口无关
3、代码声明文件
(译者注:文中的语法是基于P4_16的)下面一段代码是VSS的定义文件,定义了VSS所要用到的结构体以及函数声明。
// File "very_simple_switch_model.p4"
// Very Simple Switch P4 declaration
// core library needed for packet_in and packet_out definitions
# include <core.p4>
/* Various constants and structure declarations */
/* ports are represented using 4-bit values */
typedef bit<4> PortId;
/* only 8 ports are "real" */
const PortId REAL_PORT_COUNT = 4w8; // 4w8 is the number 8 in 4 bits
/* metadata accompanying an input packet */
struct InControl {
PortId inputPort;
}
/* special input port values */
const PortId RECIRCULATE_IN_PORT = 0xD;
const PortId CPU_IN_PORT = 0xE;
/* metadata that must be computed for outgoing packets */
struct OutControl {
PortId outputPort;
}
/* special output port values for outgoing packet */
const PortId DROP_PORT = 0xF;
const PortId CPU_OUT_PORT = 0xE;
const PortId RECIRCULATE_OUT_PORT = 0xD;
/* Prototypes for all programmable blocks */
/**
* Programmable parser.
* @param <H> type of headers; defined by user
* @param b input packet
* @param parsedHeaders headers constructed by parser
*/
parser Parser<H>(packet_in b,
out H parsedHeaders);
/**
* Match-action pipeline
* @param <H> type of input and output headers
* @param headers headers received from the parser and sent to the deparser
* @param parseError error that may have surfaced during parsing
* @param inCtrl information from architecture, accompanying input packet
* @param outCtrl information for architecture, accompanying output packet
*/
control Pipe<H>(inout H headers,
in error parseError,// parser error
in InControl inCtrl,// input port
out OutControl outCtrl); // output port
/**
* VSS deparser.
* @param <H> type of headers; defined by user
* @param b output packet
* @param outputHeaders headers for output packet
*/
control Deparser<H>(inout H outputHeaders,
packet_out b);
/**
* Top-level package declaration - must be instantiated by user.
* The arguments to the package indicate blocks that
* must be instantiated by the user.
* @param <H> user-defined type of the headers processed.
*/
package VSS<H>(Parser<H> p,
Pipe<H> map,
Deparser<H> d);
// Architecture-specific objects that can be instantiated
// Checksum unit
extern Checksum16 {
Checksum16(); // constructor
void clear(); // prepare unit for computation
void update<T>(in T data); // add data to checksum
void remove<T>(in T data); // remove data from existing checksum
bit<16> get(); // get the checksum for the data added since last clear
}
-
引入的 core.p4 是P4语言的内置库,定义了标准数据类型和错误类型
-
bit<4> 是具有4位的位字符串的类型。
-
语法4w0xF表示使用4位表示值15。 另一种表示法是4w15。在许多情况下,可以省略width修饰符,只写15。
-
error是用于保存错误代码的内置P4类型
-
Parser 声明
parser Parser<H>(packet_in b, out H parsedHeaders);
该声明描述了解析器的接口,但尚未描述其实现,该接口将由程序员负责实现。 解析器从packet_in读取其输入,packet_in是在core.p4库中声明的P4 extern对象。 解析器将其输出(关键字out)写入parsedHeaders参数。 此参数的类型为H,但是具体类型不知道,需要程序员提供。
-
Match-Action pipeline 声明
control Pipe<H>(inout H headers, in error parseError,// parser error in InControl inCtrl,// input port out OutControl outCtrl); // output port
该声明需要输入3个参数: 1、解析后的数据包头,2、解析器错误parseError 3、inCtrl控制数据。上图指出了这些信息的不同来源。 pipeline将其输出写入outCtrl,并且它必须在适当位置更新要由Deparser使用的包头。
-
Package 定义
package VSS<H>
类型变量H表示尚待用户稍后提供的类型,但未知。 在这种情况下,H是用户程序将要处理的包头的类型。 Parser将生成这些包头的解析表示,并且match-action管道将在适当位置更新输入包头以生成输出包头。
4、代码实现文件
上图展示了VSS交换机数据处理流程。下面用代码来实现上图中的逻辑。
// Include P4 core library
# include <core.p4>
// Include very simple switch architecture declarations
# include "very_simple_switch_model.p4"
// This program processes packets comprising an Ethernet and an IPv4
// header, and it forwards packets using the destination IP address
typedef bit<48> EthernetAddress;
typedef bit<32> IPv4Address;
// Standard Ethernet header
header Ethernet_h {
EthernetAddress dstAddr;
EthernetAddress srcAddr;
bit<16> etherType;
}
// IPv4 header (without options)
header IPv4_h {
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;
IPv4Address srcAddr;
IPv4Address dstAddr;
}
// Structure of parsed headers
struct Parsed_packet {
Ethernet_h ethernet;
IPv4_h ip;
}
// Parser section
// User-defined errors that may be signaled during parsing
error {
IPv4OptionsNotSupported,
IPv4IncorrectVersion,
IPv4ChecksumError
}
parser TopParser(packet_in b, out Parsed_packet p) {
Checksum16() ck; // instantiate checksum unit
state start {
b.extract(p.ethernet);
transition select(p.ethernet.etherType) {
0x0800: parse_ipv4;
// no default rule: all other packets rejected
}
}
state parse_ipv4 {
b.extract(p.ip);
verify(p.ip.version == 4w4, error.IPv4IncorrectVersion);
verify(p.ip.ihl == 4w5, error.IPv4OptionsNotSupported);
ck.clear();
ck.update(p.ip);
// Verify that packet checksum is zero
verify(ck.get() == 16w0, error.IPv4ChecksumError);
transition accept;
}
}
// Match-action pipeline section
control TopPipe(inout Parsed_packet headers,
in error parseError, // parser error
in InControl inCtrl, // input port
out OutControl outCtrl) {
IPv4Address nextHop; // local variable
/**
* Indicates that a packet is dropped by setting the
* output port to the DROP_PORT
*/
action Drop_action() {
outCtrl.outputPort = DROP_PORT;
}
/**
* Set the next hop and the output port.
* Decrements ipv4 ttl field.
* @param ivp4_dest ipv4 address of next hop
* @param port output port
*/
action Set_nhop(IPv4Address ipv4_dest, PortId port) {
nextHop = ipv4_dest;
headers.ip.ttl = headers.ip.ttl - 1;
outCtrl.outputPort = port;
}
/**
* Computes address of next IPv4 hop and output port
* based on the IPv4 destination of the current packet.
* Decrements packet IPv4 TTL.
* @param nextHop IPv4 address of next hop
*/
table ipv4_match {
key = { headers.ip.dstAddr: lpm; } // longest-prefix match
actions = {
Drop_action;
Set_nhop;
}
size = 1024;
default_action = Drop_action;
}
/**
* Send the packet to the CPU port
*/
action Send_to_cpu() {
outCtrl.outputPort = CPU_OUT_PORT;
}
/**
* Check packet TTL and send to CPU if expired.
*/
table check_ttl {
key = { headers.ip.ttl: exact; }
actions = { Send_to_cpu; NoAction; }
const default_action = NoAction; // defined in core.p4
}
/**
* Set the destination MAC address of the packet
* @param dmac destination MAC address.
*/
action Set_dmac(EthernetAddress dmac) {
headers.ethernet.dstAddr = dmac;
}
/**
* Set the destination Ethernet address of the packet
* based on the next hop IP address.
* @param nextHop IPv4 address of next hop.
*/
table dmac {
key = { nextHop: exact; }
actions = {
Drop_action;
Set_dmac;
}
size = 1024;
default_action = Drop_action;
}
/**
* Set the source MAC address.
* @param smac: source MAC address to use
*/
action Set_smac(EthernetAddress smac) {
headers.ethernet.srcAddr = smac;
}
/**
* Set the source mac address based on the output port.
*/
table smac {
key = { outCtrl.outputPort: exact; }
actions = {
Drop_action;
Set_smac;
}
size = 16;
default_action = Drop_action;
}
apply {
if (parseError != error.NoError) {
Drop_action(); // invoke drop directly
return;
}
ipv4_match.apply(); // Match result will go into nextHop
if (outCtrl.outputPort == DROP_PORT) return;
check_ttl.apply();
if (outCtrl.outputPort == CPU_OUT_PORT) return;
dmac.apply();
if (outCtrl.outputPort == DROP_PORT) return;
smac.apply();
}
}
// deparser section
control TopDeparser(inout Parsed_packet p, packet_out b) {
Checksum16() ck;
apply {
b.emit(p.ethernet);
if (p.ip.isValid()) {
ck.clear(); // prepare checksum unit
p.ip.hdrChecksum = 16w0; // clear checksum
ck.update(p.ip); // compute new checksum.
p.ip.hdrChecksum = ck.get();
}
b.emit(p.ip);
}
}
// Instantiate the top-level VSS package
VSS(TopParser(),
TopPipe(),
TopDeparser()) main;
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)