added action_profile example
This commit is contained in:
parent
1809653107
commit
97b2a77733
@ -13,6 +13,7 @@ included:
|
||||
- `register`: how to use registers in P4 and read / write the state from the
|
||||
control plane
|
||||
- `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
|
||||
the P4 source code, and a `README` file describing the P4 program and explaining
|
||||
|
76
examples/action_profile/README.md
Normal file
76
examples/action_profile/README.md
Normal 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%)
|
||||
```
|
11
examples/action_profile/commands.txt
Normal file
11
examples/action_profile/commands.txt
Normal 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
|
124
examples/action_profile/p4src/action_profile.p4
Normal file
124
examples/action_profile/p4src/action_profile.p4
Normal 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 {
|
||||
}
|
41
examples/action_profile/run_switch.sh
Executable file
41
examples/action_profile/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/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
|
59
examples/action_profile/send_and_count.py
Normal file
59
examples/action_profile/send_and_count.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user