blob: cf40de9997374fd8ec18dd4adedc7639d5df2c36 [file] [log] [blame]
Simon Hunt026a2872017-11-13 17:09:43 -08001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.t3.impl;
18
Andrea Campanella17d45192018-01-18 17:11:42 +010019import com.google.common.base.Preconditions;
Andrea Campanellae4084402017-12-15 15:27:31 +010020import com.google.common.collect.ImmutableList;
Andrea Campanella696ef032018-02-27 18:03:17 +010021import com.google.common.collect.ImmutableSet;
Andrea Campanellae4084402017-12-15 15:27:31 +010022import com.google.common.collect.Lists;
Andrea Campanella5befe1c2018-02-27 14:50:45 +010023import com.google.common.collect.Sets;
Andrea Campanella6a614fa2018-02-21 14:28:20 +010024import org.apache.commons.lang3.tuple.Pair;
Simon Hunt026a2872017-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 Campanella55c3f422018-02-08 17:10:11 +010029import org.onlab.packet.IpAddress;
Andrea Campanellae4084402017-12-15 15:27:31 +010030import org.onlab.packet.VlanId;
Andrea Campanella54923d62018-01-23 12:46:04 +010031import org.onosproject.cluster.NodeId;
32import org.onosproject.mastership.MastershipService;
Simon Hunt026a2872017-11-13 17:09:43 -080033import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010034import org.onosproject.net.DeviceId;
35import org.onosproject.net.Host;
Andrea Campanella55c3f422018-02-08 17:10:11 +010036import org.onosproject.net.HostId;
Andrea Campanellae4084402017-12-15 15:27:31 +010037import org.onosproject.net.Link;
Andrea Campanella1445f7a2018-02-07 12:00:12 +010038import org.onosproject.net.Port;
Andrea Campanellae4084402017-12-15 15:27:31 +010039import org.onosproject.net.PortNumber;
Andrea Campanella55c3f422018-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 Campanella17d45192018-01-18 17:11:42 +010043import org.onosproject.net.device.DeviceService;
Andrea Campanellae4084402017-12-15 15:27:31 +010044import org.onosproject.net.driver.DriverService;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010045import org.onosproject.net.edge.EdgePortService;
Andrea Campanellae4084402017-12-15 15:27:31 +010046import org.onosproject.net.flow.DefaultTrafficSelector;
47import org.onosproject.net.flow.FlowEntry;
48import org.onosproject.net.flow.FlowRule;
Simon Hunt026a2872017-11-13 17:09:43 -080049import org.onosproject.net.flow.FlowRuleService;
Andrea Campanellae4084402017-12-15 15:27:31 +010050import org.onosproject.net.flow.IndexTableId;
51import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080052import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-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 Campanellae6798012018-02-06 15:46:52 +010058import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanellae4084402017-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 Hunt026a2872017-11-13 17:09:43 -080065import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010066import org.onosproject.net.host.HostService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010067import org.onosproject.net.host.InterfaceIpAddress;
68import org.onosproject.net.intf.Interface;
Andrea Campanellae4084402017-12-15 15:27:31 +010069import org.onosproject.net.link.LinkService;
Andrea Campanella08d07e12018-03-07 14:27:54 -080070import org.onosproject.routeservice.ResolvedRoute;
71import org.onosproject.routeservice.RouteService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010072import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanellae4084402017-12-15 15:27:31 +010073import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080074import org.onosproject.t3.api.StaticPacketTrace;
75import org.onosproject.t3.api.TroubleshootService;
76import org.slf4j.Logger;
77
Andrea Campanellae4084402017-12-15 15:27:31 +010078import java.net.UnknownHostException;
79import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010080import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010081import java.util.Collections;
82import java.util.Comparator;
83import java.util.HashSet;
84import java.util.List;
Andrea Campanella08d07e12018-03-07 14:27:54 -080085import java.util.Optional;
Andrea Campanellae4084402017-12-15 15:27:31 +010086import java.util.Set;
87import java.util.stream.Collectors;
88
89import static org.onlab.packet.EthType.EtherType;
Andrea Campanellae6798012018-02-06 15:46:52 +010090import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanellae4084402017-12-15 15:27:31 +010091import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella94c594a2018-02-06 18:58:40 +010092import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
93import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
94import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
95import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010096import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt026a2872017-11-13 17:09:43 -080097import static org.slf4j.LoggerFactory.getLogger;
98
99/**
Andrea Campanellae4084402017-12-15 15:27:31 +0100100 * Manager to troubleshoot packets inside the network.
101 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
102 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -0800103 */
104@Service
105@Component(immediate = true)
106public class TroubleshootManager implements TroubleshootService {
107
108 private static final Logger log = getLogger(TroubleshootManager.class);
109
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100110 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
111
Simon Hunt026a2872017-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 Campanellae4084402017-12-15 15:27:31 +0100118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -0800120
Andrea Campanellae4084402017-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 Hunt026a2872017-11-13 17:09:43 -0800126
Andrea Campanella17d45192018-01-18 17:11:42 +0100127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
128 protected DeviceService deviceService;
129
Andrea Campanella54923d62018-01-23 12:46:04 +0100130 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
131 protected MastershipService mastershipService;
132
Andrea Campanella55c3f422018-02-08 17:10:11 +0100133 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
134 protected NetworkConfigService networkConfigService;
135
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100136 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
137 protected EdgePortService edgePortService;
138
Andrea Campanella08d07e12018-03-07 14:27:54 -0800139 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
140 protected RouteService routeService;
141
Andrea Campanella55c3f422018-02-08 17:10:11 +0100142 @Override
Andrea Campanella6a614fa2018-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 Campanella5befe1c2018-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 Campanella6a614fa2018-02-21 14:28:20 +0100150 hostService.getHosts().forEach(hostToPing -> {
151 List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
Andrea Campanella5befe1c2018-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 Campanella696ef032018-02-27 18:03:17 +0100160 tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100161 }
162 });
163 }
164 });
165 return tracesBuilder.build();
166 }
167
168 @Override
Andrea Campanella0cc6acd2018-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 Campanella696ef032018-02-27 18:03:17 +0100174 public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Andrea Campanella55c3f422018-02-08 17:10:11 +0100175 Host source = hostService.getHost(sourceHost);
176 Host destination = hostService.getHost(destinationHost);
177
Andrea Campanella6a614fa2018-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 Campanella55c3f422018-02-08 17:10:11 +0100180
181 if (source == null) {
182 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700183 failTrace.setSuccess(false);
184
Andrea Campanella696ef032018-02-27 18:03:17 +0100185 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100186 }
187
188 if (destination == null) {
189 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700190 failTrace.setSuccess(false);
Andrea Campanella696ef032018-02-27 18:03:17 +0100191 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100192 }
193
194 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
Andrea Campanella55c3f422018-02-08 17:10:11 +0100195 .matchEthType(etherType.ethType().toShort())
196 .matchEthDst(source.mac())
197 .matchVlanId(source.vlan());
198
199
Andrea Campanella55c3f422018-02-08 17:10:11 +0100200 try {
Andrea Campanellae24d4922018-03-26 10:39:22 -0700201 ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
Andrea Campanella55c3f422018-02-08 17:10:11 +0100202 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
203 // we are under same leaf so it's L2 Unicast.
204 if (areBridged(source, destination)) {
205 selectorBuilder.matchEthDst(destination.mac());
Andrea Campanella696ef032018-02-27 18:03:17 +0100206 source.locations().forEach(hostLocation -> {
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800207 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella696ef032018-02-27 18:03:17 +0100208 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
209 trace.addEndpointHosts(Pair.of(source, destination));
210 traces.add(trace);
211 });
Andrea Campanellae24d4922018-03-26 10:39:22 -0700212 //The destination host is not dual homed, if it is the other path might be done through routing.
213 if (destination.locations().size() == 1) {
214 return traces.build();
215 }
Andrea Campanella55c3f422018-02-08 17:10:11 +0100216 }
Andrea Campanella55c3f422018-02-08 17:10:11 +0100217 //handle the IPs for src and dst in case of L3
218 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
219
220 //Match on the source IP
221 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
Andrea Campanella696ef032018-02-27 18:03:17 +0100222 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100223 }
224
225 //Match on destination IP
226 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
Andrea Campanella696ef032018-02-27 18:03:17 +0100227 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100228 }
229
230 } else {
231 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
232 "please use packet based");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700233 failTrace.setSuccess(false);
Andrea Campanella696ef032018-02-27 18:03:17 +0100234 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100235 }
236
237 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
238 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
239 .deviceId(), SegmentRoutingDeviceConfig.class);
240 if (segmentRoutingConfig != null) {
241 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
242 } else {
243 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
244 " router MAC from segment routing config can't perform L3 tracing.");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700245 failTrace.setSuccess(false);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100246 }
Andrea Campanella696ef032018-02-27 18:03:17 +0100247 source.locations().forEach(hostLocation -> {
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800248 selectorBuilder.matchInPort(hostLocation.port());
Andrea Campanella696ef032018-02-27 18:03:17 +0100249 StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
250 trace.addEndpointHosts(Pair.of(source, destination));
251 traces.add(trace);
252 });
253 return traces.build();
Andrea Campanella55c3f422018-02-08 17:10:11 +0100254
255 } catch (ConfigException e) {
256 failTrace.addResultMessage("Can't get config " + e.getMessage());
Andrea Campanella696ef032018-02-27 18:03:17 +0100257 return ImmutableSet.of(failTrace);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100258 }
259 }
260
261 /**
262 * Matches src and dst IPs based on host information.
263 *
264 * @param host the host
265 * @param failTrace the trace to use in case of failure
266 * @param selectorBuilder the packet we are building to trace
267 * @param etherType the traffic type
268 * @param src is this src host or dst host
269 * @return true if properly matched
270 */
271 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
272 EtherType etherType, boolean src) {
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100273 List<IpAddress> ips = getIpAddresses(host, etherType, true);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100274
275 if (ips.size() > 0) {
Andrea Campanellabf8d9302018-02-21 14:43:21 +0100276 if (etherType.equals(EtherType.IPV4)) {
277 if (src) {
278 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
279 } else {
280 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
281 }
282 } else if (etherType.equals(EtherType.IPV6)) {
283 if (src) {
284 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
285 } else {
286 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
287 }
Andrea Campanella55c3f422018-02-08 17:10:11 +0100288 }
289 } else {
290 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700291 failTrace.setSuccess(false);
Andrea Campanella55c3f422018-02-08 17:10:11 +0100292 return false;
293 }
294 return true;
295 }
296
Andrea Campanella0cc6acd2018-02-28 16:43:16 +0100297 List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
Andrea Campanella6a614fa2018-02-21 14:28:20 +0100298 return host.ipAddresses().stream().filter(ipAddress -> {
299 boolean correctIp = false;
300 if (etherType.equals(EtherType.IPV4)) {
301 correctIp = ipAddress.isIp4();
302 } else if (etherType.equals(EtherType.IPV6)) {
303 correctIp = ipAddress.isIp6();
304 }
305 if (checklocal) {
306 correctIp = correctIp && !ipAddress.isLinkLocal();
307 }
308 return correctIp;
309 }).collect(Collectors.toList());
310 }
311
Andrea Campanella55c3f422018-02-08 17:10:11 +0100312 /**
313 * Checks that two hosts are bridged (L2Unicast).
314 *
315 * @param source the source host
316 * @param destination the destination host
317 * @return true if bridged.
318 * @throws ConfigException if config can't be properly retrieved
319 */
320 private boolean areBridged(Host source, Host destination) throws ConfigException {
321
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800322 //If the locations is not the same we don't even check vlan or subnets
323 if (Collections.disjoint(source.locations(), destination.locations())) {
324 return false;
325 }
326
327 if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
328 && !source.vlan().equals(destination.vlan())) {
Andrea Campanella55c3f422018-02-08 17:10:11 +0100329 return false;
330 }
331
332 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
333 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
334 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
335
336 //following can be optimized but for clarity is left as is
337 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
338 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
339
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800340 if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
341 return intfH1.vlanUntagged().equals(destination.vlan()) ||
342 intfH1.vlanNative().equals(destination.vlan());
Andrea Campanella55c3f422018-02-08 17:10:11 +0100343 }
344
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800345 if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
346 return intfH2.vlanUntagged().equals(source.vlan()) ||
347 intfH2.vlanNative().equals(source.vlan());
348 }
349
350 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
Andrea Campanella55c3f422018-02-08 17:10:11 +0100351 return false;
352 }
353
354 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
355 return false;
356 }
357
358 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
359 intersection.retainAll(intfH2.ipAddressesList());
360 if (intersection.size() == 0) {
361 return false;
362 }
363 }
364 return true;
365 }
366
Simon Hunt026a2872017-11-13 17:09:43 -0800367 @Override
368 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100369 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100370 //device must exist in ONOS
371 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
372 "Device " + in.deviceId() + " must exist in ONOS");
373
Andrea Campanellae4084402017-12-15 15:27:31 +0100374 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
Andrea Campanella696ef032018-02-27 18:03:17 +0100375 boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
Andrea Campanellae4084402017-12-15 15:27:31 +0100376 //FIXME this can be done recursively
Andrea Campanellae4084402017-12-15 15:27:31 +0100377 //Building output connect Points
378 List<ConnectPoint> path = new ArrayList<>();
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700379 trace = traceInDevice(trace, packet, in, isDualHomed, path);
Andrea Campanella696ef032018-02-27 18:03:17 +0100380 trace = getTrace(path, in, trace, isDualHomed);
Andrea Campanellae4084402017-12-15 15:27:31 +0100381 return trace;
382 }
383
384 /**
385 * Computes a trace for a give packet that start in the network at the given connect point.
386 *
387 * @param completePath the path traversed by the packet
388 * @param in the input connect point
389 * @param trace the trace to build
Andrea Campanella696ef032018-02-27 18:03:17 +0100390 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
Andrea Campanellae4084402017-12-15 15:27:31 +0100391 * @return the build trace for that packet.
392 */
Andrea Campanella696ef032018-02-27 18:03:17 +0100393 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
394 boolean isDualHomed) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100395
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100396 log.debug("------------------------------------------------------------");
397
Andrea Campanellae4084402017-12-15 15:27:31 +0100398 //if the trace already contains the input connect point there is a loop
399 if (pathContainsDevice(completePath, in.deviceId())) {
400 trace.addResultMessage("Loop encountered in device " + in.deviceId());
Andrea Campanella3ddbadb2018-03-09 14:52:10 -0800401 completePath.add(in);
402 trace.addCompletePath(completePath);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700403 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100404 return trace;
405 }
406
407 //let's add the input connect point
408 completePath.add(in);
409
410 //If the trace has no outputs for the given input we stop here
411 if (trace.getGroupOuputs(in.deviceId()) == null) {
412 computePath(completePath, trace, null);
413 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700414 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100415 return trace;
416 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100417
Andrea Campanellae4084402017-12-15 15:27:31 +0100418 //If the trace has ouputs we analyze them all
419 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100420
421 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100422 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100423 log.debug("Output path {}", cp);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800424 log.debug("{}", outputPath.getFinalPacket());
Andrea Campanella54923d62018-01-23 12:46:04 +0100425
Andrea Campanellae4084402017-12-15 15:27:31 +0100426 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100427 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100428 //Hosts queried from the original ip or mac
429 Set<Host> hosts = getHosts(trace);
430
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800431 if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
432 outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
433 && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
434 .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
435 .vlanId())) {
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700436 if (trace.getGroupOuputs(in.deviceId()).size() == 1 &&
437 computePath(completePath, trace, outputPath.getOutput())) {
Andrea Campanellafe5d8df2018-03-12 11:07:35 -0700438 trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700439 trace.setSuccess(false);
Andrea Campanellafe5d8df2018-03-12 11:07:35 -0700440 }
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700441 } else if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700442 //If the two host collections contain the same item it means we reached the proper output
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100443 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella696ef032018-02-27 18:03:17 +0100444 if (computePath(completePath, trace, outputPath.getOutput())) {
445 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanella0cc6acd2018-02-28 16:43:16 +0100446 trace.setSuccess(true);
Andrea Campanella696ef032018-02-27 18:03:17 +0100447 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100448 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100449 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100450
Andrea Campanella54923d62018-01-23 12:46:04 +0100451 //Getting the master when the packet gets sent as packet in
452 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100453 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella54923d62018-01-23 12:46:04 +0100454 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100455 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100456
Andrea Campanella8292ba62018-01-31 16:43:23 +0100457 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100458
459 //TODO this can be optimized if we use a Tree structure for paths.
460 //if we already have outputs let's check if the one we are considering starts from one of the devices
461 // in any of the ones we have.
462 if (trace.getCompletePaths().size() > 0) {
463 ConnectPoint inputForOutput = null;
464 List<ConnectPoint> previousPath = new ArrayList<>();
465 for (List<ConnectPoint> path : trace.getCompletePaths()) {
466 for (ConnectPoint connect : path) {
467 //if the path already contains the input for the output we've found we use it
468 if (connect.equals(in)) {
469 inputForOutput = connect;
470 previousPath = path;
471 break;
472 }
473 }
474 }
Andrea Campanella3ddbadb2018-03-09 14:52:10 -0800475
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100476 //we use the pre-existing path up to the point we fork to a new output
477 if (inputForOutput != null && completePath.contains(inputForOutput)) {
478 List<ConnectPoint> temp = new ArrayList<>(previousPath);
Andrea Campanella3ddbadb2018-03-09 14:52:10 -0800479 temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
480 if (completePath.containsAll(temp)) {
481 completePath = temp;
482 }
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100483 }
484 }
485
Andrea Campanellae4084402017-12-15 15:27:31 +0100486 //let's add the ouput for the input
487 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100488 //let's compute the links for the given output
489 Set<Link> links = linkService.getEgressLinks(cp);
490 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100491 //For each link we trace the corresponding device
492 for (Link link : links) {
493 ConnectPoint dst = link.dst();
494 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100495 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100496 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
497 updatedPacket.add(Criteria.matchInPort(dst.port()));
498 log.debug("DST Connect Point {}", dst);
499 //build the elements for that device
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700500 traceInDevice(trace, updatedPacket.build(), dst, isDualHomed, completePath);
Andrea Campanellae4084402017-12-15 15:27:31 +0100501 //continue the trace along the path
Andrea Campanella696ef032018-02-27 18:03:17 +0100502 getTrace(completePath, dst, trace, isDualHomed);
Andrea Campanellae4084402017-12-15 15:27:31 +0100503 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100504 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
505 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
506 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
507 .mac().isMulticast()) {
508 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
509 " which is enabled and is edge port");
Andrea Campanella0cc6acd2018-02-28 16:43:16 +0100510 trace.setSuccess(true);
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100511 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100512 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
513 return trace;
514 }
Andrea Campanellae24d4922018-03-26 10:39:22 -0700515 } else if (deviceService.getPort(cp) != null && deviceService.getPort(cp).isEnabled()) {
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100516 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
517 .getCriterion(Criterion.Type.ETH_TYPE);
518 //We treat as correct output only if it's not LLDP or BDDP
519 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
Andrea Campanellac6d10fc2018-02-27 12:42:28 +0100520 && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
Andrea Campanella08d07e12018-03-07 14:27:54 -0800521 if (computePath(completePath, trace, outputPath.getOutput())) {
522 if (hostsList.isEmpty()) {
523 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
524 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
525 cp + " with no hosts connected ");
526 } else {
527 IpAddress ipAddress = null;
528 if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
529 ipAddress = ((IPCriterion) trace.getInitialPacket()
530 .getCriterion(Criterion.Type.IPV4_DST)).ip().address();
531 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
532 ipAddress = ((IPCriterion) trace.getInitialPacket()
533 .getCriterion(Criterion.Type.IPV6_DST)).ip().address();
534 }
535 if (ipAddress != null) {
536 IpAddress finalIpAddress = ipAddress;
537 if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
538 hostService.getHostsByIp(finalIpAddress).isEmpty()) {
539 trace.addResultMessage("Packet is " +
540 ((EthTypeCriterion) outputPath.getFinalPacket()
Andrea Campanella7fa8f0a2018-03-09 15:30:22 -0800541 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
542 " and reached " + cp + " with hosts " + hostsList);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800543 } else {
544 trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
545 ipAddress);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700546 trace.setSuccess(false);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800547 }
548 } else {
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800549 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
550 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
551 cp + " with hosts " + hostsList);
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800552 }
Andrea Campanella2ff88a92018-03-06 15:21:09 -0800553 }
Andrea Campanella08d07e12018-03-07 14:27:54 -0800554 trace.setSuccess(true);
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100555 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100556 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100557
558 } else {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100559 computePath(completePath, trace, cp);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700560 trace.setSuccess(false);
Andrea Campanellae24d4922018-03-26 10:39:22 -0700561 if (deviceService.getPort(cp) == null) {
562 //Port is not existant on device.
563 log.warn("Port {} is not available on device.", cp);
564 trace.addResultMessage("Port " + cp + "is not available on device. Packet is dropped");
565 } else {
566 //No links means that the packet gets dropped.
567 log.warn("No links out of {}", cp);
568 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
569 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100570 }
571 }
572 return trace;
573 }
574
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100575
Andrea Campanellae4084402017-12-15 15:27:31 +0100576 /**
Andrea Campanellae6798012018-02-06 15:46:52 +0100577 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
578 * If ONOS applied a vlan we remove it.
579 *
580 * @param outputPath the output
581 * @param trace the trace we are building
582 */
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100583
Andrea Campanellae6798012018-02-06 15:46:52 +0100584 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
585
586 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
587 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
588
589 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
590
591 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
592 //removing the final vlanId
593 finalCriteria.remove(finalVid);
594 Builder packetUpdated = DefaultTrafficSelector.builder();
595 finalCriteria.forEach(packetUpdated::add);
596 //Initial was none so we set it to that
597 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
598 //Update final packet
599 outputPath.setFinalPacket(packetUpdated.build());
600 }
601 }
602
603 /**
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100604 * Checks if the device has other outputs than the given connect point.
605 *
606 * @param inDeviceId the device
607 * @param trace the trace we are building
608 * @param cp an output connect point
609 * @return true if the device has other outputs.
610 */
611 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
612 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
613 return !groupsInDevice.getOutput().equals(cp);
614 }).count() > 0;
615 }
616
617 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100618 * Checks if the path contains the device.
619 *
620 * @param completePath the path
621 * @param deviceId the device to check
622 * @return true if the path contains the device
623 */
624 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
625 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
626 for (ConnectPoint cp : completePath) {
627 if (cp.deviceId().equals(deviceId)) {
628 return true;
629 }
630 }
631 return false;
632 }
633
634 /**
635 * Gets the hosts for the given initial packet.
636 *
637 * @param trace the trace we are building
638 * @return set of the hosts we are trying to reach
639 */
640 private Set<Host> getHosts(StaticPacketTrace trace) {
641 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
642 .getCriterion(Criterion.Type.IPV4_DST));
643 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
644 .getCriterion(Criterion.Type.IPV6_DST));
645 Set<Host> hosts = new HashSet<>();
646 if (ipv4Criterion != null) {
647 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
648 }
649 if (ipv6Criterion != null) {
650 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
651 }
652 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
653 .getCriterion(Criterion.Type.ETH_DST));
654 if (ethCriterion != null) {
655 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
656 }
657 return hosts;
658 }
659
660 /**
661 * Computes the list of traversed connect points.
662 *
663 * @param completePath the list of devices
664 * @param trace the trace we are building
665 * @param output the final output connect point
666 */
Andrea Campanella696ef032018-02-27 18:03:17 +0100667 private boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100668 List<ConnectPoint> traverseList = new ArrayList<>();
669 if (!completePath.contains(trace.getInitialConnectPoint())) {
670 traverseList.add(trace.getInitialConnectPoint());
671 }
672 traverseList.addAll(completePath);
673 if (output != null && !completePath.contains(output)) {
674 traverseList.add(output);
675 }
Andrea Campanella696ef032018-02-27 18:03:17 +0100676 if (!trace.getCompletePaths().contains(traverseList)) {
Andrea Campanella3ddbadb2018-03-09 14:52:10 -0800677 trace.addCompletePath(ImmutableList.copyOf(traverseList));
Andrea Campanella696ef032018-02-27 18:03:17 +0100678 return true;
679 }
680 return false;
Andrea Campanellae4084402017-12-15 15:27:31 +0100681 }
682
683 /**
684 * Traces the packet inside a device starting from an input connect point.
685 *
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700686 * @param trace the trace we are building
687 * @param packet the packet we are tracing
688 * @param in the input connect point.
689 * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
690 * @param completePath the path up until this device
Andrea Campanellae4084402017-12-15 15:27:31 +0100691 * @return updated trace
692 */
Andrea Campanella696ef032018-02-27 18:03:17 +0100693 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700694 boolean isDualHomed, List<ConnectPoint> completePath) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100695
Andrea Campanella08d07e12018-03-07 14:27:54 -0800696 boolean multipleRoutes = false;
697 if (trace.getGroupOuputs(in.deviceId()) != null) {
698 multipleRoutes = multipleRoutes(trace);
699 }
700 if (trace.getGroupOuputs(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100701 log.debug("Trace already contains device and given outputs");
702 return trace;
703 }
Andrea Campanella08d07e12018-03-07 14:27:54 -0800704
Andrea Campanellae4084402017-12-15 15:27:31 +0100705 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100706
707 //if device is not available exit here.
708 if (!deviceService.isAvailable(in.deviceId())) {
709 trace.addResultMessage("Device is offline " + in.deviceId());
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700710 computePath(completePath, trace, null);
Andrea Campanella17d45192018-01-18 17:11:42 +0100711 return trace;
712 }
713
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100714 //handle when the input is the controller
715 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
716 // a packet in from the controller will not actually traverse the pipeline and have no such notion
717 // as the input port.
718 if (in.port().equals(PortNumber.CONTROLLER)) {
719 StaticPacketTrace outputTrace = inputFromController(trace, in);
720 if (outputTrace != null) {
721 return trace;
722 }
723 }
724
Andrea Campanellae4084402017-12-15 15:27:31 +0100725 List<FlowEntry> flows = new ArrayList<>();
726 List<FlowEntry> outputFlows = new ArrayList<>();
Andrea Campanella8292ba62018-01-31 16:43:23 +0100727 List<Instruction> deferredInstructions = new ArrayList<>();
728
Andrea Campanellae4084402017-12-15 15:27:31 +0100729 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
730 if (nextTableIdEntry == null) {
731 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700732 computePath(completePath, trace, null);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700733 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100734 return trace;
735 }
736 TableId tableId = nextTableIdEntry.table();
737 FlowEntry flowEntry;
738 boolean output = false;
739 while (!output) {
740 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
741 //get the rule that matches the incoming packet
742 flowEntry = matchHighestPriority(packet, in, tableId);
743 log.debug("Found Flow Entry {}", flowEntry);
744
745 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
746 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
747
748 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
749 if (flowEntry == null && isOfdpaHardware) {
750 log.debug("Ofdpa Hw setup, no flow rule means table miss");
751
Andrea Campanellae4084402017-12-15 15:27:31 +0100752 if (((IndexTableId) tableId).id() == 27) {
753 //Apparently a miss but Table 27 on OFDPA is a fixed table
754 packet = handleOfdpa27FixedTable(trace, packet);
755 }
756
757 //Finding next table to go In case of miss
758 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
759 log.debug("Next table id entry {}", nextTableIdEntry);
760
761 //FIXME find better solution that enable granularity greater than 0 or all rules
762 //(another possibility is max tableId)
763 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100764 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700765 computePath(completePath, trace, null);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700766 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100767 return trace;
768
769 } else if (nextTableIdEntry == null) {
770 //Means that no more flow rules are present
771 output = true;
772
773 } else if (((IndexTableId) tableId).id() == 20) {
774 //if the table is 20 OFDPA skips to table 50
775 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
776 tableId = IndexTableId.of(50);
777
778 } else {
779 tableId = nextTableIdEntry.table();
780 }
781
Andrea Campanellae4084402017-12-15 15:27:31 +0100782 } else if (flowEntry == null) {
783 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
784 in.deviceId() + ". Dropping");
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700785 computePath(completePath, trace, null);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -0700786 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +0100787 return trace;
788 } else {
Andrea Campanella94c594a2018-02-06 18:58:40 +0100789
Andrea Campanellae4084402017-12-15 15:27:31 +0100790 //IF the table has a transition
791 if (flowEntry.treatment().tableTransition() != null) {
792 //update the next table we transitions to
793 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
794 log.debug("Flow Entry has transition to table Id {}", tableId);
795 flows.add(flowEntry);
796 } else {
797 //table has no transition so it means that it's an output rule if on the last table
798 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
799 flows.add(flowEntry);
800 outputFlows.add(flowEntry);
801 output = true;
802 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100803 //update the packet according to the immediate actions of this flow rule.
804 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
805
806 //save the deferred rules for later
807 deferredInstructions.addAll(flowEntry.treatment().deferred());
808
809 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
810 if (flowEntry.treatment().clearedDeferred()) {
811 deferredInstructions.clear();
812 }
813
Andrea Campanella94c594a2018-02-06 18:58:40 +0100814 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
815 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
816
817 //Let's get the packet vlanId instruction
818 VlanIdCriterion packetVlanIdCriterion =
819 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
820
821 //Let's get the flow entry vlan mod instructions
822 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
823 .immediate().stream()
824 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
825 .findFirst().orElse(null);
826
827 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
828 // is a flow rule that matches on same criteria and with updated vlanId
829 if (entryModVlanIdInstruction != null) {
830
831 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
832 packetVlanIdCriterion, entryModVlanIdInstruction);
833
834 //We found the flow that we expected
835 if (secondVlanFlow != null) {
836 flows.add(secondVlanFlow);
837 } else {
838 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700839 computePath(completePath, trace, null);
Andrea Campanella94c594a2018-02-06 18:58:40 +0100840 return trace;
841 }
842 }
843
844 }
845
Andrea Campanellae4084402017-12-15 15:27:31 +0100846 }
847 }
848
849 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100850 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100851 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100852
Andrea Campanellae4084402017-12-15 15:27:31 +0100853 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100854 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100855
Andrea Campanellae4084402017-12-15 15:27:31 +0100856 List<PortNumber> outputPorts = new ArrayList<>();
Andrea Campanella08d07e12018-03-07 14:27:54 -0800857 List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
Andrea Campanellae4084402017-12-15 15:27:31 +0100858
Andrea Campanella08d07e12018-03-07 14:27:54 -0800859
860 log.debug("Handling Groups");
861 //Analyze Groups
862 List<Group> groups = new ArrayList<>();
863
864 Collection<FlowEntry> nonOutputFlows = flows;
865 nonOutputFlows.removeAll(outputFlowEntries);
866
867 //Handling groups pointed at by immediate instructions
868 for (FlowEntry entry : flows) {
869 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700870 entry.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800871 }
872
873 //If we have deferred instructions at this point we handle them.
874 if (deferredInstructions.size() > 0) {
Andrea Campanellad5e16ff2018-03-21 10:08:55 -0700875 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups, completePath);
Andrea Campanella08d07e12018-03-07 14:27:54 -0800876
877 }
878 packet = builder.build();
879
880 log.debug("Output Packet {}", packet);
881 return trace;
882 }
883
884 private List<FlowEntry> handleFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
885 List<FlowEntry> outputFlows, Builder builder, List<PortNumber> outputPorts) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100886 //TODO optimization
Andrea Campanella54923d62018-01-23 12:46:04 +0100887 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
888 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
889 .allInstructions().stream().filter(instruction -> instruction.type()
890 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100891
Andrea Campanella54923d62018-01-23 12:46:04 +0100892 if (outputFlowEntries.size() > 1) {
893 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
894 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100895 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100896
897 if (outputFlowEntries.size() == 1) {
898
899 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
900 .allInstructions().stream()
901 .filter(instruction -> {
902 return instruction.type().equals(Instruction.Type.OUTPUT);
903 }).findFirst().get();
904
905 //FIXME using GroupsInDevice for output even if flows.
906 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
907
908 }
Andrea Campanella08d07e12018-03-07 14:27:54 -0800909 return outputFlowEntries;
910 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100911
Andrea Campanella08d07e12018-03-07 14:27:54 -0800912 private boolean multipleRoutes(StaticPacketTrace trace) {
913 boolean multipleRoutes = false;
914 IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
915 IpAddress ip = null;
916 if (ipCriterion != null) {
917 ip = ipCriterion.ip().address();
918 } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
919 ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
Andrea Campanella54923d62018-01-23 12:46:04 +0100920 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100921
Andrea Campanella08d07e12018-03-07 14:27:54 -0800922 Optional<ResolvedRoute> optionalRoute = routeService.longestPrefixLookup(ip);
923 if (ip != null && optionalRoute.isPresent()) {
924 ResolvedRoute route = optionalRoute.get();
925 route.prefix();
926 multipleRoutes = routeService.getAllResolvedRoutes(route.prefix()).size() > 1;
Andrea Campanella8292ba62018-01-31 16:43:23 +0100927 }
Andrea Campanella08d07e12018-03-07 14:27:54 -0800928 return multipleRoutes;
Andrea Campanellae4084402017-12-15 15:27:31 +0100929 }
930
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100931 /**
932 * Handles the specific case where the Input is the controller.
933 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
934 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
935 * the flood to all active physical ports of the device.
936 *
937 * @param trace the trace
938 * @param in the controller port
939 * @return the augmented trace.
940 */
941 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
942 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
943 .getCriterion(Criterion.Type.ETH_TYPE);
944 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
945 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
946 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
947 //get the active ports
948 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
949 .filter(Port::isEnabled)
950 .collect(Collectors.toList());
951 //build an output from each one
952 enabledPorts.forEach(port -> {
953 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
954 ImmutableList.of(), trace.getInitialPacket());
955 trace.addGroupOutputPath(in.deviceId(), output);
956 });
957 return trace;
958 }
959 return null;
960 }
961
Andrea Campanella94c594a2018-02-06 18:58:40 +0100962 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
963 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
964 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
965 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
966 .vlanId().equals(VlanId.NONE);
967 }
968
969 /**
970 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
971 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
972 * second to transition to following table
973 *
974 * @param packet the incoming packet
975 * @param in the input connect point
976 * @param packetVlanIdCriterion the vlan criterion from the packet
977 * @param entryModVlanIdInstruction the entry vlan instruction
978 * @return the second flow entry that matched
979 */
980 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
981 VlanIdCriterion packetVlanIdCriterion,
982 ModVlanIdInstruction entryModVlanIdInstruction) {
983 FlowEntry secondVlanFlow = null;
984 //Check the packet has been update from the first rule.
985 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
986 //find a rule on the same table that matches the vlan and
987 // also all the other elements of the flow such as input port
988 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
989 .stream()
990 .filter(entry -> {
991 return entry.table().equals(IndexTableId.of(10));
992 })
993 .filter(entry -> {
994 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
995 .getCriterion(Criterion.Type.VLAN_VID);
996 return criterion != null && match(packet, entry)
997 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
998 }).findFirst().orElse(null);
999
1000 }
1001 return secondVlanFlow;
1002 }
1003
Andrea Campanella8292ba62018-01-31 16:43:23 +01001004
Andrea Campanellae4084402017-12-15 15:27:31 +01001005 /**
Andrea Campanellae4084402017-12-15 15:27:31 +01001006 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
1007 *
1008 * @param packet the incoming packet
1009 * @return the updated packet
1010 */
1011 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
1012 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
1013 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
1014 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
1015
1016 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
1017 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
1018 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +01001019 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +01001020 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
1021 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
1022 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +01001023 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +01001024 builder.add(ethInstruction);
1025 }
1026 packet = updatePacket(packet, builder.build()).build();
1027 return packet;
1028 }
1029
1030 /**
1031 * Finds the flow entry with the minimun next table Id.
1032 *
1033 * @param deviceId the device to search
1034 * @param currentId the current id. the search will use this as minimum
1035 * @return the flow entry with the minimum table Id after the given one.
1036 */
1037 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
1038
1039 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
1040
1041 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
1042 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
1043 }
1044
Andrea Campanella8292ba62018-01-31 16:43:23 +01001045 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
1046 ConnectPoint in, List<Instruction> deferredInstructions,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001047 List<PortNumber> outputPorts, List<Group> groups,
1048 List<ConnectPoint> completePath) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001049
1050 //Update the packet with the deferred instructions
1051 Builder builder = updatePacket(packet, deferredInstructions);
1052
1053 //Gather any output instructions from the deferred instruction
1054 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
1055 return instruction.type().equals(Instruction.Type.OUTPUT);
1056 }).collect(Collectors.toList());
1057
1058 //We are considering deferred instructions from flows, there can only be one output.
1059 if (outputFlowInstruction.size() > 1) {
1060 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
1061 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
1062 }
1063 //If there is one output let's go through that
1064 if (outputFlowInstruction.size() == 1) {
1065 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
1066 ImmutableList.of());
1067 }
1068 //If there is no output let's see if there any deferred instruction point to groups.
1069 if (outputFlowInstruction.size() == 0) {
1070 getGroupsFromInstructions(trace, groups, deferredInstructions,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001071 in.deviceId(), builder, outputPorts, in, completePath);
Andrea Campanella8292ba62018-01-31 16:43:23 +01001072 }
1073 return builder;
1074 }
1075
Andrea Campanellae4084402017-12-15 15:27:31 +01001076 /**
1077 * Gets group information from instructions.
1078 *
1079 * @param trace the trace we are building
1080 * @param groupsForDevice the set of groups for this device
1081 * @param instructions the set of instructions we are searching for groups.
1082 * @param deviceId the device we are considering
1083 * @param builder the builder of the input packet
1084 * @param outputPorts the output ports for that packet
1085 */
1086 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
1087 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +01001088 Builder builder, List<PortNumber> outputPorts,
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001089 ConnectPoint in, List<ConnectPoint> completePath) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001090 List<Instruction> groupInstructionlist = new ArrayList<>();
1091 for (Instruction instruction : instructions) {
1092 log.debug("Considering Instruction {}", instruction);
1093 //if the instruction is not group we need to update the packet or add the output
1094 //to the possible outputs for this packet
1095 if (!instruction.type().equals(Instruction.Type.GROUP)) {
1096 //if the instruction is not group we need to update the packet or add the output
1097 //to the possible outputs for this packet
1098 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001099 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella573d4b92018-02-19 17:03:46 +01001100 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
1101 //clearing the groups because we start from the top.
1102 groupsForDevice.clear();
Andrea Campanellae4084402017-12-15 15:27:31 +01001103 } else {
1104 builder = translateInstruction(builder, instruction);
1105 }
1106 } else {
1107 //if the instuction is pointing to a group we need to get the group
1108 groupInstructionlist.add(instruction);
1109 }
1110 }
1111 //handle all the internal instructions pointing to a group.
1112 for (Instruction instr : groupInstructionlist) {
1113 GroupInstruction groupInstruction = (GroupInstruction) instr;
1114 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
1115 return groupInternal.id().equals(groupInstruction.groupId());
1116 }).findAny().orElse(null);
1117 if (group == null) {
1118 trace.addResultMessage("Null group for Instruction " + instr);
Andrea Campanella5af2d2b2018-03-12 19:25:44 -07001119 trace.setSuccess(false);
Andrea Campanellae4084402017-12-15 15:27:31 +01001120 break;
1121 }
Andrea Campanella94dfb9e2018-02-27 12:36:00 +01001122 if (group.buckets().buckets().size() == 0) {
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001123 trace.addResultMessage("Group " + group.id() + " has no buckets");
Andrea Campanella5af2d2b2018-03-12 19:25:44 -07001124 trace.setSuccess(false);
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001125 computePath(completePath, trace, null);
Andrea Campanella94dfb9e2018-02-27 12:36:00 +01001126 break;
1127 }
Andrea Campanella573d4b92018-02-19 17:03:46 +01001128
Andrea Campanellae4084402017-12-15 15:27:31 +01001129 //Cycle in each of the group's buckets and add them to the groups for this Device.
1130 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella573d4b92018-02-19 17:03:46 +01001131
1132 //add the group to the traversed groups
1133 if (!groupsForDevice.contains(group)) {
1134 groupsForDevice.add(group);
1135 }
1136
Andrea Campanellae4084402017-12-15 15:27:31 +01001137 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanellad5e16ff2018-03-21 10:08:55 -07001138 deviceId, builder, outputPorts, in, completePath);
Andrea Campanellae4084402017-12-15 15:27:31 +01001139 }
1140 }
1141 }
1142
1143 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001144 * Check if the output is the input port, if so adds a dop result message, otherwise builds
1145 * a possible output from this device.
1146 *
1147 * @param trace the trace
1148 * @param in the input connect point
1149 * @param builder the packet builder
1150 * @param outputPorts the list of output ports for this device
1151 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +01001152 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001153 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001154 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001155 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
1156 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +01001157 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella1445f7a2018-02-07 12:00:12 +01001158
Andrea Campanella2ff88a92018-03-06 15:21:09 -08001159 outputPorts.add(outputInstruction.port());
1160
1161 GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
1162 if (trace.getGroupOuputs(output.deviceId()) != null
1163 && trace.getGroupOuputs(output.deviceId()).contains(device)) {
1164 return;
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001165 }
Andrea Campanella2ff88a92018-03-06 15:21:09 -08001166 trace.addGroupOutputPath(in.deviceId(),
1167 new GroupsInDevice(output, groupsForDevice, builder.build()));
Andrea Campanella7d3cf652018-01-22 15:10:30 +01001168 }
1169
1170 /**
Andrea Campanellae4084402017-12-15 15:27:31 +01001171 * Applies all give instructions to the input packet.
1172 *
1173 * @param packet the input packet
1174 * @param instructions the set of instructions
1175 * @return the packet with the applied instructions
1176 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001177 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
1178 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +01001179 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +01001180 //FIXME optimize
1181 for (Instruction instruction : instructions) {
1182 newSelector = translateInstruction(newSelector, instruction);
1183 }
Andrea Campanellae4084402017-12-15 15:27:31 +01001184 return newSelector;
1185 }
1186
1187 /**
1188 * Applies an instruction to the packet in the form of a selector.
1189 *
1190 * @param newSelector the packet selector
1191 * @param instruction the instruction to be translated
1192 * @return the new selector with the applied instruction
1193 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001194 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001195 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +01001196 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +01001197 //TODO add as required
1198 Criterion criterion = null;
1199 switch (instruction.type()) {
1200 case L2MODIFICATION:
1201 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1202 switch (l2Instruction.subtype()) {
1203 case VLAN_ID:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001204 ModVlanIdInstruction vlanIdInstruction =
1205 (ModVlanIdInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001206 VlanId id = vlanIdInstruction.vlanId();
1207 criterion = Criteria.matchVlanId(id);
1208 break;
1209 case VLAN_POP:
1210 criterion = Criteria.matchVlanId(VlanId.NONE);
1211 break;
1212 case MPLS_PUSH:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001213 ModMplsHeaderInstruction mplsEthInstruction =
1214 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001215 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1216 break;
1217 case MPLS_POP:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001218 ModMplsHeaderInstruction mplsPopInstruction =
1219 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001220 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +01001221
1222 //When popping MPLS we remove label and BOS
1223 TrafficSelector temporaryPacket = newSelector.build();
1224 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001225 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +01001226 temporaryPacket.criteria().stream().filter(c -> {
1227 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1228 !c.type().equals(Criterion.Type.MPLS_BOS);
1229 }).forEach(noMplsSelector::add);
1230 newSelector = noMplsSelector;
1231 }
1232
Andrea Campanellae4084402017-12-15 15:27:31 +01001233 break;
1234 case MPLS_LABEL:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001235 ModMplsLabelInstruction mplsLabelInstruction =
1236 (ModMplsLabelInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001237 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +01001238 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001239 break;
1240 case ETH_DST:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001241 ModEtherInstruction modEtherDstInstruction =
1242 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001243 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1244 break;
1245 case ETH_SRC:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001246 ModEtherInstruction modEtherSrcInstruction =
1247 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001248 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1249 break;
1250 default:
1251 log.debug("Unsupported L2 Instruction");
1252 break;
1253 }
1254 break;
1255 default:
1256 log.debug("Unsupported Instruction");
1257 break;
1258 }
1259 if (criterion != null) {
1260 log.debug("Adding criterion {}", criterion);
1261 newSelector.add(criterion);
1262 }
1263 return newSelector;
1264 }
1265
1266 /**
1267 * Finds the rule in the device that mathces the input packet and has the highest priority.
1268 *
1269 * @param packet the input packet
1270 * @param in the connect point the packet comes in from
1271 * @param tableId the table to search
1272 * @return the flow entry
1273 */
1274 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1275 //Computing the possible match rules.
1276 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
1277 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1278 .stream()
1279 .filter(flowEntry -> {
1280 return flowEntry.table().equals(tableId);
1281 })
1282 .filter(flowEntry -> {
1283 return match(packet, flowEntry);
1284 }).max(comparator).orElse(null);
1285 }
1286
1287 /**
1288 * Matches the packet with the given flow entry.
1289 *
1290 * @param packet the packet to match
1291 * @param flowEntry the flow entry to match the packet against
1292 * @return true if the packet matches the flow.
1293 */
1294 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001295 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1296 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +01001297 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +01001298 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1299 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001300 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanellae4084402017-12-15 15:27:31 +01001301 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001302 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1303 return matchMac(packet, (EthCriterion) criterion, false);
1304 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1305 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001306 } else {
1307 return packet.criteria().contains(criterion);
1308 }
1309 });
Simon Hunt026a2872017-11-13 17:09:43 -08001310 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001311
1312 /**
1313 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1314 *
1315 * @param packet the incoming packet
1316 * @param criterion the criterion to match
1317 * @return true if match
1318 */
1319 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1320 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1321 //if the packet does not have an IPv4 or IPv6 criterion we return true
1322 if (matchCriterion == null) {
1323 return false;
1324 }
1325 try {
1326 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1327 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1328 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1329 } catch (UnknownHostException e) {
1330 return false;
1331 }
1332 }
1333
1334 /**
1335 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1336 *
1337 * @param packet the incoming packet
1338 * @param hitCriterion the criterion to match
1339 * @param dst true if we are checking DST MAC
1340 * @return true if match
1341 */
1342 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1343 //Packet can have only one EthCriterion
1344 EthCriterion matchCriterion;
1345 if (dst) {
1346 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1347 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1348 criterion1.type().equals(Criterion.Type.ETH_DST);
1349 }).findFirst().orElse(null);
1350 } else {
1351 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1352 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1353 criterion1.type().equals(Criterion.Type.ETH_SRC);
1354 }).findFirst().orElse(null);
1355 }
1356 //if the packet does not have an ETH criterion we return true
1357 if (matchCriterion == null) {
1358 return true;
1359 }
1360 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1361 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1362 }
Simon Hunt026a2872017-11-13 17:09:43 -08001363}