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
|
- `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
|
||||||
|
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