blob: a3f135b0cf385c56bb7a54a4236d720333d36c9d [file] [log] [blame]
/*
* Copyright 2017-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.onosproject.openstacknetworking.impl;
import com.google.common.base.Strings;
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.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.openstacknetworking.api.InstancePort;
import org.onosproject.openstacknetworking.api.InstancePortEvent;
import org.onosproject.openstacknetworking.api.InstancePortListener;
import org.onosproject.openstacknetworking.api.InstancePortService;
import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupListener;
import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
import org.openstack4j.model.network.Port;
import org.openstack4j.model.network.SecurityGroup;
import org.openstack4j.model.network.SecurityGroupRule;
import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ACL_RULE;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Populates flow rules to handle OpenStack SecurityGroups.
*/
@Component(immediate = true)
public class OpenstackSecurityGroupHandler {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected InstancePortService instancePortService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackNetworkService openstackService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackSecurityGroupService securityGroupService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
private final InstancePortListener instancePortListener = new InternalInstancePortListener();
private final OpenstackNetworkListener portListener = new InternalOpenstackPortListener();
private final OpenstackSecurityGroupListener securityGroupListener = new InternalSecurityGroupListener();
private ApplicationId appId;
private final ExecutorService eventExecutor = newSingleThreadExecutor(
groupedThreads(this.getClass().getSimpleName(), "event-handler"));
private static final String PROTO_ICMP = "ICMP";
private static final String PROTO_TCP = "TCP";
private static final String PROTO_UDP = "UDP";
private static final String ETHTYPE_IPV4 = "IPV4";
private static final String EGRESS = "EGRESS";
private static final String INGRESS = "INGRESS";
private static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
@Activate
protected void activate() {
appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
instancePortService.addListener(instancePortListener);
securityGroupService.addListener(securityGroupListener);
openstackService.addListener(portListener);
log.info("Started");
}
@Deactivate
protected void deactivate() {
instancePortService.removeListener(instancePortListener);
securityGroupService.removeListener(securityGroupListener);
openstackService.removeListener(portListener);
eventExecutor.shutdown();
log.info("Stopped");
}
private void setSecurityGroupRules(InstancePort instPort, Port port, boolean install) {
port.getSecurityGroups().forEach(sgId -> {
log.debug("security group rule ID : " + sgId.toString());
SecurityGroup sg = securityGroupService.securityGroup(sgId);
if (sg == null) {
log.error("Security Group Not Found : {}", sgId);
return;
}
sg.getRules().forEach(sgRule -> updateSecurityGroupRule(instPort, port, sgRule, install));
});
}
private void updateSecurityGroupRule(InstancePort instPort, Port port, SecurityGroupRule sgRule, boolean install) {
if (sgRule.getRemoteGroupId() != null && !sgRule.getRemoteGroupId().isEmpty()) {
getRemoteInstPorts(port.getTenantId(), sgRule.getRemoteGroupId())
.forEach(rInstPort -> {
populateSecurityGroupRule(sgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
populateSecurityGroupRule(sgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
SecurityGroupRule rSgRule = new NeutronSecurityGroupRule.SecurityGroupRuleConcreteBuilder()
.from(sgRule)
.direction(sgRule.getDirection().toUpperCase().equals(EGRESS) ? INGRESS : EGRESS).build();
populateSecurityGroupRule(rSgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
populateSecurityGroupRule(rSgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
});
} else {
populateSecurityGroupRule(sgRule, instPort, sgRule.getRemoteIpPrefix() == null ? IP_PREFIX_ANY :
IpPrefix.valueOf(sgRule.getRemoteIpPrefix()), install);
}
}
private void populateSecurityGroupRule(SecurityGroupRule sgRule, InstancePort instPort,
IpPrefix remoteIp, boolean install) {
ForwardingObjective.Builder foBuilder = buildFlowObjective(sgRule,
Ip4Address.valueOf(instPort.ipAddress().toInetAddress()), remoteIp);
if (foBuilder == null) {
return;
}
if (install) {
flowObjectiveService.forward(instPort.deviceId(), foBuilder.add());
} else {
flowObjectiveService.forward(instPort.deviceId(), foBuilder.remove());
}
}
/**
* Returns a set of host IP addresses engaged with supplied security group ID.
* It only searches a VM in the same tenant boundary.
*
* @param tenantId tenant id
* @param sgId security group id
* @return set of ip addresses
*/
private Set<InstancePort> getRemoteInstPorts(String tenantId, String sgId) {
Set<InstancePort> remoteInstPorts;
remoteInstPorts = openstackService.ports().stream()
.filter(port -> port.getTenantId().equals(tenantId))
.filter(port -> port.getSecurityGroups().contains(sgId))
.map(port -> instancePortService.instancePort(port.getId()))
.filter(instPort -> instPort != null && instPort.ipAddress() != null)
.collect(Collectors.toSet());
return Collections.unmodifiableSet(remoteInstPorts);
}
private ForwardingObjective.Builder buildFlowObjective(SecurityGroupRule sgRule,
Ip4Address vmIp,
IpPrefix remoteIp) {
if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
// do nothing if the remote IP is my IP
return null;
}
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
return DefaultForwardingObjective.builder()
.withSelector(sBuilder.build())
.withTreatment(DefaultTrafficTreatment.builder().build())
.withPriority(PRIORITY_ACL_RULE)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(appId);
}
private void buildMatchs(TrafficSelector.Builder sBuilder, SecurityGroupRule sgRule,
Ip4Address vmIp, IpPrefix remoteIp) {
buildMatchEthType(sBuilder, sgRule.getEtherType());
buildMatchDirection(sBuilder, sgRule.getDirection(), vmIp);
buildMatchProto(sBuilder, sgRule.getProtocol());
buildMatchPort(sBuilder, sgRule.getProtocol(), sgRule.getDirection(),
sgRule.getPortRangeMax() == null ? 0 : sgRule.getPortRangeMax(),
sgRule.getPortRangeMin() == null ? 0 : sgRule.getPortRangeMin());
buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
if (sgRule.getRemoteGroupId() != null && sgRule.getRemoteGroupId().isEmpty()) {
buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
}
}
private void buildMatchDirection(TrafficSelector.Builder sBuilder,
String direction,
Ip4Address vmIp) {
if (direction.toUpperCase().equals(EGRESS)) {
sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, 32));
} else {
sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, 32));
}
}
private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
// Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
sBuilder.matchEthType(Ethernet.TYPE_IPV4);
if (etherType != null && !Objects.equals(etherType, "null") &&
!etherType.toUpperCase().equals(ETHTYPE_IPV4)) {
log.debug("EthType {} is not supported yet in Security Group", etherType);
}
}
private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder, IpPrefix remoteIpPrefix, String direction) {
if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
if (direction.toUpperCase().equals(EGRESS)) {
sBuilder.matchIPDst(remoteIpPrefix);
} else {
sBuilder.matchIPSrc(remoteIpPrefix);
}
}
}
private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
if (protocol != null) {
switch (protocol.toUpperCase()) {
case PROTO_ICMP:
sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
break;
case PROTO_TCP:
sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
break;
case PROTO_UDP:
sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
break;
default:
}
}
}
private void buildMatchPort(TrafficSelector.Builder sBuilder, String protocol, String direction,
int portMin, int portMax) {
if (portMin > 0 && portMax > 0 && portMin == portMax) {
if (protocol.toUpperCase().equals(PROTO_TCP)) {
if (direction.toUpperCase().equals(EGRESS)) {
sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
} else {
sBuilder.matchTcpDst(TpPort.tpPort(portMax));
}
} else if (protocol.toUpperCase().equals(PROTO_UDP)) {
if (direction.toUpperCase().equals(EGRESS)) {
sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
} else {
sBuilder.matchUdpDst(TpPort.tpPort(portMax));
}
}
}
}
private class InternalInstancePortListener implements InstancePortListener {
@Override
public boolean isRelevant(InstancePortEvent event) {
InstancePort instPort = event.subject();
return mastershipService.isLocalMaster(instPort.deviceId());
}
@Override
public void event(InstancePortEvent event) {
InstancePort instPort = event.subject();
switch (event.type()) {
case OPENSTACK_INSTANCE_PORT_UPDATED:
case OPENSTACK_INSTANCE_PORT_DETECTED:
eventExecutor.execute(() -> {
log.info("Instance port detected MAC:{} IP:{}",
instPort.macAddress(),
instPort.ipAddress());
instPortDetected(event.subject(), openstackService.port(event.subject().portId()));
});
break;
case OPENSTACK_INSTANCE_PORT_VANISHED:
eventExecutor.execute(() -> {
log.info("Instance port vanished MAC:{} IP:{}",
instPort.macAddress(),
instPort.ipAddress());
instPortRemoved(event.subject(), openstackService.port(event.subject().portId()));
});
break;
default:
break;
}
}
private void instPortDetected(InstancePort instPort, Port port) {
setSecurityGroupRules(instPort, port, true);
}
private void instPortRemoved(InstancePort instPort, Port port) {
setSecurityGroupRules(instPort, port, false);
}
}
private class InternalOpenstackPortListener implements OpenstackNetworkListener {
@Override
public boolean isRelevant(OpenstackNetworkEvent event) {
Port osPort = event.port();
if (osPort == null) {
return false;
}
return !Strings.isNullOrEmpty(osPort.getId());
}
@Override
public void event(OpenstackNetworkEvent event) {
switch (event.type()) {
case OPENSTACK_SECURITY_GROUP_ADDED_TO_PORT:
securityGroupAddedToPort(event.securityGroupRuleIds(), event.port());
break;
case OPENSTACK_SECURITY_GROUP_REMOVED_FROM_PORT:
securityGroupRemovedFromPort(event.securityGroupRuleIds(), event.port());
break;
default:
break;
}
}
private void securityGroupAddedToPort(Collection<String> sgToAdd, Port osPort) {
sgToAdd.forEach(sg -> {
InstancePort instPort = instancePortService.instancePort(osPort.getId());
if (instPort != null) {
securityGroupService.securityGroup(sg).getRules().stream()
.forEach(sgRule -> updateSecurityGroupRule(instancePortService.instancePort(
osPort.getId()), osPort, sgRule, true));
}
});
}
private void securityGroupRemovedFromPort(Collection<String> sgToRemove, Port osPort) {
sgToRemove.forEach(sg -> {
InstancePort instPort = instancePortService.instancePort(osPort.getId());
if (instPort != null) {
securityGroupService.securityGroup(sg).getRules().stream()
.forEach(sgRule -> updateSecurityGroupRule(instancePortService.instancePort(
osPort.getId()), osPort, sgRule, false));
}
});
}
}
private class InternalSecurityGroupListener implements OpenstackSecurityGroupListener {
@Override
public void event(OpenstackSecurityGroupEvent event) {
switch (event.type()) {
case OPENSTACK_SECURITY_GROUP_CREATED:
case OPENSTACK_SECURITY_GROUP_REMOVED:
break;
case OPENSTACK_SECURITY_GROUP_RULE_CREATED:
SecurityGroupRule securityGroupRuleToAdd = event.securityGroupRule();
eventExecutor.execute(() -> {
log.info("Security group rule detected: ID {}",
securityGroupRuleToAdd.getId());
securityGroupRuleAdded(securityGroupRuleToAdd);
});
break;
case OPENSTACK_SECURITY_GROUP_RULE_REMOVED:
SecurityGroupRule securityGroupRuleToRemove = event.securityGroupRule();
eventExecutor.execute(() -> {
log.info("security gorup rule removed: ID {}",
securityGroupRuleToRemove.getId());
securityGroupRuleRemoved(securityGroupRuleToRemove);
});
break;
default:
}
}
private void securityGroupRuleAdded(SecurityGroupRule sgRule) {
log.debug("securityGroupRuleAdded : {}" + sgRule);
openstackService.ports().stream()
.filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
.forEach(port -> updateSecurityGroupRule(instancePortService.instancePort(port.getId()),
port, sgRule, true));
}
private void securityGroupRuleRemoved(SecurityGroupRule sgRule) {
log.debug("securityGroupRuleRemoved : {}" + sgRule);
openstackService.ports().stream()
.filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
.forEach(port -> updateSecurityGroupRule(instancePortService.instancePort(port.getId()),
port, sgRule, false));
}
}
}