├── .gitmodules ├── README.md ├── build_all.sh ├── mininet └── bmv2.py ├── p4src ├── .gitignore ├── default.p4 ├── ecmp.p4 ├── empty.p4 ├── include │ ├── actions.p4 │ ├── defines.p4 │ ├── headers.p4 │ ├── parser.p4 │ └── port_counters.p4 └── wcmp.p4 └── tools └── bash_profile /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "p4c-bmv2"] 2 | path = p4c-bmv2 3 | url = https://github.com/p4lang/p4c-bm.git 4 | [submodule "onos-bmv2"] 5 | path = onos-bmv2 6 | url = https://github.com/opennetworkinglab/onos-bmv2.git -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ONOS+P4 Developer Tools 2 | 3 | *** 4 | 5 | **DEPRECTAED** The content of this repository is deprecated. Starting with ONOS 1.12, official support for P4-enabled devices is available via P4Runtime. For more information please visit: https://wiki.onosproject.org/display/ONOS/P4+brigade#P4brigade-Learnmore 6 | 7 | *** 8 | 9 | This repository maintains a set of tools and scripts to allow ONOS developers try the P4 experimental support. 10 | 11 | For more information, visit: https://wiki.onosproject.org/x/lou 12 | 13 | ## Quickstart 14 | 15 | **Important:** the following scripts have been tested on a [Mininet 2.2.1 VM with Ubuntu 14.04 64 bit](https://github.com/mininet/mininet/wiki/Mininet-VM-Images) 16 | 17 | 1. First of all you need to pull the git submodules for onos-bmv2 (ONOS fork of BMv2) and p4c-bmv2 (P4 compiler for BMv2): 18 | 19 | git submodule update --init --recursive 20 | 21 | 2. Follow the instructions inside the respective submodules to build and install onos-bmv2 and p4c-bmv2, or use the following command to do it in one shot (it might take a while to build everything): 22 | 23 | ./build_all.sh 24 | 25 | 3. To make your development experience more pleasant, we suggest you to also include the following line in your .bash_profile: 26 | 27 | source /path/to/onos-p4-dev/tools/bash_profile 28 | -------------------------------------------------------------------------------- /build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Exit on errors 4 | set -e 5 | 6 | sudo apt-get -y install python-pip 7 | 8 | # Install p4c-bmv2 9 | cd p4c-bmv2 10 | sudo pip install -r requirements.txt 11 | sudo python setup.py install 12 | 13 | # Compile all p4 programs in p4src 14 | cd ../p4src/ 15 | mkdir -p build 16 | for f in *.p4; do 17 | sudo p4c-bmv2 --json build/${f%%.*}.json $f 18 | done 19 | 20 | # Build bmv2 21 | cd ../onos-bmv2/ 22 | bash install_deps.sh 23 | sudo pip install thrift 24 | bash autogen.sh 25 | ./configure --enable-debugger 26 | make -j4 27 | 28 | cd ../ 29 | -------------------------------------------------------------------------------- /mininet/bmv2.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from mininet.log import error, info 4 | from mininet.node import Switch 5 | from os import environ 6 | from os.path import isfile 7 | 8 | 9 | class ONOSBmv2Switch(Switch): 10 | """BMv2 software switch """ 11 | 12 | deviceId = 0 13 | instanceCount = 0 14 | 15 | def __init__(self, name, thriftPort=None, deviceId=None, debugger=False, 16 | loglevel="warn", elogger=False, persistent=True, **kwargs): 17 | Switch.__init__(self, name, **kwargs) 18 | self.swPath = environ['BMV2_EXE'] 19 | self.jsonPath = environ['BMV2_JSON'] 20 | if thriftPort: 21 | self.thriftPort = thriftPort 22 | else: 23 | self.thriftPort = ONOSBmv2Switch.pickUnusedPort() 24 | if not deviceId: 25 | if self.dpid: 26 | self.deviceId = int(self.dpid, 0 if 'x' in self.dpid else 16) 27 | else: 28 | self.deviceId = ONOSBmv2Switch.deviceId 29 | ONOSBmv2Switch.deviceId += 1 30 | else: 31 | self.deviceId = deviceId 32 | ONOSBmv2Switch.deviceId = max(deviceId, ONOSBmv2Switch.deviceId) 33 | self.debugger = debugger 34 | self.loglevel = loglevel 35 | self.logfile = '/tmp/bmv2-%d.log' % self.deviceId 36 | self.output = open(self.logfile, 'w') 37 | self.elogger = elogger 38 | self.persistent = persistent 39 | if persistent: 40 | self.exectoken = "/tmp/bmv2-%d-exec-token" % self.deviceId 41 | self.cmd("touch %s" % self.exectoken) 42 | # Store thrift port for future uses. 43 | self.cmd("echo %d > /tmp/bmv2-%d-thrift-port" % (self.thriftPort, self.deviceId)) 44 | 45 | @classmethod 46 | def pickUnusedPort(cls): 47 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 48 | s.bind(('localhost', 0)) 49 | addr, port = s.getsockname() 50 | s.close() 51 | return port 52 | 53 | @classmethod 54 | def setup(cls): 55 | err = False 56 | if 'BMV2_EXE' not in environ: 57 | error("ERROR! environment var $BMV2_EXE not set\n") 58 | err = True 59 | elif not isfile(environ['BMV2_EXE']): 60 | error("ERROR! BMV2_EXE=%s: no such file\n" % environ['BMV2_EXE']) 61 | err = True 62 | if 'BMV2_JSON' not in environ: 63 | error("ERROR! environment var $BMV2_JSON not set\n") 64 | err = True 65 | elif not isfile(environ['BMV2_JSON']): 66 | error("ERROR! BMV2_JSON=%s: no such file\n" % environ['BMV2_JSON']) 67 | err = True 68 | if err: 69 | exit(1) 70 | 71 | def start(self, controllers): 72 | args = [self.swPath, '--device-id %s' % str(self.deviceId)] 73 | for port, intf in self.intfs.items(): 74 | if not intf.IP(): 75 | args.append('-i %d@%s' % (port, intf.name)) 76 | if self.thriftPort: 77 | args.append('--thrift-port %d' % self.thriftPort) 78 | if self.elogger: 79 | nanomsg = 'ipc:///tmp/bmv2-%d-log.ipc' % self.deviceId 80 | args.append('--nanolog %s' % nanomsg) 81 | if self.debugger: 82 | args.append('--debugger') 83 | args.append('--log-console -L%s' % self.loglevel) 84 | args.append(self.jsonPath) 85 | try: # onos.py 86 | clist = controllers[0].nodes() 87 | except AttributeError: 88 | clist = controllers 89 | assert len(clist) > 0 90 | # BMv2 can't connect to multiple controllers. 91 | # Uniformly balance connections among available ones. 92 | cip = clist[ONOSBmv2Switch.instanceCount % len(clist)].IP() 93 | ONOSBmv2Switch.instanceCount += 1 94 | # BMv2 controler port is hardcoded here as it is hardcoded also in ONOS. 95 | cport = 40123 96 | args.append('--') 97 | args.append('--controller-ip %s' % cip) 98 | args.append('--controller-port %d' % cport) 99 | 100 | bmv2cmd = " ".join(args) 101 | info("\nStarting BMv2 target: %s\n" % bmv2cmd) 102 | if self.persistent: 103 | # Re-exec the switch if it crashes. 104 | cmdStr = "(while [ -e {} ]; " \ 105 | "do {} ; " \ 106 | "sleep 1; " \ 107 | "done;) > {} 2>&1 &".format(self.exectoken, bmv2cmd, self.logfile) 108 | else: 109 | cmdStr = "{} > {} 2>&1 &".format(bmv2cmd, self.logfile) 110 | self.cmd(cmdStr) 111 | 112 | def stop(self): 113 | "Terminate switch." 114 | self.output.flush() 115 | self.cmd("rm -f /tmp/bmv2-%d-*" % self.deviceId) 116 | self.cmd("rm -f /tmp/bmv2-%d.log" % self.deviceId) 117 | self.cmd('kill %' + self.swPath) 118 | self.deleteIntfs() 119 | 120 | 121 | ### Exports for bin/mn 122 | switches = {'onosbmv2': ONOSBmv2Switch} 123 | -------------------------------------------------------------------------------- /p4src/.gitignore: -------------------------------------------------------------------------------- 1 | build/*.json -------------------------------------------------------------------------------- /p4src/default.p4: -------------------------------------------------------------------------------- 1 | #include "include/defines.p4" 2 | #include "include/headers.p4" 3 | #include "include/parser.p4" 4 | #include "include/actions.p4" 5 | #include "include/port_counters.p4" 6 | 7 | table table0 { 8 | reads { 9 | standard_metadata.ingress_port : ternary; 10 | ethernet.dstAddr : ternary; 11 | ethernet.srcAddr : ternary; 12 | ethernet.etherType : ternary; 13 | } 14 | actions { 15 | set_egress_port; 16 | send_to_cpu; 17 | _drop; 18 | } 19 | support_timeout: true; 20 | } 21 | 22 | counter table0_counter { 23 | type: packets; 24 | direct: table0; 25 | min_width : 32; 26 | } 27 | 28 | control ingress { 29 | apply(table0); 30 | process_port_counters(); 31 | } -------------------------------------------------------------------------------- /p4src/ecmp.p4: -------------------------------------------------------------------------------- 1 | #include "include/defines.p4" 2 | #include "include/headers.p4" 3 | #include "include/parser.p4" 4 | #include "include/actions.p4" 5 | #include "include/port_counters.p4" 6 | 7 | header_type ecmp_metadata_t { 8 | fields { 9 | groupId : 16; 10 | selector : 16; 11 | } 12 | } 13 | 14 | metadata ecmp_metadata_t ecmp_metadata; 15 | 16 | field_list ecmp_hash_fields { 17 | ipv4.srcAddr; 18 | ipv4.dstAddr; 19 | ipv4.protocol; 20 | tcp.srcPort; 21 | tcp.dstPort; 22 | udp.srcPort; 23 | udp.dstPort; 24 | } 25 | 26 | field_list_calculation ecmp_hash { 27 | input { 28 | ecmp_hash_fields; 29 | } 30 | algorithm : bmv2_hash; 31 | output_width : 64; 32 | } 33 | 34 | action ecmp_group(groupId, groupSize) { 35 | modify_field(ecmp_metadata.groupId, groupId); 36 | modify_field_with_hash_based_offset(ecmp_metadata.selector, 0, ecmp_hash, groupSize); 37 | } 38 | 39 | table table0 { 40 | reads { 41 | standard_metadata.ingress_port : ternary; 42 | ethernet.dstAddr : ternary; 43 | ethernet.srcAddr : ternary; 44 | ethernet.etherType : ternary; 45 | } 46 | actions { 47 | set_egress_port; 48 | ecmp_group; 49 | send_to_cpu; 50 | _drop; 51 | } 52 | support_timeout: true; 53 | } 54 | 55 | table ecmp_group_table { 56 | reads { 57 | ecmp_metadata.groupId : exact; 58 | ecmp_metadata.selector : exact; 59 | } 60 | actions { 61 | set_egress_port; 62 | } 63 | } 64 | 65 | counter table0_counter { 66 | type: packets; 67 | direct: table0; 68 | min_width : 32; 69 | } 70 | 71 | counter ecmp_group_table_counter { 72 | type: packets; 73 | direct: ecmp_group_table; 74 | min_width : 32; 75 | } 76 | 77 | control ingress { 78 | apply(table0) { 79 | ecmp_group { 80 | apply(ecmp_group_table); 81 | } 82 | } 83 | process_port_counters(); 84 | } -------------------------------------------------------------------------------- /p4src/empty.p4: -------------------------------------------------------------------------------- 1 | header_type dummy_t { 2 | fields { 3 | dummyField : 8; 4 | } 5 | } 6 | 7 | metadata dummy_t dummy_metadata; 8 | 9 | parser start { 10 | return ingress; 11 | } 12 | 13 | table table0 { 14 | reads { 15 | dummy_metadata.dummyField : exact; 16 | } 17 | actions { 18 | dummy_action; 19 | } 20 | } 21 | 22 | action dummy_action() { 23 | modify_field(dummy_metadata.dummyField, 1); 24 | } 25 | 26 | control ingress { 27 | apply(table0); 28 | } -------------------------------------------------------------------------------- /p4src/include/actions.p4: -------------------------------------------------------------------------------- 1 | action set_egress_port(port) { 2 | modify_field(standard_metadata.egress_spec, port); 3 | } 4 | 5 | action _drop() { 6 | modify_field(standard_metadata.egress_spec, DROP_PORT); 7 | } 8 | 9 | action send_to_cpu() { 10 | modify_field(standard_metadata.egress_spec, CPU_PORT); 11 | } -------------------------------------------------------------------------------- /p4src/include/defines.p4: -------------------------------------------------------------------------------- 1 | // Logic ports as defined in the simple_switch target 2 | #define MAX_PORTS 254 3 | #define CPU_PORT 255 4 | #define DROP_PORT 511 5 | -------------------------------------------------------------------------------- /p4src/include/headers.p4: -------------------------------------------------------------------------------- 1 | header_type intrinsic_metadata_t { 2 | fields { 3 | ingress_global_timestamp : 32; 4 | lf_field_list : 32; 5 | mcast_grp : 16; 6 | egress_rid : 16; 7 | } 8 | } 9 | 10 | header_type ethernet_t { 11 | fields { 12 | dstAddr : 48; 13 | srcAddr : 48; 14 | etherType : 16; 15 | } 16 | } 17 | 18 | header_type ipv4_t { 19 | fields { 20 | version : 4; 21 | ihl : 4; 22 | diffserv : 8; 23 | totalLen : 16; 24 | identification : 16; 25 | flags : 3; 26 | fragOffset : 13; 27 | ttl : 8; 28 | protocol : 8; 29 | hdrChecksum : 16; 30 | srcAddr : 32; 31 | dstAddr: 32; 32 | } 33 | } 34 | 35 | header_type tcp_t { 36 | fields { 37 | srcPort : 16; 38 | dstPort : 16; 39 | seqNo : 32; 40 | ackNo : 32; 41 | dataOffset : 4; 42 | res : 3; 43 | ecn : 3; 44 | ctrl : 6; 45 | window : 16; 46 | checksum : 16; 47 | urgentPtr : 16; 48 | } 49 | } 50 | 51 | header_type udp_t { 52 | fields { 53 | srcPort : 16; 54 | dstPort : 16; 55 | length_ : 16; 56 | checksum : 16; 57 | } 58 | } -------------------------------------------------------------------------------- /p4src/include/parser.p4: -------------------------------------------------------------------------------- 1 | metadata intrinsic_metadata_t intrinsic_metadata; 2 | 3 | parser start { 4 | return parse_ethernet; 5 | } 6 | 7 | #define ETHERTYPE_IPV4 0x0800 8 | 9 | header ethernet_t ethernet; 10 | 11 | parser parse_ethernet { 12 | extract(ethernet); 13 | return select(latest.etherType) { 14 | ETHERTYPE_IPV4 : parse_ipv4; 15 | default : ingress; 16 | } 17 | } 18 | 19 | header ipv4_t ipv4; 20 | 21 | #define IP_PROTOCOLS_TCP 6 22 | #define IP_PROTOCOLS_UDP 17 23 | 24 | parser parse_ipv4 { 25 | extract(ipv4); 26 | return select(latest.fragOffset, latest.protocol) { 27 | IP_PROTOCOLS_TCP : parse_tcp; 28 | IP_PROTOCOLS_UDP : parse_udp; 29 | default: ingress; 30 | } 31 | } 32 | 33 | header tcp_t tcp; 34 | 35 | parser parse_tcp { 36 | extract(tcp); 37 | return ingress; 38 | } 39 | 40 | header udp_t udp; 41 | 42 | parser parse_udp { 43 | extract(udp); 44 | return ingress; 45 | } -------------------------------------------------------------------------------- /p4src/include/port_counters.p4: -------------------------------------------------------------------------------- 1 | counter ingress_port_counter { 2 | type : packets; // bmv2 always counts both bytes and packets 3 | instance_count : MAX_PORTS; 4 | min_width : 32; 5 | } 6 | 7 | counter egress_port_counter { 8 | type: packets; 9 | instance_count : MAX_PORTS; 10 | min_width : 32; 11 | } 12 | 13 | table port_count_table { 14 | actions { 15 | count_packet; 16 | } 17 | } 18 | 19 | action count_packet() { 20 | count(ingress_port_counter, standard_metadata.ingress_port); 21 | count(egress_port_counter, standard_metadata.egress_spec); 22 | } 23 | 24 | control process_port_counters { 25 | // Avoid counting logical ports, such as drop and cpu 26 | if (standard_metadata.egress_spec < MAX_PORTS) { 27 | apply(port_count_table); 28 | } 29 | } -------------------------------------------------------------------------------- /p4src/wcmp.p4: -------------------------------------------------------------------------------- 1 | #include "include/defines.p4" 2 | #include "include/headers.p4" 3 | #include "include/parser.p4" 4 | #include "include/actions.p4" 5 | #include "include/port_counters.p4" 6 | 7 | #define SELECTOR_WIDTH 64 8 | 9 | header_type wcmp_meta_t { 10 | fields { 11 | groupId : 16; 12 | numBits: 8; 13 | selector : SELECTOR_WIDTH; 14 | } 15 | } 16 | 17 | metadata wcmp_meta_t wcmp_meta; 18 | 19 | field_list wcmp_hash_fields { 20 | ipv4.srcAddr; 21 | ipv4.dstAddr; 22 | ipv4.protocol; 23 | tcp.srcPort; 24 | tcp.dstPort; 25 | udp.srcPort; 26 | udp.dstPort; 27 | } 28 | 29 | field_list_calculation wcmp_hash { 30 | input { 31 | wcmp_hash_fields; 32 | } 33 | algorithm : bmv2_hash; 34 | output_width : 64; 35 | } 36 | 37 | action wcmp_group(groupId) { 38 | modify_field(wcmp_meta.groupId, groupId); 39 | modify_field_with_hash_based_offset(wcmp_meta.numBits, 2, wcmp_hash, (SELECTOR_WIDTH - 2)); 40 | } 41 | 42 | action wcmp_set_selector() { 43 | modify_field(wcmp_meta.selector, 44 | (((1 << wcmp_meta.numBits) - 1) << (SELECTOR_WIDTH - wcmp_meta.numBits))); 45 | } 46 | 47 | table table0 { 48 | reads { 49 | standard_metadata.ingress_port : ternary; 50 | ethernet.dstAddr : ternary; 51 | ethernet.srcAddr : ternary; 52 | ethernet.etherType : ternary; 53 | } 54 | actions { 55 | set_egress_port; 56 | wcmp_group; 57 | send_to_cpu; 58 | _drop; 59 | } 60 | support_timeout: true; 61 | } 62 | 63 | table wcmp_set_selector_table { 64 | actions { 65 | wcmp_set_selector; 66 | } 67 | } 68 | 69 | table wcmp_group_table { 70 | reads { 71 | wcmp_meta.groupId : exact; 72 | wcmp_meta.selector : lpm; 73 | } 74 | actions { 75 | set_egress_port; 76 | } 77 | } 78 | 79 | counter table0_counter { 80 | type: packets; 81 | direct: table0; 82 | min_width : 32; 83 | } 84 | 85 | counter wcmp_group_table_counter { 86 | type: packets; 87 | direct: wcmp_group_table; 88 | min_width : 32; 89 | } 90 | 91 | control ingress { 92 | apply(table0) { 93 | wcmp_group { 94 | apply(wcmp_set_selector_table) { 95 | wcmp_set_selector { 96 | apply(wcmp_group_table); 97 | } 98 | } 99 | } 100 | } 101 | process_port_counters(); 102 | } -------------------------------------------------------------------------------- /tools/bash_profile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | export P4_PATH="$( readlink -f "${DIR}/.." )" 6 | export BMV2_PATH=$P4_PATH/onos-bmv2 7 | export BMV2_EXE=$BMV2_PATH/targets/simple_switch/simple_switch 8 | export BMV2_JSON=$P4_PATH/p4src/build/empty.json 9 | export BMV2_PY=$P4_PATH/mininet/bmv2.py 10 | 11 | export P4C_BM_PATH=$P4_PATH/p4c-bmv2 12 | export P4SRC_PATH=$P4_PATH/p4src 13 | 14 | p4c () { 15 | if [ -z "$1" ]; then 16 | echo "No argument supplied. Usage: p4c P4_PROGRAM_NAME" 17 | return 18 | fi 19 | 20 | mkdir -p $P4SRC_PATH/build 21 | 22 | P4SRC="$P4SRC_PATH/$1.p4" 23 | P4JSON="$P4SRC_PATH/build/$1.json" 24 | 25 | if [ ! -f $P4SRC ] 26 | then 27 | echo "No such file $CURR_P4SRC" 28 | return 29 | fi 30 | 31 | # FIXME: p4c-bmv2 doesn't seem to work without sudo 32 | sudo p4c-bmv2 --json $P4JSON $P4SRC 33 | } 34 | 35 | p4cli () { 36 | if [ -z "$1" ]; then 37 | echo "No argument supplied. Usage: p4cli THRIFT_PORT" 38 | return 39 | fi 40 | tport=$(head -n 1 /tmp/bmv2-$1-thrift-port) 41 | sudo $BMV2_PATH/tools/runtime_CLI.py --thrift-port $tport 42 | } 43 | 44 | p4db () { 45 | if [ -z "$1" ]; then 46 | echo "No argument supplied. Usage: p4db THRIFT_PORT" 47 | return 48 | fi 49 | tport=$(head -n 1 /tmp/bmv2-$1-thrift-port) 50 | sudo $BMV2_PATH/tools/p4dbg.py --thrift-port $tport 51 | } 52 | 53 | p4nmsg () { 54 | if [ -z "$1" ]; then 55 | echo "No argument supplied. Usage: p4nmsg THRIFT_PORT" 56 | return 57 | fi 58 | tport=$(head -n 1 /tmp/bmv2-$1-thrift-port) 59 | sudo $BMV2_PATH/tools/nanomsg_client.py --thrift-port $tport 60 | } 61 | 62 | p4log () { 63 | if [ -z "$1" ]; then 64 | echo "No argument supplied. Usage: p4log DEVICE_ID" 65 | return 66 | fi 67 | tail -f /tmp/bmv2-$1.log 68 | } 69 | --------------------------------------------------------------------------------