diff --git a/examples/calc.app/README.md b/examples/calc.app/README.md new file mode 100644 index 0000000..48ed363 --- /dev/null +++ b/examples/calc.app/README.md @@ -0,0 +1,108 @@ +# Implementing a P4 Calculator + +## Introduction + +The objective of this tutorial is to implement a basic calculator +using a custom protocol header written in P4. The header will contain +an operation to perform and two operands. When a switch receives a +calculator packet header, it will execute the operation on the +operands, and return the result to the sender. + +## Step 1: Run the (incomplete) starter code + +The directory with this README also contains a skeleton P4 program, +`calc.p4`, which initially drops all packets. Your job will be to +extend it to properly implement the calculator logic. + +As a first step, compile the incomplete `calc.p4` and bring up a +switch in Mininet to test its behavior. + +1. In your shell, run: + ```bash + p4app run examples/calc.app + ``` + This will: + * start a p4app container + + * compile `calc.p4`, and + + * start a Mininet instance with one switches (`s1`) connected to + one host (`h1`). + +2. We've written a small Python-based driver program that will allow +you to test your calculator. You can run the driver program directly +from the Mininet command prompt: + +``` +mininet> h1 python calc.py +> +``` + +3. The driver program will provide a new prompt, at which you can type +basic expressions. The test harness will parse your expression, and +prepare a packet with the corresponding operator and operands. It will +then send a packet to the switch for evaluation. When the switch +returns the result of the computation, the test program will print the +result. However, because the calculator program is not implemented, +you should see an error message. + +``` +> 1+1 +Didn't receive response +> +``` + +## Step 2: Implement Calculator + +To implement the calculator, you will need to define a custom +calculator header, and implement the switch logic to parse header, +perform the requested operation, write the result in the header, and +return the packet to the sender. + +We will use the following header format: + + 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 + + +We will assume that the calculator header is carried over Ethernet, +and we will use the Ethernet type 0x1234 to indicate the presence of +the header. + +Given what you have learned so far, your task is to implement the P4 +calculator program. There is no control plane logic, so you need only +worry about the data plane implementation. + +A working calculator implementation will parse the custom headers, +execute the mathematical operation, write the result in the result +field, and return the packet to the sender. + +## Step 3: Run your solution + +Follow the instructions from Step 1. This time, you should see the +correct result: + +``` +> 1+1 +2 +> +``` \ No newline at end of file diff --git a/examples/calc.app/calc.config b/examples/calc.app/calc.config new file mode 100644 index 0000000..e69de29 diff --git a/examples/calc.app/calc.p4 b/examples/calc.app/calc.p4 new file mode 100644 index 0000000..6e5cc90 --- /dev/null +++ b/examples/calc.app/calc.p4 @@ -0,0 +1,250 @@ +/* -*- 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 +#include + +/* + * 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 it (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> op; +/* TODO + * fill p4calc_t header with P, four, ver, op, operand_a, operand_b, and res + entries based on above protocol header definition. + */ +} + +/* + * All headers, used in the program needs to be assembled 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 assembled + * 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; + } + } + + state check_p4calc { + /* TODO: just uncomment the following parse block */ + /* + transition select(packet.lookahead().p, + packet.lookahead().four, + packet.lookahead().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) { + /* TODO + * - put the result back in hdr.p4calc.res + * - swap MAC addresses in hdr.ethernet.dstAddr and + * hdr.ethernet.srcAddr using a temp variable + * - Send the packet back to the port it came from + by saving standard_metadata.ingress_port into + standard_metadata.egress_spec + */ + } + + action operation_add() { + /* TODO call send_back with operand_a + operand_b */ + } + + action operation_sub() { + /* TODO call send_back with operand_a - operand_b */ + } + + action operation_and() { + /* TODO call send_back with operand_a & operand_b */ + } + + action operation_or() { + /* TODO call send_back with operand_a | operand_b */ + } + + action operation_xor() { + /* TODO call send_back with operand_a ^ 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(); + } + } + + 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; diff --git a/examples/calc.app/calc.py b/examples/calc.app/calc.py new file mode 100644 index 0000000..aaba3fe --- /dev/null +++ b/examples/calc.app/calc.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +import argparse +import sys +import socket +import random +import struct +import re + +from scapy.all import sendp, send, srp1 +from scapy.all import Packet, hexdump +from scapy.all import Ether, StrFixedLenField, XByteField, IntField +from scapy.all import bind_layers +import readline + +class P4calc(Packet): + name = "P4calc" + fields_desc = [ StrFixedLenField("P", "P", length=1), + StrFixedLenField("Four", "4", length=1), + XByteField("version", 0x01), + StrFixedLenField("op", "+", length=1), + IntField("operand_a", 0), + IntField("operand_b", 0), + IntField("result", 0xDEADBABE)] + +bind_layers(Ether, P4calc, type=0x1234) + +class NumParseError(Exception): + pass + +class OpParseError(Exception): + pass + +class Token: + def __init__(self,type,value = None): + self.type = type + self.value = value + +def num_parser(s, i, ts): + pattern = "^\s*([0-9]+)\s*" + match = re.match(pattern,s[i:]) + if match: + ts.append(Token('num', match.group(1))) + return i + match.end(), ts + raise NumParseError('Expected number literal.') + + +def op_parser(s, i, ts): + pattern = "^\s*([-+&|^])\s*" + match = re.match(pattern,s[i:]) + if match: + ts.append(Token('num', match.group(1))) + return i + match.end(), ts + raise NumParseError("Expected binary operator '-', '+', '&', '|', or '^'.") + + +def make_seq(p1, p2): + def parse(s, i, ts): + i,ts2 = p1(s,i,ts) + return p2(s,i,ts2) + return parse + + +def main(): + + p = make_seq(num_parser, make_seq(op_parser,num_parser)) + s = '' + + while True: + s = str(raw_input('> ')) + if s == "quit": + break + try: + i,ts = p(s,0,[]) + pkt = Ether(dst='00:04:00:00:00:00', type=0x1234) / P4calc(op=ts[1].value, + operand_a=int(ts[0].value), + operand_b=int(ts[2].value)) + pkt = pkt/' ' + +# pkt.show() + resp = srp1(pkt, timeout=1, verbose=False) + if resp: + p4calc=resp[P4calc] + if p4calc: + print '%s = %d' % (s, p4calc.result) + else: + print "cannot find P4calc header in the packet" + else: + print "Didn't receive response" + except Exception as error: + print error + + +if __name__ == '__main__': + main() diff --git a/examples/calc.app/p4app.json b/examples/calc.app/p4app.json new file mode 100644 index 0000000..ef2fc85 --- /dev/null +++ b/examples/calc.app/p4app.json @@ -0,0 +1,10 @@ +{ + "program": "calc.p4", + "language": "p4-16", + "targets": { + "mininet": { + "num-hosts": 1, + "switch-config": "calc.config" + } + } +} diff --git a/examples/calc.app/solution/calc.p4 b/examples/calc.app/solution/calc.p4 new file mode 100644 index 0000000..2c68bd4 --- /dev/null +++ b/examples/calc.app/solution/calc.p4 @@ -0,0 +1,256 @@ +/* -*- 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 +#include + +/* + * 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; + } + } + + state check_p4calc { + transition select(packet.lookahead().p, + packet.lookahead().four, + packet.lookahead().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(); + } + } + + + 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;