blob: e46e260bda2a107aa8eba4efa93515200d9e3e14 [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;
Simon Hunt6fefd852017-11-13 17:09:43 -080025import org.apache.felix.scr.annotations.Component;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010029import org.onlab.packet.IpAddress;
Andrea Campanella01e886e2017-12-15 15:27:31 +010030import org.onlab.packet.VlanId;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010031import org.onosproject.cluster.NodeId;
32import org.onosproject.mastership.MastershipService;
Andrea Campanellafa3ec192018-04-06 16:30:18 +020033import org.onosproject.mcast.api.MulticastRouteService;
Simon Hunt6fefd852017-11-13 17:09:43 -080034import org.onosproject.net.ConnectPoint;
Andrea Campanella01e886e2017-12-15 15:27:31 +010035import org.onosproject.net.DeviceId;
36import org.onosproject.net.Host;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010037import org.onosproject.net.HostId;
Andrea Campanella01e886e2017-12-15 15:27:31 +010038import org.onosproject.net.Link;
Andrea Campanella6f2d6742018-02-07 12:00:12 +010039import org.onosproject.net.Port;
Andrea Campanella01e886e2017-12-15 15:27:31 +010040import org.onosproject.net.PortNumber;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010041import org.onosproject.net.config.ConfigException;
42import org.onosproject.net.config.NetworkConfigService;
43import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella37d10622018-01-18 17:11:42 +010044import org.onosproject.net.device.DeviceService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010045import org.onosproject.net.driver.DriverService;
Andrea Campanella04924b92018-01-17 16:34:51 +010046import org.onosproject.net.edge.EdgePortService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010047import org.onosproject.net.flow.DefaultTrafficSelector;
48import org.onosproject.net.flow.FlowEntry;
49import org.onosproject.net.flow.FlowRule;
Simon Hunt6fefd852017-11-13 17:09:43 -080050import org.onosproject.net.flow.FlowRuleService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010051import org.onosproject.net.flow.IndexTableId;
52import org.onosproject.net.flow.TableId;
Simon Hunt6fefd852017-11-13 17:09:43 -080053import org.onosproject.net.flow.TrafficSelector;
Andrea Campanella01e886e2017-12-15 15:27:31 +010054import org.onosproject.net.flow.criteria.Criteria;
55import org.onosproject.net.flow.criteria.Criterion;
56import org.onosproject.net.flow.criteria.EthCriterion;
57import org.onosproject.net.flow.criteria.EthTypeCriterion;
58import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanella58b3b522018-02-06 15:46:52 +010059import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanella01e886e2017-12-15 15:27:31 +010060import org.onosproject.net.flow.instructions.Instruction;
61import org.onosproject.net.flow.instructions.Instructions;
62import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
63import org.onosproject.net.flow.instructions.L2ModificationInstruction;
64import org.onosproject.net.group.Group;
65import org.onosproject.net.group.GroupBucket;
Simon Hunt6fefd852017-11-13 17:09:43 -080066import org.onosproject.net.group.GroupService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010067import org.onosproject.net.host.HostService;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010068import org.onosproject.net.host.InterfaceIpAddress;
69import org.onosproject.net.intf.Interface;
Andrea Campanella01e886e2017-12-15 15:27:31 +010070import org.onosproject.net.link.LinkService;
Andrea Campanellacc2424a2018-03-07 14:27:54 -080071import org.onosproject.routeservice.ResolvedRoute;
72import org.onosproject.routeservice.RouteService;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010073import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanella01e886e2017-12-15 15:27:31 +010074import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt6fefd852017-11-13 17:09:43 -080075import org.onosproject.t3.api.StaticPacketTrace;
76import org.onosproject.t3.api.TroubleshootService;
77import org.slf4j.Logger;
78
Andrea Campanella01e886e2017-12-15 15:27:31 +010079import java.net.UnknownHostException;
80import java.util.ArrayList;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010081import java.util.Collection;
Andrea Campanella01e886e2017-12-15 15:27:31 +010082import java.util.Collections;
83import java.util.Comparator;
84import java.util.HashSet;
85import java.util.List;
Andrea Campanellacc2424a2018-03-07 14:27:54 -080086import java.util.Optional;
Andrea Campanella01e886e2017-12-15 15:27:31 +010087import java.util.Set;
88import java.util.stream.Collectors;
89
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 */
105@Service
106@Component(immediate = true)
107public class TroubleshootManager implements TroubleshootService {
108
109 private static final Logger log = getLogger(TroubleshootManager.class);
110
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100111 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
112
Simon Hunt6fefd852017-11-13 17:09:43 -0800113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected FlowRuleService flowRuleService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected GroupService groupService;
118
Andrea Campanella01e886e2017-12-15 15:27:31 +0100119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120 protected LinkService linkService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800121
Andrea Campanella01e886e2017-12-15 15:27:31 +0100122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected HostService hostService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected DriverService driverService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800127
Andrea Campanella37d10622018-01-18 17:11:42 +0100128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected DeviceService deviceService;
130
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
132 protected MastershipService mastershipService;
133
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
135 protected NetworkConfigService networkConfigService;
136
Andrea Campanella04924b92018-01-17 16:34:51 +0100137 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
138 protected EdgePortService edgePortService;
139
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800140 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
141 protected RouteService routeService;
142
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200143 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
144 protected MulticastRouteService mcastService;
145
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100146 @Override
Andrea Campanella6be5c872018-02-21 14:28:20 +0100147 public List<StaticPacketTrace> pingAll(EtherType type) {
148 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
149 hostService.getHosts().forEach(host -> {
150 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
151 if (ipAddresses.size() > 0) {
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100152 //check if the host has only local IPs of that ETH type
153 boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
Andrea Campanella6be5c872018-02-21 14:28:20 +0100154 hostService.getHosts().forEach(hostToPing -> {
155 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100156 //check if the other host has only local IPs of that ETH type
157 boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
158 boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
159 //Trace is done only if they are both local and under the same location
160 // or not local and if they are not the same host.
161 if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
162 (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
163 && !host.equals(hostToPing)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100164 tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
Andrea Campanella6be5c872018-02-21 14:28:20 +0100165 }
166 });
167 }
168 });
169 return tracesBuilder.build();
170 }
171
172 @Override
Andrea Campanella41de8062018-02-28 16:43:16 +0100173 public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
174 return new PingAllGenerator(type, hostService, this);
175 }
176
177 @Override
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200178 public Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId) {
179 return new McastGenerator(mcastService, this, vlanId);
180 }
181
182 @Override
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100183 public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100184 Host source = hostService.getHost(sourceHost);
185 Host destination = hostService.getHost(destinationHost);
186
Andrea Campanella6be5c872018-02-21 14:28:20 +0100187 //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
188 StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100189
190 if (source == null) {
191 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700192 failTrace.setSuccess(false);
193
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100194 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100195 }
196
197 if (destination == null) {
198 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700199 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100200 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100201 }
202
203 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100204 .matchEthType(etherType.ethType().toShort())
205 .matchEthDst(source.mac())
206 .matchVlanId(source.vlan());
207
208
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100209 try {
Andrea Campanella15278a12018-03-26 10:39:22 -0700210 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100211 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
212 // we are under same leaf so it's L2 Unicast.
213 if (areBridged(source, destination)) {
214 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100215 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800216 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100217 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
218 trace.addEndpointHosts(Pair.of(source, destination));
219 traces.add(trace);
220 });
Andrea Campanella15278a12018-03-26 10:39:22 -0700221 //The destination host is not dual homed, if it is the other path might be done through routing.
222 if (destination.locations().size() == 1) {
223 return traces.build();
224 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100225 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100226 //handle the IPs for src and dst in case of L3
227 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
228
229 //Match on the source IP
230 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100231 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100232 }
233
234 //Match on destination IP
235 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100236 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100237 }
238
239 } else {
240 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
241 "please use packet based");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700242 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100243 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100244 }
245
246 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
247 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
248 .deviceId(), SegmentRoutingDeviceConfig.class);
249 if (segmentRoutingConfig != null) {
250 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
251 } else {
252 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
253 " router MAC from segment routing config can't perform L3 tracing.");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700254 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100255 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100256 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800257 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100258 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
259 trace.addEndpointHosts(Pair.of(source, destination));
260 traces.add(trace);
261 });
262 return traces.build();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100263
264 } catch (ConfigException e) {
265 failTrace.addResultMessage("Can't get config " + e.getMessage());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100266 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100267 }
268 }
269
270 /**
271 * Matches src and dst IPs based on host information.
272 *
273 * @param host the host
274 * @param failTrace the trace to use in case of failure
275 * @param selectorBuilder the packet we are building to trace
276 * @param etherType the traffic type
277 * @param src is this src host or dst host
278 * @return true if properly matched
279 */
280 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
281 EtherType etherType, boolean src) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100282 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100283
284 if (ips.size() > 0) {
Andrea Campanella465c7be2018-02-21 14:43:21 +0100285 if (etherType.equals(EtherType.IPV4)) {
286 if (src) {
287 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
288 } else {
289 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
290 }
291 } else if (etherType.equals(EtherType.IPV6)) {
292 if (src) {
293 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
294 } else {
295 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
296 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100297 }
298 } else {
299 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700300 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100301 return false;
302 }
303 return true;
304 }
305
Andrea Campanella41de8062018-02-28 16:43:16 +0100306 List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100307 return host.ipAddresses().stream().filter(ipAddress -> {
308 boolean correctIp = false;
309 if (etherType.equals(EtherType.IPV4)) {
310 correctIp = ipAddress.isIp4();
311 } else if (etherType.equals(EtherType.IPV6)) {
312 correctIp = ipAddress.isIp6();
313 }
314 if (checklocal) {
315 correctIp = correctIp && !ipAddress.isLinkLocal();
316 }
317 return correctIp;
318 }).collect(Collectors.toList());
319 }
320
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100321 /**
322 * Checks that two hosts are bridged (L2Unicast).
323 *
324 * @param source the source host
325 * @param destination the destination host
326 * @return true if bridged.
327 * @throws ConfigException if config can't be properly retrieved
328 */
329 private boolean areBridged(Host source, Host destination) throws ConfigException {
330
Andrea Campanella7b84c072018-03-06 15:21:09 -0800331 //If the locations is not the same we don't even check vlan or subnets
332 if (Collections.disjoint(source.locations(), destination.locations())) {
333 return false;
334 }
335
336 if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
337 && !source.vlan().equals(destination.vlan())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100338 return false;
339 }
340
341 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
342 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
343 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
344
345 //following can be optimized but for clarity is left as is
346 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
347 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
348
Andrea Campanella7b84c072018-03-06 15:21:09 -0800349 if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
350 return intfH1.vlanUntagged().equals(destination.vlan()) ||
351 intfH1.vlanNative().equals(destination.vlan());
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100352 }
353
Andrea Campanella7b84c072018-03-06 15:21:09 -0800354 if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
355 return intfH2.vlanUntagged().equals(source.vlan()) ||
356 intfH2.vlanNative().equals(source.vlan());
357 }
358
359 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100360 return false;
361 }
362
363 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
364 return false;
365 }
366
367 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
368 intersection.retainAll(intfH2.ipAddressesList());
369 if (intersection.size() == 0) {
370 return false;
371 }
372 }
373 return true;
374 }
375
Simon Hunt6fefd852017-11-13 17:09:43 -0800376 @Override
377 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100378 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100379 //device must exist in ONOS
380 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
381 "Device " + in.deviceId() + " must exist in ONOS");
382
Andrea Campanella01e886e2017-12-15 15:27:31 +0100383 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100384 boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100385 //FIXME this can be done recursively
Andrea Campanella01e886e2017-12-15 15:27:31 +0100386 //Building output connect Points
387 List<ConnectPoint> path = new ArrayList<>();
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700388 trace = traceInDevice(trace, packet, in, isDualHomed, path);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100389 trace = getTrace(path, in, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100390 return trace;
391 }
392
393 /**
394 * Computes a trace for a give packet that start in the network at the given connect point.
395 *
396 * @param completePath the path traversed by the packet
397 * @param in the input connect point
398 * @param trace the trace to build
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100399 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanella01e886e2017-12-15 15:27:31 +0100400 * @return the build trace for that packet.
401 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100402 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
403 boolean isDualHomed) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100404
Andrea Campanellae04aac92018-01-31 14:59:03 +0100405 log.debug("------------------------------------------------------------");
406
Andrea Campanella01e886e2017-12-15 15:27:31 +0100407 //if the trace already contains the input connect point there is a loop
408 if (pathContainsDevice(completePath, in.deviceId())) {
409 trace.addResultMessage("Loop encountered in device " + in.deviceId());
Andrea Campanellaece11772018-03-09 14:52:10 -0800410 completePath.add(in);
411 trace.addCompletePath(completePath);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700412 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100413 return trace;
414 }
415
416 //let's add the input connect point
417 completePath.add(in);
418
419 //If the trace has no outputs for the given input we stop here
420 if (trace.getGroupOuputs(in.deviceId()) == null) {
421 computePath(completePath, trace, null);
422 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700423 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100424 return trace;
425 }
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100426
Andrea Campanella67b75602018-04-09 14:22:32 +0200427 //If the trace has outputs we analyze them all
Andrea Campanella01e886e2017-12-15 15:27:31 +0100428 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100429
430 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellae04aac92018-01-31 14:59:03 +0100431 log.debug("Connect point in {}", in);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100432 log.debug("Output path {}", cp);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800433 log.debug("{}", outputPath.getFinalPacket());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100434
Andrea Campanella01e886e2017-12-15 15:27:31 +0100435 //Hosts for the the given output
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100436 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100437 //Hosts queried from the original ip or mac
438 Set<Host> hosts = getHosts(trace);
439
Andrea Campanella7b84c072018-03-06 15:21:09 -0800440 if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
441 outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
442 && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
443 .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
444 .vlanId())) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700445 if (trace.getGroupOuputs(in.deviceId()).size() == 1 &&
446 computePath(completePath, trace, outputPath.getOutput())) {
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700447 trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700448 trace.setSuccess(false);
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700449 }
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700450 } else if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700451 //If the two host collections contain the same item it means we reached the proper output
Andrea Campanellae04aac92018-01-31 14:59:03 +0100452 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100453 if (computePath(completePath, trace, outputPath.getOutput())) {
454 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella41de8062018-02-28 16:43:16 +0100455 trace.setSuccess(true);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100456 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100457 break;
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100458 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100459
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100460 //Getting the master when the packet gets sent as packet in
461 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100462 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100463 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella58b3b522018-02-06 15:46:52 +0100464 handleVlanToController(outputPath, trace);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100465
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100466 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100467
468 //TODO this can be optimized if we use a Tree structure for paths.
469 //if we already have outputs let's check if the one we are considering starts from one of the devices
470 // in any of the ones we have.
471 if (trace.getCompletePaths().size() > 0) {
472 ConnectPoint inputForOutput = null;
473 List<ConnectPoint> previousPath = new ArrayList<>();
474 for (List<ConnectPoint> path : trace.getCompletePaths()) {
475 for (ConnectPoint connect : path) {
476 //if the path already contains the input for the output we've found we use it
477 if (connect.equals(in)) {
478 inputForOutput = connect;
479 previousPath = path;
480 break;
481 }
482 }
483 }
Andrea Campanellaece11772018-03-09 14:52:10 -0800484
Andrea Campanellae04aac92018-01-31 14:59:03 +0100485 //we use the pre-existing path up to the point we fork to a new output
486 if (inputForOutput != null && completePath.contains(inputForOutput)) {
487 List<ConnectPoint> temp = new ArrayList<>(previousPath);
Andrea Campanellaece11772018-03-09 14:52:10 -0800488 temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
489 if (completePath.containsAll(temp)) {
490 completePath = temp;
491 }
Andrea Campanellae04aac92018-01-31 14:59:03 +0100492 }
493 }
494
Andrea Campanella01e886e2017-12-15 15:27:31 +0100495 //let's add the ouput for the input
496 completePath.add(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100497 //let's compute the links for the given output
498 Set<Link> links = linkService.getEgressLinks(cp);
499 log.debug("Egress Links {}", links);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100500 //For each link we trace the corresponding device
501 for (Link link : links) {
502 ConnectPoint dst = link.dst();
503 //change in-port to the dst link in port
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100504 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100505 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
506 updatedPacket.add(Criteria.matchInPort(dst.port()));
507 log.debug("DST Connect Point {}", dst);
508 //build the elements for that device
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700509 traceInDevice(trace, updatedPacket.build(), dst, isDualHomed, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100510 //continue the trace along the path
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100511 getTrace(completePath, dst, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100512 }
Andrea Campanella04924b92018-01-17 16:34:51 +0100513 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
514 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
515 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
516 .mac().isMulticast()) {
517 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
518 " which is enabled and is edge port");
Andrea Campanella41de8062018-02-28 16:43:16 +0100519 trace.setSuccess(true);
Andrea Campanella04924b92018-01-17 16:34:51 +0100520 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella04924b92018-01-17 16:34:51 +0100521 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
522 return trace;
523 }
Andrea Campanella15278a12018-03-26 10:39:22 -0700524 } else if (deviceService.getPort(cp) != null && deviceService.getPort(cp).isEnabled()) {
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100525 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
526 .getCriterion(Criterion.Type.ETH_TYPE);
527 //We treat as correct output only if it's not LLDP or BDDP
528 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
Andrea Campanella62841d42018-02-27 12:42:28 +0100529 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800530 if (computePath(completePath, trace, outputPath.getOutput())) {
531 if (hostsList.isEmpty()) {
532 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
533 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
534 cp + " with no hosts connected ");
535 } else {
536 IpAddress ipAddress = null;
537 if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
538 ipAddress = ((IPCriterion) trace.getInitialPacket()
539 .getCriterion(Criterion.Type.IPV4_DST)).ip().address();
540 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
541 ipAddress = ((IPCriterion) trace.getInitialPacket()
542 .getCriterion(Criterion.Type.IPV6_DST)).ip().address();
543 }
544 if (ipAddress != null) {
545 IpAddress finalIpAddress = ipAddress;
546 if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
547 hostService.getHostsByIp(finalIpAddress).isEmpty()) {
548 trace.addResultMessage("Packet is " +
549 ((EthTypeCriterion) outputPath.getFinalPacket()
Andrea Campanella220f19b2018-03-09 15:30:22 -0800550 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
551 " and reached " + cp + " with hosts " + hostsList);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800552 } else {
553 trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
554 ipAddress);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700555 trace.setSuccess(false);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800556 }
557 } else {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800558 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
559 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
560 cp + " with hosts " + hostsList);
Andrea Campanella7b84c072018-03-06 15:21:09 -0800561 }
Andrea Campanella7b84c072018-03-06 15:21:09 -0800562 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800563 trace.setSuccess(true);
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100564 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100565 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100566
567 } else {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100568 computePath(completePath, trace, cp);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700569 trace.setSuccess(false);
Andrea Campanella15278a12018-03-26 10:39:22 -0700570 if (deviceService.getPort(cp) == null) {
571 //Port is not existant on device.
572 log.warn("Port {} is not available on device.", cp);
573 trace.addResultMessage("Port " + cp + "is not available on device. Packet is dropped");
574 } else {
575 //No links means that the packet gets dropped.
576 log.warn("No links out of {}", cp);
577 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
578 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100579 }
580 }
581 return trace;
582 }
583
Andrea Campanella04924b92018-01-17 16:34:51 +0100584
Andrea Campanella01e886e2017-12-15 15:27:31 +0100585 /**
Andrea Campanella58b3b522018-02-06 15:46:52 +0100586 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
587 * If ONOS applied a vlan we remove it.
588 *
589 * @param outputPath the output
590 * @param trace the trace we are building
591 */
Andrea Campanella04924b92018-01-17 16:34:51 +0100592
Andrea Campanella58b3b522018-02-06 15:46:52 +0100593 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
594
595 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
596 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
597
598 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
599
600 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
601 //removing the final vlanId
602 finalCriteria.remove(finalVid);
603 Builder packetUpdated = DefaultTrafficSelector.builder();
604 finalCriteria.forEach(packetUpdated::add);
605 //Initial was none so we set it to that
606 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
607 //Update final packet
608 outputPath.setFinalPacket(packetUpdated.build());
609 }
610 }
611
612 /**
Andrea Campanella04924b92018-01-17 16:34:51 +0100613 * Checks if the device has other outputs than the given connect point.
614 *
615 * @param inDeviceId the device
616 * @param trace the trace we are building
617 * @param cp an output connect point
618 * @return true if the device has other outputs.
619 */
620 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
621 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
622 return !groupsInDevice.getOutput().equals(cp);
623 }).count() > 0;
624 }
625
626 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100627 * Checks if the path contains the device.
628 *
629 * @param completePath the path
630 * @param deviceId the device to check
631 * @return true if the path contains the device
632 */
633 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
634 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
635 for (ConnectPoint cp : completePath) {
636 if (cp.deviceId().equals(deviceId)) {
637 return true;
638 }
639 }
640 return false;
641 }
642
643 /**
644 * Gets the hosts for the given initial packet.
645 *
646 * @param trace the trace we are building
647 * @return set of the hosts we are trying to reach
648 */
649 private Set<Host> getHosts(StaticPacketTrace trace) {
650 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
651 .getCriterion(Criterion.Type.IPV4_DST));
652 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
653 .getCriterion(Criterion.Type.IPV6_DST));
654 Set<Host> hosts = new HashSet<>();
655 if (ipv4Criterion != null) {
656 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
657 }
658 if (ipv6Criterion != null) {
659 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
660 }
661 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
662 .getCriterion(Criterion.Type.ETH_DST));
663 if (ethCriterion != null) {
664 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
665 }
666 return hosts;
667 }
668
669 /**
670 * Computes the list of traversed connect points.
671 *
672 * @param completePath the list of devices
673 * @param trace the trace we are building
674 * @param output the final output connect point
675 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100676 private boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100677 List<ConnectPoint> traverseList = new ArrayList<>();
678 if (!completePath.contains(trace.getInitialConnectPoint())) {
679 traverseList.add(trace.getInitialConnectPoint());
680 }
Andrea Campanella67b75602018-04-09 14:22:32 +0200681
682 if (output != null && trace.getInitialConnectPoint().deviceId().equals(output.deviceId())) {
683 trace.addCompletePath(ImmutableList.of(trace.getInitialConnectPoint(), output));
684 return true;
685 }
686
Andrea Campanella01e886e2017-12-15 15:27:31 +0100687 traverseList.addAll(completePath);
688 if (output != null && !completePath.contains(output)) {
689 traverseList.add(output);
690 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100691 if (!trace.getCompletePaths().contains(traverseList)) {
Andrea Campanellaece11772018-03-09 14:52:10 -0800692 trace.addCompletePath(ImmutableList.copyOf(traverseList));
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100693 return true;
694 }
695 return false;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100696 }
697
698 /**
699 * Traces the packet inside a device starting from an input connect point.
700 *
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700701 * @param trace the trace we are building
702 * @param packet the packet we are tracing
703 * @param in the input connect point.
704 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
705 * @param completePath the path up until this device
Andrea Campanella01e886e2017-12-15 15:27:31 +0100706 * @return updated trace
707 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100708 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700709 boolean isDualHomed, List<ConnectPoint> completePath) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100710
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800711 boolean multipleRoutes = false;
712 if (trace.getGroupOuputs(in.deviceId()) != null) {
713 multipleRoutes = multipleRoutes(trace);
714 }
715 if (trace.getGroupOuputs(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100716 log.debug("Trace already contains device and given outputs");
717 return trace;
718 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800719
Andrea Campanella01e886e2017-12-15 15:27:31 +0100720 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100721
722 //if device is not available exit here.
723 if (!deviceService.isAvailable(in.deviceId())) {
724 trace.addResultMessage("Device is offline " + in.deviceId());
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700725 computePath(completePath, trace, null);
Andrea Campanella37d10622018-01-18 17:11:42 +0100726 return trace;
727 }
728
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100729 //handle when the input is the controller
730 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
731 // a packet in from the controller will not actually traverse the pipeline and have no such notion
732 // as the input port.
733 if (in.port().equals(PortNumber.CONTROLLER)) {
734 StaticPacketTrace outputTrace = inputFromController(trace, in);
735 if (outputTrace != null) {
736 return trace;
737 }
738 }
739
Andrea Campanella01e886e2017-12-15 15:27:31 +0100740 List<FlowEntry> flows = new ArrayList<>();
741 List<FlowEntry> outputFlows = new ArrayList<>();
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100742 List<Instruction> deferredInstructions = new ArrayList<>();
743
Andrea Campanella01e886e2017-12-15 15:27:31 +0100744 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
745 if (nextTableIdEntry == null) {
746 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700747 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700748 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100749 return trace;
750 }
751 TableId tableId = nextTableIdEntry.table();
752 FlowEntry flowEntry;
753 boolean output = false;
754 while (!output) {
755 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
756 //get the rule that matches the incoming packet
757 flowEntry = matchHighestPriority(packet, in, tableId);
758 log.debug("Found Flow Entry {}", flowEntry);
759
760 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
761 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
762
763 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
764 if (flowEntry == null && isOfdpaHardware) {
765 log.debug("Ofdpa Hw setup, no flow rule means table miss");
766
Andrea Campanella01e886e2017-12-15 15:27:31 +0100767 if (((IndexTableId) tableId).id() == 27) {
768 //Apparently a miss but Table 27 on OFDPA is a fixed table
769 packet = handleOfdpa27FixedTable(trace, packet);
770 }
771
772 //Finding next table to go In case of miss
773 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
774 log.debug("Next table id entry {}", nextTableIdEntry);
775
776 //FIXME find better solution that enable granularity greater than 0 or all rules
777 //(another possibility is max tableId)
778 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella7382c7f2018-02-05 19:39:25 +0100779 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700780 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700781 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100782 return trace;
783
784 } else if (nextTableIdEntry == null) {
785 //Means that no more flow rules are present
786 output = true;
787
788 } else if (((IndexTableId) tableId).id() == 20) {
789 //if the table is 20 OFDPA skips to table 50
790 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
791 tableId = IndexTableId.of(50);
792
793 } else {
794 tableId = nextTableIdEntry.table();
795 }
796
Andrea Campanella01e886e2017-12-15 15:27:31 +0100797 } else if (flowEntry == null) {
798 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
799 in.deviceId() + ". Dropping");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700800 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700801 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100802 return trace;
803 } else {
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100804
Andrea Campanella01e886e2017-12-15 15:27:31 +0100805 //IF the table has a transition
806 if (flowEntry.treatment().tableTransition() != null) {
807 //update the next table we transitions to
808 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
809 log.debug("Flow Entry has transition to table Id {}", tableId);
810 flows.add(flowEntry);
811 } else {
812 //table has no transition so it means that it's an output rule if on the last table
813 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
814 flows.add(flowEntry);
815 outputFlows.add(flowEntry);
816 output = true;
817 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100818 //update the packet according to the immediate actions of this flow rule.
819 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
820
821 //save the deferred rules for later
822 deferredInstructions.addAll(flowEntry.treatment().deferred());
823
824 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
825 if (flowEntry.treatment().clearedDeferred()) {
826 deferredInstructions.clear();
827 }
828
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100829 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
830 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
831
832 //Let's get the packet vlanId instruction
833 VlanIdCriterion packetVlanIdCriterion =
834 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
835
836 //Let's get the flow entry vlan mod instructions
837 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
838 .immediate().stream()
839 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
840 .findFirst().orElse(null);
841
842 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
843 // is a flow rule that matches on same criteria and with updated vlanId
844 if (entryModVlanIdInstruction != null) {
845
846 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
847 packetVlanIdCriterion, entryModVlanIdInstruction);
848
849 //We found the flow that we expected
850 if (secondVlanFlow != null) {
851 flows.add(secondVlanFlow);
852 } else {
853 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700854 computePath(completePath, trace, null);
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100855 return trace;
856 }
857 }
858
859 }
860
Andrea Campanella01e886e2017-12-15 15:27:31 +0100861 }
862 }
863
864 //Creating a modifiable builder for the output packet
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100865 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100866 packet.criteria().forEach(builder::add);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100867
Andrea Campanella01e886e2017-12-15 15:27:31 +0100868 //Adding all the flows to the trace
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100869 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100870
Andrea Campanella01e886e2017-12-15 15:27:31 +0100871 List<PortNumber> outputPorts = new ArrayList<>();
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800872 List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100873
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800874
875 log.debug("Handling Groups");
876 //Analyze Groups
877 List<Group> groups = new ArrayList<>();
878
879 Collection<FlowEntry> nonOutputFlows = flows;
880 nonOutputFlows.removeAll(outputFlowEntries);
881
882 //Handling groups pointed at by immediate instructions
883 for (FlowEntry entry : flows) {
884 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700885 entry.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800886 }
887
888 //If we have deferred instructions at this point we handle them.
889 if (deferredInstructions.size() > 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700890 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800891
892 }
893 packet = builder.build();
894
895 log.debug("Output Packet {}", packet);
896 return trace;
897 }
898
899 private List<FlowEntry> handleFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
900 List<FlowEntry> outputFlows, Builder builder, List<PortNumber> outputPorts) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100901 //TODO optimization
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100902 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
903 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
904 .allInstructions().stream().filter(instruction -> instruction.type()
905 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100906
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100907 if (outputFlowEntries.size() > 1) {
908 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
909 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100910 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100911
912 if (outputFlowEntries.size() == 1) {
913
914 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
915 .allInstructions().stream()
916 .filter(instruction -> {
917 return instruction.type().equals(Instruction.Type.OUTPUT);
918 }).findFirst().get();
919
920 //FIXME using GroupsInDevice for output even if flows.
921 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
922
923 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800924 return outputFlowEntries;
925 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100926
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800927 private boolean multipleRoutes(StaticPacketTrace trace) {
928 boolean multipleRoutes = false;
929 IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
930 IpAddress ip = null;
931 if (ipCriterion != null) {
932 ip = ipCriterion.ip().address();
933 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
934 ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100935 }
Andrea Campanellae93f6612018-04-04 13:10:45 +0200936 if (ip != null) {
937 Optional<ResolvedRoute> optionalRoute = routeService.longestPrefixLookup(ip);
938 if (optionalRoute.isPresent()) {
939 ResolvedRoute route = optionalRoute.get();
940 route.prefix();
941 multipleRoutes = routeService.getAllResolvedRoutes(route.prefix()).size() > 1;
942 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100943 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800944 return multipleRoutes;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100945 }
946
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100947 /**
948 * Handles the specific case where the Input is the controller.
949 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
950 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
951 * the flood to all active physical ports of the device.
952 *
953 * @param trace the trace
954 * @param in the controller port
955 * @return the augmented trace.
956 */
957 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
958 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
959 .getCriterion(Criterion.Type.ETH_TYPE);
960 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
961 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
962 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
963 //get the active ports
964 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
965 .filter(Port::isEnabled)
966 .collect(Collectors.toList());
967 //build an output from each one
968 enabledPorts.forEach(port -> {
969 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
970 ImmutableList.of(), trace.getInitialPacket());
971 trace.addGroupOutputPath(in.deviceId(), output);
972 });
973 return trace;
974 }
975 return null;
976 }
977
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100978 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
979 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
980 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
981 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
982 .vlanId().equals(VlanId.NONE);
983 }
984
985 /**
986 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
987 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
988 * second to transition to following table
989 *
990 * @param packet the incoming packet
991 * @param in the input connect point
992 * @param packetVlanIdCriterion the vlan criterion from the packet
993 * @param entryModVlanIdInstruction the entry vlan instruction
994 * @return the second flow entry that matched
995 */
996 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
997 VlanIdCriterion packetVlanIdCriterion,
998 ModVlanIdInstruction entryModVlanIdInstruction) {
999 FlowEntry secondVlanFlow = null;
1000 //Check the packet has been update from the first rule.
1001 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
1002 //find a rule on the same table that matches the vlan and
1003 // also all the other elements of the flow such as input port
1004 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1005 .stream()
1006 .filter(entry -> {
1007 return entry.table().equals(IndexTableId.of(10));
1008 })
1009 .filter(entry -> {
1010 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
1011 .getCriterion(Criterion.Type.VLAN_VID);
1012 return criterion != null && match(packet, entry)
1013 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
1014 }).findFirst().orElse(null);
1015
1016 }
1017 return secondVlanFlow;
1018 }
1019
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001020
Andrea Campanella01e886e2017-12-15 15:27:31 +01001021 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001022 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
1023 *
1024 * @param packet the incoming packet
1025 * @return the updated packet
1026 */
1027 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
1028 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
1029 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
1030 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
1031
1032 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
1033 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
1034 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001035 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanella01e886e2017-12-15 15:27:31 +01001036 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
1037 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
1038 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001039 //translateInstruction(builder, ethInstruction);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001040 builder.add(ethInstruction);
1041 }
1042 packet = updatePacket(packet, builder.build()).build();
1043 return packet;
1044 }
1045
1046 /**
1047 * Finds the flow entry with the minimun next table Id.
1048 *
1049 * @param deviceId the device to search
1050 * @param currentId the current id. the search will use this as minimum
1051 * @return the flow entry with the minimum table Id after the given one.
1052 */
1053 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
1054
1055 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
1056
1057 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
1058 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
1059 }
1060
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001061 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
1062 ConnectPoint in, List<Instruction> deferredInstructions,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001063 List<PortNumber> outputPorts, List<Group> groups,
1064 List<ConnectPoint> completePath) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001065
1066 //Update the packet with the deferred instructions
1067 Builder builder = updatePacket(packet, deferredInstructions);
1068
1069 //Gather any output instructions from the deferred instruction
1070 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
1071 return instruction.type().equals(Instruction.Type.OUTPUT);
1072 }).collect(Collectors.toList());
1073
1074 //We are considering deferred instructions from flows, there can only be one output.
1075 if (outputFlowInstruction.size() > 1) {
1076 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
1077 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
1078 }
1079 //If there is one output let's go through that
1080 if (outputFlowInstruction.size() == 1) {
1081 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
1082 ImmutableList.of());
1083 }
1084 //If there is no output let's see if there any deferred instruction point to groups.
1085 if (outputFlowInstruction.size() == 0) {
1086 getGroupsFromInstructions(trace, groups, deferredInstructions,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001087 in.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001088 }
1089 return builder;
1090 }
1091
Andrea Campanella01e886e2017-12-15 15:27:31 +01001092 /**
1093 * Gets group information from instructions.
1094 *
1095 * @param trace the trace we are building
1096 * @param groupsForDevice the set of groups for this device
1097 * @param instructions the set of instructions we are searching for groups.
1098 * @param deviceId the device we are considering
1099 * @param builder the builder of the input packet
1100 * @param outputPorts the output ports for that packet
1101 */
1102 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
1103 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001104 Builder builder, List<PortNumber> outputPorts,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001105 ConnectPoint in, List<ConnectPoint> completePath) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001106 List<Instruction> groupInstructionlist = new ArrayList<>();
1107 for (Instruction instruction : instructions) {
1108 log.debug("Considering Instruction {}", instruction);
1109 //if the instruction is not group we need to update the packet or add the output
1110 //to the possible outputs for this packet
1111 if (!instruction.type().equals(Instruction.Type.GROUP)) {
1112 //if the instruction is not group we need to update the packet or add the output
1113 //to the possible outputs for this packet
1114 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001115 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella3f98c212018-02-19 17:03:46 +01001116 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
1117 //clearing the groups because we start from the top.
1118 groupsForDevice.clear();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001119 } else {
1120 builder = translateInstruction(builder, instruction);
1121 }
1122 } else {
1123 //if the instuction is pointing to a group we need to get the group
1124 groupInstructionlist.add(instruction);
1125 }
1126 }
1127 //handle all the internal instructions pointing to a group.
1128 for (Instruction instr : groupInstructionlist) {
1129 GroupInstruction groupInstruction = (GroupInstruction) instr;
1130 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
1131 return groupInternal.id().equals(groupInstruction.groupId());
1132 }).findAny().orElse(null);
1133 if (group == null) {
1134 trace.addResultMessage("Null group for Instruction " + instr);
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001135 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001136 break;
1137 }
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001138 if (group.buckets().buckets().size() == 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001139 trace.addResultMessage("Group " + group.id() + " has no buckets");
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001140 trace.setSuccess(false);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001141 computePath(completePath, trace, null);
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001142 break;
1143 }
Andrea Campanella3f98c212018-02-19 17:03:46 +01001144
Andrea Campanella01e886e2017-12-15 15:27:31 +01001145 //Cycle in each of the group's buckets and add them to the groups for this Device.
1146 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella3f98c212018-02-19 17:03:46 +01001147
1148 //add the group to the traversed groups
1149 if (!groupsForDevice.contains(group)) {
1150 groupsForDevice.add(group);
1151 }
1152
Andrea Campanella01e886e2017-12-15 15:27:31 +01001153 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001154 deviceId, builder, outputPorts, in, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001155 }
1156 }
1157 }
1158
1159 /**
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001160 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1161 * a possible output from this device.
1162 *
1163 * @param trace the trace
1164 * @param in the input connect point
1165 * @param builder the packet builder
1166 * @param outputPorts the list of output ports for this device
1167 * @param outputInstruction the output instruction
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001168 * @param groupsForDevice the groups we output from
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001169 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001170 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001171 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1172 List<Group> groupsForDevice) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +01001173 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella6f2d6742018-02-07 12:00:12 +01001174
Andrea Campanella7b84c072018-03-06 15:21:09 -08001175 outputPorts.add(outputInstruction.port());
1176
1177 GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
1178 if (trace.getGroupOuputs(output.deviceId()) != null
1179 && trace.getGroupOuputs(output.deviceId()).contains(device)) {
1180 return;
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001181 }
Andrea Campanella7b84c072018-03-06 15:21:09 -08001182 trace.addGroupOutputPath(in.deviceId(),
1183 new GroupsInDevice(output, groupsForDevice, builder.build()));
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001184 }
1185
1186 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001187 * Applies all give instructions to the input packet.
1188 *
1189 * @param packet the input packet
1190 * @param instructions the set of instructions
1191 * @return the packet with the applied instructions
1192 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001193 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1194 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001195 packet.criteria().forEach(newSelector::add);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001196 //FIXME optimize
1197 for (Instruction instruction : instructions) {
1198 newSelector = translateInstruction(newSelector, instruction);
1199 }
Andrea Campanella01e886e2017-12-15 15:27:31 +01001200 return newSelector;
1201 }
1202
1203 /**
1204 * Applies an instruction to the packet in the form of a selector.
1205 *
1206 * @param newSelector the packet selector
1207 * @param instruction the instruction to be translated
1208 * @return the new selector with the applied instruction
1209 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001210 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001211 log.debug("Translating instruction {}", instruction);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001212 log.debug("New Selector {}", newSelector.build());
Andrea Campanella01e886e2017-12-15 15:27:31 +01001213 //TODO add as required
1214 Criterion criterion = null;
1215 switch (instruction.type()) {
1216 case L2MODIFICATION:
1217 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1218 switch (l2Instruction.subtype()) {
1219 case VLAN_ID:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001220 ModVlanIdInstruction vlanIdInstruction =
1221 (ModVlanIdInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001222 VlanId id = vlanIdInstruction.vlanId();
1223 criterion = Criteria.matchVlanId(id);
1224 break;
1225 case VLAN_POP:
1226 criterion = Criteria.matchVlanId(VlanId.NONE);
1227 break;
1228 case MPLS_PUSH:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001229 ModMplsHeaderInstruction mplsEthInstruction =
1230 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001231 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1232 break;
1233 case MPLS_POP:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001234 ModMplsHeaderInstruction mplsPopInstruction =
1235 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001236 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001237
1238 //When popping MPLS we remove label and BOS
1239 TrafficSelector temporaryPacket = newSelector.build();
1240 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001241 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001242 temporaryPacket.criteria().stream().filter(c -> {
1243 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1244 !c.type().equals(Criterion.Type.MPLS_BOS);
1245 }).forEach(noMplsSelector::add);
1246 newSelector = noMplsSelector;
1247 }
1248
Andrea Campanella01e886e2017-12-15 15:27:31 +01001249 break;
1250 case MPLS_LABEL:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001251 ModMplsLabelInstruction mplsLabelInstruction =
1252 (ModMplsLabelInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001253 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001254 newSelector.matchMplsBos(true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001255 break;
1256 case ETH_DST:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001257 ModEtherInstruction modEtherDstInstruction =
1258 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001259 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1260 break;
1261 case ETH_SRC:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001262 ModEtherInstruction modEtherSrcInstruction =
1263 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001264 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1265 break;
1266 default:
1267 log.debug("Unsupported L2 Instruction");
1268 break;
1269 }
1270 break;
1271 default:
1272 log.debug("Unsupported Instruction");
1273 break;
1274 }
1275 if (criterion != null) {
1276 log.debug("Adding criterion {}", criterion);
1277 newSelector.add(criterion);
1278 }
1279 return newSelector;
1280 }
1281
1282 /**
1283 * Finds the rule in the device that mathces the input packet and has the highest priority.
1284 *
1285 * @param packet the input packet
1286 * @param in the connect point the packet comes in from
1287 * @param tableId the table to search
1288 * @return the flow entry
1289 */
1290 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1291 //Computing the possible match rules.
1292 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
1293 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1294 .stream()
1295 .filter(flowEntry -> {
1296 return flowEntry.table().equals(tableId);
1297 })
1298 .filter(flowEntry -> {
1299 return match(packet, flowEntry);
1300 }).max(comparator).orElse(null);
1301 }
1302
1303 /**
1304 * Matches the packet with the given flow entry.
1305 *
1306 * @param packet the packet to match
1307 * @param flowEntry the flow entry to match the packet against
1308 * @return true if the packet matches the flow.
1309 */
1310 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001311 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1312 Criterion.Type type = criterion.type();
Andrea Campanella4ee4af12018-01-31 12:20:48 +01001313 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanella01e886e2017-12-15 15:27:31 +01001314 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1315 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella04924b92018-01-17 16:34:51 +01001316 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001317 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella04924b92018-01-17 16:34:51 +01001318 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1319 return matchMac(packet, (EthCriterion) criterion, false);
1320 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1321 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001322 } else {
1323 return packet.criteria().contains(criterion);
1324 }
1325 });
Simon Hunt6fefd852017-11-13 17:09:43 -08001326 }
Andrea Campanella04924b92018-01-17 16:34:51 +01001327
1328 /**
1329 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1330 *
1331 * @param packet the incoming packet
1332 * @param criterion the criterion to match
1333 * @return true if match
1334 */
1335 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1336 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1337 //if the packet does not have an IPv4 or IPv6 criterion we return true
1338 if (matchCriterion == null) {
1339 return false;
1340 }
1341 try {
1342 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1343 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1344 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1345 } catch (UnknownHostException e) {
1346 return false;
1347 }
1348 }
1349
1350 /**
1351 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1352 *
1353 * @param packet the incoming packet
1354 * @param hitCriterion the criterion to match
1355 * @param dst true if we are checking DST MAC
1356 * @return true if match
1357 */
1358 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1359 //Packet can have only one EthCriterion
1360 EthCriterion matchCriterion;
1361 if (dst) {
1362 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1363 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1364 criterion1.type().equals(Criterion.Type.ETH_DST);
1365 }).findFirst().orElse(null);
1366 } else {
1367 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1368 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1369 criterion1.type().equals(Criterion.Type.ETH_SRC);
1370 }).findFirst().orElse(null);
1371 }
1372 //if the packet does not have an ETH criterion we return true
1373 if (matchCriterion == null) {
1374 return true;
1375 }
1376 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1377 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1378 }
Simon Hunt6fefd852017-11-13 17:09:43 -08001379}