blob: 5d6fabb4d64d4b4ace65e579d41f3623ff1a9697 [file] [log] [blame]
Simon Hunt026a2872017-11-13 17:09:43 -08001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.t3.impl;
18
Andrea Campanella17d45192018-01-18 17:11:42 +010019import com.google.common.base.Preconditions;
Andrea Campanellae4084402017-12-15 15:27:31 +010020import com.google.common.collect.ImmutableList;
21import com.google.common.collect.Lists;
Simon Hunt026a2872017-11-13 17:09:43 -080022import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
Andrea Campanellae4084402017-12-15 15:27:31 +010026import org.onlab.packet.VlanId;
Andrea Campanella54923d62018-01-23 12:46:04 +010027import org.onosproject.cluster.NodeId;
28import org.onosproject.mastership.MastershipService;
Simon Hunt026a2872017-11-13 17:09:43 -080029import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010030import org.onosproject.net.DeviceId;
31import org.onosproject.net.Host;
32import org.onosproject.net.Link;
33import org.onosproject.net.PortNumber;
Andrea Campanella17d45192018-01-18 17:11:42 +010034import org.onosproject.net.device.DeviceService;
Andrea Campanellae4084402017-12-15 15:27:31 +010035import org.onosproject.net.driver.DriverService;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.FlowEntry;
38import org.onosproject.net.flow.FlowRule;
Simon Hunt026a2872017-11-13 17:09:43 -080039import org.onosproject.net.flow.FlowRuleService;
Andrea Campanellae4084402017-12-15 15:27:31 +010040import org.onosproject.net.flow.IndexTableId;
41import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080042import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-12-15 15:27:31 +010043import org.onosproject.net.flow.criteria.Criteria;
44import org.onosproject.net.flow.criteria.Criterion;
45import org.onosproject.net.flow.criteria.EthCriterion;
46import org.onosproject.net.flow.criteria.EthTypeCriterion;
47import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanellae6798012018-02-06 15:46:52 +010048import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanellae4084402017-12-15 15:27:31 +010049import org.onosproject.net.flow.instructions.Instruction;
50import org.onosproject.net.flow.instructions.Instructions;
51import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
52import org.onosproject.net.flow.instructions.L2ModificationInstruction;
53import org.onosproject.net.group.Group;
54import org.onosproject.net.group.GroupBucket;
Simon Hunt026a2872017-11-13 17:09:43 -080055import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010056import org.onosproject.net.host.HostService;
57import org.onosproject.net.link.LinkService;
58import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080059import org.onosproject.t3.api.StaticPacketTrace;
60import org.onosproject.t3.api.TroubleshootService;
61import org.slf4j.Logger;
62
Andrea Campanellae4084402017-12-15 15:27:31 +010063import java.net.UnknownHostException;
64import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010065import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010066import java.util.Collections;
67import java.util.Comparator;
68import java.util.HashSet;
69import java.util.List;
70import java.util.Set;
71import java.util.stream.Collectors;
72
73import static org.onlab.packet.EthType.EtherType;
Andrea Campanellae6798012018-02-06 15:46:52 +010074import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanellae4084402017-12-15 15:27:31 +010075import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Simon Hunt026a2872017-11-13 17:09:43 -080076import static org.slf4j.LoggerFactory.getLogger;
77
78/**
Andrea Campanellae4084402017-12-15 15:27:31 +010079 * Manager to troubleshoot packets inside the network.
80 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
81 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080082 */
83@Service
84@Component(immediate = true)
85public class TroubleshootManager implements TroubleshootService {
86
87 private static final Logger log = getLogger(TroubleshootManager.class);
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected FlowRuleService flowRuleService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected GroupService groupService;
94
Andrea Campanellae4084402017-12-15 15:27:31 +010095 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -080097
Andrea Campanellae4084402017-12-15 15:27:31 +010098 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99 protected HostService hostService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -0800103
Andrea Campanella17d45192018-01-18 17:11:42 +0100104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected DeviceService deviceService;
106
Andrea Campanella54923d62018-01-23 12:46:04 +0100107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected MastershipService mastershipService;
109
Simon Hunt026a2872017-11-13 17:09:43 -0800110 @Override
111 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100112 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100113 //device must exist in ONOS
114 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
115 "Device " + in.deviceId() + " must exist in ONOS");
116
Andrea Campanellae4084402017-12-15 15:27:31 +0100117 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
118 //FIXME this can be done recursively
119 trace = traceInDevice(trace, packet, in);
120 //Building output connect Points
121 List<ConnectPoint> path = new ArrayList<>();
122 trace = getTrace(path, in, trace);
123 return trace;
124 }
125
126 /**
127 * Computes a trace for a give packet that start in the network at the given connect point.
128 *
129 * @param completePath the path traversed by the packet
130 * @param in the input connect point
131 * @param trace the trace to build
132 * @return the build trace for that packet.
133 */
134 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
135
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100136 log.debug("------------------------------------------------------------");
137
Andrea Campanellae4084402017-12-15 15:27:31 +0100138 //if the trace already contains the input connect point there is a loop
139 if (pathContainsDevice(completePath, in.deviceId())) {
140 trace.addResultMessage("Loop encountered in device " + in.deviceId());
141 return trace;
142 }
143
144 //let's add the input connect point
145 completePath.add(in);
146
147 //If the trace has no outputs for the given input we stop here
148 if (trace.getGroupOuputs(in.deviceId()) == null) {
149 computePath(completePath, trace, null);
150 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
151 return trace;
152 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100153
Andrea Campanellae4084402017-12-15 15:27:31 +0100154 //If the trace has ouputs we analyze them all
155 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100156
157 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100158 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100159 log.debug("Output path {}", cp);
160
Andrea Campanellae4084402017-12-15 15:27:31 +0100161 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100162 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100163 //Hosts queried from the original ip or mac
164 Set<Host> hosts = getHosts(trace);
165
166 //If the two host collections contain the same item it means we reached the proper output
167 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100168 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella54923d62018-01-23 12:46:04 +0100169 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100170 computePath(completePath, trace, outputPath.getOutput());
171 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100172 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100173
Andrea Campanella54923d62018-01-23 12:46:04 +0100174 //Getting the master when the packet gets sent as packet in
175 NodeId master = mastershipService.getMasterFor(cp.deviceId());
176 trace.addResultMessage("Packet goes to the controller " + master.id());
177 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100178 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100179
Andrea Campanella8292ba62018-01-31 16:43:23 +0100180 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100181
182 //TODO this can be optimized if we use a Tree structure for paths.
183 //if we already have outputs let's check if the one we are considering starts from one of the devices
184 // in any of the ones we have.
185 if (trace.getCompletePaths().size() > 0) {
186 ConnectPoint inputForOutput = null;
187 List<ConnectPoint> previousPath = new ArrayList<>();
188 for (List<ConnectPoint> path : trace.getCompletePaths()) {
189 for (ConnectPoint connect : path) {
190 //if the path already contains the input for the output we've found we use it
191 if (connect.equals(in)) {
192 inputForOutput = connect;
193 previousPath = path;
194 break;
195 }
196 }
197 }
198 //we use the pre-existing path up to the point we fork to a new output
199 if (inputForOutput != null && completePath.contains(inputForOutput)) {
200 List<ConnectPoint> temp = new ArrayList<>(previousPath);
201 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
202 }
203 }
204
Andrea Campanellae4084402017-12-15 15:27:31 +0100205 //let's add the ouput for the input
206 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100207 //let's compute the links for the given output
208 Set<Link> links = linkService.getEgressLinks(cp);
209 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100210 //For each link we trace the corresponding device
211 for (Link link : links) {
212 ConnectPoint dst = link.dst();
213 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100214 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100215 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
216 updatedPacket.add(Criteria.matchInPort(dst.port()));
217 log.debug("DST Connect Point {}", dst);
218 //build the elements for that device
219 traceInDevice(trace, updatedPacket.build(), dst);
220 //continue the trace along the path
221 getTrace(completePath, dst, trace);
222 }
223
Andrea Campanella8292ba62018-01-31 16:43:23 +0100224 } else if (deviceService.getPort(cp).isEnabled()) {
225 if (hostsList.isEmpty()) {
226 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
227 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
228 cp + " with no hosts connected ");
229 } else {
230 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
231 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
232 cp + " with hosts " + hostsList);
233 }
234 computePath(completePath, trace, outputPath.getOutput());
235
236 } else {
237 //No links means that the packet gets dropped.
238 log.warn("No links out of {}", cp);
239 computePath(completePath, trace, cp);
240 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanellae4084402017-12-15 15:27:31 +0100241 }
242 }
243 return trace;
244 }
245
246 /**
Andrea Campanellae6798012018-02-06 15:46:52 +0100247 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
248 * If ONOS applied a vlan we remove it.
249 *
250 * @param outputPath the output
251 * @param trace the trace we are building
252 */
253 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
254
255 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
256 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
257
258 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
259
260 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
261 //removing the final vlanId
262 finalCriteria.remove(finalVid);
263 Builder packetUpdated = DefaultTrafficSelector.builder();
264 finalCriteria.forEach(packetUpdated::add);
265 //Initial was none so we set it to that
266 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
267 //Update final packet
268 outputPath.setFinalPacket(packetUpdated.build());
269 }
270 }
271
272 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100273 * Checks if the path contains the device.
274 *
275 * @param completePath the path
276 * @param deviceId the device to check
277 * @return true if the path contains the device
278 */
279 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
280 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
281 for (ConnectPoint cp : completePath) {
282 if (cp.deviceId().equals(deviceId)) {
283 return true;
284 }
285 }
286 return false;
287 }
288
289 /**
290 * Gets the hosts for the given initial packet.
291 *
292 * @param trace the trace we are building
293 * @return set of the hosts we are trying to reach
294 */
295 private Set<Host> getHosts(StaticPacketTrace trace) {
296 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
297 .getCriterion(Criterion.Type.IPV4_DST));
298 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
299 .getCriterion(Criterion.Type.IPV6_DST));
300 Set<Host> hosts = new HashSet<>();
301 if (ipv4Criterion != null) {
302 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
303 }
304 if (ipv6Criterion != null) {
305 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
306 }
307 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
308 .getCriterion(Criterion.Type.ETH_DST));
309 if (ethCriterion != null) {
310 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
311 }
312 return hosts;
313 }
314
315 /**
316 * Computes the list of traversed connect points.
317 *
318 * @param completePath the list of devices
319 * @param trace the trace we are building
320 * @param output the final output connect point
321 */
322 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
323 List<ConnectPoint> traverseList = new ArrayList<>();
324 if (!completePath.contains(trace.getInitialConnectPoint())) {
325 traverseList.add(trace.getInitialConnectPoint());
326 }
327 traverseList.addAll(completePath);
328 if (output != null && !completePath.contains(output)) {
329 traverseList.add(output);
330 }
331 trace.addCompletePath(traverseList);
Andrea Campanellae4084402017-12-15 15:27:31 +0100332 }
333
334 /**
335 * Traces the packet inside a device starting from an input connect point.
336 *
337 * @param trace the trace we are building
338 * @param packet the packet we are tracing
339 * @param in the input connect point.
340 * @return updated trace
341 */
342 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100343
344 //we already traversed this device.
345 if (trace.getGroupOuputs(in.deviceId()) != null) {
346 log.debug("Trace already contains device and given outputs");
347 return trace;
348 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100349 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100350
351 //if device is not available exit here.
352 if (!deviceService.isAvailable(in.deviceId())) {
353 trace.addResultMessage("Device is offline " + in.deviceId());
354 return trace;
355 }
356
Andrea Campanellae4084402017-12-15 15:27:31 +0100357 List<FlowEntry> flows = new ArrayList<>();
358 List<FlowEntry> outputFlows = new ArrayList<>();
359
Andrea Campanella8292ba62018-01-31 16:43:23 +0100360 List<Instruction> deferredInstructions = new ArrayList<>();
361
Andrea Campanellae4084402017-12-15 15:27:31 +0100362 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
363 if (nextTableIdEntry == null) {
364 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
365 return trace;
366 }
367 TableId tableId = nextTableIdEntry.table();
368 FlowEntry flowEntry;
369 boolean output = false;
370 while (!output) {
371 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
372 //get the rule that matches the incoming packet
373 flowEntry = matchHighestPriority(packet, in, tableId);
374 log.debug("Found Flow Entry {}", flowEntry);
375
376 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
377 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
378
379 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
380 if (flowEntry == null && isOfdpaHardware) {
381 log.debug("Ofdpa Hw setup, no flow rule means table miss");
382
383 //Handling Hardware Specifics
384 if (((IndexTableId) tableId).id() == 27) {
385 //Apparently a miss but Table 27 on OFDPA is a fixed table
386 packet = handleOfdpa27FixedTable(trace, packet);
387 }
388
389 //Finding next table to go In case of miss
390 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
391 log.debug("Next table id entry {}", nextTableIdEntry);
392
393 //FIXME find better solution that enable granularity greater than 0 or all rules
394 //(another possibility is max tableId)
395 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100396 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellae4084402017-12-15 15:27:31 +0100397 return trace;
398
399 } else if (nextTableIdEntry == null) {
400 //Means that no more flow rules are present
401 output = true;
402
403 } else if (((IndexTableId) tableId).id() == 20) {
404 //if the table is 20 OFDPA skips to table 50
405 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
406 tableId = IndexTableId.of(50);
407
408 } else {
409 tableId = nextTableIdEntry.table();
410 }
411
412
413 } else if (flowEntry == null) {
414 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
415 in.deviceId() + ". Dropping");
416 return trace;
417 } else {
418 //IF the table has a transition
419 if (flowEntry.treatment().tableTransition() != null) {
420 //update the next table we transitions to
421 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
422 log.debug("Flow Entry has transition to table Id {}", tableId);
423 flows.add(flowEntry);
424 } else {
425 //table has no transition so it means that it's an output rule if on the last table
426 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
427 flows.add(flowEntry);
428 outputFlows.add(flowEntry);
429 output = true;
430 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100431 //update the packet according to the immediate actions of this flow rule.
432 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
433
434 //save the deferred rules for later
435 deferredInstructions.addAll(flowEntry.treatment().deferred());
436
437 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
438 if (flowEntry.treatment().clearedDeferred()) {
439 deferredInstructions.clear();
440 }
441
Andrea Campanellae4084402017-12-15 15:27:31 +0100442 }
443 }
444
445 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100446 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100447 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100448
Andrea Campanellae4084402017-12-15 15:27:31 +0100449 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100450 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100451
452 log.debug("Flows traversed by {}", packet);
453 flows.forEach(entry -> {
454 log.debug("Flow {}", entry);
455 });
456
457 log.debug("Output Flows for {}", packet);
458 outputFlows.forEach(entry -> {
459 log.debug("Output Flow {}", entry);
460 });
461 List<PortNumber> outputPorts = new ArrayList<>();
462
Andrea Campanella54923d62018-01-23 12:46:04 +0100463 //TODO optimization
Andrea Campanellae4084402017-12-15 15:27:31 +0100464
Andrea Campanella54923d62018-01-23 12:46:04 +0100465 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
466 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
467 .allInstructions().stream().filter(instruction -> instruction.type()
468 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100469
Andrea Campanella54923d62018-01-23 12:46:04 +0100470 if (outputFlowEntries.size() > 1) {
471 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
472 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100473 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100474
475 if (outputFlowEntries.size() == 1) {
476
477 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
478 .allInstructions().stream()
479 .filter(instruction -> {
480 return instruction.type().equals(Instruction.Type.OUTPUT);
481 }).findFirst().get();
482
483 //FIXME using GroupsInDevice for output even if flows.
484 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
485
486 }
487 log.debug("Handling Groups");
488 //Analyze Groups
489 List<Group> groups = new ArrayList<>();
490
491 Collection<FlowEntry> nonOutputFlows = flows;
492 nonOutputFlows.removeAll(outputFlowEntries);
493
Andrea Campanella8292ba62018-01-31 16:43:23 +0100494 //Handling groups pointed at by immediate instructions
Andrea Campanella54923d62018-01-23 12:46:04 +0100495 for (FlowEntry entry : flows) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100496 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanella54923d62018-01-23 12:46:04 +0100497 entry.deviceId(), builder, outputPorts, in);
498 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100499
500 //If we have deferred instructions at this point we handle them.
501 if (deferredInstructions.size() > 0) {
502 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
503
504 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100505 packet = builder.build();
506 log.debug("Groups hit by packet {}", packet);
507 groups.forEach(group -> {
508 log.debug("Group {}", group);
509 });
510
Andrea Campanellae4084402017-12-15 15:27:31 +0100511 log.debug("Output ports for packet {}", packet);
512 outputPorts.forEach(port -> {
513 log.debug("Port {}", port);
514 });
Andrea Campanella8292ba62018-01-31 16:43:23 +0100515 log.info("Output Packet {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100516 return trace;
517 }
518
Andrea Campanella8292ba62018-01-31 16:43:23 +0100519
Andrea Campanellae4084402017-12-15 15:27:31 +0100520 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100521 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
522 *
523 * @param packet the incoming packet
524 * @return the updated packet
525 */
526 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
527 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
528 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
529 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
530
531 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
532 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
533 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100534 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100535 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
536 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
537 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100538 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100539 builder.add(ethInstruction);
540 }
541 packet = updatePacket(packet, builder.build()).build();
542 return packet;
543 }
544
545 /**
546 * Finds the flow entry with the minimun next table Id.
547 *
548 * @param deviceId the device to search
549 * @param currentId the current id. the search will use this as minimum
550 * @return the flow entry with the minimum table Id after the given one.
551 */
552 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
553
554 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
555
556 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
557 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
558 }
559
Andrea Campanella8292ba62018-01-31 16:43:23 +0100560 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
561 ConnectPoint in, List<Instruction> deferredInstructions,
562 List<PortNumber> outputPorts, List<Group> groups) {
563
564 //Update the packet with the deferred instructions
565 Builder builder = updatePacket(packet, deferredInstructions);
566
567 //Gather any output instructions from the deferred instruction
568 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
569 return instruction.type().equals(Instruction.Type.OUTPUT);
570 }).collect(Collectors.toList());
571
572 //We are considering deferred instructions from flows, there can only be one output.
573 if (outputFlowInstruction.size() > 1) {
574 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
575 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
576 }
577 //If there is one output let's go through that
578 if (outputFlowInstruction.size() == 1) {
579 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
580 ImmutableList.of());
581 }
582 //If there is no output let's see if there any deferred instruction point to groups.
583 if (outputFlowInstruction.size() == 0) {
584 getGroupsFromInstructions(trace, groups, deferredInstructions,
585 in.deviceId(), builder, outputPorts, in);
586 }
587 return builder;
588 }
589
Andrea Campanellae4084402017-12-15 15:27:31 +0100590 /**
591 * Gets group information from instructions.
592 *
593 * @param trace the trace we are building
594 * @param groupsForDevice the set of groups for this device
595 * @param instructions the set of instructions we are searching for groups.
596 * @param deviceId the device we are considering
597 * @param builder the builder of the input packet
598 * @param outputPorts the output ports for that packet
599 */
600 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
601 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +0100602 Builder builder, List<PortNumber> outputPorts,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100603 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100604 List<Instruction> groupInstructionlist = new ArrayList<>();
605 for (Instruction instruction : instructions) {
606 log.debug("Considering Instruction {}", instruction);
607 //if the instruction is not group we need to update the packet or add the output
608 //to the possible outputs for this packet
609 if (!instruction.type().equals(Instruction.Type.GROUP)) {
610 //if the instruction is not group we need to update the packet or add the output
611 //to the possible outputs for this packet
612 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100613 buildOutputFromDevice(trace, in, builder, outputPorts,
614 (OutputInstruction) instruction, groupsForDevice);
Andrea Campanellae4084402017-12-15 15:27:31 +0100615 } else {
616 builder = translateInstruction(builder, instruction);
617 }
618 } else {
619 //if the instuction is pointing to a group we need to get the group
620 groupInstructionlist.add(instruction);
621 }
622 }
623 //handle all the internal instructions pointing to a group.
624 for (Instruction instr : groupInstructionlist) {
625 GroupInstruction groupInstruction = (GroupInstruction) instr;
626 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
627 return groupInternal.id().equals(groupInstruction.groupId());
628 }).findAny().orElse(null);
629 if (group == null) {
630 trace.addResultMessage("Null group for Instruction " + instr);
631 break;
632 }
633 //add the group to the traversed groups
634 groupsForDevice.add(group);
635 //Cycle in each of the group's buckets and add them to the groups for this Device.
636 for (GroupBucket bucket : group.buckets().buckets()) {
637 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100638 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100639 }
640 }
641 }
642
643 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100644 * Check if the output is the input port, if so adds a dop result message, otherwise builds
645 * a possible output from this device.
646 *
647 * @param trace the trace
648 * @param in the input connect point
649 * @param builder the packet builder
650 * @param outputPorts the list of output ports for this device
651 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +0100652 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100653 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100654 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100655 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
656 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100657 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100658 if (output.equals(in)) {
659 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
660 trace.getInitialConnectPoint());
661 } else {
662 trace.addGroupOutputPath(in.deviceId(),
663 new GroupsInDevice(output, groupsForDevice, builder.build()));
664 outputPorts.add(outputInstruction.port());
665 }
666 }
667
668 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100669 * Applies all give instructions to the input packet.
670 *
671 * @param packet the input packet
672 * @param instructions the set of instructions
673 * @return the packet with the applied instructions
674 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100675 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
676 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100677 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +0100678 //FIXME optimize
679 for (Instruction instruction : instructions) {
680 newSelector = translateInstruction(newSelector, instruction);
681 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100682 return newSelector;
683 }
684
685 /**
686 * Applies an instruction to the packet in the form of a selector.
687 *
688 * @param newSelector the packet selector
689 * @param instruction the instruction to be translated
690 * @return the new selector with the applied instruction
691 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100692 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100693 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +0100694 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +0100695 //TODO add as required
696 Criterion criterion = null;
697 switch (instruction.type()) {
698 case L2MODIFICATION:
699 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
700 switch (l2Instruction.subtype()) {
701 case VLAN_ID:
702 L2ModificationInstruction.ModVlanIdInstruction vlanIdInstruction =
703 (L2ModificationInstruction.ModVlanIdInstruction) instruction;
704 VlanId id = vlanIdInstruction.vlanId();
705 criterion = Criteria.matchVlanId(id);
706 break;
707 case VLAN_POP:
708 criterion = Criteria.matchVlanId(VlanId.NONE);
709 break;
710 case MPLS_PUSH:
711 L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
712 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
713 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
714 break;
715 case MPLS_POP:
716 L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
717 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
718 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +0100719
720 //When popping MPLS we remove label and BOS
721 TrafficSelector temporaryPacket = newSelector.build();
722 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100723 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +0100724 temporaryPacket.criteria().stream().filter(c -> {
725 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
726 !c.type().equals(Criterion.Type.MPLS_BOS);
727 }).forEach(noMplsSelector::add);
728 newSelector = noMplsSelector;
729 }
730
Andrea Campanellae4084402017-12-15 15:27:31 +0100731 break;
732 case MPLS_LABEL:
733 L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
734 (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
735 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +0100736 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +0100737 break;
738 case ETH_DST:
739 L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
740 (L2ModificationInstruction.ModEtherInstruction) instruction;
741 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
742 break;
743 case ETH_SRC:
744 L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
745 (L2ModificationInstruction.ModEtherInstruction) instruction;
746 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
747 break;
748 default:
749 log.debug("Unsupported L2 Instruction");
750 break;
751 }
752 break;
753 default:
754 log.debug("Unsupported Instruction");
755 break;
756 }
757 if (criterion != null) {
758 log.debug("Adding criterion {}", criterion);
759 newSelector.add(criterion);
760 }
761 return newSelector;
762 }
763
764 /**
765 * Finds the rule in the device that mathces the input packet and has the highest priority.
766 *
767 * @param packet the input packet
768 * @param in the connect point the packet comes in from
769 * @param tableId the table to search
770 * @return the flow entry
771 */
772 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
773 //Computing the possible match rules.
774 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
775 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
776 .stream()
777 .filter(flowEntry -> {
778 return flowEntry.table().equals(tableId);
779 })
780 .filter(flowEntry -> {
781 return match(packet, flowEntry);
782 }).max(comparator).orElse(null);
783 }
784
785 /**
786 * Matches the packet with the given flow entry.
787 *
788 * @param packet the packet to match
789 * @param flowEntry the flow entry to match the packet against
790 * @return true if the packet matches the flow.
791 */
792 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
793 //TODO handle MAC matching
794 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
795 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +0100796 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +0100797 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
798 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
799 IPCriterion ipCriterion = (IPCriterion) criterion;
800 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
Andrea Campanella128d9c62018-01-31 12:20:48 +0100801 //if the packet does not have an IPv4 or IPv6 criterion we return false
Andrea Campanellae4084402017-12-15 15:27:31 +0100802 if (matchCriterion == null) {
Andrea Campanella128d9c62018-01-31 12:20:48 +0100803 return false;
Andrea Campanellae4084402017-12-15 15:27:31 +0100804 }
805 try {
806 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
807 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
808 } catch (UnknownHostException e) {
809 return false;
810 }
811 //we check that the packet contains the criterion provided by the flow rule.
812 } else {
813 return packet.criteria().contains(criterion);
814 }
815 });
Simon Hunt026a2872017-11-13 17:09:43 -0800816 }
817}