added 3 bmv2 examples: copy_to_cpu, meter, TLV_parsing
This commit is contained in:
parent
c8205b938b
commit
996bbbad31
10
examples/.gitignore
vendored
Normal file
10
examples/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Python byte code
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Emacs
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Compiled JSON
|
||||||
|
*.json
|
||||||
|
|
||||||
|
*.pcap
|
59
examples/README.md
Normal file
59
examples/README.md
Normal file
@ -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 :)
|
62
examples/TLV_parsing/README.md
Normal file
62
examples/TLV_parsing/README.md
Normal file
@ -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).
|
4
examples/TLV_parsing/commands.txt
Normal file
4
examples/TLV_parsing/commands.txt
Normal file
@ -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 =>
|
227
examples/TLV_parsing/p4src/TLV_parsing.p4
Normal file
227
examples/TLV_parsing/p4src/TLV_parsing.p4
Normal file
@ -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);
|
||||||
|
}
|
41
examples/TLV_parsing/run_switch.sh
Executable file
41
examples/TLV_parsing/run_switch.sh
Executable file
@ -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
|
6
examples/TLV_parsing/send_one.py
Normal file
6
examples/TLV_parsing/send_one.py
Normal file
@ -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")
|
47
examples/copy_to_cpu/README.md
Normal file
47
examples/copy_to_cpu/README.md
Normal file
@ -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.
|
4
examples/copy_to_cpu/commands.txt
Normal file
4
examples/copy_to_cpu/commands.txt
Normal file
@ -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
|
87
examples/copy_to_cpu/p4src/copy_to_cpu.p4
Normal file
87
examples/copy_to_cpu/p4src/copy_to_cpu.p4
Normal file
@ -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);
|
||||||
|
}
|
3
examples/copy_to_cpu/receive.py
Normal file
3
examples/copy_to_cpu/receive.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from scapy.all import *
|
||||||
|
|
||||||
|
sniff(iface = "veth6", prn = lambda x: hexdump(x))
|
41
examples/copy_to_cpu/run_switch.sh
Executable file
41
examples/copy_to_cpu/run_switch.sh
Executable file
@ -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
|
6
examples/copy_to_cpu/send_one.py
Normal file
6
examples/copy_to_cpu/send_one.py
Normal file
@ -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")
|
8
examples/env.sh
Normal file
8
examples/env.sh
Normal file
@ -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 ------------------
|
65
examples/meter/README.md
Normal file
65
examples/meter/README.md
Normal file
@ -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.
|
5
examples/meter/commands.txt
Normal file
5
examples/meter/commands.txt
Normal file
@ -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
|
82
examples/meter/p4src/meter.p4
Normal file
82
examples/meter/p4src/meter.p4
Normal file
@ -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 {
|
||||||
|
}
|
41
examples/meter/run_switch.sh
Executable file
41
examples/meter/run_switch.sh
Executable file
@ -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
|
44
examples/meter/send_and_receive.py
Normal file
44
examples/meter/send_and_receive.py
Normal file
@ -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 <packet_int (seconds)>"
|
||||||
|
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()
|
26
examples/veth_setup.sh
Executable file
26
examples/veth_setup.sh
Executable file
@ -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
|
Loading…
x
Reference in New Issue
Block a user