blob: 1c34a62a25dc98ba651b5a2b629b093bf97de756 [file] [log] [blame]
/*
* Copyright 2015 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.onosproject.cordvtn;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.VlanId;
import org.onlab.util.ItemNotFoundException;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.DefaultGroupId;
import org.onosproject.core.GroupId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.driver.DefaultDriverData;
import org.onosproject.net.driver.DefaultDriverHandler;
import org.onosproject.net.driver.Driver;
import org.onosproject.net.driver.DriverHandler;
import org.onosproject.net.driver.DriverService;
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.criteria.Criterion;
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.flow.criteria.PortCriterion;
import org.onosproject.net.flow.instructions.ExtensionPropertyException;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
import org.onosproject.net.group.DefaultGroupBucket;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.DefaultGroupKey;
import org.onosproject.net.group.Group;
import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupBuckets;
import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupKey;
import org.onosproject.net.group.GroupService;
import org.onosproject.openstackinterface.OpenstackNetwork;
import org.onosproject.openstackinterface.OpenstackSubnet;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.flow.criteria.Criterion.Type.IN_PORT;
import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_SRC;
import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_DST;
import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Populates rules for CORD VTN service.
*/
public class CordVtnRuleInstaller {
protected final Logger log = getLogger(getClass());
private static final int TABLE_FIRST = 0;
private static final int TABLE_IN_PORT = 1;
private static final int TABLE_ACCESS_TYPE = 2;
private static final int TABLE_IN_SERVICE = 3;
private static final int TABLE_DST_IP = 4;
private static final int TABLE_TUNNEL_IN = 5;
private static final int TABLE_Q_IN_Q = 6;
private static final int MANAGEMENT_PRIORITY = 55000;
private static final int VSG_PRIORITY = 55000;
private static final int HIGH_PRIORITY = 50000;
private static final int DEFAULT_PRIORITY = 5000;
private static final int LOW_PRIORITY = 4000;
private static final int LOWEST_PRIORITY = 0;
private static final int VXLAN_UDP_PORT = 4789;
private static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
private static final String PORT_NAME = "portName";
private static final String DATA_PLANE_INTF = "dataPlaneIntf";
private static final String S_TAG = "stag";
private final ApplicationId appId;
private final FlowRuleService flowRuleService;
private final DeviceService deviceService;
private final DriverService driverService;
private final GroupService groupService;
private final NetworkConfigRegistry configRegistry;
private final String tunnelType;
/**
* Creates a new rule populator.
*
* @param appId application id
* @param flowRuleService flow rule service
* @param deviceService device service
* @param driverService driver service
* @param groupService group service
* @param configRegistry config registry
* @param tunnelType tunnel type
*/
public CordVtnRuleInstaller(ApplicationId appId,
FlowRuleService flowRuleService,
DeviceService deviceService,
DriverService driverService,
GroupService groupService,
NetworkConfigRegistry configRegistry,
String tunnelType) {
this.appId = appId;
this.flowRuleService = flowRuleService;
this.deviceService = deviceService;
this.driverService = driverService;
this.groupService = groupService;
this.configRegistry = configRegistry;
this.tunnelType = checkNotNull(tunnelType);
}
/**
* Installs table miss rule to a give device.
*
* @param deviceId device id to install the rules
* @param dpIntf data plane interface name
* @param dpIp data plane ip address
*/
public void init(DeviceId deviceId, String dpIntf, IpAddress dpIp) {
// default is drop packets which can be accomplished without
// a table miss entry for all table.
PortNumber tunnelPort = getTunnelPort(deviceId);
PortNumber dpPort = getDpPort(deviceId, dpIntf);
processFirstTable(deviceId, dpPort, dpIp);
processInPortTable(deviceId, tunnelPort, dpPort);
processAccessTypeTable(deviceId, dpPort);
processQInQTable(deviceId, dpPort);
}
/**
* Flush flows installed by this application.
*/
public void flushRules() {
flowRuleService.getFlowRulesById(appId).forEach(flowRule -> processFlowRule(false, flowRule));
}
/**
* Populates basic rules that connect a VM to the other VMs in the system.
*
* @param host host
* @param tunnelIp tunnel ip
* @param vNet openstack network
*/
public void populateBasicConnectionRules(Host host, IpAddress tunnelIp, OpenstackNetwork vNet) {
checkNotNull(host);
checkNotNull(vNet);
DeviceId deviceId = host.location().deviceId();
PortNumber inPort = host.location().port();
MacAddress dstMac = host.mac();
IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
long tunnelId = Long.parseLong(vNet.segmentId());
OpenstackSubnet subnet = vNet.subnets().stream()
.findFirst()
.orElse(null);
if (subnet == null) {
log.error("Failed to get subnet for {}", host.id());
return;
}
populateLocalInPortRule(deviceId, inPort, hostIp);
populateDirectAccessRule(Ip4Prefix.valueOf(subnet.cidr()), Ip4Prefix.valueOf(subnet.cidr()));
populateServiceIsolationRule(Ip4Prefix.valueOf(subnet.cidr()));
populateDstIpRule(deviceId, inPort, dstMac, hostIp, tunnelId, tunnelIp);
populateTunnelInRule(deviceId, inPort, dstMac, tunnelId);
}
/**
* Removes all rules related to a given service VM host.
*
* @param host host to be removed
*/
public void removeBasicConnectionRules(Host host) {
checkNotNull(host);
DeviceId deviceId = host.location().deviceId();
MacAddress mac = host.mac();
PortNumber port = host.location().port();
IpAddress ip = host.ipAddresses().stream().findFirst().orElse(null);
for (FlowRule flowRule : flowRuleService.getFlowRulesById(appId)) {
if (flowRule.deviceId().equals(deviceId)) {
PortNumber inPort = getInPort(flowRule);
if (inPort != null && inPort.equals(port)) {
processFlowRule(false, flowRule);
continue;
}
PortNumber output = getOutputFromTreatment(flowRule);
if (output != null && output.equals(host.location().port())) {
processFlowRule(false, flowRule);
}
}
MacAddress dstMac = getDstMacFromTreatment(flowRule);
if (dstMac != null && dstMac.equals(mac)) {
processFlowRule(false, flowRule);
continue;
}
dstMac = getDstMacFromSelector(flowRule);
if (dstMac != null && dstMac.equals(mac)) {
processFlowRule(false, flowRule);
continue;
}
IpPrefix dstIp = getDstIpFromSelector(flowRule);
if (dstIp != null && dstIp.equals(ip.toIpPrefix())) {
processFlowRule(false, flowRule);
}
}
// TODO uninstall same network access rule in access table if no vm exists in the network
}
/**
* Populates service dependency rules.
*
* @param tService tenant cord service
* @param pService provider cord service
* @param isBidirectional true to enable bidirectional connection between two services
*/
public void populateServiceDependencyRules(CordService tService, CordService pService,
boolean isBidirectional) {
checkNotNull(tService);
checkNotNull(pService);
Ip4Prefix srcRange = tService.serviceIpRange().getIp4Prefix();
Ip4Prefix dstRange = pService.serviceIpRange().getIp4Prefix();
Ip4Address serviceIp = pService.serviceIp().getIp4Address();
Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
getVirtualSwitches().stream().forEach(deviceId -> {
GroupId groupId = createServiceGroup(deviceId, pService);
outGroups.put(deviceId, groupId);
Set<PortNumber> vms = tService.hosts().keySet()
.stream()
.filter(host -> host.location().deviceId().equals(deviceId))
.map(host -> host.location().port())
.collect(Collectors.toSet());
inPorts.put(deviceId, vms);
});
populateIndirectAccessRule(srcRange, serviceIp, outGroups);
populateDirectAccessRule(srcRange, dstRange);
if (isBidirectional) {
populateDirectAccessRule(dstRange, srcRange);
}
populateInServiceRule(inPorts, outGroups);
}
/**
* Removes service dependency rules.
*
* @param tService tenant cord service
* @param pService provider cord service
*/
public void removeServiceDependencyRules(CordService tService, CordService pService) {
checkNotNull(tService);
checkNotNull(pService);
Ip4Prefix srcRange = tService.serviceIpRange().getIp4Prefix();
Ip4Prefix dstRange = pService.serviceIpRange().getIp4Prefix();
IpPrefix serviceIp = pService.serviceIp().toIpPrefix();
Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
GroupKey groupKey = new DefaultGroupKey(pService.id().id().getBytes());
getVirtualSwitches().stream().forEach(deviceId -> {
Group group = groupService.getGroup(deviceId, groupKey);
if (group != null) {
outGroups.put(deviceId, group.id());
}
});
for (FlowRule flowRule : flowRuleService.getFlowRulesById(appId)) {
IpPrefix dstIp = getDstIpFromSelector(flowRule);
IpPrefix srcIp = getSrcIpFromSelector(flowRule);
if (dstIp != null && dstIp.equals(serviceIp)) {
processFlowRule(false, flowRule);
continue;
}
if (dstIp != null && srcIp != null) {
if (dstIp.equals(dstRange) && srcIp.equals(srcRange)) {
processFlowRule(false, flowRule);
continue;
}
if (dstIp.equals(srcRange) && srcIp.equals(dstRange)) {
processFlowRule(false, flowRule);
continue;
}
}
GroupId groupId = getGroupIdFromTreatment(flowRule);
if (groupId != null && groupId.equals(outGroups.get(flowRule.deviceId()))) {
processFlowRule(false, flowRule);
}
}
// TODO remove the group if it is not in use
}
/**
* Updates group buckets for a given service to all devices.
*
* @param service cord service
*/
public void updateServiceGroup(CordService service) {
checkNotNull(service);
GroupKey groupKey = getGroupKey(service.id());
for (DeviceId deviceId : getVirtualSwitches()) {
Group group = groupService.getGroup(deviceId, groupKey);
if (group == null) {
log.trace("No group exists for service {} in {}, do nothing.", service.id(), deviceId);
continue;
}
List<GroupBucket> oldBuckets = group.buckets().buckets();
List<GroupBucket> newBuckets = getServiceGroupBuckets(
deviceId, service.segmentationId(), service.hosts()).buckets();
if (oldBuckets.equals(newBuckets)) {
continue;
}
List<GroupBucket> bucketsToRemove = new ArrayList<>(oldBuckets);
bucketsToRemove.removeAll(newBuckets);
if (!bucketsToRemove.isEmpty()) {
groupService.removeBucketsFromGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToRemove),
groupKey, appId);
}
List<GroupBucket> bucketsToAdd = new ArrayList<>(newBuckets);
bucketsToAdd.removeAll(oldBuckets);
if (!bucketsToAdd.isEmpty()) {
groupService.addBucketsToGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToAdd),
groupKey, appId);
}
}
}
/**
* Populates flow rules for management network access.
*
* @param host host which has management network interface
* @param mService management network service
*/
public void populateManagementNetworkRules(Host host, CordService mService) {
checkNotNull(mService);
DeviceId deviceId = host.location().deviceId();
IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(mService.serviceIp().getIp4Address())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(hostIp.getIp4Address())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(host.location().port())
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(mService.serviceIpRange())
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(mService.serviceIp().toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Removes management network access rules.
*
* @param host host to be removed
* @param mService service for management network
*/
public void removeManagementNetworkRules(Host host, CordService mService) {
checkNotNull(mService);
// TODO remove management network specific rules
}
/**
* Populates rules for vSG VM.
*
* @param vSgHost vSG host
* @param vSgIps set of ip addresses of vSGs running inside the vSG VM
*/
public void populateSubscriberGatewayRules(Host vSgHost, Set<IpAddress> vSgIps) {
VlanId serviceVlan = getServiceVlan(vSgHost);
PortNumber dpPort = getDpPort(vSgHost);
if (serviceVlan == null || dpPort == null) {
log.warn("Failed to populate rules for vSG VM {}", vSgHost.id());
return;
}
// for traffics with s-tag, strip the tag and take through the vSG VM
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchVlanId(serviceVlan)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(vSgHost.location().port())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSgHost.location().deviceId())
.forTable(TABLE_Q_IN_Q)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// for traffics with customer vlan, tag with the service vlan based on input port with
// lower priority to avoid conflict with WAN tag
selector = DefaultTrafficSelector.builder()
.matchInPort(vSgHost.location().port())
.matchVlanId(serviceVlan)
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSgHost.location().deviceId())
.forTable(TABLE_Q_IN_Q)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// for traffic coming from WAN, tag 500 and take through the vSG VM
// based on destination ip
vSgIps.stream().forEach(ip -> {
TrafficSelector downstream = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(ip.toIpPrefix())
.build();
TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(VLAN_WAN)
.setEthDst(vSgHost.mac())
.setOutput(vSgHost.location().port())
.build();
FlowRule downstreamFlowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(downstream)
.withTreatment(downstreamTreatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSgHost.location().deviceId())
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
processFlowRule(true, downstreamFlowRule);
});
// remove downstream flow rules for the vSG not shown in vSgIps
for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
if (!rule.deviceId().equals(vSgHost.location().deviceId())) {
continue;
}
PortNumber output = getOutputFromTreatment(rule);
if (output == null || !output.equals(vSgHost.location().port()) ||
!isVlanPushFromTreatment(rule)) {
continue;
}
IpPrefix dstIp = getDstIpFromSelector(rule);
if (dstIp != null && !vSgIps.contains(dstIp.address())) {
processFlowRule(false, rule);
}
}
}
/**
* Populates default rules on the first table.
* It includes the rules for shuttling vxlan-encapped packets between ovs and
* linux stack,and external network connectivity.
*
* @param deviceId device id
* @param dpPort data plane interface port number
* @param dpIp data plane ip address
*/
private void processFirstTable(DeviceId deviceId, PortNumber dpPort, IpAddress dpIp) {
// take vxlan packet out onto the physical port
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a vxlan encap'd packet through the Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.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(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a packet to the data plane ip through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dpIp.toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take an arp packet from physical through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(dpIp.getIp4Address())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take all else 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(LOWEST_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take all vlan tagged packet to the Q_IN_Q table
selector = DefaultTrafficSelector.builder()
.matchVlanId(VlanId.ANY)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_Q_IN_Q)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(VSG_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Forward table miss packets in ACCESS_TYPE table to data plane port.
*
* @param deviceId device id
* @param dpPort data plane interface port number
*/
private void processAccessTypeTable(DeviceId deviceId, PortNumber dpPort) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(LOWEST_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates default rules for IN_PORT table.
* All packets from tunnel port are forwarded to TUNNEL_ID table and all packets
* from data plane interface port to ACCESS_TYPE table.
*
* @param deviceId device id to install the rules
* @param tunnelPort tunnel port number
* @param dpPort data plane interface port number
*/
private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dpPort) {
checkNotNull(tunnelPort);
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(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates default rules for Q_IN_Q table.
*
* @param deviceId device id
* @param dpPort data plane interface port number
*/
private void processQInQTable(DeviceId deviceId, PortNumber dpPort) {
// 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(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_Q_IN_Q)
.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(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_Q_IN_Q)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates rules for local in port in IN_PORT table.
* Flows from a given in port, whose source IP is service IP transition
* to DST_TYPE table. Other flows transition to IN_SERVICE table.
*
* @param deviceId device id to install the rules
* @param inPort in port
* @param srcIp source ip
*/
private void populateLocalInPortRule(DeviceId deviceId, PortNumber inPort, IpAddress srcIp) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(inPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcIp.toIpPrefix())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_ACCESS_TYPE)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(inPort)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_IN_SERVICE)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(LOW_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates direct VM access rules for ACCESS_TYPE table.
* These rules are installed to all devices.
*
* @param srcRange source ip range
* @param dstRange destination ip range
*/
private void populateDirectAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcRange)
.matchIPDst(dstRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
getVirtualSwitches().stream().forEach(deviceId -> {
FlowRule flowRuleDirect = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRuleDirect);
});
}
/**
* Populates drop rules that does not match any direct access rules but has
* destination to a different service network in ACCESS_TYPE table.
*
* @param dstRange destination ip range
*/
private void populateServiceIsolationRule(Ip4Prefix dstRange) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dstRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.drop()
.build();
getVirtualSwitches().stream().forEach(deviceId -> {
FlowRule flowRuleDirect = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(LOW_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRuleDirect);
});
}
/**
* Populates indirect service access rules for ACCESS_TYPE table.
* These rules are installed to all devices.
*
* @param srcRange source range
* @param serviceIp service ip
* @param outGroups list of output group
*/
private void populateIndirectAccessRule(Ip4Prefix srcRange, Ip4Address serviceIp,
Map<DeviceId, GroupId> outGroups) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcRange)
.matchIPDst(serviceIp.toIpPrefix())
.build();
for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.group(outGroup.getValue())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(outGroup.getKey())
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
}
/**
* Populates flow rules for IN_SERVICE table.
*
* @param inPorts list of inports related to the service for each device
* @param outGroups set of output groups
*/
private void populateInServiceRule(Map<DeviceId, Set<PortNumber>> inPorts, Map<DeviceId, GroupId> outGroups) {
checkNotNull(inPorts);
checkNotNull(outGroups);
for (Map.Entry<DeviceId, Set<PortNumber>> entry : inPorts.entrySet()) {
Set<PortNumber> ports = entry.getValue();
DeviceId deviceId = entry.getKey();
GroupId groupId = outGroups.get(deviceId);
if (groupId == null) {
continue;
}
ports.stream().forEach(port -> {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(port)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.group(groupId)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_SERVICE)
.makePermanent()
.build();
processFlowRule(true, flowRule);
});
}
}
/**
* Populates flow rules for DST_IP table.
*
* @param deviceId device id
* @param inPort in port
* @param dstMac mac address
* @param dstIp destination ip
* @param tunnelId tunnel id
* @param tunnelIp tunnel remote ip
*/
private void populateDstIpRule(DeviceId deviceId, PortNumber inPort, MacAddress dstMac,
IpAddress dstIp, long tunnelId, IpAddress tunnelIp) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dstIp.toIpPrefix())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setEthDst(dstMac)
.setOutput(inPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
processFlowRule(true, flowRule);
for (DeviceId vSwitchId : getVirtualSwitches()) {
if (vSwitchId.equals(deviceId)) {
continue;
}
ExtensionTreatment tunnelDst = getTunnelDst(vSwitchId, tunnelIp.getIp4Address());
if (tunnelDst == null) {
continue;
}
treatment = DefaultTrafficTreatment.builder()
.setEthDst(dstMac)
.setTunnelId(tunnelId)
.extension(tunnelDst, vSwitchId)
.setOutput(getTunnelPort(vSwitchId))
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSwitchId)
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
}
/**
* Populates flow rules for TUNNEL_ID table.
*
* @param deviceId device id
* @param inPort in port
* @param mac mac address
* @param tunnelId tunnel id
*/
private void populateTunnelInRule(DeviceId deviceId, PortNumber inPort, MacAddress mac, long tunnelId) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchTunnelId(tunnelId)
.matchEthDst(mac)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(inPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_TUNNEL_IN)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Installs or uninstall a given rule.
*
* @param install true to install, false to uninstall
* @param rule rule
*/
private 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()));
}
}));
}
/**
* Returns tunnel port of the device.
*
* @param deviceId device id
* @return tunnel port number, or null if no tunnel port exists on a given device
*/
private PortNumber getTunnelPort(DeviceId deviceId) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).contains(tunnelType))
.findFirst().orElse(null);
return port == null ? null : port.number();
}
/**
* Returns data plane interface port name of a given device.
*
* @param deviceId device id
* @param dpIntf data plane interface port name
* @return data plane interface port number, or null if no such port exists
*/
private PortNumber getDpPort(DeviceId deviceId, String dpIntf) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).contains(dpIntf) &&
p.isEnabled())
.findFirst().orElse(null);
return port == null ? null : port.number();
}
/** Returns data plane interface port number of a given host.
*
* @param host host
* @return port number, or null
*/
private PortNumber getDpPort(Host host) {
String portName = host.annotations().value(DATA_PLANE_INTF);
return portName == null ? null : getDpPort(host.location().deviceId(), portName);
}
/**
* Returns service vlan from a given host.
*
* @param host host
* @return vlan id, or null
*/
private VlanId getServiceVlan(Host host) {
String serviceVlan = host.annotations().value(S_TAG);
return serviceVlan == null ? null : VlanId.vlanId(Short.parseShort(serviceVlan));
}
/**
* Returns the inport from a given flow rule if the rule contains the match of it.
*
* @param flowRule flow rule
* @return port number, or null if the rule doesn't have inport match
*/
private PortNumber getInPort(FlowRule flowRule) {
Criterion criterion = flowRule.selector().getCriterion(IN_PORT);
if (criterion != null && criterion instanceof PortCriterion) {
PortCriterion port = (PortCriterion) criterion;
return port.port();
} else {
return null;
}
}
/**
* Returns the destination mac address from a given flow rule if the rule
* contains the instruction of it.
*
* @param flowRule flow rule
* @return mac address, or null if the rule doesn't have destination mac instruction
*/
private MacAddress getDstMacFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof ModEtherInstruction &&
((ModEtherInstruction) inst).subtype().equals(ETH_DST))
.findFirst()
.orElse(null);
if (instruction == null) {
return null;
}
return ((ModEtherInstruction) instruction).mac();
}
/**
* Returns the destination mac address from a given flow rule if the rule
* contains the match of it.
*
* @param flowRule flow rule
* @return mac address, or null if the rule doesn't have destination mac match
*/
private MacAddress getDstMacFromSelector(FlowRule flowRule) {
Criterion criterion = flowRule.selector().getCriterion(Criterion.Type.ETH_DST);
if (criterion != null && criterion instanceof EthCriterion) {
EthCriterion eth = (EthCriterion) criterion;
return eth.mac();
} else {
return null;
}
}
/**
* Returns the destination IP from a given flow rule if the rule contains
* the match of it.
*
* @param flowRule flow rule
* @return ip prefix, or null if the rule doesn't have ip match
*/
private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
if (criterion != null && criterion instanceof IPCriterion) {
IPCriterion ip = (IPCriterion) criterion;
return ip.ip();
} else {
return null;
}
}
/**
* Returns the source IP from a given flow rule if the rule contains
* the match of it.
*
* @param flowRule flow rule
* @return ip prefix, or null if the rule doesn't have ip match
*/
private IpPrefix getSrcIpFromSelector(FlowRule flowRule) {
Criterion criterion = flowRule.selector().getCriterion(IPV4_SRC);
if (criterion != null && criterion instanceof IPCriterion) {
IPCriterion ip = (IPCriterion) criterion;
return ip.ip();
} else {
return null;
}
}
/**
* Returns the group ID from a given flow rule if the rule contains the
* treatment of it.
*
* @param flowRule flow rule
* @return group id, or null if the rule doesn't have group instruction
*/
private GroupId getGroupIdFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof Instructions.GroupInstruction)
.findFirst()
.orElse(null);
if (instruction == null) {
return null;
}
return ((Instructions.GroupInstruction) instruction).groupId();
}
/**
* Returns the output port number from a given flow rule.
*
* @param flowRule flow rule
* @return port number, or null if the rule does not have output instruction
*/
private PortNumber getOutputFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof Instructions.OutputInstruction)
.findFirst()
.orElse(null);
if (instruction == null) {
return null;
}
return ((Instructions.OutputInstruction) instruction).port();
}
/**
* Returns if a given flow rule has vlan push instruction or not.
*
* @param flowRule flow rule
* @return true if it includes vlan push, or false
*/
private boolean isVlanPushFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof L2ModificationInstruction)
.filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
.findAny()
.orElse(null);
return instruction != null;
}
/**
* Creates a new group for a given service.
*
* @param deviceId device id to create a group
* @param service cord service
* @return group id, or null if it fails to create
*/
private GroupId createServiceGroup(DeviceId deviceId, CordService service) {
checkNotNull(service);
GroupKey groupKey = getGroupKey(service.id());
Group group = groupService.getGroup(deviceId, groupKey);
GroupId groupId = getGroupId(service.id(), deviceId);
if (group != null) {
log.debug("Group {} is already exist in {}", service.id(), deviceId);
return groupId;
}
GroupBuckets buckets = getServiceGroupBuckets(deviceId, service.segmentationId(), service.hosts());
GroupDescription groupDescription = new DefaultGroupDescription(
deviceId,
GroupDescription.Type.SELECT,
buckets,
groupKey,
groupId.id(),
appId);
groupService.addGroup(groupDescription);
return groupId;
}
/**
* Returns group buckets for a given device.
*
* @param deviceId device id
* @param tunnelId tunnel id
* @param hosts list of host
* @return group buckets
*/
private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId, Map<Host, IpAddress> hosts) {
List<GroupBucket> buckets = Lists.newArrayList();
for (Map.Entry<Host, IpAddress> entry : hosts.entrySet()) {
Host host = entry.getKey();
Ip4Address remoteIp = entry.getValue().getIp4Address();
DeviceId hostDevice = host.location().deviceId();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
.builder()
.setEthDst(host.mac());
if (deviceId.equals(hostDevice)) {
tBuilder.setOutput(host.location().port());
} else {
ExtensionTreatment tunnelDst = getTunnelDst(deviceId, remoteIp);
if (tunnelDst == null) {
continue;
}
tBuilder.extension(tunnelDst, deviceId)
.setTunnelId(tunnelId)
.setOutput(getTunnelPort(hostDevice));
}
buckets.add(DefaultGroupBucket.createSelectGroupBucket(tBuilder.build()));
}
return new GroupBuckets(buckets);
}
/**
* Returns globally unique group ID.
*
* @param serviceId service id
* @param deviceId device id
* @return group id
*/
private GroupId getGroupId(CordServiceId serviceId, DeviceId deviceId) {
return new DefaultGroupId(Objects.hash(serviceId, deviceId));
}
/**
* Returns group key of a service.
*
* @param serviceId service id
* @return group key
*/
private GroupKey getGroupKey(CordServiceId serviceId) {
return new DefaultGroupKey(serviceId.id().getBytes());
}
/**
* Returns extension instruction to set tunnel destination.
*
* @param deviceId device id
* @param remoteIp tunnel destination address
* @return extension treatment or null if it fails to get instruction
*/
private ExtensionTreatment getTunnelDst(DeviceId deviceId, Ip4Address remoteIp) {
try {
Driver driver = driverService.getDriver(deviceId);
DefaultDriverData driverData = new DefaultDriverData(driver, deviceId);
DriverHandler handler = new DefaultDriverHandler(driverData);
ExtensionTreatmentResolver resolver = handler.behaviour(ExtensionTreatmentResolver.class);
ExtensionTreatment treatment =
resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
treatment.setPropertyValue("tunnelDst", remoteIp);
return treatment;
} catch (ItemNotFoundException | UnsupportedOperationException |
ExtensionPropertyException e) {
log.error("Failed to get extension instruction {}", deviceId);
return null;
}
}
/**
* Returns integration bridges configured in the system.
*
* @return set of device ids
*/
private Set<DeviceId> getVirtualSwitches() {
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found for {}", appId.name());
return Sets.newHashSet();
}
return config.cordVtnNodes().stream()
.map(CordVtnNode::intBrId).collect(Collectors.toSet());
}
}