From 0bd3b18df2163e4fbc248a2b5cb0ac1e8bd67bee Mon Sep 17 00:00:00 2001 From: Carmelo Cascone Date: Wed, 18 Apr 2018 18:59:39 +0900 Subject: [PATCH] MyTunnel exercise with solution for P4 tutorial Change-Id: Ifddfa27d6df6545bbd529239ca400967fb623a29 --- .../p4tutorial/mytunnel/MyTunnelApp.java | 122 +++---- .../p4tutorial/mytunnel/MyTunnelApp.solution | 323 ++++++++++++++++++ .../pipeconf/src/main/resources/mytunnel.p4 | 10 +- 3 files changed, 392 insertions(+), 63 deletions(-) create mode 100644 apps/p4-tutorial/mytunnel/src/main/java/org/onosproject/p4tutorial/mytunnel/MyTunnelApp.solution 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 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 paths) { + int item = new Random().nextInt(paths.size()); + List 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/resources/mytunnel.p4 b/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4 index 4912847fd5..36de7520bb 100644 --- a/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4 +++ b/apps/p4-tutorial/pipeconf/src/main/resources/mytunnel.p4 @@ -196,9 +196,9 @@ control c_ingress(inout headers_t hdr, hdr.ethernet.ether_type : ternary; } actions = { - set_out_port(); - send_to_cpu(); - _drop(); + set_out_port; + send_to_cpu; + _drop; NoAction; } default_action = NoAction(); @@ -211,7 +211,7 @@ control c_ingress(inout headers_t hdr, } actions = { my_tunnel_ingress; - _drop(); + _drop; } default_action = _drop(); } @@ -223,7 +223,7 @@ control c_ingress(inout headers_t hdr, actions = { set_out_port; my_tunnel_egress; - _drop(); + _drop; } default_action = _drop(); }