blob: bbec3ea52ceeb96e06c5389eabeb5a9fb06454cb [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;
Andrea Campanella94c594a2018-02-06 18:58:40 +010076import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
77import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
78import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
79import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Simon Hunt026a2872017-11-13 17:09:43 -080080import static org.slf4j.LoggerFactory.getLogger;
81
82/**
Andrea Campanellae4084402017-12-15 15:27:31 +010083 * Manager to troubleshoot packets inside the network.
84 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
85 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080086 */
87@Service
88@Component(immediate = true)
89public class TroubleshootManager implements TroubleshootService {
90
91 private static final Logger log = getLogger(TroubleshootManager.class);
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected FlowRuleService flowRuleService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected GroupService groupService;
98
Andrea Campanellae4084402017-12-15 15:27:31 +010099 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -0800101
Andrea Campanellae4084402017-12-15 15:27:31 +0100102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 protected HostService hostService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -0800107
Andrea Campanella17d45192018-01-18 17:11:42 +0100108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected DeviceService deviceService;
110
Andrea Campanella54923d62018-01-23 12:46:04 +0100111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 protected MastershipService mastershipService;
113
Simon Hunt026a2872017-11-13 17:09:43 -0800114 @Override
115 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100116 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100117 //device must exist in ONOS
118 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
119 "Device " + in.deviceId() + " must exist in ONOS");
120
Andrea Campanellae4084402017-12-15 15:27:31 +0100121 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
122 //FIXME this can be done recursively
123 trace = traceInDevice(trace, packet, in);
124 //Building output connect Points
125 List<ConnectPoint> path = new ArrayList<>();
126 trace = getTrace(path, in, trace);
127 return trace;
128 }
129
130 /**
131 * Computes a trace for a give packet that start in the network at the given connect point.
132 *
133 * @param completePath the path traversed by the packet
134 * @param in the input connect point
135 * @param trace the trace to build
136 * @return the build trace for that packet.
137 */
138 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
139
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100140 log.debug("------------------------------------------------------------");
141
Andrea Campanellae4084402017-12-15 15:27:31 +0100142 //if the trace already contains the input connect point there is a loop
143 if (pathContainsDevice(completePath, in.deviceId())) {
144 trace.addResultMessage("Loop encountered in device " + in.deviceId());
145 return trace;
146 }
147
148 //let's add the input connect point
149 completePath.add(in);
150
151 //If the trace has no outputs for the given input we stop here
152 if (trace.getGroupOuputs(in.deviceId()) == null) {
153 computePath(completePath, trace, null);
154 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
155 return trace;
156 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100157
Andrea Campanellae4084402017-12-15 15:27:31 +0100158 //If the trace has ouputs we analyze them all
159 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100160
161 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100162 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100163 log.debug("Output path {}", cp);
164
Andrea Campanellae4084402017-12-15 15:27:31 +0100165 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100166 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100167 //Hosts queried from the original ip or mac
168 Set<Host> hosts = getHosts(trace);
169
170 //If the two host collections contain the same item it means we reached the proper output
171 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100172 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella54923d62018-01-23 12:46:04 +0100173 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100174 computePath(completePath, trace, outputPath.getOutput());
175 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100176 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100177
Andrea Campanella54923d62018-01-23 12:46:04 +0100178 //Getting the master when the packet gets sent as packet in
179 NodeId master = mastershipService.getMasterFor(cp.deviceId());
180 trace.addResultMessage("Packet goes to the controller " + master.id());
181 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100182 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100183
Andrea Campanella8292ba62018-01-31 16:43:23 +0100184 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100185
186 //TODO this can be optimized if we use a Tree structure for paths.
187 //if we already have outputs let's check if the one we are considering starts from one of the devices
188 // in any of the ones we have.
189 if (trace.getCompletePaths().size() > 0) {
190 ConnectPoint inputForOutput = null;
191 List<ConnectPoint> previousPath = new ArrayList<>();
192 for (List<ConnectPoint> path : trace.getCompletePaths()) {
193 for (ConnectPoint connect : path) {
194 //if the path already contains the input for the output we've found we use it
195 if (connect.equals(in)) {
196 inputForOutput = connect;
197 previousPath = path;
198 break;
199 }
200 }
201 }
202 //we use the pre-existing path up to the point we fork to a new output
203 if (inputForOutput != null && completePath.contains(inputForOutput)) {
204 List<ConnectPoint> temp = new ArrayList<>(previousPath);
205 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
206 }
207 }
208
Andrea Campanellae4084402017-12-15 15:27:31 +0100209 //let's add the ouput for the input
210 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100211 //let's compute the links for the given output
212 Set<Link> links = linkService.getEgressLinks(cp);
213 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100214 //For each link we trace the corresponding device
215 for (Link link : links) {
216 ConnectPoint dst = link.dst();
217 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100218 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100219 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
220 updatedPacket.add(Criteria.matchInPort(dst.port()));
221 log.debug("DST Connect Point {}", dst);
222 //build the elements for that device
223 traceInDevice(trace, updatedPacket.build(), dst);
224 //continue the trace along the path
225 getTrace(completePath, dst, trace);
226 }
227
Andrea Campanella8292ba62018-01-31 16:43:23 +0100228 } else if (deviceService.getPort(cp).isEnabled()) {
229 if (hostsList.isEmpty()) {
230 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
231 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
232 cp + " with no hosts connected ");
233 } else {
234 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
235 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
236 cp + " with hosts " + hostsList);
237 }
238 computePath(completePath, trace, outputPath.getOutput());
239
240 } else {
241 //No links means that the packet gets dropped.
242 log.warn("No links out of {}", cp);
243 computePath(completePath, trace, cp);
244 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanellae4084402017-12-15 15:27:31 +0100245 }
246 }
247 return trace;
248 }
249
250 /**
Andrea Campanellae6798012018-02-06 15:46:52 +0100251 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
252 * If ONOS applied a vlan we remove it.
253 *
254 * @param outputPath the output
255 * @param trace the trace we are building
256 */
257 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
258
259 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
260 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
261
262 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
263
264 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
265 //removing the final vlanId
266 finalCriteria.remove(finalVid);
267 Builder packetUpdated = DefaultTrafficSelector.builder();
268 finalCriteria.forEach(packetUpdated::add);
269 //Initial was none so we set it to that
270 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
271 //Update final packet
272 outputPath.setFinalPacket(packetUpdated.build());
273 }
274 }
275
276 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100277 * Checks if the path contains the device.
278 *
279 * @param completePath the path
280 * @param deviceId the device to check
281 * @return true if the path contains the device
282 */
283 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
284 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
285 for (ConnectPoint cp : completePath) {
286 if (cp.deviceId().equals(deviceId)) {
287 return true;
288 }
289 }
290 return false;
291 }
292
293 /**
294 * Gets the hosts for the given initial packet.
295 *
296 * @param trace the trace we are building
297 * @return set of the hosts we are trying to reach
298 */
299 private Set<Host> getHosts(StaticPacketTrace trace) {
300 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
301 .getCriterion(Criterion.Type.IPV4_DST));
302 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
303 .getCriterion(Criterion.Type.IPV6_DST));
304 Set<Host> hosts = new HashSet<>();
305 if (ipv4Criterion != null) {
306 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
307 }
308 if (ipv6Criterion != null) {
309 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
310 }
311 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
312 .getCriterion(Criterion.Type.ETH_DST));
313 if (ethCriterion != null) {
314 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
315 }
316 return hosts;
317 }
318
319 /**
320 * Computes the list of traversed connect points.
321 *
322 * @param completePath the list of devices
323 * @param trace the trace we are building
324 * @param output the final output connect point
325 */
326 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
327 List<ConnectPoint> traverseList = new ArrayList<>();
328 if (!completePath.contains(trace.getInitialConnectPoint())) {
329 traverseList.add(trace.getInitialConnectPoint());
330 }
331 traverseList.addAll(completePath);
332 if (output != null && !completePath.contains(output)) {
333 traverseList.add(output);
334 }
335 trace.addCompletePath(traverseList);
Andrea Campanellae4084402017-12-15 15:27:31 +0100336 }
337
338 /**
339 * Traces the packet inside a device starting from an input connect point.
340 *
341 * @param trace the trace we are building
342 * @param packet the packet we are tracing
343 * @param in the input connect point.
344 * @return updated trace
345 */
346 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100347
348 //we already traversed this device.
349 if (trace.getGroupOuputs(in.deviceId()) != null) {
350 log.debug("Trace already contains device and given outputs");
351 return trace;
352 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100353 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100354
355 //if device is not available exit here.
356 if (!deviceService.isAvailable(in.deviceId())) {
357 trace.addResultMessage("Device is offline " + in.deviceId());
358 return trace;
359 }
360
Andrea Campanellae4084402017-12-15 15:27:31 +0100361 List<FlowEntry> flows = new ArrayList<>();
362 List<FlowEntry> outputFlows = new ArrayList<>();
363
Andrea Campanella8292ba62018-01-31 16:43:23 +0100364 List<Instruction> deferredInstructions = new ArrayList<>();
365
Andrea Campanellae4084402017-12-15 15:27:31 +0100366 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
367 if (nextTableIdEntry == null) {
368 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
369 return trace;
370 }
371 TableId tableId = nextTableIdEntry.table();
372 FlowEntry flowEntry;
373 boolean output = false;
374 while (!output) {
375 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
376 //get the rule that matches the incoming packet
377 flowEntry = matchHighestPriority(packet, in, tableId);
378 log.debug("Found Flow Entry {}", flowEntry);
379
380 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
381 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
382
383 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
384 if (flowEntry == null && isOfdpaHardware) {
385 log.debug("Ofdpa Hw setup, no flow rule means table miss");
386
Andrea Campanellae4084402017-12-15 15:27:31 +0100387 if (((IndexTableId) tableId).id() == 27) {
388 //Apparently a miss but Table 27 on OFDPA is a fixed table
389 packet = handleOfdpa27FixedTable(trace, packet);
390 }
391
392 //Finding next table to go In case of miss
393 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
394 log.debug("Next table id entry {}", nextTableIdEntry);
395
396 //FIXME find better solution that enable granularity greater than 0 or all rules
397 //(another possibility is max tableId)
398 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100399 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellae4084402017-12-15 15:27:31 +0100400 return trace;
401
402 } else if (nextTableIdEntry == null) {
403 //Means that no more flow rules are present
404 output = true;
405
406 } else if (((IndexTableId) tableId).id() == 20) {
407 //if the table is 20 OFDPA skips to table 50
408 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
409 tableId = IndexTableId.of(50);
410
411 } else {
412 tableId = nextTableIdEntry.table();
413 }
414
Andrea Campanellae4084402017-12-15 15:27:31 +0100415 } else if (flowEntry == null) {
416 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
417 in.deviceId() + ". Dropping");
418 return trace;
419 } else {
Andrea Campanella94c594a2018-02-06 18:58:40 +0100420
Andrea Campanellae4084402017-12-15 15:27:31 +0100421 //IF the table has a transition
422 if (flowEntry.treatment().tableTransition() != null) {
423 //update the next table we transitions to
424 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
425 log.debug("Flow Entry has transition to table Id {}", tableId);
426 flows.add(flowEntry);
427 } else {
428 //table has no transition so it means that it's an output rule if on the last table
429 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
430 flows.add(flowEntry);
431 outputFlows.add(flowEntry);
432 output = true;
433 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100434 //update the packet according to the immediate actions of this flow rule.
435 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
436
437 //save the deferred rules for later
438 deferredInstructions.addAll(flowEntry.treatment().deferred());
439
440 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
441 if (flowEntry.treatment().clearedDeferred()) {
442 deferredInstructions.clear();
443 }
444
Andrea Campanella94c594a2018-02-06 18:58:40 +0100445 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
446 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
447
448 //Let's get the packet vlanId instruction
449 VlanIdCriterion packetVlanIdCriterion =
450 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
451
452 //Let's get the flow entry vlan mod instructions
453 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
454 .immediate().stream()
455 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
456 .findFirst().orElse(null);
457
458 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
459 // is a flow rule that matches on same criteria and with updated vlanId
460 if (entryModVlanIdInstruction != null) {
461
462 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
463 packetVlanIdCriterion, entryModVlanIdInstruction);
464
465 //We found the flow that we expected
466 if (secondVlanFlow != null) {
467 flows.add(secondVlanFlow);
468 } else {
469 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
470 return trace;
471 }
472 }
473
474 }
475
Andrea Campanellae4084402017-12-15 15:27:31 +0100476 }
477 }
478
479 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100480 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100481 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100482
Andrea Campanellae4084402017-12-15 15:27:31 +0100483 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100484 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100485
Andrea Campanellae4084402017-12-15 15:27:31 +0100486 List<PortNumber> outputPorts = new ArrayList<>();
487
Andrea Campanella54923d62018-01-23 12:46:04 +0100488 //TODO optimization
Andrea Campanella54923d62018-01-23 12:46:04 +0100489 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
490 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
491 .allInstructions().stream().filter(instruction -> instruction.type()
492 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100493
Andrea Campanella54923d62018-01-23 12:46:04 +0100494 if (outputFlowEntries.size() > 1) {
495 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
496 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100497 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100498
499 if (outputFlowEntries.size() == 1) {
500
501 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
502 .allInstructions().stream()
503 .filter(instruction -> {
504 return instruction.type().equals(Instruction.Type.OUTPUT);
505 }).findFirst().get();
506
507 //FIXME using GroupsInDevice for output even if flows.
508 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
509
510 }
511 log.debug("Handling Groups");
512 //Analyze Groups
513 List<Group> groups = new ArrayList<>();
514
515 Collection<FlowEntry> nonOutputFlows = flows;
516 nonOutputFlows.removeAll(outputFlowEntries);
517
Andrea Campanella8292ba62018-01-31 16:43:23 +0100518 //Handling groups pointed at by immediate instructions
Andrea Campanella54923d62018-01-23 12:46:04 +0100519 for (FlowEntry entry : flows) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100520 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanella54923d62018-01-23 12:46:04 +0100521 entry.deviceId(), builder, outputPorts, in);
522 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100523
524 //If we have deferred instructions at this point we handle them.
525 if (deferredInstructions.size() > 0) {
526 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
527
528 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100529 packet = builder.build();
Andrea Campanella54923d62018-01-23 12:46:04 +0100530
Andrea Campanella94c594a2018-02-06 18:58:40 +0100531 log.debug("Output Packet {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100532 return trace;
533 }
534
Andrea Campanella94c594a2018-02-06 18:58:40 +0100535 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
536 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
537 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
538 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
539 .vlanId().equals(VlanId.NONE);
540 }
541
542 /**
543 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
544 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
545 * second to transition to following table
546 *
547 * @param packet the incoming packet
548 * @param in the input connect point
549 * @param packetVlanIdCriterion the vlan criterion from the packet
550 * @param entryModVlanIdInstruction the entry vlan instruction
551 * @return the second flow entry that matched
552 */
553 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
554 VlanIdCriterion packetVlanIdCriterion,
555 ModVlanIdInstruction entryModVlanIdInstruction) {
556 FlowEntry secondVlanFlow = null;
557 //Check the packet has been update from the first rule.
558 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
559 //find a rule on the same table that matches the vlan and
560 // also all the other elements of the flow such as input port
561 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
562 .stream()
563 .filter(entry -> {
564 return entry.table().equals(IndexTableId.of(10));
565 })
566 .filter(entry -> {
567 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
568 .getCriterion(Criterion.Type.VLAN_VID);
569 return criterion != null && match(packet, entry)
570 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
571 }).findFirst().orElse(null);
572
573 }
574 return secondVlanFlow;
575 }
576
Andrea Campanella8292ba62018-01-31 16:43:23 +0100577
Andrea Campanellae4084402017-12-15 15:27:31 +0100578 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100579 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
580 *
581 * @param packet the incoming packet
582 * @return the updated packet
583 */
584 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
585 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
586 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
587 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
588
589 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
590 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
591 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100592 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100593 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
594 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
595 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100596 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100597 builder.add(ethInstruction);
598 }
599 packet = updatePacket(packet, builder.build()).build();
600 return packet;
601 }
602
603 /**
604 * Finds the flow entry with the minimun next table Id.
605 *
606 * @param deviceId the device to search
607 * @param currentId the current id. the search will use this as minimum
608 * @return the flow entry with the minimum table Id after the given one.
609 */
610 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
611
612 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
613
614 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
615 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
616 }
617
Andrea Campanella8292ba62018-01-31 16:43:23 +0100618 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
619 ConnectPoint in, List<Instruction> deferredInstructions,
620 List<PortNumber> outputPorts, List<Group> groups) {
621
622 //Update the packet with the deferred instructions
623 Builder builder = updatePacket(packet, deferredInstructions);
624
625 //Gather any output instructions from the deferred instruction
626 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
627 return instruction.type().equals(Instruction.Type.OUTPUT);
628 }).collect(Collectors.toList());
629
630 //We are considering deferred instructions from flows, there can only be one output.
631 if (outputFlowInstruction.size() > 1) {
632 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
633 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
634 }
635 //If there is one output let's go through that
636 if (outputFlowInstruction.size() == 1) {
637 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
638 ImmutableList.of());
639 }
640 //If there is no output let's see if there any deferred instruction point to groups.
641 if (outputFlowInstruction.size() == 0) {
642 getGroupsFromInstructions(trace, groups, deferredInstructions,
643 in.deviceId(), builder, outputPorts, in);
644 }
645 return builder;
646 }
647
Andrea Campanellae4084402017-12-15 15:27:31 +0100648 /**
649 * Gets group information from instructions.
650 *
651 * @param trace the trace we are building
652 * @param groupsForDevice the set of groups for this device
653 * @param instructions the set of instructions we are searching for groups.
654 * @param deviceId the device we are considering
655 * @param builder the builder of the input packet
656 * @param outputPorts the output ports for that packet
657 */
658 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
659 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +0100660 Builder builder, List<PortNumber> outputPorts,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100661 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100662 List<Instruction> groupInstructionlist = new ArrayList<>();
663 for (Instruction instruction : instructions) {
664 log.debug("Considering Instruction {}", instruction);
665 //if the instruction is not group we need to update the packet or add the output
666 //to the possible outputs for this packet
667 if (!instruction.type().equals(Instruction.Type.GROUP)) {
668 //if the instruction is not group we need to update the packet or add the output
669 //to the possible outputs for this packet
670 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100671 buildOutputFromDevice(trace, in, builder, outputPorts,
672 (OutputInstruction) instruction, groupsForDevice);
Andrea Campanellae4084402017-12-15 15:27:31 +0100673 } else {
674 builder = translateInstruction(builder, instruction);
675 }
676 } else {
677 //if the instuction is pointing to a group we need to get the group
678 groupInstructionlist.add(instruction);
679 }
680 }
681 //handle all the internal instructions pointing to a group.
682 for (Instruction instr : groupInstructionlist) {
683 GroupInstruction groupInstruction = (GroupInstruction) instr;
684 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
685 return groupInternal.id().equals(groupInstruction.groupId());
686 }).findAny().orElse(null);
687 if (group == null) {
688 trace.addResultMessage("Null group for Instruction " + instr);
689 break;
690 }
691 //add the group to the traversed groups
692 groupsForDevice.add(group);
693 //Cycle in each of the group's buckets and add them to the groups for this Device.
694 for (GroupBucket bucket : group.buckets().buckets()) {
695 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100696 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100697 }
698 }
699 }
700
701 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100702 * Check if the output is the input port, if so adds a dop result message, otherwise builds
703 * a possible output from this device.
704 *
705 * @param trace the trace
706 * @param in the input connect point
707 * @param builder the packet builder
708 * @param outputPorts the list of output ports for this device
709 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +0100710 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100711 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100712 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100713 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
714 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100715 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100716 if (output.equals(in)) {
717 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
718 trace.getInitialConnectPoint());
719 } else {
720 trace.addGroupOutputPath(in.deviceId(),
721 new GroupsInDevice(output, groupsForDevice, builder.build()));
722 outputPorts.add(outputInstruction.port());
723 }
724 }
725
726 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100727 * Applies all give instructions to the input packet.
728 *
729 * @param packet the input packet
730 * @param instructions the set of instructions
731 * @return the packet with the applied instructions
732 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100733 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
734 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100735 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +0100736 //FIXME optimize
737 for (Instruction instruction : instructions) {
738 newSelector = translateInstruction(newSelector, instruction);
739 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100740 return newSelector;
741 }
742
743 /**
744 * Applies an instruction to the packet in the form of a selector.
745 *
746 * @param newSelector the packet selector
747 * @param instruction the instruction to be translated
748 * @return the new selector with the applied instruction
749 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100750 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100751 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +0100752 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +0100753 //TODO add as required
754 Criterion criterion = null;
755 switch (instruction.type()) {
756 case L2MODIFICATION:
757 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
758 switch (l2Instruction.subtype()) {
759 case VLAN_ID:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100760 ModVlanIdInstruction vlanIdInstruction =
761 (ModVlanIdInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100762 VlanId id = vlanIdInstruction.vlanId();
763 criterion = Criteria.matchVlanId(id);
764 break;
765 case VLAN_POP:
766 criterion = Criteria.matchVlanId(VlanId.NONE);
767 break;
768 case MPLS_PUSH:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100769 ModMplsHeaderInstruction mplsEthInstruction =
770 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100771 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
772 break;
773 case MPLS_POP:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100774 ModMplsHeaderInstruction mplsPopInstruction =
775 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100776 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +0100777
778 //When popping MPLS we remove label and BOS
779 TrafficSelector temporaryPacket = newSelector.build();
780 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100781 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +0100782 temporaryPacket.criteria().stream().filter(c -> {
783 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
784 !c.type().equals(Criterion.Type.MPLS_BOS);
785 }).forEach(noMplsSelector::add);
786 newSelector = noMplsSelector;
787 }
788
Andrea Campanellae4084402017-12-15 15:27:31 +0100789 break;
790 case MPLS_LABEL:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100791 ModMplsLabelInstruction mplsLabelInstruction =
792 (ModMplsLabelInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100793 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +0100794 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +0100795 break;
796 case ETH_DST:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100797 ModEtherInstruction modEtherDstInstruction =
798 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100799 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
800 break;
801 case ETH_SRC:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100802 ModEtherInstruction modEtherSrcInstruction =
803 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100804 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
805 break;
806 default:
807 log.debug("Unsupported L2 Instruction");
808 break;
809 }
810 break;
811 default:
812 log.debug("Unsupported Instruction");
813 break;
814 }
815 if (criterion != null) {
816 log.debug("Adding criterion {}", criterion);
817 newSelector.add(criterion);
818 }
819 return newSelector;
820 }
821
822 /**
823 * Finds the rule in the device that mathces the input packet and has the highest priority.
824 *
825 * @param packet the input packet
826 * @param in the connect point the packet comes in from
827 * @param tableId the table to search
828 * @return the flow entry
829 */
830 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
831 //Computing the possible match rules.
832 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
833 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
834 .stream()
835 .filter(flowEntry -> {
836 return flowEntry.table().equals(tableId);
837 })
838 .filter(flowEntry -> {
839 return match(packet, flowEntry);
840 }).max(comparator).orElse(null);
841 }
842
843 /**
844 * Matches the packet with the given flow entry.
845 *
846 * @param packet the packet to match
847 * @param flowEntry the flow entry to match the packet against
848 * @return true if the packet matches the flow.
849 */
850 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
851 //TODO handle MAC matching
852 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
853 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +0100854 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +0100855 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
856 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
857 IPCriterion ipCriterion = (IPCriterion) criterion;
858 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
Andrea Campanella128d9c62018-01-31 12:20:48 +0100859 //if the packet does not have an IPv4 or IPv6 criterion we return false
Andrea Campanellae4084402017-12-15 15:27:31 +0100860 if (matchCriterion == null) {
Andrea Campanella128d9c62018-01-31 12:20:48 +0100861 return false;
Andrea Campanellae4084402017-12-15 15:27:31 +0100862 }
863 try {
864 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
865 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
866 } catch (UnknownHostException e) {
867 return false;
868 }
869 //we check that the packet contains the criterion provided by the flow rule.
870 } else {
871 return packet.criteria().contains(criterion);
872 }
873 });
Simon Hunt026a2872017-11-13 17:09:43 -0800874 }
875}