blob: 82a5bacb35222d147f332f7e2e6544919c27f8e5 [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.onosproject.openstacknetworking.switching;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
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.IpPrefix;
import org.onlab.packet.TpPort;
import org.onlab.util.Tools;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
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.openstackinterface.OpenstackInterfaceService;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstackinterface.OpenstackSecurityGroup;
import org.onosproject.openstackinterface.OpenstackSecurityGroupRule;
import org.onosproject.openstacknetworking.OpenstackSecurityGroupService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.onosproject.openstacknetworking.Constants.*;
/**
* Populates flows rules for Security Groups of VMs.
*
*/
@Component(immediate = true)
@Service
public class OpenstackSecurityGroupRulePopulator extends AbstractVmHandler
implements OpenstackSecurityGroupService {
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackInterfaceService openstackService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
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 final Map<Host, Set<SecurityGroupRule>> securityGroupRuleMap = Maps.newConcurrentMap();
@Activate
protected void activate() {
super.activate();
}
@Deactivate
protected void deactivate() {
super.deactivate();
}
@Override
public void updateSecurityGroup(OpenstackPort osPort) {
if (!osPort.status().equals(OpenstackPort.PortStatus.ACTIVE)) {
return;
}
Optional<Host> host = getVmByPortId(osPort.id());
if (!host.isPresent()) {
log.warn("No host found with {}", osPort);
return;
}
eventExecutor.execute(() -> updateSecurityGroupRules(host.get(), true));
}
/**
* Populates security group rules for all VMs in the supplied tenant ID.
* VMs in the same tenant tend to be engaged to each other by sharing the
* same security groups or setting the remote to another security group.
* To make the implementation simpler and robust, it tries to reinstall
* security group rules for all the VMs in the same tenant whenever a new
* VM is detected or port is updated.
*
* @param tenantId tenant id to update security group rules
*/
private void populateSecurityGroupRules(String tenantId, boolean install) {
securityGroupRuleMap.entrySet().stream()
.filter(entry -> getTenantId(entry.getKey()).equals(tenantId))
.forEach(entry -> {
Host local = entry.getKey();
entry.getValue().stream().forEach(sgRule -> {
setSecurityGroupRule(local.location().deviceId(),
sgRule.rule(),
getIp(local),
sgRule.remoteIp(), install);
});
});
log.debug("Updated security group rules for {}", tenantId);
}
private void setSecurityGroupRule(DeviceId deviceId, OpenstackSecurityGroupRule sgRule,
Ip4Address vmIp, IpPrefix remoteIp,
boolean install) {
ForwardingObjective.Builder foBuilder = buildFlowObjective(sgRule, vmIp, remoteIp);
if (foBuilder == null) {
return;
}
if (install) {
flowObjectiveService.forward(deviceId, foBuilder.add());
} else {
flowObjectiveService.forward(deviceId, foBuilder.remove());
}
}
private ForwardingObjective.Builder buildFlowObjective(OpenstackSecurityGroupRule 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(ACL_RULE_PRIORITY)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(appId);
}
private void buildMatchs(TrafficSelector.Builder sBuilder, OpenstackSecurityGroupRule sgRule,
Ip4Address vmIp, IpPrefix remoteIp) {
buildMatchEthType(sBuilder, sgRule.ethertype());
buildMatchDirection(sBuilder, sgRule.direction(), vmIp);
buildMatchProto(sBuilder, sgRule.protocol());
buildMatchPort(sBuilder, sgRule.protocol(), sgRule.direction(),
sgRule.portRangeMax(), sgRule.portRangeMin());
buildMatchRemoteIp(sBuilder, remoteIp, sgRule.direction());
}
private void buildMatchDirection(TrafficSelector.Builder sBuilder,
OpenstackSecurityGroupRule.Direction direction,
Ip4Address vmIp) {
if (direction.equals(OpenstackSecurityGroupRule.Direction.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,
OpenstackSecurityGroupRule.Direction direction) {
if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
if (direction.equals(OpenstackSecurityGroupRule.Direction.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,
OpenstackSecurityGroupRule.Direction direction,
int portMin, int portMax) {
if (portMin > 0 && portMax > 0 && portMin == portMax) {
if (protocol.toUpperCase().equals(PROTO_TCP)) {
if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
sBuilder.matchTcpDst(TpPort.tpPort(portMax));
} else {
sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
}
} else if (protocol.toUpperCase().equals(PROTO_UDP)) {
if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
sBuilder.matchUdpDst(TpPort.tpPort(portMax));
} else {
sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
}
}
}
}
private void updateSecurityGroupRulesMap(Host host) {
OpenstackPort osPort = openstackService.port(host.annotations().value(PORT_ID));
if (osPort == null) {
log.debug("Failed to get OpenStack port information for {}", host);
return;
}
Set<SecurityGroupRule> rules = Sets.newHashSet();
osPort.securityGroups().stream().forEach(sgId -> {
OpenstackSecurityGroup osSecGroup = openstackService.securityGroup(sgId);
if (osSecGroup != null) {
osSecGroup.rules().stream().forEach(rule -> rules.addAll(getSgRules(rule)));
} else {
// TODO handle the case that the security group removed
log.warn("Failed to get security group {}", sgId);
}
});
securityGroupRuleMap.put(host, rules);
}
/**
* Returns set of security group rules with individual remote IP by
* converting remote group to actual IP address.
*
* @param sgRule security group rule
* @return set of security group rules
*/
private Set<SecurityGroupRule> getSgRules(OpenstackSecurityGroupRule sgRule) {
Set<SecurityGroupRule> sgRules = Sets.newHashSet();
if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().equals("null")) {
sgRules = getRemoteIps(sgRule.tenantId(), sgRule.remoteGroupId())
.stream()
.map(remoteIp -> new SecurityGroupRule(sgRule, remoteIp))
.collect(Collectors.toSet());
} else {
sgRules.add(new SecurityGroupRule(sgRule, sgRule.remoteIpPrefix()));
}
return sgRules;
}
/**
* 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 in ip prefix format
*/
private Set<IpPrefix> getRemoteIps(String tenantId, String sgId) {
Set<IpPrefix> remoteIps = Sets.newHashSet();
securityGroupRuleMap.entrySet().stream()
.filter(entry -> Objects.equals(getTenantId(entry.getKey()), tenantId))
.forEach(entry -> {
if (entry.getValue().stream()
.anyMatch(rule -> rule.rule().secuityGroupId().equals(sgId))) {
remoteIps.add(IpPrefix.valueOf(getIp(entry.getKey()), 32));
}
});
return remoteIps;
}
private void updateSecurityGroupRules(Host host, boolean isHostAdded) {
String tenantId = getTenantId(host);
populateSecurityGroupRules(tenantId, false);
if (isHostAdded) {
updateSecurityGroupRulesMap(host);
} else {
securityGroupRuleMap.remove(host);
}
Tools.stream(hostService.getHosts())
.filter(h -> Objects.equals(getTenantId(h), getTenantId(host)))
.forEach(this::updateSecurityGroupRulesMap);
populateSecurityGroupRules(tenantId, true);
}
@Override
protected void hostDetected(Host host) {
updateSecurityGroupRules(host, true);
log.info("Applied security group rules for {}", host);
}
@Override
protected void hostRemoved(Host host) {
updateSecurityGroupRules(host, false);
log.info("Applied security group rules for {}", host);
}
private final class SecurityGroupRule {
private final OpenstackSecurityGroupRule rule;
private final IpPrefix remoteIp;
private SecurityGroupRule(OpenstackSecurityGroupRule rule, IpPrefix remoteIp) {
this.rule = rule;
this.remoteIp = remoteIp;
}
private OpenstackSecurityGroupRule rule() {
return rule;
}
private IpPrefix remoteIp() {
return remoteIp;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof SecurityGroupRule) {
SecurityGroupRule that = (SecurityGroupRule) obj;
if (Objects.equals(rule, that.rule) &&
Objects.equals(remoteIp, that.remoteIp)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(rule, remoteIp);
}
}
}