aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/ReactiveForwarding.java
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/ReactiveForwarding.java')
-rw-r--r--framework/src/onos/apps/fwd/src/main/java/org/onosproject/fwd/ReactiveForwarding.java844
1 files changed, 844 insertions, 0 deletions
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);
+ }
+ }
+}