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 eb91243421..c5fac985f2 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.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import static org.onlab.util.ImmutableByteSequence.copyFrom; import static org.slf4j.LoggerFactory.getLogger; /** @@ -79,34 +78,6 @@ public class MyTunnelApp { 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. //-------------------------------------------------------------------------- @@ -181,20 +152,20 @@ public class MyTunnelApp { // 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 @@ public class MyTunnelApp { * @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 @@ public class MyTunnelApp { * * @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 0000000000..ab28e52606 --- /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. + *
+ * 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 pathLinks;
+ if (srcSwitch.equals(dstSwitch)) {
+ // Source and dest hosts are connected to the same switch.
+ pathLinks = Collections.emptyList();
+ } else {
+ // Compute shortest path.
+ Set