blob: 8f03ddc19c06dc1c2b94d5ce3c8ffe32f66955fe [file] [log] [blame]
Simon Hunt6fefd852017-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 Campanella37d10622018-01-18 17:11:42 +010019import com.google.common.base.Preconditions;
Andrea Campanella01e886e2017-12-15 15:27:31 +010020import com.google.common.collect.ImmutableList;
21import com.google.common.collect.Lists;
Simon Hunt6fefd852017-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 Campanella01e886e2017-12-15 15:27:31 +010026import org.onlab.packet.VlanId;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010027import org.onosproject.cluster.NodeId;
28import org.onosproject.mastership.MastershipService;
Simon Hunt6fefd852017-11-13 17:09:43 -080029import org.onosproject.net.ConnectPoint;
Andrea Campanella01e886e2017-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 Campanella37d10622018-01-18 17:11:42 +010034import org.onosproject.net.device.DeviceService;
Andrea Campanella01e886e2017-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 Hunt6fefd852017-11-13 17:09:43 -080039import org.onosproject.net.flow.FlowRuleService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010040import org.onosproject.net.flow.IndexTableId;
41import org.onosproject.net.flow.TableId;
Simon Hunt6fefd852017-11-13 17:09:43 -080042import org.onosproject.net.flow.TrafficSelector;
Andrea Campanella01e886e2017-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 Hunt6fefd852017-11-13 17:09:43 -080054import org.onosproject.net.group.GroupService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010055import org.onosproject.net.host.HostService;
56import org.onosproject.net.link.LinkService;
57import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt6fefd852017-11-13 17:09:43 -080058import org.onosproject.t3.api.StaticPacketTrace;
59import org.onosproject.t3.api.TroubleshootService;
60import org.slf4j.Logger;
61
Andrea Campanella01e886e2017-12-15 15:27:31 +010062import java.net.UnknownHostException;
63import java.util.ArrayList;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010064import java.util.Collection;
Andrea Campanella01e886e2017-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;
73import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Simon Hunt6fefd852017-11-13 17:09:43 -080074import static org.slf4j.LoggerFactory.getLogger;
75
76/**
Andrea Campanella01e886e2017-12-15 15:27:31 +010077 * Manager to troubleshoot packets inside the network.
78 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
79 * the devices.
Simon Hunt6fefd852017-11-13 17:09:43 -080080 */
81@Service
82@Component(immediate = true)
83public class TroubleshootManager implements TroubleshootService {
84
85 private static final Logger log = getLogger(TroubleshootManager.class);
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected FlowRuleService flowRuleService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected GroupService groupService;
92
Andrea Campanella01e886e2017-12-15 15:27:31 +010093 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected LinkService linkService;
Simon Hunt6fefd852017-11-13 17:09:43 -080095
Andrea Campanella01e886e2017-12-15 15:27:31 +010096 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected HostService hostService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected DriverService driverService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800101
Andrea Campanella37d10622018-01-18 17:11:42 +0100102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 protected DeviceService deviceService;
104
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected MastershipService mastershipService;
107
Simon Hunt6fefd852017-11-13 17:09:43 -0800108 @Override
109 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100110 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100111 //device must exist in ONOS
112 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
113 "Device " + in.deviceId() + " must exist in ONOS");
114
Andrea Campanella01e886e2017-12-15 15:27:31 +0100115 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
116 //FIXME this can be done recursively
117 trace = traceInDevice(trace, packet, in);
118 //Building output connect Points
119 List<ConnectPoint> path = new ArrayList<>();
120 trace = getTrace(path, in, trace);
121 return trace;
122 }
123
124 /**
125 * Computes a trace for a give packet that start in the network at the given connect point.
126 *
127 * @param completePath the path traversed by the packet
128 * @param in the input connect point
129 * @param trace the trace to build
130 * @return the build trace for that packet.
131 */
132 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
133
Andrea Campanellae04aac92018-01-31 14:59:03 +0100134 log.debug("------------------------------------------------------------");
135
Andrea Campanella01e886e2017-12-15 15:27:31 +0100136 //if the trace already contains the input connect point there is a loop
137 if (pathContainsDevice(completePath, in.deviceId())) {
138 trace.addResultMessage("Loop encountered in device " + in.deviceId());
139 return trace;
140 }
141
142 //let's add the input connect point
143 completePath.add(in);
144
145 //If the trace has no outputs for the given input we stop here
146 if (trace.getGroupOuputs(in.deviceId()) == null) {
147 computePath(completePath, trace, null);
148 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
149 return trace;
150 }
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100151
Andrea Campanella01e886e2017-12-15 15:27:31 +0100152 //If the trace has ouputs we analyze them all
153 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100154
155 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellae04aac92018-01-31 14:59:03 +0100156 log.debug("Connect point in {}", in);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100157 log.debug("Output path {}", cp);
158
Andrea Campanella01e886e2017-12-15 15:27:31 +0100159 //Hosts for the the given output
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100160 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100161 //Hosts queried from the original ip or mac
162 Set<Host> hosts = getHosts(trace);
163
164 //If the two host collections contain the same item it means we reached the proper output
165 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100166 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100167 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100168 computePath(completePath, trace, outputPath.getOutput());
169 break;
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100170 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
171 //Getting the master when the packet gets sent as packet in
172 NodeId master = mastershipService.getMasterFor(cp.deviceId());
173 trace.addResultMessage("Packet goes to the controller " + master.id());
174 computePath(completePath, trace, outputPath.getOutput());
175
176 } else if (outputPath.getFinalPacket().getCriterion(Criterion.Type.ETH_TYPE) != null &&
177 ((EthTypeCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.ETH_TYPE)).ethType()
178 .equals(EtherType.ARP.ethType()) && deviceService.getPort(cp).isEnabled() &&
179 linkService.getEgressLinks(cp).isEmpty()) {
180 if (hostsList.isEmpty()) {
181 trace.addResultMessage("Packet is ARP and reached " + cp + " with no hosts connected ");
182 } else {
183 trace.addResultMessage("Packet is ARP and reached " + cp + " with hosts " + hostsList);
184 }
185 computePath(completePath, trace, outputPath.getOutput());
186
Andrea Campanella01e886e2017-12-15 15:27:31 +0100187 } else {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100188
189 //TODO this can be optimized if we use a Tree structure for paths.
190 //if we already have outputs let's check if the one we are considering starts from one of the devices
191 // in any of the ones we have.
192 if (trace.getCompletePaths().size() > 0) {
193 ConnectPoint inputForOutput = null;
194 List<ConnectPoint> previousPath = new ArrayList<>();
195 for (List<ConnectPoint> path : trace.getCompletePaths()) {
196 for (ConnectPoint connect : path) {
197 //if the path already contains the input for the output we've found we use it
198 if (connect.equals(in)) {
199 inputForOutput = connect;
200 previousPath = path;
201 break;
202 }
203 }
204 }
205 //we use the pre-existing path up to the point we fork to a new output
206 if (inputForOutput != null && completePath.contains(inputForOutput)) {
207 List<ConnectPoint> temp = new ArrayList<>(previousPath);
208 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
209 }
210 }
211
Andrea Campanella01e886e2017-12-15 15:27:31 +0100212 //let's add the ouput for the input
213 completePath.add(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100214 //let's compute the links for the given output
215 Set<Link> links = linkService.getEgressLinks(cp);
216 log.debug("Egress Links {}", links);
217 //No links means that the packet gets dropped.
218 if (links.size() == 0) {
219 log.warn("No links out of {}", cp);
220 computePath(completePath, trace, cp);
221 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanella01e886e2017-12-15 15:27:31 +0100222 }
223 //For each link we trace the corresponding device
224 for (Link link : links) {
225 ConnectPoint dst = link.dst();
226 //change in-port to the dst link in port
227 TrafficSelector.Builder updatedPacket = DefaultTrafficSelector.builder();
228 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
229 updatedPacket.add(Criteria.matchInPort(dst.port()));
230 log.debug("DST Connect Point {}", dst);
231 //build the elements for that device
232 traceInDevice(trace, updatedPacket.build(), dst);
233 //continue the trace along the path
234 getTrace(completePath, dst, trace);
235 }
236
237 }
238 }
239 return trace;
240 }
241
242 /**
243 * Checks if the path contains the device.
244 *
245 * @param completePath the path
246 * @param deviceId the device to check
247 * @return true if the path contains the device
248 */
249 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
250 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
251 for (ConnectPoint cp : completePath) {
252 if (cp.deviceId().equals(deviceId)) {
253 return true;
254 }
255 }
256 return false;
257 }
258
259 /**
260 * Gets the hosts for the given initial packet.
261 *
262 * @param trace the trace we are building
263 * @return set of the hosts we are trying to reach
264 */
265 private Set<Host> getHosts(StaticPacketTrace trace) {
266 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
267 .getCriterion(Criterion.Type.IPV4_DST));
268 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
269 .getCriterion(Criterion.Type.IPV6_DST));
270 Set<Host> hosts = new HashSet<>();
271 if (ipv4Criterion != null) {
272 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
273 }
274 if (ipv6Criterion != null) {
275 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
276 }
277 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
278 .getCriterion(Criterion.Type.ETH_DST));
279 if (ethCriterion != null) {
280 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
281 }
282 return hosts;
283 }
284
285 /**
286 * Computes the list of traversed connect points.
287 *
288 * @param completePath the list of devices
289 * @param trace the trace we are building
290 * @param output the final output connect point
291 */
292 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
293 List<ConnectPoint> traverseList = new ArrayList<>();
294 if (!completePath.contains(trace.getInitialConnectPoint())) {
295 traverseList.add(trace.getInitialConnectPoint());
296 }
297 traverseList.addAll(completePath);
298 if (output != null && !completePath.contains(output)) {
299 traverseList.add(output);
300 }
301 trace.addCompletePath(traverseList);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100302 }
303
304 /**
305 * Traces the packet inside a device starting from an input connect point.
306 *
307 * @param trace the trace we are building
308 * @param packet the packet we are tracing
309 * @param in the input connect point.
310 * @return updated trace
311 */
312 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100313
314 //we already traversed this device.
315 if (trace.getGroupOuputs(in.deviceId()) != null) {
316 log.debug("Trace already contains device and given outputs");
317 return trace;
318 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100319 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100320
321 //if device is not available exit here.
322 if (!deviceService.isAvailable(in.deviceId())) {
323 trace.addResultMessage("Device is offline " + in.deviceId());
324 return trace;
325 }
326
Andrea Campanella01e886e2017-12-15 15:27:31 +0100327 List<FlowEntry> flows = new ArrayList<>();
328 List<FlowEntry> outputFlows = new ArrayList<>();
329
330 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
331 if (nextTableIdEntry == null) {
332 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
333 return trace;
334 }
335 TableId tableId = nextTableIdEntry.table();
336 FlowEntry flowEntry;
337 boolean output = false;
338 while (!output) {
339 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
340 //get the rule that matches the incoming packet
341 flowEntry = matchHighestPriority(packet, in, tableId);
342 log.debug("Found Flow Entry {}", flowEntry);
343
344 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
345 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
346
347 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
348 if (flowEntry == null && isOfdpaHardware) {
349 log.debug("Ofdpa Hw setup, no flow rule means table miss");
350
351 //Handling Hardware Specifics
352 if (((IndexTableId) tableId).id() == 27) {
353 //Apparently a miss but Table 27 on OFDPA is a fixed table
354 packet = handleOfdpa27FixedTable(trace, packet);
355 }
356
357 //Finding next table to go In case of miss
358 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
359 log.debug("Next table id entry {}", nextTableIdEntry);
360
361 //FIXME find better solution that enable granularity greater than 0 or all rules
362 //(another possibility is max tableId)
363 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella7382c7f2018-02-05 19:39:25 +0100364 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanella01e886e2017-12-15 15:27:31 +0100365 return trace;
366
367 } else if (nextTableIdEntry == null) {
368 //Means that no more flow rules are present
369 output = true;
370
371 } else if (((IndexTableId) tableId).id() == 20) {
372 //if the table is 20 OFDPA skips to table 50
373 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
374 tableId = IndexTableId.of(50);
375
376 } else {
377 tableId = nextTableIdEntry.table();
378 }
379
380
381 } else if (flowEntry == null) {
382 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
383 in.deviceId() + ". Dropping");
384 return trace;
385 } else {
386 //IF the table has a transition
387 if (flowEntry.treatment().tableTransition() != null) {
388 //update the next table we transitions to
389 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
390 log.debug("Flow Entry has transition to table Id {}", tableId);
391 flows.add(flowEntry);
392 } else {
393 //table has no transition so it means that it's an output rule if on the last table
394 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
395 flows.add(flowEntry);
396 outputFlows.add(flowEntry);
397 output = true;
398 }
399 //update the packet according to the actions of this flow rule.
400 packet = updatePacket(packet, flowEntry.treatment().allInstructions()).build();
401 }
402 }
403
404 //Creating a modifiable builder for the output packet
405 TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
406 packet.criteria().forEach(builder::add);
407 //Adding all the flows to the trace
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100408 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100409
410 log.debug("Flows traversed by {}", packet);
411 flows.forEach(entry -> {
412 log.debug("Flow {}", entry);
413 });
414
415 log.debug("Output Flows for {}", packet);
416 outputFlows.forEach(entry -> {
417 log.debug("Output Flow {}", entry);
418 });
419 List<PortNumber> outputPorts = new ArrayList<>();
420
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100421 //TODO optimization
Andrea Campanella01e886e2017-12-15 15:27:31 +0100422
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100423 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
424 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
425 .allInstructions().stream().filter(instruction -> instruction.type()
426 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100427
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100428 if (outputFlowEntries.size() > 1) {
429 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
430 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100431 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100432
433 if (outputFlowEntries.size() == 1) {
434
435 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
436 .allInstructions().stream()
437 .filter(instruction -> {
438 return instruction.type().equals(Instruction.Type.OUTPUT);
439 }).findFirst().get();
440
441 //FIXME using GroupsInDevice for output even if flows.
442 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
443
444 }
445 log.debug("Handling Groups");
446 //Analyze Groups
447 List<Group> groups = new ArrayList<>();
448
449 Collection<FlowEntry> nonOutputFlows = flows;
450 nonOutputFlows.removeAll(outputFlowEntries);
451
452 for (FlowEntry entry : flows) {
453 getGroupsFromInstructions(trace, groups, entry.treatment().allInstructions(),
454 entry.deviceId(), builder, outputPorts, in);
455 }
456 packet = builder.build();
457 log.debug("Groups hit by packet {}", packet);
458 groups.forEach(group -> {
459 log.debug("Group {}", group);
460 });
461
Andrea Campanella01e886e2017-12-15 15:27:31 +0100462 log.debug("Output ports for packet {}", packet);
463 outputPorts.forEach(port -> {
464 log.debug("Port {}", port);
465 });
466 log.debug("Output Packet {}", packet);
467 return trace;
468 }
469
470 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100471 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
472 *
473 * @param packet the incoming packet
474 * @return the updated packet
475 */
476 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
477 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
478 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
479 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
480
481 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
482 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
483 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100484 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanella01e886e2017-12-15 15:27:31 +0100485 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
486 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
487 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100488 //translateInstruction(builder, ethInstruction);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100489 builder.add(ethInstruction);
490 }
491 packet = updatePacket(packet, builder.build()).build();
492 return packet;
493 }
494
495 /**
496 * Finds the flow entry with the minimun next table Id.
497 *
498 * @param deviceId the device to search
499 * @param currentId the current id. the search will use this as minimum
500 * @return the flow entry with the minimum table Id after the given one.
501 */
502 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
503
504 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
505
506 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
507 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
508 }
509
510 /**
511 * Gets group information from instructions.
512 *
513 * @param trace the trace we are building
514 * @param groupsForDevice the set of groups for this device
515 * @param instructions the set of instructions we are searching for groups.
516 * @param deviceId the device we are considering
517 * @param builder the builder of the input packet
518 * @param outputPorts the output ports for that packet
519 */
520 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
521 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100522 TrafficSelector.Builder builder, List<PortNumber> outputPorts,
523 ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100524 List<Instruction> groupInstructionlist = new ArrayList<>();
525 for (Instruction instruction : instructions) {
526 log.debug("Considering Instruction {}", instruction);
527 //if the instruction is not group we need to update the packet or add the output
528 //to the possible outputs for this packet
529 if (!instruction.type().equals(Instruction.Type.GROUP)) {
530 //if the instruction is not group we need to update the packet or add the output
531 //to the possible outputs for this packet
532 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100533 buildOutputFromDevice(trace, in, builder, outputPorts,
534 (OutputInstruction) instruction, groupsForDevice);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100535 } else {
536 builder = translateInstruction(builder, instruction);
537 }
538 } else {
539 //if the instuction is pointing to a group we need to get the group
540 groupInstructionlist.add(instruction);
541 }
542 }
543 //handle all the internal instructions pointing to a group.
544 for (Instruction instr : groupInstructionlist) {
545 GroupInstruction groupInstruction = (GroupInstruction) instr;
546 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
547 return groupInternal.id().equals(groupInstruction.groupId());
548 }).findAny().orElse(null);
549 if (group == null) {
550 trace.addResultMessage("Null group for Instruction " + instr);
551 break;
552 }
553 //add the group to the traversed groups
554 groupsForDevice.add(group);
555 //Cycle in each of the group's buckets and add them to the groups for this Device.
556 for (GroupBucket bucket : group.buckets().buckets()) {
557 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100558 deviceId, builder, outputPorts, in);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100559 }
560 }
561 }
562
563 /**
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100564 * Check if the output is the input port, if so adds a dop result message, otherwise builds
565 * a possible output from this device.
566 *
567 * @param trace the trace
568 * @param in the input connect point
569 * @param builder the packet builder
570 * @param outputPorts the list of output ports for this device
571 * @param outputInstruction the output instruction
572 * @param groupsForDevice
573 */
574 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, TrafficSelector.Builder builder,
575 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
576 List<Group> groupsForDevice) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100577 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100578 if (output.equals(in)) {
579 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
580 trace.getInitialConnectPoint());
581 } else {
582 trace.addGroupOutputPath(in.deviceId(),
583 new GroupsInDevice(output, groupsForDevice, builder.build()));
584 outputPorts.add(outputInstruction.port());
585 }
586 }
587
588 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100589 * Applies all give instructions to the input packet.
590 *
591 * @param packet the input packet
592 * @param instructions the set of instructions
593 * @return the packet with the applied instructions
594 */
595 private TrafficSelector.Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
596 TrafficSelector.Builder newSelector = DefaultTrafficSelector.builder();
597 packet.criteria().forEach(newSelector::add);
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100598 //FIXME optimize
599 for (Instruction instruction : instructions) {
600 newSelector = translateInstruction(newSelector, instruction);
601 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100602 return newSelector;
603 }
604
605 /**
606 * Applies an instruction to the packet in the form of a selector.
607 *
608 * @param newSelector the packet selector
609 * @param instruction the instruction to be translated
610 * @return the new selector with the applied instruction
611 */
612 private TrafficSelector.Builder translateInstruction(TrafficSelector.Builder newSelector, Instruction instruction) {
613 log.debug("Translating instruction {}", instruction);
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100614 log.debug("New Selector {}", newSelector.build());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100615 //TODO add as required
616 Criterion criterion = null;
617 switch (instruction.type()) {
618 case L2MODIFICATION:
619 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
620 switch (l2Instruction.subtype()) {
621 case VLAN_ID:
622 L2ModificationInstruction.ModVlanIdInstruction vlanIdInstruction =
623 (L2ModificationInstruction.ModVlanIdInstruction) instruction;
624 VlanId id = vlanIdInstruction.vlanId();
625 criterion = Criteria.matchVlanId(id);
626 break;
627 case VLAN_POP:
628 criterion = Criteria.matchVlanId(VlanId.NONE);
629 break;
630 case MPLS_PUSH:
631 L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
632 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
633 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
634 break;
635 case MPLS_POP:
636 L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
637 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
638 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100639
640 //When popping MPLS we remove label and BOS
641 TrafficSelector temporaryPacket = newSelector.build();
642 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
643 TrafficSelector.Builder noMplsSelector = DefaultTrafficSelector.builder();
644 temporaryPacket.criteria().stream().filter(c -> {
645 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
646 !c.type().equals(Criterion.Type.MPLS_BOS);
647 }).forEach(noMplsSelector::add);
648 newSelector = noMplsSelector;
649 }
650
Andrea Campanella01e886e2017-12-15 15:27:31 +0100651 break;
652 case MPLS_LABEL:
653 L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
654 (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
655 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100656 newSelector.matchMplsBos(true);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100657 break;
658 case ETH_DST:
659 L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
660 (L2ModificationInstruction.ModEtherInstruction) instruction;
661 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
662 break;
663 case ETH_SRC:
664 L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
665 (L2ModificationInstruction.ModEtherInstruction) instruction;
666 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
667 break;
668 default:
669 log.debug("Unsupported L2 Instruction");
670 break;
671 }
672 break;
673 default:
674 log.debug("Unsupported Instruction");
675 break;
676 }
677 if (criterion != null) {
678 log.debug("Adding criterion {}", criterion);
679 newSelector.add(criterion);
680 }
681 return newSelector;
682 }
683
684 /**
685 * Finds the rule in the device that mathces the input packet and has the highest priority.
686 *
687 * @param packet the input packet
688 * @param in the connect point the packet comes in from
689 * @param tableId the table to search
690 * @return the flow entry
691 */
692 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
693 //Computing the possible match rules.
694 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
695 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
696 .stream()
697 .filter(flowEntry -> {
698 return flowEntry.table().equals(tableId);
699 })
700 .filter(flowEntry -> {
701 return match(packet, flowEntry);
702 }).max(comparator).orElse(null);
703 }
704
705 /**
706 * Matches the packet with the given flow entry.
707 *
708 * @param packet the packet to match
709 * @param flowEntry the flow entry to match the packet against
710 * @return true if the packet matches the flow.
711 */
712 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
713 //TODO handle MAC matching
714 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
715 Criterion.Type type = criterion.type();
Andrea Campanella4ee4af12018-01-31 12:20:48 +0100716 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanella01e886e2017-12-15 15:27:31 +0100717 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
718 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
719 IPCriterion ipCriterion = (IPCriterion) criterion;
720 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
Andrea Campanella4ee4af12018-01-31 12:20:48 +0100721 //if the packet does not have an IPv4 or IPv6 criterion we return false
Andrea Campanella01e886e2017-12-15 15:27:31 +0100722 if (matchCriterion == null) {
Andrea Campanella4ee4af12018-01-31 12:20:48 +0100723 return false;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100724 }
725 try {
726 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
727 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
728 } catch (UnknownHostException e) {
729 return false;
730 }
731 //we check that the packet contains the criterion provided by the flow rule.
732 } else {
733 return packet.criteria().contains(criterion);
734 }
735 });
Simon Hunt6fefd852017-11-13 17:09:43 -0800736 }
737}