blob: 2c4138295c6122545523d99745ccabc0ec458f7e [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;
21import com.google.common.collect.Lists;
Simon Hunt026a2872017-11-13 17:09:43 -080022import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
Andrea Campanella55c3f422018-02-08 17:10:11 +010026import org.onlab.packet.IpAddress;
Andrea Campanellae4084402017-12-15 15:27:31 +010027import org.onlab.packet.VlanId;
Andrea Campanella54923d62018-01-23 12:46:04 +010028import org.onosproject.cluster.NodeId;
29import org.onosproject.mastership.MastershipService;
Simon Hunt026a2872017-11-13 17:09:43 -080030import org.onosproject.net.ConnectPoint;
Andrea Campanellae4084402017-12-15 15:27:31 +010031import org.onosproject.net.DeviceId;
32import org.onosproject.net.Host;
Andrea Campanella55c3f422018-02-08 17:10:11 +010033import org.onosproject.net.HostId;
Andrea Campanellae4084402017-12-15 15:27:31 +010034import org.onosproject.net.Link;
Andrea Campanella1445f7a2018-02-07 12:00:12 +010035import org.onosproject.net.Port;
Andrea Campanellae4084402017-12-15 15:27:31 +010036import org.onosproject.net.PortNumber;
Andrea Campanella55c3f422018-02-08 17:10:11 +010037import org.onosproject.net.config.ConfigException;
38import org.onosproject.net.config.NetworkConfigService;
39import org.onosproject.net.config.basics.InterfaceConfig;
Andrea Campanella17d45192018-01-18 17:11:42 +010040import org.onosproject.net.device.DeviceService;
Andrea Campanellae4084402017-12-15 15:27:31 +010041import org.onosproject.net.driver.DriverService;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010042import org.onosproject.net.edge.EdgePortService;
Andrea Campanellae4084402017-12-15 15:27:31 +010043import org.onosproject.net.flow.DefaultTrafficSelector;
44import org.onosproject.net.flow.FlowEntry;
45import org.onosproject.net.flow.FlowRule;
Simon Hunt026a2872017-11-13 17:09:43 -080046import org.onosproject.net.flow.FlowRuleService;
Andrea Campanellae4084402017-12-15 15:27:31 +010047import org.onosproject.net.flow.IndexTableId;
48import org.onosproject.net.flow.TableId;
Simon Hunt026a2872017-11-13 17:09:43 -080049import org.onosproject.net.flow.TrafficSelector;
Andrea Campanellae4084402017-12-15 15:27:31 +010050import org.onosproject.net.flow.criteria.Criteria;
51import org.onosproject.net.flow.criteria.Criterion;
52import org.onosproject.net.flow.criteria.EthCriterion;
53import org.onosproject.net.flow.criteria.EthTypeCriterion;
54import org.onosproject.net.flow.criteria.IPCriterion;
Andrea Campanellae6798012018-02-06 15:46:52 +010055import org.onosproject.net.flow.criteria.VlanIdCriterion;
Andrea Campanellae4084402017-12-15 15:27:31 +010056import org.onosproject.net.flow.instructions.Instruction;
57import org.onosproject.net.flow.instructions.Instructions;
58import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
59import org.onosproject.net.flow.instructions.L2ModificationInstruction;
60import org.onosproject.net.group.Group;
61import org.onosproject.net.group.GroupBucket;
Simon Hunt026a2872017-11-13 17:09:43 -080062import org.onosproject.net.group.GroupService;
Andrea Campanellae4084402017-12-15 15:27:31 +010063import org.onosproject.net.host.HostService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010064import org.onosproject.net.host.InterfaceIpAddress;
65import org.onosproject.net.intf.Interface;
Andrea Campanellae4084402017-12-15 15:27:31 +010066import org.onosproject.net.link.LinkService;
Andrea Campanella55c3f422018-02-08 17:10:11 +010067import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
Andrea Campanellae4084402017-12-15 15:27:31 +010068import org.onosproject.t3.api.GroupsInDevice;
Simon Hunt026a2872017-11-13 17:09:43 -080069import org.onosproject.t3.api.StaticPacketTrace;
70import org.onosproject.t3.api.TroubleshootService;
71import org.slf4j.Logger;
72
Andrea Campanellae4084402017-12-15 15:27:31 +010073import java.net.UnknownHostException;
74import java.util.ArrayList;
Andrea Campanella54923d62018-01-23 12:46:04 +010075import java.util.Collection;
Andrea Campanellae4084402017-12-15 15:27:31 +010076import java.util.Collections;
77import java.util.Comparator;
78import java.util.HashSet;
79import java.util.List;
80import java.util.Set;
81import java.util.stream.Collectors;
82
83import static org.onlab.packet.EthType.EtherType;
Andrea Campanellae6798012018-02-06 15:46:52 +010084import static org.onosproject.net.flow.TrafficSelector.Builder;
Andrea Campanellae4084402017-12-15 15:27:31 +010085import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
Andrea Campanella94c594a2018-02-06 18:58:40 +010086import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
87import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
88import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
89import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
Andrea Campanella4c6170a2018-01-17 16:34:51 +010090import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
Simon Hunt026a2872017-11-13 17:09:43 -080091import static org.slf4j.LoggerFactory.getLogger;
92
93/**
Andrea Campanellae4084402017-12-15 15:27:31 +010094 * Manager to troubleshoot packets inside the network.
95 * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
96 * the devices.
Simon Hunt026a2872017-11-13 17:09:43 -080097 */
98@Service
99@Component(immediate = true)
100public class TroubleshootManager implements TroubleshootService {
101
102 private static final Logger log = getLogger(TroubleshootManager.class);
103
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100104 static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
105
Simon Hunt026a2872017-11-13 17:09:43 -0800106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected FlowRuleService flowRuleService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected GroupService groupService;
111
Andrea Campanellae4084402017-12-15 15:27:31 +0100112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected LinkService linkService;
Simon Hunt026a2872017-11-13 17:09:43 -0800114
Andrea Campanellae4084402017-12-15 15:27:31 +0100115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected HostService hostService;
117
118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected DriverService driverService;
Simon Hunt026a2872017-11-13 17:09:43 -0800120
Andrea Campanella17d45192018-01-18 17:11:42 +0100121 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
122 protected DeviceService deviceService;
123
Andrea Campanella54923d62018-01-23 12:46:04 +0100124 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
125 protected MastershipService mastershipService;
126
Andrea Campanella55c3f422018-02-08 17:10:11 +0100127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
128 protected NetworkConfigService networkConfigService;
129
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100130 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
131 protected EdgePortService edgePortService;
132
Andrea Campanella55c3f422018-02-08 17:10:11 +0100133 @Override
134 public StaticPacketTrace trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
135 Host source = hostService.getHost(sourceHost);
136 Host destination = hostService.getHost(destinationHost);
137
138 //Temporary trace to fail in case we don't have neough information or what is provided is incoherent
139 StaticPacketTrace failTrace = new StaticPacketTrace(null, null);
140
141 if (source == null) {
142 failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
143 return failTrace;
144 }
145
146 if (destination == null) {
147 failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
148 return failTrace;
149 }
150
151 TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
152 .matchInPort(source.location().port())
153 .matchEthType(etherType.ethType().toShort())
154 .matchEthDst(source.mac())
155 .matchVlanId(source.vlan());
156
157
Andrea Campanella55c3f422018-02-08 17:10:11 +0100158 try {
159 //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
160 // we are under same leaf so it's L2 Unicast.
161 if (areBridged(source, destination)) {
162 selectorBuilder.matchEthDst(destination.mac());
163 return trace(selectorBuilder.build(), source.location());
164 }
165
166 //handle the IPs for src and dst in case of L3
167 if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
168
169 //Match on the source IP
170 if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
171 return failTrace;
172 }
173
174 //Match on destination IP
175 if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
176 return failTrace;
177 }
178
179 } else {
180 failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
181 "please use packet based");
182 return failTrace;
183 }
184
185 //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
186 SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
187 .deviceId(), SegmentRoutingDeviceConfig.class);
188 if (segmentRoutingConfig != null) {
189 selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
190 } else {
191 failTrace.addResultMessage("Can't get " + source.location().deviceId() +
192 " router MAC from segment routing config can't perform L3 tracing.");
193 }
194
195 return trace(selectorBuilder.build(), source.location());
196
197 } catch (ConfigException e) {
198 failTrace.addResultMessage("Can't get config " + e.getMessage());
199 return failTrace;
200 }
201 }
202
203 /**
204 * Matches src and dst IPs based on host information.
205 *
206 * @param host the host
207 * @param failTrace the trace to use in case of failure
208 * @param selectorBuilder the packet we are building to trace
209 * @param etherType the traffic type
210 * @param src is this src host or dst host
211 * @return true if properly matched
212 */
213 private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
214 EtherType etherType, boolean src) {
215 List<IpAddress> ips = host.ipAddresses().stream().filter(ipAddress -> {
216 if (etherType.equals(EtherType.IPV4)) {
217 return ipAddress.isIp4() && !ipAddress.isLinkLocal();
218 } else if (etherType.equals(EtherType.IPV6)) {
219 return ipAddress.isIp6() && !ipAddress.isLinkLocal();
220 }
221 return false;
222 }).collect(Collectors.toList());
223
224 if (ips.size() > 0) {
Andrea Campanellabf8d9302018-02-21 14:43:21 +0100225 if (etherType.equals(EtherType.IPV4)) {
226 if (src) {
227 selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
228 } else {
229 selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
230 }
231 } else if (etherType.equals(EtherType.IPV6)) {
232 if (src) {
233 selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
234 } else {
235 selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
236 }
Andrea Campanella55c3f422018-02-08 17:10:11 +0100237 }
238 } else {
239 failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
240 return false;
241 }
242 return true;
243 }
244
245 /**
246 * Checks that two hosts are bridged (L2Unicast).
247 *
248 * @param source the source host
249 * @param destination the destination host
250 * @return true if bridged.
251 * @throws ConfigException if config can't be properly retrieved
252 */
253 private boolean areBridged(Host source, Host destination) throws ConfigException {
254
255 //If the location is not the same we don't even check vlan or subnets
256 if (!source.location().deviceId().equals(destination.location().deviceId())) {
257 return false;
258 }
259
260 InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
261 InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
262 if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
263
264 //following can be optimized but for clarity is left as is
265 Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
266 Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
267
268 if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
269 return false;
270 }
271
272 if (!intfH1.vlanTagged().equals(intfH2.vlanTagged())) {
273 return false;
274 }
275
276 if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
277 return false;
278 }
279
280 List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
281 intersection.retainAll(intfH2.ipAddressesList());
282 if (intersection.size() == 0) {
283 return false;
284 }
285 }
286 return true;
287 }
288
Simon Hunt026a2872017-11-13 17:09:43 -0800289 @Override
290 public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100291 log.info("Tracing packet {} coming in through {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100292 //device must exist in ONOS
293 Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
294 "Device " + in.deviceId() + " must exist in ONOS");
295
Andrea Campanellae4084402017-12-15 15:27:31 +0100296 StaticPacketTrace trace = new StaticPacketTrace(packet, in);
297 //FIXME this can be done recursively
298 trace = traceInDevice(trace, packet, in);
299 //Building output connect Points
300 List<ConnectPoint> path = new ArrayList<>();
301 trace = getTrace(path, in, trace);
302 return trace;
303 }
304
305 /**
306 * Computes a trace for a give packet that start in the network at the given connect point.
307 *
308 * @param completePath the path traversed by the packet
309 * @param in the input connect point
310 * @param trace the trace to build
311 * @return the build trace for that packet.
312 */
313 private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
314
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100315 log.debug("------------------------------------------------------------");
316
Andrea Campanellae4084402017-12-15 15:27:31 +0100317 //if the trace already contains the input connect point there is a loop
318 if (pathContainsDevice(completePath, in.deviceId())) {
319 trace.addResultMessage("Loop encountered in device " + in.deviceId());
320 return trace;
321 }
322
323 //let's add the input connect point
324 completePath.add(in);
325
326 //If the trace has no outputs for the given input we stop here
327 if (trace.getGroupOuputs(in.deviceId()) == null) {
328 computePath(completePath, trace, null);
329 trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
330 return trace;
331 }
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100332
Andrea Campanellae4084402017-12-15 15:27:31 +0100333 //If the trace has ouputs we analyze them all
334 for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100335
336 ConnectPoint cp = outputPath.getOutput();
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100337 log.debug("Connect point in {}", in);
Andrea Campanella54923d62018-01-23 12:46:04 +0100338 log.debug("Output path {}", cp);
339
Andrea Campanellae4084402017-12-15 15:27:31 +0100340 //Hosts for the the given output
Andrea Campanella54923d62018-01-23 12:46:04 +0100341 Set<Host> hostsList = hostService.getConnectedHosts(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100342 //Hosts queried from the original ip or mac
343 Set<Host> hosts = getHosts(trace);
344
345 //If the two host collections contain the same item it means we reached the proper output
346 if (!Collections.disjoint(hostsList, hosts)) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100347 log.debug("Stopping here because host is expected destination {}, reached through", completePath);
Andrea Campanella54923d62018-01-23 12:46:04 +0100348 trace.addResultMessage("Reached required destination Host " + cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100349 computePath(completePath, trace, outputPath.getOutput());
350 break;
Andrea Campanella54923d62018-01-23 12:46:04 +0100351 } else if (cp.port().equals(PortNumber.CONTROLLER)) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100352
Andrea Campanella54923d62018-01-23 12:46:04 +0100353 //Getting the master when the packet gets sent as packet in
354 NodeId master = mastershipService.getMasterFor(cp.deviceId());
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100355 trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
Andrea Campanella54923d62018-01-23 12:46:04 +0100356 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanellae6798012018-02-06 15:46:52 +0100357 handleVlanToController(outputPath, trace);
Andrea Campanella54923d62018-01-23 12:46:04 +0100358
Andrea Campanella8292ba62018-01-31 16:43:23 +0100359 } else if (linkService.getEgressLinks(cp).size() > 0) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100360
361 //TODO this can be optimized if we use a Tree structure for paths.
362 //if we already have outputs let's check if the one we are considering starts from one of the devices
363 // in any of the ones we have.
364 if (trace.getCompletePaths().size() > 0) {
365 ConnectPoint inputForOutput = null;
366 List<ConnectPoint> previousPath = new ArrayList<>();
367 for (List<ConnectPoint> path : trace.getCompletePaths()) {
368 for (ConnectPoint connect : path) {
369 //if the path already contains the input for the output we've found we use it
370 if (connect.equals(in)) {
371 inputForOutput = connect;
372 previousPath = path;
373 break;
374 }
375 }
376 }
377 //we use the pre-existing path up to the point we fork to a new output
378 if (inputForOutput != null && completePath.contains(inputForOutput)) {
379 List<ConnectPoint> temp = new ArrayList<>(previousPath);
380 completePath = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
381 }
382 }
383
Andrea Campanellae4084402017-12-15 15:27:31 +0100384 //let's add the ouput for the input
385 completePath.add(cp);
Andrea Campanellae4084402017-12-15 15:27:31 +0100386 //let's compute the links for the given output
387 Set<Link> links = linkService.getEgressLinks(cp);
388 log.debug("Egress Links {}", links);
Andrea Campanellae4084402017-12-15 15:27:31 +0100389 //For each link we trace the corresponding device
390 for (Link link : links) {
391 ConnectPoint dst = link.dst();
392 //change in-port to the dst link in port
Andrea Campanella8292ba62018-01-31 16:43:23 +0100393 Builder updatedPacket = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100394 outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
395 updatedPacket.add(Criteria.matchInPort(dst.port()));
396 log.debug("DST Connect Point {}", dst);
397 //build the elements for that device
398 traceInDevice(trace, updatedPacket.build(), dst);
399 //continue the trace along the path
400 getTrace(completePath, dst, trace);
401 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100402 } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
403 trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
404 ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
405 .mac().isMulticast()) {
406 trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
407 " which is enabled and is edge port");
408 computePath(completePath, trace, outputPath.getOutput());
409 completePath.clear();
410 if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
411 return trace;
412 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100413 } else if (deviceService.getPort(cp).isEnabled()) {
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100414 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
415 .getCriterion(Criterion.Type.ETH_TYPE);
416 //We treat as correct output only if it's not LLDP or BDDP
417 if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
418 || !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
419 if (hostsList.isEmpty()) {
420 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
421 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
422 cp + " with no hosts connected ");
423 } else {
424 trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
425 .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
426 cp + " with hosts " + hostsList);
427 }
428 computePath(completePath, trace, outputPath.getOutput());
Andrea Campanella8292ba62018-01-31 16:43:23 +0100429 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100430
431 } else {
432 //No links means that the packet gets dropped.
433 log.warn("No links out of {}", cp);
434 computePath(completePath, trace, cp);
435 trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
Andrea Campanellae4084402017-12-15 15:27:31 +0100436 }
437 }
438 return trace;
439 }
440
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100441
Andrea Campanellae4084402017-12-15 15:27:31 +0100442 /**
Andrea Campanellae6798012018-02-06 15:46:52 +0100443 * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
444 * If ONOS applied a vlan we remove it.
445 *
446 * @param outputPath the output
447 * @param trace the trace we are building
448 */
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100449
Andrea Campanellae6798012018-02-06 15:46:52 +0100450 private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
451
452 VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
453 VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
454
455 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
456
457 Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
458 //removing the final vlanId
459 finalCriteria.remove(finalVid);
460 Builder packetUpdated = DefaultTrafficSelector.builder();
461 finalCriteria.forEach(packetUpdated::add);
462 //Initial was none so we set it to that
463 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
464 //Update final packet
465 outputPath.setFinalPacket(packetUpdated.build());
466 }
467 }
468
469 /**
Andrea Campanella4c6170a2018-01-17 16:34:51 +0100470 * Checks if the device has other outputs than the given connect point.
471 *
472 * @param inDeviceId the device
473 * @param trace the trace we are building
474 * @param cp an output connect point
475 * @return true if the device has other outputs.
476 */
477 private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
478 return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
479 return !groupsInDevice.getOutput().equals(cp);
480 }).count() > 0;
481 }
482
483 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100484 * Checks if the path contains the device.
485 *
486 * @param completePath the path
487 * @param deviceId the device to check
488 * @return true if the path contains the device
489 */
490 //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
491 private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
492 for (ConnectPoint cp : completePath) {
493 if (cp.deviceId().equals(deviceId)) {
494 return true;
495 }
496 }
497 return false;
498 }
499
500 /**
501 * Gets the hosts for the given initial packet.
502 *
503 * @param trace the trace we are building
504 * @return set of the hosts we are trying to reach
505 */
506 private Set<Host> getHosts(StaticPacketTrace trace) {
507 IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
508 .getCriterion(Criterion.Type.IPV4_DST));
509 IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
510 .getCriterion(Criterion.Type.IPV6_DST));
511 Set<Host> hosts = new HashSet<>();
512 if (ipv4Criterion != null) {
513 hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
514 }
515 if (ipv6Criterion != null) {
516 hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
517 }
518 EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
519 .getCriterion(Criterion.Type.ETH_DST));
520 if (ethCriterion != null) {
521 hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
522 }
523 return hosts;
524 }
525
526 /**
527 * Computes the list of traversed connect points.
528 *
529 * @param completePath the list of devices
530 * @param trace the trace we are building
531 * @param output the final output connect point
532 */
533 private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
534 List<ConnectPoint> traverseList = new ArrayList<>();
535 if (!completePath.contains(trace.getInitialConnectPoint())) {
536 traverseList.add(trace.getInitialConnectPoint());
537 }
538 traverseList.addAll(completePath);
539 if (output != null && !completePath.contains(output)) {
540 traverseList.add(output);
541 }
542 trace.addCompletePath(traverseList);
Andrea Campanellae4084402017-12-15 15:27:31 +0100543 }
544
545 /**
546 * Traces the packet inside a device starting from an input connect point.
547 *
548 * @param trace the trace we are building
549 * @param packet the packet we are tracing
550 * @param in the input connect point.
551 * @return updated trace
552 */
553 private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
Andrea Campanellab022b5e2018-01-31 14:59:03 +0100554
555 //we already traversed this device.
556 if (trace.getGroupOuputs(in.deviceId()) != null) {
557 log.debug("Trace already contains device and given outputs");
558 return trace;
559 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100560 log.debug("Packet {} coming in from {}", packet, in);
Andrea Campanella17d45192018-01-18 17:11:42 +0100561
562 //if device is not available exit here.
563 if (!deviceService.isAvailable(in.deviceId())) {
564 trace.addResultMessage("Device is offline " + in.deviceId());
565 return trace;
566 }
567
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100568 //handle when the input is the controller
569 //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
570 // a packet in from the controller will not actually traverse the pipeline and have no such notion
571 // as the input port.
572 if (in.port().equals(PortNumber.CONTROLLER)) {
573 StaticPacketTrace outputTrace = inputFromController(trace, in);
574 if (outputTrace != null) {
575 return trace;
576 }
577 }
578
Andrea Campanellae4084402017-12-15 15:27:31 +0100579 List<FlowEntry> flows = new ArrayList<>();
580 List<FlowEntry> outputFlows = new ArrayList<>();
581
Andrea Campanella8292ba62018-01-31 16:43:23 +0100582 List<Instruction> deferredInstructions = new ArrayList<>();
583
Andrea Campanellae4084402017-12-15 15:27:31 +0100584 FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
585 if (nextTableIdEntry == null) {
586 trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
587 return trace;
588 }
589 TableId tableId = nextTableIdEntry.table();
590 FlowEntry flowEntry;
591 boolean output = false;
592 while (!output) {
593 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
594 //get the rule that matches the incoming packet
595 flowEntry = matchHighestPriority(packet, in, tableId);
596 log.debug("Found Flow Entry {}", flowEntry);
597
598 boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
599 .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
600
601 //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
602 if (flowEntry == null && isOfdpaHardware) {
603 log.debug("Ofdpa Hw setup, no flow rule means table miss");
604
Andrea Campanellae4084402017-12-15 15:27:31 +0100605 if (((IndexTableId) tableId).id() == 27) {
606 //Apparently a miss but Table 27 on OFDPA is a fixed table
607 packet = handleOfdpa27FixedTable(trace, packet);
608 }
609
610 //Finding next table to go In case of miss
611 nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
612 log.debug("Next table id entry {}", nextTableIdEntry);
613
614 //FIXME find better solution that enable granularity greater than 0 or all rules
615 //(another possibility is max tableId)
616 if (nextTableIdEntry == null && flows.size() == 0) {
Andrea Campanella09eec852018-02-05 19:39:25 +0100617 trace.addResultMessage("No matching flow rules for device " + in.deviceId() + ". Aborting");
Andrea Campanellae4084402017-12-15 15:27:31 +0100618 return trace;
619
620 } else if (nextTableIdEntry == null) {
621 //Means that no more flow rules are present
622 output = true;
623
624 } else if (((IndexTableId) tableId).id() == 20) {
625 //if the table is 20 OFDPA skips to table 50
626 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
627 tableId = IndexTableId.of(50);
628
629 } else {
630 tableId = nextTableIdEntry.table();
631 }
632
Andrea Campanellae4084402017-12-15 15:27:31 +0100633 } else if (flowEntry == null) {
634 trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
635 in.deviceId() + ". Dropping");
636 return trace;
637 } else {
Andrea Campanella94c594a2018-02-06 18:58:40 +0100638
Andrea Campanellae4084402017-12-15 15:27:31 +0100639 //IF the table has a transition
640 if (flowEntry.treatment().tableTransition() != null) {
641 //update the next table we transitions to
642 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
643 log.debug("Flow Entry has transition to table Id {}", tableId);
644 flows.add(flowEntry);
645 } else {
646 //table has no transition so it means that it's an output rule if on the last table
647 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
648 flows.add(flowEntry);
649 outputFlows.add(flowEntry);
650 output = true;
651 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100652 //update the packet according to the immediate actions of this flow rule.
653 packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
654
655 //save the deferred rules for later
656 deferredInstructions.addAll(flowEntry.treatment().deferred());
657
658 //If the flow requires to clear deferred actions we do so for all the ones we encountered.
659 if (flowEntry.treatment().clearedDeferred()) {
660 deferredInstructions.clear();
661 }
662
Andrea Campanella94c594a2018-02-06 18:58:40 +0100663 //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
664 if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
665
666 //Let's get the packet vlanId instruction
667 VlanIdCriterion packetVlanIdCriterion =
668 (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
669
670 //Let's get the flow entry vlan mod instructions
671 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
672 .immediate().stream()
673 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
674 .findFirst().orElse(null);
675
676 //If the entry modVlan is not null we need to make sure that the packet has been updated and there
677 // is a flow rule that matches on same criteria and with updated vlanId
678 if (entryModVlanIdInstruction != null) {
679
680 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
681 packetVlanIdCriterion, entryModVlanIdInstruction);
682
683 //We found the flow that we expected
684 if (secondVlanFlow != null) {
685 flows.add(secondVlanFlow);
686 } else {
687 trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
688 return trace;
689 }
690 }
691
692 }
693
Andrea Campanellae4084402017-12-15 15:27:31 +0100694 }
695 }
696
697 //Creating a modifiable builder for the output packet
Andrea Campanella8292ba62018-01-31 16:43:23 +0100698 Builder builder = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100699 packet.criteria().forEach(builder::add);
Andrea Campanella8292ba62018-01-31 16:43:23 +0100700
Andrea Campanellae4084402017-12-15 15:27:31 +0100701 //Adding all the flows to the trace
Andrea Campanella54923d62018-01-23 12:46:04 +0100702 trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
Andrea Campanellae4084402017-12-15 15:27:31 +0100703
Andrea Campanellae4084402017-12-15 15:27:31 +0100704 List<PortNumber> outputPorts = new ArrayList<>();
705
Andrea Campanella54923d62018-01-23 12:46:04 +0100706 //TODO optimization
Andrea Campanella54923d62018-01-23 12:46:04 +0100707 //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
708 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
709 .allInstructions().stream().filter(instruction -> instruction.type()
710 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
Andrea Campanellae4084402017-12-15 15:27:31 +0100711
Andrea Campanella54923d62018-01-23 12:46:04 +0100712 if (outputFlowEntries.size() > 1) {
713 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
714 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100715 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100716
717 if (outputFlowEntries.size() == 1) {
718
719 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
720 .allInstructions().stream()
721 .filter(instruction -> {
722 return instruction.type().equals(Instruction.Type.OUTPUT);
723 }).findFirst().get();
724
725 //FIXME using GroupsInDevice for output even if flows.
726 buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
727
728 }
729 log.debug("Handling Groups");
730 //Analyze Groups
731 List<Group> groups = new ArrayList<>();
732
733 Collection<FlowEntry> nonOutputFlows = flows;
734 nonOutputFlows.removeAll(outputFlowEntries);
735
Andrea Campanella8292ba62018-01-31 16:43:23 +0100736 //Handling groups pointed at by immediate instructions
Andrea Campanella54923d62018-01-23 12:46:04 +0100737 for (FlowEntry entry : flows) {
Andrea Campanella8292ba62018-01-31 16:43:23 +0100738 getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
Andrea Campanella54923d62018-01-23 12:46:04 +0100739 entry.deviceId(), builder, outputPorts, in);
740 }
Andrea Campanella8292ba62018-01-31 16:43:23 +0100741
742 //If we have deferred instructions at this point we handle them.
743 if (deferredInstructions.size() > 0) {
744 builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
745
746 }
Andrea Campanella54923d62018-01-23 12:46:04 +0100747 packet = builder.build();
Andrea Campanella54923d62018-01-23 12:46:04 +0100748
Andrea Campanella94c594a2018-02-06 18:58:40 +0100749 log.debug("Output Packet {}", packet);
Andrea Campanellae4084402017-12-15 15:27:31 +0100750 return trace;
751 }
752
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100753 /**
754 * Handles the specific case where the Input is the controller.
755 * Note that the in port is used as a convenience to store the port of the controller even if the packet in
756 * from a controller should not have a physical input port. The in port from the Controller is used to make sure
757 * the flood to all active physical ports of the device.
758 *
759 * @param trace the trace
760 * @param in the controller port
761 * @return the augmented trace.
762 */
763 private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
764 EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
765 .getCriterion(Criterion.Type.ETH_TYPE);
766 //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
767 if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
768 || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
769 //get the active ports
770 List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
771 .filter(Port::isEnabled)
772 .collect(Collectors.toList());
773 //build an output from each one
774 enabledPorts.forEach(port -> {
775 GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
776 ImmutableList.of(), trace.getInitialPacket());
777 trace.addGroupOutputPath(in.deviceId(), output);
778 });
779 return trace;
780 }
781 return null;
782 }
783
Andrea Campanella94c594a2018-02-06 18:58:40 +0100784 private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
785 return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
786 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
787 && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
788 .vlanId().equals(VlanId.NONE);
789 }
790
791 /**
792 * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
793 * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
794 * second to transition to following table
795 *
796 * @param packet the incoming packet
797 * @param in the input connect point
798 * @param packetVlanIdCriterion the vlan criterion from the packet
799 * @param entryModVlanIdInstruction the entry vlan instruction
800 * @return the second flow entry that matched
801 */
802 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
803 VlanIdCriterion packetVlanIdCriterion,
804 ModVlanIdInstruction entryModVlanIdInstruction) {
805 FlowEntry secondVlanFlow = null;
806 //Check the packet has been update from the first rule.
807 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
808 //find a rule on the same table that matches the vlan and
809 // also all the other elements of the flow such as input port
810 secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
811 .stream()
812 .filter(entry -> {
813 return entry.table().equals(IndexTableId.of(10));
814 })
815 .filter(entry -> {
816 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
817 .getCriterion(Criterion.Type.VLAN_VID);
818 return criterion != null && match(packet, entry)
819 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
820 }).findFirst().orElse(null);
821
822 }
823 return secondVlanFlow;
824 }
825
Andrea Campanella8292ba62018-01-31 16:43:23 +0100826
Andrea Campanellae4084402017-12-15 15:27:31 +0100827 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100828 * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
829 *
830 * @param packet the incoming packet
831 * @return the updated packet
832 */
833 private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
834 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
835 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
836 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
837
838 //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
839 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
840 .equals(EtherType.MPLS_UNICAST.ethType())) {
Andrea Campanella3970e472018-01-25 16:44:04 +0100841 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
Andrea Campanellae4084402017-12-15 15:27:31 +0100842 Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
843 .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
844 //FIXME what do we use as L3_Unicast mpls Label ?
Andrea Campanella3970e472018-01-25 16:44:04 +0100845 //translateInstruction(builder, ethInstruction);
Andrea Campanellae4084402017-12-15 15:27:31 +0100846 builder.add(ethInstruction);
847 }
848 packet = updatePacket(packet, builder.build()).build();
849 return packet;
850 }
851
852 /**
853 * Finds the flow entry with the minimun next table Id.
854 *
855 * @param deviceId the device to search
856 * @param currentId the current id. the search will use this as minimum
857 * @return the flow entry with the minimum table Id after the given one.
858 */
859 private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
860
861 final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
862
863 return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
864 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
865 }
866
Andrea Campanella8292ba62018-01-31 16:43:23 +0100867 private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
868 ConnectPoint in, List<Instruction> deferredInstructions,
869 List<PortNumber> outputPorts, List<Group> groups) {
870
871 //Update the packet with the deferred instructions
872 Builder builder = updatePacket(packet, deferredInstructions);
873
874 //Gather any output instructions from the deferred instruction
875 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
876 return instruction.type().equals(Instruction.Type.OUTPUT);
877 }).collect(Collectors.toList());
878
879 //We are considering deferred instructions from flows, there can only be one output.
880 if (outputFlowInstruction.size() > 1) {
881 trace.addResultMessage("More than one flow rule with OUTPUT instruction");
882 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
883 }
884 //If there is one output let's go through that
885 if (outputFlowInstruction.size() == 1) {
886 buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
887 ImmutableList.of());
888 }
889 //If there is no output let's see if there any deferred instruction point to groups.
890 if (outputFlowInstruction.size() == 0) {
891 getGroupsFromInstructions(trace, groups, deferredInstructions,
892 in.deviceId(), builder, outputPorts, in);
893 }
894 return builder;
895 }
896
Andrea Campanellae4084402017-12-15 15:27:31 +0100897 /**
898 * Gets group information from instructions.
899 *
900 * @param trace the trace we are building
901 * @param groupsForDevice the set of groups for this device
902 * @param instructions the set of instructions we are searching for groups.
903 * @param deviceId the device we are considering
904 * @param builder the builder of the input packet
905 * @param outputPorts the output ports for that packet
906 */
907 private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
908 List<Instruction> instructions, DeviceId deviceId,
Andrea Campanella8292ba62018-01-31 16:43:23 +0100909 Builder builder, List<PortNumber> outputPorts,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100910 ConnectPoint in) {
Andrea Campanellae4084402017-12-15 15:27:31 +0100911 List<Instruction> groupInstructionlist = new ArrayList<>();
912 for (Instruction instruction : instructions) {
913 log.debug("Considering Instruction {}", instruction);
914 //if the instruction is not group we need to update the packet or add the output
915 //to the possible outputs for this packet
916 if (!instruction.type().equals(Instruction.Type.GROUP)) {
917 //if the instruction is not group we need to update the packet or add the output
918 //to the possible outputs for this packet
919 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100920 buildOutputFromDevice(trace, in, builder, outputPorts,
Andrea Campanella573d4b92018-02-19 17:03:46 +0100921 (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
922 //clearing the groups because we start from the top.
923 groupsForDevice.clear();
Andrea Campanellae4084402017-12-15 15:27:31 +0100924 } else {
925 builder = translateInstruction(builder, instruction);
926 }
927 } else {
928 //if the instuction is pointing to a group we need to get the group
929 groupInstructionlist.add(instruction);
930 }
931 }
932 //handle all the internal instructions pointing to a group.
933 for (Instruction instr : groupInstructionlist) {
934 GroupInstruction groupInstruction = (GroupInstruction) instr;
935 Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
936 return groupInternal.id().equals(groupInstruction.groupId());
937 }).findAny().orElse(null);
938 if (group == null) {
939 trace.addResultMessage("Null group for Instruction " + instr);
940 break;
941 }
Andrea Campanella573d4b92018-02-19 17:03:46 +0100942
Andrea Campanellae4084402017-12-15 15:27:31 +0100943 //Cycle in each of the group's buckets and add them to the groups for this Device.
944 for (GroupBucket bucket : group.buckets().buckets()) {
Andrea Campanella573d4b92018-02-19 17:03:46 +0100945
946 //add the group to the traversed groups
947 if (!groupsForDevice.contains(group)) {
948 groupsForDevice.add(group);
949 }
950
Andrea Campanellae4084402017-12-15 15:27:31 +0100951 getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100952 deviceId, builder, outputPorts, in);
Andrea Campanellae4084402017-12-15 15:27:31 +0100953 }
954 }
955 }
956
957 /**
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100958 * Check if the output is the input port, if so adds a dop result message, otherwise builds
959 * a possible output from this device.
960 *
961 * @param trace the trace
962 * @param in the input connect point
963 * @param builder the packet builder
964 * @param outputPorts the list of output ports for this device
965 * @param outputInstruction the output instruction
Andrea Campanella8292ba62018-01-31 16:43:23 +0100966 * @param groupsForDevice the groups we output from
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100967 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100968 private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100969 List<PortNumber> outputPorts, OutputInstruction outputInstruction,
970 List<Group> groupsForDevice) {
Andrea Campanella54923d62018-01-23 12:46:04 +0100971 ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
Andrea Campanella1445f7a2018-02-07 12:00:12 +0100972
973 //if the output is the input same we drop, except if the output is the controller
Andrea Campanella7d3cf652018-01-22 15:10:30 +0100974 if (output.equals(in)) {
975 trace.addResultMessage("Connect point out " + output + " is same as initial input " +
976 trace.getInitialConnectPoint());
977 } else {
978 trace.addGroupOutputPath(in.deviceId(),
979 new GroupsInDevice(output, groupsForDevice, builder.build()));
980 outputPorts.add(outputInstruction.port());
981 }
982 }
983
984 /**
Andrea Campanellae4084402017-12-15 15:27:31 +0100985 * Applies all give instructions to the input packet.
986 *
987 * @param packet the input packet
988 * @param instructions the set of instructions
989 * @return the packet with the applied instructions
990 */
Andrea Campanella8292ba62018-01-31 16:43:23 +0100991 private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
992 Builder newSelector = DefaultTrafficSelector.builder();
Andrea Campanellae4084402017-12-15 15:27:31 +0100993 packet.criteria().forEach(newSelector::add);
Andrea Campanella3970e472018-01-25 16:44:04 +0100994 //FIXME optimize
995 for (Instruction instruction : instructions) {
996 newSelector = translateInstruction(newSelector, instruction);
997 }
Andrea Campanellae4084402017-12-15 15:27:31 +0100998 return newSelector;
999 }
1000
1001 /**
1002 * Applies an instruction to the packet in the form of a selector.
1003 *
1004 * @param newSelector the packet selector
1005 * @param instruction the instruction to be translated
1006 * @return the new selector with the applied instruction
1007 */
Andrea Campanella8292ba62018-01-31 16:43:23 +01001008 private Builder translateInstruction(Builder newSelector, Instruction instruction) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001009 log.debug("Translating instruction {}", instruction);
Andrea Campanella3970e472018-01-25 16:44:04 +01001010 log.debug("New Selector {}", newSelector.build());
Andrea Campanellae4084402017-12-15 15:27:31 +01001011 //TODO add as required
1012 Criterion criterion = null;
1013 switch (instruction.type()) {
1014 case L2MODIFICATION:
1015 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
1016 switch (l2Instruction.subtype()) {
1017 case VLAN_ID:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001018 ModVlanIdInstruction vlanIdInstruction =
1019 (ModVlanIdInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001020 VlanId id = vlanIdInstruction.vlanId();
1021 criterion = Criteria.matchVlanId(id);
1022 break;
1023 case VLAN_POP:
1024 criterion = Criteria.matchVlanId(VlanId.NONE);
1025 break;
1026 case MPLS_PUSH:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001027 ModMplsHeaderInstruction mplsEthInstruction =
1028 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001029 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
1030 break;
1031 case MPLS_POP:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001032 ModMplsHeaderInstruction mplsPopInstruction =
1033 (ModMplsHeaderInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001034 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
Andrea Campanella3970e472018-01-25 16:44:04 +01001035
1036 //When popping MPLS we remove label and BOS
1037 TrafficSelector temporaryPacket = newSelector.build();
1038 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
Andrea Campanella8292ba62018-01-31 16:43:23 +01001039 Builder noMplsSelector = DefaultTrafficSelector.builder();
Andrea Campanella3970e472018-01-25 16:44:04 +01001040 temporaryPacket.criteria().stream().filter(c -> {
1041 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
1042 !c.type().equals(Criterion.Type.MPLS_BOS);
1043 }).forEach(noMplsSelector::add);
1044 newSelector = noMplsSelector;
1045 }
1046
Andrea Campanellae4084402017-12-15 15:27:31 +01001047 break;
1048 case MPLS_LABEL:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001049 ModMplsLabelInstruction mplsLabelInstruction =
1050 (ModMplsLabelInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001051 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
Andrea Campanella3970e472018-01-25 16:44:04 +01001052 newSelector.matchMplsBos(true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001053 break;
1054 case ETH_DST:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001055 ModEtherInstruction modEtherDstInstruction =
1056 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001057 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
1058 break;
1059 case ETH_SRC:
Andrea Campanella94c594a2018-02-06 18:58:40 +01001060 ModEtherInstruction modEtherSrcInstruction =
1061 (ModEtherInstruction) instruction;
Andrea Campanellae4084402017-12-15 15:27:31 +01001062 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
1063 break;
1064 default:
1065 log.debug("Unsupported L2 Instruction");
1066 break;
1067 }
1068 break;
1069 default:
1070 log.debug("Unsupported Instruction");
1071 break;
1072 }
1073 if (criterion != null) {
1074 log.debug("Adding criterion {}", criterion);
1075 newSelector.add(criterion);
1076 }
1077 return newSelector;
1078 }
1079
1080 /**
1081 * Finds the rule in the device that mathces the input packet and has the highest priority.
1082 *
1083 * @param packet the input packet
1084 * @param in the connect point the packet comes in from
1085 * @param tableId the table to search
1086 * @return the flow entry
1087 */
1088 private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
1089 //Computing the possible match rules.
1090 final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
1091 return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
1092 .stream()
1093 .filter(flowEntry -> {
1094 return flowEntry.table().equals(tableId);
1095 })
1096 .filter(flowEntry -> {
1097 return match(packet, flowEntry);
1098 }).max(comparator).orElse(null);
1099 }
1100
1101 /**
1102 * Matches the packet with the given flow entry.
1103 *
1104 * @param packet the packet to match
1105 * @param flowEntry the flow entry to match the packet against
1106 * @return true if the packet matches the flow.
1107 */
1108 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
Andrea Campanellae4084402017-12-15 15:27:31 +01001109 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
1110 Criterion.Type type = criterion.type();
Andrea Campanella128d9c62018-01-31 12:20:48 +01001111 //If the criterion has IP we need to do LPM to establish matching.
Andrea Campanellae4084402017-12-15 15:27:31 +01001112 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
1113 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001114 return matchIp(packet, (IPCriterion) criterion);
Andrea Campanellae4084402017-12-15 15:27:31 +01001115 //we check that the packet contains the criterion provided by the flow rule.
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001116 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
1117 return matchMac(packet, (EthCriterion) criterion, false);
1118 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
1119 return matchMac(packet, (EthCriterion) criterion, true);
Andrea Campanellae4084402017-12-15 15:27:31 +01001120 } else {
1121 return packet.criteria().contains(criterion);
1122 }
1123 });
Simon Hunt026a2872017-11-13 17:09:43 -08001124 }
Andrea Campanella4c6170a2018-01-17 16:34:51 +01001125
1126 /**
1127 * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
1128 *
1129 * @param packet the incoming packet
1130 * @param criterion the criterion to match
1131 * @return true if match
1132 */
1133 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
1134 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
1135 //if the packet does not have an IPv4 or IPv6 criterion we return true
1136 if (matchCriterion == null) {
1137 return false;
1138 }
1139 try {
1140 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
1141 Subnet subnet = Subnet.createInstance(criterion.ip().toString());
1142 return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
1143 } catch (UnknownHostException e) {
1144 return false;
1145 }
1146 }
1147
1148 /**
1149 * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
1150 *
1151 * @param packet the incoming packet
1152 * @param hitCriterion the criterion to match
1153 * @param dst true if we are checking DST MAC
1154 * @return true if match
1155 */
1156 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
1157 //Packet can have only one EthCriterion
1158 EthCriterion matchCriterion;
1159 if (dst) {
1160 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1161 return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
1162 criterion1.type().equals(Criterion.Type.ETH_DST);
1163 }).findFirst().orElse(null);
1164 } else {
1165 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
1166 return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
1167 criterion1.type().equals(Criterion.Type.ETH_SRC);
1168 }).findFirst().orElse(null);
1169 }
1170 //if the packet does not have an ETH criterion we return true
1171 if (matchCriterion == null) {
1172 return true;
1173 }
1174 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1175 return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
1176 }
Simon Hunt026a2872017-11-13 17:09:43 -08001177}