/* * Copyright 2016-present Open Networking Laboratory * * 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.bmv2.ctl; import com.google.common.annotations.Beta; import com.google.common.collect.Sets; import org.onlab.util.ImmutableByteSequence; import org.onosproject.bmv2.api.context.Bmv2ActionModel; import org.onosproject.bmv2.api.context.Bmv2Configuration; import org.onosproject.bmv2.api.context.Bmv2DeviceContext; import org.onosproject.bmv2.api.context.Bmv2FieldModel; import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator; import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException; import org.onosproject.bmv2.api.context.Bmv2Interpreter; import org.onosproject.bmv2.api.context.Bmv2InterpreterException; import org.onosproject.bmv2.api.context.Bmv2RuntimeDataModel; import org.onosproject.bmv2.api.context.Bmv2TableKeyModel; import org.onosproject.bmv2.api.context.Bmv2TableModel; import org.onosproject.bmv2.api.runtime.Bmv2Action; import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam; import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector; import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment; import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam; import org.onosproject.bmv2.api.runtime.Bmv2MatchKey; import org.onosproject.bmv2.api.runtime.Bmv2MatchParam; import org.onosproject.bmv2.api.runtime.Bmv2TableEntry; import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam; import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils; 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.EthTypeCriterion; import org.onosproject.net.flow.criteria.ExtensionCriterion; import org.onosproject.net.flow.criteria.PortCriterion; import org.onosproject.net.flow.instructions.ExtensionTreatment; import org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes; import org.onosproject.net.flow.instructions.Instruction; import org.onosproject.net.flow.instructions.Instructions; import org.onosproject.net.flow.instructions.Instructions.ExtensionInstructionWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes; import static org.onosproject.net.flow.criteria.Criterion.Type.EXTENSION; import static org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS; /** * Default implementation of a BMv2 flow rule translator. */ @Beta public class Bmv2FlowRuleTranslatorImpl implements Bmv2FlowRuleTranslator { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Override public Bmv2TableEntry translate(FlowRule rule, Bmv2DeviceContext context) throws Bmv2FlowRuleTranslatorException { Bmv2Configuration configuration = context.configuration(); Bmv2Interpreter interpreter = context.interpreter(); int tableId = rule.tableId(); String tableName = interpreter.tableIdMap().get(tableId); Bmv2TableModel table = (tableName == null) ? configuration.table(tableId) : configuration.table(tableName); if (table == null) { throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId); } /* Translate selector */ Bmv2MatchKey bmv2MatchKey = buildMatchKey(interpreter, rule.selector(), table); /* Translate treatment */ TrafficTreatment treatment = rule.treatment(); Bmv2Action bmv2Action = null; // If treatment has only 1 instruction of type extension, use that for (Instruction inst : treatment.allInstructions()) { if (inst.type() == Instruction.Type.EXTENSION) { if (treatment.allInstructions().size() == 1) { bmv2Action = getActionFromExtension((ExtensionInstructionWrapper) inst); } else { throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic treatment, found multiple " + "instructions of which one is an extension: " + treatment.toString()); } } } if (bmv2Action == null) { // No extension, use interpreter to build action. try { bmv2Action = interpreter.mapTreatment(treatment, configuration); } catch (Bmv2InterpreterException e) { throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment. " + e.toString()); } } if (bmv2Action == null) { // Interpreter returned null. throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment"); } // Check action Bmv2ActionModel actionModel = configuration.action(bmv2Action.name()); if (actionModel == null) { throw new Bmv2FlowRuleTranslatorException("Unknown action " + bmv2Action.name()); } if (!table.actions().contains(actionModel)) { throw new Bmv2FlowRuleTranslatorException("Action " + bmv2Action.name() + " is not defined for table " + tableName); } if (actionModel.runtimeDatas().size() != bmv2Action.parameters().size()) { throw new Bmv2FlowRuleTranslatorException("Wrong number of parameters for action " + actionModel.name() + ", expected " + actionModel.runtimeDatas().size() + ", but found " + bmv2Action.parameters().size()); } for (int i = 0; i < actionModel.runtimeDatas().size(); i++) { Bmv2RuntimeDataModel data = actionModel.runtimeDatas().get(i); ImmutableByteSequence param = bmv2Action.parameters().get(i); if (param.size() != roundToBytes(data.bitWidth())) { throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for parameter " + data.name() + " of action " + actionModel.name() + ", expected " + roundToBytes(data.bitWidth()) + " bytes, but found " + param.size()); } } Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder(); // In BMv2 0 is the highest priority. int newPriority = Integer.MAX_VALUE - rule.priority(); tableEntryBuilder .withTableName(table.name()) .withPriority(newPriority) .withMatchKey(bmv2MatchKey) .withAction(bmv2Action); if (!rule.isPermanent()) { if (table.hasTimeouts()) { tableEntryBuilder.withTimeout((double) rule.timeout()); } else { log.warn("Flow rule is temporary but table {} doesn't support timeouts, translating to permanent", table.name()); } } return tableEntryBuilder.build(); } private Bmv2TernaryMatchParam buildTernaryParam(Bmv2FieldModel field, Criterion criterion, int bitWidth) throws Bmv2FlowRuleTranslatorException { // Value and mask will be filled according to criterion type ImmutableByteSequence value; ImmutableByteSequence mask = null; int byteWidth = roundToBytes(bitWidth); switch (criterion.type()) { case IN_PORT: long port = ((PortCriterion) criterion).port().toLong(); value = ImmutableByteSequence.copyFrom(port); break; case ETH_DST: EthCriterion c = (EthCriterion) criterion; value = ImmutableByteSequence.copyFrom(c.mac().toBytes()); if (c.mask() != null) { mask = ImmutableByteSequence.copyFrom(c.mask().toBytes()); } break; case ETH_SRC: EthCriterion c2 = (EthCriterion) criterion; value = ImmutableByteSequence.copyFrom(c2.mac().toBytes()); if (c2.mask() != null) { mask = ImmutableByteSequence.copyFrom(c2.mask().toBytes()); } break; case ETH_TYPE: short ethType = ((EthTypeCriterion) criterion).ethType().toShort(); value = ImmutableByteSequence.copyFrom(ethType); break; // TODO: implement building for other criterion types (easy with DefaultCriterion of ONOS-4034) default: throw new Bmv2FlowRuleTranslatorException("Feature not implemented, ternary builder for criterion" + "type: " + criterion.type().name()); } // Fit byte sequence in field model bit-width. try { value = Bmv2TranslatorUtils.fitByteSequence(value, bitWidth); } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) { throw new Bmv2FlowRuleTranslatorException( "Fit exception for criterion " + criterion.type().name() + " value, " + e.getMessage()); } if (mask == null) { // no mask, all ones mask = ImmutableByteSequence.ofOnes(byteWidth); } else { try { mask = Bmv2TranslatorUtils.fitByteSequence(mask, bitWidth); } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) { throw new Bmv2FlowRuleTranslatorException( "Fit exception for criterion " + criterion.type().name() + " mask, " + e.getMessage()); } } return new Bmv2TernaryMatchParam(value, mask); } private Bmv2Action getActionFromExtension(Instructions.ExtensionInstructionWrapper inst) throws Bmv2FlowRuleTranslatorException { ExtensionTreatment extTreatment = inst.extensionInstruction(); if (extTreatment.type() == ExtensionTreatmentTypes.BMV2_ACTION.type()) { if (extTreatment instanceof Bmv2ExtensionTreatment) { return ((Bmv2ExtensionTreatment) extTreatment).action(); } else { throw new Bmv2FlowRuleTranslatorException("Unable to decode treatment extension: " + extTreatment); } } else { throw new Bmv2FlowRuleTranslatorException("Unsupported treatment extension type: " + extTreatment.type()); } } private Bmv2MatchKey buildMatchKey(Bmv2Interpreter interpreter, TrafficSelector selector, Bmv2TableModel tableModel) throws Bmv2FlowRuleTranslatorException { // Find a bmv2 extension selector (if any) and get the parameter map. Optional<Bmv2ExtensionSelector> extSelector = selector.criteria().stream() .filter(c -> c.type().equals(EXTENSION)) .map(c -> (ExtensionCriterion) c) .map(ExtensionCriterion::extensionSelector) .filter(c -> c.type().equals(BMV2_MATCH_PARAMS.type())) .map(c -> (Bmv2ExtensionSelector) c) .findFirst(); Map<String, Bmv2MatchParam> extParamMap = (extSelector.isPresent()) ? extSelector.get().parameterMap() : Collections.emptyMap(); Set<Criterion> translatedCriteria = Sets.newHashSet(); Set<String> usedExtParams = Sets.newHashSet(); Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder(); keysLoop: for (Bmv2TableKeyModel keyModel : tableModel.keys()) { // use fieldName dot notation (e.g. ethernet.dstAddr) String fieldName = keyModel.field().header().name() + "." + keyModel.field().type().name(); int bitWidth = keyModel.field().type().bitWidth(); int byteWidth = roundToBytes(bitWidth); Criterion.Type criterionType = interpreter.criterionTypeMap().inverse().get(fieldName); if (!extParamMap.containsKey(fieldName) && (criterionType == null || selector.getCriterion(criterionType) == null)) { // Neither an extension nor a mapping / criterion is available for this field. switch (keyModel.matchType()) { case TERNARY: // Wildcard field matchKeyBuilder.withWildcard(byteWidth); break; case LPM: // LPM with prefix 0 matchKeyBuilder.add(new Bmv2LpmMatchParam(ImmutableByteSequence.ofZeros(byteWidth), 0)); break; default: throw new Bmv2FlowRuleTranslatorException("No value found for required match field " + fieldName); } // Next key continue keysLoop; } Bmv2MatchParam matchParam; if (extParamMap.containsKey(fieldName)) { // Parameter found in extension if (criterionType != null && selector.getCriterion(criterionType) != null) { // Found also a criterion that can be mapped. This is bad. throw new Bmv2FlowRuleTranslatorException("Both an extension and a criterion mapping are defined " + "for match field " + fieldName); } matchParam = extParamMap.get(fieldName); usedExtParams.add(fieldName); // Check parameter type and size if (!keyModel.matchType().equals(matchParam.type())) { throw new Bmv2FlowRuleTranslatorException("Wrong match type for parameter " + fieldName + ", expected " + keyModel.matchType().name() + ", but found " + matchParam.type().name()); } int foundByteWidth; switch (keyModel.matchType()) { case EXACT: Bmv2ExactMatchParam m1 = (Bmv2ExactMatchParam) matchParam; foundByteWidth = m1.value().size(); break; case TERNARY: Bmv2TernaryMatchParam m2 = (Bmv2TernaryMatchParam) matchParam; foundByteWidth = m2.value().size(); break; case LPM: Bmv2LpmMatchParam m3 = (Bmv2LpmMatchParam) matchParam; foundByteWidth = m3.value().size(); break; case VALID: foundByteWidth = -1; break; default: // should never be her throw new RuntimeException("Unrecognized match type " + keyModel.matchType().name()); } if (foundByteWidth != -1 && foundByteWidth != byteWidth) { throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for match parameter " + fieldName + ", expected " + byteWidth + ", but found " + foundByteWidth); } } else { // A criterion mapping is available for this key Criterion criterion = selector.getCriterion(criterionType); translatedCriteria.add(criterion); switch (keyModel.matchType()) { case TERNARY: matchParam = buildTernaryParam(keyModel.field(), criterion, bitWidth); break; default: // TODO: implement other match param builders (exact, LPM, etc.) throw new Bmv2FlowRuleTranslatorException("Feature not yet implemented, match param builder: " + keyModel.matchType().name()); } } matchKeyBuilder.add(matchParam); } // Check if all criteria have been translated Set<Criterion> ignoredCriteria = selector.criteria() .stream() .filter(c -> !c.type().equals(EXTENSION)) .filter(c -> !translatedCriteria.contains(c)) .collect(Collectors.toSet()); if (ignoredCriteria.size() > 0) { throw new Bmv2FlowRuleTranslatorException("The following criteria cannot be translated for table " + tableModel.name() + ": " + ignoredCriteria.toString()); } // Check is all extension parameters have been used Set<String> ignoredExtParams = extParamMap.keySet() .stream() .filter(k -> !usedExtParams.contains(k)) .collect(Collectors.toSet()); if (ignoredExtParams.size() > 0) { throw new Bmv2FlowRuleTranslatorException("The following extension match parameters cannot be used for " + "table " + tableModel.name() + ": " + ignoredExtParams.toString()); } return matchKeyBuilder.build(); } }