blob: af6f27c4fdc516f44d0e4490580a2cf70755f532 [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* 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.opencord.cordvtn.impl;
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.apache.felix.scr.annotations.Service;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.VlanId;
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.Port;
import org.opencord.cordvtn.api.Constants;
import org.opencord.cordvtn.api.node.CordVtnNode;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
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.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleOperationsContext;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.ExtensionPropertyException;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.slf4j.Logger;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
import static org.opencord.cordvtn.api.Constants.DEFAULT_TUNNEL;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provides CORD VTN pipeline.
*/
@Component(immediate = true)
@Service(value = CordVtnPipeline.class)
public final class CordVtnPipeline {
protected final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
// tables
public static final int TABLE_ZERO = 0;
public static final int TABLE_IN_PORT = 1;
public static final int TABLE_ACCESS = 2;
public static final int TABLE_IN_SERVICE = 3;
public static final int TABLE_DST = 4;
public static final int TABLE_TUNNEL_IN = 5;
public static final int TABLE_VLAN = 6;
// priorities
public static final int PRIORITY_MANAGEMENT = 55000;
public static final int PRIORITY_HIGH = 50000;
public static final int PRIORITY_DEFAULT = 5000;
public static final int PRIORITY_LOW = 4000;
public static final int PRIORITY_ZERO = 0;
public static final int VXLAN_UDP_PORT = 4789;
public static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
public static final String PROPERTY_TUNNEL_DST = "tunnelDst";
private ApplicationId appId;
@Activate
protected void activate() {
appId = coreService.registerApplication(Constants.CORDVTN_APP_ID);
log.info("Started");
}
@Deactivate
protected void deactivate() {
log.info("Stopped");
}
/**
* Flush flows installed by this application.
*/
public void flushRules() {
flowRuleService.getFlowRulesById(appId).forEach(flowRule -> processFlowRule(false, flowRule));
}
/**
* Installs table miss rule to a give device.
*
* @param node cordvtn node
*/
public void initPipeline(CordVtnNode node) {
checkNotNull(node);
Optional<PortNumber> dataPort = getPortNumber(node.integrationBridgeId(), node.dataIface());
Optional<PortNumber> tunnelPort = getPortNumber(node.integrationBridgeId(), DEFAULT_TUNNEL);
if (!dataPort.isPresent() || !tunnelPort.isPresent()) {
log.warn("Node is not in COMPLETE state");
return;
}
Optional<PortNumber> hostMgmtPort = Optional.empty();
if (node.hostMgmtIface().isPresent()) {
hostMgmtPort = getPortNumber(node.integrationBridgeId(), node.hostMgmtIface().get());
}
processTableZero(node.integrationBridgeId(),
dataPort.get(),
node.dataIp().ip(),
node.localMgmtIp().ip());
processInPortTable(node.integrationBridgeId(),
tunnelPort.get(),
dataPort.get(),
hostMgmtPort);
processAccessTypeTable(node.integrationBridgeId(), dataPort.get());
processVlanTable(node.integrationBridgeId(), dataPort.get());
}
private void processTableZero(DeviceId deviceId, PortNumber dataPort, IpAddress dataIp,
IpAddress localMgmtIp) {
vxlanShuttleRule(deviceId, dataPort, dataIp);
localManagementBaseRule(deviceId, localMgmtIp.getIp4Address());
// take all vlan tagged packet to the VLAN table
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchVlanId(VlanId.ANY)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_VLAN)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_MANAGEMENT)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take all other packets to the next table
selector = DefaultTrafficSelector.builder()
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_IN_PORT)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_ZERO)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
private void vxlanShuttleRule(DeviceId deviceId, PortNumber dataPort, IpAddress dataIp) {
// take vxlan packet out onto the physical port
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dataPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a vxlan encap'd packet through the Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dataPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_UDP)
.matchUdpDst(TpPort.tpPort(VXLAN_UDP_PORT))
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a packet to the data plane ip through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dataPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dataIp.toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take an arp packet from physical through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dataPort)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(dataIp.getIp4Address())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
private void localManagementBaseRule(DeviceId deviceId, Ip4Address localMgmtIp) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(localMgmtIp)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
.forDevice(deviceId)
.forTable(CordVtnPipeline.TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(localMgmtIp.toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(CordVtnPipeline.TABLE_DST)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
.forDevice(deviceId)
.forTable(CordVtnPipeline.TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(localMgmtIp.toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
.forDevice(deviceId)
.forTable(CordVtnPipeline.TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpSpa(localMgmtIp)
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.CONTROLLER)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
.forDevice(deviceId)
.forTable(CordVtnPipeline.TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dataPort,
Optional<PortNumber> hostMgmtPort) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(tunnelPort)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_TUNNEL_IN)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(dataPort)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
if (hostMgmtPort.isPresent()) {
selector = DefaultTrafficSelector.builder()
.matchInPort(hostMgmtPort.get())
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
}
private void processAccessTypeTable(DeviceId deviceId, PortNumber dataPort) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dataPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_ZERO)
.forDevice(deviceId)
.forTable(TABLE_ACCESS)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
private void processVlanTable(DeviceId deviceId, PortNumber dataPort) {
// for traffic going out to WAN, strip vid 500 and take through data plane interface
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchVlanId(VLAN_WAN)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.popVlan()
.setOutput(dataPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_VLAN)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchVlanId(VLAN_WAN)
.matchEthType(Ethernet.TYPE_ARP)
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.CONTROLLER)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_VLAN)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
public void processFlowRule(boolean install, FlowRule rule) {
FlowRuleOperations.Builder oBuilder = FlowRuleOperations.builder();
oBuilder = install ? oBuilder.add(rule) : oBuilder.remove(rule);
flowRuleService.apply(oBuilder.build(new FlowRuleOperationsContext() {
@Override
public void onError(FlowRuleOperations ops) {
log.error(String.format("Failed %s, %s", ops.toString(), rule.toString()));
}
}));
}
public ExtensionTreatment tunnelDstTreatment(DeviceId deviceId, Ip4Address remoteIp) {
Device device = deviceService.getDevice(deviceId);
if (device != null && !device.is(ExtensionTreatmentResolver.class)) {
log.error("The extension treatment is not supported");
return null;
}
ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
ExtensionTreatment treatment = resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
try {
treatment.setPropertyValue(PROPERTY_TUNNEL_DST, remoteIp);
return treatment;
} catch (ExtensionPropertyException e) {
log.warn("Failed to get tunnelDst extension treatment for {}", deviceId);
return null;
}
}
private Optional<PortNumber> getPortNumber(DeviceId deviceId, String portName) {
PortNumber port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(AnnotationKeys.PORT_NAME).equals(portName) &&
p.isEnabled())
.map(Port::number)
.findAny()
.orElse(null);
return Optional.ofNullable(port);
}
}