blob: a93c414545d3ecc0d4b80479d0202eebf391356d [file] [log] [blame]
/*
* 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);
}
}
}