From 996bbbad31e1b3fd7ceb56f6b1479553fd268ea2 Mon Sep 17 00:00:00 2001 From: Antonin Bas Date: Thu, 22 Oct 2015 16:04:30 -0700 Subject: [PATCH 1/3] added 3 bmv2 examples: copy_to_cpu, meter, TLV_parsing --- examples/.gitignore | 10 + examples/README.md | 59 ++++++ examples/TLV_parsing/README.md | 62 ++++++ examples/TLV_parsing/commands.txt | 4 + examples/TLV_parsing/p4src/TLV_parsing.p4 | 227 ++++++++++++++++++++++ examples/TLV_parsing/run_switch.sh | 41 ++++ examples/TLV_parsing/send_one.py | 6 + examples/copy_to_cpu/README.md | 47 +++++ examples/copy_to_cpu/commands.txt | 4 + examples/copy_to_cpu/p4src/copy_to_cpu.p4 | 87 +++++++++ examples/copy_to_cpu/receive.py | 3 + examples/copy_to_cpu/run_switch.sh | 41 ++++ examples/copy_to_cpu/send_one.py | 6 + examples/env.sh | 8 + examples/meter/README.md | 65 +++++++ examples/meter/commands.txt | 5 + examples/meter/p4src/meter.p4 | 82 ++++++++ examples/meter/run_switch.sh | 41 ++++ examples/meter/send_and_receive.py | 44 +++++ examples/veth_setup.sh | 26 +++ 20 files changed, 868 insertions(+) create mode 100644 examples/.gitignore create mode 100644 examples/README.md create mode 100644 examples/TLV_parsing/README.md create mode 100644 examples/TLV_parsing/commands.txt create mode 100644 examples/TLV_parsing/p4src/TLV_parsing.p4 create mode 100755 examples/TLV_parsing/run_switch.sh create mode 100644 examples/TLV_parsing/send_one.py create mode 100644 examples/copy_to_cpu/README.md create mode 100644 examples/copy_to_cpu/commands.txt create mode 100644 examples/copy_to_cpu/p4src/copy_to_cpu.p4 create mode 100644 examples/copy_to_cpu/receive.py create mode 100755 examples/copy_to_cpu/run_switch.sh create mode 100644 examples/copy_to_cpu/send_one.py create mode 100644 examples/env.sh create mode 100644 examples/meter/README.md create mode 100644 examples/meter/commands.txt create mode 100644 examples/meter/p4src/meter.p4 create mode 100755 examples/meter/run_switch.sh create mode 100644 examples/meter/send_and_receive.py create mode 100755 examples/veth_setup.sh diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..7a46f98 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,10 @@ +# Python byte code +*.pyc + +# Emacs +*~ + +# Compiled JSON +*.json + +*.pcap diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..2a50344 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,59 @@ +# P4 Code Samples + +## Introduction + +This directory includes P4 code samples, meant to be run on bmv2, which +illustrates several more advanced features of P4. The following samples are +included: + +- `copy_to_cpu`: how to use the `clone_ingress_to_egress primitive` to clone the + packet, encapsulate it and send it to a special port. +- `meter`: how to use indirect meters in P4. +- `TLV_parsing`: how to parse IPv4 options + +All examples are orgranized the same way, with a `p4src` directory containing +the P4 source code, and a `README` file describing the P4 program and explaining +how to run a quick demonstration. + +## Obtaining required software + +To complete the exercises, you will need to clone 2 p4lang Github repositories +and install their dependencies. To clonde the repositories: + +- `git clone https://github.com/p4lang/behavioral-model.git bmv2` +- `git clone https://github.com/p4lang/p4c-bm.git p4c-bmv2` + +The first repository ([bmv2](https://github.com/p4lang/behavioral-model)) is the +second version of the behavioral model. It is a C++ software switch that will +behave according to your P4 program. The second repository +([p4c-bmv2](https://github.com/p4lang/p4c-bm)) is the compiler for the +behavioral model: it takes P4 program and output a JSON file which can be loaded +by the behavioral model. + +Each of these repositories come with dependencies. `p4c-bmv2` is a Python +repository and installing the required Python dependencies is very easy to do +using `pip`: `sudo pip install -r requirements.txt`. + +`bmv2` is a C++ repository and has more external dependencies. They are listed +in the +[README](https://github.com/p4lang/behavioral-model/blob/master/README.md). If +you are running Ubuntu 14.04+, the dependencies should be easy to install (you +can use the `install_deps.sh` script that comes with `bmv2`). Do not forget to +build the code once all the dependencies have been installed: + +- `./autogen.sh` +- `./configure` +- `make` + +## Before starting the exercises + +You need to tell us where you cloned the `bmv2` and `p4c-bm` repositories +:). Please update the values of the shell variables `BMV2_PATH` and +`P4C_BM_PATH` in the `env.sh` file - located in this directory. Note that if you +cloned both repositories in the same directory as this one (`tutorials`), you +will not need to change the value of the variables. + +You will also need to run the `veth_setup.sh` script included in this directory +as `sudo` to setup the veth interfaces needed by the switch. + +That's all :) diff --git a/examples/TLV_parsing/README.md b/examples/TLV_parsing/README.md new file mode 100644 index 0000000..e5e4066 --- /dev/null +++ b/examples/TLV_parsing/README.md @@ -0,0 +1,62 @@ +# Copy to CPU + +## Description + +This program illustrates how to parse IPv4 options with bmv2. There is a very +easy way to parse IPv4 options in P4 using a single variable length +field. However, this means that options are not parsed individually, but +together, as one block. All the options are parsed to a single field, which is +fine for many use cases but can be insufficient in some. In this example, we use +TLV parsing to parse all options separately to their own header instance. + +The program is quite straightforward. The following IPv4 options are supported: + +- end of list +- no-op +- security +- timestamp + +There is one important caveat: when compiling the P4 program, a strict ordering +of all packet headers has to be known. This is usually done by inspecting the +parse graph and running a topological sorting algorithm on it. However this +algorithm will not work if there exists loops in the header graph, as is the +case with TLV parsing. There is not yet an official way of enforcing your own +header ordeing in the P4 program, so we had to bypass this restriction by using +a `@pragma`, as you can see in the code: + + @pragma header_ordering ethernet ipv4_base ipv4_option_security ipv4_option_NOP ipv4_option_timestamp ipv4_option_EOL + +This `@pragma` instruction will be interpreted by the P4 -> bmv2 compiler. + +This order is used by the deparser, when sending a packet out of the egress +port, which means that the option layout for the outgoing packet may not be the +same as for the incoming packet. + +The table `format_options` makes sure that the IPv4 header is formatted +correctly in the outgoing packet. + +Note that the P4 program assumes the incoming packet is correctly formatted. We +do not perform any sanity checking because *parser execptions* are not yet +supported by bmv2. + +So in a nutshell, all this P4 program does is: + +1. parse the IPv4 options for the incoming packet +2. re-serialize the packet again, with a potentially different order for options + +### Running the demo + +We provide a small demo to let you test the program. It consists of the +following scripts: +- [run_switch.sh] (run_switch.sh): compile the P4 program and starts the switch, + also configures the data plane by running the CLI [commands] (commands.txt). +- [send_one.py] (send_one.py): send an IPv4 packet with options + +To run the demo: +- start the switch and configure the tables: `sudo ./run_switch.sh`. +- run the Python script: `sudo python send_one.py`. + +Then inspect the `pcap` file for port 0 of the switch (`veth0.pcap`) with +Wireshark. You will observe that the order of the IPv4 options has changed but +that the outgoing packet contains all the options of the incoming packet and is +perfectly valid (with a correct checksum). diff --git a/examples/TLV_parsing/commands.txt b/examples/TLV_parsing/commands.txt new file mode 100644 index 0000000..504d25e --- /dev/null +++ b/examples/TLV_parsing/commands.txt @@ -0,0 +1,4 @@ +table_set_default format_options _nop +table_add format_options format_options_security 1 0 => +table_add format_options format_options_timestamp 0 1 => +table_add format_options format_options_both 1 1 => diff --git a/examples/TLV_parsing/p4src/TLV_parsing.p4 b/examples/TLV_parsing/p4src/TLV_parsing.p4 new file mode 100644 index 0000000..525b2a3 --- /dev/null +++ b/examples/TLV_parsing/p4src/TLV_parsing.p4 @@ -0,0 +1,227 @@ +header_type ethernet_t { + fields { + dstAddr : 48; + srcAddr : 48; + etherType : 16; + } +} + +header_type ipv4_base_t { + fields { + version : 4; + ihl : 4; + diffserv : 8; + totalLen : 16; + identification : 16; + flags : 3; + fragOffset : 13; + ttl : 8; + protocol : 8; + hdrChecksum : 16; + srcAddr : 32; + dstAddr : 32; + } +} + +// End of Option List +#define IPV4_OPTION_EOL_VALUE 0x00 +header_type ipv4_option_EOL_t { + fields { + value : 8; + } +} + +// No operation +#define IPV4_OPTION_NOP_VALUE 0x01 +header_type ipv4_option_NOP_t { + fields { + value : 8; + } +} + +#define IPV4_OPTION_SECURITY_VALUE 0x82 +header_type ipv4_option_security_t { + fields { + value : 8; + len : 8; + security : 72; + } +} + +#define IPV4_OPTION_TIMESTAMP_VALUE 0x44 +header_type ipv4_option_timestamp_t { + fields { + value : 8; + len : 8; + data : *; + } + length : len; + max_length : 40; +} + +header_type intrinsic_metadata_t { + fields { + mcast_grp : 4; + egress_rid : 4; + mcast_hash : 16; + lf_field_list: 32; + } +} + +header_type my_metadata_t { + fields { + parse_ipv4_counter : 8; + } +} + +header ethernet_t ethernet; +header ipv4_base_t ipv4_base; +header ipv4_option_EOL_t ipv4_option_EOL[3]; +header ipv4_option_NOP_t ipv4_option_NOP[3]; +header ipv4_option_security_t ipv4_option_security; +header ipv4_option_timestamp_t ipv4_option_timestamp; +metadata intrinsic_metadata_t intrinsic_metadata; +metadata my_metadata_t my_metadata; + +@pragma header_ordering ethernet ipv4_base ipv4_option_security ipv4_option_NOP ipv4_option_timestamp ipv4_option_EOL +parser start { + return parse_ethernet; +} + +#define ETHERTYPE_IPV4 0x0800 +parser parse_ethernet { + extract(ethernet); + return select(ethernet.etherType) { + ETHERTYPE_IPV4 : parse_ipv4; + default: ingress; + } +} + +parser parse_ipv4 { + extract(ipv4_base); + set_metadata(my_metadata.parse_ipv4_counter, ipv4_base.ihl * 4 - 20); + return select(ipv4_base.ihl) { + 0x05 : ingress; + default : parse_ipv4_options; + } +} + +parser parse_ipv4_options { + // match on byte counter and option value + return select(my_metadata.parse_ipv4_counter, current(0, 8)) { + 0x0000 mask 0xff00 : ingress; + 0x0000 mask 0x00ff : parse_ipv4_option_EOL; + 0x0001 mask 0x00ff : parse_ipv4_option_NOP; + 0x0082 mask 0x00ff : parse_ipv4_option_security; + 0x0044 mask 0x00ff : parse_ipv4_option_timestamp; + } +} + +parser parse_ipv4_option_EOL { + extract(ipv4_option_EOL[next]); + set_metadata(my_metadata.parse_ipv4_counter, + my_metadata.parse_ipv4_counter - 1); + return parse_ipv4_options; +} + +parser parse_ipv4_option_NOP { + extract(ipv4_option_NOP[next]); + set_metadata(my_metadata.parse_ipv4_counter, + my_metadata.parse_ipv4_counter - 1); + return parse_ipv4_options; +} + +parser parse_ipv4_option_security { + extract(ipv4_option_security); + // security option must have length 11 bytes + set_metadata(my_metadata.parse_ipv4_counter, + my_metadata.parse_ipv4_counter - 11); + return parse_ipv4_options; +} + +parser parse_ipv4_option_timestamp { + extract(ipv4_option_timestamp); + set_metadata(my_metadata.parse_ipv4_counter, + my_metadata.parse_ipv4_counter - ipv4_option_timestamp.len); + return parse_ipv4_options; +} + +field_list ipv4_checksum_list { + ipv4_base.version; + ipv4_base.ihl; + ipv4_base.diffserv; + ipv4_base.totalLen; + ipv4_base.identification; + ipv4_base.flags; + ipv4_base.fragOffset; + ipv4_base.ttl; + ipv4_base.protocol; + ipv4_base.srcAddr; + ipv4_base.dstAddr; + ipv4_option_security; + ipv4_option_NOP[0]; + ipv4_option_timestamp; +} + +field_list_calculation ipv4_checksum { + input { + ipv4_checksum_list; + } + algorithm : csum16; + output_width : 16; +} + +calculated_field ipv4_base.hdrChecksum { + update ipv4_checksum; +} + +action _drop() { + drop(); +} + +action _nop() { +} + +control ingress { + +} + +action format_options_security() { + pop(ipv4_option_NOP, 3); + pop(ipv4_option_EOL, 3); + push(ipv4_option_EOL, 1); + modify_field(ipv4_base.ihl, 8); +} + +action format_options_timestamp() { + pop(ipv4_option_NOP, 3); + pop(ipv4_option_EOL, 3); + // timestamp option is word-aligned so no need for NOP or EOL + modify_field(ipv4_base.ihl, 5 + (ipv4_option_timestamp.len >> 3)); +} + +action format_options_both() { + pop(ipv4_option_NOP, 3); + pop(ipv4_option_EOL, 3); + push(ipv4_option_NOP, 1); + modify_field(ipv4_option_NOP[0].value, IPV4_OPTION_NOP_VALUE); + modify_field(ipv4_base.ihl, 8 + (ipv4_option_timestamp.len >> 2)); +} + +table format_options { + reads { + ipv4_option_security : valid; + ipv4_option_timestamp : valid; + } + actions { + format_options_security; + format_options_timestamp; + format_options_both; + _nop; + } + size : 4; +} + +control egress { + apply(format_options); +} diff --git a/examples/TLV_parsing/run_switch.sh b/examples/TLV_parsing/run_switch.sh new file mode 100755 index 0000000..5673c64 --- /dev/null +++ b/examples/TLV_parsing/run_switch.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +source $THIS_DIR/../env.sh + +P4C_BM_SCRIPT=$P4C_BM_PATH/p4c_bm/__main__.py + +SWITCH_PATH=$BMV2_PATH/targets/simple_switch/simple_switch + +CLI_PATH=$BMV2_PATH/targets/simple_switch/sswitch_CLI + +# Probably not very elegant but it works nice here: we enable interactive mode +# to be able to use fg. We start the switch in the background, sleep for 2 +# minutes to give it time to start, then add the entries and put the switch +# process back in the foreground +set -m +$P4C_BM_SCRIPT p4src/TLV_parsing.p4 --json TLV_parsing.json +sudo echo "sudo" > /dev/null +sudo $BMV2_PATH/targets/simple_switch/simple_switch TLV_parsing.json \ + -i 0@veth0 -i 1@veth2 -i 2@veth4 -i 3@veth6 -i 4@veth8 \ + --nanolog ipc:///tmp/bm-0-log.ipc \ + --pcap & +sleep 2 +$CLI_PATH TLV_parsing.json < commands.txt +echo "READY!!!" +fg diff --git a/examples/TLV_parsing/send_one.py b/examples/TLV_parsing/send_one.py new file mode 100644 index 0000000..56d7d7f --- /dev/null +++ b/examples/TLV_parsing/send_one.py @@ -0,0 +1,6 @@ +from scapy.all import * + +p = Ether() / IP(options=IPOption('\x44\x0c\x05\x00\x01\x02\x03\x04\x05\x06\x07\x08') / IPOption('\x82\x0b\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9')) / IPOption('\x00') / TCP() / "aaaaaaaaaaa" +# p.show() +hexdump(p) +sendp(p, iface = "veth0") diff --git a/examples/copy_to_cpu/README.md b/examples/copy_to_cpu/README.md new file mode 100644 index 0000000..862a542 --- /dev/null +++ b/examples/copy_to_cpu/README.md @@ -0,0 +1,47 @@ +# Copy to CPU + +## Description + +This program illustrates as simply as possible how to *send packets to CPU* +(e.g. to a controller). + +The P4 program does the following: +- incoming packets are mirrored to the CPU port using the + `clone_ingress_pkt_to_egress` action primitive +- packets mirrored to CPU are encapsulated with a custom `cpu_header` which + includes 2 fields: `device` (1 byte, set to `0`) and `reason` (one byte, set + to `0xab`) +- the original packet is dropped in the egress pipeline + +Take a look at the [P4 code] (p4src/copy_to_cpu.p4). The program is very short +and should be easy to understand. You will notice that we use a mirror session +id of `250` in the program. This number is not relevant in itself, but needs to +be consistent between the P4 program and the runtime application. + +### Running the demo + +We provide a small demo to let you test the program. It consists of the +following scripts: +- [run_switch.sh] (run_switch.sh): compile the P4 program and starts the switch, + also configures the data plane by running the CLI [commands] (commands.txt) +- [receive.py] (receive.py): sniff packets on port 3 (veth6) and print a hexdump + of them +- [send_one.py] (send_one.py): send one simple IPv4 packet on port 0 (veth0) + +If you take a look at [commands.txt] (commands.txt), you'll notice the following +command: `mirroring_add 250 3`. This means that all the cloned packets with +mirror id `250` will be sent to port `3`, which is our de facto *CPU port*. This +is the reason why [receive.py] (receive.py) listens for incoming packets on port +`3`. + +To run the demo: +- start the switch and configure the tables and the mirroring session: `sudo + ./run_switch.sh` +- start the CPU port listener: `sudo python receive.py` +- send packets with `sudo python send_one.py`. Every time you send one packet, + it should be displayed by the listener, encapsulated with our CPU header. + +This is a very simple example obviously. Feel free to build upon it. For +example, instead of dropping the original packet, you could try to broadcast it +out of every non-ingress port to have a working L2 switch. You could also build +a L2 controller which receives CPU packets and modifies tables appropriately. diff --git a/examples/copy_to_cpu/commands.txt b/examples/copy_to_cpu/commands.txt new file mode 100644 index 0000000..906c31f --- /dev/null +++ b/examples/copy_to_cpu/commands.txt @@ -0,0 +1,4 @@ +table_set_default copy_to_cpu do_copy_to_cpu +table_set_default redirect _drop +table_add redirect do_cpu_encap 1 => +mirroring_add 250 3 diff --git a/examples/copy_to_cpu/p4src/copy_to_cpu.p4 b/examples/copy_to_cpu/p4src/copy_to_cpu.p4 new file mode 100644 index 0000000..bcc388c --- /dev/null +++ b/examples/copy_to_cpu/p4src/copy_to_cpu.p4 @@ -0,0 +1,87 @@ +header_type ethernet_t { + fields { + dstAddr : 48; + srcAddr : 48; + etherType : 16; + } +} + +header_type intrinsic_metadata_t { + fields { + mcast_grp : 4; + egress_rid : 4; + mcast_hash : 16; + lf_field_list: 32; + } +} + +header_type cpu_header_t { + fields { + device: 8; + reason: 8; + } +} + +header cpu_header_t cpu_header; + +parser start { + return select(current(0, 64)) { + 0 : parse_cpu_header; + default: parse_ethernet; + } +} + +header ethernet_t ethernet; +metadata intrinsic_metadata_t intrinsic_metadata; + +parser parse_ethernet { + extract(ethernet); + return ingress; +} + +parser parse_cpu_header { + extract(cpu_header); + return parse_ethernet; +} + +action _drop() { + drop(); +} + +action _nop() { +} + +#define CPU_MIRROR_SESSION_ID 250 + +field_list copy_to_cpu_fields { + standard_metadata; +} + +action do_copy_to_cpu() { + clone_ingress_pkt_to_egress(CPU_MIRROR_SESSION_ID, copy_to_cpu_fields); +} + +table copy_to_cpu { + actions {do_copy_to_cpu;} + size : 1; +} + +control ingress { + apply(copy_to_cpu); +} + +action do_cpu_encap() { + add_header(cpu_header); + modify_field(cpu_header.device, 0); + modify_field(cpu_header.reason, 0xab); +} + +table redirect { + reads { standard_metadata.instance_type : exact; } + actions { _drop; do_cpu_encap; } + size : 16; +} + +control egress { + apply(redirect); +} diff --git a/examples/copy_to_cpu/receive.py b/examples/copy_to_cpu/receive.py new file mode 100644 index 0000000..908eb32 --- /dev/null +++ b/examples/copy_to_cpu/receive.py @@ -0,0 +1,3 @@ +from scapy.all import * + +sniff(iface = "veth6", prn = lambda x: hexdump(x)) diff --git a/examples/copy_to_cpu/run_switch.sh b/examples/copy_to_cpu/run_switch.sh new file mode 100755 index 0000000..1763fb4 --- /dev/null +++ b/examples/copy_to_cpu/run_switch.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +source $THIS_DIR/../env.sh + +P4C_BM_SCRIPT=$P4C_BM_PATH/p4c_bm/__main__.py + +SWITCH_PATH=$BMV2_PATH/targets/simple_switch/simple_switch + +CLI_PATH=$BMV2_PATH/targets/simple_switch/sswitch_CLI + +# Probably not very elegant but it works nice here: we enable interactive mode +# to be able to use fg. We start the switch in the background, sleep for 2 +# minutes to give it time to start, then add the entries and put the switch +# process back in the foreground +set -m +$P4C_BM_SCRIPT p4src/copy_to_cpu.p4 --json copy_to_cpu.json +sudo echo "sudo" > /dev/null +sudo $BMV2_PATH/targets/simple_switch/simple_switch copy_to_cpu.json \ + -i 0@veth0 -i 1@veth2 -i 2@veth4 -i 3@veth6 -i 4@veth8 \ + --nanolog ipc:///tmp/bm-0-log.ipc \ + --pcap & +sleep 2 +$CLI_PATH copy_to_cpu.json < commands.txt +echo "READY!!!" +fg diff --git a/examples/copy_to_cpu/send_one.py b/examples/copy_to_cpu/send_one.py new file mode 100644 index 0000000..03b9af6 --- /dev/null +++ b/examples/copy_to_cpu/send_one.py @@ -0,0 +1,6 @@ +from scapy.all import * + +p = Ether(dst="aa:bb:cc:dd:ee:ff") / IP(dst="10.0.1.10") / TCP() / "aaaaaaaaaaaaaaaaaaa" +# p.show() +hexdump(p) +sendp(p, iface = "veth0") diff --git a/examples/env.sh b/examples/env.sh new file mode 100644 index 0000000..aedb306 --- /dev/null +++ b/examples/env.sh @@ -0,0 +1,8 @@ +THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +# ---------------- EDIT THIS ------------------ +BMV2_PATH=$THIS_DIR/../../bmv2 +# e.g. BMV2_PATH=$THIS_DIR/../bmv2 +P4C_BM_PATH=$THIS_DIR/../../p4c-bmv2 +# e.g P4C_BM_PATH=$THIS_DIR/../p4c-bm +# ---------------- END ------------------ diff --git a/examples/meter/README.md b/examples/meter/README.md new file mode 100644 index 0000000..1342760 --- /dev/null +++ b/examples/meter/README.md @@ -0,0 +1,65 @@ +# Copy to CPU + +## Description + +This program illustrates as simply as possible how to use meters in P4 with +bmv2. bmv2 uses two-rate three-color meters as described [here] +(https://tools.ietf.org/html/rfc2698). + +For each incoming packet the `m_table` table is applied and the appropriate +meter (based on the packet's source MAC address) is executed. Based on the +observed traffic rate for this sender and the meter's configuration, executing +the meter will yield one of 3 values: `0` (*GREEN*), `1` (*YELLOW*) or `2` +(*RED*). This value will be copied to metadata field `meta.meter_tag`. Note that +if no meter was associated to the sender's MAC address, the table will be a +no-op. This table also redirects all packets - with a known source MAC address- +to port 2 of the switch. + +After that, the packet will go through a second table, `m_filter`, which can +either be a no-op or drop the packet based on how the packet was tagged by the +meter. If you take a look at the [runtime commands] (commands.txt) we wrote for +this example, you will see that we configure the table to drop all the packets +for which the color is not *GREEN* (i.e. all packets for which `meta.meter_tag` +is not `0`). + +The [commands.txt] (commands.txt) file also gives you the meter +configuration. In this case, the first rate is 0.5 packets per second, with a +burst size of 1, and the second rate is 10 packets per second, with a burst size +of 1 also. Feel free to play with the numbers, but these play nicely with the +demonstration below. + +Note that we use an `indirect` meter array, because `direct` ones are not +supported yet by bmv2. + +### Running the demo + +We provide a small demo to let you test the program. It consists of the +following scripts: +- [run_switch.sh] (run_switch.sh): compile the P4 program and starts the switch, + also configures the data plane by running the CLI [commands] (commands.txt). +- [send_and_receive.py] (send_and_receive.py): send packets periodically on port + 0 and listen for packets on port 2. + +To run the demo: +- start the switch and configure the tables and the meters: `sudo + ./run_switch.sh`. +- run the Python script: `sudo python send_and_receive.py 1`. As you can see, + the script takes one argument, which is the time interval (in seconds) between + two consecutive packets. + +If you run the script with an interval of one second, you should observe the +following output: + + Received one + Sent one + Sent one + Received one + Sent one + Sent one + Received one + Sent one + ... + +This is because we send one packet every second, while the first rate of the +meter is 0.5 packets per second. The P4 program therefore drops on average one +packet out of two. diff --git a/examples/meter/commands.txt b/examples/meter/commands.txt new file mode 100644 index 0000000..cce9715 --- /dev/null +++ b/examples/meter/commands.txt @@ -0,0 +1,5 @@ +table_set_default m_table _nop +table_add m_table m_action aa:aa:aa:aa:aa:aa => 0 +table_set_default m_filter _drop +table_add m_filter _nop 0 => +meter_set_rates my_meter 0.0000005:1 0.00001:1 diff --git a/examples/meter/p4src/meter.p4 b/examples/meter/p4src/meter.p4 new file mode 100644 index 0000000..66f115e --- /dev/null +++ b/examples/meter/p4src/meter.p4 @@ -0,0 +1,82 @@ +header_type ethernet_t { + fields { + dstAddr : 48; + srcAddr : 48; + etherType : 16; + } +} + +header_type intrinsic_metadata_t { + fields { + mcast_grp : 4; + egress_rid : 4; + mcast_hash : 16; + lf_field_list: 32; + } +} + +header_type meta_t { + fields { + meter_tag : 32; + } +} + +metadata meta_t meta; + +parser start { + return parse_ethernet; +} + +header ethernet_t ethernet; +metadata intrinsic_metadata_t intrinsic_metadata; + +parser parse_ethernet { + extract(ethernet); + return ingress; +} + +action _drop() { + drop(); +} + +action _nop() { +} + +meter my_meter { + type: packets; // or bytes + static: m_table; + instance_count: 16384; +} + +action m_action(meter_idx) { + execute_meter(my_meter, meter_idx, meta.meter_tag); + modify_field(standard_metadata.egress_spec, 1); +} + +table m_table { + reads { + ethernet.srcAddr : exact; + } + actions { + m_action; _nop; + } + size : 16384; +} + +table m_filter { + reads { + meta.meter_tag : exact; + } + actions { + _drop; _nop; + } + size: 16; +} + +control ingress { + apply(m_table); + apply(m_filter); +} + +control egress { +} diff --git a/examples/meter/run_switch.sh b/examples/meter/run_switch.sh new file mode 100755 index 0000000..f507683 --- /dev/null +++ b/examples/meter/run_switch.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Copyright 2013-present Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +source $THIS_DIR/../env.sh + +P4C_BM_SCRIPT=$P4C_BM_PATH/p4c_bm/__main__.py + +SWITCH_PATH=$BMV2_PATH/targets/simple_switch/simple_switch + +CLI_PATH=$BMV2_PATH/targets/simple_switch/sswitch_CLI + +# Probably not very elegant but it works nice here: we enable interactive mode +# to be able to use fg. We start the switch in the background, sleep for 2 +# minutes to give it time to start, then add the entries and put the switch +# process back in the foreground +set -m +$P4C_BM_SCRIPT p4src/meter.p4 --json meter.json +sudo echo "sudo" > /dev/null +sudo $BMV2_PATH/targets/simple_switch/simple_switch meter.json \ + -i 0@veth0 -i 1@veth2 -i 2@veth4 -i 3@veth6 -i 4@veth8 \ + --nanolog ipc:///tmp/bm-0-log.ipc \ + --pcap & +sleep 2 +$CLI_PATH meter.json < commands.txt +echo "READY!!!" +fg diff --git a/examples/meter/send_and_receive.py b/examples/meter/send_and_receive.py new file mode 100644 index 0000000..4e157db --- /dev/null +++ b/examples/meter/send_and_receive.py @@ -0,0 +1,44 @@ +from scapy.all import * +import sys +import threading + + +big_lock = threading.Lock() + + +class Receiver(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self.daemon = True + + def received(self, p): + big_lock.acquire() + print "Received one" + big_lock.release() + + def run(self): + sniff(iface="veth2", prn=lambda x: self.received(x)) + + +def main(): + try: + packet_int = int(sys.argv[1]) + print "Sending packet with interval", packet_int + except: + print "Usage: sudo python send_and_receive.py " + sys.exit(1) + + Receiver().start() + + p = Ether(src="aa:aa:aa:aa:aa:aa") / IP(dst="10.0.1.10") / TCP() / "aaaaaaaaaaaaaaaaaaa" + + while True: + big_lock.acquire() + sendp(p, iface="veth0", verbose=0) + print "Sent one" + big_lock.release() + time.sleep(packet_int) + + +if __name__ == '__main__': + main() diff --git a/examples/veth_setup.sh b/examples/veth_setup.sh new file mode 100755 index 0000000..4083541 --- /dev/null +++ b/examples/veth_setup.sh @@ -0,0 +1,26 @@ +#!/bin/bash +noOfVeths=18 +if [ $# -eq 1 ]; then + noOfVeths=$1 +fi +echo "No of Veths is $noOfVeths" +idx=0 +let "vethpairs=$noOfVeths/2" +while [ $idx -lt $vethpairs ] +do + intf0="veth$(($idx*2))" + intf1="veth$(($idx*2+1))" + idx=$((idx + 1)) + if ! ip link show $intf0 &> /dev/null; then + ip link add name $intf0 type veth peer name $intf1 + ip link set dev $intf0 up + ip link set dev $intf1 up + TOE_OPTIONS="rx tx sg tso ufo gso gro lro rxvlan txvlan rxhash" + for TOE_OPTION in $TOE_OPTIONS; do + /sbin/ethtool --offload $intf0 "$TOE_OPTION" off + /sbin/ethtool --offload $intf1 "$TOE_OPTION" off + done + fi + sysctl net.ipv6.conf.$intf0.disable_ipv6=1 + sysctl net.ipv6.conf.$intf1.disable_ipv6=1 +done From 22b200ce96637da0b7970a8ed75f74eb515b8d69 Mon Sep 17 00:00:00 2001 From: Antonin Bas Date: Thu, 22 Oct 2015 16:08:11 -0700 Subject: [PATCH 2/3] added Apache header on P4 files --- examples/TLV_parsing/p4src/TLV_parsing.p4 | 16 ++++++++++++++++ examples/copy_to_cpu/p4src/copy_to_cpu.p4 | 16 ++++++++++++++++ examples/meter/p4src/meter.p4 | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/examples/TLV_parsing/p4src/TLV_parsing.p4 b/examples/TLV_parsing/p4src/TLV_parsing.p4 index 525b2a3..02df510 100644 --- a/examples/TLV_parsing/p4src/TLV_parsing.p4 +++ b/examples/TLV_parsing/p4src/TLV_parsing.p4 @@ -1,3 +1,19 @@ +/* +Copyright 2013-present Barefoot Networks, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + header_type ethernet_t { fields { dstAddr : 48; diff --git a/examples/copy_to_cpu/p4src/copy_to_cpu.p4 b/examples/copy_to_cpu/p4src/copy_to_cpu.p4 index bcc388c..1517ead 100644 --- a/examples/copy_to_cpu/p4src/copy_to_cpu.p4 +++ b/examples/copy_to_cpu/p4src/copy_to_cpu.p4 @@ -1,3 +1,19 @@ +/* +Copyright 2013-present Barefoot Networks, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + header_type ethernet_t { fields { dstAddr : 48; diff --git a/examples/meter/p4src/meter.p4 b/examples/meter/p4src/meter.p4 index 66f115e..fb880f8 100644 --- a/examples/meter/p4src/meter.p4 +++ b/examples/meter/p4src/meter.p4 @@ -1,3 +1,19 @@ +/* +Copyright 2013-present Barefoot Networks, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + header_type ethernet_t { fields { dstAddr : 48; From b37be477b633574bab934b5eabcf43d7c801ecb9 Mon Sep 17 00:00:00 2001 From: Antonin Bas Date: Fri, 23 Oct 2015 12:20:29 -0700 Subject: [PATCH 3/3] fixed typos in READMEs --- examples/TLV_parsing/README.md | 2 +- examples/meter/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/TLV_parsing/README.md b/examples/TLV_parsing/README.md index e5e4066..2d4796d 100644 --- a/examples/TLV_parsing/README.md +++ b/examples/TLV_parsing/README.md @@ -1,4 +1,4 @@ -# Copy to CPU +# TLV parsing of IPv4 options ## Description diff --git a/examples/meter/README.md b/examples/meter/README.md index 1342760..eeadd65 100644 --- a/examples/meter/README.md +++ b/examples/meter/README.md @@ -1,4 +1,4 @@ -# Copy to CPU +# Meter ## Description