blob: 526367a238057f4e0dbdc6fbd1b1aaa814032de3 [file] [log] [blame]
Simon Hunt6fefd852017-11-13 17:09:43 -08001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.t3.impl;
18
Andrea Campanella37d10622018-01-18 17:11:42 +010019import com.google.common.base.Preconditions;
Andrea Campanella01e886e2017-12-15 15:27:31 +010020import com.google.common.collect.ImmutableList;
Andrea Campanella79cb17d2018-02-27 18:03:17 +010021import com.google.common.collect.ImmutableSet;
Andrea Campanella01e886e2017-12-15 15:27:31 +010022import com.google.common.collect.Lists;
Andrea Campanella7e2200e2018-02-27 14:50:45 +010023import com.google.common.collect.Sets;
Andrea Campanella6be5c872018-02-21 14:28:20 +010024import org.apache.commons.lang3.tuple.Pair;
Simon Hunt6fefd852017-11-13 17:09:43 -080025import org.apache.felix.scr.annotations.Component;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010029import org.onlab.packet.IpAddress;
Andrea Campanella01e886e2017-12-15 15:27:31 +010030import org.onlab.packet.VlanId;
Andrea Campanella7c8e7912018-01-23 12:46:04 +010031import org.onosproject.cluster.NodeId;
32import org.onosproject.mastership.MastershipService;
Simon Hunt6fefd852017-11-13 17:09:43 -080033import org.onosproject.net.ConnectPoint;
Andrea Campanella01e886e2017-12-15 15:27:31 +010034import org.onosproject.net.DeviceId;
35import org.onosproject.net.Host;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010036import org.onosproject.net.HostId;
Andrea Campanella01e886e2017-12-15 15:27:31 +010037import org.onosproject.net.Link;
Andrea Campanella6f2d6742018-02-07 12:00:12 +010038import org.onosproject.net.Port;
Andrea Campanella01e886e2017-12-15 15:27:31 +010039import org.onosproject.net.PortNumber;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010040import org.onosproject.net.config.ConfigException;
41import org.onosproject.net.config.NetworkConfigService;
42import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella37d10622018-01-18 17:11:42 +010043import org.onosproject.net.device.DeviceService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010044import org.onosproject.net.driver.DriverService;
Andrea Campanella04924b92018-01-17 16:34:51 +010045import org.onosproject.net.edge.EdgePortService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010046import org.onosproject.net.flow.DefaultTrafficSelector;
47import org.onosproject.net.flow.FlowEntry;
48import org.onosproject.net.flow.FlowRule;
Simon Hunt6fefd852017-11-13 17:09:43 -080049import org.onosproject.net.flow.FlowRuleService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010050import org.onosproject.net.flow.IndexTableId;
51import org.onosproject.net.flow.TableId;
Simon Hunt6fefd852017-11-13 17:09:43 -080052import org.onosproject.net.flow.TrafficSelector;
Andrea Campanella01e886e2017-12-15 15:27:31 +010053import org.onosproject.net.flow.criteria.Criteria;
54import org.onosproject.net.flow.criteria.Criterion;
55import org.onosproject.net.flow.criteria.EthCriterion;
56import org.onosproject.net.flow.criteria.EthTypeCriterion;
57import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanella58b3b522018-02-06 15:46:52 +010058import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanella01e886e2017-12-15 15:27:31 +010059import org.onosproject.net.flow.instructions.Instruction;
60import org.onosproject.net.flow.instructions.Instructions;
61import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
62import org.onosproject.net.flow.instructions.L2ModificationInstruction;
63import org.onosproject.net.group.Group;
64import org.onosproject.net.group.GroupBucket;
Simon Hunt6fefd852017-11-13 17:09:43 -080065import org.onosproject.net.group.GroupService;
Andrea Campanella01e886e2017-12-15 15:27:31 +010066import org.onosproject.net.host.HostService;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010067import org.onosproject.net.host.InterfaceIpAddress;
68import org.onosproject.net.intf.Interface;
Andrea Campanella01e886e2017-12-15 15:27:31 +010069import org.onosproject.net.link.LinkService;
Andrea Campanellacc2424a2018-03-07 14:27:54 -080070import org.onosproject.routeservice.ResolvedRoute;
71import org.onosproject.routeservice.RouteService;
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +010072import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanella01e886e2017-12-15 15:27:31 +010073import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt6fefd852017-11-13 17:09:43 -080074import org.onosproject.t3.api.StaticPacketTrace;
75import org.onosproject.t3.api.TroubleshootService;
76import 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;
88
89import static org.onlab.packet.EthType.EtherType;
Andrea Campanella58b3b522018-02-06 15:46:52 +010090import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanella01e886e2017-12-15 15:27:31 +010091import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella97f9d4c2018-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 Campanella04924b92018-01-17 16:34:51 +010096import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt6fefd852017-11-13 17:09:43 -080097import static org.slf4j.LoggerFactory.getLogger;
98
99/**
Andrea Campanella01e886e2017-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 Hunt6fefd852017-11-13 17:09:43 -0800103 */
104@Service
105@Component(immediate = true)
106public class TroubleshootManager implements TroubleshootService {
107
108 private static final Logger log = getLogger(TroubleshootManager.class);
109
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100110 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
111
Simon Hunt6fefd852017-11-13 17:09:43 -0800112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected FlowRuleService flowRuleService;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected GroupService groupService;
117
Andrea Campanella01e886e2017-12-15 15:27:31 +0100118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected LinkService linkService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800120
Andrea Campanella01e886e2017-12-15 15:27:31 +0100121 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
122 protected HostService hostService;
123
124 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
125 protected DriverService driverService;
Simon Hunt6fefd852017-11-13 17:09:43 -0800126
Andrea Campanella37d10622018-01-18 17:11:42 +0100127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
128 protected DeviceService deviceService;
129
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100130 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
131 protected MastershipService mastershipService;
132
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100133 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
134 protected NetworkConfigService networkConfigService;
135
Andrea Campanella04924b92018-01-17 16:34:51 +0100136 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
137 protected EdgePortService edgePortService;
138
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800139 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
140 protected RouteService routeService;
141
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100142 @Override
Andrea Campanella6be5c872018-02-21 14:28:20 +0100143 public List<StaticPacketTrace> pingAll(EtherType type) {
144 ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
145 hostService.getHosts().forEach(host -> {
146 List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
147 if (ipAddresses.size() > 0) {
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100148 //check if the host has only local IPs of that ETH type
149 boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
Andrea Campanella6be5c872018-02-21 14:28:20 +0100150 hostService.getHosts().forEach(hostToPing -> {
151 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella7e2200e2018-02-27 14:50:45 +0100152 //check if the other host has only local IPs of that ETH type
153 boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
154 boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
155 //Trace is done only if they are both local and under the same location
156 // or not local and if they are not the same host.
157 if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
158 (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
159 && !host.equals(hostToPing)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100160 tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
Andrea Campanella6be5c872018-02-21 14:28:20 +0100161 }
162 });
163 }
164 });
165 return tracesBuilder.build();
166 }
167
168 @Override
Andrea Campanella41de8062018-02-28 16:43:16 +0100169 public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
170 return new PingAllGenerator(type, hostService, this);
171 }
172
173 @Override
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100174 public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100175 Host source = hostService.getHost(sourceHost);
176 Host destination = hostService.getHost(destinationHost);
177
Andrea Campanella6be5c872018-02-21 14:28:20 +0100178 //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
179 StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100180
181 if (source == null) {
182 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100183 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100184 }
185
186 if (destination == null) {
187 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100188 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100189 }
190
191 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100192 .matchEthType(etherType.ethType().toShort())
193 .matchEthDst(source.mac())
194 .matchVlanId(source.vlan());
195
196
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100197 try {
198 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
199 // we are under same leaf so it's L2 Unicast.
200 if (areBridged(source, destination)) {
201 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100202 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
203 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800204 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100205 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
206 trace.addEndpointHosts(Pair.of(source, destination));
207 traces.add(trace);
208 });
209 return traces.build();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100210 }
211
212 //handle the IPs for src and dst in case of L3
213 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
214
215 //Match on the source IP
216 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100217 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100218 }
219
220 //Match on destination IP
221 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100222 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100223 }
224
225 } else {
226 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
227 "please use packet based");
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100228 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100229 }
230
231 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
232 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
233 .deviceId(), SegmentRoutingDeviceConfig.class);
234 if (segmentRoutingConfig != null) {
235 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
236 } else {
237 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
238 " router MAC from segment routing config can't perform L3 tracing.");
239 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100240 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
241 source.locations().forEach(hostLocation -> {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800242 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100243 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
244 trace.addEndpointHosts(Pair.of(source, destination));
245 traces.add(trace);
246 });
247 return traces.build();
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100248
249 } catch (ConfigException e) {
250 failTrace.addResultMessage("Can't get config " + e.getMessage());
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100251 return ImmutableSet.of(failTrace);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100252 }
253 }
254
255 /**
256 * Matches src and dst IPs based on host information.
257 *
258 * @param host the host
259 * @param failTrace the trace to use in case of failure
260 * @param selectorBuilder the packet we are building to trace
261 * @param etherType the traffic type
262 * @param src is this src host or dst host
263 * @return true if properly matched
264 */
265 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
266 EtherType etherType, boolean src) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100267 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100268
269 if (ips.size() > 0) {
Andrea Campanella465c7be2018-02-21 14:43:21 +0100270 if (etherType.equals(EtherType.IPV4)) {
271 if (src) {
272 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
273 } else {
274 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
275 }
276 } else if (etherType.equals(EtherType.IPV6)) {
277 if (src) {
278 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
279 } else {
280 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
281 }
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100282 }
283 } else {
284 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
285 return false;
286 }
287 return true;
288 }
289
Andrea Campanella41de8062018-02-28 16:43:16 +0100290 List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
Andrea Campanella6be5c872018-02-21 14:28:20 +0100291 return host.ipAddresses().stream().filter(ipAddress -> {
292 boolean correctIp = false;
293 if (etherType.equals(EtherType.IPV4)) {
294 correctIp = ipAddress.isIp4();
295 } else if (etherType.equals(EtherType.IPV6)) {
296 correctIp = ipAddress.isIp6();
297 }
298 if (checklocal) {
299 correctIp = correctIp && !ipAddress.isLinkLocal();
300 }
301 return correctIp;
302 }).collect(Collectors.toList());
303 }
304
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100305 /**
306 * Checks that two hosts are bridged (L2Unicast).
307 *
308 * @param source the source host
309 * @param destination the destination host
310 * @return true if bridged.
311 * @throws ConfigException if config can't be properly retrieved
312 */
313 private boolean areBridged(Host source, Host destination) throws ConfigException {
314
Andrea Campanella7b84c072018-03-06 15:21:09 -0800315 //If the locations is not the same we don't even check vlan or subnets
316 if (Collections.disjoint(source.locations(), destination.locations())) {
317 return false;
318 }
319
320 if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
321 && !source.vlan().equals(destination.vlan())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100322 return false;
323 }
324
325 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
326 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
327 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
328
329 //following can be optimized but for clarity is left as is
330 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
331 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
332
Andrea Campanella7b84c072018-03-06 15:21:09 -0800333 if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
334 return intfH1.vlanUntagged().equals(destination.vlan()) ||
335 intfH1.vlanNative().equals(destination.vlan());
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100336 }
337
Andrea Campanella7b84c072018-03-06 15:21:09 -0800338 if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
339 return intfH2.vlanUntagged().equals(source.vlan()) ||
340 intfH2.vlanNative().equals(source.vlan());
341 }
342
343 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
Andrea Campanellaaf34b7c2018-02-08 17:10:11 +0100344 return false;
345 }
346
347 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
348 return false;
349 }
350
351 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
352 intersection.retainAll(intfH2.ipAddressesList());
353 if (intersection.size() == 0) {
354 return false;
355 }
356 }
357 return true;
358 }
359
Simon Hunt6fefd852017-11-13 17:09:43 -0800360 @Override
361 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100362 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100363 //device must exist in ONOS
364 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
365 "Device " + in.deviceId() + " must exist in ONOS");
366
Andrea Campanella01e886e2017-12-15 15:27:31 +0100367 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100368 boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100369 //FIXME this can be done recursively
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100370 trace = traceInDevice(trace, packet, in, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100371 //Building output connect Points
372 List<ConnectPoint> path = new ArrayList<>();
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100373 trace = getTrace(path, in, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100374 return trace;
375 }
376
377 /**
378 * Computes a trace for a give packet that start in the network at the given connect point.
379 *
380 * @param completePath the path traversed by the packet
381 * @param in the input connect point
382 * @param trace the trace to build
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100383 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanella01e886e2017-12-15 15:27:31 +0100384 * @return the build trace for that packet.
385 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100386 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
387 boolean isDualHomed) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100388
Andrea Campanellae04aac92018-01-31 14:59:03 +0100389 log.debug("------------------------------------------------------------");
390
Andrea Campanella01e886e2017-12-15 15:27:31 +0100391 //if the trace already contains the input connect point there is a loop
392 if (pathContainsDevice(completePath, in.deviceId())) {
393 trace.addResultMessage("Loop encountered in device " + in.deviceId());
Andrea Campanellaece11772018-03-09 14:52:10 -0800394 completePath.add(in);
395 trace.addCompletePath(completePath);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100396 return trace;
397 }
398
399 //let's add the input connect point
400 completePath.add(in);
401
402 //If the trace has no outputs for the given input we stop here
403 if (trace.getGroupOuputs(in.deviceId()) == null) {
404 computePath(completePath, trace, null);
405 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
406 return trace;
407 }
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +0100408
Andrea Campanella01e886e2017-12-15 15:27:31 +0100409 //If the trace has ouputs we analyze them all
410 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100411
412 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellae04aac92018-01-31 14:59:03 +0100413 log.debug("Connect point in {}", in);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100414 log.debug("Output path {}", cp);
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800415 log.debug("{}", outputPath.getFinalPacket());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100416
Andrea Campanella01e886e2017-12-15 15:27:31 +0100417 //Hosts for the the given output
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100418 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100419 //Hosts queried from the original ip or mac
420 Set<Host> hosts = getHosts(trace);
421
Andrea Campanella7b84c072018-03-06 15:21:09 -0800422 if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
423 outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
424 && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
425 .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
426 .vlanId())) {
427 trace.addResultMessage("Connect point out " + cp + " is same as initial input " +
428 trace.getInitialConnectPoint());
429 break;
430 }
431
Andrea Campanella01e886e2017-12-15 15:27:31 +0100432 //If the two host collections contain the same item it means we reached the proper output
433 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100434 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100435 if (computePath(completePath, trace, outputPath.getOutput())) {
436 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella41de8062018-02-28 16:43:16 +0100437 trace.setSuccess(true);
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100438 }
Andrea Campanella01e886e2017-12-15 15:27:31 +0100439 break;
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100440 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100441
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100442 //Getting the master when the packet gets sent as packet in
443 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100444 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100445 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella58b3b522018-02-06 15:46:52 +0100446 handleVlanToController(outputPath, trace);
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100447
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100448 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100449
450 //TODO this can be optimized if we use a Tree structure for paths.
451 //if we already have outputs let's check if the one we are considering starts from one of the devices
452 // in any of the ones we have.
453 if (trace.getCompletePaths().size() > 0) {
454 ConnectPoint inputForOutput = null;
455 List<ConnectPoint> previousPath = new ArrayList<>();
456 for (List<ConnectPoint> path : trace.getCompletePaths()) {
457 for (ConnectPoint connect : path) {
458 //if the path already contains the input for the output we've found we use it
459 if (connect.equals(in)) {
460 inputForOutput = connect;
461 previousPath = path;
462 break;
463 }
464 }
465 }
Andrea Campanellaece11772018-03-09 14:52:10 -0800466
Andrea Campanellae04aac92018-01-31 14:59:03 +0100467 //we use the pre-existing path up to the point we fork to a new output
468 if (inputForOutput != null && completePath.contains(inputForOutput)) {
469 List<ConnectPoint> temp = new ArrayList<>(previousPath);
Andrea Campanellaece11772018-03-09 14:52:10 -0800470 temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
471 if (completePath.containsAll(temp)) {
472 completePath = temp;
473 }
Andrea Campanellae04aac92018-01-31 14:59:03 +0100474 }
475 }
476
Andrea Campanella01e886e2017-12-15 15:27:31 +0100477 //let's add the ouput for the input
478 completePath.add(cp);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100479 //let's compute the links for the given output
480 Set<Link> links = linkService.getEgressLinks(cp);
481 log.debug("Egress Links {}", links);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100482 //For each link we trace the corresponding device
483 for (Link link : links) {
484 ConnectPoint dst = link.dst();
485 //change in-port to the dst link in port
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100486 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100487 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
488 updatedPacket.add(Criteria.matchInPort(dst.port()));
489 log.debug("DST Connect Point {}", dst);
490 //build the elements for that device
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100491 traceInDevice(trace, updatedPacket.build(), dst, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100492 //continue the trace along the path
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100493 getTrace(completePath, dst, trace, isDualHomed);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100494 }
Andrea Campanella04924b92018-01-17 16:34:51 +0100495 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
496 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
497 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
498 .mac().isMulticast()) {
499 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
500 " which is enabled and is edge port");
Andrea Campanella41de8062018-02-28 16:43:16 +0100501 trace.setSuccess(true);
Andrea Campanella04924b92018-01-17 16:34:51 +0100502 computePath(completePath, trace, outputPath.getOutput());
503 completePath.clear();
504 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
505 return trace;
506 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100507 } else if (deviceService.getPort(cp).isEnabled()) {
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100508 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
509 .getCriterion(Criterion.Type.ETH_TYPE);
510 //We treat as correct output only if it's not LLDP or BDDP
511 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
Andrea Campanella62841d42018-02-27 12:42:28 +0100512 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800513 if (computePath(completePath, trace, outputPath.getOutput())) {
514 if (hostsList.isEmpty()) {
515 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
516 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
517 cp + " with no hosts connected ");
518 } else {
519 IpAddress ipAddress = null;
520 if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
521 ipAddress = ((IPCriterion) trace.getInitialPacket()
522 .getCriterion(Criterion.Type.IPV4_DST)).ip().address();
523 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
524 ipAddress = ((IPCriterion) trace.getInitialPacket()
525 .getCriterion(Criterion.Type.IPV6_DST)).ip().address();
526 }
527 if (ipAddress != null) {
528 IpAddress finalIpAddress = ipAddress;
529 if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
530 hostService.getHostsByIp(finalIpAddress).isEmpty()) {
531 trace.addResultMessage("Packet is " +
532 ((EthTypeCriterion) outputPath.getFinalPacket()
533 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
534 cp + " with hosts " + hostsList);
535 } else {
536 trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
537 ipAddress);
538 }
539 } else {
Andrea Campanella7b84c072018-03-06 15:21:09 -0800540 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
541 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
542 cp + " with hosts " + hostsList);
Andrea Campanella7b84c072018-03-06 15:21:09 -0800543 }
Andrea Campanella7b84c072018-03-06 15:21:09 -0800544 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800545 trace.setSuccess(true);
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100546 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100547 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100548
549 } else {
550 //No links means that the packet gets dropped.
551 log.warn("No links out of {}", cp);
552 computePath(completePath, trace, cp);
553 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanella01e886e2017-12-15 15:27:31 +0100554 }
555 }
556 return trace;
557 }
558
Andrea Campanella04924b92018-01-17 16:34:51 +0100559
Andrea Campanella01e886e2017-12-15 15:27:31 +0100560 /**
Andrea Campanella58b3b522018-02-06 15:46:52 +0100561 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
562 * If ONOS applied a vlan we remove it.
563 *
564 * @param outputPath the output
565 * @param trace the trace we are building
566 */
Andrea Campanella04924b92018-01-17 16:34:51 +0100567
Andrea Campanella58b3b522018-02-06 15:46:52 +0100568 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
569
570 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
571 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
572
573 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
574
575 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
576 //removing the final vlanId
577 finalCriteria.remove(finalVid);
578 Builder packetUpdated = DefaultTrafficSelector.builder();
579 finalCriteria.forEach(packetUpdated::add);
580 //Initial was none so we set it to that
581 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
582 //Update final packet
583 outputPath.setFinalPacket(packetUpdated.build());
584 }
585 }
586
587 /**
Andrea Campanella04924b92018-01-17 16:34:51 +0100588 * Checks if the device has other outputs than the given connect point.
589 *
590 * @param inDeviceId the device
591 * @param trace the trace we are building
592 * @param cp an output connect point
593 * @return true if the device has other outputs.
594 */
595 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
596 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
597 return !groupsInDevice.getOutput().equals(cp);
598 }).count() > 0;
599 }
600
601 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100602 * Checks if the path contains the device.
603 *
604 * @param completePath the path
605 * @param deviceId the device to check
606 * @return true if the path contains the device
607 */
608 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
609 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
610 for (ConnectPoint cp : completePath) {
611 if (cp.deviceId().equals(deviceId)) {
612 return true;
613 }
614 }
615 return false;
616 }
617
618 /**
619 * Gets the hosts for the given initial packet.
620 *
621 * @param trace the trace we are building
622 * @return set of the hosts we are trying to reach
623 */
624 private Set<Host> getHosts(StaticPacketTrace trace) {
625 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
626 .getCriterion(Criterion.Type.IPV4_DST));
627 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
628 .getCriterion(Criterion.Type.IPV6_DST));
629 Set<Host> hosts = new HashSet<>();
630 if (ipv4Criterion != null) {
631 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
632 }
633 if (ipv6Criterion != null) {
634 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
635 }
636 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
637 .getCriterion(Criterion.Type.ETH_DST));
638 if (ethCriterion != null) {
639 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
640 }
641 return hosts;
642 }
643
644 /**
645 * Computes the list of traversed connect points.
646 *
647 * @param completePath the list of devices
648 * @param trace the trace we are building
649 * @param output the final output connect point
650 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100651 private boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
Andrea Campanella01e886e2017-12-15 15:27:31 +0100652 List<ConnectPoint> traverseList = new ArrayList<>();
653 if (!completePath.contains(trace.getInitialConnectPoint())) {
654 traverseList.add(trace.getInitialConnectPoint());
655 }
656 traverseList.addAll(completePath);
657 if (output != null && !completePath.contains(output)) {
658 traverseList.add(output);
659 }
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100660 if (!trace.getCompletePaths().contains(traverseList)) {
Andrea Campanellaece11772018-03-09 14:52:10 -0800661 trace.addCompletePath(ImmutableList.copyOf(traverseList));
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100662 return true;
663 }
664 return false;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100665 }
666
667 /**
668 * Traces the packet inside a device starting from an input connect point.
669 *
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100670 * @param trace the trace we are building
671 * @param packet the packet we are tracing
672 * @param in the input connect point.
673 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanella01e886e2017-12-15 15:27:31 +0100674 * @return updated trace
675 */
Andrea Campanella79cb17d2018-02-27 18:03:17 +0100676 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
677 boolean isDualHomed) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100678
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800679 boolean multipleRoutes = false;
680 if (trace.getGroupOuputs(in.deviceId()) != null) {
681 multipleRoutes = multipleRoutes(trace);
682 }
683 if (trace.getGroupOuputs(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
Andrea Campanellae04aac92018-01-31 14:59:03 +0100684 log.debug("Trace already contains device and given outputs");
685 return trace;
686 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800687
Andrea Campanella01e886e2017-12-15 15:27:31 +0100688 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella37d10622018-01-18 17:11:42 +0100689
690 //if device is not available exit here.
691 if (!deviceService.isAvailable(in.deviceId())) {
692 trace.addResultMessage("Device is offline " + in.deviceId());
693 return trace;
694 }
695
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100696 //handle when the input is the controller
697 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
698 // a packet in from the controller will not actually traverse the pipeline and have no such notion
699 // as the input port.
700 if (in.port().equals(PortNumber.CONTROLLER)) {
701 StaticPacketTrace outputTrace = inputFromController(trace, in);
702 if (outputTrace != null) {
703 return trace;
704 }
705 }
706
Andrea Campanella01e886e2017-12-15 15:27:31 +0100707 List<FlowEntry> flows = new ArrayList<>();
708 List<FlowEntry> outputFlows = new ArrayList<>();
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100709 List<Instruction> deferredInstructions = new ArrayList<>();
710
Andrea Campanella01e886e2017-12-15 15:27:31 +0100711 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
712 if (nextTableIdEntry == null) {
713 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
714 return trace;
715 }
716 TableId tableId = nextTableIdEntry.table();
717 FlowEntry flowEntry;
718 boolean output = false;
719 while (!output) {
720 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
721 //get the rule that matches the incoming packet
722 flowEntry = matchHighestPriority(packet, in, tableId);
723 log.debug("Found Flow Entry {}", flowEntry);
724
725 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
726 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
727
728 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
729 if (flowEntry == null && isOfdpaHardware) {
730 log.debug("Ofdpa Hw setup, no flow rule means table miss");
731
Andrea Campanella01e886e2017-12-15 15:27:31 +0100732 if (((IndexTableId) tableId).id() == 27) {
733 //Apparently a miss but Table 27 on OFDPA is a fixed table
734 packet = handleOfdpa27FixedTable(trace, packet);
735 }
736
737 //Finding next table to go In case of miss
738 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
739 log.debug("Next table id entry {}", nextTableIdEntry);
740
741 //FIXME find better solution that enable granularity greater than 0 or all rules
742 //(another possibility is max tableId)
743 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella7382c7f2018-02-05 19:39:25 +0100744 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanella01e886e2017-12-15 15:27:31 +0100745 return trace;
746
747 } else if (nextTableIdEntry == null) {
748 //Means that no more flow rules are present
749 output = true;
750
751 } else if (((IndexTableId) tableId).id() == 20) {
752 //if the table is 20 OFDPA skips to table 50
753 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
754 tableId = IndexTableId.of(50);
755
756 } else {
757 tableId = nextTableIdEntry.table();
758 }
759
Andrea Campanella01e886e2017-12-15 15:27:31 +0100760 } else if (flowEntry == null) {
761 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
762 in.deviceId() + ". Dropping");
763 return trace;
764 } else {
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100765
Andrea Campanella01e886e2017-12-15 15:27:31 +0100766 //IF the table has a transition
767 if (flowEntry.treatment().tableTransition() != null) {
768 //update the next table we transitions to
769 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
770 log.debug("Flow Entry has transition to table Id {}", tableId);
771 flows.add(flowEntry);
772 } else {
773 //table has no transition so it means that it's an output rule if on the last table
774 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
775 flows.add(flowEntry);
776 outputFlows.add(flowEntry);
777 output = true;
778 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100779 //update the packet according to the immediate actions of this flow rule.
780 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
781
782 //save the deferred rules for later
783 deferredInstructions.addAll(flowEntry.treatment().deferred());
784
785 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
786 if (flowEntry.treatment().clearedDeferred()) {
787 deferredInstructions.clear();
788 }
789
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100790 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
791 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
792
793 //Let's get the packet vlanId instruction
794 VlanIdCriterion packetVlanIdCriterion =
795 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
796
797 //Let's get the flow entry vlan mod instructions
798 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
799 .immediate().stream()
800 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
801 .findFirst().orElse(null);
802
803 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
804 // is a flow rule that matches on same criteria and with updated vlanId
805 if (entryModVlanIdInstruction != null) {
806
807 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
808 packetVlanIdCriterion, entryModVlanIdInstruction);
809
810 //We found the flow that we expected
811 if (secondVlanFlow != null) {
812 flows.add(secondVlanFlow);
813 } else {
814 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
815 return trace;
816 }
817 }
818
819 }
820
Andrea Campanella01e886e2017-12-15 15:27:31 +0100821 }
822 }
823
824 //Creating a modifiable builder for the output packet
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100825 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +0100826 packet.criteria().forEach(builder::add);
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100827
Andrea Campanella01e886e2017-12-15 15:27:31 +0100828 //Adding all the flows to the trace
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100829 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanella01e886e2017-12-15 15:27:31 +0100830
Andrea Campanella01e886e2017-12-15 15:27:31 +0100831 List<PortNumber> outputPorts = new ArrayList<>();
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800832 List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100833
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800834
835 log.debug("Handling Groups");
836 //Analyze Groups
837 List<Group> groups = new ArrayList<>();
838
839 Collection<FlowEntry> nonOutputFlows = flows;
840 nonOutputFlows.removeAll(outputFlowEntries);
841
842 //Handling groups pointed at by immediate instructions
843 for (FlowEntry entry : flows) {
844 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
845 entry.deviceId(), builder, outputPorts, in);
846 }
847
848 //If we have deferred instructions at this point we handle them.
849 if (deferredInstructions.size() > 0) {
850 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
851
852 }
853 packet = builder.build();
854
855 log.debug("Output Packet {}", packet);
856 return trace;
857 }
858
859 private List<FlowEntry> handleFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
860 List<FlowEntry> outputFlows, Builder builder, List<PortNumber> outputPorts) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100861 //TODO optimization
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100862 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
863 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
864 .allInstructions().stream().filter(instruction -> instruction.type()
865 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanella01e886e2017-12-15 15:27:31 +0100866
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100867 if (outputFlowEntries.size() > 1) {
868 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
869 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100870 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100871
872 if (outputFlowEntries.size() == 1) {
873
874 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
875 .allInstructions().stream()
876 .filter(instruction -> {
877 return instruction.type().equals(Instruction.Type.OUTPUT);
878 }).findFirst().get();
879
880 //FIXME using GroupsInDevice for output even if flows.
881 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
882
883 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800884 return outputFlowEntries;
885 }
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100886
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800887 private boolean multipleRoutes(StaticPacketTrace trace) {
888 boolean multipleRoutes = false;
889 IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
890 IpAddress ip = null;
891 if (ipCriterion != null) {
892 ip = ipCriterion.ip().address();
893 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
894 ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
Andrea Campanella7c8e7912018-01-23 12:46:04 +0100895 }
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100896
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800897 Optional<ResolvedRoute> optionalRoute = routeService.longestPrefixLookup(ip);
898 if (ip != null && optionalRoute.isPresent()) {
899 ResolvedRoute route = optionalRoute.get();
900 route.prefix();
901 multipleRoutes = routeService.getAllResolvedRoutes(route.prefix()).size() > 1;
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100902 }
Andrea Campanellacc2424a2018-03-07 14:27:54 -0800903 return multipleRoutes;
Andrea Campanella01e886e2017-12-15 15:27:31 +0100904 }
905
Andrea Campanella6f2d6742018-02-07 12:00:12 +0100906 /**
907 * Handles the specific case where the Input is the controller.
908 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
909 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
910 * the flood to all active physical ports of the device.
911 *
912 * @param trace the trace
913 * @param in the controller port
914 * @return the augmented trace.
915 */
916 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
917 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
918 .getCriterion(Criterion.Type.ETH_TYPE);
919 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
920 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
921 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
922 //get the active ports
923 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
924 .filter(Port::isEnabled)
925 .collect(Collectors.toList());
926 //build an output from each one
927 enabledPorts.forEach(port -> {
928 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
929 ImmutableList.of(), trace.getInitialPacket());
930 trace.addGroupOutputPath(in.deviceId(), output);
931 });
932 return trace;
933 }
934 return null;
935 }
936
Andrea Campanella97f9d4c2018-02-06 18:58:40 +0100937 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
938 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
939 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
940 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
941 .vlanId().equals(VlanId.NONE);
942 }
943
944 /**
945 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
946 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
947 * second to transition to following table
948 *
949 * @param packet the incoming packet
950 * @param in the input connect point
951 * @param packetVlanIdCriterion the vlan criterion from the packet
952 * @param entryModVlanIdInstruction the entry vlan instruction
953 * @return the second flow entry that matched
954 */
955 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
956 VlanIdCriterion packetVlanIdCriterion,
957 ModVlanIdInstruction entryModVlanIdInstruction) {
958 FlowEntry secondVlanFlow = null;
959 //Check the packet has been update from the first rule.
960 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
961 //find a rule on the same table that matches the vlan and
962 // also all the other elements of the flow such as input port
963 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
964 .stream()
965 .filter(entry -> {
966 return entry.table().equals(IndexTableId.of(10));
967 })
968 .filter(entry -> {
969 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
970 .getCriterion(Criterion.Type.VLAN_VID);
971 return criterion != null && match(packet, entry)
972 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
973 }).findFirst().orElse(null);
974
975 }
976 return secondVlanFlow;
977 }
978
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +0100979
Andrea Campanella01e886e2017-12-15 15:27:31 +0100980 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +0100981 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
982 *
983 * @param packet the incoming packet
984 * @return the updated packet
985 */
986 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
987 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
988 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
989 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
990
991 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
992 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
993 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100994 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanella01e886e2017-12-15 15:27:31 +0100995 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
996 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
997 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella09ca07a2018-01-25 16:44:04 +0100998 //translateInstruction(builder, ethInstruction);
Andrea Campanella01e886e2017-12-15 15:27:31 +0100999 builder.add(ethInstruction);
1000 }
1001 packet = updatePacket(packet, builder.build()).build();
1002 return packet;
1003 }
1004
1005 /**
1006 * Finds the flow entry with the minimun next table Id.
1007 *
1008 * @param deviceId the device to search
1009 * @param currentId the current id. the search will use this as minimum
1010 * @return the flow entry with the minimum table Id after the given one.
1011 */
1012 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
1013
1014 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
1015
1016 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
1017 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
1018 }
1019
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001020 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
1021 ConnectPoint in, List<Instruction> deferredInstructions,
1022 List<PortNumber> outputPorts, List<Group> groups) {
1023
1024 //Update the packet with the deferred instructions
1025 Builder builder = updatePacket(packet, deferredInstructions);
1026
1027 //Gather any output instructions from the deferred instruction
1028 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
1029 return instruction.type().equals(Instruction.Type.OUTPUT);
1030 }).collect(Collectors.toList());
1031
1032 //We are considering deferred instructions from flows, there can only be one output.
1033 if (outputFlowInstruction.size() > 1) {
1034 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
1035 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
1036 }
1037 //If there is one output let's go through that
1038 if (outputFlowInstruction.size() == 1) {
1039 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
1040 ImmutableList.of());
1041 }
1042 //If there is no output let's see if there any deferred instruction point to groups.
1043 if (outputFlowInstruction.size() == 0) {
1044 getGroupsFromInstructions(trace, groups, deferredInstructions,
1045 in.deviceId(), builder, outputPorts, in);
1046 }
1047 return builder;
1048 }
1049
Andrea Campanella01e886e2017-12-15 15:27:31 +01001050 /**
1051 * Gets group information from instructions.
1052 *
1053 * @param trace the trace we are building
1054 * @param groupsForDevice the set of groups for this device
1055 * @param instructions the set of instructions we are searching for groups.
1056 * @param deviceId the device we are considering
1057 * @param builder the builder of the input packet
1058 * @param outputPorts the output ports for that packet
1059 */
1060 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
1061 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001062 Builder builder, List<PortNumber> outputPorts,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001063 ConnectPoint in) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001064 List<Instruction> groupInstructionlist = new ArrayList<>();
1065 for (Instruction instruction : instructions) {
1066 log.debug("Considering Instruction {}", instruction);
1067 //if the instruction is not group we need to update the packet or add the output
1068 //to the possible outputs for this packet
1069 if (!instruction.type().equals(Instruction.Type.GROUP)) {
1070 //if the instruction is not group we need to update the packet or add the output
1071 //to the possible outputs for this packet
1072 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001073 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella3f98c212018-02-19 17:03:46 +01001074 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
1075 //clearing the groups because we start from the top.
1076 groupsForDevice.clear();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001077 } else {
1078 builder = translateInstruction(builder, instruction);
1079 }
1080 } else {
1081 //if the instuction is pointing to a group we need to get the group
1082 groupInstructionlist.add(instruction);
1083 }
1084 }
1085 //handle all the internal instructions pointing to a group.
1086 for (Instruction instr : groupInstructionlist) {
1087 GroupInstruction groupInstruction = (GroupInstruction) instr;
1088 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
1089 return groupInternal.id().equals(groupInstruction.groupId());
1090 }).findAny().orElse(null);
1091 if (group == null) {
1092 trace.addResultMessage("Null group for Instruction " + instr);
1093 break;
1094 }
Andrea Campanella7cb4fd82018-02-27 12:36:00 +01001095 if (group.buckets().buckets().size() == 0) {
1096 trace.addResultMessage("Group " + group.id() + "has no buckets");
1097 break;
1098 }
Andrea Campanella3f98c212018-02-19 17:03:46 +01001099
Andrea Campanella01e886e2017-12-15 15:27:31 +01001100 //Cycle in each of the group's buckets and add them to the groups for this Device.
1101 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella3f98c212018-02-19 17:03:46 +01001102
1103 //add the group to the traversed groups
1104 if (!groupsForDevice.contains(group)) {
1105 groupsForDevice.add(group);
1106 }
1107
Andrea Campanella01e886e2017-12-15 15:27:31 +01001108 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001109 deviceId, builder, outputPorts, in);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001110 }
1111 }
1112 }
1113
1114 /**
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001115 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1116 * a possible output from this device.
1117 *
1118 * @param trace the trace
1119 * @param in the input connect point
1120 * @param builder the packet builder
1121 * @param outputPorts the list of output ports for this device
1122 * @param outputInstruction the output instruction
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001123 * @param groupsForDevice the groups we output from
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001124 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001125 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001126 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1127 List<Group> groupsForDevice) {
Andrea Campanella7c8e7912018-01-23 12:46:04 +01001128 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella6f2d6742018-02-07 12:00:12 +01001129
Andrea Campanella7b84c072018-03-06 15:21:09 -08001130 outputPorts.add(outputInstruction.port());
1131
1132 GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
1133 if (trace.getGroupOuputs(output.deviceId()) != null
1134 && trace.getGroupOuputs(output.deviceId()).contains(device)) {
1135 return;
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001136 }
Andrea Campanella7b84c072018-03-06 15:21:09 -08001137 trace.addGroupOutputPath(in.deviceId(),
1138 new GroupsInDevice(output, groupsForDevice, builder.build()));
Andrea Campanellabb9d3fb2018-01-22 15:10:30 +01001139 }
1140
1141 /**
Andrea Campanella01e886e2017-12-15 15:27:31 +01001142 * Applies all give instructions to the input packet.
1143 *
1144 * @param packet the input packet
1145 * @param instructions the set of instructions
1146 * @return the packet with the applied instructions
1147 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001148 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1149 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanella01e886e2017-12-15 15:27:31 +01001150 packet.criteria().forEach(newSelector::add);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001151 //FIXME optimize
1152 for (Instruction instruction : instructions) {
1153 newSelector = translateInstruction(newSelector, instruction);
1154 }
Andrea Campanella01e886e2017-12-15 15:27:31 +01001155 return newSelector;
1156 }
1157
1158 /**
1159 * Applies an instruction to the packet in the form of a selector.
1160 *
1161 * @param newSelector the packet selector
1162 * @param instruction the instruction to be translated
1163 * @return the new selector with the applied instruction
1164 */
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001165 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001166 log.debug("Translating instruction {}", instruction);
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001167 log.debug("New Selector {}", newSelector.build());
Andrea Campanella01e886e2017-12-15 15:27:31 +01001168 //TODO add as required
1169 Criterion criterion = null;
1170 switch (instruction.type()) {
1171 case L2MODIFICATION:
1172 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1173 switch (l2Instruction.subtype()) {
1174 case VLAN_ID:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001175 ModVlanIdInstruction vlanIdInstruction =
1176 (ModVlanIdInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001177 VlanId id = vlanIdInstruction.vlanId();
1178 criterion = Criteria.matchVlanId(id);
1179 break;
1180 case VLAN_POP:
1181 criterion = Criteria.matchVlanId(VlanId.NONE);
1182 break;
1183 case MPLS_PUSH:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001184 ModMplsHeaderInstruction mplsEthInstruction =
1185 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001186 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1187 break;
1188 case MPLS_POP:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001189 ModMplsHeaderInstruction mplsPopInstruction =
1190 (ModMplsHeaderInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001191 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001192
1193 //When popping MPLS we remove label and BOS
1194 TrafficSelector temporaryPacket = newSelector.build();
1195 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanellad5bb2ef2018-01-31 16:43:23 +01001196 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001197 temporaryPacket.criteria().stream().filter(c -> {
1198 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1199 !c.type().equals(Criterion.Type.MPLS_BOS);
1200 }).forEach(noMplsSelector::add);
1201 newSelector = noMplsSelector;
1202 }
1203
Andrea Campanella01e886e2017-12-15 15:27:31 +01001204 break;
1205 case MPLS_LABEL:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001206 ModMplsLabelInstruction mplsLabelInstruction =
1207 (ModMplsLabelInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001208 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella09ca07a2018-01-25 16:44:04 +01001209 newSelector.matchMplsBos(true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001210 break;
1211 case ETH_DST:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001212 ModEtherInstruction modEtherDstInstruction =
1213 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001214 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1215 break;
1216 case ETH_SRC:
Andrea Campanella97f9d4c2018-02-06 18:58:40 +01001217 ModEtherInstruction modEtherSrcInstruction =
1218 (ModEtherInstruction) instruction;
Andrea Campanella01e886e2017-12-15 15:27:31 +01001219 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1220 break;
1221 default:
1222 log.debug("Unsupported L2 Instruction");
1223 break;
1224 }
1225 break;
1226 default:
1227 log.debug("Unsupported Instruction");
1228 break;
1229 }
1230 if (criterion != null) {
1231 log.debug("Adding criterion {}", criterion);
1232 newSelector.add(criterion);
1233 }
1234 return newSelector;
1235 }
1236
1237 /**
1238 * Finds the rule in the device that mathces the input packet and has the highest priority.
1239 *
1240 * @param packet the input packet
1241 * @param in the connect point the packet comes in from
1242 * @param tableId the table to search
1243 * @return the flow entry
1244 */
1245 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1246 //Computing the possible match rules.
1247 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
1248 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1249 .stream()
1250 .filter(flowEntry -> {
1251 return flowEntry.table().equals(tableId);
1252 })
1253 .filter(flowEntry -> {
1254 return match(packet, flowEntry);
1255 }).max(comparator).orElse(null);
1256 }
1257
1258 /**
1259 * Matches the packet with the given flow entry.
1260 *
1261 * @param packet the packet to match
1262 * @param flowEntry the flow entry to match the packet against
1263 * @return true if the packet matches the flow.
1264 */
1265 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanella01e886e2017-12-15 15:27:31 +01001266 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1267 Criterion.Type type = criterion.type();
Andrea Campanella4ee4af12018-01-31 12:20:48 +01001268 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanella01e886e2017-12-15 15:27:31 +01001269 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1270 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella04924b92018-01-17 16:34:51 +01001271 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001272 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella04924b92018-01-17 16:34:51 +01001273 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1274 return matchMac(packet, (EthCriterion) criterion, false);
1275 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1276 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanella01e886e2017-12-15 15:27:31 +01001277 } else {
1278 return packet.criteria().contains(criterion);
1279 }
1280 });
Simon Hunt6fefd852017-11-13 17:09:43 -08001281 }
Andrea Campanella04924b92018-01-17 16:34:51 +01001282
1283 /**
1284 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1285 *
1286 * @param packet the incoming packet
1287 * @param criterion the criterion to match
1288 * @return true if match
1289 */
1290 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1291 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1292 //if the packet does not have an IPv4 or IPv6 criterion we return true
1293 if (matchCriterion == null) {
1294 return false;
1295 }
1296 try {
1297 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1298 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1299 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1300 } catch (UnknownHostException e) {
1301 return false;
1302 }
1303 }
1304
1305 /**
1306 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1307 *
1308 * @param packet the incoming packet
1309 * @param hitCriterion the criterion to match
1310 * @param dst true if we are checking DST MAC
1311 * @return true if match
1312 */
1313 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1314 //Packet can have only one EthCriterion
1315 EthCriterion matchCriterion;
1316 if (dst) {
1317 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1318 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1319 criterion1.type().equals(Criterion.Type.ETH_DST);
1320 }).findFirst().orElse(null);
1321 } else {
1322 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1323 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1324 criterion1.type().equals(Criterion.Type.ETH_SRC);
1325 }).findFirst().orElse(null);
1326 }
1327 //if the packet does not have an ETH criterion we return true
1328 if (matchCriterion == null) {
1329 return true;
1330 }
1331 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1332 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1333 }
Simon Hunt6fefd852017-11-13 17:09:43 -08001334}