blob: 24cd251db23206847a55e3dce3bad2d7f99ea4f5 [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())) {
438 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
439 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
440 //FIXME what do we use as L3_Unicast mpls Label ?
441 builder.add(ethInstruction);
442 }
443 packet = updatePacket(packet, builder.build()).build();
444 return packet;
445 }
446
447 /**
448 * Finds the flow entry with the minimun next table Id.
449 *
450 * @param deviceId the device to search
451 * @param currentId the current id. the search will use this as minimum
452 * @return the flow entry with the minimum table Id after the given one.
453 */
454 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
455
456 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
457
458 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
459 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
460 }
461
462 /**
463 * Gets group information from instructions.
464 *
465 * @param trace the trace we are building
466 * @param groupsForDevice the set of groups for this device
467 * @param instructions the set of instructions we are searching for groups.
468 * @param deviceId the device we are considering
469 * @param builder the builder of the input packet
470 * @param outputPorts the output ports for that packet
471 */
472 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
473 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100474 TrafficSelector.Builder builder, List<PortNumber> outputPorts,
475 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100476 List<Instruction> groupInstructionlist = new ArrayList<>();
477 for (Instruction instruction : instructions) {
478 log.debug("Considering Instruction {}", instruction);
479 //if the instruction is not group we need to update the packet or add the output
480 //to the possible outputs for this packet
481 if (!instruction.type().equals(Instruction.Type.GROUP)) {
482 //if the instruction is not group we need to update the packet or add the output
483 //to the possible outputs for this packet
484 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100485 buildOutputFromDevice(trace, in, builder, outputPorts,
486 (OutputInstruction) instruction, groupsForDevice);
Andrea Campanellae4084402017-12-15 15:27:31 +0100487 } else {
488 builder = translateInstruction(builder, instruction);
489 }
490 } else {
491 //if the instuction is pointing to a group we need to get the group
492 groupInstructionlist.add(instruction);
493 }
494 }
495 //handle all the internal instructions pointing to a group.
496 for (Instruction instr : groupInstructionlist) {
497 GroupInstruction groupInstruction = (GroupInstruction) instr;
498 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
499 return groupInternal.id().equals(groupInstruction.groupId());
500 }).findAny().orElse(null);
501 if (group == null) {
502 trace.addResultMessage("Null group for Instruction " + instr);
503 break;
504 }
505 //add the group to the traversed groups
506 groupsForDevice.add(group);
507 //Cycle in each of the group's buckets and add them to the groups for this Device.
508 for (GroupBucket bucket : group.buckets().buckets()) {
509 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100510 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100511 }
512 }
513 }
514
515 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100516 * Check if the output is the input port, if so adds a dop result message, otherwise builds
517 * a possible output from this device.
518 *
519 * @param trace the trace
520 * @param in the input connect point
521 * @param builder the packet builder
522 * @param outputPorts the list of output ports for this device
523 * @param outputInstruction the output instruction
524 * @param groupsForDevice
525 */
526 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, TrafficSelector.Builder builder,
527 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
528 List<Group> groupsForDevice) {
529 ConnectPoint output = ConnectPoint.deviceConnectPoint(in.deviceId() + "/" +
530 outputInstruction.port());
531 if (output.equals(in)) {
532 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
533 trace.getInitialConnectPoint());
534 } else {
535 trace.addGroupOutputPath(in.deviceId(),
536 new GroupsInDevice(output, groupsForDevice, builder.build()));
537 outputPorts.add(outputInstruction.port());
538 }
539 }
540
541 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100542 * Applies all give instructions to the input packet.
543 *
544 * @param packet the input packet
545 * @param instructions the set of instructions
546 * @return the packet with the applied instructions
547 */
548 private TrafficSelector.Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
549 TrafficSelector.Builder newSelector = DefaultTrafficSelector.builder();
550 packet.criteria().forEach(newSelector::add);
551 instructions.forEach(instruction -> {
552 translateInstruction(newSelector, instruction);
553 });
554 return newSelector;
555 }
556
557 /**
558 * Applies an instruction to the packet in the form of a selector.
559 *
560 * @param newSelector the packet selector
561 * @param instruction the instruction to be translated
562 * @return the new selector with the applied instruction
563 */
564 private TrafficSelector.Builder translateInstruction(TrafficSelector.Builder newSelector, Instruction instruction) {
565 log.debug("Translating instruction {}", instruction);
566 //TODO add as required
567 Criterion criterion = null;
568 switch (instruction.type()) {
569 case L2MODIFICATION:
570 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
571 switch (l2Instruction.subtype()) {
572 case VLAN_ID:
573 L2ModificationInstruction.ModVlanIdInstruction vlanIdInstruction =
574 (L2ModificationInstruction.ModVlanIdInstruction) instruction;
575 VlanId id = vlanIdInstruction.vlanId();
576 criterion = Criteria.matchVlanId(id);
577 break;
578 case VLAN_POP:
579 criterion = Criteria.matchVlanId(VlanId.NONE);
580 break;
581 case MPLS_PUSH:
582 L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
583 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
584 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
585 break;
586 case MPLS_POP:
587 L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
588 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
589 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
590 break;
591 case MPLS_LABEL:
592 L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
593 (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
594 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
595 break;
596 case ETH_DST:
597 L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
598 (L2ModificationInstruction.ModEtherInstruction) instruction;
599 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
600 break;
601 case ETH_SRC:
602 L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
603 (L2ModificationInstruction.ModEtherInstruction) instruction;
604 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
605 break;
606 default:
607 log.debug("Unsupported L2 Instruction");
608 break;
609 }
610 break;
611 default:
612 log.debug("Unsupported Instruction");
613 break;
614 }
615 if (criterion != null) {
616 log.debug("Adding criterion {}", criterion);
617 newSelector.add(criterion);
618 }
619 return newSelector;
620 }
621
622 /**
623 * Finds the rule in the device that mathces the input packet and has the highest priority.
624 *
625 * @param packet the input packet
626 * @param in the connect point the packet comes in from
627 * @param tableId the table to search
628 * @return the flow entry
629 */
630 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
631 //Computing the possible match rules.
632 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
633 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
634 .stream()
635 .filter(flowEntry -> {
636 return flowEntry.table().equals(tableId);
637 })
638 .filter(flowEntry -> {
639 return match(packet, flowEntry);
640 }).max(comparator).orElse(null);
641 }
642
643 /**
644 * Matches the packet with the given flow entry.
645 *
646 * @param packet the packet to match
647 * @param flowEntry the flow entry to match the packet against
648 * @return true if the packet matches the flow.
649 */
650 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
651 //TODO handle MAC matching
652 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
653 Criterion.Type type = criterion.type();
654 //If the critrion has IP we need to do LPM to establish matching.
655 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
656 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
657 IPCriterion ipCriterion = (IPCriterion) criterion;
658 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
659 //if the packet does not have an IPv4 or IPv6 criterion we return true
660 if (matchCriterion == null) {
661 return true;
662 }
663 try {
664 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
665 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
666 } catch (UnknownHostException e) {
667 return false;
668 }
669 //we check that the packet contains the criterion provided by the flow rule.
670 } else {
671 return packet.criteria().contains(criterion);
672 }
673 });
Simon Hunt026a2872017-11-13 17:09:43 -0800674 }
675}