| /* |
| * Copyright 2018-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.basic; |
| |
| import com.google.common.collect.ImmutableBiMap; |
| import com.google.common.collect.Sets; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.onlab.util.ImmutableByteSequence; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.core.CoreService; |
| import org.onosproject.inbandtelemetry.api.IntConfig; |
| import org.onosproject.inbandtelemetry.api.IntIntent; |
| import org.onosproject.inbandtelemetry.api.IntObjective; |
| import org.onosproject.inbandtelemetry.api.IntProgrammable; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.PortNumber; |
| import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| import org.onosproject.net.flow.DefaultFlowRule; |
| import org.onosproject.net.flow.DefaultTrafficSelector; |
| import org.onosproject.net.flow.DefaultTrafficTreatment; |
| import org.onosproject.net.flow.FlowRule; |
| import org.onosproject.net.flow.FlowRuleService; |
| import org.onosproject.net.flow.TableId; |
| 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.IPCriterion; |
| import org.onosproject.net.flow.criteria.PiCriterion; |
| import org.onosproject.net.flow.criteria.TcpPortCriterion; |
| import org.onosproject.net.flow.criteria.UdpPortCriterion; |
| import org.onosproject.net.pi.model.PiActionId; |
| import org.onosproject.net.pi.model.PiMatchFieldId; |
| import org.onosproject.net.pi.model.PiTableId; |
| import org.onosproject.net.pi.runtime.PiAction; |
| import org.onosproject.net.pi.runtime.PiActionParam; |
| import org.slf4j.Logger; |
| |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import java.util.stream.StreamSupport; |
| |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| public class IntProgrammableImpl extends AbstractHandlerBehaviour implements IntProgrammable { |
| |
| // TODO: change this value to the value of diameter of a network. |
| private static final int MAXHOP = 64; |
| private static final int PORTMASK = 0xffff; |
| private static final int IDLE_TIMEOUT = 100; |
| private static final int PKT_INSTANCE_TYPE_INGRESS_CLONE = 1; |
| // Application name of the pipeline which adds this implementation to the pipeconf |
| private static final String PIPELINE_APP_NAME = "org.onosproject.pipelines.basic"; |
| private final Logger log = getLogger(getClass()); |
| private ApplicationId appId; |
| |
| private static final Set<Criterion.Type> SUPPORTED_CRITERION = Sets.newHashSet( |
| Criterion.Type.IPV4_DST, Criterion.Type.IPV4_SRC, |
| Criterion.Type.UDP_SRC, Criterion.Type.UDP_DST, |
| Criterion.Type.TCP_SRC, Criterion.Type.TCP_DST, |
| Criterion.Type.IP_PROTO); |
| |
| private static final Set<PiTableId> TABLES_TO_CLEANUP = Sets.newHashSet( |
| IntConstants.TBL_INT_INSERT_ID, |
| IntConstants.TBL_INT_INST_0003_ID, |
| IntConstants.TBL_INT_INST_0407_ID, |
| IntConstants.TBL_SET_SOURCE_ID, |
| IntConstants.TBL_SET_SINK_ID, |
| IntConstants.TBL_INT_SOURCE_ID, |
| IntConstants.TBL_GENERATE_REPORT_ID); |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY) |
| private FlowRuleService flowRuleService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY) |
| private CoreService coreService; |
| |
| private DeviceId deviceId; |
| private static final int DEFAULT_PRIORITY = 10000; |
| private static final ImmutableBiMap<Integer, PiActionId> INST_0003_ACTION_MAP = |
| ImmutableBiMap.<Integer, PiActionId>builder() |
| .put(0, IntConstants.ACT_INT_SET_HEADER_0003_I0_ID) |
| .put(1, IntConstants.ACT_INT_SET_HEADER_0003_I1_ID) |
| .put(2, IntConstants.ACT_INT_SET_HEADER_0003_I2_ID) |
| .put(3, IntConstants.ACT_INT_SET_HEADER_0003_I3_ID) |
| .put(4, IntConstants.ACT_INT_SET_HEADER_0003_I4_ID) |
| .put(5, IntConstants.ACT_INT_SET_HEADER_0003_I5_ID) |
| .put(6, IntConstants.ACT_INT_SET_HEADER_0003_I6_ID) |
| .put(7, IntConstants.ACT_INT_SET_HEADER_0003_I7_ID) |
| .put(8, IntConstants.ACT_INT_SET_HEADER_0003_I8_ID) |
| .put(9, IntConstants.ACT_INT_SET_HEADER_0003_I9_ID) |
| .put(10, IntConstants.ACT_INT_SET_HEADER_0003_I10_ID) |
| .put(11, IntConstants.ACT_INT_SET_HEADER_0003_I11_ID) |
| .put(12, IntConstants.ACT_INT_SET_HEADER_0003_I12_ID) |
| .put(13, IntConstants.ACT_INT_SET_HEADER_0003_I13_ID) |
| .put(14, IntConstants.ACT_INT_SET_HEADER_0003_I14_ID) |
| .put(15, IntConstants.ACT_INT_SET_HEADER_0003_I15_ID) |
| .build(); |
| |
| private static final ImmutableBiMap<Integer, PiActionId> INST_0407_ACTION_MAP = |
| ImmutableBiMap.<Integer, PiActionId>builder() |
| .put(0, IntConstants.ACT_INT_SET_HEADER_0407_I0_ID) |
| .put(1, IntConstants.ACT_INT_SET_HEADER_0407_I1_ID) |
| .put(2, IntConstants.ACT_INT_SET_HEADER_0407_I2_ID) |
| .put(3, IntConstants.ACT_INT_SET_HEADER_0407_I3_ID) |
| .put(4, IntConstants.ACT_INT_SET_HEADER_0407_I4_ID) |
| .put(5, IntConstants.ACT_INT_SET_HEADER_0407_I5_ID) |
| .put(6, IntConstants.ACT_INT_SET_HEADER_0407_I6_ID) |
| .put(7, IntConstants.ACT_INT_SET_HEADER_0407_I7_ID) |
| .put(8, IntConstants.ACT_INT_SET_HEADER_0407_I8_ID) |
| .put(9, IntConstants.ACT_INT_SET_HEADER_0407_I9_ID) |
| .put(10, IntConstants.ACT_INT_SET_HEADER_0407_I10_ID) |
| .put(11, IntConstants.ACT_INT_SET_HEADER_0407_I11_ID) |
| .put(12, IntConstants.ACT_INT_SET_HEADER_0407_I12_ID) |
| .put(13, IntConstants.ACT_INT_SET_HEADER_0407_I13_ID) |
| .put(14, IntConstants.ACT_INT_SET_HEADER_0407_I14_ID) |
| .put(15, IntConstants.ACT_INT_SET_HEADER_0407_I15_ID) |
| .build(); |
| |
| private boolean setupBehaviour() { |
| deviceId = this.data().deviceId(); |
| flowRuleService = handler().get(FlowRuleService.class); |
| coreService = handler().get(CoreService.class); |
| appId = coreService.getAppId(PIPELINE_APP_NAME); |
| if (appId == null) { |
| log.warn("Application ID is null. Cannot initialize behaviour."); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean init() { |
| if (!setupBehaviour()) { |
| return false; |
| } |
| |
| // process_int_transit.tb_int_insert |
| PiActionParam transitIdParam = new PiActionParam( |
| IntConstants.ACT_PRM_SWITCH_ID, |
| ImmutableByteSequence.copyFrom( |
| Integer.parseInt(deviceId.toString().substring( |
| deviceId.toString().length() - 2)))); |
| PiAction transitAction = PiAction.builder() |
| .withId(IntConstants.ACT_INT_TRANSIT_ID) |
| .withParameter(transitIdParam) |
| .build(); |
| TrafficTreatment treatment = DefaultTrafficTreatment.builder() |
| .piTableAction(transitAction) |
| .build(); |
| |
| FlowRule transitFlowRule = DefaultFlowRule.builder() |
| .withTreatment(treatment) |
| .fromApp(appId) |
| .withPriority(DEFAULT_PRIORITY) |
| .makePermanent() |
| .forDevice(deviceId) |
| .forTable(IntConstants.TBL_INT_INSERT_ID) |
| .build(); |
| |
| flowRuleService.applyFlowRules(transitFlowRule); |
| |
| // Populate tb_int_inst_0003 table |
| INST_0003_ACTION_MAP.forEach((matchValue, actionId) -> |
| populateInstTableEntry(IntConstants.TBL_INT_INST_0003_ID, |
| IntConstants.INT_HDR_INST_MASK_0003_ID, |
| matchValue, |
| actionId, |
| appId)); |
| // Populate tb_int_inst_0407 table |
| INST_0407_ACTION_MAP.forEach((matchValue, actionId) -> |
| populateInstTableEntry(IntConstants.TBL_INT_INST_0407_ID, |
| IntConstants.INT_HDR_INST_MASK_0407_ID, |
| matchValue, |
| actionId, |
| appId)); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean setSourcePort(PortNumber port) { |
| if (!setupBehaviour()) { |
| return false; |
| } |
| |
| // process_set_source_sink.tb_set_source for each host-facing port |
| PiCriterion ingressCriterion = PiCriterion.builder() |
| .matchExact(BasicConstants.HDR_IN_PORT_ID, port.toLong()) |
| .build(); |
| TrafficSelector srcSelector = DefaultTrafficSelector.builder() |
| .matchPi(ingressCriterion) |
| .build(); |
| PiAction setSourceAct = PiAction.builder() |
| .withId(IntConstants.ACT_INT_SET_SOURCE_ID) |
| .build(); |
| TrafficTreatment srcTreatment = DefaultTrafficTreatment.builder() |
| .piTableAction(setSourceAct) |
| .build(); |
| FlowRule srcFlowRule = DefaultFlowRule.builder() |
| .withSelector(srcSelector) |
| .withTreatment(srcTreatment) |
| .fromApp(appId) |
| .withPriority(DEFAULT_PRIORITY) |
| .makePermanent() |
| .forDevice(deviceId) |
| .forTable(IntConstants.TBL_SET_SOURCE_ID) |
| .build(); |
| flowRuleService.applyFlowRules(srcFlowRule); |
| return true; |
| } |
| |
| @Override |
| public boolean setSinkPort(PortNumber port) { |
| if (!setupBehaviour()) { |
| return false; |
| } |
| |
| // process_set_source_sink.tb_set_sink |
| PiCriterion egressCriterion = PiCriterion.builder() |
| .matchExact(IntConstants.HDR_OUT_PORT_ID, port.toLong()) |
| .build(); |
| TrafficSelector sinkSelector = DefaultTrafficSelector.builder() |
| .matchPi(egressCriterion) |
| .build(); |
| PiAction setSinkAct = PiAction.builder() |
| .withId(IntConstants.ACT_INT_SET_SINK_ID) |
| .build(); |
| TrafficTreatment sinkTreatment = DefaultTrafficTreatment.builder() |
| .piTableAction(setSinkAct) |
| .build(); |
| FlowRule sinkFlowRule = DefaultFlowRule.builder() |
| .withSelector(sinkSelector) |
| .withTreatment(sinkTreatment) |
| .fromApp(appId) |
| .withPriority(DEFAULT_PRIORITY) |
| .makePermanent() |
| .forDevice(deviceId) |
| .forTable(IntConstants.TBL_SET_SINK_ID) |
| .build(); |
| flowRuleService.applyFlowRules(sinkFlowRule); |
| return true; |
| } |
| |
| @Override |
| public boolean addIntObjective(IntObjective obj) { |
| // TODO: support different types of watchlist other than flow watchlist |
| |
| return processIntObjective(obj, true); |
| } |
| |
| @Override |
| public boolean removeIntObjective(IntObjective obj) { |
| return processIntObjective(obj, false); |
| } |
| |
| @Override |
| public boolean setupIntConfig(IntConfig config) { |
| return setupIntReportInternal(config); |
| } |
| |
| @Override |
| public void cleanup() { |
| if (!setupBehaviour()) { |
| return; |
| } |
| |
| StreamSupport.stream(flowRuleService.getFlowEntries( |
| data().deviceId()).spliterator(), false) |
| .filter(f -> f.table().type() == TableId.Type.PIPELINE_INDEPENDENT) |
| .filter(f -> TABLES_TO_CLEANUP.contains((PiTableId) f.table())) |
| .forEach(flowRuleService::removeFlowRules); |
| } |
| |
| @Override |
| public boolean supportsFunctionality(IntFunctionality functionality) { |
| switch (functionality) { |
| case SOURCE: |
| case SINK: |
| case TRANSIT: |
| return true; |
| default: |
| log.warn("Unknown functionality {}", functionality); |
| return false; |
| } |
| } |
| |
| private void populateInstTableEntry(PiTableId tableId, PiMatchFieldId matchFieldId, |
| int matchValue, PiActionId actionId, ApplicationId appId) { |
| PiCriterion instCriterion = PiCriterion.builder() |
| .matchExact(matchFieldId, matchValue) |
| .build(); |
| TrafficSelector instSelector = DefaultTrafficSelector.builder() |
| .matchPi(instCriterion) |
| .build(); |
| PiAction instAction = PiAction.builder() |
| .withId(actionId) |
| .build(); |
| TrafficTreatment instTreatment = DefaultTrafficTreatment.builder() |
| .piTableAction(instAction) |
| .build(); |
| |
| FlowRule instFlowRule = DefaultFlowRule.builder() |
| .withSelector(instSelector) |
| .withTreatment(instTreatment) |
| .withPriority(DEFAULT_PRIORITY) |
| .makePermanent() |
| .forDevice(deviceId) |
| .forTable(tableId) |
| .fromApp(appId) |
| .build(); |
| |
| flowRuleService.applyFlowRules(instFlowRule); |
| } |
| |
| private FlowRule buildWatchlistEntry(IntObjective obj) { |
| int instructionBitmap = buildInstructionBitmap(obj.metadataTypes()); |
| PiActionParam maxHopParam = new PiActionParam( |
| IntConstants.ACT_PRM_MAX_HOP_ID, |
| ImmutableByteSequence.copyFrom(MAXHOP)); |
| PiActionParam instCntParam = new PiActionParam( |
| IntConstants.ACT_PRM_INS_CNT_ID, |
| ImmutableByteSequence.copyFrom(Integer.bitCount(instructionBitmap))); |
| PiActionParam inst0003Param = new PiActionParam( |
| IntConstants.ACT_PRM_INS_MASK0003_ID, |
| ImmutableByteSequence.copyFrom((instructionBitmap >> 12) & 0xF)); |
| PiActionParam inst0407Param = new PiActionParam( |
| IntConstants.ACT_PRM_INS_MASK0407_ID, |
| ImmutableByteSequence.copyFrom((instructionBitmap >> 8) & 0xF)); |
| |
| PiAction intSourceAction = PiAction.builder() |
| .withId(IntConstants.ACT_INT_SOURCE_DSCP_ID) |
| .withParameter(maxHopParam) |
| .withParameter(instCntParam) |
| .withParameter(inst0003Param) |
| .withParameter(inst0407Param) |
| .build(); |
| |
| TrafficTreatment instTreatment = DefaultTrafficTreatment.builder() |
| .piTableAction(intSourceAction) |
| .build(); |
| |
| TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder(); |
| for (Criterion criterion : obj.selector().criteria()) { |
| switch (criterion.type()) { |
| case IPV4_SRC: |
| sBuilder.matchIPSrc(((IPCriterion) criterion).ip()); |
| break; |
| case IPV4_DST: |
| sBuilder.matchIPDst(((IPCriterion) criterion).ip()); |
| break; |
| case TCP_SRC: |
| sBuilder.matchPi( |
| PiCriterion.builder().matchTernary( |
| IntConstants.LOCAL_META_SRC_PORT_ID, |
| ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK) |
| .build()); |
| break; |
| case UDP_SRC: |
| sBuilder.matchPi( |
| PiCriterion.builder().matchTernary( |
| IntConstants.LOCAL_META_SRC_PORT_ID, |
| ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK) |
| .build()); |
| break; |
| case TCP_DST: |
| sBuilder.matchPi( |
| PiCriterion.builder().matchTernary( |
| IntConstants.LOCAL_META_DST_PORT_ID, |
| ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK) |
| .build()); |
| break; |
| case UDP_DST: |
| sBuilder.matchPi( |
| PiCriterion.builder().matchTernary( |
| IntConstants.LOCAL_META_DST_PORT_ID, |
| ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK) |
| .build()); |
| break; |
| default: |
| log.warn("Unsupported criterion type: {}", criterion.type()); |
| } |
| } |
| |
| return DefaultFlowRule.builder() |
| .forDevice(this.data().deviceId()) |
| .withSelector(sBuilder.build()) |
| .withTreatment(instTreatment) |
| .withPriority(DEFAULT_PRIORITY) |
| .forTable(IntConstants.TBL_INT_SOURCE_ID) |
| .fromApp(appId) |
| .withIdleTimeout(IDLE_TIMEOUT) |
| .build(); |
| } |
| |
| private int buildInstructionBitmap(Set<IntIntent.IntMetadataType> metadataTypes) { |
| int instBitmap = 0; |
| for (IntIntent.IntMetadataType metadataType : metadataTypes) { |
| switch (metadataType) { |
| case SWITCH_ID: |
| instBitmap |= (1 << 15); |
| break; |
| case L1_PORT_ID: |
| instBitmap |= (1 << 14); |
| break; |
| case HOP_LATENCY: |
| instBitmap |= (1 << 13); |
| break; |
| case QUEUE_OCCUPANCY: |
| instBitmap |= (1 << 12); |
| break; |
| case INGRESS_TIMESTAMP: |
| instBitmap |= (1 << 11); |
| break; |
| case EGRESS_TIMESTAMP: |
| instBitmap |= (1 << 10); |
| break; |
| case L2_PORT_ID: |
| instBitmap |= (1 << 9); |
| break; |
| case EGRESS_TX_UTIL: |
| instBitmap |= (1 << 8); |
| break; |
| default: |
| log.info("Unsupported metadata type {}. Ignoring...", metadataType); |
| break; |
| } |
| } |
| return instBitmap; |
| } |
| |
| /** |
| * Returns a subset of Criterion from given selector, which is unsupported |
| * by this INT pipeline. |
| * |
| * @param selector a traffic selector |
| * @return a subset of Criterion from given selector, unsupported by this |
| * INT pipeline, empty if all criteria are supported. |
| */ |
| private Set<Criterion> unsupportedSelectors(TrafficSelector selector) { |
| return selector.criteria().stream() |
| .filter(criterion -> !SUPPORTED_CRITERION.contains(criterion.type())) |
| .collect(Collectors.toSet()); |
| } |
| |
| private boolean processIntObjective(IntObjective obj, boolean install) { |
| if (!setupBehaviour()) { |
| return false; |
| } |
| if (install && !unsupportedSelectors(obj.selector()).isEmpty()) { |
| log.warn("Device {} does not support criteria {} for INT.", |
| deviceId, unsupportedSelectors(obj.selector())); |
| return false; |
| } |
| |
| FlowRule flowRule = buildWatchlistEntry(obj); |
| if (flowRule != null) { |
| if (install) { |
| flowRuleService.applyFlowRules(flowRule); |
| } else { |
| flowRuleService.removeFlowRules(flowRule); |
| } |
| log.debug("IntObjective {} has been {} {}", |
| obj, install ? "installed to" : "removed from", deviceId); |
| return true; |
| } else { |
| log.warn("Failed to {} IntObjective {} on {}", |
| install ? "install" : "remove", obj, deviceId); |
| return false; |
| } |
| } |
| |
| private boolean setupIntReportInternal(IntConfig cfg) { |
| if (!setupBehaviour()) { |
| return false; |
| } |
| |
| FlowRule reportRule = buildReportEntry(cfg, PKT_INSTANCE_TYPE_INGRESS_CLONE); |
| if (reportRule != null) { |
| flowRuleService.applyFlowRules(reportRule); |
| log.info("Report entry {} has been added to {}", reportRule, this.data().deviceId()); |
| return true; |
| } else { |
| log.warn("Failed to add report entry on {}", this.data().deviceId()); |
| return false; |
| } |
| } |
| |
| private FlowRule buildReportEntry(IntConfig cfg, int type) { |
| PiCriterion instTypeCriterion = PiCriterion.builder() |
| .matchExact(IntConstants.STD_META_INSTANCE_TYPE_ID, type) |
| .build(); |
| TrafficSelector selector = DefaultTrafficSelector.builder() |
| .matchPi(instTypeCriterion) |
| .build(); |
| PiActionParam srcMacParam = new PiActionParam( |
| IntConstants.ACT_PRM_SRC_MAC_ID, |
| ImmutableByteSequence.copyFrom(cfg.sinkMac().toBytes())); |
| PiActionParam nextHopMacParam = new PiActionParam( |
| IntConstants.ACT_PRM_MON_MAC_ID, |
| ImmutableByteSequence.copyFrom(cfg.collectorNextHopMac().toBytes())); |
| PiActionParam srcIpParam = new PiActionParam( |
| IntConstants.ACT_PRM_SRC_IP_ID, |
| ImmutableByteSequence.copyFrom(cfg.sinkIp().toOctets())); |
| PiActionParam monIpParam = new PiActionParam( |
| IntConstants.ACT_PRM_MON_IP_ID, |
| ImmutableByteSequence.copyFrom(cfg.collectorIp().toOctets())); |
| PiActionParam monPortParam = new PiActionParam( |
| IntConstants.ACT_PRM_MON_PORT_ID, |
| ImmutableByteSequence.copyFrom(cfg.collectorPort().toInt())); |
| PiAction reportAction = PiAction.builder() |
| .withId(IntConstants.ACT_DO_REPORT_ENCAP_ID) |
| .withParameter(srcMacParam) |
| .withParameter(nextHopMacParam) |
| .withParameter(srcIpParam) |
| .withParameter(monIpParam) |
| .withParameter(monPortParam) |
| .build(); |
| TrafficTreatment treatment = DefaultTrafficTreatment.builder() |
| .piTableAction(reportAction) |
| .build(); |
| |
| return DefaultFlowRule.builder() |
| .withSelector(selector) |
| .withTreatment(treatment) |
| .fromApp(appId) |
| .withPriority(DEFAULT_PRIORITY) |
| .makePermanent() |
| .forDevice(this.data().deviceId()) |
| .forTable(IntConstants.TBL_GENERATE_REPORT_ID) |
| .build(); |
| } |
| |
| } |