added action_profile example

This commit is contained in:
Antonin Bas 2015-11-17 17:09:17 -08:00
parent 1809653107
commit 97b2a77733
6 changed files with 312 additions and 0 deletions

View File

@ -13,6 +13,7 @@ included:
- `register`: how to use registers in P4 and read / write the state from the - `register`: how to use registers in P4 and read / write the state from the
control plane control plane
- `counter`: how to use counters in P4 - `counter`: how to use counters in P4
- `action_profile`: how to use action profiles in P4, using ECMP has support
All examples are orgranized the same way, with a `p4src` directory containing 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 the P4 source code, and a `README` file describing the P4 program and explaining

View File

@ -0,0 +1,76 @@
# Action Profile
## Description
This example gives a skeleton ECMP implementation, using an action profile. A P4
action profile is mapped to an indirect table in bmv2. More precisely, in our
case, the P4 objects `ecmp_group` and `ecmp_action_profile` are mapped to one
bmv2 indirect match table. This has the limitation that sharing action profiles
between P4 tables is not supported in bmv2.
### Running the demo
As always, you can start the switch with `./run_switch.sh` (and wait until
`READY` is displayed). We program the dataplane with the following CLI commands
(from [commands.txt] (commands.txt)):
```
01. table_indirect_create_group ecmp_group
02. table_indirect_create_member ecmp_group _nop
03. table_indirect_create_member ecmp_group set_ecmp_nexthop 1
04. table_indirect_create_member ecmp_group set_ecmp_nexthop 2
05. table_indirect_create_member ecmp_group set_ecmp_nexthop 3
06. table_indirect_add_member_to_group ecmp_group 1 0
07. table_indirect_add_member_to_group ecmp_group 3 0
08. table_indirect_set_default ecmp_group 0
09. table_indirect_add_with_group ecmp_group 10.0.0.1 => 0
10. table_dump ecmp_group
```
These commands do the following:
01. create a group, which receives group handle 0
02. create a member with action _nop and no action data (member handle 0)
03. create a member with action set_ecmp_nexthop and action data port=1 (member handle 1)
04. create a member with action set_ecmp_nexthop and action data port=2 (member handle 2)
05. create a member with action set_ecmp_nexthop and action data port=3 (member handle 3)
06. add member 1 to group 0
07. add member 3 to group 0
08. set member 0 (_nop) has the default for table ecmp_group
09. add an entry to table ecmp_group, which maps destination IPv4 address
10.0.0.1 to group 0 (which includes members 1 and 3)
10. simply dump the table
These commands are sent automatically when you run `./run_switch.sh`. The last
command should display the following information:
```
ecmp_group:
0: 0a000001 => group(0)
members:
0: _nop -
1: set_ecmp_nexthop - 1,
2: set_ecmp_nexthop - 2,
3: set_ecmp_nexthop - 3,
4: set_ecmp_nexthop - 4,
groups:
0: { 1, 3, }
```
When we send a packet to the switch (on any port) with destination address
10.0.0.1, the egress port will be set to either 1 or 3. This is because we only
added members 1 and 3 to the group. Which port / member is used for a given
packet is determined by computing a hash over the IP source address and the IP
protocol number. See the P4 program for more details.
After starting the switch, run `sudo python send_and_count.py`. This Python
script will send 200 packets to the switch and count the pakcets coming out of
ports 2 and 3. The two numbers should be almost the same:
```
Sending 200 packets on port 0
port 1: 106 packets (53%)
port 2: 0 packets (0%)
port 3: 94 packets (47%)
port 4: 0 packets (0%)
```

View File

@ -0,0 +1,11 @@
table_indirect_create_group ecmp_group
table_indirect_create_member ecmp_group _nop
table_indirect_create_member ecmp_group set_ecmp_nexthop 1
table_indirect_create_member ecmp_group set_ecmp_nexthop 2
table_indirect_create_member ecmp_group set_ecmp_nexthop 3
table_indirect_create_member ecmp_group set_ecmp_nexthop 4
table_indirect_add_member_to_group ecmp_group 1 0
table_indirect_add_member_to_group ecmp_group 3 0
table_indirect_set_default ecmp_group 0
table_indirect_add_with_group ecmp_group 10.0.0.1 => 0
table_dump ecmp_group

View File

@ -0,0 +1,124 @@
/*
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;
srcAddr : 48;
etherType : 16;
}
}
header_type ipv4_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;
}
}
header_type intrinsic_metadata_t {
fields {
mcast_grp : 4;
egress_rid : 4;
mcast_hash : 16;
lf_field_list: 32;
}
}
parser start {
return parse_ethernet;
}
header ethernet_t ethernet;
header ipv4_t ipv4;
metadata intrinsic_metadata_t intrinsic_metadata;
#define ETHERTYPE_IPV4 0x0800
parser parse_ethernet {
extract(ethernet);
return select(ethernet.etherType) {
ETHERTYPE_IPV4: parse_ipv4;
default: ingress;
}
}
parser parse_ipv4 {
extract(ipv4);
return ingress;
}
action _drop() {
drop();
}
action _nop() {
}
field_list l3_hash_fields {
ipv4.srcAddr;
ipv4.protocol;
}
field_list_calculation ecmp_hash {
input {
l3_hash_fields;
}
algorithm : bmv2_hash;
output_width : 16;
}
action_selector ecmp_selector {
selection_key : ecmp_hash;
}
action set_ecmp_nexthop(port) {
modify_field(standard_metadata.egress_spec, port);
}
action_profile ecmp_action_profile {
actions {
_nop;
set_ecmp_nexthop;
}
size : 64;
dynamic_action_selection : ecmp_selector;
}
table ecmp_group {
reads {
ipv4.dstAddr : exact;
}
action_profile: ecmp_action_profile;
size : 1024;
}
control ingress {
apply(ecmp_group);
}
control egress {
}

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/action_profile.p4 --json action_profile.json
sudo echo "sudo" > /dev/null
sudo $BMV2_PATH/targets/simple_switch/simple_switch action_profile.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 action_profile.json < commands.txt
echo "READY!!!"
fg

View File

@ -0,0 +1,59 @@
from scapy.all import *
import sys
import threading
import time
big_lock = threading.Lock()
counts = {}
class Receiver(threading.Thread):
def __init__(self, port, veth):
threading.Thread.__init__(self)
self.daemon = True
self.port = port
self.veth = veth
def received(self, p):
# no need for a lock, each thread is accessing a different key, and the
# dictionary itself is not modified
counts[self.port] += 1
def run(self):
sniff(iface=self.veth, prn=lambda x: self.received(x))
def main():
try:
num_packets = int(sys.argv[1])
except:
num_packets = 200
print "Sending", num_packets, "packets on port 0"
port_map = {
1: "veth3",
2: "veth5",
3: "veth7",
4: "veth9"
}
for port in port_map:
counts[port] = 0
for port, veth in port_map.items():
Receiver(port, veth).start()
for i in xrange(num_packets):
src = "11.0.%d.%d" % (i >> 256, i % 256)
p = Ether(src="aa:aa:aa:aa:aa:aa") / IP(dst="10.0.0.1", src=src) / TCP() / "aaaaaaaaaaaaaaaaaaa"
sendp(p, iface="veth1", verbose=0)
time.sleep(1)
for port in port_map:
print "port {0}: {1} packets ({2}%)".format(
port, counts[port], (100 * counts[port]) / num_packets
)
if __name__ == '__main__':
main()