blob: 8aeb8746b7cc6feac8070e0c51f2648360095bd0 [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;
48import org.onosproject.net.flow.instructions.Instruction;
49import org.onosproject.net.flow.instructions.Instructions;
50import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
51import org.onosproject.net.flow.instructions.L2ModificationInstruction;
52import org.onosproject.net.group.Group;
53import org.onosproject.net.group.GroupBucket;
Simon Hunt026a2872017-11-13 17:09:43 -080054import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010055import org.onosproject.net.host.HostService;
56import org.onosproject.net.link.LinkService;
57import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080058import org.onosproject.t3.api.StaticPacketTrace;
59import org.onosproject.t3.api.TroubleshootService;
60import org.slf4j.Logger;
61
Andrea Campanellae4084402017-12-15 15:27:31 +010062import java.net.UnknownHostException;
63import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010064import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010065import java.util.Collections;
66import java.util.Comparator;
67import java.util.HashSet;
68import java.util.List;
69import java.util.Set;
70import java.util.stream.Collectors;
71
72import static org.onlab.packet.EthType.EtherType;
Andrea Campanella8292ba62018-01-31 16:43:23 +010073import static org.onosproject.net.flow.TrafficSelector.*;
Andrea Campanellae4084402017-12-15 15:27:31 +010074import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Simon Hunt026a2872017-11-13 17:09:43 -080075import static org.slf4j.LoggerFactory.getLogger;
76
77/**
Andrea Campanellae4084402017-12-15 15:27:31 +010078 * Manager to troubleshoot packets inside the network.
79 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
80 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080081 */
82@Service
83@Component(immediate = true)
84public class TroubleshootManager implements TroubleshootService {
85
86 private static final Logger log = getLogger(TroubleshootManager.class);
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 protected FlowRuleService flowRuleService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected GroupService groupService;
93
Andrea Campanellae4084402017-12-15 15:27:31 +010094 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -080096
Andrea Campanellae4084402017-12-15 15:27:31 +010097 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected HostService hostService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -0800102
Andrea Campanella17d45192018-01-18 17:11:42 +0100103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected DeviceService deviceService;
105
Andrea Campanella54923d62018-01-23 12:46:04 +0100106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected MastershipService mastershipService;
108
Simon Hunt026a2872017-11-13 17:09:43 -0800109 @Override
110 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100111 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100112 //device must exist in ONOS
113 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
114 "Device " + in.deviceId() + " must exist in ONOS");
115
Andrea Campanellae4084402017-12-15 15:27:31 +0100116 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
117 //FIXME this can be done recursively
118 trace = traceInDevice(trace, packet, in);
119 //Building output connect Points
120 List<ConnectPoint> path = new ArrayList<>();
121 trace = getTrace(path, in, trace);
122 return trace;
123 }
124
125 /**
126 * Computes a trace for a give packet that start in the network at the given connect point.
127 *
128 * @param completePath the path traversed by the packet
129 * @param in the input connect point
130 * @param trace the trace to build
131 * @return the build trace for that packet.
132 */
133 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
134
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100135 log.debug("------------------------------------------------------------");
136
Andrea Campanellae4084402017-12-15 15:27:31 +0100137 //if the trace already contains the input connect point there is a loop
138 if (pathContainsDevice(completePath, in.deviceId())) {
139 trace.addResultMessage("Loop encountered in device " + in.deviceId());
140 return trace;
141 }
142
143 //let's add the input connect point
144 completePath.add(in);
145
146 //If the trace has no outputs for the given input we stop here
147 if (trace.getGroupOuputs(in.deviceId()) == null) {
148 computePath(completePath, trace, null);
149 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
150 return trace;
151 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100152
Andrea Campanellae4084402017-12-15 15:27:31 +0100153 //If the trace has ouputs we analyze them all
154 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100155
156 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100157 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100158 log.debug("Output path {}", cp);
159
Andrea Campanellae4084402017-12-15 15:27:31 +0100160 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100161 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100162 //Hosts queried from the original ip or mac
163 Set<Host> hosts = getHosts(trace);
164
165 //If the two host collections contain the same item it means we reached the proper output
166 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100167 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella54923d62018-01-23 12:46:04 +0100168 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100169 computePath(completePath, trace, outputPath.getOutput());
170 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100171 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100172
Andrea Campanella54923d62018-01-23 12:46:04 +0100173 //Getting the master when the packet gets sent as packet in
174 NodeId master = mastershipService.getMasterFor(cp.deviceId());
175 trace.addResultMessage("Packet goes to the controller " + master.id());
176 computePath(completePath, trace, outputPath.getOutput());
177
Andrea Campanella8292ba62018-01-31 16:43:23 +0100178 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100179
180 //TODO this can be optimized if we use a Tree structure for paths.
181 //if we already have outputs let's check if the one we are considering starts from one of the devices
182 // in any of the ones we have.
183 if (trace.getCompletePaths().size() > 0) {
184 ConnectPoint inputForOutput = null;
185 List<ConnectPoint> previousPath = new ArrayList<>();
186 for (List<ConnectPoint> path : trace.getCompletePaths()) {
187 for (ConnectPoint connect : path) {
188 //if the path already contains the input for the output we've found we use it
189 if (connect.equals(in)) {
190 inputForOutput = connect;
191 previousPath = path;
192 break;
193 }
194 }
195 }
196 //we use the pre-existing path up to the point we fork to a new output
197 if (inputForOutput != null && completePath.contains(inputForOutput)) {
198 List<ConnectPoint> temp = new ArrayList<>(previousPath);
199 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
200 }
201 }
202
Andrea Campanellae4084402017-12-15 15:27:31 +0100203 //let's add the ouput for the input
204 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100205 //let's compute the links for the given output
206 Set<Link> links = linkService.getEgressLinks(cp);
207 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100208 //For each link we trace the corresponding device
209 for (Link link : links) {
210 ConnectPoint dst = link.dst();
211 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100212 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100213 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
214 updatedPacket.add(Criteria.matchInPort(dst.port()));
215 log.debug("DST Connect Point {}", dst);
216 //build the elements for that device
217 traceInDevice(trace, updatedPacket.build(), dst);
218 //continue the trace along the path
219 getTrace(completePath, dst, trace);
220 }
221
Andrea Campanella8292ba62018-01-31 16:43:23 +0100222 } else if (deviceService.getPort(cp).isEnabled()) {
223 if (hostsList.isEmpty()) {
224 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
225 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
226 cp + " with no hosts connected ");
227 } else {
228 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
229 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
230 cp + " with hosts " + hostsList);
231 }
232 computePath(completePath, trace, outputPath.getOutput());
233
234 } else {
235 //No links means that the packet gets dropped.
236 log.warn("No links out of {}", cp);
237 computePath(completePath, trace, cp);
238 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanellae4084402017-12-15 15:27:31 +0100239 }
240 }
241 return trace;
242 }
243
244 /**
245 * Checks if the path contains the device.
246 *
247 * @param completePath the path
248 * @param deviceId the device to check
249 * @return true if the path contains the device
250 */
251 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
252 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
253 for (ConnectPoint cp : completePath) {
254 if (cp.deviceId().equals(deviceId)) {
255 return true;
256 }
257 }
258 return false;
259 }
260
261 /**
262 * Gets the hosts for the given initial packet.
263 *
264 * @param trace the trace we are building
265 * @return set of the hosts we are trying to reach
266 */
267 private Set<Host> getHosts(StaticPacketTrace trace) {
268 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
269 .getCriterion(Criterion.Type.IPV4_DST));
270 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
271 .getCriterion(Criterion.Type.IPV6_DST));
272 Set<Host> hosts = new HashSet<>();
273 if (ipv4Criterion != null) {
274 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
275 }
276 if (ipv6Criterion != null) {
277 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
278 }
279 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
280 .getCriterion(Criterion.Type.ETH_DST));
281 if (ethCriterion != null) {
282 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
283 }
284 return hosts;
285 }
286
287 /**
288 * Computes the list of traversed connect points.
289 *
290 * @param completePath the list of devices
291 * @param trace the trace we are building
292 * @param output the final output connect point
293 */
294 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
295 List<ConnectPoint> traverseList = new ArrayList<>();
296 if (!completePath.contains(trace.getInitialConnectPoint())) {
297 traverseList.add(trace.getInitialConnectPoint());
298 }
299 traverseList.addAll(completePath);
300 if (output != null && !completePath.contains(output)) {
301 traverseList.add(output);
302 }
303 trace.addCompletePath(traverseList);
Andrea Campanellae4084402017-12-15 15:27:31 +0100304 }
305
306 /**
307 * Traces the packet inside a device starting from an input connect point.
308 *
309 * @param trace the trace we are building
310 * @param packet the packet we are tracing
311 * @param in the input connect point.
312 * @return updated trace
313 */
314 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100315
316 //we already traversed this device.
317 if (trace.getGroupOuputs(in.deviceId()) != null) {
318 log.debug("Trace already contains device and given outputs");
319 return trace;
320 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100321 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100322
323 //if device is not available exit here.
324 if (!deviceService.isAvailable(in.deviceId())) {
325 trace.addResultMessage("Device is offline " + in.deviceId());
326 return trace;
327 }
328
Andrea Campanellae4084402017-12-15 15:27:31 +0100329 List<FlowEntry> flows = new ArrayList<>();
330 List<FlowEntry> outputFlows = new ArrayList<>();
331
Andrea Campanella8292ba62018-01-31 16:43:23 +0100332 List<Instruction> deferredInstructions = new ArrayList<>();
333
Andrea Campanellae4084402017-12-15 15:27:31 +0100334 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
335 if (nextTableIdEntry == null) {
336 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
337 return trace;
338 }
339 TableId tableId = nextTableIdEntry.table();
340 FlowEntry flowEntry;
341 boolean output = false;
342 while (!output) {
343 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
344 //get the rule that matches the incoming packet
345 flowEntry = matchHighestPriority(packet, in, tableId);
346 log.debug("Found Flow Entry {}", flowEntry);
347
348 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
349 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
350
351 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
352 if (flowEntry == null && isOfdpaHardware) {
353 log.debug("Ofdpa Hw setup, no flow rule means table miss");
354
355 //Handling Hardware Specifics
356 if (((IndexTableId) tableId).id() == 27) {
357 //Apparently a miss but Table 27 on OFDPA is a fixed table
358 packet = handleOfdpa27FixedTable(trace, packet);
359 }
360
361 //Finding next table to go In case of miss
362 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
363 log.debug("Next table id entry {}", nextTableIdEntry);
364
365 //FIXME find better solution that enable granularity greater than 0 or all rules
366 //(another possibility is max tableId)
367 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100368 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellae4084402017-12-15 15:27:31 +0100369 return trace;
370
371 } else if (nextTableIdEntry == null) {
372 //Means that no more flow rules are present
373 output = true;
374
375 } else if (((IndexTableId) tableId).id() == 20) {
376 //if the table is 20 OFDPA skips to table 50
377 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
378 tableId = IndexTableId.of(50);
379
380 } else {
381 tableId = nextTableIdEntry.table();
382 }
383
384
385 } else if (flowEntry == null) {
386 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
387 in.deviceId() + ". Dropping");
388 return trace;
389 } else {
390 //IF the table has a transition
391 if (flowEntry.treatment().tableTransition() != null) {
392 //update the next table we transitions to
393 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
394 log.debug("Flow Entry has transition to table Id {}", tableId);
395 flows.add(flowEntry);
396 } else {
397 //table has no transition so it means that it's an output rule if on the last table
398 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
399 flows.add(flowEntry);
400 outputFlows.add(flowEntry);
401 output = true;
402 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100403 //update the packet according to the immediate actions of this flow rule.
404 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
405
406 //save the deferred rules for later
407 deferredInstructions.addAll(flowEntry.treatment().deferred());
408
409 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
410 if (flowEntry.treatment().clearedDeferred()) {
411 deferredInstructions.clear();
412 }
413
Andrea Campanellae4084402017-12-15 15:27:31 +0100414 }
415 }
416
417 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100418 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100419 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100420
Andrea Campanellae4084402017-12-15 15:27:31 +0100421 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100422 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100423
424 log.debug("Flows traversed by {}", packet);
425 flows.forEach(entry -> {
426 log.debug("Flow {}", entry);
427 });
428
429 log.debug("Output Flows for {}", packet);
430 outputFlows.forEach(entry -> {
431 log.debug("Output Flow {}", entry);
432 });
433 List<PortNumber> outputPorts = new ArrayList<>();
434
Andrea Campanella54923d62018-01-23 12:46:04 +0100435 //TODO optimization
Andrea Campanellae4084402017-12-15 15:27:31 +0100436
Andrea Campanella54923d62018-01-23 12:46:04 +0100437 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
438 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
439 .allInstructions().stream().filter(instruction -> instruction.type()
440 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100441
Andrea Campanella54923d62018-01-23 12:46:04 +0100442 if (outputFlowEntries.size() > 1) {
443 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
444 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100445 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100446
447 if (outputFlowEntries.size() == 1) {
448
449 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
450 .allInstructions().stream()
451 .filter(instruction -> {
452 return instruction.type().equals(Instruction.Type.OUTPUT);
453 }).findFirst().get();
454
455 //FIXME using GroupsInDevice for output even if flows.
456 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
457
458 }
459 log.debug("Handling Groups");
460 //Analyze Groups
461 List<Group> groups = new ArrayList<>();
462
463 Collection<FlowEntry> nonOutputFlows = flows;
464 nonOutputFlows.removeAll(outputFlowEntries);
465
Andrea Campanella8292ba62018-01-31 16:43:23 +0100466 //Handling groups pointed at by immediate instructions
Andrea Campanella54923d62018-01-23 12:46:04 +0100467 for (FlowEntry entry : flows) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100468 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanella54923d62018-01-23 12:46:04 +0100469 entry.deviceId(), builder, outputPorts, in);
470 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100471
472 //If we have deferred instructions at this point we handle them.
473 if (deferredInstructions.size() > 0) {
474 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
475
476 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100477 packet = builder.build();
478 log.debug("Groups hit by packet {}", packet);
479 groups.forEach(group -> {
480 log.debug("Group {}", group);
481 });
482
Andrea Campanellae4084402017-12-15 15:27:31 +0100483 log.debug("Output ports for packet {}", packet);
484 outputPorts.forEach(port -> {
485 log.debug("Port {}", port);
486 });
Andrea Campanella8292ba62018-01-31 16:43:23 +0100487 log.info("Output Packet {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100488 return trace;
489 }
490
Andrea Campanella8292ba62018-01-31 16:43:23 +0100491
Andrea Campanellae4084402017-12-15 15:27:31 +0100492 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100493 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
494 *
495 * @param packet the incoming packet
496 * @return the updated packet
497 */
498 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
499 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
500 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
501 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
502
503 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
504 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
505 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100506 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100507 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
508 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
509 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100510 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100511 builder.add(ethInstruction);
512 }
513 packet = updatePacket(packet, builder.build()).build();
514 return packet;
515 }
516
517 /**
518 * Finds the flow entry with the minimun next table Id.
519 *
520 * @param deviceId the device to search
521 * @param currentId the current id. the search will use this as minimum
522 * @return the flow entry with the minimum table Id after the given one.
523 */
524 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
525
526 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
527
528 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
529 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
530 }
531
Andrea Campanella8292ba62018-01-31 16:43:23 +0100532 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
533 ConnectPoint in, List<Instruction> deferredInstructions,
534 List<PortNumber> outputPorts, List<Group> groups) {
535
536 //Update the packet with the deferred instructions
537 Builder builder = updatePacket(packet, deferredInstructions);
538
539 //Gather any output instructions from the deferred instruction
540 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
541 return instruction.type().equals(Instruction.Type.OUTPUT);
542 }).collect(Collectors.toList());
543
544 //We are considering deferred instructions from flows, there can only be one output.
545 if (outputFlowInstruction.size() > 1) {
546 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
547 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
548 }
549 //If there is one output let's go through that
550 if (outputFlowInstruction.size() == 1) {
551 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
552 ImmutableList.of());
553 }
554 //If there is no output let's see if there any deferred instruction point to groups.
555 if (outputFlowInstruction.size() == 0) {
556 getGroupsFromInstructions(trace, groups, deferredInstructions,
557 in.deviceId(), builder, outputPorts, in);
558 }
559 return builder;
560 }
561
Andrea Campanellae4084402017-12-15 15:27:31 +0100562 /**
563 * Gets group information from instructions.
564 *
565 * @param trace the trace we are building
566 * @param groupsForDevice the set of groups for this device
567 * @param instructions the set of instructions we are searching for groups.
568 * @param deviceId the device we are considering
569 * @param builder the builder of the input packet
570 * @param outputPorts the output ports for that packet
571 */
572 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
573 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +0100574 Builder builder, List<PortNumber> outputPorts,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100575 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100576 List<Instruction> groupInstructionlist = new ArrayList<>();
577 for (Instruction instruction : instructions) {
578 log.debug("Considering Instruction {}", instruction);
579 //if the instruction is not group we need to update the packet or add the output
580 //to the possible outputs for this packet
581 if (!instruction.type().equals(Instruction.Type.GROUP)) {
582 //if the instruction is not group we need to update the packet or add the output
583 //to the possible outputs for this packet
584 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100585 buildOutputFromDevice(trace, in, builder, outputPorts,
586 (OutputInstruction) instruction, groupsForDevice);
Andrea Campanellae4084402017-12-15 15:27:31 +0100587 } else {
588 builder = translateInstruction(builder, instruction);
589 }
590 } else {
591 //if the instuction is pointing to a group we need to get the group
592 groupInstructionlist.add(instruction);
593 }
594 }
595 //handle all the internal instructions pointing to a group.
596 for (Instruction instr : groupInstructionlist) {
597 GroupInstruction groupInstruction = (GroupInstruction) instr;
598 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
599 return groupInternal.id().equals(groupInstruction.groupId());
600 }).findAny().orElse(null);
601 if (group == null) {
602 trace.addResultMessage("Null group for Instruction " + instr);
603 break;
604 }
605 //add the group to the traversed groups
606 groupsForDevice.add(group);
607 //Cycle in each of the group's buckets and add them to the groups for this Device.
608 for (GroupBucket bucket : group.buckets().buckets()) {
609 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100610 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100611 }
612 }
613 }
614
615 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100616 * Check if the output is the input port, if so adds a dop result message, otherwise builds
617 * a possible output from this device.
618 *
619 * @param trace the trace
620 * @param in the input connect point
621 * @param builder the packet builder
622 * @param outputPorts the list of output ports for this device
623 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +0100624 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100625 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100626 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100627 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
628 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100629 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100630 if (output.equals(in)) {
631 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
632 trace.getInitialConnectPoint());
633 } else {
634 trace.addGroupOutputPath(in.deviceId(),
635 new GroupsInDevice(output, groupsForDevice, builder.build()));
636 outputPorts.add(outputInstruction.port());
637 }
638 }
639
640 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100641 * Applies all give instructions to the input packet.
642 *
643 * @param packet the input packet
644 * @param instructions the set of instructions
645 * @return the packet with the applied instructions
646 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100647 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
648 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100649 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +0100650 //FIXME optimize
651 for (Instruction instruction : instructions) {
652 newSelector = translateInstruction(newSelector, instruction);
653 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100654 return newSelector;
655 }
656
657 /**
658 * Applies an instruction to the packet in the form of a selector.
659 *
660 * @param newSelector the packet selector
661 * @param instruction the instruction to be translated
662 * @return the new selector with the applied instruction
663 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100664 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100665 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +0100666 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +0100667 //TODO add as required
668 Criterion criterion = null;
669 switch (instruction.type()) {
670 case L2MODIFICATION:
671 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
672 switch (l2Instruction.subtype()) {
673 case VLAN_ID:
674 L2ModificationInstruction.ModVlanIdInstruction vlanIdInstruction =
675 (L2ModificationInstruction.ModVlanIdInstruction) instruction;
676 VlanId id = vlanIdInstruction.vlanId();
677 criterion = Criteria.matchVlanId(id);
678 break;
679 case VLAN_POP:
680 criterion = Criteria.matchVlanId(VlanId.NONE);
681 break;
682 case MPLS_PUSH:
683 L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
684 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
685 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
686 break;
687 case MPLS_POP:
688 L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
689 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
690 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +0100691
692 //When popping MPLS we remove label and BOS
693 TrafficSelector temporaryPacket = newSelector.build();
694 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100695 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +0100696 temporaryPacket.criteria().stream().filter(c -> {
697 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
698 !c.type().equals(Criterion.Type.MPLS_BOS);
699 }).forEach(noMplsSelector::add);
700 newSelector = noMplsSelector;
701 }
702
Andrea Campanellae4084402017-12-15 15:27:31 +0100703 break;
704 case MPLS_LABEL:
705 L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
706 (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
707 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +0100708 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +0100709 break;
710 case ETH_DST:
711 L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
712 (L2ModificationInstruction.ModEtherInstruction) instruction;
713 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
714 break;
715 case ETH_SRC:
716 L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
717 (L2ModificationInstruction.ModEtherInstruction) instruction;
718 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
719 break;
720 default:
721 log.debug("Unsupported L2 Instruction");
722 break;
723 }
724 break;
725 default:
726 log.debug("Unsupported Instruction");
727 break;
728 }
729 if (criterion != null) {
730 log.debug("Adding criterion {}", criterion);
731 newSelector.add(criterion);
732 }
733 return newSelector;
734 }
735
736 /**
737 * Finds the rule in the device that mathces the input packet and has the highest priority.
738 *
739 * @param packet the input packet
740 * @param in the connect point the packet comes in from
741 * @param tableId the table to search
742 * @return the flow entry
743 */
744 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
745 //Computing the possible match rules.
746 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
747 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
748 .stream()
749 .filter(flowEntry -> {
750 return flowEntry.table().equals(tableId);
751 })
752 .filter(flowEntry -> {
753 return match(packet, flowEntry);
754 }).max(comparator).orElse(null);
755 }
756
757 /**
758 * Matches the packet with the given flow entry.
759 *
760 * @param packet the packet to match
761 * @param flowEntry the flow entry to match the packet against
762 * @return true if the packet matches the flow.
763 */
764 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
765 //TODO handle MAC matching
766 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
767 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +0100768 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +0100769 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
770 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
771 IPCriterion ipCriterion = (IPCriterion) criterion;
772 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
Andrea Campanella128d9c62018-01-31 12:20:48 +0100773 //if the packet does not have an IPv4 or IPv6 criterion we return false
Andrea Campanellae4084402017-12-15 15:27:31 +0100774 if (matchCriterion == null) {
Andrea Campanella128d9c62018-01-31 12:20:48 +0100775 return false;
Andrea Campanellae4084402017-12-15 15:27:31 +0100776 }
777 try {
778 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
779 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
780 } catch (UnknownHostException e) {
781 return false;
782 }
783 //we check that the packet contains the criterion provided by the flow rule.
784 } else {
785 return packet.criteria().contains(criterion);
786 }
787 });
Simon Hunt026a2872017-11-13 17:09:43 -0800788 }
789}