added 3 bmv2 examples: copy_to_cpu, meter, TLV_parsing

This commit is contained in:
Antonin Bas
2015-10-22 16:04:30 -07:00
parent c8205b938b
commit 996bbbad31
20 changed files with 868 additions and 0 deletions

View 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).

View 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 =>

View 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);
}

View 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

View 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")