blob: b66996dfe83eda69132d683c71eabf55cd5f0b67 [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;
Simon Hunt026a2872017-11-13 17:09:43 -080027import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010028import org.onosproject.net.DeviceId;
29import org.onosproject.net.Host;
30import org.onosproject.net.Link;
31import org.onosproject.net.PortNumber;
Andrea Campanella17d45192018-01-18 17:11:42 +010032import org.onosproject.net.device.DeviceService;
Andrea Campanellae4084402017-12-15 15:27:31 +010033import org.onosproject.net.driver.DriverService;
34import org.onosproject.net.flow.DefaultTrafficSelector;
35import org.onosproject.net.flow.FlowEntry;
36import org.onosproject.net.flow.FlowRule;
Simon Hunt026a2872017-11-13 17:09:43 -080037import org.onosproject.net.flow.FlowRuleService;
Andrea Campanellae4084402017-12-15 15:27:31 +010038import org.onosproject.net.flow.IndexTableId;
39import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080040import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-12-15 15:27:31 +010041import org.onosproject.net.flow.criteria.Criteria;
42import org.onosproject.net.flow.criteria.Criterion;
43import org.onosproject.net.flow.criteria.EthCriterion;
44import org.onosproject.net.flow.criteria.EthTypeCriterion;
45import org.onosproject.net.flow.criteria.IPCriterion;
46import org.onosproject.net.flow.instructions.Instruction;
47import org.onosproject.net.flow.instructions.Instructions;
48import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
49import org.onosproject.net.flow.instructions.L2ModificationInstruction;
50import org.onosproject.net.group.Group;
51import org.onosproject.net.group.GroupBucket;
Simon Hunt026a2872017-11-13 17:09:43 -080052import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010053import org.onosproject.net.host.HostService;
54import org.onosproject.net.link.LinkService;
55import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080056import org.onosproject.t3.api.StaticPacketTrace;
57import org.onosproject.t3.api.TroubleshootService;
58import org.slf4j.Logger;
59
Andrea Campanellae4084402017-12-15 15:27:31 +010060import java.net.UnknownHostException;
61import java.util.ArrayList;
62import java.util.Collections;
63import java.util.Comparator;
64import java.util.HashSet;
65import java.util.List;
66import java.util.Set;
67import java.util.stream.Collectors;
68
69import static org.onlab.packet.EthType.EtherType;
70import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Simon Hunt026a2872017-11-13 17:09:43 -080071import static org.slf4j.LoggerFactory.getLogger;
72
73/**
Andrea Campanellae4084402017-12-15 15:27:31 +010074 * Manager to troubleshoot packets inside the network.
75 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
76 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080077 */
78@Service
79@Component(immediate = true)
80public class TroubleshootManager implements TroubleshootService {
81
82 private static final Logger log = getLogger(TroubleshootManager.class);
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected FlowRuleService flowRuleService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected GroupService groupService;
89
Andrea Campanellae4084402017-12-15 15:27:31 +010090 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -080092
Andrea Campanellae4084402017-12-15 15:27:31 +010093 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected HostService hostService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -080098
Andrea Campanella17d45192018-01-18 17:11:42 +010099 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected DeviceService deviceService;
101
Simon Hunt026a2872017-11-13 17:09:43 -0800102 @Override
103 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100104 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100105 //device must exist in ONOS
106 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
107 "Device " + in.deviceId() + " must exist in ONOS");
108
Andrea Campanellae4084402017-12-15 15:27:31 +0100109 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
110 //FIXME this can be done recursively
111 trace = traceInDevice(trace, packet, in);
112 //Building output connect Points
113 List<ConnectPoint> path = new ArrayList<>();
114 trace = getTrace(path, in, trace);
115 return trace;
116 }
117
118 /**
119 * Computes a trace for a give packet that start in the network at the given connect point.
120 *
121 * @param completePath the path traversed by the packet
122 * @param in the input connect point
123 * @param trace the trace to build
124 * @return the build trace for that packet.
125 */
126 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
127
128 //if the trace already contains the input connect point there is a loop
129 if (pathContainsDevice(completePath, in.deviceId())) {
130 trace.addResultMessage("Loop encountered in device " + in.deviceId());
131 return trace;
132 }
133
134 //let's add the input connect point
135 completePath.add(in);
136
137 //If the trace has no outputs for the given input we stop here
138 if (trace.getGroupOuputs(in.deviceId()) == null) {
139 computePath(completePath, trace, null);
140 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
141 return trace;
142 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100143
Andrea Campanellae4084402017-12-15 15:27:31 +0100144 //If the trace has ouputs we analyze them all
145 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
146 log.debug("Output path {}", outputPath.getOutput());
147 //Hosts for the the given output
148 Set<Host> hostsList = hostService.getConnectedHosts(outputPath.getOutput());
149 //Hosts queried from the original ip or mac
150 Set<Host> hosts = getHosts(trace);
151
152 //If the two host collections contain the same item it means we reached the proper output
153 if (!Collections.disjoint(hostsList, hosts)) {
154 log.debug("Stopping here because host is expected destination");
155 trace.addResultMessage("Reached required destination Host");
156 computePath(completePath, trace, outputPath.getOutput());
157 break;
158 } else {
159 ConnectPoint cp = outputPath.getOutput();
160 //let's add the ouput for the input
161 completePath.add(cp);
162 log.debug("------------------------------------------------------------");
163 log.debug("Connect Point out {}", cp);
164 //let's compute the links for the given output
165 Set<Link> links = linkService.getEgressLinks(cp);
166 log.debug("Egress Links {}", links);
167 //No links means that the packet gets dropped.
168 if (links.size() == 0) {
169 log.warn("No links out of {}", cp);
170 computePath(completePath, trace, cp);
171 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
172 return trace;
173 }
174 //For each link we trace the corresponding device
175 for (Link link : links) {
176 ConnectPoint dst = link.dst();
177 //change in-port to the dst link in port
178 TrafficSelector.Builder updatedPacket = DefaultTrafficSelector.builder();
179 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
180 updatedPacket.add(Criteria.matchInPort(dst.port()));
181 log.debug("DST Connect Point {}", dst);
182 //build the elements for that device
183 traceInDevice(trace, updatedPacket.build(), dst);
184 //continue the trace along the path
185 getTrace(completePath, dst, trace);
186 }
187
188 }
189 }
190 return trace;
191 }
192
193 /**
194 * Checks if the path contains the device.
195 *
196 * @param completePath the path
197 * @param deviceId the device to check
198 * @return true if the path contains the device
199 */
200 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
201 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
202 for (ConnectPoint cp : completePath) {
203 if (cp.deviceId().equals(deviceId)) {
204 return true;
205 }
206 }
207 return false;
208 }
209
210 /**
211 * Gets the hosts for the given initial packet.
212 *
213 * @param trace the trace we are building
214 * @return set of the hosts we are trying to reach
215 */
216 private Set<Host> getHosts(StaticPacketTrace trace) {
217 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
218 .getCriterion(Criterion.Type.IPV4_DST));
219 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
220 .getCriterion(Criterion.Type.IPV6_DST));
221 Set<Host> hosts = new HashSet<>();
222 if (ipv4Criterion != null) {
223 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
224 }
225 if (ipv6Criterion != null) {
226 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
227 }
228 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
229 .getCriterion(Criterion.Type.ETH_DST));
230 if (ethCriterion != null) {
231 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
232 }
233 return hosts;
234 }
235
236 /**
237 * Computes the list of traversed connect points.
238 *
239 * @param completePath the list of devices
240 * @param trace the trace we are building
241 * @param output the final output connect point
242 */
243 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
244 List<ConnectPoint> traverseList = new ArrayList<>();
245 if (!completePath.contains(trace.getInitialConnectPoint())) {
246 traverseList.add(trace.getInitialConnectPoint());
247 }
248 traverseList.addAll(completePath);
249 if (output != null && !completePath.contains(output)) {
250 traverseList.add(output);
251 }
252 trace.addCompletePath(traverseList);
253 completePath.clear();
254 }
255
256 /**
257 * Traces the packet inside a device starting from an input connect point.
258 *
259 * @param trace the trace we are building
260 * @param packet the packet we are tracing
261 * @param in the input connect point.
262 * @return updated trace
263 */
264 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
265 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100266
267 //if device is not available exit here.
268 if (!deviceService.isAvailable(in.deviceId())) {
269 trace.addResultMessage("Device is offline " + in.deviceId());
270 return trace;
271 }
272
Andrea Campanellae4084402017-12-15 15:27:31 +0100273 List<FlowEntry> flows = new ArrayList<>();
274 List<FlowEntry> outputFlows = new ArrayList<>();
275
276 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
277 if (nextTableIdEntry == null) {
278 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
279 return trace;
280 }
281 TableId tableId = nextTableIdEntry.table();
282 FlowEntry flowEntry;
283 boolean output = false;
284 while (!output) {
285 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
286 //get the rule that matches the incoming packet
287 flowEntry = matchHighestPriority(packet, in, tableId);
288 log.debug("Found Flow Entry {}", flowEntry);
289
290 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
291 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
292
293 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
294 if (flowEntry == null && isOfdpaHardware) {
295 log.debug("Ofdpa Hw setup, no flow rule means table miss");
296
297 //Handling Hardware Specifics
298 if (((IndexTableId) tableId).id() == 27) {
299 //Apparently a miss but Table 27 on OFDPA is a fixed table
300 packet = handleOfdpa27FixedTable(trace, packet);
301 }
302
303 //Finding next table to go In case of miss
304 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
305 log.debug("Next table id entry {}", nextTableIdEntry);
306
307 //FIXME find better solution that enable granularity greater than 0 or all rules
308 //(another possibility is max tableId)
309 if (nextTableIdEntry == null && flows.size() == 0) {
310 trace.addResultMessage("No flow rules for device" + in.deviceId() + ". Aborting");
311 return trace;
312
313 } else if (nextTableIdEntry == null) {
314 //Means that no more flow rules are present
315 output = true;
316
317 } else if (((IndexTableId) tableId).id() == 20) {
318 //if the table is 20 OFDPA skips to table 50
319 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
320 tableId = IndexTableId.of(50);
321
322 } else {
323 tableId = nextTableIdEntry.table();
324 }
325
326
327 } else if (flowEntry == null) {
328 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
329 in.deviceId() + ". Dropping");
330 return trace;
331 } else {
332 //IF the table has a transition
333 if (flowEntry.treatment().tableTransition() != null) {
334 //update the next table we transitions to
335 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
336 log.debug("Flow Entry has transition to table Id {}", tableId);
337 flows.add(flowEntry);
338 } else {
339 //table has no transition so it means that it's an output rule if on the last table
340 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
341 flows.add(flowEntry);
342 outputFlows.add(flowEntry);
343 output = true;
344 }
345 //update the packet according to the actions of this flow rule.
346 packet = updatePacket(packet, flowEntry.treatment().allInstructions()).build();
347 }
348 }
349
350 //Creating a modifiable builder for the output packet
351 TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
352 packet.criteria().forEach(builder::add);
353 //Adding all the flows to the trace
354 trace.addFlowsForDevice(in.deviceId(), flows);
355
356 log.debug("Flows traversed by {}", packet);
357 flows.forEach(entry -> {
358 log.debug("Flow {}", entry);
359 });
360
361 log.debug("Output Flows for {}", packet);
362 outputFlows.forEach(entry -> {
363 log.debug("Output Flow {}", entry);
364 });
365 List<PortNumber> outputPorts = new ArrayList<>();
366
367 //Decide Output for packet when flow rule contains an OUTPUT instruction
368 Set<Instruction> outputFlowEntries = outputFlows.stream().flatMap(flow -> {
369 return flow.treatment().allInstructions().stream();
370 })
371 .filter(instruction -> {
372 return instruction.type().equals(Instruction.Type.OUTPUT);
373 }).collect(Collectors.toSet());
374 log.debug("Output instructions {}", outputFlowEntries);
375
376 if (outputFlowEntries.size() != 0) {
377 outputThroughFlows(trace, packet, in, builder, outputPorts, outputFlowEntries);
378
379 } else {
380 log.debug("Handling Groups");
381 //Analyze Groups
382 List<Group> groups = new ArrayList<>();
383
384 for (FlowEntry entry : flows) {
385 getGroupsFromInstructions(trace, groups, entry.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100386 entry.deviceId(), builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100387 }
388 packet = builder.build();
389 log.debug("Groups hit by packet {}", packet);
390 groups.forEach(group -> {
391 log.debug("Group {}", group);
392 });
393 }
394 log.debug("Output ports for packet {}", packet);
395 outputPorts.forEach(port -> {
396 log.debug("Port {}", port);
397 });
398 log.debug("Output Packet {}", packet);
399 return trace;
400 }
401
402 /**
403 * Method that saves the output if that si done via an OUTPUT treatment of a flow rule.
404 *
405 * @param trace the trace
406 * @param packet the packet coming in to this device
407 * @param in the input connect point for this device
408 * @param builder the updated packet0
409 * @param outputPorts the list of output ports for this device
410 * @param outputFlowEntries the list of flow entries with OUTPUT treatment
411 */
412 private void outputThroughFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
413 TrafficSelector.Builder builder, List<PortNumber> outputPorts,
414 Set<Instruction> outputFlowEntries) {
415 if (outputFlowEntries.size() > 1) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100416 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100417 } else {
418 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.iterator().next();
419 //FIXME using GroupsInDevice for output even if flows.
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100420 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
Andrea Campanellae4084402017-12-15 15:27:31 +0100421 }
422 }
423
424 /**
425 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
426 *
427 * @param packet the incoming packet
428 * @return the updated packet
429 */
430 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
431 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
432 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
433 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
434
435 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
436 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
437 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100438 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100439 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
440 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
441 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100442 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100443 builder.add(ethInstruction);
444 }
445 packet = updatePacket(packet, builder.build()).build();
446 return packet;
447 }
448
449 /**
450 * Finds the flow entry with the minimun next table Id.
451 *
452 * @param deviceId the device to search
453 * @param currentId the current id. the search will use this as minimum
454 * @return the flow entry with the minimum table Id after the given one.
455 */
456 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
457
458 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
459
460 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
461 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
462 }
463
464 /**
465 * Gets group information from instructions.
466 *
467 * @param trace the trace we are building
468 * @param groupsForDevice the set of groups for this device
469 * @param instructions the set of instructions we are searching for groups.
470 * @param deviceId the device we are considering
471 * @param builder the builder of the input packet
472 * @param outputPorts the output ports for that packet
473 */
474 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
475 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100476 TrafficSelector.Builder builder, List<PortNumber> outputPorts,
477 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100478 List<Instruction> groupInstructionlist = new ArrayList<>();
479 for (Instruction instruction : instructions) {
480 log.debug("Considering Instruction {}", instruction);
481 //if the instruction is not group we need to update the packet or add the output
482 //to the possible outputs for this packet
483 if (!instruction.type().equals(Instruction.Type.GROUP)) {
484 //if the instruction is not group we need to update the packet or add the output
485 //to the possible outputs for this packet
486 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100487 buildOutputFromDevice(trace, in, builder, outputPorts,
488 (OutputInstruction) instruction, groupsForDevice);
Andrea Campanellae4084402017-12-15 15:27:31 +0100489 } else {
490 builder = translateInstruction(builder, instruction);
491 }
492 } else {
493 //if the instuction is pointing to a group we need to get the group
494 groupInstructionlist.add(instruction);
495 }
496 }
497 //handle all the internal instructions pointing to a group.
498 for (Instruction instr : groupInstructionlist) {
499 GroupInstruction groupInstruction = (GroupInstruction) instr;
500 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
501 return groupInternal.id().equals(groupInstruction.groupId());
502 }).findAny().orElse(null);
503 if (group == null) {
504 trace.addResultMessage("Null group for Instruction " + instr);
505 break;
506 }
507 //add the group to the traversed groups
508 groupsForDevice.add(group);
509 //Cycle in each of the group's buckets and add them to the groups for this Device.
510 for (GroupBucket bucket : group.buckets().buckets()) {
511 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100512 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100513 }
514 }
515 }
516
517 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100518 * Check if the output is the input port, if so adds a dop result message, otherwise builds
519 * a possible output from this device.
520 *
521 * @param trace the trace
522 * @param in the input connect point
523 * @param builder the packet builder
524 * @param outputPorts the list of output ports for this device
525 * @param outputInstruction the output instruction
526 * @param groupsForDevice
527 */
528 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, TrafficSelector.Builder builder,
529 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
530 List<Group> groupsForDevice) {
531 ConnectPoint output = ConnectPoint.deviceConnectPoint(in.deviceId() + "/" +
532 outputInstruction.port());
533 if (output.equals(in)) {
534 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
535 trace.getInitialConnectPoint());
536 } else {
537 trace.addGroupOutputPath(in.deviceId(),
538 new GroupsInDevice(output, groupsForDevice, builder.build()));
539 outputPorts.add(outputInstruction.port());
540 }
541 }
542
543 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100544 * Applies all give instructions to the input packet.
545 *
546 * @param packet the input packet
547 * @param instructions the set of instructions
548 * @return the packet with the applied instructions
549 */
550 private TrafficSelector.Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
551 TrafficSelector.Builder newSelector = DefaultTrafficSelector.builder();
552 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +0100553 //FIXME optimize
554 for (Instruction instruction : instructions) {
555 newSelector = translateInstruction(newSelector, instruction);
556 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100557 return newSelector;
558 }
559
560 /**
561 * Applies an instruction to the packet in the form of a selector.
562 *
563 * @param newSelector the packet selector
564 * @param instruction the instruction to be translated
565 * @return the new selector with the applied instruction
566 */
567 private TrafficSelector.Builder translateInstruction(TrafficSelector.Builder newSelector, Instruction instruction) {
568 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +0100569 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +0100570 //TODO add as required
571 Criterion criterion = null;
572 switch (instruction.type()) {
573 case L2MODIFICATION:
574 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
575 switch (l2Instruction.subtype()) {
576 case VLAN_ID:
577 L2ModificationInstruction.ModVlanIdInstruction vlanIdInstruction =
578 (L2ModificationInstruction.ModVlanIdInstruction) instruction;
579 VlanId id = vlanIdInstruction.vlanId();
580 criterion = Criteria.matchVlanId(id);
581 break;
582 case VLAN_POP:
583 criterion = Criteria.matchVlanId(VlanId.NONE);
584 break;
585 case MPLS_PUSH:
586 L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
587 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
588 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
589 break;
590 case MPLS_POP:
591 L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
592 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
593 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +0100594
595 //When popping MPLS we remove label and BOS
596 TrafficSelector temporaryPacket = newSelector.build();
597 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
598 TrafficSelector.Builder noMplsSelector = DefaultTrafficSelector.builder();
599 temporaryPacket.criteria().stream().filter(c -> {
600 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
601 !c.type().equals(Criterion.Type.MPLS_BOS);
602 }).forEach(noMplsSelector::add);
603 newSelector = noMplsSelector;
604 }
605
Andrea Campanellae4084402017-12-15 15:27:31 +0100606 break;
607 case MPLS_LABEL:
608 L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
609 (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
610 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +0100611 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +0100612 break;
613 case ETH_DST:
614 L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
615 (L2ModificationInstruction.ModEtherInstruction) instruction;
616 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
617 break;
618 case ETH_SRC:
619 L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
620 (L2ModificationInstruction.ModEtherInstruction) instruction;
621 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
622 break;
623 default:
624 log.debug("Unsupported L2 Instruction");
625 break;
626 }
627 break;
628 default:
629 log.debug("Unsupported Instruction");
630 break;
631 }
632 if (criterion != null) {
633 log.debug("Adding criterion {}", criterion);
634 newSelector.add(criterion);
635 }
636 return newSelector;
637 }
638
639 /**
640 * Finds the rule in the device that mathces the input packet and has the highest priority.
641 *
642 * @param packet the input packet
643 * @param in the connect point the packet comes in from
644 * @param tableId the table to search
645 * @return the flow entry
646 */
647 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
648 //Computing the possible match rules.
649 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
650 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
651 .stream()
652 .filter(flowEntry -> {
653 return flowEntry.table().equals(tableId);
654 })
655 .filter(flowEntry -> {
656 return match(packet, flowEntry);
657 }).max(comparator).orElse(null);
658 }
659
660 /**
661 * Matches the packet with the given flow entry.
662 *
663 * @param packet the packet to match
664 * @param flowEntry the flow entry to match the packet against
665 * @return true if the packet matches the flow.
666 */
667 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
668 //TODO handle MAC matching
669 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
670 Criterion.Type type = criterion.type();
671 //If the critrion has IP we need to do LPM to establish matching.
672 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
673 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
674 IPCriterion ipCriterion = (IPCriterion) criterion;
675 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
676 //if the packet does not have an IPv4 or IPv6 criterion we return true
677 if (matchCriterion == null) {
678 return true;
679 }
680 try {
681 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
682 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
683 } catch (UnknownHostException e) {
684 return false;
685 }
686 //we check that the packet contains the criterion provided by the flow rule.
687 } else {
688 return packet.criteria().contains(criterion);
689 }
690 });
Simon Hunt026a2872017-11-13 17:09:43 -0800691 }
692}