blob: b89732de5cc7a87aa69da59823ee3603b970acd6 [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 Campanella55c3f422018-02-08 17:10:11 +010026import org.onlab.packet.IpAddress;
Andrea Campanellae4084402017-12-15 15:27:31 +010027import org.onlab.packet.VlanId;
Andrea Campanella54923d62018-01-23 12:46:04 +010028import org.onosproject.cluster.NodeId;
29import org.onosproject.mastership.MastershipService;
Simon Hunt026a2872017-11-13 17:09:43 -080030import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010031import org.onosproject.net.DeviceId;
32import org.onosproject.net.Host;
Andrea Campanella55c3f422018-02-08 17:10:11 +010033import org.onosproject.net.HostId;
Andrea Campanellae4084402017-12-15 15:27:31 +010034import org.onosproject.net.Link;
Andrea Campanella1445f7a2018-02-07 12:00:12 +010035import org.onosproject.net.Port;
Andrea Campanellae4084402017-12-15 15:27:31 +010036import org.onosproject.net.PortNumber;
Andrea Campanella55c3f422018-02-08 17:10:11 +010037import org.onosproject.net.config.ConfigException;
38import org.onosproject.net.config.NetworkConfigService;
39import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella17d45192018-01-18 17:11:42 +010040import org.onosproject.net.device.DeviceService;
Andrea Campanellae4084402017-12-15 15:27:31 +010041import org.onosproject.net.driver.DriverService;
42import org.onosproject.net.flow.DefaultTrafficSelector;
43import org.onosproject.net.flow.FlowEntry;
44import org.onosproject.net.flow.FlowRule;
Simon Hunt026a2872017-11-13 17:09:43 -080045import org.onosproject.net.flow.FlowRuleService;
Andrea Campanellae4084402017-12-15 15:27:31 +010046import org.onosproject.net.flow.IndexTableId;
47import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080048import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-12-15 15:27:31 +010049import org.onosproject.net.flow.criteria.Criteria;
50import org.onosproject.net.flow.criteria.Criterion;
51import org.onosproject.net.flow.criteria.EthCriterion;
52import org.onosproject.net.flow.criteria.EthTypeCriterion;
53import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanellae6798012018-02-06 15:46:52 +010054import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanellae4084402017-12-15 15:27:31 +010055import org.onosproject.net.flow.instructions.Instruction;
56import org.onosproject.net.flow.instructions.Instructions;
57import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
58import org.onosproject.net.flow.instructions.L2ModificationInstruction;
59import org.onosproject.net.group.Group;
60import org.onosproject.net.group.GroupBucket;
Simon Hunt026a2872017-11-13 17:09:43 -080061import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010062import org.onosproject.net.host.HostService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010063import org.onosproject.net.host.InterfaceIpAddress;
64import org.onosproject.net.intf.Interface;
Andrea Campanellae4084402017-12-15 15:27:31 +010065import org.onosproject.net.link.LinkService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010066import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanellae4084402017-12-15 15:27:31 +010067import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080068import org.onosproject.t3.api.StaticPacketTrace;
69import org.onosproject.t3.api.TroubleshootService;
70import org.slf4j.Logger;
71
Andrea Campanellae4084402017-12-15 15:27:31 +010072import java.net.UnknownHostException;
73import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010074import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010075import java.util.Collections;
76import java.util.Comparator;
77import java.util.HashSet;
78import java.util.List;
79import java.util.Set;
80import java.util.stream.Collectors;
81
82import static org.onlab.packet.EthType.EtherType;
Andrea Campanellae6798012018-02-06 15:46:52 +010083import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanellae4084402017-12-15 15:27:31 +010084import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella94c594a2018-02-06 18:58:40 +010085import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
86import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
87import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
88import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Simon Hunt026a2872017-11-13 17:09:43 -080089import static org.slf4j.LoggerFactory.getLogger;
90
91/**
Andrea Campanellae4084402017-12-15 15:27:31 +010092 * Manager to troubleshoot packets inside the network.
93 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
94 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080095 */
96@Service
97@Component(immediate = true)
98public class TroubleshootManager implements TroubleshootService {
99
100 private static final Logger log = getLogger(TroubleshootManager.class);
101
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100102 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
103
Simon Hunt026a2872017-11-13 17:09:43 -0800104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected FlowRuleService flowRuleService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected GroupService groupService;
109
Andrea Campanellae4084402017-12-15 15:27:31 +0100110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -0800112
Andrea Campanellae4084402017-12-15 15:27:31 +0100113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected HostService hostService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -0800118
Andrea Campanella17d45192018-01-18 17:11:42 +0100119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120 protected DeviceService deviceService;
121
Andrea Campanella54923d62018-01-23 12:46:04 +0100122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected MastershipService mastershipService;
124
Andrea Campanella55c3f422018-02-08 17:10:11 +0100125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected NetworkConfigService networkConfigService;
127
128 @Override
129 public StaticPacketTrace trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
130 Host source = hostService.getHost(sourceHost);
131 Host destination = hostService.getHost(destinationHost);
132
133 //Temporary trace to fail in case we don't have neough information or what is provided is incoherent
134 StaticPacketTrace failTrace = new StaticPacketTrace(null, null);
135
136 if (source == null) {
137 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
138 return failTrace;
139 }
140
141 if (destination == null) {
142 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
143 return failTrace;
144 }
145
146 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
147 .matchInPort(source.location().port())
148 .matchEthType(etherType.ethType().toShort())
149 .matchEthDst(source.mac())
150 .matchVlanId(source.vlan());
151
152
Andrea Campanella55c3f422018-02-08 17:10:11 +0100153 try {
154 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
155 // we are under same leaf so it's L2 Unicast.
156 if (areBridged(source, destination)) {
157 selectorBuilder.matchEthDst(destination.mac());
158 return trace(selectorBuilder.build(), source.location());
159 }
160
161 //handle the IPs for src and dst in case of L3
162 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
163
164 //Match on the source IP
165 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
166 return failTrace;
167 }
168
169 //Match on destination IP
170 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
171 return failTrace;
172 }
173
174 } else {
175 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
176 "please use packet based");
177 return failTrace;
178 }
179
180 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
181 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
182 .deviceId(), SegmentRoutingDeviceConfig.class);
183 if (segmentRoutingConfig != null) {
184 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
185 } else {
186 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
187 " router MAC from segment routing config can't perform L3 tracing.");
188 }
189
190 return trace(selectorBuilder.build(), source.location());
191
192 } catch (ConfigException e) {
193 failTrace.addResultMessage("Can't get config " + e.getMessage());
194 return failTrace;
195 }
196 }
197
198 /**
199 * Matches src and dst IPs based on host information.
200 *
201 * @param host the host
202 * @param failTrace the trace to use in case of failure
203 * @param selectorBuilder the packet we are building to trace
204 * @param etherType the traffic type
205 * @param src is this src host or dst host
206 * @return true if properly matched
207 */
208 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
209 EtherType etherType, boolean src) {
210 List<IpAddress> ips = host.ipAddresses().stream().filter(ipAddress -> {
211 if (etherType.equals(EtherType.IPV4)) {
212 return ipAddress.isIp4() && !ipAddress.isLinkLocal();
213 } else if (etherType.equals(EtherType.IPV6)) {
214 return ipAddress.isIp6() && !ipAddress.isLinkLocal();
215 }
216 return false;
217 }).collect(Collectors.toList());
218
219 if (ips.size() > 0) {
Andrea Campanellabf8d9302018-02-21 14:43:21 +0100220 if (etherType.equals(EtherType.IPV4)) {
221 if (src) {
222 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
223 } else {
224 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
225 }
226 } else if (etherType.equals(EtherType.IPV6)) {
227 if (src) {
228 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
229 } else {
230 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
231 }
Andrea Campanella55c3f422018-02-08 17:10:11 +0100232 }
233 } else {
234 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
235 return false;
236 }
237 return true;
238 }
239
240 /**
241 * Checks that two hosts are bridged (L2Unicast).
242 *
243 * @param source the source host
244 * @param destination the destination host
245 * @return true if bridged.
246 * @throws ConfigException if config can't be properly retrieved
247 */
248 private boolean areBridged(Host source, Host destination) throws ConfigException {
249
250 //If the location is not the same we don't even check vlan or subnets
251 if (!source.location().deviceId().equals(destination.location().deviceId())) {
252 return false;
253 }
254
255 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
256 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
257 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
258
259 //following can be optimized but for clarity is left as is
260 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
261 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
262
263 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
264 return false;
265 }
266
267 if (!intfH1.vlanTagged().equals(intfH2.vlanTagged())) {
268 return false;
269 }
270
271 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
272 return false;
273 }
274
275 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
276 intersection.retainAll(intfH2.ipAddressesList());
277 if (intersection.size() == 0) {
278 return false;
279 }
280 }
281 return true;
282 }
283
Simon Hunt026a2872017-11-13 17:09:43 -0800284 @Override
285 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100286 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100287 //device must exist in ONOS
288 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
289 "Device " + in.deviceId() + " must exist in ONOS");
290
Andrea Campanellae4084402017-12-15 15:27:31 +0100291 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
292 //FIXME this can be done recursively
293 trace = traceInDevice(trace, packet, in);
294 //Building output connect Points
295 List<ConnectPoint> path = new ArrayList<>();
296 trace = getTrace(path, in, trace);
297 return trace;
298 }
299
300 /**
301 * Computes a trace for a give packet that start in the network at the given connect point.
302 *
303 * @param completePath the path traversed by the packet
304 * @param in the input connect point
305 * @param trace the trace to build
306 * @return the build trace for that packet.
307 */
308 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
309
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100310 log.debug("------------------------------------------------------------");
311
Andrea Campanellae4084402017-12-15 15:27:31 +0100312 //if the trace already contains the input connect point there is a loop
313 if (pathContainsDevice(completePath, in.deviceId())) {
314 trace.addResultMessage("Loop encountered in device " + in.deviceId());
315 return trace;
316 }
317
318 //let's add the input connect point
319 completePath.add(in);
320
321 //If the trace has no outputs for the given input we stop here
322 if (trace.getGroupOuputs(in.deviceId()) == null) {
323 computePath(completePath, trace, null);
324 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
325 return trace;
326 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100327
Andrea Campanellae4084402017-12-15 15:27:31 +0100328 //If the trace has ouputs we analyze them all
329 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100330
331 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100332 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100333 log.debug("Output path {}", cp);
334
Andrea Campanellae4084402017-12-15 15:27:31 +0100335 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100336 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100337 //Hosts queried from the original ip or mac
338 Set<Host> hosts = getHosts(trace);
339
340 //If the two host collections contain the same item it means we reached the proper output
341 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100342 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella54923d62018-01-23 12:46:04 +0100343 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100344 computePath(completePath, trace, outputPath.getOutput());
345 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100346 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100347
Andrea Campanella54923d62018-01-23 12:46:04 +0100348 //Getting the master when the packet gets sent as packet in
349 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100350 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella54923d62018-01-23 12:46:04 +0100351 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100352 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100353
Andrea Campanella8292ba62018-01-31 16:43:23 +0100354 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100355
356 //TODO this can be optimized if we use a Tree structure for paths.
357 //if we already have outputs let's check if the one we are considering starts from one of the devices
358 // in any of the ones we have.
359 if (trace.getCompletePaths().size() > 0) {
360 ConnectPoint inputForOutput = null;
361 List<ConnectPoint> previousPath = new ArrayList<>();
362 for (List<ConnectPoint> path : trace.getCompletePaths()) {
363 for (ConnectPoint connect : path) {
364 //if the path already contains the input for the output we've found we use it
365 if (connect.equals(in)) {
366 inputForOutput = connect;
367 previousPath = path;
368 break;
369 }
370 }
371 }
372 //we use the pre-existing path up to the point we fork to a new output
373 if (inputForOutput != null && completePath.contains(inputForOutput)) {
374 List<ConnectPoint> temp = new ArrayList<>(previousPath);
375 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
376 }
377 }
378
Andrea Campanellae4084402017-12-15 15:27:31 +0100379 //let's add the ouput for the input
380 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100381 //let's compute the links for the given output
382 Set<Link> links = linkService.getEgressLinks(cp);
383 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100384 //For each link we trace the corresponding device
385 for (Link link : links) {
386 ConnectPoint dst = link.dst();
387 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100388 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100389 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
390 updatedPacket.add(Criteria.matchInPort(dst.port()));
391 log.debug("DST Connect Point {}", dst);
392 //build the elements for that device
393 traceInDevice(trace, updatedPacket.build(), dst);
394 //continue the trace along the path
395 getTrace(completePath, dst, trace);
396 }
397
Andrea Campanella8292ba62018-01-31 16:43:23 +0100398 } else if (deviceService.getPort(cp).isEnabled()) {
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100399 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
400 .getCriterion(Criterion.Type.ETH_TYPE);
401 //We treat as correct output only if it's not LLDP or BDDP
402 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
403 || !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
404 if (hostsList.isEmpty()) {
405 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
406 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
407 cp + " with no hosts connected ");
408 } else {
409 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
410 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
411 cp + " with hosts " + hostsList);
412 }
413 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella8292ba62018-01-31 16:43:23 +0100414 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100415
416 } else {
417 //No links means that the packet gets dropped.
418 log.warn("No links out of {}", cp);
419 computePath(completePath, trace, cp);
420 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanellae4084402017-12-15 15:27:31 +0100421 }
422 }
423 return trace;
424 }
425
426 /**
Andrea Campanellae6798012018-02-06 15:46:52 +0100427 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
428 * If ONOS applied a vlan we remove it.
429 *
430 * @param outputPath the output
431 * @param trace the trace we are building
432 */
433 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
434
435 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
436 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
437
438 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
439
440 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
441 //removing the final vlanId
442 finalCriteria.remove(finalVid);
443 Builder packetUpdated = DefaultTrafficSelector.builder();
444 finalCriteria.forEach(packetUpdated::add);
445 //Initial was none so we set it to that
446 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
447 //Update final packet
448 outputPath.setFinalPacket(packetUpdated.build());
449 }
450 }
451
452 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100453 * Checks if the path contains the device.
454 *
455 * @param completePath the path
456 * @param deviceId the device to check
457 * @return true if the path contains the device
458 */
459 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
460 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
461 for (ConnectPoint cp : completePath) {
462 if (cp.deviceId().equals(deviceId)) {
463 return true;
464 }
465 }
466 return false;
467 }
468
469 /**
470 * Gets the hosts for the given initial packet.
471 *
472 * @param trace the trace we are building
473 * @return set of the hosts we are trying to reach
474 */
475 private Set<Host> getHosts(StaticPacketTrace trace) {
476 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
477 .getCriterion(Criterion.Type.IPV4_DST));
478 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
479 .getCriterion(Criterion.Type.IPV6_DST));
480 Set<Host> hosts = new HashSet<>();
481 if (ipv4Criterion != null) {
482 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
483 }
484 if (ipv6Criterion != null) {
485 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
486 }
487 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
488 .getCriterion(Criterion.Type.ETH_DST));
489 if (ethCriterion != null) {
490 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
491 }
492 return hosts;
493 }
494
495 /**
496 * Computes the list of traversed connect points.
497 *
498 * @param completePath the list of devices
499 * @param trace the trace we are building
500 * @param output the final output connect point
501 */
502 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
503 List<ConnectPoint> traverseList = new ArrayList<>();
504 if (!completePath.contains(trace.getInitialConnectPoint())) {
505 traverseList.add(trace.getInitialConnectPoint());
506 }
507 traverseList.addAll(completePath);
508 if (output != null && !completePath.contains(output)) {
509 traverseList.add(output);
510 }
511 trace.addCompletePath(traverseList);
Andrea Campanellae4084402017-12-15 15:27:31 +0100512 }
513
514 /**
515 * Traces the packet inside a device starting from an input connect point.
516 *
517 * @param trace the trace we are building
518 * @param packet the packet we are tracing
519 * @param in the input connect point.
520 * @return updated trace
521 */
522 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100523
524 //we already traversed this device.
525 if (trace.getGroupOuputs(in.deviceId()) != null) {
526 log.debug("Trace already contains device and given outputs");
527 return trace;
528 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100529 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100530
531 //if device is not available exit here.
532 if (!deviceService.isAvailable(in.deviceId())) {
533 trace.addResultMessage("Device is offline " + in.deviceId());
534 return trace;
535 }
536
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100537 //handle when the input is the controller
538 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
539 // a packet in from the controller will not actually traverse the pipeline and have no such notion
540 // as the input port.
541 if (in.port().equals(PortNumber.CONTROLLER)) {
542 StaticPacketTrace outputTrace = inputFromController(trace, in);
543 if (outputTrace != null) {
544 return trace;
545 }
546 }
547
Andrea Campanellae4084402017-12-15 15:27:31 +0100548 List<FlowEntry> flows = new ArrayList<>();
549 List<FlowEntry> outputFlows = new ArrayList<>();
550
Andrea Campanella8292ba62018-01-31 16:43:23 +0100551 List<Instruction> deferredInstructions = new ArrayList<>();
552
Andrea Campanellae4084402017-12-15 15:27:31 +0100553 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
554 if (nextTableIdEntry == null) {
555 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
556 return trace;
557 }
558 TableId tableId = nextTableIdEntry.table();
559 FlowEntry flowEntry;
560 boolean output = false;
561 while (!output) {
562 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
563 //get the rule that matches the incoming packet
564 flowEntry = matchHighestPriority(packet, in, tableId);
565 log.debug("Found Flow Entry {}", flowEntry);
566
567 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
568 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
569
570 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
571 if (flowEntry == null && isOfdpaHardware) {
572 log.debug("Ofdpa Hw setup, no flow rule means table miss");
573
Andrea Campanellae4084402017-12-15 15:27:31 +0100574 if (((IndexTableId) tableId).id() == 27) {
575 //Apparently a miss but Table 27 on OFDPA is a fixed table
576 packet = handleOfdpa27FixedTable(trace, packet);
577 }
578
579 //Finding next table to go In case of miss
580 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
581 log.debug("Next table id entry {}", nextTableIdEntry);
582
583 //FIXME find better solution that enable granularity greater than 0 or all rules
584 //(another possibility is max tableId)
585 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100586 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellae4084402017-12-15 15:27:31 +0100587 return trace;
588
589 } else if (nextTableIdEntry == null) {
590 //Means that no more flow rules are present
591 output = true;
592
593 } else if (((IndexTableId) tableId).id() == 20) {
594 //if the table is 20 OFDPA skips to table 50
595 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
596 tableId = IndexTableId.of(50);
597
598 } else {
599 tableId = nextTableIdEntry.table();
600 }
601
Andrea Campanellae4084402017-12-15 15:27:31 +0100602 } else if (flowEntry == null) {
603 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
604 in.deviceId() + ". Dropping");
605 return trace;
606 } else {
Andrea Campanella94c594a2018-02-06 18:58:40 +0100607
Andrea Campanellae4084402017-12-15 15:27:31 +0100608 //IF the table has a transition
609 if (flowEntry.treatment().tableTransition() != null) {
610 //update the next table we transitions to
611 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
612 log.debug("Flow Entry has transition to table Id {}", tableId);
613 flows.add(flowEntry);
614 } else {
615 //table has no transition so it means that it's an output rule if on the last table
616 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
617 flows.add(flowEntry);
618 outputFlows.add(flowEntry);
619 output = true;
620 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100621 //update the packet according to the immediate actions of this flow rule.
622 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
623
624 //save the deferred rules for later
625 deferredInstructions.addAll(flowEntry.treatment().deferred());
626
627 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
628 if (flowEntry.treatment().clearedDeferred()) {
629 deferredInstructions.clear();
630 }
631
Andrea Campanella94c594a2018-02-06 18:58:40 +0100632 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
633 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
634
635 //Let's get the packet vlanId instruction
636 VlanIdCriterion packetVlanIdCriterion =
637 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
638
639 //Let's get the flow entry vlan mod instructions
640 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
641 .immediate().stream()
642 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
643 .findFirst().orElse(null);
644
645 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
646 // is a flow rule that matches on same criteria and with updated vlanId
647 if (entryModVlanIdInstruction != null) {
648
649 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
650 packetVlanIdCriterion, entryModVlanIdInstruction);
651
652 //We found the flow that we expected
653 if (secondVlanFlow != null) {
654 flows.add(secondVlanFlow);
655 } else {
656 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
657 return trace;
658 }
659 }
660
661 }
662
Andrea Campanellae4084402017-12-15 15:27:31 +0100663 }
664 }
665
666 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100667 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100668 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100669
Andrea Campanellae4084402017-12-15 15:27:31 +0100670 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100671 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100672
Andrea Campanellae4084402017-12-15 15:27:31 +0100673 List<PortNumber> outputPorts = new ArrayList<>();
674
Andrea Campanella54923d62018-01-23 12:46:04 +0100675 //TODO optimization
Andrea Campanella54923d62018-01-23 12:46:04 +0100676 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
677 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
678 .allInstructions().stream().filter(instruction -> instruction.type()
679 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100680
Andrea Campanella54923d62018-01-23 12:46:04 +0100681 if (outputFlowEntries.size() > 1) {
682 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
683 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100684 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100685
686 if (outputFlowEntries.size() == 1) {
687
688 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
689 .allInstructions().stream()
690 .filter(instruction -> {
691 return instruction.type().equals(Instruction.Type.OUTPUT);
692 }).findFirst().get();
693
694 //FIXME using GroupsInDevice for output even if flows.
695 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
696
697 }
698 log.debug("Handling Groups");
699 //Analyze Groups
700 List<Group> groups = new ArrayList<>();
701
702 Collection<FlowEntry> nonOutputFlows = flows;
703 nonOutputFlows.removeAll(outputFlowEntries);
704
Andrea Campanella8292ba62018-01-31 16:43:23 +0100705 //Handling groups pointed at by immediate instructions
Andrea Campanella54923d62018-01-23 12:46:04 +0100706 for (FlowEntry entry : flows) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100707 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanella54923d62018-01-23 12:46:04 +0100708 entry.deviceId(), builder, outputPorts, in);
709 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100710
711 //If we have deferred instructions at this point we handle them.
712 if (deferredInstructions.size() > 0) {
713 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
714
715 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100716 packet = builder.build();
Andrea Campanella54923d62018-01-23 12:46:04 +0100717
Andrea Campanella94c594a2018-02-06 18:58:40 +0100718 log.debug("Output Packet {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100719 return trace;
720 }
721
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100722 /**
723 * Handles the specific case where the Input is the controller.
724 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
725 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
726 * the flood to all active physical ports of the device.
727 *
728 * @param trace the trace
729 * @param in the controller port
730 * @return the augmented trace.
731 */
732 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
733 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
734 .getCriterion(Criterion.Type.ETH_TYPE);
735 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
736 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
737 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
738 //get the active ports
739 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
740 .filter(Port::isEnabled)
741 .collect(Collectors.toList());
742 //build an output from each one
743 enabledPorts.forEach(port -> {
744 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
745 ImmutableList.of(), trace.getInitialPacket());
746 trace.addGroupOutputPath(in.deviceId(), output);
747 });
748 return trace;
749 }
750 return null;
751 }
752
Andrea Campanella94c594a2018-02-06 18:58:40 +0100753 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
754 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
755 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
756 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
757 .vlanId().equals(VlanId.NONE);
758 }
759
760 /**
761 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
762 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
763 * second to transition to following table
764 *
765 * @param packet the incoming packet
766 * @param in the input connect point
767 * @param packetVlanIdCriterion the vlan criterion from the packet
768 * @param entryModVlanIdInstruction the entry vlan instruction
769 * @return the second flow entry that matched
770 */
771 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
772 VlanIdCriterion packetVlanIdCriterion,
773 ModVlanIdInstruction entryModVlanIdInstruction) {
774 FlowEntry secondVlanFlow = null;
775 //Check the packet has been update from the first rule.
776 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
777 //find a rule on the same table that matches the vlan and
778 // also all the other elements of the flow such as input port
779 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
780 .stream()
781 .filter(entry -> {
782 return entry.table().equals(IndexTableId.of(10));
783 })
784 .filter(entry -> {
785 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
786 .getCriterion(Criterion.Type.VLAN_VID);
787 return criterion != null && match(packet, entry)
788 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
789 }).findFirst().orElse(null);
790
791 }
792 return secondVlanFlow;
793 }
794
Andrea Campanella8292ba62018-01-31 16:43:23 +0100795
Andrea Campanellae4084402017-12-15 15:27:31 +0100796 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100797 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
798 *
799 * @param packet the incoming packet
800 * @return the updated packet
801 */
802 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
803 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
804 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
805 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
806
807 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
808 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
809 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100810 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100811 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
812 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
813 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100814 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100815 builder.add(ethInstruction);
816 }
817 packet = updatePacket(packet, builder.build()).build();
818 return packet;
819 }
820
821 /**
822 * Finds the flow entry with the minimun next table Id.
823 *
824 * @param deviceId the device to search
825 * @param currentId the current id. the search will use this as minimum
826 * @return the flow entry with the minimum table Id after the given one.
827 */
828 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
829
830 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
831
832 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
833 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
834 }
835
Andrea Campanella8292ba62018-01-31 16:43:23 +0100836 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
837 ConnectPoint in, List<Instruction> deferredInstructions,
838 List<PortNumber> outputPorts, List<Group> groups) {
839
840 //Update the packet with the deferred instructions
841 Builder builder = updatePacket(packet, deferredInstructions);
842
843 //Gather any output instructions from the deferred instruction
844 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
845 return instruction.type().equals(Instruction.Type.OUTPUT);
846 }).collect(Collectors.toList());
847
848 //We are considering deferred instructions from flows, there can only be one output.
849 if (outputFlowInstruction.size() > 1) {
850 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
851 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
852 }
853 //If there is one output let's go through that
854 if (outputFlowInstruction.size() == 1) {
855 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
856 ImmutableList.of());
857 }
858 //If there is no output let's see if there any deferred instruction point to groups.
859 if (outputFlowInstruction.size() == 0) {
860 getGroupsFromInstructions(trace, groups, deferredInstructions,
861 in.deviceId(), builder, outputPorts, in);
862 }
863 return builder;
864 }
865
Andrea Campanellae4084402017-12-15 15:27:31 +0100866 /**
867 * Gets group information from instructions.
868 *
869 * @param trace the trace we are building
870 * @param groupsForDevice the set of groups for this device
871 * @param instructions the set of instructions we are searching for groups.
872 * @param deviceId the device we are considering
873 * @param builder the builder of the input packet
874 * @param outputPorts the output ports for that packet
875 */
876 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
877 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +0100878 Builder builder, List<PortNumber> outputPorts,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100879 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100880 List<Instruction> groupInstructionlist = new ArrayList<>();
881 for (Instruction instruction : instructions) {
882 log.debug("Considering Instruction {}", instruction);
883 //if the instruction is not group we need to update the packet or add the output
884 //to the possible outputs for this packet
885 if (!instruction.type().equals(Instruction.Type.GROUP)) {
886 //if the instruction is not group we need to update the packet or add the output
887 //to the possible outputs for this packet
888 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100889 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella573d4b92018-02-19 17:03:46 +0100890 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
891 //clearing the groups because we start from the top.
892 groupsForDevice.clear();
Andrea Campanellae4084402017-12-15 15:27:31 +0100893 } else {
894 builder = translateInstruction(builder, instruction);
895 }
896 } else {
897 //if the instuction is pointing to a group we need to get the group
898 groupInstructionlist.add(instruction);
899 }
900 }
901 //handle all the internal instructions pointing to a group.
902 for (Instruction instr : groupInstructionlist) {
903 GroupInstruction groupInstruction = (GroupInstruction) instr;
904 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
905 return groupInternal.id().equals(groupInstruction.groupId());
906 }).findAny().orElse(null);
907 if (group == null) {
908 trace.addResultMessage("Null group for Instruction " + instr);
909 break;
910 }
Andrea Campanella573d4b92018-02-19 17:03:46 +0100911
Andrea Campanellae4084402017-12-15 15:27:31 +0100912 //Cycle in each of the group's buckets and add them to the groups for this Device.
913 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella573d4b92018-02-19 17:03:46 +0100914
915 //add the group to the traversed groups
916 if (!groupsForDevice.contains(group)) {
917 groupsForDevice.add(group);
918 }
919
Andrea Campanellae4084402017-12-15 15:27:31 +0100920 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100921 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100922 }
923 }
924 }
925
926 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100927 * Check if the output is the input port, if so adds a dop result message, otherwise builds
928 * a possible output from this device.
929 *
930 * @param trace the trace
931 * @param in the input connect point
932 * @param builder the packet builder
933 * @param outputPorts the list of output ports for this device
934 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +0100935 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100936 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100937 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100938 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
939 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100940 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100941
942 //if the output is the input same we drop, except if the output is the controller
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100943 if (output.equals(in)) {
944 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
945 trace.getInitialConnectPoint());
946 } else {
947 trace.addGroupOutputPath(in.deviceId(),
948 new GroupsInDevice(output, groupsForDevice, builder.build()));
949 outputPorts.add(outputInstruction.port());
950 }
951 }
952
953 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100954 * Applies all give instructions to the input packet.
955 *
956 * @param packet the input packet
957 * @param instructions the set of instructions
958 * @return the packet with the applied instructions
959 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100960 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
961 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100962 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +0100963 //FIXME optimize
964 for (Instruction instruction : instructions) {
965 newSelector = translateInstruction(newSelector, instruction);
966 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100967 return newSelector;
968 }
969
970 /**
971 * Applies an instruction to the packet in the form of a selector.
972 *
973 * @param newSelector the packet selector
974 * @param instruction the instruction to be translated
975 * @return the new selector with the applied instruction
976 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100977 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100978 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +0100979 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +0100980 //TODO add as required
981 Criterion criterion = null;
982 switch (instruction.type()) {
983 case L2MODIFICATION:
984 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
985 switch (l2Instruction.subtype()) {
986 case VLAN_ID:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100987 ModVlanIdInstruction vlanIdInstruction =
988 (ModVlanIdInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100989 VlanId id = vlanIdInstruction.vlanId();
990 criterion = Criteria.matchVlanId(id);
991 break;
992 case VLAN_POP:
993 criterion = Criteria.matchVlanId(VlanId.NONE);
994 break;
995 case MPLS_PUSH:
Andrea Campanella94c594a2018-02-06 18:58:40 +0100996 ModMplsHeaderInstruction mplsEthInstruction =
997 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +0100998 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
999 break;
1000 case MPLS_POP:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001001 ModMplsHeaderInstruction mplsPopInstruction =
1002 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001003 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +01001004
1005 //When popping MPLS we remove label and BOS
1006 TrafficSelector temporaryPacket = newSelector.build();
1007 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001008 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +01001009 temporaryPacket.criteria().stream().filter(c -> {
1010 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1011 !c.type().equals(Criterion.Type.MPLS_BOS);
1012 }).forEach(noMplsSelector::add);
1013 newSelector = noMplsSelector;
1014 }
1015
Andrea Campanellae4084402017-12-15 15:27:31 +01001016 break;
1017 case MPLS_LABEL:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001018 ModMplsLabelInstruction mplsLabelInstruction =
1019 (ModMplsLabelInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001020 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +01001021 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001022 break;
1023 case ETH_DST:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001024 ModEtherInstruction modEtherDstInstruction =
1025 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001026 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1027 break;
1028 case ETH_SRC:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001029 ModEtherInstruction modEtherSrcInstruction =
1030 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001031 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1032 break;
1033 default:
1034 log.debug("Unsupported L2 Instruction");
1035 break;
1036 }
1037 break;
1038 default:
1039 log.debug("Unsupported Instruction");
1040 break;
1041 }
1042 if (criterion != null) {
1043 log.debug("Adding criterion {}", criterion);
1044 newSelector.add(criterion);
1045 }
1046 return newSelector;
1047 }
1048
1049 /**
1050 * Finds the rule in the device that mathces the input packet and has the highest priority.
1051 *
1052 * @param packet the input packet
1053 * @param in the connect point the packet comes in from
1054 * @param tableId the table to search
1055 * @return the flow entry
1056 */
1057 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1058 //Computing the possible match rules.
1059 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
1060 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1061 .stream()
1062 .filter(flowEntry -> {
1063 return flowEntry.table().equals(tableId);
1064 })
1065 .filter(flowEntry -> {
1066 return match(packet, flowEntry);
1067 }).max(comparator).orElse(null);
1068 }
1069
1070 /**
1071 * Matches the packet with the given flow entry.
1072 *
1073 * @param packet the packet to match
1074 * @param flowEntry the flow entry to match the packet against
1075 * @return true if the packet matches the flow.
1076 */
1077 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
1078 //TODO handle MAC matching
1079 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1080 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +01001081 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +01001082 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1083 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
1084 IPCriterion ipCriterion = (IPCriterion) criterion;
1085 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
Andrea Campanella128d9c62018-01-31 12:20:48 +01001086 //if the packet does not have an IPv4 or IPv6 criterion we return false
Andrea Campanellae4084402017-12-15 15:27:31 +01001087 if (matchCriterion == null) {
Andrea Campanella128d9c62018-01-31 12:20:48 +01001088 return false;
Andrea Campanellae4084402017-12-15 15:27:31 +01001089 }
1090 try {
1091 Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
1092 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1093 } catch (UnknownHostException e) {
1094 return false;
1095 }
1096 //we check that the packet contains the criterion provided by the flow rule.
1097 } else {
1098 return packet.criteria().contains(criterion);
1099 }
1100 });
Simon Hunt026a2872017-11-13 17:09:43 -08001101 }
1102}