blob: b1359bc166b6821bc67f31bc07470d30005f19b5 [file] [log] [blame]
Simon Hunt026a2872017-11-13 17:09:43 -08001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.t3.impl;
18
Andrea Campanella17d45192018-01-18 17:11:42 +010019import com.google.common.base.Preconditions;
Andrea Campanellae4084402017-12-15 15:27:31 +010020import com.google.common.collect.ImmutableList;
Andrea Campanella696ef032018-02-27 18:03:17 +010021import com.google.common.collect.ImmutableSet;
Andrea Campanellae4084402017-12-15 15:27:31 +010022import com.google.common.collect.Lists;
Andrea Campanella5befe1c2018-02-27 14:50:45 +010023import com.google.common.collect.Sets;
Andrea Campanella6a614fa2018-02-21 14:28:20 +010024import org.apache.commons.lang3.tuple.Pair;
Andrea Campanella55c3f422018-02-08 17:10:11 +010025import org.onlab.packet.IpAddress;
Andrea Campanellae4084402017-12-15 15:27:31 +010026import org.onlab.packet.VlanId;
Andrea Campanella54923d62018-01-23 12:46:04 +010027import org.onosproject.cluster.NodeId;
Simon Hunt026a2872017-11-13 17:09:43 -080028import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010029import org.onosproject.net.DeviceId;
30import org.onosproject.net.Host;
Andrea Campanella55c3f422018-02-08 17:10:11 +010031import org.onosproject.net.HostId;
Andrea Campanellae4084402017-12-15 15:27:31 +010032import org.onosproject.net.Link;
Andrea Campanella1445f7a2018-02-07 12:00:12 +010033import org.onosproject.net.Port;
Andrea Campanellae4084402017-12-15 15:27:31 +010034import org.onosproject.net.PortNumber;
Andrea Campanella55c3f422018-02-08 17:10:11 +010035import org.onosproject.net.config.ConfigException;
Andrea Campanella55c3f422018-02-08 17:10:11 +010036import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanellae4084402017-12-15 15:27:31 +010037import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.FlowEntry;
39import org.onosproject.net.flow.FlowRule;
Andrea Campanellae4084402017-12-15 15:27:31 +010040import org.onosproject.net.flow.IndexTableId;
41import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080042import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-12-15 15:27:31 +010043import org.onosproject.net.flow.criteria.Criteria;
44import org.onosproject.net.flow.criteria.Criterion;
45import org.onosproject.net.flow.criteria.EthCriterion;
46import org.onosproject.net.flow.criteria.EthTypeCriterion;
47import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanellae6798012018-02-06 15:46:52 +010048import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanellae4084402017-12-15 15:27:31 +010049import org.onosproject.net.flow.instructions.Instruction;
50import org.onosproject.net.flow.instructions.Instructions;
51import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
52import org.onosproject.net.flow.instructions.L2ModificationInstruction;
53import org.onosproject.net.group.Group;
54import org.onosproject.net.group.GroupBucket;
Andrea Campanella55c3f422018-02-08 17:10:11 +010055import org.onosproject.net.host.InterfaceIpAddress;
56import org.onosproject.net.intf.Interface;
Andrea Campanella08d07e12018-03-07 14:27:54 -080057import org.onosproject.routeservice.ResolvedRoute;
Andrea Campanella55c3f422018-02-08 17:10:11 +010058import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Seyeon Jeong83e79862020-02-28 01:17:34 -080059import org.onosproject.t3.api.DeviceNib;
60import org.onosproject.t3.api.DriverNib;
61import org.onosproject.t3.api.EdgePortNib;
62import org.onosproject.t3.api.FlowNib;
63import org.onosproject.t3.api.GroupNib;
Andrea Campanellae4084402017-12-15 15:27:31 +010064import org.onosproject.t3.api.GroupsInDevice;
Seyeon Jeong83e79862020-02-28 01:17:34 -080065import org.onosproject.t3.api.HostNib;
66import org.onosproject.t3.api.LinkNib;
67import org.onosproject.t3.api.MastershipNib;
68import org.onosproject.t3.api.MulticastRouteNib;
69import org.onosproject.t3.api.NetworkConfigNib;
70import org.onosproject.t3.api.RouteNib;
Simon Hunt026a2872017-11-13 17:09:43 -080071import org.onosproject.t3.api.StaticPacketTrace;
72import org.onosproject.t3.api.TroubleshootService;
Ray Milkey9511b512018-08-17 14:54:17 -070073import org.osgi.service.component.annotations.Component;
Simon Hunt026a2872017-11-13 17:09:43 -080074import org.slf4j.Logger;
75
Andrea Campanellae4084402017-12-15 15:27:31 +010076import java.net.UnknownHostException;
77import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010078import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010079import java.util.Collections;
80import java.util.Comparator;
81import java.util.HashSet;
82import java.util.List;
Andrea Campanella08d07e12018-03-07 14:27:54 -080083import java.util.Optional;
Andrea Campanellae4084402017-12-15 15:27:31 +010084import java.util.Set;
85import java.util.stream.Collectors;
Seyeon Jeong83e79862020-02-28 01:17:34 -080086import java.util.stream.Stream;
psnehab823c512018-08-02 07:41:43 -040087import java.util.stream.StreamSupport;
Andrea Campanellae4084402017-12-15 15:27:31 +010088
89import static org.onlab.packet.EthType.EtherType;
Andrea Campanellae6798012018-02-06 15:46:52 +010090import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanellae4084402017-12-15 15:27:31 +010091import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella94c594a2018-02-06 18:58:40 +010092import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
93import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
94import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
95import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010096import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt026a2872017-11-13 17:09:43 -080097import static org.slf4j.LoggerFactory.getLogger;
98
99/**
Andrea Campanellae4084402017-12-15 15:27:31 +0100100 * Manager to troubleshoot packets inside the network.
101 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
102 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -0800103 */
Ray Milkey9511b512018-08-17 14:54:17 -0700104@Component(immediate = true, service = TroubleshootService.class)
Simon Hunt026a2872017-11-13 17:09:43 -0800105public class TroubleshootManager implements TroubleshootService {
106
107 private static final Logger log = getLogger(TroubleshootManager.class);
108
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100109 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
110
Seyeon Jeong83e79862020-02-28 01:17:34 -0800111 // uses a snapshot (cache) of NIBs instead of interacting with ONOS core in runtime
112 protected FlowNib flowNib;
113 protected GroupNib groupNib;
114 protected LinkNib linkNib;
115 protected HostNib hostNib;
116 protected DeviceNib deviceNib;
117 protected DriverNib driverNib;
118 protected MastershipNib mastershipNib;
119 protected EdgePortNib edgePortNib;
120 protected RouteNib routeNib;
121 protected NetworkConfigNib networkConfigNib;
122 protected MulticastRouteNib mcastRouteNib;
Simon Hunt026a2872017-11-13 17:09:43 -0800123
Seyeon Jeong83e79862020-02-28 01:17:34 -0800124 @Override
125 public boolean checkNibsUnavailable() {
126 return Stream.of(flowNib, groupNib, linkNib, hostNib, deviceNib, driverNib,
127 mastershipNib, edgePortNib, routeNib, networkConfigNib, mcastRouteNib)
128 .anyMatch(x -> x == null);
129 }
Simon Hunt026a2872017-11-13 17:09:43 -0800130
Seyeon Jeong83e79862020-02-28 01:17:34 -0800131 @Override
132 public void applyNibs() {
133 flowNib = FlowNib.getInstance();
134 groupNib = GroupNib.getInstance();
135 linkNib = LinkNib.getInstance();
136 hostNib = HostNib.getInstance();
137 deviceNib = DeviceNib.getInstance();
138 driverNib = DriverNib.getInstance();
139 mastershipNib = MastershipNib.getInstance();
140 edgePortNib = EdgePortNib.getInstance();
141 routeNib = RouteNib.getInstance();
142 networkConfigNib = NetworkConfigNib.getInstance();
143 mcastRouteNib = MulticastRouteNib.getInstance();
144 }
Andrea Campanellabd15bf52018-04-06 16:30:18 +0200145
Andrea Campanella55c3f422018-02-08 17:10:11 +0100146 @Override
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100147 public List<StaticPacketTrace> pingAll(EtherType type) {
148 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
Seyeon Jeong83e79862020-02-28 01:17:34 -0800149 hostNib.getHosts().forEach(host -> {
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100150 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
151 if (ipAddresses.size() > 0) {
Andrea Campanella5befe1c2018-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();
Seyeon Jeong83e79862020-02-28 01:17:34 -0800154 hostNib.getHosts().forEach(hostToPing -> {
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100155 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella5befe1c2018-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 Campanella696ef032018-02-27 18:03:17 +0100164 tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100165 }
166 });
167 }
168 });
169 return tracesBuilder.build();
170 }
171
172 @Override
Andrea Campanella0cc6acd2018-02-28 16:43:16 +0100173 public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800174 return new PingAllGenerator(type, hostNib, this);
Andrea Campanella0cc6acd2018-02-28 16:43:16 +0100175 }
176
177 @Override
Andrea Campanellabd15bf52018-04-06 16:30:18 +0200178 public Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800179 return new McastGenerator(mcastRouteNib, this, vlanId);
Andrea Campanellabd15bf52018-04-06 16:30:18 +0200180 }
181
182 @Override
Andrea Campanella696ef032018-02-27 18:03:17 +0100183 public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800184 Host source = hostNib.getHost(sourceHost);
185 Host destination = hostNib.getHost(destinationHost);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100186
Andrea Campanella6a614fa2018-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 Campanella55c3f422018-02-08 17:10:11 +0100189
190 if (source == null) {
191 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700192 failTrace.setSuccess(false);
193
Andrea Campanella696ef032018-02-27 18:03:17 +0100194 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100195 }
196
197 if (destination == null) {
198 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700199 failTrace.setSuccess(false);
Andrea Campanella696ef032018-02-27 18:03:17 +0100200 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100201 }
202
203 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
Andrea Campanella55c3f422018-02-08 17:10:11 +0100204 .matchEthType(etherType.ethType().toShort())
205 .matchEthDst(source.mac())
206 .matchVlanId(source.vlan());
207
208
Andrea Campanella55c3f422018-02-08 17:10:11 +0100209 try {
Andrea Campanellae24d4922018-03-26 10:39:22 -0700210 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
Andrea Campanella55c3f422018-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 Campanella696ef032018-02-27 18:03:17 +0100215 source.locations().forEach(hostLocation -> {
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800216 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella696ef032018-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 Campanellae24d4922018-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 Campanella55c3f422018-02-08 17:10:11 +0100225 }
Andrea Campanella55c3f422018-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 Campanella696ef032018-02-27 18:03:17 +0100231 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100232 }
233
234 //Match on destination IP
235 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
Andrea Campanella696ef032018-02-27 18:03:17 +0100236 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-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 Campanella5af2d2b2018-03-12 19:25:44 -0700242 failTrace.setSuccess(false);
Andrea Campanella696ef032018-02-27 18:03:17 +0100243 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-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
Seyeon Jeong83e79862020-02-28 01:17:34 -0800247 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigNib.getConfig(source.location()
Andrea Campanella55c3f422018-02-08 17:10:11 +0100248 .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 Campanella5af2d2b2018-03-12 19:25:44 -0700254 failTrace.setSuccess(false);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100255 }
Andrea Campanella696ef032018-02-27 18:03:17 +0100256 source.locations().forEach(hostLocation -> {
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800257 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella696ef032018-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 Campanella55c3f422018-02-08 17:10:11 +0100263
264 } catch (ConfigException e) {
265 failTrace.addResultMessage("Can't get config " + e.getMessage());
Andrea Campanella696ef032018-02-27 18:03:17 +0100266 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-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 Campanella6a614fa2018-02-21 14:28:20 +0100282 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100283
284 if (ips.size() > 0) {
Andrea Campanellabf8d9302018-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 Campanella55c3f422018-02-08 17:10:11 +0100297 }
298 } else {
299 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700300 failTrace.setSuccess(false);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100301 return false;
302 }
303 return true;
304 }
305
Andrea Campanella0cc6acd2018-02-28 16:43:16 +0100306 List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
Andrea Campanella6a614fa2018-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 Campanella55c3f422018-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 Campanella2ff88a92018-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 Campanella55c3f422018-02-08 17:10:11 +0100338 return false;
339 }
340
Seyeon Jeong83e79862020-02-28 01:17:34 -0800341 InterfaceConfig interfaceCfgH1 = networkConfigNib.getConfig(source.location(), InterfaceConfig.class);
342 InterfaceConfig interfaceCfgH2 = networkConfigNib.getConfig(destination.location(), InterfaceConfig.class);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100343 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 Campanella2ff88a92018-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 Campanella55c3f422018-02-08 17:10:11 +0100352 }
353
Andrea Campanella2ff88a92018-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 Campanella55c3f422018-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 Hunt026a2872017-11-13 17:09:43 -0800376 @Override
377 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100378 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100379 //device must exist in ONOS
Seyeon Jeong83e79862020-02-28 01:17:34 -0800380 Preconditions.checkNotNull(deviceNib.getDevice(in.deviceId()),
Andrea Campanella17d45192018-01-18 17:11:42 +0100381 "Device " + in.deviceId() + " must exist in ONOS");
382
Andrea Campanellae4084402017-12-15 15:27:31 +0100383 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
Andrea Campanella696ef032018-02-27 18:03:17 +0100384 boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
Andrea Campanellae4084402017-12-15 15:27:31 +0100385 //FIXME this can be done recursively
Andrea Campanellae4084402017-12-15 15:27:31 +0100386 //Building output connect Points
387 List<ConnectPoint> path = new ArrayList<>();
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700388 trace = traceInDevice(trace, packet, in, isDualHomed, path);
Andrea Campanella696ef032018-02-27 18:03:17 +0100389 trace = getTrace(path, in, trace, isDualHomed);
Andrea Campanellae4084402017-12-15 15:27:31 +0100390 return trace;
391 }
392
psnehab823c512018-08-02 07:41:43 -0400393 @Override
394 public List<Set<StaticPacketTrace>> getMulitcastTrace(VlanId vlanId) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800395 Generator<Set<StaticPacketTrace>> gen = new McastGenerator(mcastRouteNib, this, vlanId);
psnehab823c512018-08-02 07:41:43 -0400396 List<Set<StaticPacketTrace>> multicastTraceList =
397 StreamSupport.stream(gen.spliterator(), false).collect(Collectors.toList());
398 return multicastTraceList;
399 }
400
Andrea Campanellae4084402017-12-15 15:27:31 +0100401 /**
402 * Computes a trace for a give packet that start in the network at the given connect point.
403 *
404 * @param completePath the path traversed by the packet
405 * @param in the input connect point
406 * @param trace the trace to build
Andrea Campanella696ef032018-02-27 18:03:17 +0100407 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanellae4084402017-12-15 15:27:31 +0100408 * @return the build trace for that packet.
409 */
Andrea Campanella696ef032018-02-27 18:03:17 +0100410 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
411 boolean isDualHomed) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100412
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100413 log.debug("------------------------------------------------------------");
414
Andrea Campanellae4084402017-12-15 15:27:31 +0100415 //if the trace already contains the input connect point there is a loop
416 if (pathContainsDevice(completePath, in.deviceId())) {
417 trace.addResultMessage("Loop encountered in device " + in.deviceId());
Andrea Campanella3ddbadb2018-03-09 14:52:10 -0800418 completePath.add(in);
419 trace.addCompletePath(completePath);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700420 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100421 return trace;
422 }
423
424 //let's add the input connect point
425 completePath.add(in);
426
427 //If the trace has no outputs for the given input we stop here
428 if (trace.getGroupOuputs(in.deviceId()) == null) {
429 computePath(completePath, trace, null);
430 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700431 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100432 return trace;
433 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100434
Andrea Campanella27140da2018-04-09 14:22:32 +0200435 //If the trace has outputs we analyze them all
Andrea Campanellae4084402017-12-15 15:27:31 +0100436 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100437
438 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100439 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100440 log.debug("Output path {}", cp);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800441 log.debug("{}", outputPath.getFinalPacket());
Andrea Campanella54923d62018-01-23 12:46:04 +0100442
Andrea Campanellae4084402017-12-15 15:27:31 +0100443 //Hosts for the the given output
Seyeon Jeong83e79862020-02-28 01:17:34 -0800444 Set<Host> hostsList = hostNib.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100445 //Hosts queried from the original ip or mac
446 Set<Host> hosts = getHosts(trace);
447
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800448 if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
449 outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
450 && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
451 .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
452 .vlanId())) {
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700453 if (trace.getGroupOuputs(in.deviceId()).size() == 1 &&
454 computePath(completePath, trace, outputPath.getOutput())) {
Andrea Campanellafe5d8df2018-03-12 11:07:35 -0700455 trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700456 trace.setSuccess(false);
Andrea Campanellafe5d8df2018-03-12 11:07:35 -0700457 }
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700458 } else if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700459 //If the two host collections contain the same item it means we reached the proper output
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100460 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella696ef032018-02-27 18:03:17 +0100461 if (computePath(completePath, trace, outputPath.getOutput())) {
462 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella0cc6acd2018-02-28 16:43:16 +0100463 trace.setSuccess(true);
Andrea Campanella696ef032018-02-27 18:03:17 +0100464 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100465 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100466 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100467
Andrea Campanella54923d62018-01-23 12:46:04 +0100468 //Getting the master when the packet gets sent as packet in
Seyeon Jeong83e79862020-02-28 01:17:34 -0800469 NodeId master = mastershipNib.getMasterFor(cp.deviceId());
470 // TODO if we don't need to print master node id, exclude mastership NIB which is used only here
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100471 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella54923d62018-01-23 12:46:04 +0100472 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100473 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100474
Seyeon Jeong83e79862020-02-28 01:17:34 -0800475 } else if (linkNib.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-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 Campanella3ddbadb2018-03-09 14:52:10 -0800493
Andrea Campanellab022b5e2018-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 Campanella3ddbadb2018-03-09 14:52:10 -0800497 temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
498 if (completePath.containsAll(temp)) {
499 completePath = temp;
500 }
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100501 }
502 }
503
Andrea Campanellae4084402017-12-15 15:27:31 +0100504 //let's add the ouput for the input
505 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100506 //let's compute the links for the given output
Seyeon Jeong83e79862020-02-28 01:17:34 -0800507 Set<Link> links = linkNib.getEgressLinks(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100508 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-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 Campanella8292ba62018-01-31 16:43:23 +0100513 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-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 Campanellad5e16ff2018-03-21 10:08:55 -0700518 traceInDevice(trace, updatedPacket.build(), dst, isDualHomed, completePath);
Andrea Campanellae4084402017-12-15 15:27:31 +0100519 //continue the trace along the path
Andrea Campanella696ef032018-02-27 18:03:17 +0100520 getTrace(completePath, dst, trace, isDualHomed);
Andrea Campanellae4084402017-12-15 15:27:31 +0100521 }
Seyeon Jeong83e79862020-02-28 01:17:34 -0800522 } else if (edgePortNib.isEdgePoint(outputPath.getOutput()) &&
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100523 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 Campanella0cc6acd2018-02-28 16:43:16 +0100528 trace.setSuccess(true);
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100529 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100530 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
531 return trace;
532 }
Seyeon Jeong83e79862020-02-28 01:17:34 -0800533 } else if (deviceNib.getPort(cp) != null && deviceNib.getPort(cp).isEnabled()) {
Andrea Campanella1445f7a2018-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 Campanellac6d10fc2018-02-27 12:42:28 +0100538 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanella08d07e12018-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)) ||
Seyeon Jeong83e79862020-02-28 01:17:34 -0800556 hostNib.getHostsByIp(finalIpAddress).isEmpty()) {
Andrea Campanella08d07e12018-03-07 14:27:54 -0800557 trace.addResultMessage("Packet is " +
558 ((EthTypeCriterion) outputPath.getFinalPacket()
Andrea Campanella7fa8f0a2018-03-09 15:30:22 -0800559 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
560 " and reached " + cp + " with hosts " + hostsList);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800561 } else {
562 trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
563 ipAddress);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700564 trace.setSuccess(false);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800565 }
566 } else {
Andrea Campanella2ff88a92018-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 Campanella2ff88a92018-03-06 15:21:09 -0800570 }
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800571 }
Andrea Campanella08d07e12018-03-07 14:27:54 -0800572 trace.setSuccess(true);
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100573 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100574 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100575
576 } else {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100577 computePath(completePath, trace, cp);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700578 trace.setSuccess(false);
Seyeon Jeong83e79862020-02-28 01:17:34 -0800579 if (deviceNib.getPort(cp) == null) {
Jon Hall4a28fc92018-04-24 18:03:10 -0700580 //Port is not existent on device.
Andrea Campanellae24d4922018-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 Campanellae4084402017-12-15 15:27:31 +0100588 }
589 }
590 return trace;
591 }
592
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100593
Andrea Campanellae4084402017-12-15 15:27:31 +0100594 /**
Andrea Campanellae6798012018-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 Campanella4c6170a2018-01-17 16:34:51 +0100601
Andrea Campanellae6798012018-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 Campanella4c6170a2018-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 Campanellae4084402017-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) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800665 hosts.addAll(hostNib.getHostsByIp(ipv4Criterion.ip().address()));
Andrea Campanellae4084402017-12-15 15:27:31 +0100666 }
667 if (ipv6Criterion != null) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800668 hosts.addAll(hostNib.getHostsByIp(ipv6Criterion.ip().address()));
Andrea Campanellae4084402017-12-15 15:27:31 +0100669 }
670 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
671 .getCriterion(Criterion.Type.ETH_DST));
672 if (ethCriterion != null) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800673 hosts.addAll(hostNib.getHostsByMac(ethCriterion.mac()));
Andrea Campanellae4084402017-12-15 15:27:31 +0100674 }
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 Campanella696ef032018-02-27 18:03:17 +0100685 private boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100686 List<ConnectPoint> traverseList = new ArrayList<>();
687 if (!completePath.contains(trace.getInitialConnectPoint())) {
688 traverseList.add(trace.getInitialConnectPoint());
689 }
Andrea Campanella27140da2018-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 Campanellae4084402017-12-15 15:27:31 +0100696 traverseList.addAll(completePath);
697 if (output != null && !completePath.contains(output)) {
698 traverseList.add(output);
699 }
Andrea Campanella696ef032018-02-27 18:03:17 +0100700 if (!trace.getCompletePaths().contains(traverseList)) {
Andrea Campanella3ddbadb2018-03-09 14:52:10 -0800701 trace.addCompletePath(ImmutableList.copyOf(traverseList));
Andrea Campanella696ef032018-02-27 18:03:17 +0100702 return true;
703 }
704 return false;
Andrea Campanellae4084402017-12-15 15:27:31 +0100705 }
706
707 /**
708 * Traces the packet inside a device starting from an input connect point.
709 *
Andrea Campanellad5e16ff2018-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 Campanellae4084402017-12-15 15:27:31 +0100715 * @return updated trace
716 */
Andrea Campanella696ef032018-02-27 18:03:17 +0100717 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700718 boolean isDualHomed, List<ConnectPoint> completePath) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100719
Andrea Campanella08d07e12018-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 Campanellab022b5e2018-01-31 14:59:03 +0100725 log.debug("Trace already contains device and given outputs");
726 return trace;
727 }
Andrea Campanella08d07e12018-03-07 14:27:54 -0800728
Andrea Campanellae4084402017-12-15 15:27:31 +0100729 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100730
731 //if device is not available exit here.
Seyeon Jeong83e79862020-02-28 01:17:34 -0800732 if (!deviceNib.isAvailable(in.deviceId())) {
Andrea Campanella17d45192018-01-18 17:11:42 +0100733 trace.addResultMessage("Device is offline " + in.deviceId());
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700734 computePath(completePath, trace, null);
Andrea Campanella17d45192018-01-18 17:11:42 +0100735 return trace;
736 }
737
Andrea Campanella1445f7a2018-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 Campanellae4084402017-12-15 15:27:31 +0100749 List<FlowEntry> flows = new ArrayList<>();
750 List<FlowEntry> outputFlows = new ArrayList<>();
Andrea Campanella8292ba62018-01-31 16:43:23 +0100751 List<Instruction> deferredInstructions = new ArrayList<>();
752
Andrea Campanellae4084402017-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 Campanellad5e16ff2018-03-21 10:08:55 -0700756 computePath(completePath, trace, null);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700757 trace.setSuccess(false);
Andrea Campanellae4084402017-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
Seyeon Jeong83e79862020-02-28 01:17:34 -0800770 .getOrDefault(driverNib.getDriverName(in.deviceId()), false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100771
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 Campanellae4084402017-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 Campanella09eec852018-02-05 19:39:25 +0100788 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700789 computePath(completePath, trace, null);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700790 trace.setSuccess(false);
Andrea Campanellae4084402017-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 Campanellacdf35cb2018-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 Campanellae4084402017-12-15 15:27:31 +0100806 } else {
807 tableId = nextTableIdEntry.table();
808 }
809
Andrea Campanellae4084402017-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 Campanellad5e16ff2018-03-21 10:08:55 -0700813 computePath(completePath, trace, null);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700814 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100815 return trace;
816 } else {
Andrea Campanella94c594a2018-02-06 18:58:40 +0100817
Andrea Campanellae4084402017-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 Campanella8292ba62018-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 Campanella94c594a2018-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 Campanellad5e16ff2018-03-21 10:08:55 -0700867 computePath(completePath, trace, null);
Andrea Campanella94c594a2018-02-06 18:58:40 +0100868 return trace;
869 }
870 }
871
872 }
873
Andrea Campanellae4084402017-12-15 15:27:31 +0100874 }
875 }
876
877 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100878 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100879 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100880
Andrea Campanellae4084402017-12-15 15:27:31 +0100881 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100882 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100883
Andrea Campanellae4084402017-12-15 15:27:31 +0100884 List<PortNumber> outputPorts = new ArrayList<>();
Andrea Campanella08d07e12018-03-07 14:27:54 -0800885 List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
Andrea Campanellae4084402017-12-15 15:27:31 +0100886
Andrea Campanella08d07e12018-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 Campanellad5e16ff2018-03-21 10:08:55 -0700898 entry.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanella08d07e12018-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 Campanellad5e16ff2018-03-21 10:08:55 -0700903 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups, completePath);
Andrea Campanella08d07e12018-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 Campanella54923d62018-01-23 12:46:04 +0100914 //TODO optimization
Andrea Campanella54923d62018-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 Campanellae4084402017-12-15 15:27:31 +0100919
Andrea Campanella54923d62018-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 Campanellae4084402017-12-15 15:27:31 +0100923 }
Andrea Campanella54923d62018-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 Campanella08d07e12018-03-07 14:27:54 -0800937 return outputFlowEntries;
938 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100939
Andrea Campanella08d07e12018-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 Campanella54923d62018-01-23 12:46:04 +0100948 }
Andrea Campanelladd71aca2018-04-04 13:10:45 +0200949 if (ip != null) {
Seyeon Jeong83e79862020-02-28 01:17:34 -0800950 Optional<ResolvedRoute> optionalRoute = routeNib.longestPrefixLookup(ip);
Andrea Campanelladd71aca2018-04-04 13:10:45 +0200951 if (optionalRoute.isPresent()) {
952 ResolvedRoute route = optionalRoute.get();
Seyeon Jeong83e79862020-02-28 01:17:34 -0800953 multipleRoutes = routeNib.getAllResolvedRoutes(route.prefix()).size() > 1;
Andrea Campanelladd71aca2018-04-04 13:10:45 +0200954 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100955 }
Andrea Campanella08d07e12018-03-07 14:27:54 -0800956 return multipleRoutes;
Andrea Campanellae4084402017-12-15 15:27:31 +0100957 }
958
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100959 /**
960 * Handles the specific case where the Input is the controller.
961 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
962 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
963 * the flood to all active physical ports of the device.
964 *
965 * @param trace the trace
966 * @param in the controller port
967 * @return the augmented trace.
968 */
969 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
970 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
971 .getCriterion(Criterion.Type.ETH_TYPE);
972 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
973 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
974 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
975 //get the active ports
Seyeon Jeong83e79862020-02-28 01:17:34 -0800976 List<Port> enabledPorts = deviceNib.getPorts(in.deviceId()).stream()
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100977 .filter(Port::isEnabled)
978 .collect(Collectors.toList());
979 //build an output from each one
980 enabledPorts.forEach(port -> {
981 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
982 ImmutableList.of(), trace.getInitialPacket());
983 trace.addGroupOutputPath(in.deviceId(), output);
984 });
985 return trace;
986 }
987 return null;
988 }
989
Andrea Campanella94c594a2018-02-06 18:58:40 +0100990 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
991 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
992 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
993 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
994 .vlanId().equals(VlanId.NONE);
995 }
996
997 /**
998 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
999 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
1000 * second to transition to following table
1001 *
1002 * @param packet the incoming packet
1003 * @param in the input connect point
1004 * @param packetVlanIdCriterion the vlan criterion from the packet
1005 * @param entryModVlanIdInstruction the entry vlan instruction
1006 * @return the second flow entry that matched
1007 */
1008 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
1009 VlanIdCriterion packetVlanIdCriterion,
1010 ModVlanIdInstruction entryModVlanIdInstruction) {
1011 FlowEntry secondVlanFlow = null;
1012 //Check the packet has been update from the first rule.
1013 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
1014 //find a rule on the same table that matches the vlan and
1015 // also all the other elements of the flow such as input port
Seyeon Jeong83e79862020-02-28 01:17:34 -08001016 secondVlanFlow = Lists.newArrayList(flowNib.getFlowEntriesByState(in.deviceId(),
1017 FlowEntry.FlowEntryState.ADDED)
1018 .iterator()).stream()
Andrea Campanella94c594a2018-02-06 18:58:40 +01001019 .filter(entry -> {
1020 return entry.table().equals(IndexTableId.of(10));
1021 })
1022 .filter(entry -> {
1023 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
1024 .getCriterion(Criterion.Type.VLAN_VID);
1025 return criterion != null && match(packet, entry)
1026 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
1027 }).findFirst().orElse(null);
1028
1029 }
1030 return secondVlanFlow;
1031 }
1032
Andrea Campanella8292ba62018-01-31 16:43:23 +01001033
Andrea Campanellae4084402017-12-15 15:27:31 +01001034 /**
Andrea Campanellae4084402017-12-15 15:27:31 +01001035 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
1036 *
1037 * @param packet the incoming packet
1038 * @return the updated packet
1039 */
1040 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
1041 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
1042 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
1043 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
1044
1045 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
1046 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
1047 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +01001048 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +01001049 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
1050 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
1051 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +01001052 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +01001053 builder.add(ethInstruction);
1054 }
1055 packet = updatePacket(packet, builder.build()).build();
1056 return packet;
1057 }
1058
1059 /**
1060 * Finds the flow entry with the minimun next table Id.
1061 *
1062 * @param deviceId the device to search
1063 * @param currentId the current id. the search will use this as minimum
1064 * @return the flow entry with the minimum table Id after the given one.
1065 */
1066 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
1067
1068 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
Seyeon Jeong83e79862020-02-28 01:17:34 -08001069 return Lists.newArrayList(flowNib.getFlowEntriesByState(deviceId, FlowEntry.FlowEntryState.ADDED)
Andrea Campanella39ec33e2018-04-13 12:14:48 +02001070 .iterator()).stream()
1071 .filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
Andrea Campanellae4084402017-12-15 15:27:31 +01001072 }
1073
Andrea Campanella8292ba62018-01-31 16:43:23 +01001074 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
1075 ConnectPoint in, List<Instruction> deferredInstructions,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001076 List<PortNumber> outputPorts, List<Group> groups,
1077 List<ConnectPoint> completePath) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001078
1079 //Update the packet with the deferred instructions
1080 Builder builder = updatePacket(packet, deferredInstructions);
1081
1082 //Gather any output instructions from the deferred instruction
1083 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
1084 return instruction.type().equals(Instruction.Type.OUTPUT);
1085 }).collect(Collectors.toList());
1086
1087 //We are considering deferred instructions from flows, there can only be one output.
1088 if (outputFlowInstruction.size() > 1) {
1089 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
1090 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
1091 }
1092 //If there is one output let's go through that
1093 if (outputFlowInstruction.size() == 1) {
1094 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
1095 ImmutableList.of());
1096 }
1097 //If there is no output let's see if there any deferred instruction point to groups.
1098 if (outputFlowInstruction.size() == 0) {
1099 getGroupsFromInstructions(trace, groups, deferredInstructions,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001100 in.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanella8292ba62018-01-31 16:43:23 +01001101 }
1102 return builder;
1103 }
1104
Andrea Campanellae4084402017-12-15 15:27:31 +01001105 /**
1106 * Gets group information from instructions.
1107 *
1108 * @param trace the trace we are building
1109 * @param groupsForDevice the set of groups for this device
1110 * @param instructions the set of instructions we are searching for groups.
1111 * @param deviceId the device we are considering
1112 * @param builder the builder of the input packet
1113 * @param outputPorts the output ports for that packet
1114 */
1115 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
1116 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +01001117 Builder builder, List<PortNumber> outputPorts,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001118 ConnectPoint in, List<ConnectPoint> completePath) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001119 List<Instruction> groupInstructionlist = new ArrayList<>();
Seyeon Jeonga9745512020-04-07 12:06:18 -07001120 // sort instructions according to priority (larger Instruction.Type ENUM constant first)
1121 // which enables to treat other actions before the OUTPUT action
1122 //TODO improve the priority scheme according to the OpenFlow ActionSet spec
1123 List<Instruction> instructionsSorted = new ArrayList<>();
1124 instructionsSorted.addAll(instructions);
1125 instructionsSorted.sort((instr1, instr2) -> {
1126 return Integer.compare(instr2.type().ordinal(), instr1.type().ordinal());
1127 });
1128
1129 for (Instruction instruction : instructionsSorted) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001130 log.debug("Considering Instruction {}", instruction);
1131 //if the instruction is not group we need to update the packet or add the output
1132 //to the possible outputs for this packet
1133 if (!instruction.type().equals(Instruction.Type.GROUP)) {
1134 //if the instruction is not group we need to update the packet or add the output
1135 //to the possible outputs for this packet
1136 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001137 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella573d4b92018-02-19 17:03:46 +01001138 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
1139 //clearing the groups because we start from the top.
1140 groupsForDevice.clear();
Andrea Campanellae4084402017-12-15 15:27:31 +01001141 } else {
1142 builder = translateInstruction(builder, instruction);
1143 }
1144 } else {
1145 //if the instuction is pointing to a group we need to get the group
1146 groupInstructionlist.add(instruction);
1147 }
1148 }
1149 //handle all the internal instructions pointing to a group.
1150 for (Instruction instr : groupInstructionlist) {
1151 GroupInstruction groupInstruction = (GroupInstruction) instr;
Seyeon Jeong83e79862020-02-28 01:17:34 -08001152 Group group = Lists.newArrayList(groupNib.getGroups(deviceId)).stream().filter(groupInternal -> {
Andrea Campanellae4084402017-12-15 15:27:31 +01001153 return groupInternal.id().equals(groupInstruction.groupId());
1154 }).findAny().orElse(null);
1155 if (group == null) {
1156 trace.addResultMessage("Null group for Instruction " + instr);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -07001157 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +01001158 break;
1159 }
Andrea Campanella94dfb9e2018-02-27 12:36:00 +01001160 if (group.buckets().buckets().size() == 0) {
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001161 trace.addResultMessage("Group " + group.id() + " has no buckets");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -07001162 trace.setSuccess(false);
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001163 computePath(completePath, trace, null);
Andrea Campanella94dfb9e2018-02-27 12:36:00 +01001164 break;
1165 }
Andrea Campanella573d4b92018-02-19 17:03:46 +01001166
Andrea Campanellae4084402017-12-15 15:27:31 +01001167 //Cycle in each of the group's buckets and add them to the groups for this Device.
1168 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella573d4b92018-02-19 17:03:46 +01001169
1170 //add the group to the traversed groups
1171 if (!groupsForDevice.contains(group)) {
1172 groupsForDevice.add(group);
1173 }
1174
Andrea Campanellae4084402017-12-15 15:27:31 +01001175 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001176 deviceId, builder, outputPorts, in, completePath);
Andrea Campanellae4084402017-12-15 15:27:31 +01001177 }
1178 }
1179 }
1180
1181 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001182 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1183 * a possible output from this device.
1184 *
1185 * @param trace the trace
1186 * @param in the input connect point
1187 * @param builder the packet builder
1188 * @param outputPorts the list of output ports for this device
1189 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +01001190 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001191 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001192 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001193 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1194 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +01001195 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella1445f7a2018-02-07 12:00:12 +01001196
Andrea Campanella2ff88a92018-03-06 15:21:09 -08001197 outputPorts.add(outputInstruction.port());
1198
1199 GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
1200 if (trace.getGroupOuputs(output.deviceId()) != null
1201 && trace.getGroupOuputs(output.deviceId()).contains(device)) {
1202 return;
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001203 }
Andrea Campanella2ff88a92018-03-06 15:21:09 -08001204 trace.addGroupOutputPath(in.deviceId(),
1205 new GroupsInDevice(output, groupsForDevice, builder.build()));
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001206 }
1207
1208 /**
Andrea Campanellae4084402017-12-15 15:27:31 +01001209 * Applies all give instructions to the input packet.
1210 *
1211 * @param packet the input packet
1212 * @param instructions the set of instructions
1213 * @return the packet with the applied instructions
1214 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001215 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1216 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +01001217 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +01001218 //FIXME optimize
1219 for (Instruction instruction : instructions) {
1220 newSelector = translateInstruction(newSelector, instruction);
1221 }
Andrea Campanellae4084402017-12-15 15:27:31 +01001222 return newSelector;
1223 }
1224
1225 /**
1226 * Applies an instruction to the packet in the form of a selector.
1227 *
1228 * @param newSelector the packet selector
1229 * @param instruction the instruction to be translated
1230 * @return the new selector with the applied instruction
1231 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001232 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001233 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +01001234 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +01001235 //TODO add as required
1236 Criterion criterion = null;
1237 switch (instruction.type()) {
1238 case L2MODIFICATION:
1239 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1240 switch (l2Instruction.subtype()) {
1241 case VLAN_ID:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001242 ModVlanIdInstruction vlanIdInstruction =
1243 (ModVlanIdInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001244 VlanId id = vlanIdInstruction.vlanId();
1245 criterion = Criteria.matchVlanId(id);
1246 break;
1247 case VLAN_POP:
1248 criterion = Criteria.matchVlanId(VlanId.NONE);
1249 break;
1250 case MPLS_PUSH:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001251 ModMplsHeaderInstruction mplsEthInstruction =
1252 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001253 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1254 break;
1255 case MPLS_POP:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001256 ModMplsHeaderInstruction mplsPopInstruction =
1257 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001258 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +01001259
1260 //When popping MPLS we remove label and BOS
1261 TrafficSelector temporaryPacket = newSelector.build();
1262 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001263 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +01001264 temporaryPacket.criteria().stream().filter(c -> {
1265 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1266 !c.type().equals(Criterion.Type.MPLS_BOS);
1267 }).forEach(noMplsSelector::add);
1268 newSelector = noMplsSelector;
1269 }
1270
Andrea Campanellae4084402017-12-15 15:27:31 +01001271 break;
1272 case MPLS_LABEL:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001273 ModMplsLabelInstruction mplsLabelInstruction =
1274 (ModMplsLabelInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001275 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +01001276 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001277 break;
1278 case ETH_DST:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001279 ModEtherInstruction modEtherDstInstruction =
1280 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001281 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1282 break;
1283 case ETH_SRC:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001284 ModEtherInstruction modEtherSrcInstruction =
1285 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001286 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1287 break;
1288 default:
1289 log.debug("Unsupported L2 Instruction");
1290 break;
1291 }
1292 break;
1293 default:
1294 log.debug("Unsupported Instruction");
1295 break;
1296 }
1297 if (criterion != null) {
1298 log.debug("Adding criterion {}", criterion);
1299 newSelector.add(criterion);
1300 }
1301 return newSelector;
1302 }
1303
1304 /**
1305 * Finds the rule in the device that mathces the input packet and has the highest priority.
1306 *
1307 * @param packet the input packet
1308 * @param in the connect point the packet comes in from
1309 * @param tableId the table to search
1310 * @return the flow entry
1311 */
1312 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1313 //Computing the possible match rules.
1314 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
Seyeon Jeong83e79862020-02-28 01:17:34 -08001315 return Lists.newArrayList(flowNib.getFlowEntriesByState(in.deviceId(), FlowEntry.FlowEntryState.ADDED)
Andrea Campanella39ec33e2018-04-13 12:14:48 +02001316 .iterator()).stream()
Andrea Campanellae4084402017-12-15 15:27:31 +01001317 .filter(flowEntry -> {
1318 return flowEntry.table().equals(tableId);
1319 })
1320 .filter(flowEntry -> {
1321 return match(packet, flowEntry);
1322 }).max(comparator).orElse(null);
1323 }
1324
1325 /**
1326 * Matches the packet with the given flow entry.
1327 *
1328 * @param packet the packet to match
1329 * @param flowEntry the flow entry to match the packet against
1330 * @return true if the packet matches the flow.
1331 */
1332 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001333 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1334 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +01001335 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +01001336 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1337 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001338 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanellae4084402017-12-15 15:27:31 +01001339 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001340 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1341 return matchMac(packet, (EthCriterion) criterion, false);
1342 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1343 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001344 } else {
1345 return packet.criteria().contains(criterion);
1346 }
1347 });
Simon Hunt026a2872017-11-13 17:09:43 -08001348 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001349
1350 /**
1351 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1352 *
1353 * @param packet the incoming packet
1354 * @param criterion the criterion to match
1355 * @return true if match
1356 */
1357 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1358 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1359 //if the packet does not have an IPv4 or IPv6 criterion we return true
1360 if (matchCriterion == null) {
1361 return false;
1362 }
1363 try {
1364 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1365 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1366 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1367 } catch (UnknownHostException e) {
1368 return false;
1369 }
1370 }
1371
1372 /**
1373 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1374 *
1375 * @param packet the incoming packet
1376 * @param hitCriterion the criterion to match
1377 * @param dst true if we are checking DST MAC
1378 * @return true if match
1379 */
1380 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1381 //Packet can have only one EthCriterion
1382 EthCriterion matchCriterion;
1383 if (dst) {
1384 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1385 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1386 criterion1.type().equals(Criterion.Type.ETH_DST);
1387 }).findFirst().orElse(null);
1388 } else {
1389 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1390 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1391 criterion1.type().equals(Criterion.Type.ETH_SRC);
1392 }).findFirst().orElse(null);
1393 }
1394 //if the packet does not have an ETH criterion we return true
1395 if (matchCriterion == null) {
1396 return true;
1397 }
1398 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1399 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1400 }
Simon Hunt026a2872017-11-13 17:09:43 -08001401}