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