/* * Copyright 2015-present 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.flowanalyzer; 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.apache.felix.scr.annotations.Service; import org.onosproject.net.ConnectPoint; import org.onosproject.net.PortNumber; import org.onosproject.net.flow.FlowEntry; import org.onosproject.net.flow.FlowRuleService; import org.onosproject.net.DeviceId; import org.onosproject.net.HostId; import org.onosproject.net.flow.criteria.Criteria; import org.onosproject.net.flow.criteria.Criterion; import org.onosproject.net.flow.criteria.PortCriterion; import org.onosproject.net.flow.instructions.Instruction; import org.onosproject.net.flow.instructions.Instructions; import org.onosproject.net.topology.TopologyService; import org.onosproject.net.topology.TopologyGraph; import org.onosproject.net.link.LinkService; import org.onosproject.net.Link; import org.onosproject.net.topology.TopologyVertex; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import java.util.HashSet; import java.util.List; import java.util.HashMap; import java.util.Map; import java.util.Set; import static org.slf4j.LoggerFactory.getLogger; /** * Simple flow space analyzer app. */ @Component(immediate = true) @Service(value = FlowAnalyzer.class) public class FlowAnalyzer { private final Logger log = getLogger(getClass()); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected FlowRuleService flowRuleService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected TopologyService topologyService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected LinkService linkService; @Activate public void activate(ComponentContext context) { log.info("Started"); } @Deactivate public void deactivate() { log.info("Stopped"); } TopologyGraph graph; Map<FlowEntry, String> label = new HashMap<>(); Set<FlowEntry> ignoredFlows = new HashSet<>(); /** * Analyzes and prints out a report on the status of every flow entry inside * the network. The possible states are: Cleared (implying that the entry leads to * a host), Cycle (implying that it is part of cycle), and Black Hole (implying * that the entry does not lead to a single host). * * @return result string */ public String analyze() { graph = topologyService.getGraph(topologyService.currentTopology()); for (TopologyVertex v: graph.getVertexes()) { DeviceId srcDevice = v.deviceId(); Iterable<FlowEntry> flowTable = flowRuleService.getFlowEntries(srcDevice); for (FlowEntry flow: flowTable) { dfs(flow); } } //analyze the cycles to look for "critical flows" that can be removed //to break the cycle Set<FlowEntry> critpts = new HashSet<>(); for (FlowEntry flow: label.keySet()) { if ("Cycle".equals(label.get(flow))) { Map<FlowEntry, String> labelSaved = label; label = new HashMap<FlowEntry, String>(); ignoredFlows.add(flow); for (TopologyVertex v: graph.getVertexes()) { DeviceId srcDevice = v.deviceId(); Iterable<FlowEntry> flowTable = flowRuleService.getFlowEntries(srcDevice); for (FlowEntry flow1: flowTable) { dfs(flow1); } } boolean replacable = true; for (FlowEntry flow2: label.keySet()) { if ("Cleared".equals(labelSaved.get(flow2)) && !("Cleared".equals(label.get(flow2)))) { replacable = false; } } if (replacable) { critpts.add(flow); } label = labelSaved; } } for (FlowEntry flow: critpts) { label.put(flow, "Cycle Critical Point"); } String s = "\n"; for (FlowEntry flow: label.keySet()) { s += ("Flow Rule: " + flowEntryRepresentation(flow) + "\n"); s += ("Analysis: " + label.get(flow) + "!\n\n"); } s += ("Analyzed " + label.keySet().size() + " flows."); //log.info(s); return s; } public Map<FlowEntry, String> calcLabels() { analyze(); return label; } public String analysisOutput() { analyze(); String s = "\n"; for (FlowEntry flow: label.keySet()) { s += ("Flow Rule: " + flowEntryRepresentation(flow) + "\n"); s += ("Analysis: " + label.get(flow) + "!\n\n"); } return s; } private boolean dfs(FlowEntry flow) { if (ignoredFlows.contains(flow)) { return false; } if ("Cycle".equals(label.get(flow)) || "Black Hole".equals(label.get(flow)) || "Cleared".equals(label.get(flow)) || "NA".equals(label.get(flow)) || "Cycle Critical Point".equals(label.get(flow))) { // This flow has already been analyzed and there is no need to analyze it further return !"Black Hole".equals(label.get(flow)); } if ("Visiting".equals(label.get(flow))) { //you've detected a cycle because you reached the same entry again during your dfs //let it continue so you can label the whole cycle label.put(flow, "Cycle"); } else { //otherwise, mark off the current flow entry as currently being visited label.put(flow, "Visiting"); } boolean pointsToLiveEntry = false; List<Instruction> instructions = flow.treatment().allInstructions(); for (Instruction i: instructions) { if (i instanceof Instructions.OutputInstruction) { pointsToLiveEntry |= analyzeInstruction(i, flow); } if ("NA".equals(label.get(flow))) { return pointsToLiveEntry; } } if (!pointsToLiveEntry) { //this entry does not point to any "live" entries thus must be a black hole label.put(flow, "Black Hole"); } else if ("Visiting".equals(label.get(flow))) { //the flow is not in a cycle or in a black hole label.put(flow, "Cleared"); } return pointsToLiveEntry; } private boolean analyzeInstruction(Instruction i, FlowEntry flow) { boolean pointsToLiveEntry = false; Instructions.OutputInstruction output = (Instructions.OutputInstruction) i; PortNumber port = output.port(); PortNumber outPort = null; DeviceId egress = null; boolean hasHost = false; ConnectPoint portPt = new ConnectPoint(flow.deviceId(), port); for (Link l: linkService.getEgressLinks(portPt)) { if (l.dst().elementId() instanceof DeviceId) { egress = l.dst().deviceId(); outPort = l.dst().port(); } else if (l.dst().elementId() instanceof HostId) { //the port leads to a host: therefore it is not a dead link pointsToLiveEntry = true; hasHost = true; } } if (!topologyService.isInfrastructure(topologyService.currentTopology(), portPt) && egress == null) { pointsToLiveEntry = true; hasHost = true; } if (hasHost) { return pointsToLiveEntry; } if (egress == null) { //the port that the flow instructions tells you to send the packet //to doesn't exist or is a controller port label.put(flow, "NA"); return pointsToLiveEntry; } Iterable<FlowEntry> dstFlowTable = flowRuleService.getFlowEntries(egress); Set<Criterion> flowCriteria = flow.selector().criteria(); //filter the criteria in order to remove port dependency Set<Criterion> filteredCriteria = new HashSet<>(); for (Criterion criterion : flowCriteria) { if (!(criterion instanceof PortCriterion)) { filteredCriteria.add(criterion); } } //ensure that the in port is equal to the port that it is coming in from filteredCriteria.add(Criteria.matchInPort(outPort)); for (FlowEntry entry: dstFlowTable) { if (ignoredFlows.contains(entry)) { continue; } if (filteredCriteria.containsAll(entry.selector().criteria())) { dfs(entry); if (!"Black Hole".equals(label.get(entry))) { //this entry is "live" i.e not a black hole pointsToLiveEntry = true; } } } return pointsToLiveEntry; } public String flowEntryRepresentation(FlowEntry flow) { return "Device: " + flow.deviceId() + ", " + flow.selector().criteria() + ", " + flow.treatment().immediate(); } }