blob: df73a616c1e0d985e9fca51e521bce173bbd1bfb [file] [log] [blame]
Simon Hunt6fefd852017-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 Campanella37d10622018-01-18 17:11:42 +010019import com.google.common.base.Preconditions;
Andrea Campanella01e886e2017-12-15 15:27:31 +010020import com.google.common.collect.ImmutableList;
Andrea Campanella79cb17d2018-02-27 18:03:17 +010021import com.google.common.collect.ImmutableSet;
Andrea Campanella01e886e2017-12-15 15:27:31 +010022import com.google.common.collect.Lists;
Andrea Campanella7e2200e2018-02-27 14:50:45 +010023import com.google.common.collect.Sets;
Andrea Campanella6be5c872018-02-21 14:28:20 +010024import org.apache.commons.lang3.tuple.Pair;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010025import org.onlab.packet.IpAddress;
Andrea Campanella01e886e2017-12-15 15:27:31 +010026import org.onlab.packet.VlanId;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010027import org.onosproject.cluster.NodeId;
28import org.onosproject.mastership.MastershipService;
Andrea Campanellafa3ec192018-04-06 16:30:18 +020029import org.onosproject.mcast.api.MulticastRouteService;
Simon Hunt6fefd852017-11-13 17:09:43 -080030import org.onosproject.net.ConnectPoint;
Andrea Campanella01e886e2017-12-15 15:27:31 +010031import org.onosproject.net.DeviceId;
32import org.onosproject.net.Host;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010033import org.onosproject.net.HostId;
Andrea Campanella01e886e2017-12-15 15:27:31 +010034import org.onosproject.net.Link;
Andrea Campanella6f2d6742018-02-07 12:00:12 +010035import org.onosproject.net.Port;
Andrea Campanella01e886e2017-12-15 15:27:31 +010036import org.onosproject.net.PortNumber;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010037import org.onosproject.net.config.ConfigException;
38import org.onosproject.net.config.NetworkConfigService;
39import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella37d10622018-01-18 17:11:42 +010040import org.onosproject.net.device.DeviceService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010041import org.onosproject.net.driver.DriverService;
Andrea Campanella04924b92018-01-17 16:34:51 +010042import org.onosproject.net.edge.EdgePortService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010043import org.onosproject.net.flow.DefaultTrafficSelector;
44import org.onosproject.net.flow.FlowEntry;
45import org.onosproject.net.flow.FlowRule;
Simon Hunt6fefd852017-11-13 17:09:43 -080046import org.onosproject.net.flow.FlowRuleService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010047import org.onosproject.net.flow.IndexTableId;
48import org.onosproject.net.flow.TableId;
Simon Hunt6fefd852017-11-13 17:09:43 -080049import org.onosproject.net.flow.TrafficSelector;
Andrea Campanella01e886e2017-12-15 15:27:31 +010050import org.onosproject.net.flow.criteria.Criteria;
51import org.onosproject.net.flow.criteria.Criterion;
52import org.onosproject.net.flow.criteria.EthCriterion;
53import org.onosproject.net.flow.criteria.EthTypeCriterion;
54import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanella58b3b522018-02-06 15:46:52 +010055import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanella01e886e2017-12-15 15:27:31 +010056import org.onosproject.net.flow.instructions.Instruction;
57import org.onosproject.net.flow.instructions.Instructions;
58import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
59import org.onosproject.net.flow.instructions.L2ModificationInstruction;
60import org.onosproject.net.group.Group;
61import org.onosproject.net.group.GroupBucket;
Simon Hunt6fefd852017-11-13 17:09:43 -080062import org.onosproject.net.group.GroupService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010063import org.onosproject.net.host.HostService;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010064import org.onosproject.net.host.InterfaceIpAddress;
65import org.onosproject.net.intf.Interface;
Andrea Campanella01e886e2017-12-15 15:27:31 +010066import org.onosproject.net.link.LinkService;
Andrea Campanellacc2424a2018-03-07 14:27:54 -080067import org.onosproject.routeservice.ResolvedRoute;
68import org.onosproject.routeservice.RouteService;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010069import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanella01e886e2017-12-15 15:27:31 +010070import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt6fefd852017-11-13 17:09:43 -080071import org.onosproject.t3.api.StaticPacketTrace;
72import org.onosproject.t3.api.TroubleshootService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070073import org.osgi.service.component.annotations.Component;
74import org.osgi.service.component.annotations.Reference;
75import org.osgi.service.component.annotations.ReferenceCardinality;
Simon Hunt6fefd852017-11-13 17:09:43 -080076import org.slf4j.Logger;
77
Andrea Campanella01e886e2017-12-15 15:27:31 +010078import java.net.UnknownHostException;
79import java.util.ArrayList;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010080import java.util.Collection;
Andrea Campanella01e886e2017-12-15 15:27:31 +010081import java.util.Collections;
82import java.util.Comparator;
83import java.util.HashSet;
84import java.util.List;
Andrea Campanellacc2424a2018-03-07 14:27:54 -080085import java.util.Optional;
Andrea Campanella01e886e2017-12-15 15:27:31 +010086import java.util.Set;
87import java.util.stream.Collectors;
psneha5c9f51b2018-08-02 07:41:43 -040088import java.util.stream.StreamSupport;
Andrea Campanella01e886e2017-12-15 15:27:31 +010089
90import static org.onlab.packet.EthType.EtherType;
Andrea Campanella58b3b522018-02-06 15:46:52 +010091import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanella01e886e2017-12-15 15:27:31 +010092import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella97f9d4c2018-02-06 18:58:40 +010093import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
94import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
95import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
96import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Andrea Campanella04924b92018-01-17 16:34:51 +010097import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt6fefd852017-11-13 17:09:43 -080098import static org.slf4j.LoggerFactory.getLogger;
99
100/**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100101 * Manager to troubleshoot packets inside the network.
102 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
103 * the devices.
Simon Hunt6fefd852017-11-13 17:09:43 -0800104 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700105@Component(immediate = true, service = TroubleshootService.class)
Simon Hunt6fefd852017-11-13 17:09:43 -0800106public class TroubleshootManager implements TroubleshootService {
107
108 private static final Logger log = getLogger(TroubleshootManager.class);
109
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100110 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
111
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700112 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Simon Hunt6fefd852017-11-13 17:09:43 -0800113 protected FlowRuleService flowRuleService;
114
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700115 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Simon Hunt6fefd852017-11-13 17:09:43 -0800116 protected GroupService groupService;
117
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700118 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella01e886e2017-12-15 15:27:31 +0100119 protected LinkService linkService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800120
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700121 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella01e886e2017-12-15 15:27:31 +0100122 protected HostService hostService;
123
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700124 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella01e886e2017-12-15 15:27:31 +0100125 protected DriverService driverService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800126
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700127 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella37d10622018-01-18 17:11:42 +0100128 protected DeviceService deviceService;
129
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700130 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100131 protected MastershipService mastershipService;
132
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700133 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100134 protected NetworkConfigService networkConfigService;
135
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700136 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanella04924b92018-01-17 16:34:51 +0100137 protected EdgePortService edgePortService;
138
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700139 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800140 protected RouteService routeService;
141
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700142 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200143 protected MulticastRouteService mcastService;
144
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100145 @Override
Andrea Campanella6be5c872018-02-21 14:28:20 +0100146 public List<StaticPacketTrace> pingAll(EtherType type) {
147 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
148 hostService.getHosts().forEach(host -> {
149 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
150 if (ipAddresses.size() > 0) {
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100151 //check if the host has only local IPs of that ETH type
152 boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
Andrea Campanella6be5c872018-02-21 14:28:20 +0100153 hostService.getHosts().forEach(hostToPing -> {
154 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100155 //check if the other host has only local IPs of that ETH type
156 boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
157 boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
158 //Trace is done only if they are both local and under the same location
159 // or not local and if they are not the same host.
160 if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
161 (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
162 && !host.equals(hostToPing)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100163 tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
Andrea Campanella6be5c872018-02-21 14:28:20 +0100164 }
165 });
166 }
167 });
168 return tracesBuilder.build();
169 }
170
171 @Override
Andrea Campanella41de8062018-02-28 16:43:16 +0100172 public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
173 return new PingAllGenerator(type, hostService, this);
174 }
175
176 @Override
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200177 public Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId) {
178 return new McastGenerator(mcastService, this, vlanId);
179 }
180
181 @Override
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100182 public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100183 Host source = hostService.getHost(sourceHost);
184 Host destination = hostService.getHost(destinationHost);
185
Andrea Campanella6be5c872018-02-21 14:28:20 +0100186 //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
187 StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100188
189 if (source == null) {
190 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700191 failTrace.setSuccess(false);
192
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100193 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100194 }
195
196 if (destination == null) {
197 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700198 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100199 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100200 }
201
202 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100203 .matchEthType(etherType.ethType().toShort())
204 .matchEthDst(source.mac())
205 .matchVlanId(source.vlan());
206
207
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100208 try {
Andrea Campanella15278a12018-03-26 10:39:22 -0700209 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100210 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
211 // we are under same leaf so it's L2 Unicast.
212 if (areBridged(source, destination)) {
213 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100214 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800215 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100216 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
217 trace.addEndpointHosts(Pair.of(source, destination));
218 traces.add(trace);
219 });
Andrea Campanella15278a12018-03-26 10:39:22 -0700220 //The destination host is not dual homed, if it is the other path might be done through routing.
221 if (destination.locations().size() == 1) {
222 return traces.build();
223 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100224 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100225 //handle the IPs for src and dst in case of L3
226 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
227
228 //Match on the source IP
229 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100230 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100231 }
232
233 //Match on destination IP
234 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100235 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100236 }
237
238 } else {
239 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
240 "please use packet based");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700241 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100242 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100243 }
244
245 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
246 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
247 .deviceId(), SegmentRoutingDeviceConfig.class);
248 if (segmentRoutingConfig != null) {
249 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
250 } else {
251 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
252 " router MAC from segment routing config can't perform L3 tracing.");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700253 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100254 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100255 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800256 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100257 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
258 trace.addEndpointHosts(Pair.of(source, destination));
259 traces.add(trace);
260 });
261 return traces.build();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100262
263 } catch (ConfigException e) {
264 failTrace.addResultMessage("Can't get config " + e.getMessage());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100265 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100266 }
267 }
268
269 /**
270 * Matches src and dst IPs based on host information.
271 *
272 * @param host the host
273 * @param failTrace the trace to use in case of failure
274 * @param selectorBuilder the packet we are building to trace
275 * @param etherType the traffic type
276 * @param src is this src host or dst host
277 * @return true if properly matched
278 */
279 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
280 EtherType etherType, boolean src) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100281 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100282
283 if (ips.size() > 0) {
Andrea Campanella465c7be2018-02-21 14:43:21 +0100284 if (etherType.equals(EtherType.IPV4)) {
285 if (src) {
286 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
287 } else {
288 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
289 }
290 } else if (etherType.equals(EtherType.IPV6)) {
291 if (src) {
292 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
293 } else {
294 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
295 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100296 }
297 } else {
298 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700299 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100300 return false;
301 }
302 return true;
303 }
304
Andrea Campanella41de8062018-02-28 16:43:16 +0100305 List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100306 return host.ipAddresses().stream().filter(ipAddress -> {
307 boolean correctIp = false;
308 if (etherType.equals(EtherType.IPV4)) {
309 correctIp = ipAddress.isIp4();
310 } else if (etherType.equals(EtherType.IPV6)) {
311 correctIp = ipAddress.isIp6();
312 }
313 if (checklocal) {
314 correctIp = correctIp && !ipAddress.isLinkLocal();
315 }
316 return correctIp;
317 }).collect(Collectors.toList());
318 }
319
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100320 /**
321 * Checks that two hosts are bridged (L2Unicast).
322 *
323 * @param source the source host
324 * @param destination the destination host
325 * @return true if bridged.
326 * @throws ConfigException if config can't be properly retrieved
327 */
328 private boolean areBridged(Host source, Host destination) throws ConfigException {
329
Andrea Campanella7b84c072018-03-06 15:21:09 -0800330 //If the locations is not the same we don't even check vlan or subnets
331 if (Collections.disjoint(source.locations(), destination.locations())) {
332 return false;
333 }
334
335 if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
336 && !source.vlan().equals(destination.vlan())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100337 return false;
338 }
339
340 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
341 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
342 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
343
344 //following can be optimized but for clarity is left as is
345 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
346 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
347
Andrea Campanella7b84c072018-03-06 15:21:09 -0800348 if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
349 return intfH1.vlanUntagged().equals(destination.vlan()) ||
350 intfH1.vlanNative().equals(destination.vlan());
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100351 }
352
Andrea Campanella7b84c072018-03-06 15:21:09 -0800353 if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
354 return intfH2.vlanUntagged().equals(source.vlan()) ||
355 intfH2.vlanNative().equals(source.vlan());
356 }
357
358 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100359 return false;
360 }
361
362 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
363 return false;
364 }
365
366 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
367 intersection.retainAll(intfH2.ipAddressesList());
368 if (intersection.size() == 0) {
369 return false;
370 }
371 }
372 return true;
373 }
374
Simon Hunt6fefd852017-11-13 17:09:43 -0800375 @Override
376 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100377 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100378 //device must exist in ONOS
379 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
380 "Device " + in.deviceId() + " must exist in ONOS");
381
Andrea Campanella01e886e2017-12-15 15:27:31 +0100382 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100383 boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100384 //FIXME this can be done recursively
Andrea Campanella01e886e2017-12-15 15:27:31 +0100385 //Building output connect Points
386 List<ConnectPoint> path = new ArrayList<>();
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700387 trace = traceInDevice(trace, packet, in, isDualHomed, path);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100388 trace = getTrace(path, in, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100389 return trace;
390 }
391
psneha5c9f51b2018-08-02 07:41:43 -0400392 @Override
393 public List<Set<StaticPacketTrace>> getMulitcastTrace(VlanId vlanId) {
394 Generator<Set<StaticPacketTrace>> gen = new McastGenerator(mcastService, this, vlanId);
395 List<Set<StaticPacketTrace>> multicastTraceList =
396 StreamSupport.stream(gen.spliterator(), false).collect(Collectors.toList());
397 return multicastTraceList;
398 }
399
Andrea Campanella01e886e2017-12-15 15:27:31 +0100400 /**
401 * Computes a trace for a give packet that start in the network at the given connect point.
402 *
403 * @param completePath the path traversed by the packet
404 * @param in the input connect point
405 * @param trace the trace to build
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100406 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanella01e886e2017-12-15 15:27:31 +0100407 * @return the build trace for that packet.
408 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100409 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
410 boolean isDualHomed) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100411
Andrea Campanellae04aac92018-01-31 14:59:03 +0100412 log.debug("------------------------------------------------------------");
413
Andrea Campanella01e886e2017-12-15 15:27:31 +0100414 //if the trace already contains the input connect point there is a loop
415 if (pathContainsDevice(completePath, in.deviceId())) {
416 trace.addResultMessage("Loop encountered in device " + in.deviceId());
Andrea Campanellaece11772018-03-09 14:52:10 -0800417 completePath.add(in);
418 trace.addCompletePath(completePath);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700419 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100420 return trace;
421 }
422
423 //let's add the input connect point
424 completePath.add(in);
425
426 //If the trace has no outputs for the given input we stop here
427 if (trace.getGroupOuputs(in.deviceId()) == null) {
428 computePath(completePath, trace, null);
429 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700430 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100431 return trace;
432 }
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100433
Andrea Campanella67b75602018-04-09 14:22:32 +0200434 //If the trace has outputs we analyze them all
Andrea Campanella01e886e2017-12-15 15:27:31 +0100435 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100436
437 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellae04aac92018-01-31 14:59:03 +0100438 log.debug("Connect point in {}", in);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100439 log.debug("Output path {}", cp);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800440 log.debug("{}", outputPath.getFinalPacket());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100441
Andrea Campanella01e886e2017-12-15 15:27:31 +0100442 //Hosts for the the given output
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100443 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100444 //Hosts queried from the original ip or mac
445 Set<Host> hosts = getHosts(trace);
446
Andrea Campanella7b84c072018-03-06 15:21:09 -0800447 if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
448 outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
449 && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
450 .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
451 .vlanId())) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700452 if (trace.getGroupOuputs(in.deviceId()).size() == 1 &&
453 computePath(completePath, trace, outputPath.getOutput())) {
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700454 trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700455 trace.setSuccess(false);
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700456 }
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700457 } else if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700458 //If the two host collections contain the same item it means we reached the proper output
Andrea Campanellae04aac92018-01-31 14:59:03 +0100459 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100460 if (computePath(completePath, trace, outputPath.getOutput())) {
461 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella41de8062018-02-28 16:43:16 +0100462 trace.setSuccess(true);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100463 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100464 break;
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100465 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100466
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100467 //Getting the master when the packet gets sent as packet in
468 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100469 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100470 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella58b3b522018-02-06 15:46:52 +0100471 handleVlanToController(outputPath, trace);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100472
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100473 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100474
475 //TODO this can be optimized if we use a Tree structure for paths.
476 //if we already have outputs let's check if the one we are considering starts from one of the devices
477 // in any of the ones we have.
478 if (trace.getCompletePaths().size() > 0) {
479 ConnectPoint inputForOutput = null;
480 List<ConnectPoint> previousPath = new ArrayList<>();
481 for (List<ConnectPoint> path : trace.getCompletePaths()) {
482 for (ConnectPoint connect : path) {
483 //if the path already contains the input for the output we've found we use it
484 if (connect.equals(in)) {
485 inputForOutput = connect;
486 previousPath = path;
487 break;
488 }
489 }
490 }
Andrea Campanellaece11772018-03-09 14:52:10 -0800491
Andrea Campanellae04aac92018-01-31 14:59:03 +0100492 //we use the pre-existing path up to the point we fork to a new output
493 if (inputForOutput != null && completePath.contains(inputForOutput)) {
494 List<ConnectPoint> temp = new ArrayList<>(previousPath);
Andrea Campanellaece11772018-03-09 14:52:10 -0800495 temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
496 if (completePath.containsAll(temp)) {
497 completePath = temp;
498 }
Andrea Campanellae04aac92018-01-31 14:59:03 +0100499 }
500 }
501
Andrea Campanella01e886e2017-12-15 15:27:31 +0100502 //let's add the ouput for the input
503 completePath.add(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100504 //let's compute the links for the given output
505 Set<Link> links = linkService.getEgressLinks(cp);
506 log.debug("Egress Links {}", links);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100507 //For each link we trace the corresponding device
508 for (Link link : links) {
509 ConnectPoint dst = link.dst();
510 //change in-port to the dst link in port
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100511 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100512 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
513 updatedPacket.add(Criteria.matchInPort(dst.port()));
514 log.debug("DST Connect Point {}", dst);
515 //build the elements for that device
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700516 traceInDevice(trace, updatedPacket.build(), dst, isDualHomed, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100517 //continue the trace along the path
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100518 getTrace(completePath, dst, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100519 }
Andrea Campanella04924b92018-01-17 16:34:51 +0100520 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
521 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
522 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
523 .mac().isMulticast()) {
524 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
525 " which is enabled and is edge port");
Andrea Campanella41de8062018-02-28 16:43:16 +0100526 trace.setSuccess(true);
Andrea Campanella04924b92018-01-17 16:34:51 +0100527 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella04924b92018-01-17 16:34:51 +0100528 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
529 return trace;
530 }
Andrea Campanella15278a12018-03-26 10:39:22 -0700531 } else if (deviceService.getPort(cp) != null && deviceService.getPort(cp).isEnabled()) {
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100532 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
533 .getCriterion(Criterion.Type.ETH_TYPE);
534 //We treat as correct output only if it's not LLDP or BDDP
535 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
Andrea Campanella62841d42018-02-27 12:42:28 +0100536 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800537 if (computePath(completePath, trace, outputPath.getOutput())) {
538 if (hostsList.isEmpty()) {
539 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
540 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
541 cp + " with no hosts connected ");
542 } else {
543 IpAddress ipAddress = null;
544 if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
545 ipAddress = ((IPCriterion) trace.getInitialPacket()
546 .getCriterion(Criterion.Type.IPV4_DST)).ip().address();
547 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
548 ipAddress = ((IPCriterion) trace.getInitialPacket()
549 .getCriterion(Criterion.Type.IPV6_DST)).ip().address();
550 }
551 if (ipAddress != null) {
552 IpAddress finalIpAddress = ipAddress;
553 if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
554 hostService.getHostsByIp(finalIpAddress).isEmpty()) {
555 trace.addResultMessage("Packet is " +
556 ((EthTypeCriterion) outputPath.getFinalPacket()
Andrea Campanella220f19b2018-03-09 15:30:22 -0800557 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
558 " and reached " + cp + " with hosts " + hostsList);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800559 } else {
560 trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
561 ipAddress);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700562 trace.setSuccess(false);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800563 }
564 } else {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800565 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
566 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
567 cp + " with hosts " + hostsList);
Andrea Campanella7b84c072018-03-06 15:21:09 -0800568 }
Andrea Campanella7b84c072018-03-06 15:21:09 -0800569 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800570 trace.setSuccess(true);
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100571 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100572 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100573
574 } else {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100575 computePath(completePath, trace, cp);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700576 trace.setSuccess(false);
Andrea Campanella15278a12018-03-26 10:39:22 -0700577 if (deviceService.getPort(cp) == null) {
Jon Hall7d77fe12018-04-24 18:03:10 -0700578 //Port is not existent on device.
Andrea Campanella15278a12018-03-26 10:39:22 -0700579 log.warn("Port {} is not available on device.", cp);
580 trace.addResultMessage("Port " + cp + "is not available on device. Packet is dropped");
581 } else {
582 //No links means that the packet gets dropped.
583 log.warn("No links out of {}", cp);
584 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
585 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100586 }
587 }
588 return trace;
589 }
590
Andrea Campanella04924b92018-01-17 16:34:51 +0100591
Andrea Campanella01e886e2017-12-15 15:27:31 +0100592 /**
Andrea Campanella58b3b522018-02-06 15:46:52 +0100593 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
594 * If ONOS applied a vlan we remove it.
595 *
596 * @param outputPath the output
597 * @param trace the trace we are building
598 */
Andrea Campanella04924b92018-01-17 16:34:51 +0100599
Andrea Campanella58b3b522018-02-06 15:46:52 +0100600 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
601
602 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
603 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
604
605 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
606
607 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
608 //removing the final vlanId
609 finalCriteria.remove(finalVid);
610 Builder packetUpdated = DefaultTrafficSelector.builder();
611 finalCriteria.forEach(packetUpdated::add);
612 //Initial was none so we set it to that
613 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
614 //Update final packet
615 outputPath.setFinalPacket(packetUpdated.build());
616 }
617 }
618
619 /**
Andrea Campanella04924b92018-01-17 16:34:51 +0100620 * Checks if the device has other outputs than the given connect point.
621 *
622 * @param inDeviceId the device
623 * @param trace the trace we are building
624 * @param cp an output connect point
625 * @return true if the device has other outputs.
626 */
627 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
628 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
629 return !groupsInDevice.getOutput().equals(cp);
630 }).count() > 0;
631 }
632
633 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100634 * Checks if the path contains the device.
635 *
636 * @param completePath the path
637 * @param deviceId the device to check
638 * @return true if the path contains the device
639 */
640 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
641 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
642 for (ConnectPoint cp : completePath) {
643 if (cp.deviceId().equals(deviceId)) {
644 return true;
645 }
646 }
647 return false;
648 }
649
650 /**
651 * Gets the hosts for the given initial packet.
652 *
653 * @param trace the trace we are building
654 * @return set of the hosts we are trying to reach
655 */
656 private Set<Host> getHosts(StaticPacketTrace trace) {
657 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
658 .getCriterion(Criterion.Type.IPV4_DST));
659 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
660 .getCriterion(Criterion.Type.IPV6_DST));
661 Set<Host> hosts = new HashSet<>();
662 if (ipv4Criterion != null) {
663 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
664 }
665 if (ipv6Criterion != null) {
666 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
667 }
668 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
669 .getCriterion(Criterion.Type.ETH_DST));
670 if (ethCriterion != null) {
671 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
672 }
673 return hosts;
674 }
675
676 /**
677 * Computes the list of traversed connect points.
678 *
679 * @param completePath the list of devices
680 * @param trace the trace we are building
681 * @param output the final output connect point
682 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100683 private boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100684 List<ConnectPoint> traverseList = new ArrayList<>();
685 if (!completePath.contains(trace.getInitialConnectPoint())) {
686 traverseList.add(trace.getInitialConnectPoint());
687 }
Andrea Campanella67b75602018-04-09 14:22:32 +0200688
689 if (output != null && trace.getInitialConnectPoint().deviceId().equals(output.deviceId())) {
690 trace.addCompletePath(ImmutableList.of(trace.getInitialConnectPoint(), output));
691 return true;
692 }
693
Andrea Campanella01e886e2017-12-15 15:27:31 +0100694 traverseList.addAll(completePath);
695 if (output != null && !completePath.contains(output)) {
696 traverseList.add(output);
697 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100698 if (!trace.getCompletePaths().contains(traverseList)) {
Andrea Campanellaece11772018-03-09 14:52:10 -0800699 trace.addCompletePath(ImmutableList.copyOf(traverseList));
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100700 return true;
701 }
702 return false;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100703 }
704
705 /**
706 * Traces the packet inside a device starting from an input connect point.
707 *
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700708 * @param trace the trace we are building
709 * @param packet the packet we are tracing
710 * @param in the input connect point.
711 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
712 * @param completePath the path up until this device
Andrea Campanella01e886e2017-12-15 15:27:31 +0100713 * @return updated trace
714 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100715 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700716 boolean isDualHomed, List<ConnectPoint> completePath) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100717
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800718 boolean multipleRoutes = false;
719 if (trace.getGroupOuputs(in.deviceId()) != null) {
720 multipleRoutes = multipleRoutes(trace);
721 }
722 if (trace.getGroupOuputs(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100723 log.debug("Trace already contains device and given outputs");
724 return trace;
725 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800726
Andrea Campanella01e886e2017-12-15 15:27:31 +0100727 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100728
729 //if device is not available exit here.
730 if (!deviceService.isAvailable(in.deviceId())) {
731 trace.addResultMessage("Device is offline " + in.deviceId());
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700732 computePath(completePath, trace, null);
Andrea Campanella37d10622018-01-18 17:11:42 +0100733 return trace;
734 }
735
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100736 //handle when the input is the controller
737 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
738 // a packet in from the controller will not actually traverse the pipeline and have no such notion
739 // as the input port.
740 if (in.port().equals(PortNumber.CONTROLLER)) {
741 StaticPacketTrace outputTrace = inputFromController(trace, in);
742 if (outputTrace != null) {
743 return trace;
744 }
745 }
746
Andrea Campanella01e886e2017-12-15 15:27:31 +0100747 List<FlowEntry> flows = new ArrayList<>();
748 List<FlowEntry> outputFlows = new ArrayList<>();
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100749 List<Instruction> deferredInstructions = new ArrayList<>();
750
Andrea Campanella01e886e2017-12-15 15:27:31 +0100751 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
752 if (nextTableIdEntry == null) {
753 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700754 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700755 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100756 return trace;
757 }
758 TableId tableId = nextTableIdEntry.table();
759 FlowEntry flowEntry;
760 boolean output = false;
761 while (!output) {
762 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
763 //get the rule that matches the incoming packet
764 flowEntry = matchHighestPriority(packet, in, tableId);
765 log.debug("Found Flow Entry {}", flowEntry);
766
767 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
768 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
769
770 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
771 if (flowEntry == null && isOfdpaHardware) {
772 log.debug("Ofdpa Hw setup, no flow rule means table miss");
773
Andrea Campanella01e886e2017-12-15 15:27:31 +0100774 if (((IndexTableId) tableId).id() == 27) {
775 //Apparently a miss but Table 27 on OFDPA is a fixed table
776 packet = handleOfdpa27FixedTable(trace, packet);
777 }
778
779 //Finding next table to go In case of miss
780 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
781 log.debug("Next table id entry {}", nextTableIdEntry);
782
783 //FIXME find better solution that enable granularity greater than 0 or all rules
784 //(another possibility is max tableId)
785 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella7382c7f2018-02-05 19:39:25 +0100786 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700787 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700788 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100789 return trace;
790
791 } else if (nextTableIdEntry == null) {
792 //Means that no more flow rules are present
793 output = true;
794
795 } else if (((IndexTableId) tableId).id() == 20) {
796 //if the table is 20 OFDPA skips to table 50
797 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
798 tableId = IndexTableId.of(50);
799
Andrea Campanella14c84e52018-06-05 12:04:02 +0200800 } else if (((IndexTableId) tableId).id() == 40) {
801 //if the table is 40 OFDPA skips to table 60
802 log.debug("A miss on Table 40 on OFDPA means that we skip directly to table 60");
803 tableId = IndexTableId.of(60);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100804 } else {
805 tableId = nextTableIdEntry.table();
806 }
807
Andrea Campanella01e886e2017-12-15 15:27:31 +0100808 } else if (flowEntry == null) {
809 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
810 in.deviceId() + ". Dropping");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700811 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700812 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100813 return trace;
814 } else {
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100815
Andrea Campanella01e886e2017-12-15 15:27:31 +0100816 //IF the table has a transition
817 if (flowEntry.treatment().tableTransition() != null) {
818 //update the next table we transitions to
819 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
820 log.debug("Flow Entry has transition to table Id {}", tableId);
821 flows.add(flowEntry);
822 } else {
823 //table has no transition so it means that it's an output rule if on the last table
824 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
825 flows.add(flowEntry);
826 outputFlows.add(flowEntry);
827 output = true;
828 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100829 //update the packet according to the immediate actions of this flow rule.
830 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
831
832 //save the deferred rules for later
833 deferredInstructions.addAll(flowEntry.treatment().deferred());
834
835 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
836 if (flowEntry.treatment().clearedDeferred()) {
837 deferredInstructions.clear();
838 }
839
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100840 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
841 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
842
843 //Let's get the packet vlanId instruction
844 VlanIdCriterion packetVlanIdCriterion =
845 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
846
847 //Let's get the flow entry vlan mod instructions
848 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
849 .immediate().stream()
850 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
851 .findFirst().orElse(null);
852
853 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
854 // is a flow rule that matches on same criteria and with updated vlanId
855 if (entryModVlanIdInstruction != null) {
856
857 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
858 packetVlanIdCriterion, entryModVlanIdInstruction);
859
860 //We found the flow that we expected
861 if (secondVlanFlow != null) {
862 flows.add(secondVlanFlow);
863 } else {
864 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700865 computePath(completePath, trace, null);
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100866 return trace;
867 }
868 }
869
870 }
871
Andrea Campanella01e886e2017-12-15 15:27:31 +0100872 }
873 }
874
875 //Creating a modifiable builder for the output packet
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100876 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100877 packet.criteria().forEach(builder::add);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100878
Andrea Campanella01e886e2017-12-15 15:27:31 +0100879 //Adding all the flows to the trace
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100880 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100881
Andrea Campanella01e886e2017-12-15 15:27:31 +0100882 List<PortNumber> outputPorts = new ArrayList<>();
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800883 List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100884
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800885
886 log.debug("Handling Groups");
887 //Analyze Groups
888 List<Group> groups = new ArrayList<>();
889
890 Collection<FlowEntry> nonOutputFlows = flows;
891 nonOutputFlows.removeAll(outputFlowEntries);
892
893 //Handling groups pointed at by immediate instructions
894 for (FlowEntry entry : flows) {
895 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700896 entry.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800897 }
898
899 //If we have deferred instructions at this point we handle them.
900 if (deferredInstructions.size() > 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700901 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800902
903 }
904 packet = builder.build();
905
906 log.debug("Output Packet {}", packet);
907 return trace;
908 }
909
910 private List<FlowEntry> handleFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
911 List<FlowEntry> outputFlows, Builder builder, List<PortNumber> outputPorts) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100912 //TODO optimization
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100913 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
914 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
915 .allInstructions().stream().filter(instruction -> instruction.type()
916 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100917
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100918 if (outputFlowEntries.size() > 1) {
919 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
920 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100921 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100922
923 if (outputFlowEntries.size() == 1) {
924
925 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
926 .allInstructions().stream()
927 .filter(instruction -> {
928 return instruction.type().equals(Instruction.Type.OUTPUT);
929 }).findFirst().get();
930
931 //FIXME using GroupsInDevice for output even if flows.
932 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
933
934 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800935 return outputFlowEntries;
936 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100937
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800938 private boolean multipleRoutes(StaticPacketTrace trace) {
939 boolean multipleRoutes = false;
940 IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
941 IpAddress ip = null;
942 if (ipCriterion != null) {
943 ip = ipCriterion.ip().address();
944 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
945 ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100946 }
Andrea Campanellae93f6612018-04-04 13:10:45 +0200947 if (ip != null) {
948 Optional<ResolvedRoute> optionalRoute = routeService.longestPrefixLookup(ip);
949 if (optionalRoute.isPresent()) {
950 ResolvedRoute route = optionalRoute.get();
951 route.prefix();
952 multipleRoutes = routeService.getAllResolvedRoutes(route.prefix()).size() > 1;
953 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100954 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800955 return multipleRoutes;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100956 }
957
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100958 /**
959 * Handles the specific case where the Input is the controller.
960 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
961 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
962 * the flood to all active physical ports of the device.
963 *
964 * @param trace the trace
965 * @param in the controller port
966 * @return the augmented trace.
967 */
968 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
969 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
970 .getCriterion(Criterion.Type.ETH_TYPE);
971 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
972 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
973 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
974 //get the active ports
975 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
976 .filter(Port::isEnabled)
977 .collect(Collectors.toList());
978 //build an output from each one
979 enabledPorts.forEach(port -> {
980 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
981 ImmutableList.of(), trace.getInitialPacket());
982 trace.addGroupOutputPath(in.deviceId(), output);
983 });
984 return trace;
985 }
986 return null;
987 }
988
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100989 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
990 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
991 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
992 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
993 .vlanId().equals(VlanId.NONE);
994 }
995
996 /**
997 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
998 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
999 * second to transition to following table
1000 *
1001 * @param packet the incoming packet
1002 * @param in the input connect point
1003 * @param packetVlanIdCriterion the vlan criterion from the packet
1004 * @param entryModVlanIdInstruction the entry vlan instruction
1005 * @return the second flow entry that matched
1006 */
1007 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
1008 VlanIdCriterion packetVlanIdCriterion,
1009 ModVlanIdInstruction entryModVlanIdInstruction) {
1010 FlowEntry secondVlanFlow = null;
1011 //Check the packet has been update from the first rule.
1012 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
1013 //find a rule on the same table that matches the vlan and
1014 // also all the other elements of the flow such as input port
Andrea Campanellaa3369742018-04-13 12:14:48 +02001015 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntriesByState(in.deviceId(),
1016 FlowEntry.FlowEntryState.ADDED).iterator())
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001017 .stream()
1018 .filter(entry -> {
1019 return entry.table().equals(IndexTableId.of(10));
1020 })
1021 .filter(entry -> {
1022 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
1023 .getCriterion(Criterion.Type.VLAN_VID);
1024 return criterion != null && match(packet, entry)
1025 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
1026 }).findFirst().orElse(null);
1027
1028 }
1029 return secondVlanFlow;
1030 }
1031
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001032
Andrea Campanella01e886e2017-12-15 15:27:31 +01001033 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001034 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
1035 *
1036 * @param packet the incoming packet
1037 * @return the updated packet
1038 */
1039 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
1040 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
1041 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
1042 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
1043
1044 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
1045 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
1046 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001047 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanella01e886e2017-12-15 15:27:31 +01001048 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
1049 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
1050 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001051 //translateInstruction(builder, ethInstruction);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001052 builder.add(ethInstruction);
1053 }
1054 packet = updatePacket(packet, builder.build()).build();
1055 return packet;
1056 }
1057
1058 /**
1059 * Finds the flow entry with the minimun next table Id.
1060 *
1061 * @param deviceId the device to search
1062 * @param currentId the current id. the search will use this as minimum
1063 * @return the flow entry with the minimum table Id after the given one.
1064 */
1065 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
1066
1067 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
1068
Andrea Campanellaa3369742018-04-13 12:14:48 +02001069 return Lists.newArrayList(flowRuleService.getFlowEntriesByState(deviceId, FlowEntry.FlowEntryState.ADDED)
1070 .iterator()).stream()
1071 .filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001072 }
1073
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001074 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
1075 ConnectPoint in, List<Instruction> deferredInstructions,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001076 List<PortNumber> outputPorts, List<Group> groups,
1077 List<ConnectPoint> completePath) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001078
1079 //Update the packet with the deferred instructions
1080 Builder builder = updatePacket(packet, deferredInstructions);
1081
1082 //Gather any output instructions from the deferred instruction
1083 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
1084 return instruction.type().equals(Instruction.Type.OUTPUT);
1085 }).collect(Collectors.toList());
1086
1087 //We are considering deferred instructions from flows, there can only be one output.
1088 if (outputFlowInstruction.size() > 1) {
1089 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
1090 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
1091 }
1092 //If there is one output let's go through that
1093 if (outputFlowInstruction.size() == 1) {
1094 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
1095 ImmutableList.of());
1096 }
1097 //If there is no output let's see if there any deferred instruction point to groups.
1098 if (outputFlowInstruction.size() == 0) {
1099 getGroupsFromInstructions(trace, groups, deferredInstructions,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001100 in.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001101 }
1102 return builder;
1103 }
1104
Andrea Campanella01e886e2017-12-15 15:27:31 +01001105 /**
1106 * Gets group information from instructions.
1107 *
1108 * @param trace the trace we are building
1109 * @param groupsForDevice the set of groups for this device
1110 * @param instructions the set of instructions we are searching for groups.
1111 * @param deviceId the device we are considering
1112 * @param builder the builder of the input packet
1113 * @param outputPorts the output ports for that packet
1114 */
1115 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
1116 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001117 Builder builder, List<PortNumber> outputPorts,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001118 ConnectPoint in, List<ConnectPoint> completePath) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001119 List<Instruction> groupInstructionlist = new ArrayList<>();
1120 for (Instruction instruction : instructions) {
1121 log.debug("Considering Instruction {}", instruction);
1122 //if the instruction is not group we need to update the packet or add the output
1123 //to the possible outputs for this packet
1124 if (!instruction.type().equals(Instruction.Type.GROUP)) {
1125 //if the instruction is not group we need to update the packet or add the output
1126 //to the possible outputs for this packet
1127 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001128 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella3f98c212018-02-19 17:03:46 +01001129 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
1130 //clearing the groups because we start from the top.
1131 groupsForDevice.clear();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001132 } else {
1133 builder = translateInstruction(builder, instruction);
1134 }
1135 } else {
1136 //if the instuction is pointing to a group we need to get the group
1137 groupInstructionlist.add(instruction);
1138 }
1139 }
1140 //handle all the internal instructions pointing to a group.
1141 for (Instruction instr : groupInstructionlist) {
1142 GroupInstruction groupInstruction = (GroupInstruction) instr;
1143 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
1144 return groupInternal.id().equals(groupInstruction.groupId());
1145 }).findAny().orElse(null);
1146 if (group == null) {
1147 trace.addResultMessage("Null group for Instruction " + instr);
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001148 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001149 break;
1150 }
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001151 if (group.buckets().buckets().size() == 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001152 trace.addResultMessage("Group " + group.id() + " has no buckets");
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001153 trace.setSuccess(false);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001154 computePath(completePath, trace, null);
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001155 break;
1156 }
Andrea Campanella3f98c212018-02-19 17:03:46 +01001157
Andrea Campanella01e886e2017-12-15 15:27:31 +01001158 //Cycle in each of the group's buckets and add them to the groups for this Device.
1159 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella3f98c212018-02-19 17:03:46 +01001160
1161 //add the group to the traversed groups
1162 if (!groupsForDevice.contains(group)) {
1163 groupsForDevice.add(group);
1164 }
1165
Andrea Campanella01e886e2017-12-15 15:27:31 +01001166 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001167 deviceId, builder, outputPorts, in, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001168 }
1169 }
1170 }
1171
1172 /**
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001173 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1174 * a possible output from this device.
1175 *
1176 * @param trace the trace
1177 * @param in the input connect point
1178 * @param builder the packet builder
1179 * @param outputPorts the list of output ports for this device
1180 * @param outputInstruction the output instruction
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001181 * @param groupsForDevice the groups we output from
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001182 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001183 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001184 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1185 List<Group> groupsForDevice) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +01001186 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella6f2d6742018-02-07 12:00:12 +01001187
Andrea Campanella7b84c072018-03-06 15:21:09 -08001188 outputPorts.add(outputInstruction.port());
1189
1190 GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
1191 if (trace.getGroupOuputs(output.deviceId()) != null
1192 && trace.getGroupOuputs(output.deviceId()).contains(device)) {
1193 return;
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001194 }
Andrea Campanella7b84c072018-03-06 15:21:09 -08001195 trace.addGroupOutputPath(in.deviceId(),
1196 new GroupsInDevice(output, groupsForDevice, builder.build()));
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001197 }
1198
1199 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001200 * Applies all give instructions to the input packet.
1201 *
1202 * @param packet the input packet
1203 * @param instructions the set of instructions
1204 * @return the packet with the applied instructions
1205 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001206 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1207 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001208 packet.criteria().forEach(newSelector::add);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001209 //FIXME optimize
1210 for (Instruction instruction : instructions) {
1211 newSelector = translateInstruction(newSelector, instruction);
1212 }
Andrea Campanella01e886e2017-12-15 15:27:31 +01001213 return newSelector;
1214 }
1215
1216 /**
1217 * Applies an instruction to the packet in the form of a selector.
1218 *
1219 * @param newSelector the packet selector
1220 * @param instruction the instruction to be translated
1221 * @return the new selector with the applied instruction
1222 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001223 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001224 log.debug("Translating instruction {}", instruction);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001225 log.debug("New Selector {}", newSelector.build());
Andrea Campanella01e886e2017-12-15 15:27:31 +01001226 //TODO add as required
1227 Criterion criterion = null;
1228 switch (instruction.type()) {
1229 case L2MODIFICATION:
1230 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1231 switch (l2Instruction.subtype()) {
1232 case VLAN_ID:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001233 ModVlanIdInstruction vlanIdInstruction =
1234 (ModVlanIdInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001235 VlanId id = vlanIdInstruction.vlanId();
1236 criterion = Criteria.matchVlanId(id);
1237 break;
1238 case VLAN_POP:
1239 criterion = Criteria.matchVlanId(VlanId.NONE);
1240 break;
1241 case MPLS_PUSH:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001242 ModMplsHeaderInstruction mplsEthInstruction =
1243 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001244 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1245 break;
1246 case MPLS_POP:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001247 ModMplsHeaderInstruction mplsPopInstruction =
1248 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001249 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001250
1251 //When popping MPLS we remove label and BOS
1252 TrafficSelector temporaryPacket = newSelector.build();
1253 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001254 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001255 temporaryPacket.criteria().stream().filter(c -> {
1256 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1257 !c.type().equals(Criterion.Type.MPLS_BOS);
1258 }).forEach(noMplsSelector::add);
1259 newSelector = noMplsSelector;
1260 }
1261
Andrea Campanella01e886e2017-12-15 15:27:31 +01001262 break;
1263 case MPLS_LABEL:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001264 ModMplsLabelInstruction mplsLabelInstruction =
1265 (ModMplsLabelInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001266 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001267 newSelector.matchMplsBos(true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001268 break;
1269 case ETH_DST:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001270 ModEtherInstruction modEtherDstInstruction =
1271 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001272 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1273 break;
1274 case ETH_SRC:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001275 ModEtherInstruction modEtherSrcInstruction =
1276 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001277 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1278 break;
1279 default:
1280 log.debug("Unsupported L2 Instruction");
1281 break;
1282 }
1283 break;
1284 default:
1285 log.debug("Unsupported Instruction");
1286 break;
1287 }
1288 if (criterion != null) {
1289 log.debug("Adding criterion {}", criterion);
1290 newSelector.add(criterion);
1291 }
1292 return newSelector;
1293 }
1294
1295 /**
1296 * Finds the rule in the device that mathces the input packet and has the highest priority.
1297 *
1298 * @param packet the input packet
1299 * @param in the connect point the packet comes in from
1300 * @param tableId the table to search
1301 * @return the flow entry
1302 */
1303 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1304 //Computing the possible match rules.
1305 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
Andrea Campanellaa3369742018-04-13 12:14:48 +02001306 return Lists.newArrayList(flowRuleService.getFlowEntriesByState(in.deviceId(), FlowEntry.FlowEntryState.ADDED)
1307 .iterator()).stream()
Andrea Campanella01e886e2017-12-15 15:27:31 +01001308 .filter(flowEntry -> {
1309 return flowEntry.table().equals(tableId);
1310 })
1311 .filter(flowEntry -> {
1312 return match(packet, flowEntry);
1313 }).max(comparator).orElse(null);
1314 }
1315
1316 /**
1317 * Matches the packet with the given flow entry.
1318 *
1319 * @param packet the packet to match
1320 * @param flowEntry the flow entry to match the packet against
1321 * @return true if the packet matches the flow.
1322 */
1323 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001324 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1325 Criterion.Type type = criterion.type();
Andrea Campanella4ee4af12018-01-31 12:20:48 +01001326 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanella01e886e2017-12-15 15:27:31 +01001327 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1328 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella04924b92018-01-17 16:34:51 +01001329 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001330 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella04924b92018-01-17 16:34:51 +01001331 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1332 return matchMac(packet, (EthCriterion) criterion, false);
1333 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1334 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001335 } else {
1336 return packet.criteria().contains(criterion);
1337 }
1338 });
Simon Hunt6fefd852017-11-13 17:09:43 -08001339 }
Andrea Campanella04924b92018-01-17 16:34:51 +01001340
1341 /**
1342 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1343 *
1344 * @param packet the incoming packet
1345 * @param criterion the criterion to match
1346 * @return true if match
1347 */
1348 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1349 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1350 //if the packet does not have an IPv4 or IPv6 criterion we return true
1351 if (matchCriterion == null) {
1352 return false;
1353 }
1354 try {
1355 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1356 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1357 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1358 } catch (UnknownHostException e) {
1359 return false;
1360 }
1361 }
1362
1363 /**
1364 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1365 *
1366 * @param packet the incoming packet
1367 * @param hitCriterion the criterion to match
1368 * @param dst true if we are checking DST MAC
1369 * @return true if match
1370 */
1371 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1372 //Packet can have only one EthCriterion
1373 EthCriterion matchCriterion;
1374 if (dst) {
1375 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1376 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1377 criterion1.type().equals(Criterion.Type.ETH_DST);
1378 }).findFirst().orElse(null);
1379 } else {
1380 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1381 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1382 criterion1.type().equals(Criterion.Type.ETH_SRC);
1383 }).findFirst().orElse(null);
1384 }
1385 //if the packet does not have an ETH criterion we return true
1386 if (matchCriterion == null) {
1387 return true;
1388 }
1389 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1390 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1391 }
Simon Hunt6fefd852017-11-13 17:09:43 -08001392}