blob: c206176705e99c272c3d348fd7c905b482ed92e7 [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
134 //if the trace already contains the input connect point there is a loop
135 if (pathContainsDevice(completePath, in.deviceId())) {
136 trace.addResultMessage("Loop encountered in device " + in.deviceId());
137 return trace;
138 }
139
140 //let's add the input connect point
141 completePath.add(in);
142
143 //If the trace has no outputs for the given input we stop here
144 if (trace.getGroupOuputs(in.deviceId()) == null) {
145 computePath(completePath, trace, null);
146 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
147 return trace;
148 }
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100149
Andrea Campanella01e886e2017-12-15 15:27:31 +0100150 //If the trace has ouputs we analyze them all
151 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100152
153 ConnectPoint cp = outputPath.getOutput();
154 log.debug("Output path {}", cp);
155
Andrea Campanella01e886e2017-12-15 15:27:31 +0100156 //Hosts for the the given output
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100157 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100158 //Hosts queried from the original ip or mac
159 Set<Host> hosts = getHosts(trace);
160
161 //If the two host collections contain the same item it means we reached the proper output
162 if (!Collections.disjoint(hostsList, hosts)) {
163 log.debug("Stopping here because host is expected destination");
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100164 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100165 computePath(completePath, trace, outputPath.getOutput());
166 break;
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100167 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
168 //Getting the master when the packet gets sent as packet in
169 NodeId master = mastershipService.getMasterFor(cp.deviceId());
170 trace.addResultMessage("Packet goes to the controller " + master.id());
171 computePath(completePath, trace, outputPath.getOutput());
172
173 } else if (outputPath.getFinalPacket().getCriterion(Criterion.Type.ETH_TYPE) != null &&
174 ((EthTypeCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.ETH_TYPE)).ethType()
175 .equals(EtherType.ARP.ethType()) && deviceService.getPort(cp).isEnabled() &&
176 linkService.getEgressLinks(cp).isEmpty()) {
177 if (hostsList.isEmpty()) {
178 trace.addResultMessage("Packet is ARP and reached " + cp + " with no hosts connected ");
179 } else {
180 trace.addResultMessage("Packet is ARP and reached " + cp + " with hosts " + hostsList);
181 }
182 computePath(completePath, trace, outputPath.getOutput());
183
Andrea Campanella01e886e2017-12-15 15:27:31 +0100184 } else {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100185 //let's add the ouput for the input
186 completePath.add(cp);
187 log.debug("------------------------------------------------------------");
188 log.debug("Connect Point out {}", cp);
189 //let's compute the links for the given output
190 Set<Link> links = linkService.getEgressLinks(cp);
191 log.debug("Egress Links {}", links);
192 //No links means that the packet gets dropped.
193 if (links.size() == 0) {
194 log.warn("No links out of {}", cp);
195 computePath(completePath, trace, cp);
196 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanella01e886e2017-12-15 15:27:31 +0100197 }
198 //For each link we trace the corresponding device
199 for (Link link : links) {
200 ConnectPoint dst = link.dst();
201 //change in-port to the dst link in port
202 TrafficSelector.Builder updatedPacket = DefaultTrafficSelector.builder();
203 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
204 updatedPacket.add(Criteria.matchInPort(dst.port()));
205 log.debug("DST Connect Point {}", dst);
206 //build the elements for that device
207 traceInDevice(trace, updatedPacket.build(), dst);
208 //continue the trace along the path
209 getTrace(completePath, dst, trace);
210 }
211
212 }
213 }
214 return trace;
215 }
216
217 /**
218 * Checks if the path contains the device.
219 *
220 * @param completePath the path
221 * @param deviceId the device to check
222 * @return true if the path contains the device
223 */
224 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
225 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
226 for (ConnectPoint cp : completePath) {
227 if (cp.deviceId().equals(deviceId)) {
228 return true;
229 }
230 }
231 return false;
232 }
233
234 /**
235 * Gets the hosts for the given initial packet.
236 *
237 * @param trace the trace we are building
238 * @return set of the hosts we are trying to reach
239 */
240 private Set<Host> getHosts(StaticPacketTrace trace) {
241 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
242 .getCriterion(Criterion.Type.IPV4_DST));
243 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
244 .getCriterion(Criterion.Type.IPV6_DST));
245 Set<Host> hosts = new HashSet<>();
246 if (ipv4Criterion != null) {
247 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
248 }
249 if (ipv6Criterion != null) {
250 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
251 }
252 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
253 .getCriterion(Criterion.Type.ETH_DST));
254 if (ethCriterion != null) {
255 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
256 }
257 return hosts;
258 }
259
260 /**
261 * Computes the list of traversed connect points.
262 *
263 * @param completePath the list of devices
264 * @param trace the trace we are building
265 * @param output the final output connect point
266 */
267 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
268 List<ConnectPoint> traverseList = new ArrayList<>();
269 if (!completePath.contains(trace.getInitialConnectPoint())) {
270 traverseList.add(trace.getInitialConnectPoint());
271 }
272 traverseList.addAll(completePath);
273 if (output != null && !completePath.contains(output)) {
274 traverseList.add(output);
275 }
276 trace.addCompletePath(traverseList);
277 completePath.clear();
278 }
279
280 /**
281 * Traces the packet inside a device starting from an input connect point.
282 *
283 * @param trace the trace we are building
284 * @param packet the packet we are tracing
285 * @param in the input connect point.
286 * @return updated trace
287 */
288 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
289 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100290
291 //if device is not available exit here.
292 if (!deviceService.isAvailable(in.deviceId())) {
293 trace.addResultMessage("Device is offline " + in.deviceId());
294 return trace;
295 }
296
Andrea Campanella01e886e2017-12-15 15:27:31 +0100297 List<FlowEntry> flows = new ArrayList<>();
298 List<FlowEntry> outputFlows = new ArrayList<>();
299
300 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
301 if (nextTableIdEntry == null) {
302 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
303 return trace;
304 }
305 TableId tableId = nextTableIdEntry.table();
306 FlowEntry flowEntry;
307 boolean output = false;
308 while (!output) {
309 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
310 //get the rule that matches the incoming packet
311 flowEntry = matchHighestPriority(packet, in, tableId);
312 log.debug("Found Flow Entry {}", flowEntry);
313
314 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
315 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
316
317 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
318 if (flowEntry == null && isOfdpaHardware) {
319 log.debug("Ofdpa Hw setup, no flow rule means table miss");
320
321 //Handling Hardware Specifics
322 if (((IndexTableId) tableId).id() == 27) {
323 //Apparently a miss but Table 27 on OFDPA is a fixed table
324 packet = handleOfdpa27FixedTable(trace, packet);
325 }
326
327 //Finding next table to go In case of miss
328 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
329 log.debug("Next table id entry {}", nextTableIdEntry);
330
331 //FIXME find better solution that enable granularity greater than 0 or all rules
332 //(another possibility is max tableId)
333 if (nextTableIdEntry == null && flows.size() == 0) {
334 trace.addResultMessage("No flow rules for device" + in.deviceId() + ". Aborting");
335 return trace;
336
337 } else if (nextTableIdEntry == null) {
338 //Means that no more flow rules are present
339 output = true;
340
341 } else if (((IndexTableId) tableId).id() == 20) {
342 //if the table is 20 OFDPA skips to table 50
343 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
344 tableId = IndexTableId.of(50);
345
346 } else {
347 tableId = nextTableIdEntry.table();
348 }
349
350
351 } else if (flowEntry == null) {
352 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
353 in.deviceId() + ". Dropping");
354 return trace;
355 } else {
356 //IF the table has a transition
357 if (flowEntry.treatment().tableTransition() != null) {
358 //update the next table we transitions to
359 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
360 log.debug("Flow Entry has transition to table Id {}", tableId);
361 flows.add(flowEntry);
362 } else {
363 //table has no transition so it means that it's an output rule if on the last table
364 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
365 flows.add(flowEntry);
366 outputFlows.add(flowEntry);
367 output = true;
368 }
369 //update the packet according to the actions of this flow rule.
370 packet = updatePacket(packet, flowEntry.treatment().allInstructions()).build();
371 }
372 }
373
374 //Creating a modifiable builder for the output packet
375 TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
376 packet.criteria().forEach(builder::add);
377 //Adding all the flows to the trace
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100378 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100379
380 log.debug("Flows traversed by {}", packet);
381 flows.forEach(entry -> {
382 log.debug("Flow {}", entry);
383 });
384
385 log.debug("Output Flows for {}", packet);
386 outputFlows.forEach(entry -> {
387 log.debug("Output Flow {}", entry);
388 });
389 List<PortNumber> outputPorts = new ArrayList<>();
390
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100391 //TODO optimization
Andrea Campanella01e886e2017-12-15 15:27:31 +0100392
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100393 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
394 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
395 .allInstructions().stream().filter(instruction -> instruction.type()
396 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100397
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100398 if (outputFlowEntries.size() > 1) {
399 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
400 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100401 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100402
403 if (outputFlowEntries.size() == 1) {
404
405 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
406 .allInstructions().stream()
407 .filter(instruction -> {
408 return instruction.type().equals(Instruction.Type.OUTPUT);
409 }).findFirst().get();
410
411 //FIXME using GroupsInDevice for output even if flows.
412 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
413
414 }
415 log.debug("Handling Groups");
416 //Analyze Groups
417 List<Group> groups = new ArrayList<>();
418
419 Collection<FlowEntry> nonOutputFlows = flows;
420 nonOutputFlows.removeAll(outputFlowEntries);
421
422 for (FlowEntry entry : flows) {
423 getGroupsFromInstructions(trace, groups, entry.treatment().allInstructions(),
424 entry.deviceId(), builder, outputPorts, in);
425 }
426 packet = builder.build();
427 log.debug("Groups hit by packet {}", packet);
428 groups.forEach(group -> {
429 log.debug("Group {}", group);
430 });
431
Andrea Campanella01e886e2017-12-15 15:27:31 +0100432 log.debug("Output ports for packet {}", packet);
433 outputPorts.forEach(port -> {
434 log.debug("Port {}", port);
435 });
436 log.debug("Output Packet {}", packet);
437 return trace;
438 }
439
440 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100441 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
442 *
443 * @param packet the incoming packet
444 * @return the updated packet
445 */
446 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
447 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
448 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
449 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
450
451 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
452 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
453 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100454 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanella01e886e2017-12-15 15:27:31 +0100455 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
456 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
457 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100458 //translateInstruction(builder, ethInstruction);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100459 builder.add(ethInstruction);
460 }
461 packet = updatePacket(packet, builder.build()).build();
462 return packet;
463 }
464
465 /**
466 * Finds the flow entry with the minimun next table Id.
467 *
468 * @param deviceId the device to search
469 * @param currentId the current id. the search will use this as minimum
470 * @return the flow entry with the minimum table Id after the given one.
471 */
472 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
473
474 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
475
476 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
477 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
478 }
479
480 /**
481 * Gets group information from instructions.
482 *
483 * @param trace the trace we are building
484 * @param groupsForDevice the set of groups for this device
485 * @param instructions the set of instructions we are searching for groups.
486 * @param deviceId the device we are considering
487 * @param builder the builder of the input packet
488 * @param outputPorts the output ports for that packet
489 */
490 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
491 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100492 TrafficSelector.Builder builder, List<PortNumber> outputPorts,
493 ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100494 List<Instruction> groupInstructionlist = new ArrayList<>();
495 for (Instruction instruction : instructions) {
496 log.debug("Considering Instruction {}", instruction);
497 //if the instruction is not group we need to update the packet or add the output
498 //to the possible outputs for this packet
499 if (!instruction.type().equals(Instruction.Type.GROUP)) {
500 //if the instruction is not group we need to update the packet or add the output
501 //to the possible outputs for this packet
502 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100503 buildOutputFromDevice(trace, in, builder, outputPorts,
504 (OutputInstruction) instruction, groupsForDevice);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100505 } else {
506 builder = translateInstruction(builder, instruction);
507 }
508 } else {
509 //if the instuction is pointing to a group we need to get the group
510 groupInstructionlist.add(instruction);
511 }
512 }
513 //handle all the internal instructions pointing to a group.
514 for (Instruction instr : groupInstructionlist) {
515 GroupInstruction groupInstruction = (GroupInstruction) instr;
516 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
517 return groupInternal.id().equals(groupInstruction.groupId());
518 }).findAny().orElse(null);
519 if (group == null) {
520 trace.addResultMessage("Null group for Instruction " + instr);
521 break;
522 }
523 //add the group to the traversed groups
524 groupsForDevice.add(group);
525 //Cycle in each of the group's buckets and add them to the groups for this Device.
526 for (GroupBucket bucket : group.buckets().buckets()) {
527 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100528 deviceId, builder, outputPorts, in);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100529 }
530 }
531 }
532
533 /**
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100534 * Check if the output is the input port, if so adds a dop result message, otherwise builds
535 * a possible output from this device.
536 *
537 * @param trace the trace
538 * @param in the input connect point
539 * @param builder the packet builder
540 * @param outputPorts the list of output ports for this device
541 * @param outputInstruction the output instruction
542 * @param groupsForDevice
543 */
544 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, TrafficSelector.Builder builder,
545 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
546 List<Group> groupsForDevice) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100547 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100548 if (output.equals(in)) {
549 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
550 trace.getInitialConnectPoint());
551 } else {
552 trace.addGroupOutputPath(in.deviceId(),
553 new GroupsInDevice(output, groupsForDevice, builder.build()));
554 outputPorts.add(outputInstruction.port());
555 }
556 }
557
558 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100559 * Applies all give instructions to the input packet.
560 *
561 * @param packet the input packet
562 * @param instructions the set of instructions
563 * @return the packet with the applied instructions
564 */
565 private TrafficSelector.Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
566 TrafficSelector.Builder newSelector = DefaultTrafficSelector.builder();
567 packet.criteria().forEach(newSelector::add);
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100568 //FIXME optimize
569 for (Instruction instruction : instructions) {
570 newSelector = translateInstruction(newSelector, instruction);
571 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100572 return newSelector;
573 }
574
575 /**
576 * Applies an instruction to the packet in the form of a selector.
577 *
578 * @param newSelector the packet selector
579 * @param instruction the instruction to be translated
580 * @return the new selector with the applied instruction
581 */
582 private TrafficSelector.Builder translateInstruction(TrafficSelector.Builder newSelector, Instruction instruction) {
583 log.debug("Translating instruction {}", instruction);
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100584 log.debug("New Selector {}", newSelector.build());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100585 //TODO add as required
586 Criterion criterion = null;
587 switch (instruction.type()) {
588 case L2MODIFICATION:
589 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
590 switch (l2Instruction.subtype()) {
591 case VLAN_ID:
592 L2ModificationInstruction.ModVlanIdInstruction vlanIdInstruction =
593 (L2ModificationInstruction.ModVlanIdInstruction) instruction;
594 VlanId id = vlanIdInstruction.vlanId();
595 criterion = Criteria.matchVlanId(id);
596 break;
597 case VLAN_POP:
598 criterion = Criteria.matchVlanId(VlanId.NONE);
599 break;
600 case MPLS_PUSH:
601 L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
602 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
603 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
604 break;
605 case MPLS_POP:
606 L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
607 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
608 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100609
610 //When popping MPLS we remove label and BOS
611 TrafficSelector temporaryPacket = newSelector.build();
612 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
613 TrafficSelector.Builder noMplsSelector = DefaultTrafficSelector.builder();
614 temporaryPacket.criteria().stream().filter(c -> {
615 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
616 !c.type().equals(Criterion.Type.MPLS_BOS);
617 }).forEach(noMplsSelector::add);
618 newSelector = noMplsSelector;
619 }
620
Andrea Campanella01e886e2017-12-15 15:27:31 +0100621 break;
622 case MPLS_LABEL:
623 L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
624 (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
625 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100626 newSelector.matchMplsBos(true);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100627 break;
628 case ETH_DST:
629 L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
630 (L2ModificationInstruction.ModEtherInstruction) instruction;
631 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
632 break;
633 case ETH_SRC:
634 L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
635 (L2ModificationInstruction.ModEtherInstruction) instruction;
636 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
637 break;
638 default:
639 log.debug("Unsupported L2 Instruction");
640 break;
641 }
642 break;
643 default:
644 log.debug("Unsupported Instruction");
645 break;
646 }
647 if (criterion != null) {
648 log.debug("Adding criterion {}", criterion);
649 newSelector.add(criterion);
650 }
651 return newSelector;
652 }
653
654 /**
655 * Finds the rule in the device that mathces the input packet and has the highest priority.
656 *
657 * @param packet the input packet
658 * @param in the connect point the packet comes in from
659 * @param tableId the table to search
660 * @return the flow entry
661 */
662 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
663 //Computing the possible match rules.
664 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
665 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
666 .stream()
667 .filter(flowEntry -> {
668 return flowEntry.table().equals(tableId);
669 })
670 .filter(flowEntry -> {
671 return match(packet, flowEntry);
672 }).max(comparator).orElse(null);
673 }
674
675 /**
676 * Matches the packet with the given flow entry.
677 *
678 * @param packet the packet to match
679 * @param flowEntry the flow entry to match the packet against
680 * @return true if the packet matches the flow.
681 */
682 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
683 //TODO handle MAC matching
684 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
685 Criterion.Type type = criterion.type();
Andrea Campanella4ee4af12018-01-31 12:20:48 +0100686 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanella01e886e2017-12-15 15:27:31 +0100687 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
688 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
689 IPCriterion ipCriterion = (IPCriterion) criterion;
690 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
Andrea Campanella4ee4af12018-01-31 12:20:48 +0100691 //if the packet does not have an IPv4 or IPv6 criterion we return false
Andrea Campanella01e886e2017-12-15 15:27:31 +0100692 if (matchCriterion == null) {
Andrea Campanella4ee4af12018-01-31 12:20:48 +0100693 return false;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100694 }
695 try {
696 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
697 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
698 } catch (UnknownHostException e) {
699 return false;
700 }
701 //we check that the packet contains the criterion provided by the flow rule.
702 } else {
703 return packet.criteria().contains(criterion);
704 }
705 });
Simon Hunt6fefd852017-11-13 17:09:43 -0800706 }
707}