Carmelo Cascone | b7388bd | 2016-04-14 10:20:13 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2016-present Open Networking Laboratory |
| 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 | |
| 17 | package org.onosproject.drivers.bmv2.translators; |
| 18 | |
| 19 | import com.google.common.annotations.Beta; |
| 20 | import org.onlab.util.ImmutableByteSequence; |
| 21 | import org.onosproject.bmv2.api.model.Bmv2Model; |
| 22 | import org.onosproject.bmv2.api.model.Bmv2ModelField; |
| 23 | import org.onosproject.bmv2.api.model.Bmv2ModelTable; |
| 24 | import org.onosproject.bmv2.api.model.Bmv2ModelTableKey; |
| 25 | import org.onosproject.bmv2.api.runtime.Bmv2Action; |
| 26 | import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector; |
| 27 | import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment; |
| 28 | import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam; |
| 29 | import org.onosproject.bmv2.api.runtime.Bmv2MatchKey; |
| 30 | import org.onosproject.bmv2.api.runtime.Bmv2TableEntry; |
| 31 | import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam; |
| 32 | import org.onosproject.net.flow.FlowRule; |
| 33 | import org.onosproject.net.flow.TrafficSelector; |
| 34 | import org.onosproject.net.flow.TrafficTreatment; |
| 35 | import org.onosproject.net.flow.criteria.Criterion; |
| 36 | import org.onosproject.net.flow.criteria.EthCriterion; |
| 37 | import org.onosproject.net.flow.criteria.EthTypeCriterion; |
| 38 | import org.onosproject.net.flow.criteria.ExtensionCriterion; |
| 39 | import org.onosproject.net.flow.criteria.ExtensionSelector; |
| 40 | import org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes; |
| 41 | import org.onosproject.net.flow.criteria.PortCriterion; |
| 42 | import org.onosproject.net.flow.instructions.ExtensionTreatment; |
| 43 | import org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes; |
| 44 | import org.onosproject.net.flow.instructions.Instruction; |
| 45 | import org.onosproject.net.flow.instructions.Instructions; |
| 46 | import org.onosproject.net.flow.instructions.Instructions.ExtensionInstructionWrapper; |
| 47 | |
| 48 | /** |
| 49 | * Default Bmv2 flow rule translator implementation. |
| 50 | * <p> |
| 51 | * Flow rules are translated into {@link Bmv2TableEntry BMv2 table entries} according to the following logic: |
| 52 | * <ul> |
| 53 | * <li> table name: obtained from the Bmv2 model using the flow rule table ID; |
| 54 | * <li> match key: if the flow rule selector defines only a criterion of type {@link Criterion.Type#EXTENSION EXTENSION} |
| 55 | * , then the latter is expected to contain a {@link Bmv2ExtensionSelector Bmv2ExtensionSelector}, which should provide |
| 56 | * a match key already formatted for the given table; otherwise a match key is built using the |
| 57 | * {@link TranslatorConfig#fieldToCriterionTypeMap() mapping} defined by this translator configuration. |
| 58 | * <li> action: if the flow rule treatment contains only one instruction of type |
| 59 | * {@link Instruction.Type#EXTENSION EXTENSION}, then the latter is expected to contain a {@link Bmv2ExtensionTreatment} |
| 60 | * , which should provide a {@link Bmv2Action} already formatted for the given table; otherwise, an action is |
| 61 | * {@link TranslatorConfig#buildAction(TrafficTreatment) built} using this translator configuration. |
| 62 | * <li> priority: the same as the flow rule. |
| 63 | * <li> timeout: if the table supports timeout, use the same as the flow rule, otherwise none (i.e. permanent entry). |
| 64 | * </ul> |
| 65 | */ |
| 66 | @Beta |
| 67 | public class Bmv2DefaultFlowRuleTranslator implements Bmv2FlowRuleTranslator { |
| 68 | |
| 69 | // TODO: config is harcoded now, instead it should be selected based on device model |
Carmelo Cascone | e912164 | 2016-04-27 17:02:38 -0700 | [diff] [blame] | 70 | private final TranslatorConfig config = new Bmv2SimpleTranslatorConfig(); |
Carmelo Cascone | b7388bd | 2016-04-14 10:20:13 -0700 | [diff] [blame] | 71 | private final Bmv2Model model = config.model(); |
| 72 | |
| 73 | private static Bmv2TernaryMatchParam buildTernaryParam(Bmv2ModelField field, Criterion criterion, int byteWidth) |
| 74 | throws Bmv2FlowRuleTranslatorException { |
| 75 | |
| 76 | // Value and mask will be filled according to criterion type |
| 77 | ImmutableByteSequence value; |
| 78 | ImmutableByteSequence mask = null; |
| 79 | |
| 80 | switch (criterion.type()) { |
| 81 | case IN_PORT: |
| 82 | // FIXME: allow port numbers of variable bit length (based on model), truncating when necessary |
| 83 | short port = (short) ((PortCriterion) criterion).port().toLong(); |
| 84 | value = ImmutableByteSequence.copyFrom(port); |
| 85 | break; |
| 86 | case ETH_DST: |
| 87 | EthCriterion c = (EthCriterion) criterion; |
| 88 | value = ImmutableByteSequence.copyFrom(c.mac().toBytes()); |
| 89 | if (c.mask() != null) { |
| 90 | mask = ImmutableByteSequence.copyFrom(c.mask().toBytes()); |
| 91 | } |
| 92 | break; |
| 93 | case ETH_SRC: |
| 94 | EthCriterion c2 = (EthCriterion) criterion; |
| 95 | value = ImmutableByteSequence.copyFrom(c2.mac().toBytes()); |
| 96 | if (c2.mask() != null) { |
| 97 | mask = ImmutableByteSequence.copyFrom(c2.mask().toBytes()); |
| 98 | } |
| 99 | break; |
| 100 | case ETH_TYPE: |
| 101 | short ethType = ((EthTypeCriterion) criterion).ethType().toShort(); |
| 102 | value = ImmutableByteSequence.copyFrom(ethType); |
| 103 | break; |
| 104 | // TODO: implement building for other criterion types (easy with DefaultCriterion of ONOS-4034) |
| 105 | default: |
| 106 | throw new Bmv2FlowRuleTranslatorException("Feature not implemented, ternary builder for criterion" + |
| 107 | "type: " + criterion.type().name()); |
| 108 | } |
| 109 | |
| 110 | if (mask == null) { |
| 111 | // no mask, all ones |
| 112 | mask = ImmutableByteSequence.ofOnes(byteWidth); |
| 113 | } |
| 114 | |
| 115 | return new Bmv2TernaryMatchParam(value, mask); |
| 116 | } |
| 117 | |
| 118 | private static Bmv2MatchKey getMatchKeyFromExtension(ExtensionCriterion criterion) |
| 119 | throws Bmv2FlowRuleTranslatorException { |
| 120 | |
| 121 | ExtensionSelector extSelector = criterion.extensionSelector(); |
| 122 | |
| 123 | if (extSelector.type() == ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type()) { |
| 124 | if (extSelector instanceof Bmv2ExtensionSelector) { |
| 125 | return ((Bmv2ExtensionSelector) extSelector).matchKey(); |
| 126 | } else { |
| 127 | throw new Bmv2FlowRuleTranslatorException("Unable to decode extension selector " + extSelector); |
| 128 | } |
| 129 | } else { |
| 130 | throw new Bmv2FlowRuleTranslatorException("Unsupported extension selector type " + extSelector.type()); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | private static Bmv2Action getActionFromExtension(Instructions.ExtensionInstructionWrapper inst) |
| 135 | throws Bmv2FlowRuleTranslatorException { |
| 136 | |
| 137 | ExtensionTreatment extTreatment = inst.extensionInstruction(); |
| 138 | |
| 139 | if (extTreatment.type() == ExtensionTreatmentTypes.P4_BMV2_ACTION.type()) { |
| 140 | if (extTreatment instanceof Bmv2ExtensionTreatment) { |
| 141 | return ((Bmv2ExtensionTreatment) extTreatment).getAction(); |
| 142 | } else { |
| 143 | throw new Bmv2FlowRuleTranslatorException("Unable to decode treatment extension: " + extTreatment); |
| 144 | } |
| 145 | } else { |
| 146 | throw new Bmv2FlowRuleTranslatorException("Unsupported treatment extension type: " + extTreatment.type()); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | private static Bmv2MatchKey buildMatchKey(TranslatorConfig config, TrafficSelector selector, Bmv2ModelTable table) |
| 151 | throws Bmv2FlowRuleTranslatorException { |
| 152 | |
| 153 | Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder(); |
| 154 | |
| 155 | for (Bmv2ModelTableKey key : table.keys()) { |
| 156 | |
| 157 | String fieldName = key.field().header().name() + "." + key.field().type().name(); |
| 158 | int byteWidth = (int) Math.ceil((double) key.field().type().bitWidth() / 8.0); |
| 159 | Criterion.Type criterionType = config.fieldToCriterionTypeMap().get(fieldName); |
| 160 | |
| 161 | if (criterionType == null || selector.getCriterion(criterionType) == null) { |
| 162 | // A mapping is not available or the selector doesn't have such a type |
| 163 | switch (key.matchType()) { |
| 164 | case TERNARY: |
| 165 | // Wildcard field |
| 166 | matchKeyBuilder.withWildcard(byteWidth); |
| 167 | break; |
| 168 | case LPM: |
| 169 | // LPM with prefix 0 |
| 170 | matchKeyBuilder.add(new Bmv2LpmMatchParam(ImmutableByteSequence.ofZeros(byteWidth), 0)); |
| 171 | break; |
| 172 | default: |
| 173 | throw new Bmv2FlowRuleTranslatorException("Match field not supported: " + fieldName); |
| 174 | } |
| 175 | // Next key |
| 176 | continue; |
| 177 | } |
| 178 | |
| 179 | Criterion criterion = selector.getCriterion(criterionType); |
| 180 | Bmv2TernaryMatchParam matchParam = null; |
| 181 | |
| 182 | switch (key.matchType()) { |
| 183 | case TERNARY: |
| 184 | matchParam = buildTernaryParam(key.field(), criterion, byteWidth); |
| 185 | break; |
| 186 | default: |
| 187 | // TODO: implement other match param builders (exact, LPM, etc.) |
| 188 | throw new Bmv2FlowRuleTranslatorException("Feature not implemented, match param builder: " |
| 189 | + key.matchType().name()); |
| 190 | } |
| 191 | |
| 192 | matchKeyBuilder.add(matchParam); |
| 193 | } |
| 194 | |
| 195 | return matchKeyBuilder.build(); |
| 196 | } |
| 197 | |
| 198 | @Override |
| 199 | public Bmv2TableEntry translate(FlowRule rule) |
| 200 | throws Bmv2FlowRuleTranslatorException { |
| 201 | |
| 202 | int tableId = rule.tableId(); |
| 203 | |
| 204 | Bmv2ModelTable table = model.table(tableId); |
| 205 | |
| 206 | if (table == null) { |
| 207 | throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId); |
| 208 | } |
| 209 | |
| 210 | /* Translate selector */ |
| 211 | |
| 212 | TrafficSelector selector = rule.selector(); |
| 213 | Bmv2MatchKey bmv2MatchKey = null; |
| 214 | |
| 215 | // If selector has only 1 criterion of type extension, use that |
| 216 | Criterion criterion = selector.getCriterion(Criterion.Type.EXTENSION); |
| 217 | if (criterion != null) { |
| 218 | if (selector.criteria().size() == 1) { |
| 219 | bmv2MatchKey = getMatchKeyFromExtension((ExtensionCriterion) criterion); |
| 220 | } else { |
| 221 | throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic selector, found multiple " + |
| 222 | "criteria of which one is an extension: " + |
| 223 | selector.toString()); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | if (bmv2MatchKey == null) { |
| 228 | // not an extension |
| 229 | bmv2MatchKey = buildMatchKey(config, selector, table); |
| 230 | } |
| 231 | |
| 232 | /* Translate treatment */ |
| 233 | |
| 234 | TrafficTreatment treatment = rule.treatment(); |
| 235 | Bmv2Action bmv2Action = null; |
| 236 | |
| 237 | // If treatment has only 1 instruction of type extension, use that |
| 238 | for (Instruction inst : treatment.allInstructions()) { |
| 239 | if (inst.type() == Instruction.Type.EXTENSION) { |
| 240 | if (treatment.allInstructions().size() == 1) { |
| 241 | bmv2Action = getActionFromExtension((ExtensionInstructionWrapper) inst); |
| 242 | } else { |
| 243 | throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic treatment, found multiple " + |
| 244 | "instructions of which one is an extension: " + |
| 245 | selector.toString()); |
| 246 | } |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | if (bmv2Action == null) { |
| 251 | // No extension, use config to build action |
| 252 | bmv2Action = config.buildAction(treatment); |
| 253 | } |
| 254 | |
| 255 | if (bmv2Action == null) { |
| 256 | // Config returned null |
| 257 | throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment: " + treatment); |
| 258 | } |
| 259 | |
| 260 | Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder(); |
| 261 | |
| 262 | tableEntryBuilder |
| 263 | .withTableName(table.name()) |
| 264 | .withPriority(rule.priority()) |
| 265 | .withMatchKey(bmv2MatchKey) |
| 266 | .withAction(bmv2Action); |
| 267 | |
| 268 | if (!rule.isPermanent()) { |
| 269 | if (table.hasTimeouts()) { |
| 270 | tableEntryBuilder.withTimeout((double) rule.timeout()); |
| 271 | } |
| 272 | //FIXME: add warn log or exception? |
| 273 | } |
| 274 | |
| 275 | return tableEntryBuilder.build(); |
| 276 | } |
| 277 | |
| 278 | @Override |
| 279 | public TranslatorConfig config() { |
| 280 | return this.config; |
| 281 | } |
| 282 | } |