blob: bc04136a9e8e911c001624b291c00b97fd4fd63b [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;
Seyeon Jeongac129562020-02-28 01:17:34 -080024import org.apache.commons.lang.StringUtils;
Andrea Campanella6be5c872018-02-21 14:28:20 +010025import org.apache.commons.lang3.tuple.Pair;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010026import org.onlab.packet.IpAddress;
Andrea Campanella01e886e2017-12-15 15:27:31 +010027import org.onlab.packet.VlanId;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010028import org.onosproject.cluster.NodeId;
Simon Hunt6fefd852017-11-13 17:09:43 -080029import org.onosproject.net.ConnectPoint;
Andrea Campanella01e886e2017-12-15 15:27:31 +010030import org.onosproject.net.DeviceId;
31import org.onosproject.net.Host;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010032import org.onosproject.net.HostId;
Andrea Campanella01e886e2017-12-15 15:27:31 +010033import org.onosproject.net.Link;
Andrea Campanella6f2d6742018-02-07 12:00:12 +010034import org.onosproject.net.Port;
Andrea Campanella01e886e2017-12-15 15:27:31 +010035import org.onosproject.net.PortNumber;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010036import org.onosproject.net.config.ConfigException;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010037import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella01e886e2017-12-15 15:27:31 +010038import org.onosproject.net.flow.DefaultTrafficSelector;
39import org.onosproject.net.flow.FlowEntry;
40import org.onosproject.net.flow.FlowRule;
Andrea Campanella01e886e2017-12-15 15:27:31 +010041import org.onosproject.net.flow.IndexTableId;
42import org.onosproject.net.flow.TableId;
Simon Hunt6fefd852017-11-13 17:09:43 -080043import org.onosproject.net.flow.TrafficSelector;
Andrea Campanella01e886e2017-12-15 15:27:31 +010044import org.onosproject.net.flow.criteria.Criteria;
45import org.onosproject.net.flow.criteria.Criterion;
46import org.onosproject.net.flow.criteria.EthCriterion;
47import org.onosproject.net.flow.criteria.EthTypeCriterion;
48import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanella58b3b522018-02-06 15:46:52 +010049import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanella01e886e2017-12-15 15:27:31 +010050import org.onosproject.net.flow.instructions.Instruction;
51import org.onosproject.net.flow.instructions.Instructions;
52import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
53import org.onosproject.net.flow.instructions.L2ModificationInstruction;
54import org.onosproject.net.group.Group;
55import org.onosproject.net.group.GroupBucket;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010056import org.onosproject.net.host.InterfaceIpAddress;
57import org.onosproject.net.intf.Interface;
Andrea Campanellacc2424a2018-03-07 14:27:54 -080058import org.onosproject.routeservice.ResolvedRoute;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010059import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Seyeon Jeong8d3cad22020-02-28 01:17:34 -080060import org.onosproject.t3.api.DeviceNib;
61import org.onosproject.t3.api.DriverNib;
62import org.onosproject.t3.api.EdgePortNib;
63import org.onosproject.t3.api.FlowNib;
64import org.onosproject.t3.api.GroupNib;
Andrea Campanella01e886e2017-12-15 15:27:31 +010065import org.onosproject.t3.api.GroupsInDevice;
Seyeon Jeong8d3cad22020-02-28 01:17:34 -080066import org.onosproject.t3.api.HostNib;
67import org.onosproject.t3.api.LinkNib;
68import org.onosproject.t3.api.MastershipNib;
69import org.onosproject.t3.api.MulticastRouteNib;
70import org.onosproject.t3.api.NetworkConfigNib;
Seyeon Jeongac129562020-02-28 01:17:34 -080071import org.onosproject.t3.api.NibProfile;
Seyeon Jeong8d3cad22020-02-28 01:17:34 -080072import org.onosproject.t3.api.RouteNib;
Simon Hunt6fefd852017-11-13 17:09:43 -080073import org.onosproject.t3.api.StaticPacketTrace;
74import org.onosproject.t3.api.TroubleshootService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070075import org.osgi.service.component.annotations.Component;
Simon Hunt6fefd852017-11-13 17:09:43 -080076import org.slf4j.Logger;
77
Andrea Campanella01e886e2017-12-15 15:27:31 +010078import java.net.UnknownHostException;
79import java.util.ArrayList;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010080import java.util.Collection;
Andrea Campanella01e886e2017-12-15 15:27:31 +010081import java.util.Collections;
82import java.util.Comparator;
83import java.util.HashSet;
84import java.util.List;
Andrea Campanellacc2424a2018-03-07 14:27:54 -080085import java.util.Optional;
Andrea Campanella01e886e2017-12-15 15:27:31 +010086import java.util.Set;
87import java.util.stream.Collectors;
Seyeon Jeong8d3cad22020-02-28 01:17:34 -080088import java.util.stream.Stream;
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 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700106@Component(immediate = true, service = TroubleshootService.class)
Simon Hunt6fefd852017-11-13 17:09:43 -0800107public class TroubleshootManager implements TroubleshootService {
108
109 private static final Logger log = getLogger(TroubleshootManager.class);
110
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100111 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
112
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800113 // uses a snapshot (cache) of NIBs instead of interacting with ONOS core in runtime
Seyeon Jeongac129562020-02-28 01:17:34 -0800114 protected FlowNib flowNib = FlowNib.getInstance();
115 protected GroupNib groupNib = GroupNib.getInstance();
116 protected LinkNib linkNib = LinkNib.getInstance();
117 protected HostNib hostNib = HostNib.getInstance();
118 protected DeviceNib deviceNib = DeviceNib.getInstance();
119 protected DriverNib driverNib = DriverNib.getInstance();
120 protected MastershipNib mastershipNib = MastershipNib.getInstance();
121 protected EdgePortNib edgePortNib = EdgePortNib.getInstance();
122 protected RouteNib routeNib = RouteNib.getInstance();
123 protected NetworkConfigNib networkConfigNib = NetworkConfigNib.getInstance();
124 protected MulticastRouteNib mcastRouteNib = MulticastRouteNib.getInstance();
Simon Hunt6fefd852017-11-13 17:09:43 -0800125
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800126 @Override
Seyeon Jeongac129562020-02-28 01:17:34 -0800127 public boolean checkNibValidity() {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800128 return Stream.of(flowNib, groupNib, linkNib, hostNib, deviceNib, driverNib,
129 mastershipNib, edgePortNib, routeNib, networkConfigNib, mcastRouteNib)
Seyeon Jeongac129562020-02-28 01:17:34 -0800130 .allMatch(nib -> nib != null && nib.isValid());
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800131 }
Simon Hunt6fefd852017-11-13 17:09:43 -0800132
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800133 @Override
Seyeon Jeongac129562020-02-28 01:17:34 -0800134 public String printNibSummary() {
135 StringBuilder summary = new StringBuilder().append("*** Current NIB in valid: ***\n");
136 Stream.of(flowNib, groupNib, linkNib, hostNib, deviceNib, driverNib,
137 mastershipNib, edgePortNib, routeNib, networkConfigNib, mcastRouteNib)
138 .forEach(nib -> {
139 NibProfile profile = nib.getProfile();
140 summary.append(String.format(
141 nib.getClass().getName() + " created %s from %s\n",
142 profile.date(), profile.sourceType()));
143 });
144
145 return summary.append(StringUtils.rightPad("", 125, '-')).toString();
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800146 }
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200147
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100148 @Override
Andrea Campanella6be5c872018-02-21 14:28:20 +0100149 public List<StaticPacketTrace> pingAll(EtherType type) {
150 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800151 hostNib.getHosts().forEach(host -> {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100152 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
153 if (ipAddresses.size() > 0) {
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100154 //check if the host has only local IPs of that ETH type
155 boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800156 hostNib.getHosts().forEach(hostToPing -> {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100157 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100158 //check if the other host has only local IPs of that ETH type
159 boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
160 boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
161 //Trace is done only if they are both local and under the same location
162 // or not local and if they are not the same host.
163 if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
164 (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
165 && !host.equals(hostToPing)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100166 tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
Andrea Campanella6be5c872018-02-21 14:28:20 +0100167 }
168 });
169 }
170 });
171 return tracesBuilder.build();
172 }
173
174 @Override
Andrea Campanella41de8062018-02-28 16:43:16 +0100175 public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800176 return new PingAllGenerator(type, hostNib, this);
Andrea Campanella41de8062018-02-28 16:43:16 +0100177 }
178
179 @Override
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200180 public Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800181 return new McastGenerator(mcastRouteNib, this, vlanId);
Andrea Campanellafa3ec192018-04-06 16:30:18 +0200182 }
183
184 @Override
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100185 public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800186 Host source = hostNib.getHost(sourceHost);
187 Host destination = hostNib.getHost(destinationHost);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100188
Andrea Campanella6be5c872018-02-21 14:28:20 +0100189 //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
190 StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100191
192 if (source == null) {
193 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700194 failTrace.setSuccess(false);
195
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100196 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100197 }
198
199 if (destination == null) {
200 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700201 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100202 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100203 }
204
205 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100206 .matchEthType(etherType.ethType().toShort())
207 .matchEthDst(source.mac())
208 .matchVlanId(source.vlan());
209
210
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100211 try {
Andrea Campanella15278a12018-03-26 10:39:22 -0700212 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100213 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
214 // we are under same leaf so it's L2 Unicast.
215 if (areBridged(source, destination)) {
216 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100217 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800218 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100219 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
220 trace.addEndpointHosts(Pair.of(source, destination));
221 traces.add(trace);
222 });
Andrea Campanella15278a12018-03-26 10:39:22 -0700223 //The destination host is not dual homed, if it is the other path might be done through routing.
224 if (destination.locations().size() == 1) {
225 return traces.build();
226 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100227 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100228 //handle the IPs for src and dst in case of L3
229 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
230
231 //Match on the source IP
232 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100233 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100234 }
235
236 //Match on destination IP
237 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100238 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100239 }
240
241 } else {
242 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
243 "please use packet based");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700244 failTrace.setSuccess(false);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100245 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100246 }
247
248 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800249 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigNib.getConfig(source.location()
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100250 .deviceId(), SegmentRoutingDeviceConfig.class);
251 if (segmentRoutingConfig != null) {
252 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
253 } else {
254 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
255 " router MAC from segment routing config can't perform L3 tracing.");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700256 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100257 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100258 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800259 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100260 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
261 trace.addEndpointHosts(Pair.of(source, destination));
262 traces.add(trace);
263 });
264 return traces.build();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100265
266 } catch (ConfigException e) {
267 failTrace.addResultMessage("Can't get config " + e.getMessage());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100268 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100269 }
270 }
271
272 /**
273 * Matches src and dst IPs based on host information.
274 *
275 * @param host the host
276 * @param failTrace the trace to use in case of failure
277 * @param selectorBuilder the packet we are building to trace
278 * @param etherType the traffic type
279 * @param src is this src host or dst host
280 * @return true if properly matched
281 */
282 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
283 EtherType etherType, boolean src) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100284 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100285
286 if (ips.size() > 0) {
Andrea Campanella465c7be2018-02-21 14:43:21 +0100287 if (etherType.equals(EtherType.IPV4)) {
288 if (src) {
289 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
290 } else {
291 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
292 }
293 } else if (etherType.equals(EtherType.IPV6)) {
294 if (src) {
295 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
296 } else {
297 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
298 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100299 }
300 } else {
301 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700302 failTrace.setSuccess(false);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100303 return false;
304 }
305 return true;
306 }
307
Andrea Campanella41de8062018-02-28 16:43:16 +0100308 List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100309 return host.ipAddresses().stream().filter(ipAddress -> {
310 boolean correctIp = false;
311 if (etherType.equals(EtherType.IPV4)) {
312 correctIp = ipAddress.isIp4();
313 } else if (etherType.equals(EtherType.IPV6)) {
314 correctIp = ipAddress.isIp6();
315 }
316 if (checklocal) {
317 correctIp = correctIp && !ipAddress.isLinkLocal();
318 }
319 return correctIp;
320 }).collect(Collectors.toList());
321 }
322
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100323 /**
324 * Checks that two hosts are bridged (L2Unicast).
325 *
326 * @param source the source host
327 * @param destination the destination host
328 * @return true if bridged.
329 * @throws ConfigException if config can't be properly retrieved
330 */
331 private boolean areBridged(Host source, Host destination) throws ConfigException {
332
Andrea Campanella7b84c072018-03-06 15:21:09 -0800333 //If the locations is not the same we don't even check vlan or subnets
334 if (Collections.disjoint(source.locations(), destination.locations())) {
335 return false;
336 }
337
338 if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
339 && !source.vlan().equals(destination.vlan())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100340 return false;
341 }
342
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800343 InterfaceConfig interfaceCfgH1 = networkConfigNib.getConfig(source.location(), InterfaceConfig.class);
344 InterfaceConfig interfaceCfgH2 = networkConfigNib.getConfig(destination.location(), InterfaceConfig.class);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100345 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
346
347 //following can be optimized but for clarity is left as is
348 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
349 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
350
Andrea Campanella7b84c072018-03-06 15:21:09 -0800351 if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
352 return intfH1.vlanUntagged().equals(destination.vlan()) ||
353 intfH1.vlanNative().equals(destination.vlan());
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100354 }
355
Andrea Campanella7b84c072018-03-06 15:21:09 -0800356 if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
357 return intfH2.vlanUntagged().equals(source.vlan()) ||
358 intfH2.vlanNative().equals(source.vlan());
359 }
360
361 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100362 return false;
363 }
364
365 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
366 return false;
367 }
368
369 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
370 intersection.retainAll(intfH2.ipAddressesList());
371 if (intersection.size() == 0) {
372 return false;
373 }
374 }
375 return true;
376 }
377
Simon Hunt6fefd852017-11-13 17:09:43 -0800378 @Override
379 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100380 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100381 //device must exist in ONOS
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800382 Preconditions.checkNotNull(deviceNib.getDevice(in.deviceId()),
Andrea Campanella37d10622018-01-18 17:11:42 +0100383 "Device " + in.deviceId() + " must exist in ONOS");
384
Andrea Campanella01e886e2017-12-15 15:27:31 +0100385 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100386 boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100387 //FIXME this can be done recursively
Andrea Campanella01e886e2017-12-15 15:27:31 +0100388 //Building output connect Points
389 List<ConnectPoint> path = new ArrayList<>();
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700390 trace = traceInDevice(trace, packet, in, isDualHomed, path);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100391 trace = getTrace(path, in, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100392 return trace;
393 }
394
psneha5c9f51b2018-08-02 07:41:43 -0400395 @Override
396 public List<Set<StaticPacketTrace>> getMulitcastTrace(VlanId vlanId) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800397 Generator<Set<StaticPacketTrace>> gen = new McastGenerator(mcastRouteNib, this, vlanId);
psneha5c9f51b2018-08-02 07:41:43 -0400398 List<Set<StaticPacketTrace>> multicastTraceList =
399 StreamSupport.stream(gen.spliterator(), false).collect(Collectors.toList());
400 return multicastTraceList;
401 }
402
Andrea Campanella01e886e2017-12-15 15:27:31 +0100403 /**
404 * Computes a trace for a give packet that start in the network at the given connect point.
405 *
406 * @param completePath the path traversed by the packet
407 * @param in the input connect point
408 * @param trace the trace to build
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100409 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanella01e886e2017-12-15 15:27:31 +0100410 * @return the build trace for that packet.
411 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100412 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
413 boolean isDualHomed) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100414
Andrea Campanellae04aac92018-01-31 14:59:03 +0100415 log.debug("------------------------------------------------------------");
416
Andrea Campanella01e886e2017-12-15 15:27:31 +0100417 //if the trace already contains the input connect point there is a loop
418 if (pathContainsDevice(completePath, in.deviceId())) {
419 trace.addResultMessage("Loop encountered in device " + in.deviceId());
Andrea Campanellaece11772018-03-09 14:52:10 -0800420 completePath.add(in);
421 trace.addCompletePath(completePath);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700422 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100423 return trace;
424 }
425
426 //let's add the input connect point
427 completePath.add(in);
428
429 //If the trace has no outputs for the given input we stop here
430 if (trace.getGroupOuputs(in.deviceId()) == null) {
431 computePath(completePath, trace, null);
432 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700433 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100434 return trace;
435 }
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100436
Andrea Campanella67b75602018-04-09 14:22:32 +0200437 //If the trace has outputs we analyze them all
Andrea Campanella01e886e2017-12-15 15:27:31 +0100438 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100439
440 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellae04aac92018-01-31 14:59:03 +0100441 log.debug("Connect point in {}", in);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100442 log.debug("Output path {}", cp);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800443 log.debug("{}", outputPath.getFinalPacket());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100444
Andrea Campanella01e886e2017-12-15 15:27:31 +0100445 //Hosts for the the given output
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800446 Set<Host> hostsList = hostNib.getConnectedHosts(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100447 //Hosts queried from the original ip or mac
448 Set<Host> hosts = getHosts(trace);
449
Andrea Campanella7b84c072018-03-06 15:21:09 -0800450 if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
451 outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
452 && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
453 .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
454 .vlanId())) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700455 if (trace.getGroupOuputs(in.deviceId()).size() == 1 &&
456 computePath(completePath, trace, outputPath.getOutput())) {
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700457 trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700458 trace.setSuccess(false);
Andrea Campanella6fb95fc2018-03-12 11:07:35 -0700459 }
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700460 } else if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700461 //If the two host collections contain the same item it means we reached the proper output
Andrea Campanellae04aac92018-01-31 14:59:03 +0100462 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100463 if (computePath(completePath, trace, outputPath.getOutput())) {
464 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella41de8062018-02-28 16:43:16 +0100465 trace.setSuccess(true);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100466 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100467 break;
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100468 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100469
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100470 //Getting the master when the packet gets sent as packet in
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800471 NodeId master = mastershipNib.getMasterFor(cp.deviceId());
472 // TODO if we don't need to print master node id, exclude mastership NIB which is used only here
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100473 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100474 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella58b3b522018-02-06 15:46:52 +0100475 handleVlanToController(outputPath, trace);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100476
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800477 } else if (linkNib.getEgressLinks(cp).size() > 0) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100478
479 //TODO this can be optimized if we use a Tree structure for paths.
480 //if we already have outputs let's check if the one we are considering starts from one of the devices
481 // in any of the ones we have.
482 if (trace.getCompletePaths().size() > 0) {
483 ConnectPoint inputForOutput = null;
484 List<ConnectPoint> previousPath = new ArrayList<>();
485 for (List<ConnectPoint> path : trace.getCompletePaths()) {
486 for (ConnectPoint connect : path) {
487 //if the path already contains the input for the output we've found we use it
488 if (connect.equals(in)) {
489 inputForOutput = connect;
490 previousPath = path;
491 break;
492 }
493 }
494 }
Andrea Campanellaece11772018-03-09 14:52:10 -0800495
Andrea Campanellae04aac92018-01-31 14:59:03 +0100496 //we use the pre-existing path up to the point we fork to a new output
497 if (inputForOutput != null && completePath.contains(inputForOutput)) {
498 List<ConnectPoint> temp = new ArrayList<>(previousPath);
Andrea Campanellaece11772018-03-09 14:52:10 -0800499 temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
500 if (completePath.containsAll(temp)) {
501 completePath = temp;
502 }
Andrea Campanellae04aac92018-01-31 14:59:03 +0100503 }
504 }
505
Andrea Campanella01e886e2017-12-15 15:27:31 +0100506 //let's add the ouput for the input
507 completePath.add(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100508 //let's compute the links for the given output
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800509 Set<Link> links = linkNib.getEgressLinks(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100510 log.debug("Egress Links {}", links);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100511 //For each link we trace the corresponding device
512 for (Link link : links) {
513 ConnectPoint dst = link.dst();
514 //change in-port to the dst link in port
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100515 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100516 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
517 updatedPacket.add(Criteria.matchInPort(dst.port()));
518 log.debug("DST Connect Point {}", dst);
519 //build the elements for that device
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700520 traceInDevice(trace, updatedPacket.build(), dst, isDualHomed, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100521 //continue the trace along the path
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100522 getTrace(completePath, dst, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100523 }
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800524 } else if (edgePortNib.isEdgePoint(outputPath.getOutput()) &&
Andrea Campanella04924b92018-01-17 16:34:51 +0100525 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
526 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
527 .mac().isMulticast()) {
528 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
529 " which is enabled and is edge port");
Andrea Campanella41de8062018-02-28 16:43:16 +0100530 trace.setSuccess(true);
Andrea Campanella04924b92018-01-17 16:34:51 +0100531 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella04924b92018-01-17 16:34:51 +0100532 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
533 return trace;
534 }
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800535 } else if (deviceNib.getPort(cp) != null && deviceNib.getPort(cp).isEnabled()) {
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100536 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
537 .getCriterion(Criterion.Type.ETH_TYPE);
538 //We treat as correct output only if it's not LLDP or BDDP
539 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
Andrea Campanella62841d42018-02-27 12:42:28 +0100540 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800541 if (computePath(completePath, trace, outputPath.getOutput())) {
542 if (hostsList.isEmpty()) {
543 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
544 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
545 cp + " with no hosts connected ");
546 } else {
547 IpAddress ipAddress = null;
548 if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
549 ipAddress = ((IPCriterion) trace.getInitialPacket()
550 .getCriterion(Criterion.Type.IPV4_DST)).ip().address();
551 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
552 ipAddress = ((IPCriterion) trace.getInitialPacket()
553 .getCriterion(Criterion.Type.IPV6_DST)).ip().address();
554 }
555 if (ipAddress != null) {
556 IpAddress finalIpAddress = ipAddress;
557 if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800558 hostNib.getHostsByIp(finalIpAddress).isEmpty()) {
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800559 trace.addResultMessage("Packet is " +
560 ((EthTypeCriterion) outputPath.getFinalPacket()
Andrea Campanella220f19b2018-03-09 15:30:22 -0800561 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
562 " and reached " + cp + " with hosts " + hostsList);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800563 } else {
564 trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
565 ipAddress);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700566 trace.setSuccess(false);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800567 }
568 } else {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800569 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
570 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
571 cp + " with hosts " + hostsList);
Andrea Campanella7b84c072018-03-06 15:21:09 -0800572 }
Andrea Campanella7b84c072018-03-06 15:21:09 -0800573 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800574 trace.setSuccess(true);
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100575 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100576 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100577
578 } else {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100579 computePath(completePath, trace, cp);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700580 trace.setSuccess(false);
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800581 if (deviceNib.getPort(cp) == null) {
Jon Hall7d77fe12018-04-24 18:03:10 -0700582 //Port is not existent on device.
Andrea Campanella15278a12018-03-26 10:39:22 -0700583 log.warn("Port {} is not available on device.", cp);
584 trace.addResultMessage("Port " + cp + "is not available on device. Packet is dropped");
585 } else {
586 //No links means that the packet gets dropped.
587 log.warn("No links out of {}", cp);
588 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
589 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100590 }
591 }
592 return trace;
593 }
594
Andrea Campanella04924b92018-01-17 16:34:51 +0100595
Andrea Campanella01e886e2017-12-15 15:27:31 +0100596 /**
Andrea Campanella58b3b522018-02-06 15:46:52 +0100597 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
598 * If ONOS applied a vlan we remove it.
599 *
600 * @param outputPath the output
601 * @param trace the trace we are building
602 */
Andrea Campanella04924b92018-01-17 16:34:51 +0100603
Andrea Campanella58b3b522018-02-06 15:46:52 +0100604 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
605
606 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
607 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
608
609 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
610
611 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
612 //removing the final vlanId
613 finalCriteria.remove(finalVid);
614 Builder packetUpdated = DefaultTrafficSelector.builder();
615 finalCriteria.forEach(packetUpdated::add);
616 //Initial was none so we set it to that
617 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
618 //Update final packet
619 outputPath.setFinalPacket(packetUpdated.build());
620 }
621 }
622
623 /**
Andrea Campanella04924b92018-01-17 16:34:51 +0100624 * Checks if the device has other outputs than the given connect point.
625 *
626 * @param inDeviceId the device
627 * @param trace the trace we are building
628 * @param cp an output connect point
629 * @return true if the device has other outputs.
630 */
631 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
632 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
633 return !groupsInDevice.getOutput().equals(cp);
634 }).count() > 0;
635 }
636
637 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100638 * Checks if the path contains the device.
639 *
640 * @param completePath the path
641 * @param deviceId the device to check
642 * @return true if the path contains the device
643 */
644 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
645 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
646 for (ConnectPoint cp : completePath) {
647 if (cp.deviceId().equals(deviceId)) {
648 return true;
649 }
650 }
651 return false;
652 }
653
654 /**
655 * Gets the hosts for the given initial packet.
656 *
657 * @param trace the trace we are building
658 * @return set of the hosts we are trying to reach
659 */
660 private Set<Host> getHosts(StaticPacketTrace trace) {
661 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
662 .getCriterion(Criterion.Type.IPV4_DST));
663 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
664 .getCriterion(Criterion.Type.IPV6_DST));
665 Set<Host> hosts = new HashSet<>();
666 if (ipv4Criterion != null) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800667 hosts.addAll(hostNib.getHostsByIp(ipv4Criterion.ip().address()));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100668 }
669 if (ipv6Criterion != null) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800670 hosts.addAll(hostNib.getHostsByIp(ipv6Criterion.ip().address()));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100671 }
672 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
673 .getCriterion(Criterion.Type.ETH_DST));
674 if (ethCriterion != null) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800675 hosts.addAll(hostNib.getHostsByMac(ethCriterion.mac()));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100676 }
677 return hosts;
678 }
679
680 /**
681 * Computes the list of traversed connect points.
682 *
683 * @param completePath the list of devices
684 * @param trace the trace we are building
685 * @param output the final output connect point
686 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100687 private boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100688 List<ConnectPoint> traverseList = new ArrayList<>();
689 if (!completePath.contains(trace.getInitialConnectPoint())) {
690 traverseList.add(trace.getInitialConnectPoint());
691 }
Andrea Campanella67b75602018-04-09 14:22:32 +0200692
693 if (output != null && trace.getInitialConnectPoint().deviceId().equals(output.deviceId())) {
694 trace.addCompletePath(ImmutableList.of(trace.getInitialConnectPoint(), output));
695 return true;
696 }
697
Andrea Campanella01e886e2017-12-15 15:27:31 +0100698 traverseList.addAll(completePath);
699 if (output != null && !completePath.contains(output)) {
700 traverseList.add(output);
701 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100702 if (!trace.getCompletePaths().contains(traverseList)) {
Andrea Campanellaece11772018-03-09 14:52:10 -0800703 trace.addCompletePath(ImmutableList.copyOf(traverseList));
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100704 return true;
705 }
706 return false;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100707 }
708
709 /**
710 * Traces the packet inside a device starting from an input connect point.
711 *
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700712 * @param trace the trace we are building
713 * @param packet the packet we are tracing
714 * @param in the input connect point.
715 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
716 * @param completePath the path up until this device
Andrea Campanella01e886e2017-12-15 15:27:31 +0100717 * @return updated trace
718 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100719 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700720 boolean isDualHomed, List<ConnectPoint> completePath) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100721
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800722 boolean multipleRoutes = false;
723 if (trace.getGroupOuputs(in.deviceId()) != null) {
724 multipleRoutes = multipleRoutes(trace);
725 }
726 if (trace.getGroupOuputs(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100727 log.debug("Trace already contains device and given outputs");
728 return trace;
729 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800730
Andrea Campanella01e886e2017-12-15 15:27:31 +0100731 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100732
733 //if device is not available exit here.
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800734 if (!deviceNib.isAvailable(in.deviceId())) {
Andrea Campanella37d10622018-01-18 17:11:42 +0100735 trace.addResultMessage("Device is offline " + in.deviceId());
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700736 computePath(completePath, trace, null);
Andrea Campanella37d10622018-01-18 17:11:42 +0100737 return trace;
738 }
739
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100740 //handle when the input is the controller
741 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
742 // a packet in from the controller will not actually traverse the pipeline and have no such notion
743 // as the input port.
744 if (in.port().equals(PortNumber.CONTROLLER)) {
745 StaticPacketTrace outputTrace = inputFromController(trace, in);
746 if (outputTrace != null) {
747 return trace;
748 }
749 }
750
Andrea Campanella01e886e2017-12-15 15:27:31 +0100751 List<FlowEntry> flows = new ArrayList<>();
752 List<FlowEntry> outputFlows = new ArrayList<>();
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100753 List<Instruction> deferredInstructions = new ArrayList<>();
754
Andrea Campanella01e886e2017-12-15 15:27:31 +0100755 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
756 if (nextTableIdEntry == null) {
757 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700758 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700759 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100760 return trace;
761 }
762 TableId tableId = nextTableIdEntry.table();
763 FlowEntry flowEntry;
764 boolean output = false;
765 while (!output) {
766 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
767 //get the rule that matches the incoming packet
768 flowEntry = matchHighestPriority(packet, in, tableId);
769 log.debug("Found Flow Entry {}", flowEntry);
770
771 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800772 .getOrDefault(driverNib.getDriverName(in.deviceId()), false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100773
774 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
775 if (flowEntry == null && isOfdpaHardware) {
776 log.debug("Ofdpa Hw setup, no flow rule means table miss");
777
Andrea Campanella01e886e2017-12-15 15:27:31 +0100778 if (((IndexTableId) tableId).id() == 27) {
779 //Apparently a miss but Table 27 on OFDPA is a fixed table
780 packet = handleOfdpa27FixedTable(trace, packet);
781 }
782
783 //Finding next table to go In case of miss
784 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
785 log.debug("Next table id entry {}", nextTableIdEntry);
786
787 //FIXME find better solution that enable granularity greater than 0 or all rules
788 //(another possibility is max tableId)
789 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella7382c7f2018-02-05 19:39:25 +0100790 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700791 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700792 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100793 return trace;
794
795 } else if (nextTableIdEntry == null) {
796 //Means that no more flow rules are present
797 output = true;
798
799 } else if (((IndexTableId) tableId).id() == 20) {
800 //if the table is 20 OFDPA skips to table 50
801 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
802 tableId = IndexTableId.of(50);
803
Andrea Campanella14c84e52018-06-05 12:04:02 +0200804 } else if (((IndexTableId) tableId).id() == 40) {
805 //if the table is 40 OFDPA skips to table 60
806 log.debug("A miss on Table 40 on OFDPA means that we skip directly to table 60");
807 tableId = IndexTableId.of(60);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100808 } else {
809 tableId = nextTableIdEntry.table();
810 }
811
Andrea Campanella01e886e2017-12-15 15:27:31 +0100812 } else if (flowEntry == null) {
813 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
814 in.deviceId() + ". Dropping");
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700815 computePath(completePath, trace, null);
Andrea Campanellac8e6a502018-03-12 19:25:44 -0700816 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100817 return trace;
818 } else {
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100819
Andrea Campanella01e886e2017-12-15 15:27:31 +0100820 //IF the table has a transition
821 if (flowEntry.treatment().tableTransition() != null) {
822 //update the next table we transitions to
823 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
824 log.debug("Flow Entry has transition to table Id {}", tableId);
825 flows.add(flowEntry);
826 } else {
827 //table has no transition so it means that it's an output rule if on the last table
828 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
829 flows.add(flowEntry);
830 outputFlows.add(flowEntry);
831 output = true;
832 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100833 //update the packet according to the immediate actions of this flow rule.
834 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
835
836 //save the deferred rules for later
837 deferredInstructions.addAll(flowEntry.treatment().deferred());
838
839 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
840 if (flowEntry.treatment().clearedDeferred()) {
841 deferredInstructions.clear();
842 }
843
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100844 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
845 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
846
847 //Let's get the packet vlanId instruction
848 VlanIdCriterion packetVlanIdCriterion =
849 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
850
851 //Let's get the flow entry vlan mod instructions
852 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
853 .immediate().stream()
854 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
855 .findFirst().orElse(null);
856
857 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
858 // is a flow rule that matches on same criteria and with updated vlanId
859 if (entryModVlanIdInstruction != null) {
860
861 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
862 packetVlanIdCriterion, entryModVlanIdInstruction);
863
864 //We found the flow that we expected
865 if (secondVlanFlow != null) {
866 flows.add(secondVlanFlow);
867 } else {
868 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700869 computePath(completePath, trace, null);
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100870 return trace;
871 }
872 }
873
874 }
875
Andrea Campanella01e886e2017-12-15 15:27:31 +0100876 }
877 }
878
879 //Creating a modifiable builder for the output packet
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100880 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100881 packet.criteria().forEach(builder::add);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100882
Andrea Campanella01e886e2017-12-15 15:27:31 +0100883 //Adding all the flows to the trace
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100884 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100885
Andrea Campanella01e886e2017-12-15 15:27:31 +0100886 List<PortNumber> outputPorts = new ArrayList<>();
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800887 List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100888
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800889
890 log.debug("Handling Groups");
891 //Analyze Groups
892 List<Group> groups = new ArrayList<>();
893
894 Collection<FlowEntry> nonOutputFlows = flows;
895 nonOutputFlows.removeAll(outputFlowEntries);
896
897 //Handling groups pointed at by immediate instructions
898 for (FlowEntry entry : flows) {
899 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700900 entry.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800901 }
902
903 //If we have deferred instructions at this point we handle them.
904 if (deferredInstructions.size() > 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -0700905 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups, completePath);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800906
907 }
908 packet = builder.build();
909
910 log.debug("Output Packet {}", packet);
911 return trace;
912 }
913
914 private List<FlowEntry> handleFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
915 List<FlowEntry> outputFlows, Builder builder, List<PortNumber> outputPorts) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100916 //TODO optimization
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100917 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
918 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
919 .allInstructions().stream().filter(instruction -> instruction.type()
920 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100921
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100922 if (outputFlowEntries.size() > 1) {
923 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
924 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100925 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100926
927 if (outputFlowEntries.size() == 1) {
928
929 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
930 .allInstructions().stream()
931 .filter(instruction -> {
932 return instruction.type().equals(Instruction.Type.OUTPUT);
933 }).findFirst().get();
934
935 //FIXME using GroupsInDevice for output even if flows.
936 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
937
938 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800939 return outputFlowEntries;
940 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100941
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800942 private boolean multipleRoutes(StaticPacketTrace trace) {
943 boolean multipleRoutes = false;
944 IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
945 IpAddress ip = null;
946 if (ipCriterion != null) {
947 ip = ipCriterion.ip().address();
948 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
949 ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100950 }
Andrea Campanellae93f6612018-04-04 13:10:45 +0200951 if (ip != null) {
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800952 Optional<ResolvedRoute> optionalRoute = routeNib.longestPrefixLookup(ip);
Andrea Campanellae93f6612018-04-04 13:10:45 +0200953 if (optionalRoute.isPresent()) {
954 ResolvedRoute route = optionalRoute.get();
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800955 multipleRoutes = routeNib.getAllResolvedRoutes(route.prefix()).size() > 1;
Andrea Campanellae93f6612018-04-04 13:10:45 +0200956 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100957 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800958 return multipleRoutes;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100959 }
960
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100961 /**
962 * Handles the specific case where the Input is the controller.
963 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
964 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
965 * the flood to all active physical ports of the device.
966 *
967 * @param trace the trace
968 * @param in the controller port
969 * @return the augmented trace.
970 */
971 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
972 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
973 .getCriterion(Criterion.Type.ETH_TYPE);
974 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
975 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
976 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
977 //get the active ports
Seyeon Jeong8d3cad22020-02-28 01:17:34 -0800978 List<Port> enabledPorts = deviceNib.getPorts(in.deviceId()).stream()
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100979 .filter(Port::isEnabled)
980 .collect(Collectors.toList());
981 //build an output from each one
982 enabledPorts.forEach(port -> {
983 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
984 ImmutableList.of(), trace.getInitialPacket());
985 trace.addGroupOutputPath(in.deviceId(), output);
986 });
987 return trace;
988 }
989 return null;
990 }
991
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100992 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
993 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
994 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
995 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
996 .vlanId().equals(VlanId.NONE);
997 }
998
999 /**
1000 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
1001 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
1002 * second to transition to following table
1003 *
1004 * @param packet the incoming packet
1005 * @param in the input connect point
1006 * @param packetVlanIdCriterion the vlan criterion from the packet
1007 * @param entryModVlanIdInstruction the entry vlan instruction
1008 * @return the second flow entry that matched
1009 */
1010 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
1011 VlanIdCriterion packetVlanIdCriterion,
1012 ModVlanIdInstruction entryModVlanIdInstruction) {
1013 FlowEntry secondVlanFlow = null;
1014 //Check the packet has been update from the first rule.
1015 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
1016 //find a rule on the same table that matches the vlan and
1017 // also all the other elements of the flow such as input port
Seyeon Jeong8d3cad22020-02-28 01:17:34 -08001018 secondVlanFlow = Lists.newArrayList(flowNib.getFlowEntriesByState(in.deviceId(),
1019 FlowEntry.FlowEntryState.ADDED)
1020 .iterator()).stream()
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001021 .filter(entry -> {
1022 return entry.table().equals(IndexTableId.of(10));
1023 })
1024 .filter(entry -> {
1025 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
1026 .getCriterion(Criterion.Type.VLAN_VID);
1027 return criterion != null && match(packet, entry)
1028 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
1029 }).findFirst().orElse(null);
1030
1031 }
1032 return secondVlanFlow;
1033 }
1034
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001035
Andrea Campanella01e886e2017-12-15 15:27:31 +01001036 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001037 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
1038 *
1039 * @param packet the incoming packet
1040 * @return the updated packet
1041 */
1042 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
1043 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
1044 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
1045 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
1046
1047 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
1048 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
1049 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001050 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanella01e886e2017-12-15 15:27:31 +01001051 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
1052 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
1053 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001054 //translateInstruction(builder, ethInstruction);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001055 builder.add(ethInstruction);
1056 }
1057 packet = updatePacket(packet, builder.build()).build();
1058 return packet;
1059 }
1060
1061 /**
1062 * Finds the flow entry with the minimun next table Id.
1063 *
1064 * @param deviceId the device to search
1065 * @param currentId the current id. the search will use this as minimum
1066 * @return the flow entry with the minimum table Id after the given one.
1067 */
1068 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
1069
1070 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
Seyeon Jeong8d3cad22020-02-28 01:17:34 -08001071 return Lists.newArrayList(flowNib.getFlowEntriesByState(deviceId, FlowEntry.FlowEntryState.ADDED)
Andrea Campanellaa3369742018-04-13 12:14:48 +02001072 .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<>();
Seyeon Jeong525b68a2020-04-07 12:06:18 -07001122 // sort instructions according to priority (larger Instruction.Type ENUM constant first)
1123 // which enables to treat other actions before the OUTPUT action
1124 //TODO improve the priority scheme according to the OpenFlow ActionSet spec
1125 List<Instruction> instructionsSorted = new ArrayList<>();
1126 instructionsSorted.addAll(instructions);
1127 instructionsSorted.sort((instr1, instr2) -> {
1128 return Integer.compare(instr2.type().ordinal(), instr1.type().ordinal());
1129 });
1130
1131 for (Instruction instruction : instructionsSorted) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001132 log.debug("Considering Instruction {}", instruction);
1133 //if the instruction is not group we need to update the packet or add the output
1134 //to the possible outputs for this packet
1135 if (!instruction.type().equals(Instruction.Type.GROUP)) {
1136 //if the instruction is not group we need to update the packet or add the output
1137 //to the possible outputs for this packet
1138 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001139 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella3f98c212018-02-19 17:03:46 +01001140 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
1141 //clearing the groups because we start from the top.
1142 groupsForDevice.clear();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001143 } else {
1144 builder = translateInstruction(builder, instruction);
1145 }
1146 } else {
1147 //if the instuction is pointing to a group we need to get the group
1148 groupInstructionlist.add(instruction);
1149 }
1150 }
1151 //handle all the internal instructions pointing to a group.
1152 for (Instruction instr : groupInstructionlist) {
1153 GroupInstruction groupInstruction = (GroupInstruction) instr;
Seyeon Jeong8d3cad22020-02-28 01:17:34 -08001154 Group group = Lists.newArrayList(groupNib.getGroups(deviceId)).stream().filter(groupInternal -> {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001155 return groupInternal.id().equals(groupInstruction.groupId());
1156 }).findAny().orElse(null);
1157 if (group == null) {
1158 trace.addResultMessage("Null group for Instruction " + instr);
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001159 trace.setSuccess(false);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001160 break;
1161 }
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001162 if (group.buckets().buckets().size() == 0) {
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001163 trace.addResultMessage("Group " + group.id() + " has no buckets");
Andrea Campanellac8e6a502018-03-12 19:25:44 -07001164 trace.setSuccess(false);
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001165 computePath(completePath, trace, null);
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001166 break;
1167 }
Andrea Campanella3f98c212018-02-19 17:03:46 +01001168
Andrea Campanella01e886e2017-12-15 15:27:31 +01001169 //Cycle in each of the group's buckets and add them to the groups for this Device.
1170 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella3f98c212018-02-19 17:03:46 +01001171
1172 //add the group to the traversed groups
1173 if (!groupsForDevice.contains(group)) {
1174 groupsForDevice.add(group);
1175 }
1176
Andrea Campanella01e886e2017-12-15 15:27:31 +01001177 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellad1cc1a32018-03-21 10:08:55 -07001178 deviceId, builder, outputPorts, in, completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001179 }
1180 }
1181 }
1182
1183 /**
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001184 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1185 * a possible output from this device.
1186 *
1187 * @param trace the trace
1188 * @param in the input connect point
1189 * @param builder the packet builder
1190 * @param outputPorts the list of output ports for this device
1191 * @param outputInstruction the output instruction
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001192 * @param groupsForDevice the groups we output from
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001193 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001194 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001195 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1196 List<Group> groupsForDevice) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +01001197 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella6f2d6742018-02-07 12:00:12 +01001198
Andrea Campanella7b84c072018-03-06 15:21:09 -08001199 outputPorts.add(outputInstruction.port());
1200
1201 GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
1202 if (trace.getGroupOuputs(output.deviceId()) != null
1203 && trace.getGroupOuputs(output.deviceId()).contains(device)) {
1204 return;
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001205 }
Andrea Campanella7b84c072018-03-06 15:21:09 -08001206 trace.addGroupOutputPath(in.deviceId(),
1207 new GroupsInDevice(output, groupsForDevice, builder.build()));
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001208 }
1209
1210 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001211 * Applies all give instructions to the input packet.
1212 *
1213 * @param packet the input packet
1214 * @param instructions the set of instructions
1215 * @return the packet with the applied instructions
1216 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001217 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1218 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001219 packet.criteria().forEach(newSelector::add);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001220 //FIXME optimize
1221 for (Instruction instruction : instructions) {
1222 newSelector = translateInstruction(newSelector, instruction);
1223 }
Andrea Campanella01e886e2017-12-15 15:27:31 +01001224 return newSelector;
1225 }
1226
1227 /**
1228 * Applies an instruction to the packet in the form of a selector.
1229 *
1230 * @param newSelector the packet selector
1231 * @param instruction the instruction to be translated
1232 * @return the new selector with the applied instruction
1233 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001234 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001235 log.debug("Translating instruction {}", instruction);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001236 log.debug("New Selector {}", newSelector.build());
Andrea Campanella01e886e2017-12-15 15:27:31 +01001237 //TODO add as required
1238 Criterion criterion = null;
1239 switch (instruction.type()) {
1240 case L2MODIFICATION:
1241 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1242 switch (l2Instruction.subtype()) {
1243 case VLAN_ID:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001244 ModVlanIdInstruction vlanIdInstruction =
1245 (ModVlanIdInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001246 VlanId id = vlanIdInstruction.vlanId();
1247 criterion = Criteria.matchVlanId(id);
1248 break;
1249 case VLAN_POP:
1250 criterion = Criteria.matchVlanId(VlanId.NONE);
1251 break;
1252 case MPLS_PUSH:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001253 ModMplsHeaderInstruction mplsEthInstruction =
1254 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001255 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1256 break;
1257 case MPLS_POP:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001258 ModMplsHeaderInstruction mplsPopInstruction =
1259 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001260 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001261
1262 //When popping MPLS we remove label and BOS
1263 TrafficSelector temporaryPacket = newSelector.build();
1264 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001265 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001266 temporaryPacket.criteria().stream().filter(c -> {
1267 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1268 !c.type().equals(Criterion.Type.MPLS_BOS);
1269 }).forEach(noMplsSelector::add);
1270 newSelector = noMplsSelector;
1271 }
1272
Andrea Campanella01e886e2017-12-15 15:27:31 +01001273 break;
1274 case MPLS_LABEL:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001275 ModMplsLabelInstruction mplsLabelInstruction =
1276 (ModMplsLabelInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001277 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001278 newSelector.matchMplsBos(true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001279 break;
1280 case ETH_DST:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001281 ModEtherInstruction modEtherDstInstruction =
1282 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001283 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1284 break;
1285 case ETH_SRC:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001286 ModEtherInstruction modEtherSrcInstruction =
1287 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001288 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1289 break;
1290 default:
1291 log.debug("Unsupported L2 Instruction");
1292 break;
1293 }
1294 break;
1295 default:
1296 log.debug("Unsupported Instruction");
1297 break;
1298 }
1299 if (criterion != null) {
1300 log.debug("Adding criterion {}", criterion);
1301 newSelector.add(criterion);
1302 }
1303 return newSelector;
1304 }
1305
1306 /**
1307 * Finds the rule in the device that mathces the input packet and has the highest priority.
1308 *
1309 * @param packet the input packet
1310 * @param in the connect point the packet comes in from
1311 * @param tableId the table to search
1312 * @return the flow entry
1313 */
1314 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1315 //Computing the possible match rules.
1316 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
Seyeon Jeong8d3cad22020-02-28 01:17:34 -08001317 return Lists.newArrayList(flowNib.getFlowEntriesByState(in.deviceId(), FlowEntry.FlowEntryState.ADDED)
Andrea Campanellaa3369742018-04-13 12:14:48 +02001318 .iterator()).stream()
Andrea Campanella01e886e2017-12-15 15:27:31 +01001319 .filter(flowEntry -> {
1320 return flowEntry.table().equals(tableId);
1321 })
1322 .filter(flowEntry -> {
1323 return match(packet, flowEntry);
1324 }).max(comparator).orElse(null);
1325 }
1326
1327 /**
1328 * Matches the packet with the given flow entry.
1329 *
1330 * @param packet the packet to match
1331 * @param flowEntry the flow entry to match the packet against
1332 * @return true if the packet matches the flow.
1333 */
1334 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001335 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1336 Criterion.Type type = criterion.type();
Andrea Campanella4ee4af12018-01-31 12:20:48 +01001337 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanella01e886e2017-12-15 15:27:31 +01001338 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1339 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella04924b92018-01-17 16:34:51 +01001340 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001341 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella04924b92018-01-17 16:34:51 +01001342 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1343 return matchMac(packet, (EthCriterion) criterion, false);
1344 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1345 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001346 } else {
1347 return packet.criteria().contains(criterion);
1348 }
1349 });
Simon Hunt6fefd852017-11-13 17:09:43 -08001350 }
Andrea Campanella04924b92018-01-17 16:34:51 +01001351
1352 /**
1353 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1354 *
1355 * @param packet the incoming packet
1356 * @param criterion the criterion to match
1357 * @return true if match
1358 */
1359 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1360 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1361 //if the packet does not have an IPv4 or IPv6 criterion we return true
1362 if (matchCriterion == null) {
1363 return false;
1364 }
1365 try {
1366 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1367 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1368 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1369 } catch (UnknownHostException e) {
1370 return false;
1371 }
1372 }
1373
1374 /**
1375 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1376 *
1377 * @param packet the incoming packet
1378 * @param hitCriterion the criterion to match
1379 * @param dst true if we are checking DST MAC
1380 * @return true if match
1381 */
1382 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1383 //Packet can have only one EthCriterion
1384 EthCriterion matchCriterion;
1385 if (dst) {
1386 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1387 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1388 criterion1.type().equals(Criterion.Type.ETH_DST);
1389 }).findFirst().orElse(null);
1390 } else {
1391 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1392 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1393 criterion1.type().equals(Criterion.Type.ETH_SRC);
1394 }).findFirst().orElse(null);
1395 }
1396 //if the packet does not have an ETH criterion we return true
1397 if (matchCriterion == null) {
1398 return true;
1399 }
1400 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1401 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1402 }
Simon Hunt6fefd852017-11-13 17:09:43 -08001403}