blob: 00c2335cba66a20e7feb7256934645b37e8e5b28 [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 Campanella5befe1c2018-02-27 14:50:45 +010022import com.google.common.collect.Sets;
Andrea Campanella6a614fa2018-02-21 14:28:20 +010023import org.apache.commons.lang3.tuple.Pair;
Simon Hunt026a2872017-11-13 17:09:43 -080024import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.apache.felix.scr.annotations.Service;
Andrea Campanella55c3f422018-02-08 17:10:11 +010028import org.onlab.packet.IpAddress;
Andrea Campanellae4084402017-12-15 15:27:31 +010029import org.onlab.packet.VlanId;
Andrea Campanella54923d62018-01-23 12:46:04 +010030import org.onosproject.cluster.NodeId;
31import org.onosproject.mastership.MastershipService;
Simon Hunt026a2872017-11-13 17:09:43 -080032import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010033import org.onosproject.net.DeviceId;
34import org.onosproject.net.Host;
Andrea Campanella55c3f422018-02-08 17:10:11 +010035import org.onosproject.net.HostId;
Andrea Campanellae4084402017-12-15 15:27:31 +010036import org.onosproject.net.Link;
Andrea Campanella1445f7a2018-02-07 12:00:12 +010037import org.onosproject.net.Port;
Andrea Campanellae4084402017-12-15 15:27:31 +010038import org.onosproject.net.PortNumber;
Andrea Campanella55c3f422018-02-08 17:10:11 +010039import org.onosproject.net.config.ConfigException;
40import org.onosproject.net.config.NetworkConfigService;
41import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella17d45192018-01-18 17:11:42 +010042import org.onosproject.net.device.DeviceService;
Andrea Campanellae4084402017-12-15 15:27:31 +010043import org.onosproject.net.driver.DriverService;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010044import org.onosproject.net.edge.EdgePortService;
Andrea Campanellae4084402017-12-15 15:27:31 +010045import org.onosproject.net.flow.DefaultTrafficSelector;
46import org.onosproject.net.flow.FlowEntry;
47import org.onosproject.net.flow.FlowRule;
Simon Hunt026a2872017-11-13 17:09:43 -080048import org.onosproject.net.flow.FlowRuleService;
Andrea Campanellae4084402017-12-15 15:27:31 +010049import org.onosproject.net.flow.IndexTableId;
50import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080051import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-12-15 15:27:31 +010052import org.onosproject.net.flow.criteria.Criteria;
53import org.onosproject.net.flow.criteria.Criterion;
54import org.onosproject.net.flow.criteria.EthCriterion;
55import org.onosproject.net.flow.criteria.EthTypeCriterion;
56import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanellae6798012018-02-06 15:46:52 +010057import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanellae4084402017-12-15 15:27:31 +010058import org.onosproject.net.flow.instructions.Instruction;
59import org.onosproject.net.flow.instructions.Instructions;
60import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
61import org.onosproject.net.flow.instructions.L2ModificationInstruction;
62import org.onosproject.net.group.Group;
63import org.onosproject.net.group.GroupBucket;
Simon Hunt026a2872017-11-13 17:09:43 -080064import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010065import org.onosproject.net.host.HostService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010066import org.onosproject.net.host.InterfaceIpAddress;
67import org.onosproject.net.intf.Interface;
Andrea Campanellae4084402017-12-15 15:27:31 +010068import org.onosproject.net.link.LinkService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010069import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanellae4084402017-12-15 15:27:31 +010070import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080071import org.onosproject.t3.api.StaticPacketTrace;
72import org.onosproject.t3.api.TroubleshootService;
73import org.slf4j.Logger;
74
Andrea Campanellae4084402017-12-15 15:27:31 +010075import java.net.UnknownHostException;
76import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010077import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010078import java.util.Collections;
79import java.util.Comparator;
80import java.util.HashSet;
81import java.util.List;
82import java.util.Set;
83import java.util.stream.Collectors;
84
85import static org.onlab.packet.EthType.EtherType;
Andrea Campanellae6798012018-02-06 15:46:52 +010086import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanellae4084402017-12-15 15:27:31 +010087import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella94c594a2018-02-06 18:58:40 +010088import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
89import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
90import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
91import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010092import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt026a2872017-11-13 17:09:43 -080093import static org.slf4j.LoggerFactory.getLogger;
94
95/**
Andrea Campanellae4084402017-12-15 15:27:31 +010096 * Manager to troubleshoot packets inside the network.
97 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
98 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080099 */
100@Service
101@Component(immediate = true)
102public class TroubleshootManager implements TroubleshootService {
103
104 private static final Logger log = getLogger(TroubleshootManager.class);
105
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100106 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
107
Simon Hunt026a2872017-11-13 17:09:43 -0800108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected FlowRuleService flowRuleService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 protected GroupService groupService;
113
Andrea Campanellae4084402017-12-15 15:27:31 +0100114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -0800116
Andrea Campanellae4084402017-12-15 15:27:31 +0100117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 protected HostService hostService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -0800122
Andrea Campanella17d45192018-01-18 17:11:42 +0100123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 protected DeviceService deviceService;
125
Andrea Campanella54923d62018-01-23 12:46:04 +0100126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
127 protected MastershipService mastershipService;
128
Andrea Campanella55c3f422018-02-08 17:10:11 +0100129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
130 protected NetworkConfigService networkConfigService;
131
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
133 protected EdgePortService edgePortService;
134
Andrea Campanella55c3f422018-02-08 17:10:11 +0100135 @Override
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100136 public List<StaticPacketTrace> pingAll(EtherType type) {
137 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
138 hostService.getHosts().forEach(host -> {
139 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
140 if (ipAddresses.size() > 0) {
Andrea Campanella5befe1c2018-02-27 14:50:45 +0100141 //check if the host has only local IPs of that ETH type
142 boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100143 hostService.getHosts().forEach(hostToPing -> {
144 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella5befe1c2018-02-27 14:50:45 +0100145 //check if the other host has only local IPs of that ETH type
146 boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
147 boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
148 //Trace is done only if they are both local and under the same location
149 // or not local and if they are not the same host.
150 if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
151 (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
152 && !host.equals(hostToPing)) {
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100153 tracesBuilder.add(trace(host.id(), hostToPing.id(), type));
154 }
155 });
156 }
157 });
158 return tracesBuilder.build();
159 }
160
161 @Override
Andrea Campanella55c3f422018-02-08 17:10:11 +0100162 public StaticPacketTrace trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
163 Host source = hostService.getHost(sourceHost);
164 Host destination = hostService.getHost(destinationHost);
165
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100166 //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
167 StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
Andrea Campanella55c3f422018-02-08 17:10:11 +0100168
169 if (source == null) {
170 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
171 return failTrace;
172 }
173
174 if (destination == null) {
175 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
176 return failTrace;
177 }
178
179 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
180 .matchInPort(source.location().port())
181 .matchEthType(etherType.ethType().toShort())
182 .matchEthDst(source.mac())
183 .matchVlanId(source.vlan());
184
185
Andrea Campanella55c3f422018-02-08 17:10:11 +0100186 try {
187 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
188 // we are under same leaf so it's L2 Unicast.
189 if (areBridged(source, destination)) {
190 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100191 StaticPacketTrace trace = trace(selectorBuilder.build(), source.location());
192 trace.addEndpointHosts(Pair.of(source, destination));
193 return trace;
Andrea Campanella55c3f422018-02-08 17:10:11 +0100194 }
195
196 //handle the IPs for src and dst in case of L3
197 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
198
199 //Match on the source IP
200 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
201 return failTrace;
202 }
203
204 //Match on destination IP
205 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
206 return failTrace;
207 }
208
209 } else {
210 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
211 "please use packet based");
212 return failTrace;
213 }
214
215 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
216 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
217 .deviceId(), SegmentRoutingDeviceConfig.class);
218 if (segmentRoutingConfig != null) {
219 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
220 } else {
221 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
222 " router MAC from segment routing config can't perform L3 tracing.");
223 }
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100224 StaticPacketTrace trace = trace(selectorBuilder.build(), source.location());
225 trace.addEndpointHosts(Pair.of(source, destination));
226 return trace;
Andrea Campanella55c3f422018-02-08 17:10:11 +0100227
228 } catch (ConfigException e) {
229 failTrace.addResultMessage("Can't get config " + e.getMessage());
230 return failTrace;
231 }
232 }
233
234 /**
235 * Matches src and dst IPs based on host information.
236 *
237 * @param host the host
238 * @param failTrace the trace to use in case of failure
239 * @param selectorBuilder the packet we are building to trace
240 * @param etherType the traffic type
241 * @param src is this src host or dst host
242 * @return true if properly matched
243 */
244 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
245 EtherType etherType, boolean src) {
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100246 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100247
248 if (ips.size() > 0) {
Andrea Campanellabf8d9302018-02-21 14:43:21 +0100249 if (etherType.equals(EtherType.IPV4)) {
250 if (src) {
251 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
252 } else {
253 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
254 }
255 } else if (etherType.equals(EtherType.IPV6)) {
256 if (src) {
257 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
258 } else {
259 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
260 }
Andrea Campanella55c3f422018-02-08 17:10:11 +0100261 }
262 } else {
263 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
264 return false;
265 }
266 return true;
267 }
268
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100269 private List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
270 return host.ipAddresses().stream().filter(ipAddress -> {
271 boolean correctIp = false;
272 if (etherType.equals(EtherType.IPV4)) {
273 correctIp = ipAddress.isIp4();
274 } else if (etherType.equals(EtherType.IPV6)) {
275 correctIp = ipAddress.isIp6();
276 }
277 if (checklocal) {
278 correctIp = correctIp && !ipAddress.isLinkLocal();
279 }
280 return correctIp;
281 }).collect(Collectors.toList());
282 }
283
Andrea Campanella55c3f422018-02-08 17:10:11 +0100284 /**
285 * Checks that two hosts are bridged (L2Unicast).
286 *
287 * @param source the source host
288 * @param destination the destination host
289 * @return true if bridged.
290 * @throws ConfigException if config can't be properly retrieved
291 */
292 private boolean areBridged(Host source, Host destination) throws ConfigException {
293
294 //If the location is not the same we don't even check vlan or subnets
295 if (!source.location().deviceId().equals(destination.location().deviceId())) {
296 return false;
297 }
298
299 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
300 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
301 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
302
303 //following can be optimized but for clarity is left as is
304 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
305 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
306
307 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
308 return false;
309 }
310
311 if (!intfH1.vlanTagged().equals(intfH2.vlanTagged())) {
312 return false;
313 }
314
315 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
316 return false;
317 }
318
319 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
320 intersection.retainAll(intfH2.ipAddressesList());
321 if (intersection.size() == 0) {
322 return false;
323 }
324 }
325 return true;
326 }
327
Simon Hunt026a2872017-11-13 17:09:43 -0800328 @Override
329 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100330 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100331 //device must exist in ONOS
332 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
333 "Device " + in.deviceId() + " must exist in ONOS");
334
Andrea Campanellae4084402017-12-15 15:27:31 +0100335 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
336 //FIXME this can be done recursively
337 trace = traceInDevice(trace, packet, in);
338 //Building output connect Points
339 List<ConnectPoint> path = new ArrayList<>();
340 trace = getTrace(path, in, trace);
341 return trace;
342 }
343
344 /**
345 * Computes a trace for a give packet that start in the network at the given connect point.
346 *
347 * @param completePath the path traversed by the packet
348 * @param in the input connect point
349 * @param trace the trace to build
350 * @return the build trace for that packet.
351 */
352 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
353
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100354 log.debug("------------------------------------------------------------");
355
Andrea Campanellae4084402017-12-15 15:27:31 +0100356 //if the trace already contains the input connect point there is a loop
357 if (pathContainsDevice(completePath, in.deviceId())) {
358 trace.addResultMessage("Loop encountered in device " + in.deviceId());
359 return trace;
360 }
361
362 //let's add the input connect point
363 completePath.add(in);
364
365 //If the trace has no outputs for the given input we stop here
366 if (trace.getGroupOuputs(in.deviceId()) == null) {
367 computePath(completePath, trace, null);
368 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
369 return trace;
370 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100371
Andrea Campanellae4084402017-12-15 15:27:31 +0100372 //If the trace has ouputs we analyze them all
373 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100374
375 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100376 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100377 log.debug("Output path {}", cp);
378
Andrea Campanellae4084402017-12-15 15:27:31 +0100379 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100380 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100381 //Hosts queried from the original ip or mac
382 Set<Host> hosts = getHosts(trace);
383
384 //If the two host collections contain the same item it means we reached the proper output
385 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100386 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella54923d62018-01-23 12:46:04 +0100387 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100388 computePath(completePath, trace, outputPath.getOutput());
389 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100390 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100391
Andrea Campanella54923d62018-01-23 12:46:04 +0100392 //Getting the master when the packet gets sent as packet in
393 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100394 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella54923d62018-01-23 12:46:04 +0100395 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100396 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100397
Andrea Campanella8292ba62018-01-31 16:43:23 +0100398 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100399
400 //TODO this can be optimized if we use a Tree structure for paths.
401 //if we already have outputs let's check if the one we are considering starts from one of the devices
402 // in any of the ones we have.
403 if (trace.getCompletePaths().size() > 0) {
404 ConnectPoint inputForOutput = null;
405 List<ConnectPoint> previousPath = new ArrayList<>();
406 for (List<ConnectPoint> path : trace.getCompletePaths()) {
407 for (ConnectPoint connect : path) {
408 //if the path already contains the input for the output we've found we use it
409 if (connect.equals(in)) {
410 inputForOutput = connect;
411 previousPath = path;
412 break;
413 }
414 }
415 }
416 //we use the pre-existing path up to the point we fork to a new output
417 if (inputForOutput != null && completePath.contains(inputForOutput)) {
418 List<ConnectPoint> temp = new ArrayList<>(previousPath);
419 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
420 }
421 }
422
Andrea Campanellae4084402017-12-15 15:27:31 +0100423 //let's add the ouput for the input
424 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100425 //let's compute the links for the given output
426 Set<Link> links = linkService.getEgressLinks(cp);
427 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100428 //For each link we trace the corresponding device
429 for (Link link : links) {
430 ConnectPoint dst = link.dst();
431 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100432 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100433 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
434 updatedPacket.add(Criteria.matchInPort(dst.port()));
435 log.debug("DST Connect Point {}", dst);
436 //build the elements for that device
437 traceInDevice(trace, updatedPacket.build(), dst);
438 //continue the trace along the path
439 getTrace(completePath, dst, trace);
440 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100441 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
442 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
443 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
444 .mac().isMulticast()) {
445 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
446 " which is enabled and is edge port");
447 computePath(completePath, trace, outputPath.getOutput());
448 completePath.clear();
449 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
450 return trace;
451 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100452 } else if (deviceService.getPort(cp).isEnabled()) {
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100453 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
454 .getCriterion(Criterion.Type.ETH_TYPE);
455 //We treat as correct output only if it's not LLDP or BDDP
456 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
Andrea Campanellac6d10fc2018-02-27 12:42:28 +0100457 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100458 if (hostsList.isEmpty()) {
459 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
460 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
461 cp + " with no hosts connected ");
462 } else {
463 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
464 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
465 cp + " with hosts " + hostsList);
466 }
467 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella8292ba62018-01-31 16:43:23 +0100468 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100469
470 } else {
471 //No links means that the packet gets dropped.
472 log.warn("No links out of {}", cp);
473 computePath(completePath, trace, cp);
474 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanellae4084402017-12-15 15:27:31 +0100475 }
476 }
477 return trace;
478 }
479
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100480
Andrea Campanellae4084402017-12-15 15:27:31 +0100481 /**
Andrea Campanellae6798012018-02-06 15:46:52 +0100482 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
483 * If ONOS applied a vlan we remove it.
484 *
485 * @param outputPath the output
486 * @param trace the trace we are building
487 */
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100488
Andrea Campanellae6798012018-02-06 15:46:52 +0100489 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
490
491 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
492 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
493
494 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
495
496 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
497 //removing the final vlanId
498 finalCriteria.remove(finalVid);
499 Builder packetUpdated = DefaultTrafficSelector.builder();
500 finalCriteria.forEach(packetUpdated::add);
501 //Initial was none so we set it to that
502 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
503 //Update final packet
504 outputPath.setFinalPacket(packetUpdated.build());
505 }
506 }
507
508 /**
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100509 * Checks if the device has other outputs than the given connect point.
510 *
511 * @param inDeviceId the device
512 * @param trace the trace we are building
513 * @param cp an output connect point
514 * @return true if the device has other outputs.
515 */
516 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
517 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
518 return !groupsInDevice.getOutput().equals(cp);
519 }).count() > 0;
520 }
521
522 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100523 * Checks if the path contains the device.
524 *
525 * @param completePath the path
526 * @param deviceId the device to check
527 * @return true if the path contains the device
528 */
529 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
530 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
531 for (ConnectPoint cp : completePath) {
532 if (cp.deviceId().equals(deviceId)) {
533 return true;
534 }
535 }
536 return false;
537 }
538
539 /**
540 * Gets the hosts for the given initial packet.
541 *
542 * @param trace the trace we are building
543 * @return set of the hosts we are trying to reach
544 */
545 private Set<Host> getHosts(StaticPacketTrace trace) {
546 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
547 .getCriterion(Criterion.Type.IPV4_DST));
548 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
549 .getCriterion(Criterion.Type.IPV6_DST));
550 Set<Host> hosts = new HashSet<>();
551 if (ipv4Criterion != null) {
552 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
553 }
554 if (ipv6Criterion != null) {
555 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
556 }
557 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
558 .getCriterion(Criterion.Type.ETH_DST));
559 if (ethCriterion != null) {
560 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
561 }
562 return hosts;
563 }
564
565 /**
566 * Computes the list of traversed connect points.
567 *
568 * @param completePath the list of devices
569 * @param trace the trace we are building
570 * @param output the final output connect point
571 */
572 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
573 List<ConnectPoint> traverseList = new ArrayList<>();
574 if (!completePath.contains(trace.getInitialConnectPoint())) {
575 traverseList.add(trace.getInitialConnectPoint());
576 }
577 traverseList.addAll(completePath);
578 if (output != null && !completePath.contains(output)) {
579 traverseList.add(output);
580 }
581 trace.addCompletePath(traverseList);
Andrea Campanellae4084402017-12-15 15:27:31 +0100582 }
583
584 /**
585 * Traces the packet inside a device starting from an input connect point.
586 *
587 * @param trace the trace we are building
588 * @param packet the packet we are tracing
589 * @param in the input connect point.
590 * @return updated trace
591 */
592 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100593
594 //we already traversed this device.
595 if (trace.getGroupOuputs(in.deviceId()) != null) {
596 log.debug("Trace already contains device and given outputs");
597 return trace;
598 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100599 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100600
601 //if device is not available exit here.
602 if (!deviceService.isAvailable(in.deviceId())) {
603 trace.addResultMessage("Device is offline " + in.deviceId());
604 return trace;
605 }
606
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100607 //handle when the input is the controller
608 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
609 // a packet in from the controller will not actually traverse the pipeline and have no such notion
610 // as the input port.
611 if (in.port().equals(PortNumber.CONTROLLER)) {
612 StaticPacketTrace outputTrace = inputFromController(trace, in);
613 if (outputTrace != null) {
614 return trace;
615 }
616 }
617
Andrea Campanellae4084402017-12-15 15:27:31 +0100618 List<FlowEntry> flows = new ArrayList<>();
619 List<FlowEntry> outputFlows = new ArrayList<>();
620
Andrea Campanella8292ba62018-01-31 16:43:23 +0100621 List<Instruction> deferredInstructions = new ArrayList<>();
622
Andrea Campanellae4084402017-12-15 15:27:31 +0100623 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
624 if (nextTableIdEntry == null) {
625 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
626 return trace;
627 }
628 TableId tableId = nextTableIdEntry.table();
629 FlowEntry flowEntry;
630 boolean output = false;
631 while (!output) {
632 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
633 //get the rule that matches the incoming packet
634 flowEntry = matchHighestPriority(packet, in, tableId);
635 log.debug("Found Flow Entry {}", flowEntry);
636
637 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
638 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
639
640 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
641 if (flowEntry == null && isOfdpaHardware) {
642 log.debug("Ofdpa Hw setup, no flow rule means table miss");
643
Andrea Campanellae4084402017-12-15 15:27:31 +0100644 if (((IndexTableId) tableId).id() == 27) {
645 //Apparently a miss but Table 27 on OFDPA is a fixed table
646 packet = handleOfdpa27FixedTable(trace, packet);
647 }
648
649 //Finding next table to go In case of miss
650 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
651 log.debug("Next table id entry {}", nextTableIdEntry);
652
653 //FIXME find better solution that enable granularity greater than 0 or all rules
654 //(another possibility is max tableId)
655 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100656 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellae4084402017-12-15 15:27:31 +0100657 return trace;
658
659 } else if (nextTableIdEntry == null) {
660 //Means that no more flow rules are present
661 output = true;
662
663 } else if (((IndexTableId) tableId).id() == 20) {
664 //if the table is 20 OFDPA skips to table 50
665 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
666 tableId = IndexTableId.of(50);
667
668 } else {
669 tableId = nextTableIdEntry.table();
670 }
671
Andrea Campanellae4084402017-12-15 15:27:31 +0100672 } else if (flowEntry == null) {
673 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
674 in.deviceId() + ". Dropping");
675 return trace;
676 } else {
Andrea Campanella94c594a2018-02-06 18:58:40 +0100677
Andrea Campanellae4084402017-12-15 15:27:31 +0100678 //IF the table has a transition
679 if (flowEntry.treatment().tableTransition() != null) {
680 //update the next table we transitions to
681 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
682 log.debug("Flow Entry has transition to table Id {}", tableId);
683 flows.add(flowEntry);
684 } else {
685 //table has no transition so it means that it's an output rule if on the last table
686 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
687 flows.add(flowEntry);
688 outputFlows.add(flowEntry);
689 output = true;
690 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100691 //update the packet according to the immediate actions of this flow rule.
692 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
693
694 //save the deferred rules for later
695 deferredInstructions.addAll(flowEntry.treatment().deferred());
696
697 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
698 if (flowEntry.treatment().clearedDeferred()) {
699 deferredInstructions.clear();
700 }
701
Andrea Campanella94c594a2018-02-06 18:58:40 +0100702 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
703 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
704
705 //Let's get the packet vlanId instruction
706 VlanIdCriterion packetVlanIdCriterion =
707 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
708
709 //Let's get the flow entry vlan mod instructions
710 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
711 .immediate().stream()
712 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
713 .findFirst().orElse(null);
714
715 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
716 // is a flow rule that matches on same criteria and with updated vlanId
717 if (entryModVlanIdInstruction != null) {
718
719 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
720 packetVlanIdCriterion, entryModVlanIdInstruction);
721
722 //We found the flow that we expected
723 if (secondVlanFlow != null) {
724 flows.add(secondVlanFlow);
725 } else {
726 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
727 return trace;
728 }
729 }
730
731 }
732
Andrea Campanellae4084402017-12-15 15:27:31 +0100733 }
734 }
735
736 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100737 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100738 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100739
Andrea Campanellae4084402017-12-15 15:27:31 +0100740 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100741 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100742
Andrea Campanellae4084402017-12-15 15:27:31 +0100743 List<PortNumber> outputPorts = new ArrayList<>();
744
Andrea Campanella54923d62018-01-23 12:46:04 +0100745 //TODO optimization
Andrea Campanella54923d62018-01-23 12:46:04 +0100746 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
747 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
748 .allInstructions().stream().filter(instruction -> instruction.type()
749 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100750
Andrea Campanella54923d62018-01-23 12:46:04 +0100751 if (outputFlowEntries.size() > 1) {
752 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
753 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100754 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100755
756 if (outputFlowEntries.size() == 1) {
757
758 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
759 .allInstructions().stream()
760 .filter(instruction -> {
761 return instruction.type().equals(Instruction.Type.OUTPUT);
762 }).findFirst().get();
763
764 //FIXME using GroupsInDevice for output even if flows.
765 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
766
767 }
768 log.debug("Handling Groups");
769 //Analyze Groups
770 List<Group> groups = new ArrayList<>();
771
772 Collection<FlowEntry> nonOutputFlows = flows;
773 nonOutputFlows.removeAll(outputFlowEntries);
774
Andrea Campanella8292ba62018-01-31 16:43:23 +0100775 //Handling groups pointed at by immediate instructions
Andrea Campanella54923d62018-01-23 12:46:04 +0100776 for (FlowEntry entry : flows) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100777 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanella54923d62018-01-23 12:46:04 +0100778 entry.deviceId(), builder, outputPorts, in);
779 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100780
781 //If we have deferred instructions at this point we handle them.
782 if (deferredInstructions.size() > 0) {
783 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
784
785 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100786 packet = builder.build();
Andrea Campanella54923d62018-01-23 12:46:04 +0100787
Andrea Campanella94c594a2018-02-06 18:58:40 +0100788 log.debug("Output Packet {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100789 return trace;
790 }
791
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100792 /**
793 * Handles the specific case where the Input is the controller.
794 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
795 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
796 * the flood to all active physical ports of the device.
797 *
798 * @param trace the trace
799 * @param in the controller port
800 * @return the augmented trace.
801 */
802 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
803 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
804 .getCriterion(Criterion.Type.ETH_TYPE);
805 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
806 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
807 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
808 //get the active ports
809 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
810 .filter(Port::isEnabled)
811 .collect(Collectors.toList());
812 //build an output from each one
813 enabledPorts.forEach(port -> {
814 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
815 ImmutableList.of(), trace.getInitialPacket());
816 trace.addGroupOutputPath(in.deviceId(), output);
817 });
818 return trace;
819 }
820 return null;
821 }
822
Andrea Campanella94c594a2018-02-06 18:58:40 +0100823 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
824 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
825 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
826 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
827 .vlanId().equals(VlanId.NONE);
828 }
829
830 /**
831 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
832 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
833 * second to transition to following table
834 *
835 * @param packet the incoming packet
836 * @param in the input connect point
837 * @param packetVlanIdCriterion the vlan criterion from the packet
838 * @param entryModVlanIdInstruction the entry vlan instruction
839 * @return the second flow entry that matched
840 */
841 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
842 VlanIdCriterion packetVlanIdCriterion,
843 ModVlanIdInstruction entryModVlanIdInstruction) {
844 FlowEntry secondVlanFlow = null;
845 //Check the packet has been update from the first rule.
846 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
847 //find a rule on the same table that matches the vlan and
848 // also all the other elements of the flow such as input port
849 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
850 .stream()
851 .filter(entry -> {
852 return entry.table().equals(IndexTableId.of(10));
853 })
854 .filter(entry -> {
855 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
856 .getCriterion(Criterion.Type.VLAN_VID);
857 return criterion != null && match(packet, entry)
858 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
859 }).findFirst().orElse(null);
860
861 }
862 return secondVlanFlow;
863 }
864
Andrea Campanella8292ba62018-01-31 16:43:23 +0100865
Andrea Campanellae4084402017-12-15 15:27:31 +0100866 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100867 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
868 *
869 * @param packet the incoming packet
870 * @return the updated packet
871 */
872 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
873 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
874 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
875 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
876
877 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
878 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
879 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100880 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100881 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
882 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
883 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100884 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100885 builder.add(ethInstruction);
886 }
887 packet = updatePacket(packet, builder.build()).build();
888 return packet;
889 }
890
891 /**
892 * Finds the flow entry with the minimun next table Id.
893 *
894 * @param deviceId the device to search
895 * @param currentId the current id. the search will use this as minimum
896 * @return the flow entry with the minimum table Id after the given one.
897 */
898 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
899
900 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
901
902 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
903 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
904 }
905
Andrea Campanella8292ba62018-01-31 16:43:23 +0100906 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
907 ConnectPoint in, List<Instruction> deferredInstructions,
908 List<PortNumber> outputPorts, List<Group> groups) {
909
910 //Update the packet with the deferred instructions
911 Builder builder = updatePacket(packet, deferredInstructions);
912
913 //Gather any output instructions from the deferred instruction
914 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
915 return instruction.type().equals(Instruction.Type.OUTPUT);
916 }).collect(Collectors.toList());
917
918 //We are considering deferred instructions from flows, there can only be one output.
919 if (outputFlowInstruction.size() > 1) {
920 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
921 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
922 }
923 //If there is one output let's go through that
924 if (outputFlowInstruction.size() == 1) {
925 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
926 ImmutableList.of());
927 }
928 //If there is no output let's see if there any deferred instruction point to groups.
929 if (outputFlowInstruction.size() == 0) {
930 getGroupsFromInstructions(trace, groups, deferredInstructions,
931 in.deviceId(), builder, outputPorts, in);
932 }
933 return builder;
934 }
935
Andrea Campanellae4084402017-12-15 15:27:31 +0100936 /**
937 * Gets group information from instructions.
938 *
939 * @param trace the trace we are building
940 * @param groupsForDevice the set of groups for this device
941 * @param instructions the set of instructions we are searching for groups.
942 * @param deviceId the device we are considering
943 * @param builder the builder of the input packet
944 * @param outputPorts the output ports for that packet
945 */
946 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
947 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +0100948 Builder builder, List<PortNumber> outputPorts,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100949 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100950 List<Instruction> groupInstructionlist = new ArrayList<>();
951 for (Instruction instruction : instructions) {
952 log.debug("Considering Instruction {}", instruction);
953 //if the instruction is not group we need to update the packet or add the output
954 //to the possible outputs for this packet
955 if (!instruction.type().equals(Instruction.Type.GROUP)) {
956 //if the instruction is not group we need to update the packet or add the output
957 //to the possible outputs for this packet
958 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100959 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella573d4b92018-02-19 17:03:46 +0100960 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
961 //clearing the groups because we start from the top.
962 groupsForDevice.clear();
Andrea Campanellae4084402017-12-15 15:27:31 +0100963 } else {
964 builder = translateInstruction(builder, instruction);
965 }
966 } else {
967 //if the instuction is pointing to a group we need to get the group
968 groupInstructionlist.add(instruction);
969 }
970 }
971 //handle all the internal instructions pointing to a group.
972 for (Instruction instr : groupInstructionlist) {
973 GroupInstruction groupInstruction = (GroupInstruction) instr;
974 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
975 return groupInternal.id().equals(groupInstruction.groupId());
976 }).findAny().orElse(null);
977 if (group == null) {
978 trace.addResultMessage("Null group for Instruction " + instr);
979 break;
980 }
Andrea Campanella94dfb9e2018-02-27 12:36:00 +0100981 if (group.buckets().buckets().size() == 0) {
982 trace.addResultMessage("Group " + group.id() + "has no buckets");
983 break;
984 }
Andrea Campanella573d4b92018-02-19 17:03:46 +0100985
Andrea Campanellae4084402017-12-15 15:27:31 +0100986 //Cycle in each of the group's buckets and add them to the groups for this Device.
987 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella573d4b92018-02-19 17:03:46 +0100988
989 //add the group to the traversed groups
990 if (!groupsForDevice.contains(group)) {
991 groupsForDevice.add(group);
992 }
993
Andrea Campanellae4084402017-12-15 15:27:31 +0100994 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100995 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100996 }
997 }
998 }
999
1000 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001001 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1002 * a possible output from this device.
1003 *
1004 * @param trace the trace
1005 * @param in the input connect point
1006 * @param builder the packet builder
1007 * @param outputPorts the list of output ports for this device
1008 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +01001009 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001010 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001011 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001012 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1013 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +01001014 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella1445f7a2018-02-07 12:00:12 +01001015
1016 //if the output is the input same we drop, except if the output is the controller
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001017 if (output.equals(in)) {
1018 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
1019 trace.getInitialConnectPoint());
1020 } else {
1021 trace.addGroupOutputPath(in.deviceId(),
1022 new GroupsInDevice(output, groupsForDevice, builder.build()));
1023 outputPorts.add(outputInstruction.port());
1024 }
1025 }
1026
1027 /**
Andrea Campanellae4084402017-12-15 15:27:31 +01001028 * Applies all give instructions to the input packet.
1029 *
1030 * @param packet the input packet
1031 * @param instructions the set of instructions
1032 * @return the packet with the applied instructions
1033 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001034 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1035 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +01001036 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +01001037 //FIXME optimize
1038 for (Instruction instruction : instructions) {
1039 newSelector = translateInstruction(newSelector, instruction);
1040 }
Andrea Campanellae4084402017-12-15 15:27:31 +01001041 return newSelector;
1042 }
1043
1044 /**
1045 * Applies an instruction to the packet in the form of a selector.
1046 *
1047 * @param newSelector the packet selector
1048 * @param instruction the instruction to be translated
1049 * @return the new selector with the applied instruction
1050 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001051 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001052 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +01001053 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +01001054 //TODO add as required
1055 Criterion criterion = null;
1056 switch (instruction.type()) {
1057 case L2MODIFICATION:
1058 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1059 switch (l2Instruction.subtype()) {
1060 case VLAN_ID:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001061 ModVlanIdInstruction vlanIdInstruction =
1062 (ModVlanIdInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001063 VlanId id = vlanIdInstruction.vlanId();
1064 criterion = Criteria.matchVlanId(id);
1065 break;
1066 case VLAN_POP:
1067 criterion = Criteria.matchVlanId(VlanId.NONE);
1068 break;
1069 case MPLS_PUSH:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001070 ModMplsHeaderInstruction mplsEthInstruction =
1071 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001072 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1073 break;
1074 case MPLS_POP:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001075 ModMplsHeaderInstruction mplsPopInstruction =
1076 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001077 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +01001078
1079 //When popping MPLS we remove label and BOS
1080 TrafficSelector temporaryPacket = newSelector.build();
1081 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001082 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +01001083 temporaryPacket.criteria().stream().filter(c -> {
1084 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1085 !c.type().equals(Criterion.Type.MPLS_BOS);
1086 }).forEach(noMplsSelector::add);
1087 newSelector = noMplsSelector;
1088 }
1089
Andrea Campanellae4084402017-12-15 15:27:31 +01001090 break;
1091 case MPLS_LABEL:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001092 ModMplsLabelInstruction mplsLabelInstruction =
1093 (ModMplsLabelInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001094 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +01001095 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001096 break;
1097 case ETH_DST:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001098 ModEtherInstruction modEtherDstInstruction =
1099 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001100 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1101 break;
1102 case ETH_SRC:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001103 ModEtherInstruction modEtherSrcInstruction =
1104 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001105 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1106 break;
1107 default:
1108 log.debug("Unsupported L2 Instruction");
1109 break;
1110 }
1111 break;
1112 default:
1113 log.debug("Unsupported Instruction");
1114 break;
1115 }
1116 if (criterion != null) {
1117 log.debug("Adding criterion {}", criterion);
1118 newSelector.add(criterion);
1119 }
1120 return newSelector;
1121 }
1122
1123 /**
1124 * Finds the rule in the device that mathces the input packet and has the highest priority.
1125 *
1126 * @param packet the input packet
1127 * @param in the connect point the packet comes in from
1128 * @param tableId the table to search
1129 * @return the flow entry
1130 */
1131 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1132 //Computing the possible match rules.
1133 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
1134 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1135 .stream()
1136 .filter(flowEntry -> {
1137 return flowEntry.table().equals(tableId);
1138 })
1139 .filter(flowEntry -> {
1140 return match(packet, flowEntry);
1141 }).max(comparator).orElse(null);
1142 }
1143
1144 /**
1145 * Matches the packet with the given flow entry.
1146 *
1147 * @param packet the packet to match
1148 * @param flowEntry the flow entry to match the packet against
1149 * @return true if the packet matches the flow.
1150 */
1151 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001152 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1153 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +01001154 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +01001155 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1156 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001157 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanellae4084402017-12-15 15:27:31 +01001158 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001159 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1160 return matchMac(packet, (EthCriterion) criterion, false);
1161 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1162 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001163 } else {
1164 return packet.criteria().contains(criterion);
1165 }
1166 });
Simon Hunt026a2872017-11-13 17:09:43 -08001167 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001168
1169 /**
1170 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1171 *
1172 * @param packet the incoming packet
1173 * @param criterion the criterion to match
1174 * @return true if match
1175 */
1176 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1177 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1178 //if the packet does not have an IPv4 or IPv6 criterion we return true
1179 if (matchCriterion == null) {
1180 return false;
1181 }
1182 try {
1183 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1184 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1185 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1186 } catch (UnknownHostException e) {
1187 return false;
1188 }
1189 }
1190
1191 /**
1192 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1193 *
1194 * @param packet the incoming packet
1195 * @param hitCriterion the criterion to match
1196 * @param dst true if we are checking DST MAC
1197 * @return true if match
1198 */
1199 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1200 //Packet can have only one EthCriterion
1201 EthCriterion matchCriterion;
1202 if (dst) {
1203 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1204 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1205 criterion1.type().equals(Criterion.Type.ETH_DST);
1206 }).findFirst().orElse(null);
1207 } else {
1208 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1209 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1210 criterion1.type().equals(Criterion.Type.ETH_SRC);
1211 }).findFirst().orElse(null);
1212 }
1213 //if the packet does not have an ETH criterion we return true
1214 if (matchCriterion == null) {
1215 return true;
1216 }
1217 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1218 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1219 }
Simon Hunt026a2872017-11-13 17:09:43 -08001220}