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