| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner; |
| |
| import com.google.common.collect.Lists; |
| import org.onlab.packet.Ethernet; |
| import org.onlab.packet.MacAddress; |
| import org.onlab.packet.VlanId; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.PortNumber; |
| import org.onosproject.net.flow.DefaultTrafficSelector; |
| import org.onosproject.net.flow.DefaultTrafficTreatment; |
| import org.onosproject.net.flow.FlowRule; |
| import org.onosproject.net.flow.TrafficSelector; |
| import org.onosproject.net.flow.TrafficTreatment; |
| import org.onosproject.net.flow.criteria.Criterion; |
| import org.onosproject.net.flow.criteria.EthCriterion; |
| import org.onosproject.net.flow.criteria.PiCriterion; |
| import org.onosproject.net.flow.criteria.PortCriterion; |
| import org.onosproject.net.flow.criteria.VlanIdCriterion; |
| import org.onosproject.net.flow.instructions.Instructions; |
| import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType; |
| import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction; |
| import org.onosproject.net.flowobjective.FilteringObjective; |
| import org.onosproject.net.flowobjective.Objective; |
| import org.onosproject.net.flowobjective.ObjectiveError; |
| import org.onosproject.net.pi.model.PiTableId; |
| import org.onosproject.net.pi.runtime.PiAction; |
| import org.onosproject.net.pi.runtime.PiActionParam; |
| import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities; |
| import org.onosproject.pipelines.fabric.FabricConstants; |
| import org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils; |
| |
| import java.util.Collection; |
| import java.util.List; |
| |
| import static java.lang.String.format; |
| import static org.onosproject.net.flow.criteria.Criterion.Type.INNER_VLAN_VID; |
| import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID; |
| import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_ID; |
| import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP; |
| import static org.onosproject.net.pi.model.PiPipelineInterpreter.PiInterpreterException; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_VLAN; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_PW_TRANSPORT_VLAN; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.ETH_TYPE_EXACT_MASK; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.FWD_MPLS; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.FWD_IPV4_ROUTING; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.FWD_IPV6_ROUTING; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.ONE; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.PORT_TYPE_EDGE; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.PORT_TYPE_INFRA; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.ZERO; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2InstructionOrFail; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.criterion; |
| import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2Instruction; |
| |
| |
| /** |
| * ObjectiveTranslator implementation for FilteringObjective. |
| */ |
| class FilteringObjectiveTranslator |
| extends AbstractObjectiveTranslator<FilteringObjective> { |
| |
| private static final PiAction DENY = PiAction.builder() |
| .withId(FabricConstants.FABRIC_INGRESS_FILTERING_DENY) |
| .build(); |
| |
| private static final int INTERFACE_CONFIG_UPDATE = 2; |
| |
| FilteringObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) { |
| super(deviceId, capabilities); |
| } |
| |
| @Override |
| public ObjectiveTranslation doTranslate(FilteringObjective obj) |
| throws FabricPipelinerException { |
| |
| final ObjectiveTranslation.Builder resultBuilder = |
| ObjectiveTranslation.builder(); |
| |
| if (obj.key() == null || obj.key().type() != Criterion.Type.IN_PORT) { |
| throw new FabricPipelinerException( |
| format("Unsupported or missing filtering key: key=%s", obj.key()), |
| ObjectiveError.BADPARAMS); |
| } |
| |
| final PortCriterion inPort = (PortCriterion) obj.key(); |
| |
| final VlanIdCriterion outerVlan = (VlanIdCriterion) criterion( |
| obj.conditions(), Criterion.Type.VLAN_VID); |
| final VlanIdCriterion innerVlan = (VlanIdCriterion) criterion( |
| obj.conditions(), Criterion.Type.INNER_VLAN_VID); |
| final EthCriterion ethDst = (EthCriterion) criterion( |
| obj.conditions(), Criterion.Type.ETH_DST); |
| final EthCriterion ethDstMasked = (EthCriterion) criterion( |
| obj.conditions(), Criterion.Type.ETH_DST_MASKED); |
| |
| ingressPortVlanRule(obj, inPort, outerVlan, innerVlan, resultBuilder); |
| if (shouldModifyFwdClassifierTable(obj)) { |
| fwdClassifierRules(obj, inPort, ethDst, ethDstMasked, resultBuilder); |
| } else { |
| log.debug("Skipping fwd classifier rules for device {}.", deviceId); |
| } |
| return resultBuilder.build(); |
| } |
| |
| private boolean shouldModifyFwdClassifierTable(FilteringObjective obj) { |
| // NOTE: in fabric pipeline the forwarding classifier acts similarly |
| // to the TMAC table of OFDPA that matches on input port. |
| // NOTE: that SR signals when it is a port update event by not setting |
| // the INTERFACE_CONFIG_UPDATE metadata. During the INTERFACE_CONFIG_UPDATE |
| // there is no need to add/remove rules in the fwd_classifier table. |
| // NOTE: that in scenarios like (T, N) -> T where we remove only the native |
| // VLAN there is not an ADD following the remove. |
| |
| // Forwarding classifier rules should be added/removed to translation when: |
| // - the operation is ADD |
| // AND it is a port update event (ADD or UPDATE) OR |
| // - it doesn't refer to double tagged traffic |
| // AND it is a port REMOVE event OR |
| // - it refers to double tagged traffic |
| // and SR is triggering the removal of forwarding classifier rules. |
| return (obj.op() == Objective.Operation.ADD && !isInterfaceConfigUpdate(obj)) || |
| (!isDoubleTagged(obj) && !isInterfaceConfigUpdate(obj)) || |
| (isDoubleTagged(obj) && isLastDoubleTaggedForPort(obj)); |
| } |
| |
| /** |
| * Check if the given filtering objective is triggered by a interface config change. |
| * |
| * @param obj Filtering objective to check. |
| * @return True if SR is signaling to not remove the forwarding classifier rule, |
| * false otherwise. |
| */ |
| private boolean isInterfaceConfigUpdate(FilteringObjective obj) { |
| if (obj.meta() == null) { |
| return false; |
| } |
| Instructions.MetadataInstruction meta = obj.meta().writeMetadata(); |
| // SR is setting this metadata when an interface config update has |
| // been performed and thus fwd classifier rules should not be removed |
| return (meta != null && (meta.metadata() & meta.metadataMask()) == INTERFACE_CONFIG_UPDATE); |
| } |
| |
| /** |
| * Check if the given filtering objective is the last filtering objective |
| * for a double-tagged host for a specific port. |
| * <p> |
| * {@see org.onosproject.segmentrouting.RoutingRulePopulator#buildDoubleTaggedFilteringObj()} |
| * {@see org.onosproject.segmentrouting.RoutingRulePopulator#processDoubleTaggedFilter()} |
| * |
| * @param obj Filtering objective to check. |
| * @return True if SR is signaling to remove the forwarding classifier rule, |
| * false otherwise. |
| */ |
| private boolean isLastDoubleTaggedForPort(FilteringObjective obj) { |
| Instructions.MetadataInstruction meta = obj.meta().writeMetadata(); |
| // SR is setting this metadata when a double tagged filtering objective |
| // is removed and no other hosts is sharing the same input port. |
| return (meta != null && (meta.metadata() & meta.metadataMask()) == 1); |
| } |
| |
| private boolean isDoubleTagged(FilteringObjective obj) { |
| return obj.meta() != null && |
| FabricUtils.l2Instruction(obj.meta(), L2SubType.VLAN_POP) != null && |
| FabricUtils.criterion(obj.conditions(), VLAN_VID) != null && |
| FabricUtils.criterion(obj.conditions(), INNER_VLAN_VID) != null; |
| } |
| |
| private void ingressPortVlanRule( |
| FilteringObjective obj, |
| Criterion inPortCriterion, |
| VlanIdCriterion outerVlanCriterion, |
| VlanIdCriterion innerVlanCriterion, |
| ObjectiveTranslation.Builder resultBuilder) |
| throws FabricPipelinerException { |
| |
| final boolean outerVlanValid = outerVlanCriterion != null |
| && !outerVlanCriterion.vlanId().equals(VlanId.NONE); |
| final boolean innerVlanValid = innerVlanCriterion != null |
| && !innerVlanCriterion.vlanId().equals(VlanId.NONE); |
| |
| if (innerVlanValid && !capabilities.supportDoubleVlanTerm()) { |
| throw new FabricPipelinerException( |
| "Found 2 VLAN IDs, but the pipeline does not support double VLAN termination", |
| ObjectiveError.UNSUPPORTED); |
| } |
| |
| final PiCriterion piCriterion = PiCriterion.builder() |
| .matchExact(FabricConstants.HDR_VLAN_IS_VALID, outerVlanValid ? ONE : ZERO) |
| .build(); |
| |
| final TrafficSelector.Builder selector = DefaultTrafficSelector.builder() |
| .add(inPortCriterion) |
| .add(piCriterion); |
| if (outerVlanValid) { |
| selector.add(outerVlanCriterion); |
| } |
| if (innerVlanValid) { |
| selector.add(innerVlanCriterion); |
| } |
| |
| final TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); |
| if (obj.type().equals(FilteringObjective.Type.DENY)) { |
| treatmentBuilder.piTableAction(DENY); |
| } else { |
| byte portType = PORT_TYPE_EDGE; |
| if (!innerVlanValid && outerVlanValid && |
| outerVlanCriterion.vlanId().toShort() == DEFAULT_PW_TRANSPORT_VLAN) { |
| portType = PORT_TYPE_INFRA; |
| } else if (obj.meta() != null) { |
| ModVlanIdInstruction modVlanIdInstruction = (ModVlanIdInstruction) l2Instruction(obj.meta(), VLAN_ID); |
| if (modVlanIdInstruction != null && modVlanIdInstruction.vlanId().toShort() == DEFAULT_VLAN) { |
| portType = PORT_TYPE_INFRA; |
| } |
| } |
| try { |
| treatmentBuilder.piTableAction(mapFilteringTreatment(obj.meta(), |
| FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN, portType)); |
| } catch (PiInterpreterException ex) { |
| throw new FabricPipelinerException(format("Unable to map treatment for table '%s': %s", |
| FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN, |
| ex.getMessage()), ObjectiveError.UNSUPPORTED); |
| } |
| } |
| resultBuilder.addFlowRule(flowRule( |
| obj, FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN, |
| selector.build(), treatmentBuilder.build())); |
| } |
| |
| private PiAction mapFilteringTreatment(TrafficTreatment treatment, PiTableId tableId, byte portType) |
| throws PiInterpreterException { |
| if (treatment == null) { |
| treatment = DefaultTrafficTreatment.emptyTreatment(); |
| } |
| // VLAN_POP action is equivalent to the permit action (VLANs pop is done anyway) |
| if (isFilteringNoAction(treatment) || isFilteringPopAction(treatment)) { |
| // Permit action if table is ingress_port_vlan; |
| return PiAction.builder() |
| .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT) |
| .withParameter(new PiActionParam(FabricConstants.PORT_TYPE, portType)) |
| .build(); |
| } |
| |
| final ModVlanIdInstruction setVlanInst = (ModVlanIdInstruction) l2InstructionOrFail( |
| treatment, VLAN_ID, tableId); |
| return PiAction.builder() |
| .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN) |
| .withParameter(new PiActionParam(FabricConstants.VLAN_ID, setVlanInst.vlanId().toShort())) |
| .withParameter(new PiActionParam(FabricConstants.PORT_TYPE, portType)) |
| .build(); |
| } |
| |
| // NOTE: we use clearDeferred to signal when there are no more ports associated to a given vlan |
| private static boolean isFilteringNoAction(TrafficTreatment treatment) { |
| return treatment.equals(DefaultTrafficTreatment.emptyTreatment()) || |
| (treatment.allInstructions().isEmpty()) || |
| (treatment.allInstructions().size() == 1 && treatment.writeMetadata() != null); |
| } |
| |
| private boolean isFilteringPopAction(TrafficTreatment treatment) { |
| return l2Instruction(treatment, VLAN_POP) != null; |
| } |
| |
| private void fwdClassifierRules( |
| FilteringObjective obj, |
| PortCriterion inPortCriterion, |
| EthCriterion ethDstCriterion, |
| EthCriterion ethDstMaskedCriterion, |
| ObjectiveTranslation.Builder resultBuilder) |
| throws FabricPipelinerException { |
| |
| final List<FlowRule> flowRules = Lists.newArrayList(); |
| |
| final PortNumber inPort = inPortCriterion.port(); |
| if (ethDstCriterion == null) { |
| if (ethDstMaskedCriterion == null) { |
| // No match. Do bridging (default action). |
| return; |
| } |
| // Masked fwd classifier rule |
| final MacAddress dstMac = ethDstMaskedCriterion.mac(); |
| final MacAddress dstMacMask = ethDstMaskedCriterion.mask(); |
| flowRules.add(maskedFwdClassifierRule(inPort, dstMac, dstMacMask, obj)); |
| } else { |
| final MacAddress dstMac = ethDstCriterion.mac(); |
| flowRules.addAll(ipFwdClassifierRules(inPort, dstMac, obj)); |
| flowRules.addAll(mplsFwdClassifierRules(inPort, dstMac, obj)); |
| } |
| |
| for (FlowRule f : flowRules) { |
| resultBuilder.addFlowRule(f); |
| } |
| } |
| |
| private FlowRule maskedFwdClassifierRule( |
| PortNumber inPort, MacAddress dstMac, MacAddress dstMacMask, |
| FilteringObjective obj) |
| throws FabricPipelinerException { |
| final TrafficTreatment treatment; |
| final short ethType; |
| if (dstMac.equals(MacAddress.IPV4_MULTICAST) |
| && dstMacMask.equals(MacAddress.IPV4_MULTICAST_MASK)) { |
| treatment = fwdClassifierTreatment(FWD_IPV4_ROUTING); |
| ethType = Ethernet.TYPE_IPV4; |
| } else if (dstMac.equals(MacAddress.IPV6_MULTICAST) |
| && dstMacMask.equals(MacAddress.IPV6_MULTICAST_MASK)) { |
| treatment = fwdClassifierTreatment(FWD_IPV6_ROUTING); |
| ethType = Ethernet.TYPE_IPV6; |
| } else { |
| throw new FabricPipelinerException(format( |
| "Unsupported masked Ethernet address for fwd " + |
| "classifier rule (mac=%s, mask=%s)", |
| dstMac, dstMacMask)); |
| } |
| return fwdClassifierRule(inPort, ethType, dstMac, dstMacMask, treatment, obj); |
| } |
| |
| private Collection<FlowRule> ipFwdClassifierRules( |
| PortNumber inPort, MacAddress dstMac, FilteringObjective obj) |
| throws FabricPipelinerException { |
| final Collection<FlowRule> flowRules = Lists.newArrayList(); |
| flowRules.add(fwdClassifierRule( |
| inPort, Ethernet.TYPE_IPV4, dstMac, null, |
| fwdClassifierTreatment(FWD_IPV4_ROUTING), obj)); |
| flowRules.add(fwdClassifierRule( |
| inPort, Ethernet.TYPE_IPV6, dstMac, null, |
| fwdClassifierTreatment(FWD_IPV6_ROUTING), obj)); |
| return flowRules; |
| } |
| |
| private Collection<FlowRule> mplsFwdClassifierRules( |
| PortNumber inPort, MacAddress dstMac, FilteringObjective obj) |
| throws FabricPipelinerException { |
| // Forwarding classifier for MPLS is composed of 2 rules |
| // with higher priority wrt standard forwarding classifier rules, |
| // this is due to overlap on ternary matching. |
| TrafficTreatment treatment = fwdClassifierTreatment(FWD_MPLS); |
| final PiCriterion ethTypeMplsIpv4 = PiCriterion.builder() |
| .matchTernary(FabricConstants.HDR_ETH_TYPE, |
| Ethernet.MPLS_UNICAST, ETH_TYPE_EXACT_MASK) |
| .matchExact(FabricConstants.HDR_IP_ETH_TYPE, |
| Ethernet.TYPE_IPV4) |
| .build(); |
| final TrafficSelector selectorMplsIpv4 = DefaultTrafficSelector.builder() |
| .matchInPort(inPort) |
| .matchPi(ethTypeMplsIpv4) |
| .matchEthDstMasked(dstMac, MacAddress.EXACT_MASK) |
| .build(); |
| |
| final PiCriterion ethTypeMplsIpv6 = PiCriterion.builder() |
| .matchTernary(FabricConstants.HDR_ETH_TYPE, |
| Ethernet.MPLS_UNICAST, ETH_TYPE_EXACT_MASK) |
| .matchExact(FabricConstants.HDR_IP_ETH_TYPE, |
| Ethernet.TYPE_IPV6) |
| .build(); |
| final TrafficSelector selectorMplsIpv6 = DefaultTrafficSelector.builder() |
| .matchInPort(inPort) |
| .matchPi(ethTypeMplsIpv6) |
| .matchEthDstMasked(dstMac, MacAddress.EXACT_MASK) |
| .build(); |
| |
| return List.of( |
| flowRule(obj, FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER, |
| selectorMplsIpv4, treatment, obj.priority() + 1), |
| flowRule(obj, FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER, |
| selectorMplsIpv6, treatment, obj.priority() + 1) |
| ); |
| } |
| |
| private FlowRule fwdClassifierRule( |
| PortNumber inPort, short ethType, MacAddress dstMac, MacAddress dstMacMask, |
| TrafficTreatment treatment, FilteringObjective obj) |
| throws FabricPipelinerException { |
| // Match on ip_eth_type that is the eth_type of the L3 protocol. |
| // i.e., if the packet has an IP header, ip_eth_type should |
| // contain the corresponding eth_type (for IPv4 or IPv6) |
| final PiCriterion ethTypeCriterion = PiCriterion.builder() |
| .matchExact(FabricConstants.HDR_IP_ETH_TYPE, ethType) |
| .build(); |
| final TrafficSelector selector = DefaultTrafficSelector.builder() |
| .matchInPort(inPort) |
| .matchPi(ethTypeCriterion) |
| .matchEthDstMasked(dstMac, dstMacMask == null |
| ? MacAddress.EXACT_MASK : dstMacMask) |
| .build(); |
| return flowRule( |
| obj, FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER, |
| selector, treatment); |
| } |
| |
| private TrafficTreatment fwdClassifierTreatment(byte fwdType) { |
| final PiActionParam param = new PiActionParam(FabricConstants.FWD_TYPE, fwdType); |
| final PiAction action = PiAction.builder() |
| .withId(FabricConstants.FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE) |
| .withParameter(param) |
| .build(); |
| return DefaultTrafficTreatment.builder() |
| .piTableAction(action) |
| .build(); |
| |
| } |
| } |