本教程的目标是使用P4编写的自定义协议头来实现一个基本的计算器。 报头将包含一个要执行的操作和两个操作数。 当交换机接收到计算器包头时,它将对操作数执行操作,并将结果返回给发送者。
0 1 2 3
+----------------+----------------+----------------+---------------+
| P | 4 | Version | Op |
+----------------+----------------+----------------+---------------+
| Operand A |
+----------------+----------------+----------------+---------------+
| Operand B |
+----------------+----------------+----------------+---------------+
| Result |
+----------------+----------------+----------------+---------------+
/* -*- P4_16 -*- */
/*
* P4 Calculator
*
* This program implements a simple protocol. It can be carried over Ethernet
* (Ethertype 0x1234).
*
* The Protocol header looks like this:
*
* 0 1 2 3
* +----------------+----------------+----------------+---------------+
* | P | 4 | Version | Op |
* +----------------+----------------+----------------+---------------+
* | Operand A |
* +----------------+----------------+----------------+---------------+
* | Operand B |
* +----------------+----------------+----------------+---------------+
* | Result |
* +----------------+----------------+----------------+---------------+
*
* P is an ASCII Letter 'P' (0x50)
* 4 is an ASCII Letter '4' (0x34)
* Version is currently 0.1 (0x01)
* Op is an operation to Perform:
* '+' (0x2b) Result = OperandA + OperandB
* '-' (0x2d) Result = OperandA - OperandB
* '&' (0x26) Result = OperandA & OperandB
* '|' (0x7c) Result = OperandA | OperandB
* '^' (0x5e) Result = OperandA ^ OperandB
*
* The device receives a packet, performs the requested operation, fills in the
* result and sends the packet back out of the same port it came in on, while
* swapping the source and destination addresses.
*
* If an unknown operation is specified or the header is not valid, the packet
* is dropped
*/
#include <core.p4>
#include <v1model.p4>
/*
* Define the headers the program will recognize
*/
/*
* Standard ethernet header
*/
header ethernet_t {
bit<48> dstAddr;
bit<48> srcAddr;
bit<16> etherType;
}
/*
* This is a custom protocol header for the calculator. We'll use
* ethertype 0x1234 for is (see parser)
*/
const bit<16> P4CALC_ETYPE = 0x1234;
const bit<8> P4CALC_P = 0x50; // 'P'
const bit<8> P4CALC_4 = 0x34; // '4'
const bit<8> P4CALC_VER = 0x01; // v0.1
const bit<8> P4CALC_PLUS = 0x2b; // '+'
const bit<8> P4CALC_MINUS = 0x2d; // '-'
const bit<8> P4CALC_AND = 0x26; // '&'
const bit<8> P4CALC_OR = 0x7c; // '|'
const bit<8> P4CALC_CARET = 0x5e; // '^'
//用于计算的头文件
header p4calc_t {
bit<8> p;
bit<8> four;
bit<8> ver;
bit<8> op;
bit<32> operand_a;
bit<32> operand_b;
bit<32> res;
}
/*
* All headers, used in the program needs to be assembed into a single struct.
* We only need to declare the type, but there is no need to instantiate it,
* because it is done "by the architecture", i.e. outside of P4 functions
*/
struct headers {
ethernet_t ethernet;
p4calc_t p4calc;
}
/*
* All metadata, globally used in the program, also needs to be assembed
* into a single struct. As in the case of the headers, we only need to
* declare the type, but there is no need to instantiate it,
* because it is done "by the architecture", i.e. outside of P4 functions
*/
struct metadata {
/* In our case it is empty */
}
/*************************************************************************
*********************** 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 {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
P4CALC_ETYPE : check_p4calc;
default : accept;
}
}
//packet_in包抽象提供的超前方法计算输入包的一组位,而不前进nextBitIndex指针。
//与extract类似,如果包中没有足够的位,它将转换为拒绝并设置错误。 可以按如下方式调用lookahead方法
state check_p4calc {
transition select(packet.lookahead<p4calc_t>().p,
packet.lookahead<p4calc_t>().four,
packet.lookahead<p4calc_t>().ver) {
(P4CALC_P, P4CALC_4, P4CALC_VER) : parse_p4calc;
default : accept;
}
}
state parse_p4calc {
packet.extract(hdr.p4calc);
transition accept;
}
}
/*************************************************************************
************ 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 { }
}
/*************************************************************************
************** 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 send_back(bit<32> result) {
bit<48> tmp;
/* Put the result back in */
hdr.p4calc.res = result;
/* Swap the MAC addresses */
tmp = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = hdr.ethernet.srcAddr;
hdr.ethernet.srcAddr = tmp;
/* Send the packet back to the port it came from */
standard_metadata.egress_spec = standard_metadata.ingress_port;
}
action operation_add() {
send_back(hdr.p4calc.operand_a + hdr.p4calc.operand_b);
}
action operation_sub() {
send_back(hdr.p4calc.operand_a - hdr.p4calc.operand_b);
}
action operation_and() {
send_back(hdr.p4calc.operand_a & hdr.p4calc.operand_b);
}
action operation_or() {
send_back(hdr.p4calc.operand_a | hdr.p4calc.operand_b);
}
action operation_xor() {
send_back(hdr.p4calc.operand_a ^ hdr.p4calc.operand_b);
}
action operation_drop() {
mark_to_drop(standard_metadata);
}
table calculate {
key = {
hdr.p4calc.op : exact;
}
actions = {
operation_add;
operation_sub;
operation_and;
operation_or;
operation_xor;
operation_drop;
}
const default_action = operation_drop();
const entries = {
P4CALC_PLUS : operation_add();
P4CALC_MINUS: operation_sub();
P4CALC_AND : operation_and();
P4CALC_OR : operation_or();
P4CALC_CARET: operation_xor();
}
}
//虽然表项通常由控制平面安装,但也可以在编译时用一组表项初始化表项。 这在表用于实现固定算法的情况下非常有用——静态定义的表条目可以在P4中直接表示这些算法,这允许编译器推断表的实际使用方式,并可能为资源有限的目标做出更好的分配决策。 当程序加载到目标程序时,P4源中声明的条目将安装在表中。
apply {
if (hdr.p4calc.isValid()) {
calculate.apply();
} else {
operation_drop();
}
}
}
/*************************************************************************
**************** 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 { }
}
/*************************************************************************
************* 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 { }
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.p4calc);
}
}
/*************************************************************************
*********************** S W I T T C H **********************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;
{
"target": "bmv2",
"p4info": "build/calc.p4.p4info.txt",
"bmv2_json": "build/calc.json",
"table_entries": [ ]
}
{
"hosts": {
"h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:01"},
"h2": {"ip": "10.0.1.2/24", "mac": "08:00:00:00:01:02"}
},
"switches": {
"s1": { "runtime_json" : "s1-runtime.json" }
},
"links": [
["h1", "s1-p1"], ["h2", "s1-p2"]
]
}