blob: 170e955e08aca4cd2018db235c54e05511d90cbd [file] [log] [blame]
/*
* 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.drivers.bmv2.translators;
import com.google.common.annotations.Beta;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.bmv2.api.model.Bmv2ModelField;
import org.onosproject.bmv2.api.model.Bmv2ModelTable;
import org.onosproject.bmv2.api.model.Bmv2ModelTableKey;
import org.onosproject.bmv2.api.runtime.Bmv2Action;
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.Bmv2TableEntry;
import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
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.ExtensionSelector;
import org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes;
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;
/**
* Default Bmv2 flow rule translator implementation.
* <p>
* Flow rules are translated into {@link Bmv2TableEntry BMv2 table entries} according to the following logic:
* <ul>
* <li> table name: obtained from the Bmv2 model using the flow rule table ID;
* <li> match key: if the flow rule selector defines only a criterion of type {@link Criterion.Type#EXTENSION EXTENSION}
* , then the latter is expected to contain a {@link Bmv2ExtensionSelector Bmv2ExtensionSelector}, which should provide
* a match key already formatted for the given table; otherwise a match key is built using the
* {@link TranslatorConfig#fieldToCriterionTypeMap() mapping} defined by this translator configuration.
* <li> action: if the flow rule treatment contains only one instruction of type
* {@link Instruction.Type#EXTENSION EXTENSION}, then the latter is expected to contain a {@link Bmv2ExtensionTreatment}
* , which should provide a {@link Bmv2Action} already formatted for the given table; otherwise, an action is
* {@link TranslatorConfig#buildAction(TrafficTreatment) built} using this translator configuration.
* <li> priority: the same as the flow rule.
* <li> timeout: if the table supports timeout, use the same as the flow rule, otherwise none (i.e. permanent entry).
* </ul>
*/
@Beta
public class Bmv2DefaultFlowRuleTranslator implements Bmv2FlowRuleTranslator {
private final TranslatorConfig config;
public Bmv2DefaultFlowRuleTranslator(TranslatorConfig config) {
this.config = config;
}
private static Bmv2TernaryMatchParam buildTernaryParam(Bmv2ModelField field, Criterion criterion, int byteWidth)
throws Bmv2FlowRuleTranslatorException {
// Value and mask will be filled according to criterion type
ImmutableByteSequence value;
ImmutableByteSequence mask = null;
switch (criterion.type()) {
case IN_PORT:
// FIXME: allow port numbers of variable bit length (based on model), truncating when necessary
short port = (short) ((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());
}
if (mask == null) {
// no mask, all ones
mask = ImmutableByteSequence.ofOnes(byteWidth);
}
return new Bmv2TernaryMatchParam(value, mask);
}
private static Bmv2MatchKey getMatchKeyFromExtension(ExtensionCriterion criterion)
throws Bmv2FlowRuleTranslatorException {
ExtensionSelector extSelector = criterion.extensionSelector();
if (extSelector.type() == ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type()) {
if (extSelector instanceof Bmv2ExtensionSelector) {
return ((Bmv2ExtensionSelector) extSelector).matchKey();
} else {
throw new Bmv2FlowRuleTranslatorException("Unable to decode extension selector " + extSelector);
}
} else {
throw new Bmv2FlowRuleTranslatorException("Unsupported extension selector type " + extSelector.type());
}
}
private static Bmv2Action getActionFromExtension(Instructions.ExtensionInstructionWrapper inst)
throws Bmv2FlowRuleTranslatorException {
ExtensionTreatment extTreatment = inst.extensionInstruction();
if (extTreatment.type() == ExtensionTreatmentTypes.P4_BMV2_ACTION.type()) {
if (extTreatment instanceof Bmv2ExtensionTreatment) {
return ((Bmv2ExtensionTreatment) extTreatment).getAction();
} else {
throw new Bmv2FlowRuleTranslatorException("Unable to decode treatment extension: " + extTreatment);
}
} else {
throw new Bmv2FlowRuleTranslatorException("Unsupported treatment extension type: " + extTreatment.type());
}
}
private static Bmv2MatchKey buildMatchKey(TranslatorConfig config, TrafficSelector selector, Bmv2ModelTable table)
throws Bmv2FlowRuleTranslatorException {
Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder();
for (Bmv2ModelTableKey key : table.keys()) {
String fieldName = key.field().header().name() + "." + key.field().type().name();
int byteWidth = (int) Math.ceil((double) key.field().type().bitWidth() / 8.0);
Criterion.Type criterionType = config.fieldToCriterionTypeMap().get(fieldName);
if (criterionType == null || selector.getCriterion(criterionType) == null) {
// A mapping is not available or the selector doesn't have such a type
switch (key.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("Match field not supported: " + fieldName);
}
// Next key
continue;
}
Criterion criterion = selector.getCriterion(criterionType);
Bmv2TernaryMatchParam matchParam = null;
switch (key.matchType()) {
case TERNARY:
matchParam = buildTernaryParam(key.field(), criterion, byteWidth);
break;
default:
// TODO: implement other match param builders (exact, LPM, etc.)
throw new Bmv2FlowRuleTranslatorException("Feature not implemented, match param builder: "
+ key.matchType().name());
}
matchKeyBuilder.add(matchParam);
}
return matchKeyBuilder.build();
}
@Override
public Bmv2TableEntry translate(FlowRule rule)
throws Bmv2FlowRuleTranslatorException {
int tableId = rule.tableId();
Bmv2ModelTable table = config.model().table(tableId);
if (table == null) {
throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId);
}
/* Translate selector */
TrafficSelector selector = rule.selector();
Bmv2MatchKey bmv2MatchKey = null;
// If selector has only 1 criterion of type extension, use that
Criterion criterion = selector.getCriterion(Criterion.Type.EXTENSION);
if (criterion != null) {
if (selector.criteria().size() == 1) {
bmv2MatchKey = getMatchKeyFromExtension((ExtensionCriterion) criterion);
} else {
throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic selector, found multiple " +
"criteria of which one is an extension: " +
selector.toString());
}
}
if (bmv2MatchKey == null) {
// not an extension
bmv2MatchKey = buildMatchKey(config, 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: " +
selector.toString());
}
}
}
if (bmv2Action == null) {
// No extension, use config to build action
bmv2Action = config.buildAction(treatment);
}
if (bmv2Action == null) {
// Config returned null
throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment: " + treatment);
}
Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder();
tableEntryBuilder
.withTableName(table.name())
.withPriority(rule.priority())
.withMatchKey(bmv2MatchKey)
.withAction(bmv2Action);
if (!rule.isPermanent()) {
if (table.hasTimeouts()) {
tableEntryBuilder.withTimeout((double) rule.timeout());
}
//FIXME: add warn log or exception?
}
return tableEntryBuilder.build();
}
@Override
public TranslatorConfig config() {
return this.config;
}
}