| /* |
| * 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.intdemo; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import org.apache.commons.lang.ArrayUtils; |
| import org.apache.felix.scr.annotations.Activate; |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.Deactivate; |
| import org.apache.felix.scr.annotations.Reference; |
| import org.apache.felix.scr.annotations.ReferenceCardinality; |
| import org.onlab.packet.Ip4Address; |
| import org.onlab.util.ImmutableByteSequence; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.core.CoreService; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.device.DeviceEvent; |
| import org.onosproject.net.device.DeviceListener; |
| import org.onosproject.net.device.DeviceService; |
| 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.criteria.PiCriterion; |
| import org.onosproject.net.pi.model.PiActionId; |
| import org.onosproject.net.pi.model.PiActionParamId; |
| import org.onosproject.net.pi.model.PiMatchFieldId; |
| import org.onosproject.net.pi.model.PiPipeconfId; |
| import org.onosproject.net.pi.model.PiTableId; |
| import org.onosproject.net.pi.runtime.PiAction; |
| import org.onosproject.net.pi.runtime.PiActionParam; |
| import org.onosproject.net.pi.service.PiPipeconfService; |
| import org.slf4j.Logger; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.Optional; |
| |
| import static org.onlab.util.ImmutableByteSequence.copyFrom; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Implementation of an upgradable fabric app for the Basic pipeconf (basic.p4) |
| * with ECMP support. |
| */ |
| @Component(immediate = true) |
| public class IntDemoApp { |
| |
| private static final int DEFAULT_TEID = 0; |
| private final Logger log = getLogger(getClass()); |
| |
| private static final String SPGW_INT_PIPECONF_KEYWORD = "spgw-int"; |
| private static final String SPGW_DEVICE_KEYWORD = "leaf2"; |
| private static final Ip4Address S1U_SGW_ADDR = Ip4Address.valueOf("140.0.0.2"); |
| |
| private static final Collection<String> UE_ADDRS = ImmutableList.of( |
| "160.0.2.1", "160.0.2.2", "160.0.2.3", "160.0.2.4", "160.0.2.5"); |
| |
| private static final Map<String, Integer> SWITCH_ID_MAP = new ImmutableMap.Builder<String, Integer>() |
| .put("leaf1", 100) |
| .put("leaf2", 200) |
| .build(); |
| private static final PiTableId TBL_ID_INT_PREP = PiTableId |
| .of("int_egress.int_prep"); |
| |
| private static final PiActionId ACT_ID_INT_TRANSIT = PiActionId |
| .of("int_egress.int_transit"); |
| |
| private static final PiActionParamId ACTP_ID_SWITCH_ID = PiActionParamId |
| .of("switch_id"); |
| |
| private static final PiTableId TBL_ID_UE_FILTER = PiTableId |
| .of("spgw_ingress.ue_filter_table"); |
| private static final PiTableId TBL_ID_DL_SESS_LOOKUP = PiTableId |
| .of("spgw_ingress.dl_sess_lookup"); |
| private static final PiMatchFieldId MF_ID_IPV4_DST = PiMatchFieldId |
| .of("ipv4.dst_addr"); |
| private static final PiActionId ACT_ID_SET_DL_SESS_INFO = PiActionId |
| .of("spgw_ingress.set_dl_sess_info"); |
| private static final PiActionParamId PARAM_S1U_ENB_ADDR = PiActionParamId |
| .of("s1u_enb_addr"); |
| private static final PiActionParamId PARAM_ID_S1U_SGW_ADDR = PiActionParamId |
| .of("s1u_sgw_addr"); |
| private static final PiAction NO_ACTION = PiAction.builder() |
| .withId(PiActionId.of("NoAction")) |
| .build(); |
| private static final PiActionParamId ACTP_TEID = PiActionParamId |
| .of("teid"); |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| private DeviceService deviceService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| private FlowRuleService flowRuleService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| private CoreService coreService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| private PiPipeconfService piPipeconfService; |
| |
| private final DeviceListener deviceListener = new InternalDeviceListener(); |
| |
| private ApplicationId appId; |
| |
| @Activate |
| public void activate() { |
| log.info("Starting..."); |
| appId = coreService.registerApplication("org.onosproject.int-demo"); |
| deviceService.getDevices().forEach(d -> checkDevice(d.id())); |
| deviceService.addListener(deviceListener); |
| log.info("STARTED", appId.id()); |
| } |
| |
| @Deactivate |
| public void deactivate() { |
| log.info("Stopping..."); |
| deviceService.removeListener(deviceListener); |
| flowRuleService.removeFlowRulesById(appId); |
| log.info("STOPPED"); |
| } |
| |
| private void confIntSwitch(DeviceId deviceId) { |
| Optional<Integer> switchId = SWITCH_ID_MAP.keySet().stream() |
| .filter(k -> deviceId.toString().contains(k)) |
| .findFirst() |
| .map(SWITCH_ID_MAP::get); |
| |
| if (!switchId.isPresent()) { |
| log.error("Unknown INT switch ID for device {}", deviceId); |
| return; |
| } |
| |
| log.info("Configuring device {} for INT transit capability: switchId={}...", deviceId, switchId.get()); |
| |
| FlowRule intPrepRule = flowRuleBuilder(deviceId, TBL_ID_INT_PREP) |
| .withSelector(DefaultTrafficSelector.emptySelector()) |
| .withTreatment( |
| DefaultTrafficTreatment.builder() |
| .piTableAction( |
| PiAction.builder() |
| .withId(ACT_ID_INT_TRANSIT) |
| .withParameter(new PiActionParam( |
| ACTP_ID_SWITCH_ID, copyFrom(switchId.get()))) |
| .build()) |
| .build()) |
| .build(); |
| |
| for (String instMask : ImmutableList.of("0003", "0407")) { |
| for (int i = 0; i < 16; i++) { |
| PiTableId tableId = PiTableId |
| .of("int_egress.int_metadata_insert.int_inst_" + instMask); |
| PiMatchFieldId fmId = PiMatchFieldId |
| .of("hdr.int_header.instruction_mask_" + instMask); |
| PiActionId actionId = PiActionId |
| .of("int_egress.int_metadata_insert.int_set_header_" + instMask + "_i" + String.valueOf(i)); |
| FlowRule intIntrRule = flowRuleBuilder(deviceId, tableId) |
| .withSelector( |
| DefaultTrafficSelector.builder() |
| .matchPi( |
| PiCriterion.builder() |
| .matchExact(fmId, (byte) i) |
| .build()) |
| .build()) |
| .withTreatment( |
| DefaultTrafficTreatment.builder() |
| .piTableAction(PiAction.builder() |
| .withId(actionId) |
| .build()) |
| .build()) |
| .build(); |
| |
| flowRuleService.applyFlowRules(intPrepRule, intIntrRule); |
| } |
| } |
| } |
| |
| private void confSpgw(DeviceId deviceId) { |
| log.info("Configuring device {} for SPGW downlink processing...", deviceId); |
| UE_ADDRS.stream() |
| .map(Ip4Address::valueOf) |
| .forEach(ueAddr -> { |
| flowRuleService.applyFlowRules(ueFilterRule(deviceId, ueAddr)); |
| flowRuleService.applyFlowRules( |
| dlSessLookupRule(deviceId, ueAddr, DEFAULT_TEID, ueAddr, S1U_SGW_ADDR) |
| ); |
| }); |
| } |
| |
| private FlowRule ueFilterRule(DeviceId deviceId, Ip4Address ueAddr) { |
| final PiCriterion piCriterion = PiCriterion.builder() |
| .matchLpm(MF_ID_IPV4_DST, ueAddr.toOctets(), 32) |
| .build(); |
| |
| return flowRuleBuilder(deviceId, TBL_ID_UE_FILTER) |
| .withSelector(DefaultTrafficSelector.builder() |
| .matchPi(piCriterion) |
| .build()) |
| .withTreatment(DefaultTrafficTreatment.builder() |
| .piTableAction(NO_ACTION) |
| .build()) |
| .build(); |
| } |
| |
| private FlowRule dlSessLookupRule(DeviceId deviceId, Ip4Address ueAddr, long teid, |
| Ip4Address enbAddr, Ip4Address s1uAddr) { |
| |
| // FIXME: the long teid is obtained from the wrong byte order |
| ImmutableByteSequence teidBs = copyFrom(teid); |
| byte[] byteArray = teidBs.asArray(); |
| ArrayUtils.reverse(byteArray); |
| byte[] trimmedArray = Arrays.copyOfRange(byteArray, 0, 4); |
| ImmutableByteSequence revTeidBs = copyFrom(trimmedArray); |
| |
| // Add rule to |
| final PiAction action = PiAction.builder() |
| .withId(ACT_ID_SET_DL_SESS_INFO) |
| .withParameter(new PiActionParam(ACTP_TEID, revTeidBs)) |
| .withParameter(new PiActionParam(PARAM_S1U_ENB_ADDR, |
| copyFrom(enbAddr.toOctets()))) |
| .withParameter(new PiActionParam(PARAM_ID_S1U_SGW_ADDR, |
| copyFrom(s1uAddr.toOctets()))) |
| .build(); |
| |
| final PiCriterion piCriterion = PiCriterion.builder() |
| .matchExact(MF_ID_IPV4_DST, ueAddr.toOctets()) |
| .build(); |
| |
| return flowRuleBuilder(deviceId, TBL_ID_DL_SESS_LOOKUP) |
| .withSelector(DefaultTrafficSelector.builder() |
| .matchPi(piCriterion) |
| .build()) |
| .withTreatment(DefaultTrafficTreatment.builder() |
| .piTableAction(action) |
| .build()) |
| .build(); |
| } |
| |
| /** |
| * Returns a new, pre-configured flow rule builder. |
| * |
| * @param did a device id |
| * @param tableId a table id |
| * @return a new flow rule builder |
| */ |
| private FlowRule.Builder flowRuleBuilder(DeviceId did, PiTableId tableId) { |
| return DefaultFlowRule.builder() |
| .forDevice(did) |
| .forTable(tableId) |
| .fromApp(appId) |
| .withPriority(0) |
| .makePermanent(); |
| } |
| |
| private void checkDevice(DeviceId deviceId) { |
| // If device has pipeconf spgw-int |
| Optional<PiPipeconfId> pipeconf = piPipeconfService.ofDevice(deviceId); |
| |
| if (!pipeconf.isPresent() || !pipeconf.get().id().contains(SPGW_INT_PIPECONF_KEYWORD)) { |
| log.info("Device {} has no pipeconf associated or pipeconf is not SPGW/INT-capable, ignoring", deviceId); |
| return; |
| } |
| |
| // confIntSwitch(deviceId); |
| |
| if (deviceId.toString().contains(SPGW_DEVICE_KEYWORD)) { |
| confSpgw(deviceId); |
| } |
| } |
| |
| private class InternalDeviceListener implements DeviceListener { |
| |
| @Override |
| public void event(DeviceEvent event) { |
| if (event.type() != DeviceEvent.Type.DEVICE_ADDED) { |
| return; |
| } |
| DeviceId deviceId = event.subject().id(); |
| checkDevice(deviceId); |
| |
| } |
| } |
| } |