blob: 4087ec9d41ab481c698d998377398da7be2b5d21 [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;
Andrea Campanella6a614fa2018-02-21 14:28:20 +010022import org.apache.commons.lang3.tuple.Pair;
Simon Hunt026a2872017-11-13 17:09:43 -080023import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.apache.felix.scr.annotations.Service;
Andrea Campanella55c3f422018-02-08 17:10:11 +010027import org.onlab.packet.IpAddress;
Andrea Campanellae4084402017-12-15 15:27:31 +010028import org.onlab.packet.VlanId;
Andrea Campanella54923d62018-01-23 12:46:04 +010029import org.onosproject.cluster.NodeId;
30import org.onosproject.mastership.MastershipService;
Simon Hunt026a2872017-11-13 17:09:43 -080031import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010032import org.onosproject.net.DeviceId;
33import org.onosproject.net.Host;
Andrea Campanella55c3f422018-02-08 17:10:11 +010034import org.onosproject.net.HostId;
Andrea Campanellae4084402017-12-15 15:27:31 +010035import org.onosproject.net.Link;
Andrea Campanella1445f7a2018-02-07 12:00:12 +010036import org.onosproject.net.Port;
Andrea Campanellae4084402017-12-15 15:27:31 +010037import org.onosproject.net.PortNumber;
Andrea Campanella55c3f422018-02-08 17:10:11 +010038import org.onosproject.net.config.ConfigException;
39import org.onosproject.net.config.NetworkConfigService;
40import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella17d45192018-01-18 17:11:42 +010041import org.onosproject.net.device.DeviceService;
Andrea Campanellae4084402017-12-15 15:27:31 +010042import org.onosproject.net.driver.DriverService;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010043import org.onosproject.net.edge.EdgePortService;
Andrea Campanellae4084402017-12-15 15:27:31 +010044import org.onosproject.net.flow.DefaultTrafficSelector;
45import org.onosproject.net.flow.FlowEntry;
46import org.onosproject.net.flow.FlowRule;
Simon Hunt026a2872017-11-13 17:09:43 -080047import org.onosproject.net.flow.FlowRuleService;
Andrea Campanellae4084402017-12-15 15:27:31 +010048import org.onosproject.net.flow.IndexTableId;
49import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080050import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-12-15 15:27:31 +010051import org.onosproject.net.flow.criteria.Criteria;
52import org.onosproject.net.flow.criteria.Criterion;
53import org.onosproject.net.flow.criteria.EthCriterion;
54import org.onosproject.net.flow.criteria.EthTypeCriterion;
55import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanellae6798012018-02-06 15:46:52 +010056import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanellae4084402017-12-15 15:27:31 +010057import org.onosproject.net.flow.instructions.Instruction;
58import org.onosproject.net.flow.instructions.Instructions;
59import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
60import org.onosproject.net.flow.instructions.L2ModificationInstruction;
61import org.onosproject.net.group.Group;
62import org.onosproject.net.group.GroupBucket;
Simon Hunt026a2872017-11-13 17:09:43 -080063import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010064import org.onosproject.net.host.HostService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010065import org.onosproject.net.host.InterfaceIpAddress;
66import org.onosproject.net.intf.Interface;
Andrea Campanellae4084402017-12-15 15:27:31 +010067import org.onosproject.net.link.LinkService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010068import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanellae4084402017-12-15 15:27:31 +010069import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080070import org.onosproject.t3.api.StaticPacketTrace;
71import org.onosproject.t3.api.TroubleshootService;
72import org.slf4j.Logger;
73
Andrea Campanellae4084402017-12-15 15:27:31 +010074import java.net.UnknownHostException;
75import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010076import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010077import java.util.Collections;
78import java.util.Comparator;
79import java.util.HashSet;
80import java.util.List;
81import java.util.Set;
82import java.util.stream.Collectors;
83
84import static org.onlab.packet.EthType.EtherType;
Andrea Campanellae6798012018-02-06 15:46:52 +010085import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanellae4084402017-12-15 15:27:31 +010086import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella94c594a2018-02-06 18:58:40 +010087import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
88import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
89import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
90import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010091import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt026a2872017-11-13 17:09:43 -080092import static org.slf4j.LoggerFactory.getLogger;
93
94/**
Andrea Campanellae4084402017-12-15 15:27:31 +010095 * Manager to troubleshoot packets inside the network.
96 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
97 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080098 */
99@Service
100@Component(immediate = true)
101public class TroubleshootManager implements TroubleshootService {
102
103 private static final Logger log = getLogger(TroubleshootManager.class);
104
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100105 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
106
Simon Hunt026a2872017-11-13 17:09:43 -0800107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected FlowRuleService flowRuleService;
109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected GroupService groupService;
112
Andrea Campanellae4084402017-12-15 15:27:31 +0100113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -0800115
Andrea Campanellae4084402017-12-15 15:27:31 +0100116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected HostService hostService;
118
119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -0800121
Andrea Campanella17d45192018-01-18 17:11:42 +0100122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected DeviceService deviceService;
124
Andrea Campanella54923d62018-01-23 12:46:04 +0100125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected MastershipService mastershipService;
127
Andrea Campanella55c3f422018-02-08 17:10:11 +0100128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected NetworkConfigService networkConfigService;
130
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
132 protected EdgePortService edgePortService;
133
Andrea Campanella55c3f422018-02-08 17:10:11 +0100134 @Override
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100135 public List<StaticPacketTrace> pingAll(EtherType type) {
136 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
137 hostService.getHosts().forEach(host -> {
138 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
139 if (ipAddresses.size() > 0) {
140 hostService.getHosts().forEach(hostToPing -> {
141 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
142 if (ipAddressesToPing.size() > 0 && !host.equals(hostToPing)) {
143 tracesBuilder.add(trace(host.id(), hostToPing.id(), type));
144 }
145 });
146 }
147 });
148 return tracesBuilder.build();
149 }
150
151 @Override
Andrea Campanella55c3f422018-02-08 17:10:11 +0100152 public StaticPacketTrace trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
153 Host source = hostService.getHost(sourceHost);
154 Host destination = hostService.getHost(destinationHost);
155
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100156 //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
157 StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
Andrea Campanella55c3f422018-02-08 17:10:11 +0100158
159 if (source == null) {
160 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
161 return failTrace;
162 }
163
164 if (destination == null) {
165 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
166 return failTrace;
167 }
168
169 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
170 .matchInPort(source.location().port())
171 .matchEthType(etherType.ethType().toShort())
172 .matchEthDst(source.mac())
173 .matchVlanId(source.vlan());
174
175
Andrea Campanella55c3f422018-02-08 17:10:11 +0100176 try {
177 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
178 // we are under same leaf so it's L2 Unicast.
179 if (areBridged(source, destination)) {
180 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100181 StaticPacketTrace trace = trace(selectorBuilder.build(), source.location());
182 trace.addEndpointHosts(Pair.of(source, destination));
183 return trace;
Andrea Campanella55c3f422018-02-08 17:10:11 +0100184 }
185
186 //handle the IPs for src and dst in case of L3
187 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
188
189 //Match on the source IP
190 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
191 return failTrace;
192 }
193
194 //Match on destination IP
195 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
196 return failTrace;
197 }
198
199 } else {
200 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
201 "please use packet based");
202 return failTrace;
203 }
204
205 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
206 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
207 .deviceId(), SegmentRoutingDeviceConfig.class);
208 if (segmentRoutingConfig != null) {
209 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
210 } else {
211 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
212 " router MAC from segment routing config can't perform L3 tracing.");
213 }
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100214 StaticPacketTrace trace = trace(selectorBuilder.build(), source.location());
215 trace.addEndpointHosts(Pair.of(source, destination));
216 return trace;
Andrea Campanella55c3f422018-02-08 17:10:11 +0100217
218 } catch (ConfigException e) {
219 failTrace.addResultMessage("Can't get config " + e.getMessage());
220 return failTrace;
221 }
222 }
223
224 /**
225 * Matches src and dst IPs based on host information.
226 *
227 * @param host the host
228 * @param failTrace the trace to use in case of failure
229 * @param selectorBuilder the packet we are building to trace
230 * @param etherType the traffic type
231 * @param src is this src host or dst host
232 * @return true if properly matched
233 */
234 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
235 EtherType etherType, boolean src) {
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100236 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100237
238 if (ips.size() > 0) {
Andrea Campanellabf8d9302018-02-21 14:43:21 +0100239 if (etherType.equals(EtherType.IPV4)) {
240 if (src) {
241 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
242 } else {
243 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
244 }
245 } else if (etherType.equals(EtherType.IPV6)) {
246 if (src) {
247 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
248 } else {
249 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
250 }
Andrea Campanella55c3f422018-02-08 17:10:11 +0100251 }
252 } else {
253 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
254 return false;
255 }
256 return true;
257 }
258
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100259 private List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
260 return host.ipAddresses().stream().filter(ipAddress -> {
261 boolean correctIp = false;
262 if (etherType.equals(EtherType.IPV4)) {
263 correctIp = ipAddress.isIp4();
264 } else if (etherType.equals(EtherType.IPV6)) {
265 correctIp = ipAddress.isIp6();
266 }
267 if (checklocal) {
268 correctIp = correctIp && !ipAddress.isLinkLocal();
269 }
270 return correctIp;
271 }).collect(Collectors.toList());
272 }
273
Andrea Campanella55c3f422018-02-08 17:10:11 +0100274 /**
275 * Checks that two hosts are bridged (L2Unicast).
276 *
277 * @param source the source host
278 * @param destination the destination host
279 * @return true if bridged.
280 * @throws ConfigException if config can't be properly retrieved
281 */
282 private boolean areBridged(Host source, Host destination) throws ConfigException {
283
284 //If the location is not the same we don't even check vlan or subnets
285 if (!source.location().deviceId().equals(destination.location().deviceId())) {
286 return false;
287 }
288
289 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
290 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
291 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
292
293 //following can be optimized but for clarity is left as is
294 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
295 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
296
297 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
298 return false;
299 }
300
301 if (!intfH1.vlanTagged().equals(intfH2.vlanTagged())) {
302 return false;
303 }
304
305 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
306 return false;
307 }
308
309 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
310 intersection.retainAll(intfH2.ipAddressesList());
311 if (intersection.size() == 0) {
312 return false;
313 }
314 }
315 return true;
316 }
317
Simon Hunt026a2872017-11-13 17:09:43 -0800318 @Override
319 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100320 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100321 //device must exist in ONOS
322 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
323 "Device " + in.deviceId() + " must exist in ONOS");
324
Andrea Campanellae4084402017-12-15 15:27:31 +0100325 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
326 //FIXME this can be done recursively
327 trace = traceInDevice(trace, packet, in);
328 //Building output connect Points
329 List<ConnectPoint> path = new ArrayList<>();
330 trace = getTrace(path, in, trace);
331 return trace;
332 }
333
334 /**
335 * Computes a trace for a give packet that start in the network at the given connect point.
336 *
337 * @param completePath the path traversed by the packet
338 * @param in the input connect point
339 * @param trace the trace to build
340 * @return the build trace for that packet.
341 */
342 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
343
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100344 log.debug("------------------------------------------------------------");
345
Andrea Campanellae4084402017-12-15 15:27:31 +0100346 //if the trace already contains the input connect point there is a loop
347 if (pathContainsDevice(completePath, in.deviceId())) {
348 trace.addResultMessage("Loop encountered in device " + in.deviceId());
349 return trace;
350 }
351
352 //let's add the input connect point
353 completePath.add(in);
354
355 //If the trace has no outputs for the given input we stop here
356 if (trace.getGroupOuputs(in.deviceId()) == null) {
357 computePath(completePath, trace, null);
358 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
359 return trace;
360 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100361
Andrea Campanellae4084402017-12-15 15:27:31 +0100362 //If the trace has ouputs we analyze them all
363 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100364
365 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100366 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100367 log.debug("Output path {}", cp);
368
Andrea Campanellae4084402017-12-15 15:27:31 +0100369 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100370 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100371 //Hosts queried from the original ip or mac
372 Set<Host> hosts = getHosts(trace);
373
374 //If the two host collections contain the same item it means we reached the proper output
375 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100376 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella54923d62018-01-23 12:46:04 +0100377 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100378 computePath(completePath, trace, outputPath.getOutput());
379 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100380 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100381
Andrea Campanella54923d62018-01-23 12:46:04 +0100382 //Getting the master when the packet gets sent as packet in
383 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100384 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella54923d62018-01-23 12:46:04 +0100385 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100386 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100387
Andrea Campanella8292ba62018-01-31 16:43:23 +0100388 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100389
390 //TODO this can be optimized if we use a Tree structure for paths.
391 //if we already have outputs let's check if the one we are considering starts from one of the devices
392 // in any of the ones we have.
393 if (trace.getCompletePaths().size() > 0) {
394 ConnectPoint inputForOutput = null;
395 List<ConnectPoint> previousPath = new ArrayList<>();
396 for (List<ConnectPoint> path : trace.getCompletePaths()) {
397 for (ConnectPoint connect : path) {
398 //if the path already contains the input for the output we've found we use it
399 if (connect.equals(in)) {
400 inputForOutput = connect;
401 previousPath = path;
402 break;
403 }
404 }
405 }
406 //we use the pre-existing path up to the point we fork to a new output
407 if (inputForOutput != null && completePath.contains(inputForOutput)) {
408 List<ConnectPoint> temp = new ArrayList<>(previousPath);
409 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
410 }
411 }
412
Andrea Campanellae4084402017-12-15 15:27:31 +0100413 //let's add the ouput for the input
414 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100415 //let's compute the links for the given output
416 Set<Link> links = linkService.getEgressLinks(cp);
417 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100418 //For each link we trace the corresponding device
419 for (Link link : links) {
420 ConnectPoint dst = link.dst();
421 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100422 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100423 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
424 updatedPacket.add(Criteria.matchInPort(dst.port()));
425 log.debug("DST Connect Point {}", dst);
426 //build the elements for that device
427 traceInDevice(trace, updatedPacket.build(), dst);
428 //continue the trace along the path
429 getTrace(completePath, dst, trace);
430 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100431 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
432 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
433 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
434 .mac().isMulticast()) {
435 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
436 " which is enabled and is edge port");
437 computePath(completePath, trace, outputPath.getOutput());
438 completePath.clear();
439 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
440 return trace;
441 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100442 } else if (deviceService.getPort(cp).isEnabled()) {
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100443 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
444 .getCriterion(Criterion.Type.ETH_TYPE);
445 //We treat as correct output only if it's not LLDP or BDDP
446 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
447 || !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
448 if (hostsList.isEmpty()) {
449 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
450 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
451 cp + " with no hosts connected ");
452 } else {
453 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
454 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
455 cp + " with hosts " + hostsList);
456 }
457 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella8292ba62018-01-31 16:43:23 +0100458 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100459
460 } else {
461 //No links means that the packet gets dropped.
462 log.warn("No links out of {}", cp);
463 computePath(completePath, trace, cp);
464 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanellae4084402017-12-15 15:27:31 +0100465 }
466 }
467 return trace;
468 }
469
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100470
Andrea Campanellae4084402017-12-15 15:27:31 +0100471 /**
Andrea Campanellae6798012018-02-06 15:46:52 +0100472 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
473 * If ONOS applied a vlan we remove it.
474 *
475 * @param outputPath the output
476 * @param trace the trace we are building
477 */
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100478
Andrea Campanellae6798012018-02-06 15:46:52 +0100479 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
480
481 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
482 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
483
484 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
485
486 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
487 //removing the final vlanId
488 finalCriteria.remove(finalVid);
489 Builder packetUpdated = DefaultTrafficSelector.builder();
490 finalCriteria.forEach(packetUpdated::add);
491 //Initial was none so we set it to that
492 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
493 //Update final packet
494 outputPath.setFinalPacket(packetUpdated.build());
495 }
496 }
497
498 /**
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100499 * Checks if the device has other outputs than the given connect point.
500 *
501 * @param inDeviceId the device
502 * @param trace the trace we are building
503 * @param cp an output connect point
504 * @return true if the device has other outputs.
505 */
506 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
507 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
508 return !groupsInDevice.getOutput().equals(cp);
509 }).count() > 0;
510 }
511
512 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100513 * Checks if the path contains the device.
514 *
515 * @param completePath the path
516 * @param deviceId the device to check
517 * @return true if the path contains the device
518 */
519 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
520 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
521 for (ConnectPoint cp : completePath) {
522 if (cp.deviceId().equals(deviceId)) {
523 return true;
524 }
525 }
526 return false;
527 }
528
529 /**
530 * Gets the hosts for the given initial packet.
531 *
532 * @param trace the trace we are building
533 * @return set of the hosts we are trying to reach
534 */
535 private Set<Host> getHosts(StaticPacketTrace trace) {
536 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
537 .getCriterion(Criterion.Type.IPV4_DST));
538 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
539 .getCriterion(Criterion.Type.IPV6_DST));
540 Set<Host> hosts = new HashSet<>();
541 if (ipv4Criterion != null) {
542 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
543 }
544 if (ipv6Criterion != null) {
545 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
546 }
547 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
548 .getCriterion(Criterion.Type.ETH_DST));
549 if (ethCriterion != null) {
550 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
551 }
552 return hosts;
553 }
554
555 /**
556 * Computes the list of traversed connect points.
557 *
558 * @param completePath the list of devices
559 * @param trace the trace we are building
560 * @param output the final output connect point
561 */
562 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
563 List<ConnectPoint> traverseList = new ArrayList<>();
564 if (!completePath.contains(trace.getInitialConnectPoint())) {
565 traverseList.add(trace.getInitialConnectPoint());
566 }
567 traverseList.addAll(completePath);
568 if (output != null && !completePath.contains(output)) {
569 traverseList.add(output);
570 }
571 trace.addCompletePath(traverseList);
Andrea Campanellae4084402017-12-15 15:27:31 +0100572 }
573
574 /**
575 * Traces the packet inside a device starting from an input connect point.
576 *
577 * @param trace the trace we are building
578 * @param packet the packet we are tracing
579 * @param in the input connect point.
580 * @return updated trace
581 */
582 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100583
584 //we already traversed this device.
585 if (trace.getGroupOuputs(in.deviceId()) != null) {
586 log.debug("Trace already contains device and given outputs");
587 return trace;
588 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100589 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100590
591 //if device is not available exit here.
592 if (!deviceService.isAvailable(in.deviceId())) {
593 trace.addResultMessage("Device is offline " + in.deviceId());
594 return trace;
595 }
596
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100597 //handle when the input is the controller
598 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
599 // a packet in from the controller will not actually traverse the pipeline and have no such notion
600 // as the input port.
601 if (in.port().equals(PortNumber.CONTROLLER)) {
602 StaticPacketTrace outputTrace = inputFromController(trace, in);
603 if (outputTrace != null) {
604 return trace;
605 }
606 }
607
Andrea Campanellae4084402017-12-15 15:27:31 +0100608 List<FlowEntry> flows = new ArrayList<>();
609 List<FlowEntry> outputFlows = new ArrayList<>();
610
Andrea Campanella8292ba62018-01-31 16:43:23 +0100611 List<Instruction> deferredInstructions = new ArrayList<>();
612
Andrea Campanellae4084402017-12-15 15:27:31 +0100613 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
614 if (nextTableIdEntry == null) {
615 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
616 return trace;
617 }
618 TableId tableId = nextTableIdEntry.table();
619 FlowEntry flowEntry;
620 boolean output = false;
621 while (!output) {
622 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
623 //get the rule that matches the incoming packet
624 flowEntry = matchHighestPriority(packet, in, tableId);
625 log.debug("Found Flow Entry {}", flowEntry);
626
627 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
628 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
629
630 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
631 if (flowEntry == null && isOfdpaHardware) {
632 log.debug("Ofdpa Hw setup, no flow rule means table miss");
633
Andrea Campanellae4084402017-12-15 15:27:31 +0100634 if (((IndexTableId) tableId).id() == 27) {
635 //Apparently a miss but Table 27 on OFDPA is a fixed table
636 packet = handleOfdpa27FixedTable(trace, packet);
637 }
638
639 //Finding next table to go In case of miss
640 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
641 log.debug("Next table id entry {}", nextTableIdEntry);
642
643 //FIXME find better solution that enable granularity greater than 0 or all rules
644 //(another possibility is max tableId)
645 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100646 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellae4084402017-12-15 15:27:31 +0100647 return trace;
648
649 } else if (nextTableIdEntry == null) {
650 //Means that no more flow rules are present
651 output = true;
652
653 } else if (((IndexTableId) tableId).id() == 20) {
654 //if the table is 20 OFDPA skips to table 50
655 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
656 tableId = IndexTableId.of(50);
657
658 } else {
659 tableId = nextTableIdEntry.table();
660 }
661
Andrea Campanellae4084402017-12-15 15:27:31 +0100662 } else if (flowEntry == null) {
663 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
664 in.deviceId() + ". Dropping");
665 return trace;
666 } else {
Andrea Campanella94c594a2018-02-06 18:58:40 +0100667
Andrea Campanellae4084402017-12-15 15:27:31 +0100668 //IF the table has a transition
669 if (flowEntry.treatment().tableTransition() != null) {
670 //update the next table we transitions to
671 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
672 log.debug("Flow Entry has transition to table Id {}", tableId);
673 flows.add(flowEntry);
674 } else {
675 //table has no transition so it means that it's an output rule if on the last table
676 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
677 flows.add(flowEntry);
678 outputFlows.add(flowEntry);
679 output = true;
680 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100681 //update the packet according to the immediate actions of this flow rule.
682 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
683
684 //save the deferred rules for later
685 deferredInstructions.addAll(flowEntry.treatment().deferred());
686
687 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
688 if (flowEntry.treatment().clearedDeferred()) {
689 deferredInstructions.clear();
690 }
691
Andrea Campanella94c594a2018-02-06 18:58:40 +0100692 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
693 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
694
695 //Let's get the packet vlanId instruction
696 VlanIdCriterion packetVlanIdCriterion =
697 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
698
699 //Let's get the flow entry vlan mod instructions
700 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
701 .immediate().stream()
702 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
703 .findFirst().orElse(null);
704
705 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
706 // is a flow rule that matches on same criteria and with updated vlanId
707 if (entryModVlanIdInstruction != null) {
708
709 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
710 packetVlanIdCriterion, entryModVlanIdInstruction);
711
712 //We found the flow that we expected
713 if (secondVlanFlow != null) {
714 flows.add(secondVlanFlow);
715 } else {
716 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
717 return trace;
718 }
719 }
720
721 }
722
Andrea Campanellae4084402017-12-15 15:27:31 +0100723 }
724 }
725
726 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100727 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100728 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100729
Andrea Campanellae4084402017-12-15 15:27:31 +0100730 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100731 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100732
Andrea Campanellae4084402017-12-15 15:27:31 +0100733 List<PortNumber> outputPorts = new ArrayList<>();
734
Andrea Campanella54923d62018-01-23 12:46:04 +0100735 //TODO optimization
Andrea Campanella54923d62018-01-23 12:46:04 +0100736 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
737 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
738 .allInstructions().stream().filter(instruction -> instruction.type()
739 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100740
Andrea Campanella54923d62018-01-23 12:46:04 +0100741 if (outputFlowEntries.size() > 1) {
742 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
743 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100744 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100745
746 if (outputFlowEntries.size() == 1) {
747
748 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
749 .allInstructions().stream()
750 .filter(instruction -> {
751 return instruction.type().equals(Instruction.Type.OUTPUT);
752 }).findFirst().get();
753
754 //FIXME using GroupsInDevice for output even if flows.
755 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
756
757 }
758 log.debug("Handling Groups");
759 //Analyze Groups
760 List<Group> groups = new ArrayList<>();
761
762 Collection<FlowEntry> nonOutputFlows = flows;
763 nonOutputFlows.removeAll(outputFlowEntries);
764
Andrea Campanella8292ba62018-01-31 16:43:23 +0100765 //Handling groups pointed at by immediate instructions
Andrea Campanella54923d62018-01-23 12:46:04 +0100766 for (FlowEntry entry : flows) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100767 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanella54923d62018-01-23 12:46:04 +0100768 entry.deviceId(), builder, outputPorts, in);
769 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100770
771 //If we have deferred instructions at this point we handle them.
772 if (deferredInstructions.size() > 0) {
773 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
774
775 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100776 packet = builder.build();
Andrea Campanella54923d62018-01-23 12:46:04 +0100777
Andrea Campanella94c594a2018-02-06 18:58:40 +0100778 log.debug("Output Packet {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100779 return trace;
780 }
781
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100782 /**
783 * Handles the specific case where the Input is the controller.
784 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
785 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
786 * the flood to all active physical ports of the device.
787 *
788 * @param trace the trace
789 * @param in the controller port
790 * @return the augmented trace.
791 */
792 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
793 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
794 .getCriterion(Criterion.Type.ETH_TYPE);
795 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
796 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
797 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
798 //get the active ports
799 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
800 .filter(Port::isEnabled)
801 .collect(Collectors.toList());
802 //build an output from each one
803 enabledPorts.forEach(port -> {
804 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
805 ImmutableList.of(), trace.getInitialPacket());
806 trace.addGroupOutputPath(in.deviceId(), output);
807 });
808 return trace;
809 }
810 return null;
811 }
812
Andrea Campanella94c594a2018-02-06 18:58:40 +0100813 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
814 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
815 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
816 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
817 .vlanId().equals(VlanId.NONE);
818 }
819
820 /**
821 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
822 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
823 * second to transition to following table
824 *
825 * @param packet the incoming packet
826 * @param in the input connect point
827 * @param packetVlanIdCriterion the vlan criterion from the packet
828 * @param entryModVlanIdInstruction the entry vlan instruction
829 * @return the second flow entry that matched
830 */
831 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
832 VlanIdCriterion packetVlanIdCriterion,
833 ModVlanIdInstruction entryModVlanIdInstruction) {
834 FlowEntry secondVlanFlow = null;
835 //Check the packet has been update from the first rule.
836 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
837 //find a rule on the same table that matches the vlan and
838 // also all the other elements of the flow such as input port
839 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
840 .stream()
841 .filter(entry -> {
842 return entry.table().equals(IndexTableId.of(10));
843 })
844 .filter(entry -> {
845 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
846 .getCriterion(Criterion.Type.VLAN_VID);
847 return criterion != null && match(packet, entry)
848 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
849 }).findFirst().orElse(null);
850
851 }
852 return secondVlanFlow;
853 }
854
Andrea Campanella8292ba62018-01-31 16:43:23 +0100855
Andrea Campanellae4084402017-12-15 15:27:31 +0100856 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100857 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
858 *
859 * @param packet the incoming packet
860 * @return the updated packet
861 */
862 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
863 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
864 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
865 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
866
867 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
868 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
869 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100870 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100871 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
872 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
873 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100874 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100875 builder.add(ethInstruction);
876 }
877 packet = updatePacket(packet, builder.build()).build();
878 return packet;
879 }
880
881 /**
882 * Finds the flow entry with the minimun next table Id.
883 *
884 * @param deviceId the device to search
885 * @param currentId the current id. the search will use this as minimum
886 * @return the flow entry with the minimum table Id after the given one.
887 */
888 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
889
890 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
891
892 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
893 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
894 }
895
Andrea Campanella8292ba62018-01-31 16:43:23 +0100896 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
897 ConnectPoint in, List<Instruction> deferredInstructions,
898 List<PortNumber> outputPorts, List<Group> groups) {
899
900 //Update the packet with the deferred instructions
901 Builder builder = updatePacket(packet, deferredInstructions);
902
903 //Gather any output instructions from the deferred instruction
904 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
905 return instruction.type().equals(Instruction.Type.OUTPUT);
906 }).collect(Collectors.toList());
907
908 //We are considering deferred instructions from flows, there can only be one output.
909 if (outputFlowInstruction.size() > 1) {
910 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
911 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
912 }
913 //If there is one output let's go through that
914 if (outputFlowInstruction.size() == 1) {
915 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
916 ImmutableList.of());
917 }
918 //If there is no output let's see if there any deferred instruction point to groups.
919 if (outputFlowInstruction.size() == 0) {
920 getGroupsFromInstructions(trace, groups, deferredInstructions,
921 in.deviceId(), builder, outputPorts, in);
922 }
923 return builder;
924 }
925
Andrea Campanellae4084402017-12-15 15:27:31 +0100926 /**
927 * Gets group information from instructions.
928 *
929 * @param trace the trace we are building
930 * @param groupsForDevice the set of groups for this device
931 * @param instructions the set of instructions we are searching for groups.
932 * @param deviceId the device we are considering
933 * @param builder the builder of the input packet
934 * @param outputPorts the output ports for that packet
935 */
936 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
937 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +0100938 Builder builder, List<PortNumber> outputPorts,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100939 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100940 List<Instruction> groupInstructionlist = new ArrayList<>();
941 for (Instruction instruction : instructions) {
942 log.debug("Considering Instruction {}", instruction);
943 //if the instruction is not group we need to update the packet or add the output
944 //to the possible outputs for this packet
945 if (!instruction.type().equals(Instruction.Type.GROUP)) {
946 //if the instruction is not group we need to update the packet or add the output
947 //to the possible outputs for this packet
948 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100949 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella573d4b92018-02-19 17:03:46 +0100950 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
951 //clearing the groups because we start from the top.
952 groupsForDevice.clear();
Andrea Campanellae4084402017-12-15 15:27:31 +0100953 } else {
954 builder = translateInstruction(builder, instruction);
955 }
956 } else {
957 //if the instuction is pointing to a group we need to get the group
958 groupInstructionlist.add(instruction);
959 }
960 }
961 //handle all the internal instructions pointing to a group.
962 for (Instruction instr : groupInstructionlist) {
963 GroupInstruction groupInstruction = (GroupInstruction) instr;
964 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
965 return groupInternal.id().equals(groupInstruction.groupId());
966 }).findAny().orElse(null);
967 if (group == null) {
968 trace.addResultMessage("Null group for Instruction " + instr);
969 break;
970 }
Andrea Campanella573d4b92018-02-19 17:03:46 +0100971
Andrea Campanellae4084402017-12-15 15:27:31 +0100972 //Cycle in each of the group's buckets and add them to the groups for this Device.
973 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella573d4b92018-02-19 17:03:46 +0100974
975 //add the group to the traversed groups
976 if (!groupsForDevice.contains(group)) {
977 groupsForDevice.add(group);
978 }
979
Andrea Campanellae4084402017-12-15 15:27:31 +0100980 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100981 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100982 }
983 }
984 }
985
986 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100987 * Check if the output is the input port, if so adds a dop result message, otherwise builds
988 * a possible output from this device.
989 *
990 * @param trace the trace
991 * @param in the input connect point
992 * @param builder the packet builder
993 * @param outputPorts the list of output ports for this device
994 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +0100995 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100996 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100997 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100998 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
999 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +01001000 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella1445f7a2018-02-07 12:00:12 +01001001
1002 //if the output is the input same we drop, except if the output is the controller
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001003 if (output.equals(in)) {
1004 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
1005 trace.getInitialConnectPoint());
1006 } else {
1007 trace.addGroupOutputPath(in.deviceId(),
1008 new GroupsInDevice(output, groupsForDevice, builder.build()));
1009 outputPorts.add(outputInstruction.port());
1010 }
1011 }
1012
1013 /**
Andrea Campanellae4084402017-12-15 15:27:31 +01001014 * Applies all give instructions to the input packet.
1015 *
1016 * @param packet the input packet
1017 * @param instructions the set of instructions
1018 * @return the packet with the applied instructions
1019 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001020 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1021 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +01001022 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +01001023 //FIXME optimize
1024 for (Instruction instruction : instructions) {
1025 newSelector = translateInstruction(newSelector, instruction);
1026 }
Andrea Campanellae4084402017-12-15 15:27:31 +01001027 return newSelector;
1028 }
1029
1030 /**
1031 * Applies an instruction to the packet in the form of a selector.
1032 *
1033 * @param newSelector the packet selector
1034 * @param instruction the instruction to be translated
1035 * @return the new selector with the applied instruction
1036 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001037 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001038 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +01001039 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +01001040 //TODO add as required
1041 Criterion criterion = null;
1042 switch (instruction.type()) {
1043 case L2MODIFICATION:
1044 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1045 switch (l2Instruction.subtype()) {
1046 case VLAN_ID:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001047 ModVlanIdInstruction vlanIdInstruction =
1048 (ModVlanIdInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001049 VlanId id = vlanIdInstruction.vlanId();
1050 criterion = Criteria.matchVlanId(id);
1051 break;
1052 case VLAN_POP:
1053 criterion = Criteria.matchVlanId(VlanId.NONE);
1054 break;
1055 case MPLS_PUSH:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001056 ModMplsHeaderInstruction mplsEthInstruction =
1057 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001058 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1059 break;
1060 case MPLS_POP:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001061 ModMplsHeaderInstruction mplsPopInstruction =
1062 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001063 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +01001064
1065 //When popping MPLS we remove label and BOS
1066 TrafficSelector temporaryPacket = newSelector.build();
1067 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001068 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +01001069 temporaryPacket.criteria().stream().filter(c -> {
1070 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1071 !c.type().equals(Criterion.Type.MPLS_BOS);
1072 }).forEach(noMplsSelector::add);
1073 newSelector = noMplsSelector;
1074 }
1075
Andrea Campanellae4084402017-12-15 15:27:31 +01001076 break;
1077 case MPLS_LABEL:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001078 ModMplsLabelInstruction mplsLabelInstruction =
1079 (ModMplsLabelInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001080 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +01001081 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001082 break;
1083 case ETH_DST:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001084 ModEtherInstruction modEtherDstInstruction =
1085 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001086 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1087 break;
1088 case ETH_SRC:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001089 ModEtherInstruction modEtherSrcInstruction =
1090 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001091 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1092 break;
1093 default:
1094 log.debug("Unsupported L2 Instruction");
1095 break;
1096 }
1097 break;
1098 default:
1099 log.debug("Unsupported Instruction");
1100 break;
1101 }
1102 if (criterion != null) {
1103 log.debug("Adding criterion {}", criterion);
1104 newSelector.add(criterion);
1105 }
1106 return newSelector;
1107 }
1108
1109 /**
1110 * Finds the rule in the device that mathces the input packet and has the highest priority.
1111 *
1112 * @param packet the input packet
1113 * @param in the connect point the packet comes in from
1114 * @param tableId the table to search
1115 * @return the flow entry
1116 */
1117 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1118 //Computing the possible match rules.
1119 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
1120 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1121 .stream()
1122 .filter(flowEntry -> {
1123 return flowEntry.table().equals(tableId);
1124 })
1125 .filter(flowEntry -> {
1126 return match(packet, flowEntry);
1127 }).max(comparator).orElse(null);
1128 }
1129
1130 /**
1131 * Matches the packet with the given flow entry.
1132 *
1133 * @param packet the packet to match
1134 * @param flowEntry the flow entry to match the packet against
1135 * @return true if the packet matches the flow.
1136 */
1137 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001138 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1139 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +01001140 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +01001141 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1142 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001143 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanellae4084402017-12-15 15:27:31 +01001144 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001145 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1146 return matchMac(packet, (EthCriterion) criterion, false);
1147 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1148 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001149 } else {
1150 return packet.criteria().contains(criterion);
1151 }
1152 });
Simon Hunt026a2872017-11-13 17:09:43 -08001153 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001154
1155 /**
1156 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1157 *
1158 * @param packet the incoming packet
1159 * @param criterion the criterion to match
1160 * @return true if match
1161 */
1162 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1163 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1164 //if the packet does not have an IPv4 or IPv6 criterion we return true
1165 if (matchCriterion == null) {
1166 return false;
1167 }
1168 try {
1169 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1170 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1171 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1172 } catch (UnknownHostException e) {
1173 return false;
1174 }
1175 }
1176
1177 /**
1178 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1179 *
1180 * @param packet the incoming packet
1181 * @param hitCriterion the criterion to match
1182 * @param dst true if we are checking DST MAC
1183 * @return true if match
1184 */
1185 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1186 //Packet can have only one EthCriterion
1187 EthCriterion matchCriterion;
1188 if (dst) {
1189 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1190 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1191 criterion1.type().equals(Criterion.Type.ETH_DST);
1192 }).findFirst().orElse(null);
1193 } else {
1194 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1195 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1196 criterion1.type().equals(Criterion.Type.ETH_SRC);
1197 }).findFirst().orElse(null);
1198 }
1199 //if the packet does not have an ETH criterion we return true
1200 if (matchCriterion == null) {
1201 return true;
1202 }
1203 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1204 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1205 }
Simon Hunt026a2872017-11-13 17:09:43 -08001206}