diff options
Diffstat (limited to 'framework/src/onos/apps/fwd')
3 files changed, 909 insertions, 0 deletions
diff --git a/framework/src/onos/apps/fwd/pom.xml b/framework/src/onos/apps/fwd/pom.xml new file mode 100644 index 00000000..324f46cd --- /dev/null +++ b/framework/src/onos/apps/fwd/pom.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-apps</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-app-fwd</artifactId> + <packaging>bundle</packaging> + + <description>Reactive forwarding application using flow subsystem</description> + + <properties> + <onos.app.name>org.onosproject.fwd</onos.app.name> + </properties> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/ReactiveForwarding.java b/framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/ReactiveForwarding.java new file mode 100644 index 00000000..8540ba14 --- /dev/null +++ b/framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/ReactiveForwarding.java @@ -0,0 +1,844 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * 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.fwd; + +import com.google.common.collect.ImmutableSet; +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.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ICMP; +import org.onlab.packet.ICMP6; +import org.onlab.packet.IPv4; +import org.onlab.packet.IPv6; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.Ip6Prefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.TCP; +import org.onlab.packet.TpPort; +import org.onlab.packet.UDP; +import org.onlab.packet.VlanId; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.event.Event; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.host.HostService; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketPriority; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.topology.TopologyEvent; +import org.onosproject.net.topology.TopologyListener; +import org.onosproject.net.topology.TopologyService; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Sample reactive forwarding application. + */ +@Component(immediate = true) +public class ReactiveForwarding { + + private static final int DEFAULT_TIMEOUT = 10; + private static final int DEFAULT_PRIORITY = 10; + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleService flowRuleService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowObjectiveService flowObjectiveService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private ReactivePacketProcessor processor = new ReactivePacketProcessor(); + + private ApplicationId appId; + + @Property(name = "packetOutOnly", boolValue = false, + label = "Enable packet-out only forwarding; default is false") + private boolean packetOutOnly = false; + + @Property(name = "packetOutOfppTable", boolValue = false, + label = "Enable first packet forwarding using OFPP_TABLE port " + + "instead of PacketOut with actual port; default is false") + private boolean packetOutOfppTable = false; + + @Property(name = "flowTimeout", intValue = DEFAULT_TIMEOUT, + label = "Configure Flow Timeout for installed flow rules; " + + "default is 10 sec") + private int flowTimeout = DEFAULT_TIMEOUT; + + @Property(name = "flowPriority", intValue = DEFAULT_PRIORITY, + label = "Configure Flow Priority for installed flow rules; " + + "default is 10") + private int flowPriority = DEFAULT_PRIORITY; + + @Property(name = "ipv6Forwarding", boolValue = false, + label = "Enable IPv6 forwarding; default is false") + private boolean ipv6Forwarding = false; + + @Property(name = "matchDstMacOnly", boolValue = false, + label = "Enable matching Dst Mac Only; default is false") + private boolean matchDstMacOnly = false; + + @Property(name = "matchVlanId", boolValue = false, + label = "Enable matching Vlan ID; default is false") + private boolean matchVlanId = false; + + @Property(name = "matchIpv4Address", boolValue = false, + label = "Enable matching IPv4 Addresses; default is false") + private boolean matchIpv4Address = false; + + @Property(name = "matchIpv4Dscp", boolValue = false, + label = "Enable matching IPv4 DSCP and ECN; default is false") + private boolean matchIpv4Dscp = false; + + @Property(name = "matchIpv6Address", boolValue = false, + label = "Enable matching IPv6 Addresses; default is false") + private boolean matchIpv6Address = false; + + @Property(name = "matchIpv6FlowLabel", boolValue = false, + label = "Enable matching IPv6 FlowLabel; default is false") + private boolean matchIpv6FlowLabel = false; + + @Property(name = "matchTcpUdpPorts", boolValue = false, + label = "Enable matching TCP/UDP ports; default is false") + private boolean matchTcpUdpPorts = false; + + @Property(name = "matchIcmpFields", boolValue = false, + label = "Enable matching ICMPv4 and ICMPv6 fields; " + + "default is false") + private boolean matchIcmpFields = false; + + + @Property(name = "ignoreIPv4Multicast", boolValue = false, + label = "Ignore (do not forward) IPv4 multicast packets; default is false") + private boolean ignoreIpv4McastPackets = false; + + private final TopologyListener topologyListener = new InternalTopologyListener(); + + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + appId = coreService.registerApplication("org.onosproject.fwd"); + + packetService.addProcessor(processor, PacketProcessor.director(2)); + topologyService.addListener(topologyListener); + readComponentConfiguration(context); + requestIntercepts(); + + log.info("Started", appId.id()); + } + + @Deactivate + public void deactivate() { + cfgService.unregisterProperties(getClass(), false); + withdrawIntercepts(); + flowRuleService.removeFlowRulesById(appId); + packetService.removeProcessor(processor); + topologyService.removeListener(topologyListener); + processor = null; + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + readComponentConfiguration(context); + requestIntercepts(); + } + + /** + * Request packet in via packet service. + */ + private void requestIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId); + selector.matchEthType(Ethernet.TYPE_ARP); + packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId); + + selector.matchEthType(Ethernet.TYPE_IPV6); + if (ipv6Forwarding) { + packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId); + } else { + packetService.cancelPackets(selector.build(), PacketPriority.REACTIVE, appId); + } + } + + /** + * Cancel request for packet in via packet service. + */ + private void withdrawIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + packetService.cancelPackets(selector.build(), PacketPriority.REACTIVE, appId); + selector.matchEthType(Ethernet.TYPE_ARP); + packetService.cancelPackets(selector.build(), PacketPriority.REACTIVE, appId); + selector.matchEthType(Ethernet.TYPE_IPV6); + packetService.cancelPackets(selector.build(), PacketPriority.REACTIVE, appId); + } + + /** + * Extracts properties from the component configuration context. + * + * @param context the component context + */ + private void readComponentConfiguration(ComponentContext context) { + Dictionary<?, ?> properties = context.getProperties(); + boolean packetOutOnlyEnabled = + isPropertyEnabled(properties, "packetOutOnly"); + if (packetOutOnly != packetOutOnlyEnabled) { + packetOutOnly = packetOutOnlyEnabled; + log.info("Configured. Packet-out only forwarding is {}", + packetOutOnly ? "enabled" : "disabled"); + } + boolean packetOutOfppTableEnabled = + isPropertyEnabled(properties, "packetOutOfppTable"); + if (packetOutOfppTable != packetOutOfppTableEnabled) { + packetOutOfppTable = packetOutOfppTableEnabled; + log.info("Configured. Forwarding using OFPP_TABLE port is {}", + packetOutOfppTable ? "enabled" : "disabled"); + } + boolean ipv6ForwardingEnabled = + isPropertyEnabled(properties, "ipv6Forwarding"); + if (ipv6Forwarding != ipv6ForwardingEnabled) { + ipv6Forwarding = ipv6ForwardingEnabled; + log.info("Configured. IPv6 forwarding is {}", + ipv6Forwarding ? "enabled" : "disabled"); + } + boolean matchDstMacOnlyEnabled = + isPropertyEnabled(properties, "matchDstMacOnly"); + if (matchDstMacOnly != matchDstMacOnlyEnabled) { + matchDstMacOnly = matchDstMacOnlyEnabled; + log.info("Configured. Match Dst MAC Only is {}", + matchDstMacOnly ? "enabled" : "disabled"); + } + boolean matchVlanIdEnabled = + isPropertyEnabled(properties, "matchVlanId"); + if (matchVlanId != matchVlanIdEnabled) { + matchVlanId = matchVlanIdEnabled; + log.info("Configured. Matching Vlan ID is {}", + matchVlanId ? "enabled" : "disabled"); + } + boolean matchIpv4AddressEnabled = + isPropertyEnabled(properties, "matchIpv4Address"); + if (matchIpv4Address != matchIpv4AddressEnabled) { + matchIpv4Address = matchIpv4AddressEnabled; + log.info("Configured. Matching IPv4 Addresses is {}", + matchIpv4Address ? "enabled" : "disabled"); + } + boolean matchIpv4DscpEnabled = + isPropertyEnabled(properties, "matchIpv4Dscp"); + if (matchIpv4Dscp != matchIpv4DscpEnabled) { + matchIpv4Dscp = matchIpv4DscpEnabled; + log.info("Configured. Matching IPv4 DSCP and ECN is {}", + matchIpv4Dscp ? "enabled" : "disabled"); + } + boolean matchIpv6AddressEnabled = + isPropertyEnabled(properties, "matchIpv6Address"); + if (matchIpv6Address != matchIpv6AddressEnabled) { + matchIpv6Address = matchIpv6AddressEnabled; + log.info("Configured. Matching IPv6 Addresses is {}", + matchIpv6Address ? "enabled" : "disabled"); + } + boolean matchIpv6FlowLabelEnabled = + isPropertyEnabled(properties, "matchIpv6FlowLabel"); + if (matchIpv6FlowLabel != matchIpv6FlowLabelEnabled) { + matchIpv6FlowLabel = matchIpv6FlowLabelEnabled; + log.info("Configured. Matching IPv6 FlowLabel is {}", + matchIpv6FlowLabel ? "enabled" : "disabled"); + } + boolean matchTcpUdpPortsEnabled = + isPropertyEnabled(properties, "matchTcpUdpPorts"); + if (matchTcpUdpPorts != matchTcpUdpPortsEnabled) { + matchTcpUdpPorts = matchTcpUdpPortsEnabled; + log.info("Configured. Matching TCP/UDP fields is {}", + matchTcpUdpPorts ? "enabled" : "disabled"); + } + boolean matchIcmpFieldsEnabled = + isPropertyEnabled(properties, "matchIcmpFields"); + if (matchIcmpFields != matchIcmpFieldsEnabled) { + matchIcmpFields = matchIcmpFieldsEnabled; + log.info("Configured. Matching ICMP (v4 and v6) fields is {}", + matchIcmpFields ? "enabled" : "disabled"); + } + Integer flowTimeoutConfigured = + getIntegerProperty(properties, "flowTimeout"); + if (flowTimeoutConfigured == null) { + log.info("Flow Timeout is not configured, default value is {}", + flowTimeout); + } else { + flowTimeout = flowTimeoutConfigured; + log.info("Configured. Flow Timeout is configured to {}", + flowTimeout, " seconds"); + } + Integer flowPriorityConfigured = + getIntegerProperty(properties, "flowPriority"); + if (flowPriorityConfigured == null) { + log.info("Flow Priority is not configured, default value is {}", + flowPriority); + } else { + flowPriority = flowPriorityConfigured; + log.info("Configured. Flow Priority is configured to {}", + flowPriority); + } + + boolean ignoreIpv4McastPacketsEnabled = + isPropertyEnabled(properties, "ignoreIpv4McastPackets"); + if (ignoreIpv4McastPackets != ignoreIpv4McastPacketsEnabled) { + ignoreIpv4McastPackets = ignoreIpv4McastPacketsEnabled; + log.info("Configured. Ignore IPv4 multicast packets is {}", + ignoreIpv4McastPackets ? "enabled" : "disabled"); + } + } + + /** + * Get Integer property from the propertyName + * Return null if propertyName is not found. + * + * @param properties properties to be looked up + * @param propertyName the name of the property to look up + * @return value when the propertyName is defined or return null + */ + private static Integer getIntegerProperty(Dictionary<?, ?> properties, + String propertyName) { + Integer value = null; + try { + String s = (String) properties.get(propertyName); + value = isNullOrEmpty(s) ? value : Integer.parseInt(s.trim()); + } catch (NumberFormatException | ClassCastException e) { + value = null; + } + return value; + } + + /** + * Check property name is defined and set to true. + * + * @param properties properties to be looked up + * @param propertyName the name of the property to look up + * @return true when the propertyName is defined and set to true + */ + private static boolean isPropertyEnabled(Dictionary<?, ?> properties, + String propertyName) { + boolean enabled = false; + try { + String flag = (String) properties.get(propertyName); + if (flag != null) { + enabled = flag.trim().equals("true"); + } + } catch (ClassCastException e) { + // No propertyName defined. + enabled = false; + } + return enabled; + } + + /** + * Packet processor responsible for forwarding packets along their paths. + */ + private class ReactivePacketProcessor implements PacketProcessor { + + @Override + public void process(PacketContext context) { + // Stop processing if the packet has been handled, since we + // can't do any more to it. + + if (context.isHandled()) { + return; + } + + InboundPacket pkt = context.inPacket(); + Ethernet ethPkt = pkt.parsed(); + + if (ethPkt == null) { + return; + } + + // Bail if this is deemed to be a control packet. + if (isControlPacket(ethPkt)) { + return; + } + + // Skip IPv6 multicast packet when IPv6 forward is disabled. + if (!ipv6Forwarding && isIpv6Multicast(ethPkt)) { + return; + } + + HostId id = HostId.hostId(ethPkt.getDestinationMAC()); + + // Do not process link-local addresses in any way. + if (id.mac().isLinkLocal()) { + return; + } + + // Do not process IPv4 multicast packets, let mfwd handle them + if (ignoreIpv4McastPackets && ethPkt.getEtherType() == Ethernet.TYPE_IPV4) { + if (id.mac().isMulticast()) { + return; + } + } + + // Do we know who this is for? If not, flood and bail. + Host dst = hostService.getHost(id); + if (dst == null) { + flood(context); + return; + } + + // Are we on an edge switch that our destination is on? If so, + // simply forward out to the destination and bail. + if (pkt.receivedFrom().deviceId().equals(dst.location().deviceId())) { + if (!context.inPacket().receivedFrom().port().equals(dst.location().port())) { + installRule(context, dst.location().port()); + } + return; + } + + // Otherwise, get a set of paths that lead from here to the + // destination edge switch. + Set<Path> paths = + topologyService.getPaths(topologyService.currentTopology(), + pkt.receivedFrom().deviceId(), + dst.location().deviceId()); + if (paths.isEmpty()) { + // If there are no paths, flood and bail. + flood(context); + return; + } + + // Otherwise, pick a path that does not lead back to where we + // came from; if no such path, flood and bail. + Path path = pickForwardPathIfPossible(paths, pkt.receivedFrom().port()); + if (path == null) { + log.warn("Don't know where to go from here {} for {} -> {}", + pkt.receivedFrom(), ethPkt.getSourceMAC(), ethPkt.getDestinationMAC()); + flood(context); + return; + } + + // Otherwise forward and be done with it. + installRule(context, path.src().port()); + } + + } + + // Indicates whether this is a control packet, e.g. LLDP, BDDP + private boolean isControlPacket(Ethernet eth) { + short type = eth.getEtherType(); + return type == Ethernet.TYPE_LLDP || type == Ethernet.TYPE_BSN; + } + + // Indicated whether this is an IPv6 multicast packet. + private boolean isIpv6Multicast(Ethernet eth) { + return eth.getEtherType() == Ethernet.TYPE_IPV6 && eth.isMulticast(); + } + + // Selects a path from the given set that does not lead back to the + // specified port if possible. + private Path pickForwardPathIfPossible(Set<Path> paths, PortNumber notToPort) { + Path lastPath = null; + for (Path path : paths) { + lastPath = path; + if (!path.src().port().equals(notToPort)) { + return path; + } + } + return lastPath; + } + + // Floods the specified packet if permissible. + private void flood(PacketContext context) { + if (topologyService.isBroadcastPoint(topologyService.currentTopology(), + context.inPacket().receivedFrom())) { + packetOut(context, PortNumber.FLOOD); + } else { + context.block(); + } + } + + // Sends a packet out the specified port. + private void packetOut(PacketContext context, PortNumber portNumber) { + context.treatmentBuilder().setOutput(portNumber); + context.send(); + } + + // Install a rule forwarding the packet to the specified port. + private void installRule(PacketContext context, PortNumber portNumber) { + // + // We don't support (yet) buffer IDs in the Flow Service so + // packet out first. + // + Ethernet inPkt = context.inPacket().parsed(); + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + + // If PacketOutOnly or ARP packet than forward directly to output port + if (packetOutOnly || inPkt.getEtherType() == Ethernet.TYPE_ARP) { + packetOut(context, portNumber); + return; + } + + // + // If matchDstMacOnly + // Create flows matching dstMac only + // Else + // Create flows with default matching and include configured fields + // + if (matchDstMacOnly) { + selectorBuilder.matchEthDst(inPkt.getDestinationMAC()); + } else { + selectorBuilder.matchInPort(context.inPacket().receivedFrom().port()) + .matchEthSrc(inPkt.getSourceMAC()) + .matchEthDst(inPkt.getDestinationMAC()); + + // If configured Match Vlan ID + if (matchVlanId && inPkt.getVlanID() != Ethernet.VLAN_UNTAGGED) { + selectorBuilder.matchVlanId(VlanId.vlanId(inPkt.getVlanID())); + } + + // + // If configured and EtherType is IPv4 - Match IPv4 and + // TCP/UDP/ICMP fields + // + if (matchIpv4Address && inPkt.getEtherType() == Ethernet.TYPE_IPV4) { + IPv4 ipv4Packet = (IPv4) inPkt.getPayload(); + byte ipv4Protocol = ipv4Packet.getProtocol(); + Ip4Prefix matchIp4SrcPrefix = + Ip4Prefix.valueOf(ipv4Packet.getSourceAddress(), + Ip4Prefix.MAX_MASK_LENGTH); + Ip4Prefix matchIp4DstPrefix = + Ip4Prefix.valueOf(ipv4Packet.getDestinationAddress(), + Ip4Prefix.MAX_MASK_LENGTH); + selectorBuilder.matchEthType(Ethernet.TYPE_IPV4) + .matchIPSrc(matchIp4SrcPrefix) + .matchIPDst(matchIp4DstPrefix); + + if (matchIpv4Dscp) { + byte dscp = ipv4Packet.getDscp(); + byte ecn = ipv4Packet.getEcn(); + selectorBuilder.matchIPDscp(dscp).matchIPEcn(ecn); + } + + if (matchTcpUdpPorts && ipv4Protocol == IPv4.PROTOCOL_TCP) { + TCP tcpPacket = (TCP) ipv4Packet.getPayload(); + selectorBuilder.matchIPProtocol(ipv4Protocol) + .matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort())) + .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort())); + } + if (matchTcpUdpPorts && ipv4Protocol == IPv4.PROTOCOL_UDP) { + UDP udpPacket = (UDP) ipv4Packet.getPayload(); + selectorBuilder.matchIPProtocol(ipv4Protocol) + .matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort())) + .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort())); + } + if (matchIcmpFields && ipv4Protocol == IPv4.PROTOCOL_ICMP) { + ICMP icmpPacket = (ICMP) ipv4Packet.getPayload(); + selectorBuilder.matchIPProtocol(ipv4Protocol) + .matchIcmpType(icmpPacket.getIcmpType()) + .matchIcmpCode(icmpPacket.getIcmpCode()); + } + } + + // + // If configured and EtherType is IPv6 - Match IPv6 and + // TCP/UDP/ICMP fields + // + if (matchIpv6Address && inPkt.getEtherType() == Ethernet.TYPE_IPV6) { + IPv6 ipv6Packet = (IPv6) inPkt.getPayload(); + byte ipv6NextHeader = ipv6Packet.getNextHeader(); + Ip6Prefix matchIp6SrcPrefix = + Ip6Prefix.valueOf(ipv6Packet.getSourceAddress(), + Ip6Prefix.MAX_MASK_LENGTH); + Ip6Prefix matchIp6DstPrefix = + Ip6Prefix.valueOf(ipv6Packet.getDestinationAddress(), + Ip6Prefix.MAX_MASK_LENGTH); + selectorBuilder.matchEthType(Ethernet.TYPE_IPV6) + .matchIPv6Src(matchIp6SrcPrefix) + .matchIPv6Dst(matchIp6DstPrefix); + + if (matchIpv6FlowLabel) { + selectorBuilder.matchIPv6FlowLabel(ipv6Packet.getFlowLabel()); + } + + if (matchTcpUdpPorts && ipv6NextHeader == IPv6.PROTOCOL_TCP) { + TCP tcpPacket = (TCP) ipv6Packet.getPayload(); + selectorBuilder.matchIPProtocol(ipv6NextHeader) + .matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort())) + .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort())); + } + if (matchTcpUdpPorts && ipv6NextHeader == IPv6.PROTOCOL_UDP) { + UDP udpPacket = (UDP) ipv6Packet.getPayload(); + selectorBuilder.matchIPProtocol(ipv6NextHeader) + .matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort())) + .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort())); + } + if (matchIcmpFields && ipv6NextHeader == IPv6.PROTOCOL_ICMP6) { + ICMP6 icmp6Packet = (ICMP6) ipv6Packet.getPayload(); + selectorBuilder.matchIPProtocol(ipv6NextHeader) + .matchIcmpv6Type(icmp6Packet.getIcmpType()) + .matchIcmpv6Code(icmp6Packet.getIcmpCode()); + } + } + } + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(portNumber) + .build(); + + ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder() + .withSelector(selectorBuilder.build()) + .withTreatment(treatment) + .withPriority(flowPriority) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .fromApp(appId) + .makeTemporary(flowTimeout) + .add(); + + flowObjectiveService.forward(context.inPacket().receivedFrom().deviceId(), + forwardingObjective); + + // + // If packetOutOfppTable + // Send packet back to the OpenFlow pipeline to match installed flow + // Else + // Send packet direction on the appropriate port + // + if (packetOutOfppTable) { + packetOut(context, PortNumber.TABLE); + } else { + packetOut(context, portNumber); + } + } + + private class InternalTopologyListener implements TopologyListener { + @Override + public void event(TopologyEvent event) { + List<Event> reasons = event.reasons(); + if (reasons != null) { + reasons.forEach(re -> { + if (re instanceof LinkEvent) { + LinkEvent le = (LinkEvent) re; + if (le.type() == LinkEvent.Type.LINK_REMOVED) { + fixBlackhole(le.subject().src()); + } + } + }); + } + } + } + + private void fixBlackhole(ConnectPoint egress) { + Set<FlowEntry> rules = getFlowRulesFrom(egress); + Set<SrcDstPair> pairs = findSrcDstPairs(rules); + + Map<DeviceId, Set<Path>> srcPaths = new HashMap<>(); + + for (SrcDstPair sd : pairs) { + // get the edge deviceID for the src host + Host srcHost = hostService.getHost(HostId.hostId(sd.src)); + Host dstHost = hostService.getHost(HostId.hostId(sd.dst)); + if (srcHost != null && dstHost != null) { + DeviceId srcId = srcHost.location().deviceId(); + DeviceId dstId = dstHost.location().deviceId(); + log.trace("SRC ID is " + srcId + ", DST ID is " + dstId); + + cleanFlowRules(sd, egress.deviceId()); + + Set<Path> shortestPaths = srcPaths.get(srcId); + if (shortestPaths == null) { + shortestPaths = topologyService.getPaths(topologyService.currentTopology(), + egress.deviceId(), srcId); + srcPaths.put(srcId, shortestPaths); + } + backTrackBadNodes(shortestPaths, dstId, sd); + } + } + } + + // Backtracks from link down event to remove flows that lead to blackhole + private void backTrackBadNodes(Set<Path> shortestPaths, DeviceId dstId, SrcDstPair sd) { + for (Path p : shortestPaths) { + List<Link> pathLinks = p.links(); + for (int i = 0; i < pathLinks.size(); i = i + 1) { + Link curLink = pathLinks.get(i); + DeviceId curDevice = curLink.src().deviceId(); + + // skipping the first link because this link's src has already been pruned beforehand + if (i != 0) { + cleanFlowRules(sd, curDevice); + } + + Set<Path> pathsFromCurDevice = + topologyService.getPaths(topologyService.currentTopology(), + curDevice, dstId); + if (pickForwardPathIfPossible(pathsFromCurDevice, curLink.src().port()) != null) { + break; + } else { + if (i + 1 == pathLinks.size()) { + cleanFlowRules(sd, curLink.dst().deviceId()); + } + } + } + } + } + + // Removes flow rules off specified device with specific SrcDstPair + private void cleanFlowRules(SrcDstPair pair, DeviceId id) { + log.trace("Searching for flow rules to remove from: " + id); + log.trace("Removing flows w/ SRC=" + pair.src + ", DST=" + pair.dst); + for (FlowEntry r : flowRuleService.getFlowEntries(id)) { + boolean matchesSrc = false, matchesDst = false; + for (Instruction i : r.treatment().allInstructions()) { + if (i.type() == Instruction.Type.OUTPUT) { + // if the flow has matching src and dst + for (Criterion cr : r.selector().criteria()) { + if (cr.type() == Criterion.Type.ETH_DST) { + if (((EthCriterion) cr).mac().equals(pair.dst)) { + matchesDst = true; + } + } else if (cr.type() == Criterion.Type.ETH_SRC) { + if (((EthCriterion) cr).mac().equals(pair.src)) { + matchesSrc = true; + } + } + } + } + } + if (matchesDst && matchesSrc) { + log.trace("Removed flow rule from device: " + id); + flowRuleService.removeFlowRules((FlowRule) r); + } + } + + } + + // Returns a set of src/dst MAC pairs extracted from the specified set of flow entries + private Set<SrcDstPair> findSrcDstPairs(Set<FlowEntry> rules) { + ImmutableSet.Builder<SrcDstPair> builder = ImmutableSet.builder(); + for (FlowEntry r : rules) { + MacAddress src = null, dst = null; + for (Criterion cr : r.selector().criteria()) { + if (cr.type() == Criterion.Type.ETH_DST) { + dst = ((EthCriterion) cr).mac(); + } else if (cr.type() == Criterion.Type.ETH_SRC) { + src = ((EthCriterion) cr).mac(); + } + } + builder.add(new SrcDstPair(src, dst)); + } + return builder.build(); + } + + // Returns set of flow entries which were created by this application and + // which egress from the specified connection port + private Set<FlowEntry> getFlowRulesFrom(ConnectPoint egress) { + ImmutableSet.Builder<FlowEntry> builder = ImmutableSet.builder(); + flowRuleService.getFlowEntries(egress.deviceId()).forEach(r -> { + if (r.appId() == appId.id()) { + r.treatment().allInstructions().forEach(i -> { + if (i.type() == Instruction.Type.OUTPUT) { + if (((Instructions.OutputInstruction) i).port().equals(egress.port())) { + builder.add(r); + } + } + }); + } + }); + + return builder.build(); + } + + // Wrapper class for a source and destination pair of MAC addresses + private final class SrcDstPair { + final MacAddress src; + final MacAddress dst; + + private SrcDstPair(MacAddress src, MacAddress dst) { + this.src = src; + this.dst = dst; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SrcDstPair that = (SrcDstPair) o; + return Objects.equals(src, that.src) && + Objects.equals(dst, that.dst); + } + + @Override + public int hashCode() { + return Objects.hash(src, dst); + } + } +} diff --git a/framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/package-info.java b/framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/package-info.java new file mode 100644 index 00000000..b947d40b --- /dev/null +++ b/framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * 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. + */ + +/** + * Sample application that provides simple form of reactive forwarding. + */ +package org.onosproject.fwd; |