| /* |
| * 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.core.ApplicationId; |
| 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.onosproject.openstacknetworking.AbstractVmHandler; |
| 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 OpenstackSecurityGroupManager 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(); |
| private ApplicationId appId; |
| |
| @Activate |
| protected void activate() { |
| super.activate(); |
| appId = coreService.registerApplication(SWITCHING_APP_ID); |
| } |
| |
| @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.debug("No host found with {}", osPort.id()); |
| 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().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.matchTcpSrc(TpPort.tpPort(portMax)); |
| } else { |
| sBuilder.matchTcpDst(TpPort.tpPort(portMax)); |
| } |
| } else if (protocol.toUpperCase().equals(PROTO_UDP)) { |
| if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) { |
| sBuilder.matchUdpSrc(TpPort.tpPort(portMax)); |
| } else { |
| sBuilder.matchUdpDst(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().forEach(sgId -> { |
| OpenstackSecurityGroup osSecGroup = openstackService.securityGroup(sgId); |
| if (osSecGroup != null) { |
| osSecGroup.rules().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); |
| } |
| |
| @Override |
| public void reinstallVmFlow(Host host) { |
| if (host == null) { |
| hostService.getHosts().forEach(h -> { |
| updateSecurityGroupRules(h, true); |
| log.info("Re-Install data plane flow of virtual machine {}", h); |
| }); |
| } else { |
| securityGroupRuleMap.entrySet().stream() |
| .filter(entry -> entry.getKey().id().equals(host.id())) |
| .forEach(entry -> { |
| Host local = entry.getKey(); |
| entry.getValue().forEach(sgRule -> { |
| setSecurityGroupRule(local.location().deviceId(), |
| sgRule.rule(), |
| getIp(local), |
| sgRule.remoteIp(), true); |
| }); |
| }); |
| log.info("Re-Install data plane flow of virtual machine {}", host); |
| } |
| } |
| |
| @Override |
| public void purgeVmFlow(Host host) { |
| if (host == null) { |
| securityGroupRuleMap.entrySet().stream() |
| .forEach(entry -> { |
| Host local = entry.getKey(); |
| entry.getValue().forEach(sgRule -> { |
| setSecurityGroupRule(local.location().deviceId(), |
| sgRule.rule(), |
| getIp(local), |
| sgRule.remoteIp(), false); |
| }); |
| log.info("Purge data plane flow of virtual machine {}", local); |
| }); |
| } else { |
| securityGroupRuleMap.entrySet().stream() |
| .filter(entry -> entry.getKey().id().equals(host.id())) |
| .forEach(entry -> { |
| Host local = entry.getKey(); |
| entry.getValue().forEach(sgRule -> { |
| setSecurityGroupRule(local.location().deviceId(), |
| sgRule.rule(), |
| getIp(local), |
| sgRule.remoteIp(), false); |
| }); |
| }); |
| log.info("Purge data plane flow of virtual machine {}", 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); |
| } |
| } |
| } |