blob: c26902224e53aecfd5bc611338eff46f9cce380f [file] [log] [blame]
pierventre4b72c472020-05-22 09:42:31 -07001/*
2 * Copyright 2020-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.driver.traceable;
18
19import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Lists;
21import org.onlab.packet.EthType;
22import org.onlab.packet.IpPrefix;
23import org.onlab.packet.VlanId;
24import org.onosproject.core.GroupId;
25import org.onosproject.driver.pipeline.ofdpa.Ofdpa2Pipeline;
26import org.onosproject.driver.pipeline.ofdpa.OvsOfdpaPipeline;
27import org.onosproject.net.DeviceId;
28import org.onosproject.net.PipelineTraceableHitChain;
29import org.onosproject.net.ConnectPoint;
30import org.onosproject.net.DataPlaneEntity;
31import org.onosproject.net.PipelineTraceableInput;
32import org.onosproject.net.PipelineTraceableOutput;
33import org.onosproject.net.PortNumber;
34import org.onosproject.net.behaviour.PipelineTraceable;
35import org.onosproject.net.behaviour.Pipeliner;
36import org.onosproject.net.driver.AbstractHandlerBehaviour;
37import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.FlowEntry;
39import org.onosproject.net.flow.FlowRule;
40import org.onosproject.net.flow.IndexTableId;
41import org.onosproject.net.flow.TableId;
42import org.onosproject.net.flow.TrafficSelector;
43import org.onosproject.net.flow.criteria.Criteria;
44import org.onosproject.net.flow.criteria.Criterion;
45import org.onosproject.net.flow.criteria.EthCriterion;
46import org.onosproject.net.flow.criteria.EthTypeCriterion;
47import org.onosproject.net.flow.criteria.IPCriterion;
48import org.onosproject.net.flow.criteria.MetadataCriterion;
49import org.onosproject.net.flow.criteria.VlanIdCriterion;
50import org.onosproject.net.flow.instructions.Instruction;
51import org.onosproject.net.flow.instructions.Instructions;
52import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
53import org.onosproject.net.flow.instructions.L2ModificationInstruction;
54import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
55import org.onosproject.net.group.Group;
56import org.onosproject.net.group.GroupBucket;
57import org.slf4j.Logger;
58
59import java.util.ArrayList;
60import java.util.Comparator;
61import java.util.HashSet;
62import java.util.List;
63import java.util.Map;
64import java.util.Set;
65import java.util.stream.Collectors;
66
67import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.L2_FLOOD_TYPE;
68import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.L2_INTERFACE_TYPE;
69import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.L2_MULTICAST_TYPE;
70import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.L3_MULTICAST_TYPE;
71import static org.onosproject.driver.pipeline.ofdpa.OfdpaPipelineUtility.ACL_TABLE;
72import static org.onosproject.driver.pipeline.ofdpa.OfdpaPipelineUtility.BRIDGING_TABLE;
73import static org.onosproject.driver.pipeline.ofdpa.OfdpaPipelineUtility.MPLS_L3_TYPE_TABLE;
74import static org.onosproject.driver.pipeline.ofdpa.OfdpaPipelineUtility.MULTICAST_ROUTING_TABLE;
75import static org.onosproject.driver.pipeline.ofdpa.OfdpaPipelineUtility.TMAC_TABLE;
76import static org.onosproject.driver.pipeline.ofdpa.OfdpaPipelineUtility.VLAN_TABLE;
77
78import static org.slf4j.LoggerFactory.getLogger;
79
80/**
81 * Implements a driver behavior that enables a logical probe packet to traverse the device pipeline
82 * and to return dataplane entities that matched against the logical probe packet.
83 */
84public class OfdpaPipelineTraceable extends AbstractHandlerBehaviour implements PipelineTraceable {
85
86 private static final Logger log = getLogger(OfdpaPipelineTraceable.class);
87 // Behavior context
88 private DeviceId deviceId;
89 private String driverName;
90 // Utility
91 private final Comparator<FlowEntry> comparatorById = Comparator.comparing(
92 (FlowEntry f) -> ((IndexTableId) f.table()).id());
93 private final Comparator<FlowEntry> comparatorByPriority = Comparator.comparing(
94 FlowRule::priority);
95
96 @Override
97 public void init() {
98 this.deviceId = this.data().deviceId();
99 this.driverName = this.data().driver().name();
100 }
101
102 @Override
103 public PipelineTraceableOutput apply(PipelineTraceableInput input) {
104 PipelineTraceableOutput.Builder outputBuilder = PipelineTraceableOutput.builder();
105 log.debug("Current packet {} - applying flow tables", input.ingressPacket());
106 List<FlowEntry> outputFlows = new ArrayList<>();
107 List<Instruction> deferredInstructions = new ArrayList<>();
108 PipelineTraceableHitChain currentHitChain = PipelineTraceableHitChain.emptyHitChain();
109 TrafficSelector currentPacket = DefaultTrafficSelector.builder(input.ingressPacket()).build();
110
111 // Init step - find out the first table
112 int initialTableId = -1;
113 FlowEntry nextTableIdEntry = findNextTableIdEntry(initialTableId, input.flows());
114 if (nextTableIdEntry == null) {
115 currentHitChain.setEgressPacket(currentPacket);
116 currentHitChain.dropped();
117 return outputBuilder.appendToLog("No flow rules for device " + deviceId + ". Aborting")
118 .noFlows()
119 .addHitChain(currentHitChain)
120 .build();
121 }
122
123 // Iterates over the flow tables until the end of the pipeline
124 TableId tableId = nextTableIdEntry.table();
125 FlowEntry flowEntry;
126 boolean lastTable = false;
127 while (!lastTable) {
128 log.debug("Searching a Flow Entry on table {} for packet {}", tableId, currentPacket);
129
130 // Gets the rule that matches the incoming packet
131 flowEntry = matchHighestPriority(currentPacket, tableId, input.flows());
132 log.debug("Found Flow Entry {}", flowEntry);
133
134 // If the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
135 if (flowEntry == null && isHardwareSwitch()) {
136 log.debug("Ofdpa Hw setup, no flow rule means table miss");
137
138 if (((IndexTableId) tableId).id() == MPLS_L3_TYPE_TABLE) {
139 // Apparently a miss but Table 27 on OFDPA is a fixed table
140 currentPacket = handleOfdpa27FixedTable(input.ingressPacket(), currentPacket);
141 // The nextTable should be ACL
142 tableId = IndexTableId.of(ACL_TABLE - 1);
143 }
144
145 // Finding next table to go In case of miss
146 nextTableIdEntry = findNextTableIdEntry(((IndexTableId) tableId).id(), input.flows());
147 log.debug("Next table id entry {}", nextTableIdEntry);
148 // FIXME Find better solution that enable granularity greater than 0 or all rules
149 // (another possibility is max tableId)
150 if (nextTableIdEntry == null && currentHitChain.getHitChain().size() == 0) {
151 currentHitChain.setEgressPacket(currentPacket);
152 currentHitChain.dropped();
153 return outputBuilder.appendToLog("No flow rules for device " + deviceId + ". Aborting")
154 .noFlows()
155 .addHitChain(currentHitChain)
156 .build();
157
158 } else if (nextTableIdEntry == null) {
159 // Means that no more flow rules are present
160 lastTable = true;
161
162 } else if (((IndexTableId) tableId).id() == TMAC_TABLE) {
163 // If the table is 20 OFDPA skips to table 50
164 log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
165 tableId = IndexTableId.of(BRIDGING_TABLE);
166
167 } else if (((IndexTableId) tableId).id() == MULTICAST_ROUTING_TABLE) {
168 // If the table is 40 OFDPA skips to table 60
169 log.debug("A miss on Table 40 on OFDPA means that we skip directly to table 60");
170 tableId = IndexTableId.of(ACL_TABLE);
171 } else {
172 tableId = nextTableIdEntry.table();
173 }
174
175 } else if (flowEntry == null) {
176 currentHitChain.setEgressPacket(currentPacket);
177 currentHitChain.dropped();
178 return outputBuilder.appendToLog("Packet has no match on table " + tableId
179 + " in device " + deviceId + ". Dropping")
180 .noFlows()
181 .addHitChain(currentHitChain)
182 .build();
183 } else {
184
185 // If the table has a transition
186 if (flowEntry.treatment().tableTransition() != null) {
187 // Updates the next table we transitions to
188 tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
189 log.debug("Flow Entry has transition to table Id {}", tableId);
190 currentHitChain.addDataPlaneEntity(new DataPlaneEntity(flowEntry));
191 } else {
192 // Table has no transition so it means that it's an output rule if on the last table
193 log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
194 currentHitChain.addDataPlaneEntity(new DataPlaneEntity(flowEntry));
195 outputFlows.add(flowEntry);
196 lastTable = true;
197 }
198
199 // Updates the packet according to the immediate actions of this flow rule.
200 currentPacket = updatePacket(currentPacket, flowEntry.treatment().immediate()).build();
201
202 // Saves the deferred rules for later maintaining the order
203 deferredInstructions.addAll(flowEntry.treatment().deferred());
204
205 // If the flow requires to clear deferred actions we do so for all the ones we encountered.
206 if (flowEntry.treatment().clearedDeferred()) {
207 deferredInstructions.clear();
208 }
209
210 // On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
211 if (shouldMatchSecondVlanFlow(flowEntry)) {
212
213 // Let's get the packet vlanId instruction
214 VlanIdCriterion packetVlanIdCriterion =
215 (VlanIdCriterion) currentPacket.getCriterion(Criterion.Type.VLAN_VID);
216
217 // Let's get the flow entry vlan mod instructions
218 ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
219 .immediate().stream()
220 .filter(instruction -> instruction instanceof ModVlanIdInstruction)
221 .findFirst().orElse(null);
222
223 // If the entry modVlan is not null we need to make sure that the packet has been updated and there
224 // is a flow rule that matches on same criteria and with updated vlanId
225 if (entryModVlanIdInstruction != null) {
226
227 FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(currentPacket,
228 packetVlanIdCriterion, entryModVlanIdInstruction, input.flows());
229
230 // We found the flow that we expected
231 if (secondVlanFlow != null) {
232 currentHitChain.addDataPlaneEntity(new DataPlaneEntity(secondVlanFlow));
233 } else {
234 currentHitChain.setEgressPacket(currentPacket);
235 currentHitChain.dropped();
236 return outputBuilder.appendToLog("Missing forwarding rule for tagged"
237 + " packet on " + deviceId)
238 .noFlows()
239 .addHitChain(currentHitChain)
240 .build();
241 }
242 }
243 }
244 }
245 }
246
247 // Creating a modifiable builder for the egress packet
248 TrafficSelector.Builder egressPacket = DefaultTrafficSelector.builder(currentPacket);
249
250 log.debug("Current packet {} - applying output flows", currentPacket);
251 // Handling output flows which basically means handling output to controller.
252 // OVS and OFDPA have both immediate -> OUTPUT:CONTROLLER. Theoretically there is no
253 // need to reflect the updates performed on the packets and on the chain.
254 List<PortNumber> outputPorts = new ArrayList<>();
255 handleOutputFlows(currentPacket, outputFlows, egressPacket, outputPorts, currentHitChain,
256 outputBuilder, input.ingressPacket());
257
258 // Immediate instructions
259 log.debug("Current packet {} - applying immediate instructions", currentPacket);
260 // Handling immediate instructions which basically means handling output to controller.
261 // OVS has immediate -> group -> OUTPUT:CONTROLLER.
262 List<DataPlaneEntity> entries = ImmutableList.copyOf(currentHitChain.getHitChain());
263 // Go to the next step - using a copy of the egress packet and of the hit chain
264 PipelineTraceableHitChain newHitChain = PipelineTraceableHitChain.emptyHitChain();
265 currentHitChain.getHitChain().forEach(newHitChain::addDataPlaneEntity);
266 TrafficSelector.Builder newEgressPacket = DefaultTrafficSelector.builder(egressPacket.build());
267 for (DataPlaneEntity entry : entries) {
268 flowEntry = entry.getFlowEntry();
269 if (flowEntry != null) {
270 getGroupsFromInstructions(input.groups(), flowEntry.treatment().immediate(), newEgressPacket,
271 outputPorts, newHitChain, outputBuilder, input, false);
272 }
273 }
274
275 // Deferred instructions
276 log.debug("Current packet {} - applying deferred instructions", egressPacket.build());
277 // If we have deferred instructions at this point we handle them.
278 // Here, we are basically handling the normal forwarding scenarios that
279 // always happen through deferred:group. Here we don't care about the
280 // egress packet and of the hit chain. This is the last step.
281 if (deferredInstructions.size() > 0) {
282 handleDeferredActions(egressPacket.build(), input.groups(), deferredInstructions, outputPorts,
283 currentHitChain, outputBuilder, input);
284 }
285
286 // If there are no outputs - packet is dropped
287 // Let's store the partial hit chain and set a message
288 if (outputPorts.isEmpty()) {
289 currentHitChain.setEgressPacket(egressPacket.build());
290 currentHitChain.dropped();
291 outputBuilder.appendToLog("Packet has no output in device " + deviceId + ". Dropping")
292 .dropped()
293 .addHitChain(currentHitChain);
294 }
295
296 // Done!
297 return outputBuilder.build();
298 }
299
300 // Finds the flow entry with the minimum next table Id.
301 private FlowEntry findNextTableIdEntry(int currentId, List<FlowEntry> flows) {
302 return flows.stream()
303 .filter(f -> ((IndexTableId) f.table()).id() > currentId)
304 .min(comparatorById).orElse(null);
305 }
306
307 // Finds the rule in the device that matches the input packet and has the highest priority.
308 // TODO Candidate for an AbstractBehavior implementation
309 private FlowEntry matchHighestPriority(TrafficSelector packet, TableId tableId, List<FlowEntry> flows) {
310 //Computing the possible match rules.
311 return flows.stream()
312 .filter(flowEntry -> flowEntry.table().equals(tableId))
313 .filter(flowEntry -> match(packet, flowEntry))
314 .max(comparatorByPriority).orElse(null);
315 }
316
317 // Matches the packet with the given flow entry
318 // TODO Candidate for an AbstractBehavior implementation
319 private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
320 return flowEntry.selector().criteria().stream().allMatch(criterion -> {
321 Criterion.Type type = criterion.type();
322 //If the criterion has IP we need to do LPM to establish matching.
323 if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
324 type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
325 return matchIp(packet, (IPCriterion) criterion);
326 //we check that the packet contains the criterion provided by the flow rule.
327 } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
328 return matchMac(packet, (EthCriterion) criterion, false);
329 } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
330 return matchMac(packet, (EthCriterion) criterion, true);
331 } else {
332 return packet.criteria().contains(criterion);
333 }
334 });
335 }
336
337 // Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion
338 // TODO Candidate for an AbstractBehavior implementation
339 private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
340 IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
341 // if the packet does not have an IPv4 or IPv6 criterion we return true
342 if (matchCriterion == null) {
343 return false;
344 }
345 log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
346 IpPrefix subnet = criterion.ip();
347 return subnet.contains(matchCriterion.ip().address());
348 }
349
350 // Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion
351 // TODO Candidate for an AbstractBehavior implementation
352 private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
353 //Packet can have only one EthCriterion
354 EthCriterion matchCriterion;
355 if (dst) {
356 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 ->
357 criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
358 criterion1.type().equals(Criterion.Type.ETH_DST))
359 .findFirst().orElse(null);
360 } else {
361 matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 ->
362 criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
363 criterion1.type().equals(Criterion.Type.ETH_SRC))
364 .findFirst().orElse(null);
365 }
366 //if the packet does not have an ETH criterion we return true
367 if (matchCriterion == null) {
368 return true;
369 }
370 log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
371 return matchCriterion.mac().inRange(hitCriterion.mac(), hitCriterion.mask());
372 }
373
374 // Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
375 private TrafficSelector handleOfdpa27FixedTable(TrafficSelector initialPacket, TrafficSelector packet) {
376 log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
377
378 Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
379 // T3 was using the initial packet of the trace - using the metadata in the packet to carry this info
380 Criterion metadataCriterion = initialPacket.getCriterion(Criterion.Type.METADATA);
381 ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
382
383 // If the packet comes in with the expected elements we update it as per OFDPA spec.
384 if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
385 .equals(EthType.EtherType.MPLS_UNICAST.ethType()) && metadataCriterion != null) {
386
387 // Get the metadata to restore the original ethertype
388 long ethType = ((MetadataCriterion) metadataCriterion).metadata();
389 //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
390 Instruction ethInstruction = Instructions.popMpls(EthType.EtherType.lookup((short) ethType).ethType());
391 //FIXME what do we use as L3_Unicast mpls Label ?
392 //translateInstruction(builder, ethInstruction);
393 builder.add(ethInstruction);
394
395 // Filtering out metadata
396 TrafficSelector.Builder currentPacketBuilder = DefaultTrafficSelector.builder();
397 packet.criteria().stream()
398 .filter(criterion -> criterion.type() != Criterion.Type.METADATA)
399 .forEach(currentPacketBuilder::add);
400 packet = currentPacketBuilder.build();
401 }
402 packet = updatePacket(packet, builder.build()).build();
403 return packet;
404 }
405
406 // Applies all give instructions to the input packet
407 private TrafficSelector.Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
408 TrafficSelector.Builder newSelector = DefaultTrafficSelector.builder(packet);
409
410 //FIXME optimize
411 for (Instruction instruction : instructions) {
412 newSelector = translateInstruction(newSelector, instruction);
413 }
414 return newSelector;
415 }
416
417 // Applies an instruction to the packet in the form of a selector
418 private TrafficSelector.Builder translateInstruction(TrafficSelector.Builder newSelector, Instruction instruction) {
419 log.debug("Translating instruction {}", instruction);
420 log.debug("New Selector {}", newSelector.build());
421 //TODO add as required
422 Criterion criterion = null;
423 if (instruction.type() == Instruction.Type.L2MODIFICATION) {
424 L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
425 switch (l2Instruction.subtype()) {
426 case VLAN_ID:
427 ModVlanIdInstruction vlanIdInstruction =
428 (ModVlanIdInstruction) instruction;
429 VlanId id = vlanIdInstruction.vlanId();
430 criterion = Criteria.matchVlanId(id);
431 break;
432 case VLAN_POP:
433 criterion = Criteria.matchVlanId(VlanId.NONE);
434 break;
435 case MPLS_PUSH:
436 L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
437 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
438 criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
439
440 // When pushing MPLS adding metadata to remember the original ethtype
441 if (isHardwareSwitch()) {
442 TrafficSelector temporaryPacket = newSelector.build();
443 Criterion ethCriterion = temporaryPacket.getCriterion(Criterion.Type.ETH_TYPE);
444 if (ethCriterion != null) {
445 TrafficSelector.Builder tempSelector = DefaultTrafficSelector.builder(temporaryPacket);
446 // Store the old ether type for the
447 tempSelector.matchMetadata(((EthTypeCriterion) ethCriterion).ethType().toShort());
448 newSelector = tempSelector;
449 }
450 }
451
452 break;
453 case MPLS_POP:
454 L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
455 (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
456 criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
457
458 //When popping MPLS we remove label and BOS
459 TrafficSelector temporaryPacket = newSelector.build();
460 if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
461 TrafficSelector.Builder noMplsSelector = DefaultTrafficSelector.builder();
462 temporaryPacket.criteria().stream().filter(c ->
463 !c.type().equals(Criterion.Type.MPLS_LABEL) &&
464 !c.type().equals(Criterion.Type.MPLS_BOS))
465 .forEach(noMplsSelector::add);
466 newSelector = noMplsSelector;
467 }
468
469 break;
470 case MPLS_LABEL:
471 L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
472 (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
473 criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
474 newSelector.matchMplsBos(true);
475 break;
476 case ETH_DST:
477 L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
478 (L2ModificationInstruction.ModEtherInstruction) instruction;
479 criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
480 break;
481 case ETH_SRC:
482 L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
483 (L2ModificationInstruction.ModEtherInstruction) instruction;
484 criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
485 break;
486 default:
487 log.debug("Unsupported L2 Instruction");
488 break;
489 }
490 } else {
491 log.debug("Unsupported Instruction");
492 }
493 if (criterion != null) {
494 log.debug("Adding criterion {}", criterion);
495 newSelector.add(criterion);
496 }
497 return newSelector;
498 }
499
500 // Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
501 // found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
502 // second to transition to following table
503 private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, VlanIdCriterion packetVlanIdCriterion,
504 ModVlanIdInstruction entryModVlanIdInstruction,
505 List<FlowEntry> flows) {
506 FlowEntry secondVlanFlow = null;
507 // Check the packet has been update from the first rule.
508 if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
509 // find a rule on the same table that matches the vlan and
510 // also all the other elements of the flow such as input port
511 secondVlanFlow = flows.stream()
512 .filter(entry -> entry.table().equals(IndexTableId.of(VLAN_TABLE)))
513 .filter(entry -> {
514 VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
515 .getCriterion(Criterion.Type.VLAN_VID);
516 return criterion != null && match(packet, entry)
517 && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
518 }).findFirst().orElse(null);
519
520 }
521 return secondVlanFlow;
522 }
523
524 // Handles output flows
525 private List<FlowEntry> handleOutputFlows(TrafficSelector currentPacket, List<FlowEntry> outputFlows,
526 TrafficSelector.Builder egressPacket, List<PortNumber> outputPorts,
527 PipelineTraceableHitChain currentHitChain,
528 PipelineTraceableOutput.Builder outputBuilder,
529 TrafficSelector initialPacket) {
530 // TODO optimization
531 // outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
532 List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
533 .allInstructions().stream().filter(instruction -> instruction.type()
534 .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
535
536 if (outputFlowEntries.size() > 1) {
537 outputBuilder.appendToLog("More than one flow rule with OUTPUT instruction");
538 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", currentPacket);
539 }
540
541 if (outputFlowEntries.size() == 1) {
542 OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
543 .allInstructions().stream()
544 .filter(instruction -> instruction.type().equals(Instruction.Type.OUTPUT))
545 .findFirst().get();
546 buildOutputFromDevice(egressPacket, outputPorts, outputInstruction, currentHitChain,
547 outputBuilder, initialPacket, false);
548 }
549
550 return outputFlowEntries;
551 }
552
553 // Builds a possible output from this device
554 private void buildOutputFromDevice(TrafficSelector.Builder egressPacket,
555 List<PortNumber> outputPorts,
556 OutputInstruction outputInstruction,
557 PipelineTraceableHitChain currentHitChain,
558 PipelineTraceableOutput.Builder outputBuilder,
559 TrafficSelector initialPacket,
560 boolean dropped) {
561 // Store the output port for further processing
562 outputPorts.add(outputInstruction.port());
563 // Create the final hit chain from the current one (deep copy)
564 ConnectPoint outputPort = new ConnectPoint(deviceId, outputInstruction.port());
565 PipelineTraceableHitChain finalHitChain = new PipelineTraceableHitChain(outputPort,
566 Lists.newArrayList(currentHitChain.getHitChain()),
567 egressPacket.build());
568 // Dropped early
569 if (dropped) {
570 log.debug("Packet {} has been dropped", egressPacket.build());
571 } else {
572 finalHitChain.pass();
573 }
574 if (outputPort.port().equals(PortNumber.CONTROLLER)) {
575 handleVlanToController(finalHitChain, initialPacket);
576 }
577 // If there is already a chain do not add a copy
578 outputBuilder.addHitChain(finalHitChain);
579 }
580
581 // If the initial packet comes tagged with a Vlan we output it with that to ONOS.
582 // If ONOS applied a vlan we remove it.
583 // TODO Candidate for an AbstractBehavior implementation
584 private void handleVlanToController(PipelineTraceableHitChain currentHitChain, TrafficSelector initialPacket) {
585
586 VlanIdCriterion initialVid = (VlanIdCriterion) initialPacket
587 .getCriterion(Criterion.Type.VLAN_VID);
588 VlanIdCriterion finalVid = (VlanIdCriterion) currentHitChain.getEgressPacket()
589 .getCriterion(Criterion.Type.VLAN_VID);
590
591 if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
592 Set<Criterion> finalCriteria = new HashSet<>(currentHitChain.getEgressPacket().criteria());
593 //removing the final vlanId
594 finalCriteria.remove(finalVid);
595 TrafficSelector.Builder packetUpdated = DefaultTrafficSelector.builder();
596 finalCriteria.forEach(packetUpdated::add);
597 //Initial was none so we set it to that
598 packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
599 //Update final packet
600 currentHitChain.setEgressPacket(packetUpdated.build());
601 }
602 }
603
604 // Gets group information from instructions.
605 private void getGroupsFromInstructions(Map<GroupId, Group> groups, List<Instruction> instructions,
606 TrafficSelector.Builder egressPacket, List<PortNumber> outputPorts,
607 PipelineTraceableHitChain currentHitChain,
608 PipelineTraceableOutput.Builder outputBuilder,
609 PipelineTraceableInput input,
610 boolean dropped) {
611
612 List<Instruction> groupInstructionlist = new ArrayList<>();
613 // sort instructions according to priority (larger Instruction.Type ENUM constant first)
614 // which enables to treat other actions before the OUTPUT action
615 // TODO improve the priority scheme according to the OpenFlow ActionSet spec
616 List<Instruction> instructionsSorted = new ArrayList<>();
617 instructionsSorted.addAll(instructions);
618 instructionsSorted.sort((instr1, instr2) ->
619 Integer.compare(instr2.type().ordinal(), instr1.type().ordinal()));
620
621 // Handles first all non-group instructions
622 for (Instruction instruction : instructionsSorted) {
623 log.debug("Considering Instruction {}", instruction);
624 // if the instruction is not group we need to update the packet or add the output
625 // to the possible outputs for this packet
626 if (!instruction.type().equals(Instruction.Type.GROUP)) {
627 // FIXME ?? if the instruction is not group we need to update the packet
628 // or add the output to the possible outputs for this packet
629 if (instruction.type().equals(Instruction.Type.OUTPUT)) {
630 buildOutputFromDevice(egressPacket, outputPorts, (OutputInstruction) instruction,
631 currentHitChain, outputBuilder, input.ingressPacket(), dropped);
632 } else {
633 egressPacket = translateInstruction(egressPacket, instruction);
634 }
635 } else {
636 // Store for later if the instruction is pointing to a group
637 groupInstructionlist.add(instruction);
638 }
639 }
640
641 // handle all the internal instructions pointing to a group.
642 for (Instruction instr : groupInstructionlist) {
643 Instructions.GroupInstruction groupInstruction = (Instructions.GroupInstruction) instr;
644 Group group = groups.get(groupInstruction.groupId());
645
646 // group does not exist in the dataplane
647 if (group == null) {
648 currentHitChain.setEgressPacket(egressPacket.build());
649 currentHitChain.dropped();
650 outputBuilder.appendToLog("Null group for Instruction " + instr)
651 .noGroups()
652 .addHitChain(currentHitChain);
653 break;
654 }
655
656 log.debug("Analyzing group {}", group.id());
657
658 // group is there but there are no members/buckets
659 if (group.buckets().buckets().size() == 0) {
660 // add the group to the traversed groups
661 currentHitChain.addDataPlaneEntity(new DataPlaneEntity(group));
662 currentHitChain.setEgressPacket(egressPacket.build());
663 currentHitChain.dropped();
664 outputBuilder.appendToLog("Group " + group.id() + " has no buckets")
665 .noMembers()
666 .addHitChain(currentHitChain);
667 break;
668 }
669
670 PipelineTraceableHitChain newHitChain;
671 TrafficSelector.Builder newEgressPacket;
672 // Cycle in each of the group's buckets and add them to the groups for this Device.
673 for (GroupBucket bucket : group.buckets().buckets()) {
674
675 // add the group to the traversed groups
676 currentHitChain.addDataPlaneEntity(new DataPlaneEntity(group));
677
678 // Go to the next step - using a copy of the egress packet and of the hit chain
679 newHitChain = PipelineTraceableHitChain.emptyHitChain();
680 currentHitChain.getHitChain().forEach(newHitChain::addDataPlaneEntity);
681 newEgressPacket = DefaultTrafficSelector.builder(egressPacket.build());
682 getGroupsFromInstructions(groups, bucket.treatment().allInstructions(), newEgressPacket,
683 outputPorts, newHitChain, outputBuilder, input,
684 dropped | isDropped(group.id(), bucket, input.ingressPort()));
685 }
686 }
687 }
688
689 private boolean isDropped(GroupId groupId, GroupBucket bucket, ConnectPoint ingressPort) {
690 log.debug("Verify if the packet has to be dropped by the input port {}",
691 ingressPort);
692 // if It is not a l2 flood group and l2/l3 mcast skip
693 int maskedId = groupId.id() & 0xF0000000;
694 if (maskedId != L2_FLOOD_TYPE && maskedId != L2_MULTICAST_TYPE &&
695 maskedId != L3_MULTICAST_TYPE) {
696 return false;
697 }
698 // Verify if the bucket points to the ingress port
699 Instructions.GroupInstruction groupInstruction;
700 for (Instruction instr : bucket.treatment().allInstructions()) {
701 if (instr.type().equals(Instruction.Type.GROUP)) {
702 groupInstruction = (Instructions.GroupInstruction) instr;
703 // FIXME According to the OFDPA spec for L3 MCAST if the VLAN is changed packet is not dropped
704 if ((groupInstruction.groupId().id() & 0xF0000000) == L2_INTERFACE_TYPE) {
705 return (groupInstruction.groupId().id() & 0x0000FFFF) == ingressPort.port().toLong();
706 }
707 }
708 }
709 return false;
710 }
711
712 // Handles deferred instructions taken from the flows
713 private void handleDeferredActions(TrafficSelector egressPacket, Map<GroupId, Group> groups,
714 List<Instruction> deferredInstructions, List<PortNumber> outputPorts,
715 PipelineTraceableHitChain currentHitChain,
716 PipelineTraceableOutput.Builder outputBuilder,
717 PipelineTraceableInput input) {
718 // Update the packet with the deferred instructions
719 TrafficSelector.Builder newEgressPacket = updatePacket(egressPacket, deferredInstructions);
720
721 //Gather any output instructions from the deferred instruction
722 List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction ->
723 instruction.type().equals(Instruction.Type.OUTPUT))
724 .collect(Collectors.toList());
725
726 //We are considering deferred instructions from flows, there can only be one output.
727 if (outputFlowInstruction.size() > 1) {
728 outputBuilder.appendToLog("More than one flow rule with OUTPUT instruction");
729 log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", egressPacket);
730 }
731
732 // If there is one output let's go through that. No need to make a copy
733 // of the egress packet and of the current hit chain.
734 if (outputFlowInstruction.size() == 1) {
735 buildOutputFromDevice(newEgressPacket, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
736 currentHitChain, outputBuilder, input.ingressPacket(), false);
737 }
738
739 // If there is no output let's see if there any deferred instruction point to groups.
740 // No need to make a copy of the egress packet and of the current chain.
741 if (outputFlowInstruction.size() == 0) {
742 getGroupsFromInstructions(groups, deferredInstructions, newEgressPacket, outputPorts,
743 currentHitChain, outputBuilder, input, false);
744 }
745 }
746
747 // Checks whether it is an hw device based on different means.
748 // throws an exception if the behavior has been used with wrong drivers
749 private boolean isHardwareSwitch() {
750 // Check if we are using ofdpa hw device by looking at the pipeliner
751 // if we need to support a device that does not have a pipeliner
752 // we can add an exclusion rules before this
753 if (!this.handler().hasBehaviour(Pipeliner.class)) {
754 throw new UnsupportedOperationException("Not supported device");
755 }
756 Pipeliner pipeliner = this.handler().behaviour(Pipeliner.class);
757 if (pipeliner instanceof OvsOfdpaPipeline) {
758 return false;
759 } else if (pipeliner instanceof Ofdpa2Pipeline) {
760 return true;
761 }
762 throw new UnsupportedOperationException("Not supported device");
763 }
764
765 // OF-DPA hardware requires one VLAN filtering rule and one VLAN assignment flow in the VLAN table.
766 // This method is used to determine whether there is a need to match a second VLAN flow after
767 // matching the given flowEntry.
768 private boolean shouldMatchSecondVlanFlow(FlowEntry flowEntry) {
769 // if we need to support a device that does not have a pipeliner
770 // we can add an exclusion rules before this
771 if (!this.handler().hasBehaviour(Pipeliner.class)) {
772 throw new UnsupportedOperationException("Not supported device");
773 }
774 Pipeliner pipeliner = this.handler().behaviour(Pipeliner.class);
775 if (!(pipeliner instanceof Ofdpa2Pipeline)) {
776 return false;
777 }
778 return ((Ofdpa2Pipeline) pipeliner).requireSecondVlanTableEntry() &&
779 flowEntry.table().equals(IndexTableId.of(VLAN_TABLE)) &&
780 flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null &&
781 ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
782 .vlanId().equals(VlanId.NONE);
783 }
784
785}