Updated P4 tutorial code/instructions for onos-1.13
Change-Id: Iccaa4114dbe2f408506ef5cf5e5cf7d3e22fb62e
diff --git a/apps/p4-tutorial/README.md b/apps/p4-tutorial/README.md
new file mode 100644
index 0000000..9373890
--- /dev/null
+++ b/apps/p4-tutorial/README.md
@@ -0,0 +1,123 @@
+# ONOS+P4 Tutorial
+
+This directory contains source code and instructions to run the ONOS+P4
+tutorial exercises. Goal of the exercises is to learn how to use ONOS to control
+P4-capable devices via P4Runtime, and how to write ONOS apps to control custom
+data plane capabilities implemented in P4.
+
+For help, please write to the mailing list
+[brigade-p4@onosproject.org](mailto:brigade-p4@onosproject.org) or check the
+[mailing list archives](https://groups.google.com/a/onosproject.org/forum/#!forum/brigade-p4).
+
+## Tutorial VM
+
+To complete the exercises, you will need to download and run the following VM
+(in .ova format):
+
+<http://onlab.vicci.org/onos/onos-p4-tutorial.ova>
+
+To run the VM you can use any modern virtualization system, although we
+recommend using VirtualBox. To download VirtualBox and import the VM use the
+following links:
+
+* <https://www.virtualbox.org/wiki/Downloads>
+* <https://docs.oracle.com/cd/E26217_01/E26796/html/qs-import-vm.html>
+
+For more information on the content of the VM and minimum system requirements,
+[click here](/tools/dev/p4vm/README.md).
+
+### VM credentials
+
+The VM comes with one user with sudo privileges. Use these credentials to log in:
+
+* Username: `sdn`
+* Password: `rocks`
+
+## Overview
+
+### mytunnel.p4
+
+These exercises are based on a simple P4 program called
+[mytunnel.p4](./pipeconf/src/main/resources/mytunnel.p4) designed for this
+tutorial.
+
+To start, have a look a the P4 program. Even if this is the first time you
+see P4 code, the program has been commented to provide an understanding of the
+pipeline behavior to anyone with basic programming and networking background.
+While checking the P4 program, try answering the following questions:
+
+* Which protocol headers are being extracted from each packet?
+* How can the parser distinguish a packet with MyTunnel encapsulation from one
+ without?
+* How many match+action tables are defined in the P4 program?
+* What is the first table in the pipeline applied to every packet?
+* Which headers can be matched on table `t_l2_fwd`?
+* Which type of match is applied to `t_l2_fwd`? E.g. exact match, ternary, or
+ longest-prefix match?
+* Which actions can be executed on matched packets?
+* Which action can be used to send a packet to the controller?
+* What happens if a matching entry is not found in table `t_l2_fwd`? What's the
+ next table applied to the packet?
+
+### MyTunnel Pipeconf
+
+The `mytunnel.p4` program is provided to ONOS as part of a "pipeconf".
+
+The main class used to implement the pipeconf is
+[PipeconfFactory.java](./pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java).
+This class is declared as an OSGi runtime component which is "activated" once
+the pipeconf app is loaded in ONOS. The main purpose of this class is to
+instantiate the Pipeconf object and register that with the corresponding service
+in ONOS. This is where we associate ONOS driver behaviors with the pipeconf, and
+also define the necessary pipeconf extensions to be able to deploy the P4
+program to a device.
+
+This pipeconf contains:
+
+* [mytunnel.json](/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.json):
+The JSON configuration used to execute the P4 program on BMv2. This is an output
+of the P4 compiler for BMv2.
+
+* [mytunnel.p4info](/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4info):
+P4Info file obtained from the P4 compiler.
+
+* [PipelineInterpreterImpl.java](./pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java):
+Implementation of the `PipelineInterpreter` ONOS driver behavior. The main
+purpose of this class is to provide a mapping between ONOS constructs and P4
+program-specific ones, for example methods to map ONOS well-known header fields
+and packet forwarding/manipulation actions to those defined in the P4 program.
+For a more detailed explanation of each method, check the
+[PipelineInterpreter interface](./core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java).
+
+* [PortStatisticsDiscoveryImpl.java](./pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java):
+Implementation of the `PortStatisticsDiscovery` ONOS driver behavior. As the
+name suggests, this behavior is used to report statistics on the switch ports to
+ONOS, e.g. number of packets/bytes received and transmitted for each port. This
+implementation works by reading the value of two P4 counters defined in
+`mytunnel.p4`, `tx_port_counter` and `rx_port_counter`.
+
+### MyTunnel App
+
+This app is used to provide connectivity between each pair of hosts via the
+MyTunnel protocol, a non-standard tunneling protocol created for this exercise.
+The implementation of this app can be found
+[here](./mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java),
+and it will be discussed in more details on Exercise 2.
+
+## Tutorial exercises
+
+### Exercise 1
+
+[Click here to go to this exercise instructions](./exercise-1.md)
+
+This exercise shows how to start ONOS and Mininet with BMv2, it also
+demonstrates connectivity between hosts using the pipeline-agnostic app
+Reactive Forwarding, in combination with other well known ONOS services such as
+Proxy ARP, Host Location Provider, and LLDP Link Discovery.
+
+### Exercise 2
+
+[Click here to go to this exercise instructions](./exercise-2.md)
+
+Similar to exercise 1, but here connectivity between hosts is demonstrated using
+pipeline-specific app "MyTunnel".
diff --git a/apps/p4-tutorial/exercise-1.md b/apps/p4-tutorial/exercise-1.md
new file mode 100644
index 0000000..631ea1f
--- /dev/null
+++ b/apps/p4-tutorial/exercise-1.md
@@ -0,0 +1,372 @@
+# ONOS+P4 Tutorial: Exercise 1
+
+The goal of this exercise is to introduce P4 and P4Runtime support in ONOS,
+along with the tools to practically experiment with it. In this exercise we will
+see how the ONOS "pipeconf" mechanism allow one to re-use existing ONOS apps to
+provide basic forwarding capabilities in a pipeline-agnostic manner, i.e.
+independently of the P4 program.
+
+To run this exercise you will need multiple terminal windows (or tabs) to
+operate with the CLI of Mininet, ONOS, and BMv2. We use the following convention
+to distinguish between commands of different CLIs:
+
+* Commands starting with `$` are intended to be executed in the Ubuntu terminal
+ prompt;
+* `onos>` for commands in the ONOS CLI;
+* `mininet>` for the Mininet CLI;
+* `RuntimeCmd:` for the BMv2 CLI.
+
+## Exercise steps
+
+1. On terminal window 1, **start ONOS with a small subset of the apps**
+by executing the following command:
+
+ ```
+ $ cd $ONOS_ROOT
+ $ ONOS_APPS=proxyarp,hostprovider,lldpprovider ok clean
+ ```
+
+ The `$ONOS_ROOT` environment variable points to the root ONOS directory. The
+ `ok` command is an alias to run ONOS locally in your dev machine. Please
+ note that if this the first time you run ONOS on this machine, or if you
+ haven't built ONOS before, it can take some time (5-10 minutes depending on
+ your Internet speed).
+
+ Once ONOS has started you should see log messages being print on the screen.
+
+2. On terminal window 2, **activate the BMv2 driver and tutorial pipeconf** via
+ the ONOS CLI.
+
+ 1. Use the following command to **access the ONOS CLI**:
+
+ ```
+ $ onos localhost
+ ```
+
+ You should now see the ONOS CLI command prompt. For a list of possible
+ commands that you can use here, type:
+
+ ```
+ onos> help onos
+ ```
+
+ 2. Enter the following command to **activate the BMv2 driver**:
+
+ ```
+ onos> app activate org.onosproject.drivers.bmv2
+ ```
+
+ You should see the following message on the ONOS log:
+
+ ```
+ Application org.onosproject.drivers.bmv2 has been activated
+ ```
+
+ 3. Enter the following command to **activate the pipeconf**:
+
+ ```
+ onos> app activate org.onosproject.p4tutorial.pipeconf
+ ```
+
+ You should see the following messages on the log:
+
+ ```
+ New pipeconf registered: p4-tutorial-pipeconf
+ Application org.onosproject.p4tutorial.pipeconf has been activated
+ ```
+
+ Please note the specific name used for this pipeconf `p4-tutorial-pipeconf`. We
+ will later use this name to tell ONOS to deploy that specific P4 program
+ to the switches.
+
+ 4. To **verify that you have activated all the required apps**, run the
+ following command:
+
+ ```
+ onos> apps -a -s
+ ```
+
+ Make sure you see the following list of apps displayed:
+
+ ```
+ org.onosproject.generaldeviceprovider ... General Device Provider
+ org.onosproject.drivers ... Default Drivers
+ org.onosproject.proxyarp ... Proxy ARP/NDP
+ org.onosproject.lldpprovider ... LLDP Link Provider
+ org.onosproject.protocols.grpc ... gRPC Protocol Subsystem
+ org.onosproject.protocols.p4runtime ... P4Runtime Protocol Subsystem
+ org.onosproject.p4runtime ... P4Runtime Provider
+ org.onosproject.drivers.p4runtime ... P4Runtime Drivers
+ org.onosproject.hostprovider ... Host Location Provider
+ org.onosproject.drivers.bmv2 ... BMv2 Drivers
+ org.onosproject.p4tutorial.pipeconf ... P4 Tutorial Pipeconf
+ ```
+
+ 5. (optional) **Change flow rule polling interval**. Run the following
+ command in the ONOS CLI:
+
+ ```
+ onos> cfg set org.onosproject.net.flow.impl.FlowRuleManager fallbackFlowPollFrequency 5
+ ```
+
+ This command tells ONOS to check the state of flow rules on switches
+ every 5 seconds (default is 30). This is used to obtain more often flow
+ rules stats such as byte/packet counters. It helps also resolving more
+ quickly issues where some flow rules are installed in the ONOS store but
+ not on the device (which can often happen when emulating a large number
+ of devices in the same VM).
+
+3. On terminal window 3, **run Mininet to set up a topology of BMv2 devices**.
+
+ 1. To **run Mininet**, use the following command:
+
+ ```
+ $ sudo -E mn --custom $BMV2_MN_PY --switch onosbmv2,pipeconf=p4-tutorial-pipeconf --controller remote,ip=127.0.0.1
+ ```
+
+ The `--custom` argument tells Mininet to use the `bmv2.py` custom script
+ to execute the BMv2 switch. The environment variable `$BMV2_MN_PY`
+ points to the exact location of the script (you can use the command
+ `echo $BMV2_MN_PY` to find out the location).
+
+ The `--switch` argument specifies the kind of switch instance we want to
+ run inside Mininet. In this case we are running a version of BMv2 that
+ also produces some configuration files used by ONOS to discover the
+ device (see steps below), hence the name `onosbmv2`. The `pipeconf`
+ sub-argument is used to tell ONOS which pipeconf to deploy on all
+ devices.
+
+ The `--controller` argument specifies the address of the controller,
+ ONOS in this case, which is running on the same machine where we are
+ executing Mininet.
+
+ 2. A set of **files are generated in the `/tmp` folder as part of this
+ startup process**, to view them (on a separate terminal window):
+
+ ```
+ $ ls /tmp
+ ```
+
+ 3. You will **find ONOS netcfg JSON files in this folder** for each BMv2
+ switch, open this file up, for example:
+
+ ```
+ $ cat /tmp/bmv2-s1-netcfg.json
+ ```
+
+ It contains the configuration for (1) the gRPC server and port used by the
+ BMv2 switch process for the P4Runtime service, (2) the ID of pipeconf to
+ deploy on the device, (3) switch ports details, and other
+ driver-specific information.
+
+ **This file is pushed to ONOS automatically by Mininet when executing
+ the switch instance**. If everything went as expected, you should see
+ the ONOS log populating with messages like:
+
+ ```
+ Connecting to device device:bmv2:s1 with driver bmv2
+ [...]
+ Setting pipeline config for device:bmv2:s1 to p4-tutorial-pipeconf...
+ [...]
+ Device device:bmv2:s1 connected
+ [...]
+ ```
+
+ 4. **Check the BMv2 switch instance log**:
+
+ ```
+ $ less /tmp/bmv2-s1-log
+ ```
+
+ By scrolling the BMv2 log, you should see all P4Runtime messages
+ received by the switch. These messages are sent by ONOS and are used to
+ install table entries and to read counters. You should also see many
+ `PACKET_IN` and `PACKET_OUT` messages corresponding to packet-in/out
+ processed by the switch and used for LLDP-based link discovery.
+
+ Table entry messages are generated by ONOS according to the flow rules
+ generated by each app and based on the P4Info associated with
+ the `p4-tutorial-pipeconf`.
+
+ If you prefer to watch the BMv2 log updating in real time, you can use
+ the following command to print on screen all new messages:
+
+ ```
+ $ bm-log s1
+ ```
+
+ This command will show the log of the BMv2 switch in Mininet with name
+ "s1".
+
+ If needed, you can run BMv2 with **debug logging** enabled by passing
+ the sub-argument `loglevel=debug` when starting Mininet. For example:
+
+ ```
+ $ sudo -E mn [...] --switch onosbmv2,loglevel=debug,pipeconf=p4-tutorial-pipeconf [...]
+ ```
+
+ Debug logging in BMv2 is useful to observe the life of a packet inside the
+ pipeline, e.g. showing the header fields extracted by the parser for a
+ specific packet, the tables used to process the packets, matching table
+ entries (if any), etc.
+
+ 5. **Check the flow rules inserted by each app in ONOS**. In the
+ ONOS CLI type:
+
+ ```
+ onos> flows -s
+ ```
+
+ You should see 3 flow rules:
+
+ ```
+ deviceId=device:bmv2:s1, flowRuleCount=3
+ ADDED, bytes=0, packets=0, table=0, priority=40000, selector=[ETH_TYPE:arp], treatment=[immediate=[OUTPUT:CONTROLLER], clearDeferred]
+ ADDED, bytes=0, packets=0, table=0, priority=40000, selector=[ETH_TYPE:bddp], treatment=[immediate=[OUTPUT:CONTROLLER], clearDeferred]
+ ADDED, bytes=0, packets=0, table=0, priority=40000, selector=[ETH_TYPE:lldp], treatment=[immediate=[OUTPUT:CONTROLLER], clearDeferred]
+ ```
+
+ These flow rules are installed automatically for each device by the
+ Proxy ARP and LLDP Link Discovery apps. The first one is used to
+ intercept ARP requests (`selector=[ETH_TYPE:arp]`), which are sent to
+ the controller (`treatment=[immediate=[OUTPUT:CONTROLLER]`), who in turn
+ will reply with an ARP response or broadcast the requests to all hosts.
+ The other two flow rules are used to intercept LLDP and BBDP packets
+ (for the purpose of link discovery).
+
+ These flow rules appear to be installed on table "0". This is a logical
+ table number mapped by the pipeconf's interpreter to the P4 table named
+ `t_l2_fwd` in [mytunnel.p4 (line 191)](./pipeconf/src/main/resources/mytunnel.p4#L191).
+
+ This mapping is defined in
+ [PipelineInterpreterImpl.java (line 103)](./pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java#L103)
+
+ 6. **Compare ONOS flow rules to the table entries installed on the BMv2
+ switch**.
+
+ We can **use the BMv2 CLI to dump all table entries currently
+ installed on the switch**. On a separate terminal window type:
+
+ ```
+ $ bm-cli s1
+ ```
+
+ This command will start the CLI for the BMv2 switch in Mininet with name
+ "s1".
+
+ On the BMv2 CLI prompt, type the following command:
+
+ ```
+ RuntimeCmd: table_dump c_ingress.t_l2_fwd
+ ```
+
+ You should see exactly 3 entries, each one corresponding to a flow rule
+ in ONOS. For example, the flow rule matching on ARP packets should look
+ like this in the BMv2 CLI:
+
+ ```
+ ...
+ **********
+ Dumping entry 0x2000002
+ Match key:
+ * standard_metadata.ingress_port: TERNARY 0000 &&& 0000
+ * ethernet.dst_addr : TERNARY 000000000000 &&& 000000000000
+ * ethernet.src_addr : TERNARY 000000000000 &&& 000000000000
+ * ethernet.ether_type : TERNARY 0806 &&& ffff
+ Priority: 16737216
+ Action entry: c_ingress.send_to_cpu -
+ ...
+ ```
+
+ Note how the ONOS selector `[ETH_TYPE:arp]` has been translated to an
+ entry matching only the header field `ethernet.ether_type`, while the
+ bits of all other fields are set as "don't care" (mask is all zeros).
+ While the ONOS treatment `[OUTPUT:CONTROLLER]` has been translated to
+ the action `c_ingress.send_to_cpu`.
+
+ **Important:** The BMv2 CLI is a powerful tool to debug the state of a
+ BMv2 switch. Type `help` to show a list of possible commands. This CLI
+ provides also auto-completion when pressing the `tab` key.
+
+4. It is finally time to **test connectivity between the hosts** of our Mininet
+ network.
+
+ 1. On the Mininet prompt, **start a ping between host1 and host2**:
+
+ ```
+ mininet> h1 ping h2
+ ```
+
+ The **ping should NOT work**, and the reason is that we did not activate
+ yet any ONOS app providing connectivity between hosts.
+
+ 2. While leaving the ping running on Mininet, **activate the Reactive
+ Forwarding app using the ONOS CLI**:
+
+ ```
+ onos> app activate org.onosproject.fwd
+ ```
+
+ Once activated, you should see the the ping working. Indeed, this
+ app installs the necessary flow rules to forward packets between
+ the two hosts.
+
+ 3. Use steps 3.v and 3.vi to **check the new flow rules**.
+
+ You should see 3 new flow rules.
+
+ The Reactive Forwarding app works in the following way. It installs a
+ low priority flow rule to intercepts all IPv4 packets via a
+ `send_to_cpu` action (`[OUTPUT:CONTROLLER]` in ONOS). When a packet is
+ received by the control plane, the packet is processed by the app, which
+ in turn, by querying the Topology service and the Host Location service
+ is ONOS, computes the shortest path between the two hosts, and installs
+ higher priority flow rules on each hop to forward packets between the
+ two hosts (after having re-injected that packet in the network via a
+ packet-out).
+
+5. Congratulations, you completed the first exercise of the ONOS+P4 tutorial!
+
+ To kill ONOS, press `ctrl-c` in the ONOS log terminal window. To kill
+ Mininet, press `ctrl-d` in the Mininet CLI or type `exit`.
+
+## Bonus exercise
+
+As a bonus exercise, you can re-run Mininet with a larger topology to see how
+Exercise 1 works with a more complex topology.
+
+1. Rerun the steps in Exercise 1, replacing step 3.i. with the following:
+
+ ```
+ $ sudo -E mn --custom $BMV2_MN_PY --switch onosbmv2,pipeconf=p4-tutorial-pipeconf --topo tree,3 --controller remote,ip=127.0.0.1
+ ```
+
+ By using the argument `--topo` we are telling Mininet to emulate a Tree
+ topology with depth 3, i.e. with 7 switches, 6 links, and 8 hosts.
+
+ **Important:** due to the limited resources of the VM, when executing many
+ switches in Mininet, it might happen that some flow rules are not installed
+ correctly on the switch (showing state `PENDING_ADD` when using ONOS command
+ `flows`). In this case, ONOS provides an automatic reconciliation mechanism
+ that tries to re-install the failed entries. To force ONOS to perform this
+ process more often, **make sure to apply step 2.v before starting Mininet**.
+
+2. After you activate the Reactive Forwarding app as in step 4.ii.,
+ you can ping all hosts in the network using the following Mininet command:
+
+ ```
+ mininet> pingall
+ ```
+
+ If everything went well, ping should work for every host pair in the
+ network.
+
+3. You can visualize the topology using the ONOS web UI.
+
+ Open a browser from within the tutorial VM (e.g. Firefox) to
+ <http://127.0.0.1:8181/onos/ui/>. When asked, use the username `onos`
+ and password `rocks`. You should see a nice tree topology.
+
+ While here, feel free to interact with and discover the ONOS UI. For more
+ information on how to use the ONOS web UI please refer to this guide:
+ <https://wiki.onosproject.org/x/OYMg>
diff --git a/apps/p4-tutorial/exercise-2.md b/apps/p4-tutorial/exercise-2.md
new file mode 100644
index 0000000..a2a4b1c
--- /dev/null
+++ b/apps/p4-tutorial/exercise-2.md
@@ -0,0 +1,245 @@
+# Exercise 2 (ONOS+P4 Tutorial)
+
+The goal of this exercise is to demonstrate how ONOS apps can be used to
+control any P4-defined pipeline, even those implementing custom non-standard
+protocols.
+
+## Overview
+
+Similarly to exercise 1, in this example we want to provide connectivity between
+hosts of a network when using switches programmed with the `mytunnel.p4`
+program. Differently from exercise 1, forwarding between hosts will be provided
+by the MyTunnel app, instead of Reactive Forwarding. The MyTunnel app provides
+connectivity by programming the data plane to forward packets using the MyTunnel
+protocol.
+
+Before starting, we suggest to open the `onos/apps/p4-tutorial` directory in
+your editor of choice for an easier access to the different files of this
+exercise. For example, if using the Atom editor:
+
+```
+$ atom $ONOS_ROOT/apps/p4-tutorial/
+```
+
+## Protocol overview
+
+The MyTunnel protocol works by encapsulating IPv4 frames into a MyTunnel header
+defined as following:
+
+```
+header my_tunnel_t {
+ bit<16> proto_id; /* EtherType of the original
+ unencapsulated Ethernet frame */
+ bit<32> tun_id; /* Arbitrary tunnel identifier uniquelly
+ representing the egress endpoint of the tunnel */
+}
+```
+
+A switch implementing the MyTunnel protocol can forward packets using three
+different forwarding behaviors.
+
+1. **Ingress**: for IPv4 packets received at a edge switch, i.e. the first node
+in the tunnel path, the MyTunnel header is applied with an arbitrary tunnel
+identifier decided by the control plane.
+
+2. **Transit**: for packets with the MyTunnel header processed by an
+intermediate node in the tunnel path. When operating in this mode, the switch
+simply forwards the packet by looking at the tunnel ID field.
+
+3. **Egress**: for packets with the MyTunnel header processed by the last node
+in the path, the switch removes the MyTunnel header before forwarding the packet
+to the output port.
+
+## MyTunnel pipeline overview
+
+The three forwarding behaviors described before can be achieved by inserting
+entries in two different tables of `mytunnel.p4`, namely `t_tunnel_ingress` and
+`t_tunnel_fwd`.
+
+* `t_tunnel_ingress`: this table is used to implement the ingress behavior. It
+matches on the IPv4 destination address (longest-prefix match), and provides
+the `my_tunnel_ingress` action, which encapsulates the packet in the MyTunnel
+header with a given tunnel ID (action parameter).
+
+* `t_tunnel_fwd`: this table is used to implement both the transit and egress
+behaviors. It matches on the tunnel ID, and allows two different actions,
+`set_out_port` and `my_tunnel_egress`. `set_out_port` is used to set the
+output port where the packet should be transmitted without further
+modifications. With `my_tunnel_egress`, the packet is stripped of the MyTunnel
+header before setting the output port.
+
+## MyTunnel app overview
+
+To begin, open
+[MyTunnelApp.java](./mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java)
+in your editor of choice, and familiarize with the app implementation.
+
+For example, if using the Atom editor:
+
+```
+$ atom $ONOS_ROOT/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java
+```
+
+The MyTunnel app works by registering an event listener with the ONOS Host
+Service (`class InternalHostListener` at line 308). This listener is used to
+notify the MyTunnel app every time a new host is discovered. Host discovery is
+performed by means of two ONOS core services: Host Location Provider and
+Proxy-ARP app. Each time an ARP request is received (via packet-in), ONOS learns
+the location of the sender of the ARP request, before generating an ARP reply or
+forwarding the requests to other hosts. When learning the location of a new
+host, ONOS informs all apps that have registered a listener with an `HOST_ADDED`
+event.
+
+Once an `HOST_ADDED` event is notified to the MyTunnel app, this creates two
+unidirectional tunnels between that host and any other host previously
+discovered. For each tunnel, the app computes the shortest path between the two
+hosts (method `provisionTunnel` at line 128), and for each switch in the path it
+installs flow rules for the `t_tunnel_ingress` table (method
+`insertTunnelIngressRule` at line 182), and/or the `t_tunnel_fwd` table (method
+`insertTunnelForwardRule` at line 219), depending on the position of the switch
+in the path, the app will install rule to perform the ingress, transit, or
+egress behaviors.
+
+## Exercise steps
+
+1. **Complete the implementation of the MyTunnel app**:
+
+ 1. Open [MyTunnelApp.java](./mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java) in your editor of choice.
+
+ 2. Look for the `insertTunnelForwardRule` method (line 219).
+
+ 3. Complete the implementation of this method (There's a `TODO EXERCISE`
+ comment at line 251).
+
+ **Spoiler alert:** There is a reference solution in the same directory
+ as MyTunnelApp.java. Feel free to compare your implementation to the
+ reference one.
+
+2. **Start ONOS with and all the apps**.
+
+ 1. On a first terminal window, start ONOS:
+
+ ```
+ $ cd $ONOS_ROOT
+ $ ONOS_APPS=proxyarp,hostprovider,lldpprovider ok clean
+ ```
+
+ 2. On a second terminal window to **access the ONOS CLI**:
+
+ ```
+ $ onos localhost
+ ```
+
+ 2. **Acticate the BMv2 drivers, pipeconf, and MyTunnel app**:
+
+ ```
+ onos> app activate org.onosproject.drivers.bmv2
+ onos> app activate org.onosproject.p4tutorial.pipeconf
+ onos> app activate org.onosproject.p4tutorial.mytunnel
+ ```
+
+ **Hint:** To avoid accessing the CLI to start all applications, you can
+ modify the value of the `ONOS_APPS` variable when starting ONOS. For
+ example:
+
+ ```
+ $ cd $ONOS_ROOT
+ $ ONOS_APPS=proxyarp,hostprovider,lldpprovider,drivers.bmv2,p4tutorial.pipeconf,p4tutorial.mytunnel ok clean
+ ```
+
+ 3. **Check that all apps have been activated successfully**:
+
+ ```
+ onos> apps -s -a
+ ```
+
+ You should see an output like this:
+
+ ```
+ org.onosproject.hostprovider ... Host Location Provider
+ org.onosproject.lldpprovider ... LLDP Link Provider
+ org.onosproject.proxyarp ... Proxy ARP/NDP
+ org.onosproject.drivers ... Default Drivers
+ org.onosproject.protocols.grpc ... gRPC Protocol Subsystem
+ org.onosproject.protocols.p4runtime ... P4Runtime Protocol Subsystem
+ org.onosproject.p4runtime ... P4Runtime Provider
+ org.onosproject.generaldeviceprovider ... General Device Provider
+ org.onosproject.drivers.p4runtime ... P4Runtime Drivers
+ org.onosproject.p4tutorial.pipeconf ... P4 Tutorial Pipeconf
+ org.onosproject.pipelines.basic ... Basic Pipelines
+ org.onosproject.protocols.gnmi ... gNMI Protocol Subsystem
+ org.onosproject.drivers.gnmi ... gNMI Drivers
+ org.onosproject.drivers.bmv2 ... BMv2 Drivers
+ org.onosproject.p4tutorial.mytunnel ... MyTunnel Demo App
+ ```
+
+ 4. (optional) **Change flow rule polling interval**. Run the following
+ command in the ONOS CLI:
+
+ ```
+ onos> cfg set org.onosproject.net.flow.impl.FlowRuleManager fallbackFlowPollFrequency 5
+ ```
+
+3. **Run Mininet to set up a tree topology of BMv2 devices**, on a new terminal
+window type:
+
+ ```
+ $ sudo -E mn --custom $BMV2_MN_PY --switch onosbmv2,pipeconf=p4-tutorial-pipeconf --topo tree,3 --controller remote,ip=127.0.0.1
+ ```
+
+4. **Check that all devices, link, and hosts have been discovered correctly in ONOS**.
+
+ 1. To check the devices, on the ONOS CLI, type:
+
+ ```
+ onos> devices -s
+ ```
+
+ The `-s` argument provides a more compact output.
+
+ You should see 7 devices in total. Please note the driver that has been
+ assigned to this device `bmv2:p4-tutorial-pipeconf`. It means that the
+ device is being controlled using the driver behaviors provided the BMv2
+ device driver (which uses P4Runtime) and the pipeconf.
+
+ 2. Check the links:
+
+ ```
+ onos> links
+ ```
+
+ The `-s` argument provides a more compact output.
+
+ You should see 12 links (the topology has 6 bidirectional links in total).
+
+ 3. Check the hosts:
+
+ ```
+ onos> hosts -s
+ ```
+
+ You should see 0 hosts, as we have not injected any ARP packet yet.
+
+5. **Ping hosts**, on the Mininet CLI, type:
+
+ ```
+ mininet> h1 ping h7
+ ```
+
+ If the implementation of MyTunnelApp.java has been completed correctly,
+ ping should work. If not, check the ONOS log for possible errors in the
+ MyTunnel app. As a last resort, please check the reference solution in
+ the same directory as MyTunnelApp.java and compare that to yours.
+
+6. **Look around**.
+
+ 1. Repeat step 3.v and 3.vi from exercise one to check the
+flow rules in ONOS and on BMv2.
+
+ 2. Check the hosts in ONOS:
+
+ ```
+ onos> hosts -s
+ ```
+
+ You should see 2 hosts, h1 and h7.
diff --git a/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java b/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java
index eb91243..5acc434 100644
--- a/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java
+++ b/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.java
@@ -55,7 +55,6 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
-import static org.onlab.util.ImmutableByteSequence.copyFrom;
import static org.slf4j.LoggerFactory.getLogger;
/**
@@ -80,35 +79,7 @@
private static final Logger log = getLogger(MyTunnelApp.class);
//--------------------------------------------------------------------------
- // P4 program entity names. Values derived from mytunnel.p4info
- //--------------------------------------------------------------------------
-
- // Match field IDs.
- private static final PiMatchFieldId MF_ID_MY_TUNNEL_DST_ID =
- PiMatchFieldId.of("hdr.my_tunnel.tun_id");
- private static final PiMatchFieldId MF_ID_IP_DST =
- PiMatchFieldId.of("hdr.ipv4.dst_addr");
- // Table IDs.
- private static final PiTableId TBL_ID_TUNNEL_FWD =
- PiTableId.of("c_ingress.t_tunnel_fwd");
- private static final PiTableId TBL_ID_TUNNEL_INGRESS =
- PiTableId.of("c_ingress.t_tunnel_ingress");
- // Action IDs.
- private static final PiActionId ACT_ID_MY_TUNNEL_INGRESS =
- PiActionId.of("c_ingress.my_tunnel_ingress");
- private static final PiActionId ACT_ID_SET_OUT_PORT =
- PiActionId.of("c_ingress.set_out_port");
- private static final PiActionId ACT_ID_MY_TUNNEL_EGRESS =
- PiActionId.of("c_ingress.my_tunnel_egress");
-
- // Action params ID.
- private static final PiActionParamId ACT_PARAM_ID_TUN_ID =
- PiActionParamId.of("tun_id");
- private static final PiActionParamId ACT_PARAM_ID_PORT =
- PiActionParamId.of("port");
-
- //--------------------------------------------------------------------------
- // ONOS services needed by this application.
+ // ONOS core services needed by this application.
//--------------------------------------------------------------------------
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -181,20 +152,20 @@
// In ONOS discovered hosts can have multiple IP addresses.
// Insert tunnel ingress rule for each IP address.
// Next switches will forward based only on tunnel ID.
- insertIngressRule(srcSwitch, dstIpAddr, tunId);
+ insertTunnelIngressRule(srcSwitch, dstIpAddr, tunId);
}
- // Insert tunnel forward rules on all switches in the path, excluded the
+ // Insert tunnel transit rules on all switches in the path, excluded the
// last one.
for (Link link : pathLinks) {
DeviceId sw = link.src().deviceId();
PortNumber port = link.src().port();
- insertForwardRule(sw, port, tunId, false);
+ insertTunnelForwardRule(sw, port, tunId, false);
}
// Tunnel egress rule.
PortNumber egressSwitchPort = dstHost.location().port();
- insertForwardRule(dstSwitch, egressSwitchPort, tunId, true);
+ insertTunnelForwardRule(dstSwitch, egressSwitchPort, tunId, true);
log.info("** Completed provisioning of tunnel {} (srcHost={} dstHost={})",
tunId, srcHost.id(), dstHost.id());
@@ -208,24 +179,31 @@
* @param dstIpAddr IP address to forward inside the tunnel
* @param tunId tunnel ID
*/
- private void insertIngressRule(DeviceId switchId,
- IpAddress dstIpAddr,
- int tunId) {
+ private void insertTunnelIngressRule(DeviceId switchId,
+ IpAddress dstIpAddr,
+ int tunId) {
- log.info("Inserting INGRESS rule: switchId={}, dstIpAddr={}, tunId={}",
- switchId, dstIpAddr, tunId);
+ PiTableId tunnelIngressTableId = PiTableId.of("c_ingress.t_tunnel_ingress");
+
+ // Longest prefix match on IPv4 dest address.
+ PiMatchFieldId ipDestMatchFieldId = PiMatchFieldId.of("hdr.ipv4.dst_addr");
PiCriterion match = PiCriterion.builder()
- .matchLpm(MF_ID_IP_DST, dstIpAddr.toOctets(), 32)
+ .matchLpm(ipDestMatchFieldId, dstIpAddr.toOctets(), 32)
.build();
+ PiActionParam tunIdParam = new PiActionParam(PiActionParamId.of("tun_id"), tunId);
+
+ PiActionId ingressActionId = PiActionId.of("c_ingress.my_tunnel_ingress");
PiAction action = PiAction.builder()
- .withId(ACT_ID_MY_TUNNEL_INGRESS)
- .withParameter(new PiActionParam(
- ACT_PARAM_ID_TUN_ID, copyFrom(tunId)))
+ .withId(ingressActionId)
+ .withParameter(tunIdParam)
.build();
- insertPiFlowRule(switchId, TBL_ID_TUNNEL_INGRESS, match, action);
+ log.info("Inserting INGRESS rule on switch {}: table={}, match={}, action={}",
+ switchId, tunnelIngressTableId, match, action);
+
+ insertPiFlowRule(switchId, tunnelIngressTableId, match, action);
}
/**
@@ -234,31 +212,59 @@
*
* @param switchId switch ID
* @param outPort output port where to forward tunneled packets
- * @param tunId tunnel ID
+ * @param tunId tunnel ID
* @param isEgress if true, perform tunnel egress action, otherwise forward
* packet as is to port
*/
- private void insertForwardRule(DeviceId switchId,
- PortNumber outPort,
- int tunId,
- boolean isEgress) {
+ private void insertTunnelForwardRule(DeviceId switchId,
+ PortNumber outPort,
+ int tunId,
+ boolean isEgress) {
- log.info("Inserting {} rule: switchId={}, outPort={}, tunId={}",
- isEgress ? "EGRESS" : "FORWARD", switchId, outPort, tunId);
+ PiTableId tunnelForwardTableId = PiTableId.of("c_ingress.t_tunnel_fwd");
+ // Exact match on tun_id
+ PiMatchFieldId tunIdMatchFieldId = PiMatchFieldId.of("hdr.my_tunnel.tun_id");
PiCriterion match = PiCriterion.builder()
- .matchExact(MF_ID_MY_TUNNEL_DST_ID, tunId)
+ .matchExact(tunIdMatchFieldId, tunId)
.build();
- PiActionId actionId = isEgress ? ACT_ID_MY_TUNNEL_EGRESS : ACT_ID_SET_OUT_PORT;
+ // Action depend on isEgress parameter.
+ // if true, perform tunnel egress action on the given outPort, otherwise
+ // simply forward packet as is (set_out_port action).
+ PiActionParamId portParamId = PiActionParamId.of("port");
+ PiActionParam portParam = new PiActionParam(portParamId, (short) outPort.toLong());
- PiAction action = PiAction.builder()
- .withId(actionId)
- .withParameter(new PiActionParam(
- ACT_PARAM_ID_PORT, copyFrom((short) outPort.toLong())))
- .build();
+ final PiAction action;
+ if (isEgress) {
+ // Tunnel egress action.
+ // Remove MyTunnel header and forward to outPort.
+ PiActionId egressActionId = PiActionId.of("c_ingress.my_tunnel_egress");
+ action = PiAction.builder()
+ .withId(egressActionId)
+ .withParameter(portParam)
+ .build();
+ } else {
+ // Tunnel transit action.
+ // Forward the packet as is to outPort.
+ /*
+ * TODO EXERCISE: create action object for the transit case.
+ * Look at the t_tunnel_fwd table in the P4 program. Which of the 3
+ * actions can be used to simply set the output port? Get the full
+ * action name from the P4Info file, and use that when creating the
+ * PiActionId object. When creating the PiAction object, remember to
+ * add all action parameters as defined in the P4 program.
+ *
+ * Hint: the code will be similar to the case when isEgress = false.
+ */
+ action = null;
+ }
- insertPiFlowRule(switchId, TBL_ID_TUNNEL_FWD, match, action);
+ log.info("Inserting {} rule on switch {}: table={}, match={}, action={}",
+ isEgress ? "EGRESS" : "TRANSIT",
+ switchId, tunnelForwardTableId, match, action);
+
+ insertPiFlowRule(switchId, tunnelForwardTableId, match, action);
}
/**
diff --git a/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.solution b/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.solution
new file mode 100644
index 0000000..ab28e52
--- /dev/null
+++ b/apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.solution
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+package org.onosproject.p4tutorial.mytunnel;
+
+import com.google.common.collect.Lists;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.IpAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyService;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * MyTunnel application which provides forwarding between each pair of hosts via
+ * MyTunnel protocol as defined in mytunnel.p4.
+ * <p>
+ * The app works by listening for host events. Each time a new host is
+ * discovered, it provisions a tunnel between that host and all the others.
+ */
+@Component(immediate = true)
+public class MyTunnelApp {
+
+ private static final String APP_NAME = "org.onosproject.p4tutorial.mytunnel";
+
+ // Default priority used for flow rules installed by this app.
+ private static final int FLOW_RULE_PRIORITY = 100;
+
+ private final HostListener hostListener = new InternalHostListener();
+ private ApplicationId appId;
+ private AtomicInteger nextTunnelId = new AtomicInteger();
+
+ private static final Logger log = getLogger(MyTunnelApp.class);
+
+ //--------------------------------------------------------------------------
+ // ONOS services needed by this application.
+ //--------------------------------------------------------------------------
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private TopologyService topologyService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private HostService hostService;
+
+ //--------------------------------------------------------------------------
+ //--------------------------------------------------------------------------
+
+ @Activate
+ public void activate() {
+ // Register app and event listeners.
+ log.info("Starting...");
+ appId = coreService.registerApplication(APP_NAME);
+ hostService.addListener(hostListener);
+ log.info("STARTED", appId.id());
+ }
+
+ @Deactivate
+ public void deactivate() {
+ // Remove listeners and clean-up flow rules.
+ log.info("Stopping...");
+ hostService.removeListener(hostListener);
+ flowRuleService.removeFlowRulesById(appId);
+ log.info("STOPPED");
+ }
+
+ /**
+ * Provisions a tunnel between the given source and destination host with
+ * the given tunnel ID. The tunnel is established using a randomly picked
+ * shortest path based on the given topology snapshot.
+ *
+ * @param tunId tunnel ID
+ * @param srcHost tunnel source host
+ * @param dstHost tunnel destination host
+ * @param topo topology snapshot
+ */
+ private void provisionTunnel(int tunId, Host srcHost, Host dstHost, Topology topo) {
+
+ // Get all shortest paths between switches connected to source and
+ // destination hosts.
+ DeviceId srcSwitch = srcHost.location().deviceId();
+ DeviceId dstSwitch = dstHost.location().deviceId();
+
+ List<Link> pathLinks;
+ if (srcSwitch.equals(dstSwitch)) {
+ // Source and dest hosts are connected to the same switch.
+ pathLinks = Collections.emptyList();
+ } else {
+ // Compute shortest path.
+ Set<Path> allPaths = topologyService.getPaths(topo, srcSwitch, dstSwitch);
+ if (allPaths.size() == 0) {
+ log.warn("No paths between {} and {}", srcHost.id(), dstHost.id());
+ return;
+ }
+ // If many shortest paths are available, pick a random one.
+ pathLinks = pickRandomPath(allPaths).links();
+ }
+
+ // Tunnel ingress rules.
+ for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
+ // In ONOS discovered hosts can have multiple IP addresses.
+ // Insert tunnel ingress rule for each IP address.
+ // Next switches will forward based only on tunnel ID.
+ insertTunnelIngressRule(srcSwitch, dstIpAddr, tunId);
+ }
+
+ // Insert tunnel transit rules on all switches in the path, excluded the
+ // last one.
+ for (Link link : pathLinks) {
+ DeviceId sw = link.src().deviceId();
+ PortNumber port = link.src().port();
+ insertTunnelForwardRule(sw, port, tunId, false);
+ }
+
+ // Tunnel egress rule.
+ PortNumber egressSwitchPort = dstHost.location().port();
+ insertTunnelForwardRule(dstSwitch, egressSwitchPort, tunId, true);
+
+ log.info("** Completed provisioning of tunnel {} (srcHost={} dstHost={})",
+ tunId, srcHost.id(), dstHost.id());
+ }
+
+ /**
+ * Generates and insert a flow rule to perform the tunnel INGRESS function
+ * for the given switch, destination IP address and tunnel ID.
+ *
+ * @param switchId switch ID
+ * @param dstIpAddr IP address to forward inside the tunnel
+ * @param tunId tunnel ID
+ */
+ private void insertTunnelIngressRule(DeviceId switchId,
+ IpAddress dstIpAddr,
+ int tunId) {
+
+
+ PiTableId tunnelIngressTableId = PiTableId.of("c_ingress.t_tunnel_ingress");
+
+ // Longest prefix match on IPv4 dest address.
+ PiMatchFieldId ipDestMatchFieldId = PiMatchFieldId.of("hdr.ipv4.dst_addr");
+ PiCriterion match = PiCriterion.builder()
+ .matchLpm(ipDestMatchFieldId, dstIpAddr.toOctets(), 32)
+ .build();
+
+ PiActionParam tunIdParam = new PiActionParam(PiActionParamId.of("tun_id"), tunId);
+
+ PiActionId ingressActionId = PiActionId.of("c_ingress.my_tunnel_ingress");
+ PiAction action = PiAction.builder()
+ .withId(ingressActionId)
+ .withParameter(tunIdParam)
+ .build();
+
+ log.info("Inserting INGRESS rule on switch {}: table={}, match={}, action={}",
+ switchId, tunnelIngressTableId, match, action);
+
+ insertPiFlowRule(switchId, tunnelIngressTableId, match, action);
+ }
+
+ /**
+ * Generates and insert a flow rule to perform the tunnel FORWARD/EGRESS
+ * function for the given switch, output port address and tunnel ID.
+ *
+ * @param switchId switch ID
+ * @param outPort output port where to forward tunneled packets
+ * @param tunId tunnel ID
+ * @param isEgress if true, perform tunnel egress action, otherwise forward
+ * packet as is to port
+ */
+ private void insertTunnelForwardRule(DeviceId switchId,
+ PortNumber outPort,
+ int tunId,
+ boolean isEgress) {
+
+ PiTableId tunnelForwardTableId = PiTableId.of("c_ingress.t_tunnel_fwd");
+
+ // Exact match on tun_id
+ PiMatchFieldId tunIdMatchFieldId = PiMatchFieldId.of("hdr.my_tunnel.tun_id");
+ PiCriterion match = PiCriterion.builder()
+ .matchExact(tunIdMatchFieldId, tunId)
+ .build();
+
+ // Action depend on isEgress parameter.
+ // if true, perform tunnel egress action on the given outPort, otherwise
+ // simply forward packet as is (set_out_port action).
+ PiActionParamId portParamId = PiActionParamId.of("port");
+ PiActionParam portParam = new PiActionParam(portParamId, (short) outPort.toLong());
+
+ final PiAction action;
+ if (isEgress) {
+ // Tunnel egress action.
+ // Remove MyTunnel header and forward to outPort.
+ PiActionId egressActionId = PiActionId.of("c_ingress.my_tunnel_egress");
+ action = PiAction.builder()
+ .withId(egressActionId)
+ .withParameter(portParam)
+ .build();
+ } else {
+ // Tunnel transit action.
+ // Forward the packet as is to outPort.
+ PiActionId egressActionId = PiActionId.of("c_ingress.set_out_port");
+ action = PiAction.builder()
+ .withId(egressActionId)
+ .withParameter(portParam)
+ .build();
+ }
+
+ log.info("Inserting {} rule on switch {}: table={}, match={}, action={}",
+ isEgress ? "EGRESS" : "TRANSIT",
+ switchId, tunnelForwardTableId, match, action);
+
+ insertPiFlowRule(switchId, tunnelForwardTableId, match, action);
+ }
+
+ /**
+ * Inserts a flow rule in the system that using a PI criterion and action.
+ *
+ * @param switchId switch ID
+ * @param tableId table ID
+ * @param piCriterion PI criterion
+ * @param piAction PI action
+ */
+ private void insertPiFlowRule(DeviceId switchId, PiTableId tableId,
+ PiCriterion piCriterion, PiAction piAction) {
+ FlowRule rule = DefaultFlowRule.builder()
+ .forDevice(switchId)
+ .forTable(tableId)
+ .fromApp(appId)
+ .withPriority(FLOW_RULE_PRIORITY)
+ .makePermanent()
+ .withSelector(DefaultTrafficSelector.builder()
+ .matchPi(piCriterion).build())
+ .withTreatment(DefaultTrafficTreatment.builder()
+ .piTableAction(piAction).build())
+ .build();
+ flowRuleService.applyFlowRules(rule);
+ }
+
+ private int getNewTunnelId() {
+ return nextTunnelId.incrementAndGet();
+ }
+
+ private Path pickRandomPath(Set<Path> paths) {
+ int item = new Random().nextInt(paths.size());
+ List<Path> pathList = Lists.newArrayList(paths);
+ return pathList.get(item);
+ }
+
+ /**
+ * A listener of host events that provisions two tunnels for each pair of
+ * hosts when a new host is discovered.
+ */
+ private class InternalHostListener implements HostListener {
+
+ @Override
+ public void event(HostEvent event) {
+ if (event.type() != HostEvent.Type.HOST_ADDED) {
+ // Ignore other host events.
+ return;
+ }
+ synchronized (this) {
+ // Synchronizing here is an overkill, but safer for demo purposes.
+ Host host = event.subject();
+ Topology topo = topologyService.currentTopology();
+ for (Host otherHost : hostService.getHosts()) {
+ if (!host.equals(otherHost)) {
+ provisionTunnel(getNewTunnelId(), host, otherHost, topo);
+ provisionTunnel(getNewTunnelId(), otherHost, host, topo);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
index 58afa10..77a6dc3 100644
--- a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
@@ -93,7 +93,7 @@
private static final PiActionId ACT_ID_SEND_TO_CPU =
PiActionId.of(C_INGRESS + DOT + "send_to_cpu");
private static final PiActionId ACT_ID_SET_EGRESS_PORT =
- PiActionId.of(C_INGRESS + DOT + "set_egress_port");
+ PiActionId.of(C_INGRESS + DOT + "set_out_port");
private static final PiActionParamId ACT_PARAM_ID_PORT =
PiActionParamId.of("port");
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4 b/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4
index 7d73489..36de752 100644
--- a/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4
@@ -14,6 +14,15 @@
* limitations under the License.
*/
+ /*
+ * This program describes a pipeline implementing a very simple
+ * tunneling protocol called MyTunnel. The pipeline defines also table called
+ * t_l2_fwd that provides basic L2 forwarding capabilities and actions to
+ * send packets to the controller. This table is needed to provide
+ * compatibility with existing ONOS applications such as Proxy-ARP, LLDP Link
+ * Discovery and Reactive Forwarding.
+ */
+
#include <core.p4>
#include <v1model.p4>
@@ -92,6 +101,9 @@
inout metadata_t meta,
inout standard_metadata_t standard_metadata) {
+ // A P4 parser is described as a state machine, with initial state "start"
+ // and final one "accept". Each intermediate state can specify the next
+ // state by using a select statement over the header fields extracted.
state start {
transition select(standard_metadata.ingress_port) {
CPU_PORT: parse_packet_out;
@@ -150,6 +162,8 @@
}
action set_out_port(port_t port) {
+ // Specifies the output port for this packet by setting the
+ // corresponding metadata.
standard_metadata.egress_spec = port;
}
@@ -170,6 +184,8 @@
hdr.my_tunnel.setInvalid();
}
+ // Table counter used to count packets and bytes matched by each entry of
+ // t_l2_fwd table.
direct_counter(CounterType.packets_and_bytes) l2_fwd_counter;
table t_l2_fwd {
@@ -180,9 +196,9 @@
hdr.ethernet.ether_type : ternary;
}
actions = {
- set_out_port();
- send_to_cpu();
- _drop();
+ set_out_port;
+ send_to_cpu;
+ _drop;
NoAction;
}
default_action = NoAction();
@@ -195,7 +211,7 @@
}
actions = {
my_tunnel_ingress;
- _drop();
+ _drop;
}
default_action = _drop();
}
@@ -207,12 +223,13 @@
actions = {
set_out_port;
my_tunnel_egress;
- _drop();
+ _drop;
}
default_action = _drop();
}
- // Define processing applied by this control block.
+ // Defines the processing applied by this control block. You can see this as
+ // the main function applied to every packet received by the switch.
apply {
if (standard_metadata.ingress_port == CPU_PORT) {
// Packet received from CPU_PORT, this is a packet-out sent by the
@@ -223,6 +240,7 @@
hdr.packet_out.setInvalid();
} else {
// Packet received from data plane port.
+ // Applies table t_l2_fwd to the packet.
if (t_l2_fwd.apply().hit) {
// Packet hit an entry in t_l2_fwd table. A forwarding action
// has already been taken. No need to apply other tables, exit