blob: 018f6ac69f13eae3ca653ae0c60cf04a41cfdeeb [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;
psneha5c9f51b2018-08-02 07:41:43 -040089import java.util.stream.StreamSupport;
Andrea Campanella01e886e2017-12-15 15:27:31 +010090
91import static org.onlab.packet.EthType.EtherType;
Andrea Campanella58b3b522018-02-06 15:46:52 +010092import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanella01e886e2017-12-15 15:27:31 +010093import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella97f9d4c2018-02-06 18:58:40 +010094import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
95import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
96import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
97import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Andrea Campanella04924b92018-01-17 16:34:51 +010098import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt6fefd852017-11-13 17:09:43 -080099import static org.slf4j.LoggerFactory.getLogger;
100
101/**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100102 * Manager to troubleshoot packets inside the network.
103 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
104 * the devices.
Simon Hunt6fefd852017-11-13 17:09:43 -0800105 */
106@Service
107@Component(immediate = true)
108public class TroubleshootManager implements TroubleshootService {
109
110 private static final Logger log = getLogger(TroubleshootManager.class);
111
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100112 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
113
Simon Hunt6fefd852017-11-13 17:09:43 -0800114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 protected FlowRuleService flowRuleService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 protected GroupService groupService;
119
Andrea Campanella01e886e2017-12-15 15:27:31 +0100120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121 protected LinkService linkService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800122
Andrea Campanella01e886e2017-12-15 15:27:31 +0100123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 protected HostService hostService;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
127 protected DriverService driverService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800128
Andrea Campanella37d10622018-01-18 17:11:42 +0100129 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
130 protected DeviceService deviceService;
131
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
133 protected MastershipService mastershipService;
134
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100135 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
136 protected NetworkConfigService networkConfigService;
137
Andrea Campanella04924b92018-01-17 16:34:51 +0100138 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
139 protected EdgePortService edgePortService;
140
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800141 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
142 protected RouteService routeService;
143
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200144 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
145 protected MulticastRouteService mcastService;
146
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100147 @Override
Andrea Campanella6be5c872018-02-21 14:28:20 +0100148 public List<StaticPacketTrace> pingAll(EtherType type) {
149 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
150 hostService.getHosts().forEach(host -> {
151 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
152 if (ipAddresses.size() > 0) {
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100153 //check if the host has only local IPs of that ETH type
154 boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
Andrea Campanella6be5c872018-02-21 14:28:20 +0100155 hostService.getHosts().forEach(hostToPing -> {
156 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100157 //check if the other host has only local IPs of that ETH type
158 boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
159 boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
160 //Trace is done only if they are both local and under the same location
161 // or not local and if they are not the same host.
162 if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
163 (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
164 && !host.equals(hostToPing)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100165 tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
Andrea Campanella6be5c872018-02-21 14:28:20 +0100166 }
167 });
168 }
169 });
170 return tracesBuilder.build();
171 }
172
173 @Override
Andrea Campanella41de8062018-02-28 16:43:16 +0100174 public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
175 return new PingAllGenerator(type, hostService, this);
176 }
177
178 @Override
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200179 public Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId) {
180 return new McastGenerator(mcastService, this, vlanId);
181 }
182
183 @Override
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100184 public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100185 Host source = hostService.getHost(sourceHost);
186 Host destination = hostService.getHost(destinationHost);
187
Andrea Campanella6be5c872018-02-21 14:28:20 +0100188 //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
189 StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100190
191 if (source == null) {
192 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700193 failTrace.setSuccess(false);
194
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100195 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100196 }
197
198 if (destination == null) {
199 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700200 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100201 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100202 }
203
204 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100205 .matchEthType(etherType.ethType().toShort())
206 .matchEthDst(source.mac())
207 .matchVlanId(source.vlan());
208
209
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100210 try {
Andrea Campanella15278a12018-03-26 10:39:22 -0700211 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100212 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
213 // we are under same leaf so it's L2 Unicast.
214 if (areBridged(source, destination)) {
215 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100216 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800217 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100218 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
219 trace.addEndpointHosts(Pair.of(source, destination));
220 traces.add(trace);
221 });
Andrea Campanella15278a12018-03-26 10:39:22 -0700222 //The destination host is not dual homed, if it is the other path might be done through routing.
223 if (destination.locations().size() == 1) {
224 return traces.build();
225 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100226 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100227 //handle the IPs for src and dst in case of L3
228 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
229
230 //Match on the source IP
231 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100232 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100233 }
234
235 //Match on destination IP
236 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100237 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100238 }
239
240 } else {
241 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
242 "please use packet based");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700243 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100244 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100245 }
246
247 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
248 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
249 .deviceId(), SegmentRoutingDeviceConfig.class);
250 if (segmentRoutingConfig != null) {
251 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
252 } else {
253 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
254 " router MAC from segment routing config can't perform L3 tracing.");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700255 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100256 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100257 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800258 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100259 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
260 trace.addEndpointHosts(Pair.of(source, destination));
261 traces.add(trace);
262 });
263 return traces.build();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100264
265 } catch (ConfigException e) {
266 failTrace.addResultMessage("Can't get config " + e.getMessage());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100267 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100268 }
269 }
270
271 /**
272 * Matches src and dst IPs based on host information.
273 *
274 * @param host the host
275 * @param failTrace the trace to use in case of failure
276 * @param selectorBuilder the packet we are building to trace
277 * @param etherType the traffic type
278 * @param src is this src host or dst host
279 * @return true if properly matched
280 */
281 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
282 EtherType etherType, boolean src) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100283 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100284
285 if (ips.size() > 0) {
Andrea Campanella465c7be2018-02-21 14:43:21 +0100286 if (etherType.equals(EtherType.IPV4)) {
287 if (src) {
288 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
289 } else {
290 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
291 }
292 } else if (etherType.equals(EtherType.IPV6)) {
293 if (src) {
294 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
295 } else {
296 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
297 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100298 }
299 } else {
300 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700301 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100302 return false;
303 }
304 return true;
305 }
306
Andrea Campanella41de8062018-02-28 16:43:16 +0100307 List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100308 return host.ipAddresses().stream().filter(ipAddress -> {
309 boolean correctIp = false;
310 if (etherType.equals(EtherType.IPV4)) {
311 correctIp = ipAddress.isIp4();
312 } else if (etherType.equals(EtherType.IPV6)) {
313 correctIp = ipAddress.isIp6();
314 }
315 if (checklocal) {
316 correctIp = correctIp && !ipAddress.isLinkLocal();
317 }
318 return correctIp;
319 }).collect(Collectors.toList());
320 }
321
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100322 /**
323 * Checks that two hosts are bridged (L2Unicast).
324 *
325 * @param source the source host
326 * @param destination the destination host
327 * @return true if bridged.
328 * @throws ConfigException if config can't be properly retrieved
329 */
330 private boolean areBridged(Host source, Host destination) throws ConfigException {
331
Andrea Campanella7b84c072018-03-06 15:21:09 -0800332 //If the locations is not the same we don't even check vlan or subnets
333 if (Collections.disjoint(source.locations(), destination.locations())) {
334 return false;
335 }
336
337 if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
338 && !source.vlan().equals(destination.vlan())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100339 return false;
340 }
341
342 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
343 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
344 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
345
346 //following can be optimized but for clarity is left as is
347 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
348 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
349
Andrea Campanella7b84c072018-03-06 15:21:09 -0800350 if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
351 return intfH1.vlanUntagged().equals(destination.vlan()) ||
352 intfH1.vlanNative().equals(destination.vlan());
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100353 }
354
Andrea Campanella7b84c072018-03-06 15:21:09 -0800355 if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
356 return intfH2.vlanUntagged().equals(source.vlan()) ||
357 intfH2.vlanNative().equals(source.vlan());
358 }
359
360 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100361 return false;
362 }
363
364 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
365 return false;
366 }
367
368 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
369 intersection.retainAll(intfH2.ipAddressesList());
370 if (intersection.size() == 0) {
371 return false;
372 }
373 }
374 return true;
375 }
376
Simon Hunt6fefd852017-11-13 17:09:43 -0800377 @Override
378 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100379 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100380 //device must exist in ONOS
381 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
382 "Device " + in.deviceId() + " must exist in ONOS");
383
Andrea Campanella01e886e2017-12-15 15:27:31 +0100384 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100385 boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100386 //FIXME this can be done recursively
Andrea Campanella01e886e2017-12-15 15:27:31 +0100387 //Building output connect Points
388 List<ConnectPoint> path = new ArrayList<>();
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700389 trace = traceInDevice(trace, packet, in, isDualHomed, path);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100390 trace = getTrace(path, in, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100391 return trace;
392 }
393
psneha5c9f51b2018-08-02 07:41:43 -0400394 @Override
395 public List<Set<StaticPacketTrace>> getMulitcastTrace(VlanId vlanId) {
396 Generator<Set<StaticPacketTrace>> gen = new McastGenerator(mcastService, this, vlanId);
397 List<Set<StaticPacketTrace>> multicastTraceList =
398 StreamSupport.stream(gen.spliterator(), false).collect(Collectors.toList());
399 return multicastTraceList;
400 }
401
Andrea Campanella01e886e2017-12-15 15:27:31 +0100402 /**
403 * Computes a trace for a give packet that start in the network at the given connect point.
404 *
405 * @param completePath the path traversed by the packet
406 * @param in the input connect point
407 * @param trace the trace to build
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100408 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanella01e886e2017-12-15 15:27:31 +0100409 * @return the build trace for that packet.
410 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100411 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
412 boolean isDualHomed) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100413
Andrea Campanellae04aac92018-01-31 14:59:03 +0100414 log.debug("------------------------------------------------------------");
415
Andrea Campanella01e886e2017-12-15 15:27:31 +0100416 //if the trace already contains the input connect point there is a loop
417 if (pathContainsDevice(completePath, in.deviceId())) {
418 trace.addResultMessage("Loop encountered in device " + in.deviceId());
Andrea Campanellaece11772018-03-09 14:52:10 -0800419 completePath.add(in);
420 trace.addCompletePath(completePath);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700421 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100422 return trace;
423 }
424
425 //let's add the input connect point
426 completePath.add(in);
427
428 //If the trace has no outputs for the given input we stop here
429 if (trace.getGroupOuputs(in.deviceId()) == null) {
430 computePath(completePath, trace, null);
431 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700432 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100433 return trace;
434 }
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100435
Andrea Campanella67b75602018-04-09 14:22:32 +0200436 //If the trace has outputs we analyze them all
Andrea Campanella01e886e2017-12-15 15:27:31 +0100437 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100438
439 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellae04aac92018-01-31 14:59:03 +0100440 log.debug("Connect point in {}", in);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100441 log.debug("Output path {}", cp);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800442 log.debug("{}", outputPath.getFinalPacket());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100443
Andrea Campanella01e886e2017-12-15 15:27:31 +0100444 //Hosts for the the given output
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100445 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100446 //Hosts queried from the original ip or mac
447 Set<Host> hosts = getHosts(trace);
448
Andrea Campanella7b84c072018-03-06 15:21:09 -0800449 if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
450 outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
451 && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
452 .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
453 .vlanId())) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700454 if (trace.getGroupOuputs(in.deviceId()).size() == 1 &&
455 computePath(completePath, trace, outputPath.getOutput())) {
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700456 trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700457 trace.setSuccess(false);
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700458 }
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700459 } else if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700460 //If the two host collections contain the same item it means we reached the proper output
Andrea Campanellae04aac92018-01-31 14:59:03 +0100461 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100462 if (computePath(completePath, trace, outputPath.getOutput())) {
463 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella41de8062018-02-28 16:43:16 +0100464 trace.setSuccess(true);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100465 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100466 break;
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100467 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100468
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100469 //Getting the master when the packet gets sent as packet in
470 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100471 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100472 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella58b3b522018-02-06 15:46:52 +0100473 handleVlanToController(outputPath, trace);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100474
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100475 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100476
477 //TODO this can be optimized if we use a Tree structure for paths.
478 //if we already have outputs let's check if the one we are considering starts from one of the devices
479 // in any of the ones we have.
480 if (trace.getCompletePaths().size() > 0) {
481 ConnectPoint inputForOutput = null;
482 List<ConnectPoint> previousPath = new ArrayList<>();
483 for (List<ConnectPoint> path : trace.getCompletePaths()) {
484 for (ConnectPoint connect : path) {
485 //if the path already contains the input for the output we've found we use it
486 if (connect.equals(in)) {
487 inputForOutput = connect;
488 previousPath = path;
489 break;
490 }
491 }
492 }
Andrea Campanellaece11772018-03-09 14:52:10 -0800493
Andrea Campanellae04aac92018-01-31 14:59:03 +0100494 //we use the pre-existing path up to the point we fork to a new output
495 if (inputForOutput != null && completePath.contains(inputForOutput)) {
496 List<ConnectPoint> temp = new ArrayList<>(previousPath);
Andrea Campanellaece11772018-03-09 14:52:10 -0800497 temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
498 if (completePath.containsAll(temp)) {
499 completePath = temp;
500 }
Andrea Campanellae04aac92018-01-31 14:59:03 +0100501 }
502 }
503
Andrea Campanella01e886e2017-12-15 15:27:31 +0100504 //let's add the ouput for the input
505 completePath.add(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100506 //let's compute the links for the given output
507 Set<Link> links = linkService.getEgressLinks(cp);
508 log.debug("Egress Links {}", links);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100509 //For each link we trace the corresponding device
510 for (Link link : links) {
511 ConnectPoint dst = link.dst();
512 //change in-port to the dst link in port
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100513 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100514 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
515 updatedPacket.add(Criteria.matchInPort(dst.port()));
516 log.debug("DST Connect Point {}", dst);
517 //build the elements for that device
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700518 traceInDevice(trace, updatedPacket.build(), dst, isDualHomed, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100519 //continue the trace along the path
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100520 getTrace(completePath, dst, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100521 }
Andrea Campanella04924b92018-01-17 16:34:51 +0100522 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
523 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
524 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
525 .mac().isMulticast()) {
526 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
527 " which is enabled and is edge port");
Andrea Campanella41de8062018-02-28 16:43:16 +0100528 trace.setSuccess(true);
Andrea Campanella04924b92018-01-17 16:34:51 +0100529 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella04924b92018-01-17 16:34:51 +0100530 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
531 return trace;
532 }
Andrea Campanella15278a12018-03-26 10:39:22 -0700533 } else if (deviceService.getPort(cp) != null && deviceService.getPort(cp).isEnabled()) {
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100534 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
535 .getCriterion(Criterion.Type.ETH_TYPE);
536 //We treat as correct output only if it's not LLDP or BDDP
537 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
Andrea Campanella62841d42018-02-27 12:42:28 +0100538 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800539 if (computePath(completePath, trace, outputPath.getOutput())) {
540 if (hostsList.isEmpty()) {
541 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
542 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
543 cp + " with no hosts connected ");
544 } else {
545 IpAddress ipAddress = null;
546 if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
547 ipAddress = ((IPCriterion) trace.getInitialPacket()
548 .getCriterion(Criterion.Type.IPV4_DST)).ip().address();
549 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
550 ipAddress = ((IPCriterion) trace.getInitialPacket()
551 .getCriterion(Criterion.Type.IPV6_DST)).ip().address();
552 }
553 if (ipAddress != null) {
554 IpAddress finalIpAddress = ipAddress;
555 if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
556 hostService.getHostsByIp(finalIpAddress).isEmpty()) {
557 trace.addResultMessage("Packet is " +
558 ((EthTypeCriterion) outputPath.getFinalPacket()
Andrea Campanella220f19b2018-03-09 15:30:22 -0800559 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
560 " and reached " + cp + " with hosts " + hostsList);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800561 } else {
562 trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
563 ipAddress);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700564 trace.setSuccess(false);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800565 }
566 } else {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800567 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
568 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
569 cp + " with hosts " + hostsList);
Andrea Campanella7b84c072018-03-06 15:21:09 -0800570 }
Andrea Campanella7b84c072018-03-06 15:21:09 -0800571 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800572 trace.setSuccess(true);
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100573 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100574 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100575
576 } else {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100577 computePath(completePath, trace, cp);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700578 trace.setSuccess(false);
Andrea Campanella15278a12018-03-26 10:39:22 -0700579 if (deviceService.getPort(cp) == null) {
Jon Hall7d77fe12018-04-24 18:03:10 -0700580 //Port is not existent on device.
Andrea Campanella15278a12018-03-26 10:39:22 -0700581 log.warn("Port {} is not available on device.", cp);
582 trace.addResultMessage("Port " + cp + "is not available on device. Packet is dropped");
583 } else {
584 //No links means that the packet gets dropped.
585 log.warn("No links out of {}", cp);
586 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
587 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100588 }
589 }
590 return trace;
591 }
592
Andrea Campanella04924b92018-01-17 16:34:51 +0100593
Andrea Campanella01e886e2017-12-15 15:27:31 +0100594 /**
Andrea Campanella58b3b522018-02-06 15:46:52 +0100595 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
596 * If ONOS applied a vlan we remove it.
597 *
598 * @param outputPath the output
599 * @param trace the trace we are building
600 */
Andrea Campanella04924b92018-01-17 16:34:51 +0100601
Andrea Campanella58b3b522018-02-06 15:46:52 +0100602 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
603
604 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
605 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
606
607 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
608
609 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
610 //removing the final vlanId
611 finalCriteria.remove(finalVid);
612 Builder packetUpdated = DefaultTrafficSelector.builder();
613 finalCriteria.forEach(packetUpdated::add);
614 //Initial was none so we set it to that
615 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
616 //Update final packet
617 outputPath.setFinalPacket(packetUpdated.build());
618 }
619 }
620
621 /**
Andrea Campanella04924b92018-01-17 16:34:51 +0100622 * Checks if the device has other outputs than the given connect point.
623 *
624 * @param inDeviceId the device
625 * @param trace the trace we are building
626 * @param cp an output connect point
627 * @return true if the device has other outputs.
628 */
629 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
630 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
631 return !groupsInDevice.getOutput().equals(cp);
632 }).count() > 0;
633 }
634
635 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100636 * Checks if the path contains the device.
637 *
638 * @param completePath the path
639 * @param deviceId the device to check
640 * @return true if the path contains the device
641 */
642 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
643 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
644 for (ConnectPoint cp : completePath) {
645 if (cp.deviceId().equals(deviceId)) {
646 return true;
647 }
648 }
649 return false;
650 }
651
652 /**
653 * Gets the hosts for the given initial packet.
654 *
655 * @param trace the trace we are building
656 * @return set of the hosts we are trying to reach
657 */
658 private Set<Host> getHosts(StaticPacketTrace trace) {
659 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
660 .getCriterion(Criterion.Type.IPV4_DST));
661 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
662 .getCriterion(Criterion.Type.IPV6_DST));
663 Set<Host> hosts = new HashSet<>();
664 if (ipv4Criterion != null) {
665 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
666 }
667 if (ipv6Criterion != null) {
668 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
669 }
670 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
671 .getCriterion(Criterion.Type.ETH_DST));
672 if (ethCriterion != null) {
673 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
674 }
675 return hosts;
676 }
677
678 /**
679 * Computes the list of traversed connect points.
680 *
681 * @param completePath the list of devices
682 * @param trace the trace we are building
683 * @param output the final output connect point
684 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100685 private boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100686 List<ConnectPoint> traverseList = new ArrayList<>();
687 if (!completePath.contains(trace.getInitialConnectPoint())) {
688 traverseList.add(trace.getInitialConnectPoint());
689 }
Andrea Campanella67b75602018-04-09 14:22:32 +0200690
691 if (output != null && trace.getInitialConnectPoint().deviceId().equals(output.deviceId())) {
692 trace.addCompletePath(ImmutableList.of(trace.getInitialConnectPoint(), output));
693 return true;
694 }
695
Andrea Campanella01e886e2017-12-15 15:27:31 +0100696 traverseList.addAll(completePath);
697 if (output != null && !completePath.contains(output)) {
698 traverseList.add(output);
699 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100700 if (!trace.getCompletePaths().contains(traverseList)) {
Andrea Campanellaece11772018-03-09 14:52:10 -0800701 trace.addCompletePath(ImmutableList.copyOf(traverseList));
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100702 return true;
703 }
704 return false;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100705 }
706
707 /**
708 * Traces the packet inside a device starting from an input connect point.
709 *
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700710 * @param trace the trace we are building
711 * @param packet the packet we are tracing
712 * @param in the input connect point.
713 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
714 * @param completePath the path up until this device
Andrea Campanella01e886e2017-12-15 15:27:31 +0100715 * @return updated trace
716 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100717 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700718 boolean isDualHomed, List<ConnectPoint> completePath) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100719
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800720 boolean multipleRoutes = false;
721 if (trace.getGroupOuputs(in.deviceId()) != null) {
722 multipleRoutes = multipleRoutes(trace);
723 }
724 if (trace.getGroupOuputs(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100725 log.debug("Trace already contains device and given outputs");
726 return trace;
727 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800728
Andrea Campanella01e886e2017-12-15 15:27:31 +0100729 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100730
731 //if device is not available exit here.
732 if (!deviceService.isAvailable(in.deviceId())) {
733 trace.addResultMessage("Device is offline " + in.deviceId());
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700734 computePath(completePath, trace, null);
Andrea Campanella37d10622018-01-18 17:11:42 +0100735 return trace;
736 }
737
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100738 //handle when the input is the controller
739 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
740 // a packet in from the controller will not actually traverse the pipeline and have no such notion
741 // as the input port.
742 if (in.port().equals(PortNumber.CONTROLLER)) {
743 StaticPacketTrace outputTrace = inputFromController(trace, in);
744 if (outputTrace != null) {
745 return trace;
746 }
747 }
748
Andrea Campanella01e886e2017-12-15 15:27:31 +0100749 List<FlowEntry> flows = new ArrayList<>();
750 List<FlowEntry> outputFlows = new ArrayList<>();
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100751 List<Instruction> deferredInstructions = new ArrayList<>();
752
Andrea Campanella01e886e2017-12-15 15:27:31 +0100753 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
754 if (nextTableIdEntry == null) {
755 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700756 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700757 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100758 return trace;
759 }
760 TableId tableId = nextTableIdEntry.table();
761 FlowEntry flowEntry;
762 boolean output = false;
763 while (!output) {
764 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
765 //get the rule that matches the incoming packet
766 flowEntry = matchHighestPriority(packet, in, tableId);
767 log.debug("Found Flow Entry {}", flowEntry);
768
769 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
770 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
771
772 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
773 if (flowEntry == null && isOfdpaHardware) {
774 log.debug("Ofdpa Hw setup, no flow rule means table miss");
775
Andrea Campanella01e886e2017-12-15 15:27:31 +0100776 if (((IndexTableId) tableId).id() == 27) {
777 //Apparently a miss but Table 27 on OFDPA is a fixed table
778 packet = handleOfdpa27FixedTable(trace, packet);
779 }
780
781 //Finding next table to go In case of miss
782 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
783 log.debug("Next table id entry {}", nextTableIdEntry);
784
785 //FIXME find better solution that enable granularity greater than 0 or all rules
786 //(another possibility is max tableId)
787 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella7382c7f2018-02-05 19:39:25 +0100788 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700789 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700790 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100791 return trace;
792
793 } else if (nextTableIdEntry == null) {
794 //Means that no more flow rules are present
795 output = true;
796
797 } else if (((IndexTableId) tableId).id() == 20) {
798 //if the table is 20 OFDPA skips to table 50
799 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
800 tableId = IndexTableId.of(50);
801
Andrea Campanella14c84e52018-06-05 12:04:02 +0200802 } else if (((IndexTableId) tableId).id() == 40) {
803 //if the table is 40 OFDPA skips to table 60
804 log.debug("A miss on Table 40 on OFDPA means that we skip directly to table 60");
805 tableId = IndexTableId.of(60);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100806 } else {
807 tableId = nextTableIdEntry.table();
808 }
809
Andrea Campanella01e886e2017-12-15 15:27:31 +0100810 } else if (flowEntry == null) {
811 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
812 in.deviceId() + ". Dropping");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700813 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700814 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100815 return trace;
816 } else {
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100817
Andrea Campanella01e886e2017-12-15 15:27:31 +0100818 //IF the table has a transition
819 if (flowEntry.treatment().tableTransition() != null) {
820 //update the next table we transitions to
821 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
822 log.debug("Flow Entry has transition to table Id {}", tableId);
823 flows.add(flowEntry);
824 } else {
825 //table has no transition so it means that it's an output rule if on the last table
826 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
827 flows.add(flowEntry);
828 outputFlows.add(flowEntry);
829 output = true;
830 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100831 //update the packet according to the immediate actions of this flow rule.
832 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
833
834 //save the deferred rules for later
835 deferredInstructions.addAll(flowEntry.treatment().deferred());
836
837 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
838 if (flowEntry.treatment().clearedDeferred()) {
839 deferredInstructions.clear();
840 }
841
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100842 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
843 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
844
845 //Let's get the packet vlanId instruction
846 VlanIdCriterion packetVlanIdCriterion =
847 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
848
849 //Let's get the flow entry vlan mod instructions
850 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
851 .immediate().stream()
852 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
853 .findFirst().orElse(null);
854
855 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
856 // is a flow rule that matches on same criteria and with updated vlanId
857 if (entryModVlanIdInstruction != null) {
858
859 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
860 packetVlanIdCriterion, entryModVlanIdInstruction);
861
862 //We found the flow that we expected
863 if (secondVlanFlow != null) {
864 flows.add(secondVlanFlow);
865 } else {
866 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700867 computePath(completePath, trace, null);
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100868 return trace;
869 }
870 }
871
872 }
873
Andrea Campanella01e886e2017-12-15 15:27:31 +0100874 }
875 }
876
877 //Creating a modifiable builder for the output packet
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100878 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100879 packet.criteria().forEach(builder::add);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100880
Andrea Campanella01e886e2017-12-15 15:27:31 +0100881 //Adding all the flows to the trace
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100882 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100883
Andrea Campanella01e886e2017-12-15 15:27:31 +0100884 List<PortNumber> outputPorts = new ArrayList<>();
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800885 List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100886
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800887
888 log.debug("Handling Groups");
889 //Analyze Groups
890 List<Group> groups = new ArrayList<>();
891
892 Collection<FlowEntry> nonOutputFlows = flows;
893 nonOutputFlows.removeAll(outputFlowEntries);
894
895 //Handling groups pointed at by immediate instructions
896 for (FlowEntry entry : flows) {
897 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700898 entry.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800899 }
900
901 //If we have deferred instructions at this point we handle them.
902 if (deferredInstructions.size() > 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700903 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800904
905 }
906 packet = builder.build();
907
908 log.debug("Output Packet {}", packet);
909 return trace;
910 }
911
912 private List<FlowEntry> handleFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
913 List<FlowEntry> outputFlows, Builder builder, List<PortNumber> outputPorts) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100914 //TODO optimization
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100915 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
916 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
917 .allInstructions().stream().filter(instruction -> instruction.type()
918 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100919
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100920 if (outputFlowEntries.size() > 1) {
921 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
922 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100923 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100924
925 if (outputFlowEntries.size() == 1) {
926
927 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
928 .allInstructions().stream()
929 .filter(instruction -> {
930 return instruction.type().equals(Instruction.Type.OUTPUT);
931 }).findFirst().get();
932
933 //FIXME using GroupsInDevice for output even if flows.
934 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
935
936 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800937 return outputFlowEntries;
938 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100939
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800940 private boolean multipleRoutes(StaticPacketTrace trace) {
941 boolean multipleRoutes = false;
942 IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
943 IpAddress ip = null;
944 if (ipCriterion != null) {
945 ip = ipCriterion.ip().address();
946 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
947 ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100948 }
Andrea Campanellae93f6612018-04-04 13:10:45 +0200949 if (ip != null) {
950 Optional<ResolvedRoute> optionalRoute = routeService.longestPrefixLookup(ip);
951 if (optionalRoute.isPresent()) {
952 ResolvedRoute route = optionalRoute.get();
953 route.prefix();
954 multipleRoutes = routeService.getAllResolvedRoutes(route.prefix()).size() > 1;
955 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100956 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800957 return multipleRoutes;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100958 }
959
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100960 /**
961 * Handles the specific case where the Input is the controller.
962 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
963 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
964 * the flood to all active physical ports of the device.
965 *
966 * @param trace the trace
967 * @param in the controller port
968 * @return the augmented trace.
969 */
970 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
971 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
972 .getCriterion(Criterion.Type.ETH_TYPE);
973 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
974 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
975 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
976 //get the active ports
977 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
978 .filter(Port::isEnabled)
979 .collect(Collectors.toList());
980 //build an output from each one
981 enabledPorts.forEach(port -> {
982 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
983 ImmutableList.of(), trace.getInitialPacket());
984 trace.addGroupOutputPath(in.deviceId(), output);
985 });
986 return trace;
987 }
988 return null;
989 }
990
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100991 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
992 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
993 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
994 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
995 .vlanId().equals(VlanId.NONE);
996 }
997
998 /**
999 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
1000 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
1001 * second to transition to following table
1002 *
1003 * @param packet the incoming packet
1004 * @param in the input connect point
1005 * @param packetVlanIdCriterion the vlan criterion from the packet
1006 * @param entryModVlanIdInstruction the entry vlan instruction
1007 * @return the second flow entry that matched
1008 */
1009 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
1010 VlanIdCriterion packetVlanIdCriterion,
1011 ModVlanIdInstruction entryModVlanIdInstruction) {
1012 FlowEntry secondVlanFlow = null;
1013 //Check the packet has been update from the first rule.
1014 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
1015 //find a rule on the same table that matches the vlan and
1016 // also all the other elements of the flow such as input port
Andrea Campanellaa3369742018-04-13 12:14:48 +02001017 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntriesByState(in.deviceId(),
1018 FlowEntry.FlowEntryState.ADDED).iterator())
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001019 .stream()
1020 .filter(entry -> {
1021 return entry.table().equals(IndexTableId.of(10));
1022 })
1023 .filter(entry -> {
1024 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
1025 .getCriterion(Criterion.Type.VLAN_VID);
1026 return criterion != null && match(packet, entry)
1027 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
1028 }).findFirst().orElse(null);
1029
1030 }
1031 return secondVlanFlow;
1032 }
1033
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001034
Andrea Campanella01e886e2017-12-15 15:27:31 +01001035 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001036 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
1037 *
1038 * @param packet the incoming packet
1039 * @return the updated packet
1040 */
1041 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
1042 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
1043 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
1044 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
1045
1046 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
1047 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
1048 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001049 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanella01e886e2017-12-15 15:27:31 +01001050 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
1051 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
1052 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001053 //translateInstruction(builder, ethInstruction);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001054 builder.add(ethInstruction);
1055 }
1056 packet = updatePacket(packet, builder.build()).build();
1057 return packet;
1058 }
1059
1060 /**
1061 * Finds the flow entry with the minimun next table Id.
1062 *
1063 * @param deviceId the device to search
1064 * @param currentId the current id. the search will use this as minimum
1065 * @return the flow entry with the minimum table Id after the given one.
1066 */
1067 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
1068
1069 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
1070
Andrea Campanellaa3369742018-04-13 12:14:48 +02001071 return Lists.newArrayList(flowRuleService.getFlowEntriesByState(deviceId, FlowEntry.FlowEntryState.ADDED)
1072 .iterator()).stream()
1073 .filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001074 }
1075
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001076 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
1077 ConnectPoint in, List<Instruction> deferredInstructions,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001078 List<PortNumber> outputPorts, List<Group> groups,
1079 List<ConnectPoint> completePath) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001080
1081 //Update the packet with the deferred instructions
1082 Builder builder = updatePacket(packet, deferredInstructions);
1083
1084 //Gather any output instructions from the deferred instruction
1085 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
1086 return instruction.type().equals(Instruction.Type.OUTPUT);
1087 }).collect(Collectors.toList());
1088
1089 //We are considering deferred instructions from flows, there can only be one output.
1090 if (outputFlowInstruction.size() > 1) {
1091 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
1092 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
1093 }
1094 //If there is one output let's go through that
1095 if (outputFlowInstruction.size() == 1) {
1096 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
1097 ImmutableList.of());
1098 }
1099 //If there is no output let's see if there any deferred instruction point to groups.
1100 if (outputFlowInstruction.size() == 0) {
1101 getGroupsFromInstructions(trace, groups, deferredInstructions,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001102 in.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001103 }
1104 return builder;
1105 }
1106
Andrea Campanella01e886e2017-12-15 15:27:31 +01001107 /**
1108 * Gets group information from instructions.
1109 *
1110 * @param trace the trace we are building
1111 * @param groupsForDevice the set of groups for this device
1112 * @param instructions the set of instructions we are searching for groups.
1113 * @param deviceId the device we are considering
1114 * @param builder the builder of the input packet
1115 * @param outputPorts the output ports for that packet
1116 */
1117 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
1118 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001119 Builder builder, List<PortNumber> outputPorts,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001120 ConnectPoint in, List<ConnectPoint> completePath) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001121 List<Instruction> groupInstructionlist = new ArrayList<>();
1122 for (Instruction instruction : instructions) {
1123 log.debug("Considering Instruction {}", instruction);
1124 //if the instruction is not group we need to update the packet or add the output
1125 //to the possible outputs for this packet
1126 if (!instruction.type().equals(Instruction.Type.GROUP)) {
1127 //if the instruction is not group we need to update the packet or add the output
1128 //to the possible outputs for this packet
1129 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001130 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella3f98c212018-02-19 17:03:46 +01001131 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
1132 //clearing the groups because we start from the top.
1133 groupsForDevice.clear();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001134 } else {
1135 builder = translateInstruction(builder, instruction);
1136 }
1137 } else {
1138 //if the instuction is pointing to a group we need to get the group
1139 groupInstructionlist.add(instruction);
1140 }
1141 }
1142 //handle all the internal instructions pointing to a group.
1143 for (Instruction instr : groupInstructionlist) {
1144 GroupInstruction groupInstruction = (GroupInstruction) instr;
1145 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
1146 return groupInternal.id().equals(groupInstruction.groupId());
1147 }).findAny().orElse(null);
1148 if (group == null) {
1149 trace.addResultMessage("Null group for Instruction " + instr);
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001150 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001151 break;
1152 }
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001153 if (group.buckets().buckets().size() == 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001154 trace.addResultMessage("Group " + group.id() + " has no buckets");
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001155 trace.setSuccess(false);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001156 computePath(completePath, trace, null);
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001157 break;
1158 }
Andrea Campanella3f98c212018-02-19 17:03:46 +01001159
Andrea Campanella01e886e2017-12-15 15:27:31 +01001160 //Cycle in each of the group's buckets and add them to the groups for this Device.
1161 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella3f98c212018-02-19 17:03:46 +01001162
1163 //add the group to the traversed groups
1164 if (!groupsForDevice.contains(group)) {
1165 groupsForDevice.add(group);
1166 }
1167
Andrea Campanella01e886e2017-12-15 15:27:31 +01001168 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001169 deviceId, builder, outputPorts, in, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001170 }
1171 }
1172 }
1173
1174 /**
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001175 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1176 * a possible output from this device.
1177 *
1178 * @param trace the trace
1179 * @param in the input connect point
1180 * @param builder the packet builder
1181 * @param outputPorts the list of output ports for this device
1182 * @param outputInstruction the output instruction
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001183 * @param groupsForDevice the groups we output from
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001184 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001185 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001186 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1187 List<Group> groupsForDevice) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +01001188 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella6f2d6742018-02-07 12:00:12 +01001189
Andrea Campanella7b84c072018-03-06 15:21:09 -08001190 outputPorts.add(outputInstruction.port());
1191
1192 GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
1193 if (trace.getGroupOuputs(output.deviceId()) != null
1194 && trace.getGroupOuputs(output.deviceId()).contains(device)) {
1195 return;
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001196 }
Andrea Campanella7b84c072018-03-06 15:21:09 -08001197 trace.addGroupOutputPath(in.deviceId(),
1198 new GroupsInDevice(output, groupsForDevice, builder.build()));
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001199 }
1200
1201 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001202 * Applies all give instructions to the input packet.
1203 *
1204 * @param packet the input packet
1205 * @param instructions the set of instructions
1206 * @return the packet with the applied instructions
1207 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001208 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1209 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001210 packet.criteria().forEach(newSelector::add);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001211 //FIXME optimize
1212 for (Instruction instruction : instructions) {
1213 newSelector = translateInstruction(newSelector, instruction);
1214 }
Andrea Campanella01e886e2017-12-15 15:27:31 +01001215 return newSelector;
1216 }
1217
1218 /**
1219 * Applies an instruction to the packet in the form of a selector.
1220 *
1221 * @param newSelector the packet selector
1222 * @param instruction the instruction to be translated
1223 * @return the new selector with the applied instruction
1224 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001225 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001226 log.debug("Translating instruction {}", instruction);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001227 log.debug("New Selector {}", newSelector.build());
Andrea Campanella01e886e2017-12-15 15:27:31 +01001228 //TODO add as required
1229 Criterion criterion = null;
1230 switch (instruction.type()) {
1231 case L2MODIFICATION:
1232 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1233 switch (l2Instruction.subtype()) {
1234 case VLAN_ID:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001235 ModVlanIdInstruction vlanIdInstruction =
1236 (ModVlanIdInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001237 VlanId id = vlanIdInstruction.vlanId();
1238 criterion = Criteria.matchVlanId(id);
1239 break;
1240 case VLAN_POP:
1241 criterion = Criteria.matchVlanId(VlanId.NONE);
1242 break;
1243 case MPLS_PUSH:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001244 ModMplsHeaderInstruction mplsEthInstruction =
1245 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001246 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1247 break;
1248 case MPLS_POP:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001249 ModMplsHeaderInstruction mplsPopInstruction =
1250 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001251 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001252
1253 //When popping MPLS we remove label and BOS
1254 TrafficSelector temporaryPacket = newSelector.build();
1255 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001256 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001257 temporaryPacket.criteria().stream().filter(c -> {
1258 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1259 !c.type().equals(Criterion.Type.MPLS_BOS);
1260 }).forEach(noMplsSelector::add);
1261 newSelector = noMplsSelector;
1262 }
1263
Andrea Campanella01e886e2017-12-15 15:27:31 +01001264 break;
1265 case MPLS_LABEL:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001266 ModMplsLabelInstruction mplsLabelInstruction =
1267 (ModMplsLabelInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001268 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001269 newSelector.matchMplsBos(true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001270 break;
1271 case ETH_DST:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001272 ModEtherInstruction modEtherDstInstruction =
1273 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001274 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1275 break;
1276 case ETH_SRC:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001277 ModEtherInstruction modEtherSrcInstruction =
1278 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001279 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1280 break;
1281 default:
1282 log.debug("Unsupported L2 Instruction");
1283 break;
1284 }
1285 break;
1286 default:
1287 log.debug("Unsupported Instruction");
1288 break;
1289 }
1290 if (criterion != null) {
1291 log.debug("Adding criterion {}", criterion);
1292 newSelector.add(criterion);
1293 }
1294 return newSelector;
1295 }
1296
1297 /**
1298 * Finds the rule in the device that mathces the input packet and has the highest priority.
1299 *
1300 * @param packet the input packet
1301 * @param in the connect point the packet comes in from
1302 * @param tableId the table to search
1303 * @return the flow entry
1304 */
1305 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1306 //Computing the possible match rules.
1307 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
Andrea Campanellaa3369742018-04-13 12:14:48 +02001308 return Lists.newArrayList(flowRuleService.getFlowEntriesByState(in.deviceId(), FlowEntry.FlowEntryState.ADDED)
1309 .iterator()).stream()
Andrea Campanella01e886e2017-12-15 15:27:31 +01001310 .filter(flowEntry -> {
1311 return flowEntry.table().equals(tableId);
1312 })
1313 .filter(flowEntry -> {
1314 return match(packet, flowEntry);
1315 }).max(comparator).orElse(null);
1316 }
1317
1318 /**
1319 * Matches the packet with the given flow entry.
1320 *
1321 * @param packet the packet to match
1322 * @param flowEntry the flow entry to match the packet against
1323 * @return true if the packet matches the flow.
1324 */
1325 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001326 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1327 Criterion.Type type = criterion.type();
Andrea Campanella4ee4af12018-01-31 12:20:48 +01001328 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanella01e886e2017-12-15 15:27:31 +01001329 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1330 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella04924b92018-01-17 16:34:51 +01001331 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001332 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella04924b92018-01-17 16:34:51 +01001333 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1334 return matchMac(packet, (EthCriterion) criterion, false);
1335 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1336 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001337 } else {
1338 return packet.criteria().contains(criterion);
1339 }
1340 });
Simon Hunt6fefd852017-11-13 17:09:43 -08001341 }
Andrea Campanella04924b92018-01-17 16:34:51 +01001342
1343 /**
1344 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1345 *
1346 * @param packet the incoming packet
1347 * @param criterion the criterion to match
1348 * @return true if match
1349 */
1350 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1351 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1352 //if the packet does not have an IPv4 or IPv6 criterion we return true
1353 if (matchCriterion == null) {
1354 return false;
1355 }
1356 try {
1357 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1358 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1359 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1360 } catch (UnknownHostException e) {
1361 return false;
1362 }
1363 }
1364
1365 /**
1366 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1367 *
1368 * @param packet the incoming packet
1369 * @param hitCriterion the criterion to match
1370 * @param dst true if we are checking DST MAC
1371 * @return true if match
1372 */
1373 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1374 //Packet can have only one EthCriterion
1375 EthCriterion matchCriterion;
1376 if (dst) {
1377 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1378 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1379 criterion1.type().equals(Criterion.Type.ETH_DST);
1380 }).findFirst().orElse(null);
1381 } else {
1382 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1383 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1384 criterion1.type().equals(Criterion.Type.ETH_SRC);
1385 }).findFirst().orElse(null);
1386 }
1387 //if the packet does not have an ETH criterion we return true
1388 if (matchCriterion == null) {
1389 return true;
1390 }
1391 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1392 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1393 }
Simon Hunt6fefd852017-11-13 17:09:43 -08001394}