| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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 com.cloud.network; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| import java.util.stream.Collectors; |
| |
| import javax.inject.Inject; |
| import javax.naming.ConfigurationException; |
| |
| import org.apache.cloudstack.api.ApiCommandResourceType; |
| import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd; |
| import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd; |
| import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd; |
| import org.apache.cloudstack.api.command.user.ipv6.CreateIpv6FirewallRuleCmd; |
| import org.apache.cloudstack.api.command.user.ipv6.DeleteIpv6FirewallRuleCmd; |
| import org.apache.cloudstack.api.command.user.ipv6.ListIpv6FirewallRulesCmd; |
| import org.apache.cloudstack.api.command.user.ipv6.UpdateIpv6FirewallRuleCmd; |
| import org.apache.cloudstack.api.response.Ipv6RouteResponse; |
| import org.apache.cloudstack.api.response.VpcResponse; |
| import org.apache.cloudstack.context.CallContext; |
| import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; |
| import org.apache.cloudstack.framework.config.ConfigKey; |
| import org.apache.cloudstack.managed.context.ManagedContextRunnable; |
| import org.apache.commons.collections.CollectionUtils; |
| import org.apache.commons.lang3.ObjectUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.log4j.Logger; |
| |
| import com.cloud.api.ApiDBUtils; |
| import com.cloud.configuration.Resource; |
| import com.cloud.dc.DataCenter; |
| import com.cloud.dc.DataCenterGuestIpv6Prefix; |
| import com.cloud.dc.DataCenterGuestIpv6PrefixVO; |
| import com.cloud.dc.Vlan; |
| import com.cloud.dc.VlanVO; |
| import com.cloud.dc.dao.DataCenterGuestIpv6PrefixDao; |
| import com.cloud.dc.dao.VlanDao; |
| import com.cloud.event.ActionEvent; |
| import com.cloud.event.ActionEventUtils; |
| import com.cloud.event.EventTypes; |
| import com.cloud.event.EventVO; |
| import com.cloud.event.UsageEventUtils; |
| import com.cloud.exception.InsufficientAddressCapacityException; |
| import com.cloud.exception.InvalidParameterValueException; |
| import com.cloud.exception.NetworkRuleConflictException; |
| import com.cloud.exception.ResourceAllocationException; |
| import com.cloud.network.dao.FirewallRulesDao; |
| import com.cloud.network.dao.IPAddressDao; |
| import com.cloud.network.dao.IPAddressVO; |
| import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; |
| import com.cloud.network.dao.NetworkDetailsDao; |
| import com.cloud.network.firewall.FirewallService; |
| import com.cloud.network.guru.PublicNetworkGuru; |
| import com.cloud.network.router.VirtualRouter; |
| import com.cloud.network.rules.FirewallManager; |
| import com.cloud.network.rules.FirewallRule; |
| import com.cloud.network.rules.FirewallRuleVO; |
| import com.cloud.network.vpc.Vpc; |
| import com.cloud.offerings.dao.NetworkOfferingDao; |
| import com.cloud.user.Account; |
| import com.cloud.user.AccountManager; |
| import com.cloud.utils.Pair; |
| import com.cloud.utils.component.ComponentLifecycleBase; |
| import com.cloud.utils.concurrency.NamedThreadFactory; |
| import com.cloud.utils.db.GlobalLock; |
| import com.cloud.utils.db.Transaction; |
| import com.cloud.utils.db.TransactionCallbackNoReturn; |
| import com.cloud.utils.db.TransactionCallbackWithException; |
| import com.cloud.utils.db.TransactionStatus; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.utils.net.NetUtils; |
| import com.cloud.vm.DomainRouterVO; |
| import com.cloud.vm.Nic; |
| import com.cloud.vm.NicProfile; |
| import com.cloud.vm.NicVO; |
| import com.cloud.vm.VirtualMachine; |
| import com.cloud.vm.dao.DomainRouterDao; |
| import com.cloud.vm.dao.NicDao; |
| import com.googlecode.ipv6.IPv6Address; |
| import com.googlecode.ipv6.IPv6Network; |
| import com.googlecode.ipv6.IPv6NetworkMask; |
| |
| public class Ipv6ServiceImpl extends ComponentLifecycleBase implements Ipv6Service { |
| |
| public static final Logger s_logger = Logger.getLogger(Ipv6ServiceImpl.class.getName()); |
| private static final String s_publicNetworkReserver = PublicNetworkGuru.class.getSimpleName(); |
| |
| ScheduledExecutorService _ipv6GuestPrefixSubnetNetworkMapStateScanner; |
| |
| @Inject |
| NetworkOfferingDao networkOfferingDao; |
| @Inject |
| VlanDao vlanDao; |
| @Inject |
| DataCenterGuestIpv6PrefixDao dataCenterGuestIpv6PrefixDao; |
| @Inject |
| Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao; |
| @Inject |
| FirewallRulesDao firewallDao; |
| @Inject |
| FirewallService firewallService; |
| @Inject |
| NetworkDetailsDao networkDetailsDao; |
| @Inject |
| NicDao nicDao; |
| @Inject |
| DomainRouterDao domainRouterDao; |
| @Inject |
| AccountManager accountManager; |
| @Inject |
| NetworkModel networkModel; |
| @Inject |
| IPAddressDao ipAddressDao; |
| @Inject |
| FirewallManager firewallManager; |
| @Inject |
| NetworkOrchestrationService networkOrchestrationService; |
| |
| private boolean isPublicIpv6PlaceholderNic(NicVO nic) { |
| return ObjectUtils.allNotNull(nic.getIPv6Address(), nic.getIPv6Cidr(), nic.getIPv6Gateway()) && |
| s_publicNetworkReserver.equals(nic.getReserver()); |
| } |
| |
| private Pair<String, ? extends Vlan> getPublicIpv6FromNetworkPlaceholder(Network network, List<VlanVO> ranges) { |
| List<NicVO> placeholderNics = nicDao.listPlaceholderNicsByNetworkIdAndVmType(network.getId(), VirtualMachine.Type.DomainRouter); |
| if (CollectionUtils.isEmpty(placeholderNics)) { |
| return null; |
| } |
| Optional<NicVO> nicOptional = placeholderNics.stream().filter(this::isPublicIpv6PlaceholderNic).findFirst(); |
| if (nicOptional.isEmpty()) { |
| return null; |
| } |
| NicVO nic = nicOptional.get(); |
| Optional<VlanVO> vlanOptional = ranges.stream().filter(v -> nic.getIPv6Cidr().equals(v.getIp6Cidr()) && nic.getIPv6Gateway().equals(v.getIp6Gateway())).findFirst(); |
| if (vlanOptional.isEmpty()) { |
| s_logger.error(String.format("Public IPv6 placeholder NIC with cidr: %s, gateway: %s for network ID: %d is not present in the allocated VLAN: %s", |
| nic.getIPv6Cidr(), nic.getIPv6Gateway(),network.getId(), ranges.get(0).getVlanTag())); |
| return null; |
| } |
| return new Pair<>(nic.getIPv6Address(), vlanOptional.get()); |
| } |
| |
| private void publishPublicIpv6AssignActionEvent(final Network network, final String ipv6Address) { |
| String event = EventTypes.EVENT_NET_IP6_ASSIGN; |
| final String description = String.format("Assigned public IPv6 address: %1$s for network ID: %2$s. Subnet: %3$s, gateway: %1$s", ipv6Address, network.getUuid(), network.getIp6Cidr()); |
| ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), network.getAccountId(), EventVO.LEVEL_INFO, event, description, network.getId(), ApiCommandResourceType.Network.toString(), 0); |
| final boolean usageHidden = networkDetailsDao.isNetworkUsageHidden(network.getId()); |
| final String guestType = Vlan.VlanType.VirtualNetwork.toString(); |
| UsageEventUtils.publishUsageEvent(event, network.getAccountId(), network.getDataCenterId(), 0L, |
| ipv6Address, false, guestType, false, usageHidden, |
| IPv6Network.class.getName(), null); |
| } |
| |
| private void publishPublicIpv6ReleaseActionEvent(Network network, String nicIpv6Address) { |
| String event = EventTypes.EVENT_NET_IP6_RELEASE; |
| String description = String.format("Released public IPv6 address: %1$s from network ID: %2$s. Subnet: %3$s, gateway: %1$s", nicIpv6Address, network.getUuid(), network.getIp6Cidr()); |
| ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), network.getAccountId(), EventVO.LEVEL_INFO, event, description, network.getId(), ApiCommandResourceType.Network.toString(), 0); |
| final boolean usageHidden = networkDetailsDao.isNetworkUsageHidden(network.getId()); |
| UsageEventUtils.publishUsageEvent(event, network.getAccountId(), network.getDataCenterId(), 0L, |
| nicIpv6Address, false, Vlan.VlanType.VirtualNetwork.toString(), false, usageHidden, |
| IPv6Address.class.getName(), null); |
| } |
| |
| private void processPublicIpv6AddressUpdateForVpcTier(final Network network, final Nic nic) { |
| if (network.getVpcId() == null || !Network.State.Implementing.equals(network.getState())) { |
| return; |
| } |
| DomainRouterVO router = domainRouterDao.findById(nic.getInstanceId()); |
| if (router == null) { |
| return; |
| } |
| if (router.getIsRedundantRouter() && !VirtualRouter.RedundantState.PRIMARY.equals(router.getRedundantState())) { |
| return; |
| } |
| networkOrchestrationService.savePlaceholderNic(network, null, nic.getIPv6Address(), nic.getIPv6Cidr(), nic.getIPv6Gateway(), s_publicNetworkReserver, VirtualMachine.Type.DomainRouter); |
| publishPublicIpv6AssignActionEvent(network, nic.getIPv6Address()); |
| } |
| |
| private Pair<String, ? extends Vlan> assignPublicIpv6ToNetworkInternal(Network network, String vlanId, String nicMacAddress) throws InsufficientAddressCapacityException { |
| final List<VlanVO> ranges = vlanDao.listIpv6RangeByZoneIdAndVlanId(network.getDataCenterId(), vlanId); |
| if (CollectionUtils.isEmpty(ranges)) { |
| s_logger.error(String.format("Unable to find IPv6 address for zone ID: %d, physical network ID: %d, VLAN: %s", network.getDataCenterId(), network.getPhysicalNetworkId(), vlanId)); |
| InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", DataCenter.class, network.getDataCenterId()); |
| ex.addProxyObject(ApiDBUtils.findZoneById(network.getDataCenterId()).getUuid()); |
| throw ex; |
| } |
| Pair<String, ? extends Vlan> placeholderResult = getPublicIpv6FromNetworkPlaceholder(network, ranges); |
| if (placeholderResult != null) { |
| return placeholderResult; |
| } |
| Pair<String, ? extends Vlan> result = Transaction.execute((TransactionCallbackWithException<Pair<String, ? extends Vlan>, InsufficientAddressCapacityException>) status -> { |
| removePublicIpv6PlaceholderNics(network); |
| Collections.shuffle(ranges); |
| VlanVO selectedVlan = ranges.get(0); |
| IPv6Network ipv6Network = IPv6Network.fromString(selectedVlan.getIp6Cidr()); |
| if (ipv6Network.getNetmask().asPrefixLength() < IPV6_SLAAC_CIDR_NETMASK) { |
| Iterator<IPv6Network> splits = ipv6Network.split(IPv6NetworkMask.fromPrefixLength(IPV6_SLAAC_CIDR_NETMASK)); |
| if (splits.hasNext()) { |
| ipv6Network = splits.next(); |
| } |
| } |
| IPv6Address ipv6Addr = NetUtils.EUI64Address(ipv6Network, nicMacAddress); |
| networkOrchestrationService.savePlaceholderNic(network, null, ipv6Addr.toString(), selectedVlan.getIp6Cidr(), selectedVlan.getIp6Gateway(), s_publicNetworkReserver, VirtualMachine.Type.DomainRouter); |
| return new Pair<>(ipv6Addr.toString(), selectedVlan); |
| }); |
| final String ipv6Address = result.first(); |
| publishPublicIpv6AssignActionEvent(network, ipv6Address); |
| return result; |
| } |
| |
| private Ipv6GuestPrefixSubnetNetworkMapVO preallocatePrefixSubnetRandomly(DataCenterGuestIpv6PrefixVO prefix) { |
| Ipv6GuestPrefixSubnetNetworkMapVO ip6Subnet = null; |
| List<Ipv6GuestPrefixSubnetNetworkMapVO> prefixSubnetNetworkMapVOList = ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefix.getId()); |
| List<String> usedPrefixList = prefixSubnetNetworkMapVOList.stream().map(Ipv6GuestPrefixSubnetNetworkMap::getSubnet).collect(Collectors.toList()); |
| final IPv6Network ip6Prefix = IPv6Network.fromString(prefix.getPrefix()); |
| Iterator<IPv6Network> splits = ip6Prefix.split(IPv6NetworkMask.fromPrefixLength(IPV6_SLAAC_CIDR_NETMASK)); |
| List<IPv6Network> availableSubnets = new ArrayList<>(); |
| while (splits.hasNext()) { |
| IPv6Network i = splits.next(); |
| if (!usedPrefixList.contains(i.toString())) { |
| availableSubnets.add(i); |
| } |
| } |
| if (CollectionUtils.isNotEmpty(availableSubnets)) { |
| Random r = new Random(); |
| IPv6Network subnet = availableSubnets.get(r.nextInt(availableSubnets.size())); |
| ip6Subnet = new Ipv6GuestPrefixSubnetNetworkMapVO(prefix.getId(), subnet.toString(), null, Ipv6GuestPrefixSubnetNetworkMap.State.Allocating); |
| } |
| return ip6Subnet; |
| } |
| |
| protected void releaseIpv6Subnet(long subnetId) { |
| Ipv6GuestPrefixSubnetNetworkMapVO ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.createForUpdate(subnetId); |
| ipv6GuestPrefixSubnetNetworkMapVO.setState(Ipv6GuestPrefixSubnetNetworkMap.State.Free); |
| ipv6GuestPrefixSubnetNetworkMapVO.setNetworkId(null); |
| ipv6GuestPrefixSubnetNetworkMapVO.setUpdated(new Date()); |
| ipv6GuestPrefixSubnetNetworkMapDao.update(ipv6GuestPrefixSubnetNetworkMapVO.getId(), ipv6GuestPrefixSubnetNetworkMapVO); |
| } |
| |
| @Override |
| public boolean start() { |
| _ipv6GuestPrefixSubnetNetworkMapStateScanner.scheduleWithFixedDelay(new Ipv6GuestPrefixSubnetNetworkMapStateScanner(), 300, 30*60, TimeUnit.SECONDS); |
| return true; |
| } |
| |
| @Override |
| public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { |
| _name = name; |
| _configParams = params; |
| _ipv6GuestPrefixSubnetNetworkMapStateScanner = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Ipv6GuestPrefixSubnet-State-Scanner")); |
| |
| return true; |
| } |
| |
| @Override |
| public List<Class<?>> getCommands() { |
| final List<Class<?>> cmdList = new ArrayList<Class<?>>(); |
| cmdList.add(CreateGuestNetworkIpv6PrefixCmd.class); |
| cmdList.add(ListGuestNetworkIpv6PrefixesCmd.class); |
| cmdList.add(DeleteGuestNetworkIpv6PrefixCmd.class); |
| cmdList.add(CreateIpv6FirewallRuleCmd.class); |
| cmdList.add(ListIpv6FirewallRulesCmd.class); |
| cmdList.add(UpdateIpv6FirewallRuleCmd.class); |
| cmdList.add(DeleteIpv6FirewallRuleCmd.class); |
| return cmdList; |
| } |
| |
| @Override |
| public String getConfigComponentName() { |
| return Ipv6Service.class.getSimpleName(); |
| } |
| |
| @Override |
| public ConfigKey<?>[] getConfigKeys() { |
| return new ConfigKey<?>[] { |
| Ipv6OfferingCreationEnabled, |
| Ipv6PrefixSubnetCleanupInterval |
| }; |
| } |
| |
| @Override |
| public Pair<Integer, Integer> getUsedTotalIpv6SubnetForPrefix(DataCenterGuestIpv6Prefix prefix) { |
| List<Ipv6GuestPrefixSubnetNetworkMapVO> usedSubnets = ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefix.getId()); |
| final IPv6Network ip6Prefix = IPv6Network.fromString(prefix.getPrefix()); |
| Iterator<IPv6Network> splits = ip6Prefix.split(IPv6NetworkMask.fromPrefixLength(IPV6_SLAAC_CIDR_NETMASK)); |
| int total = 0; |
| while(splits.hasNext()) { |
| total++; |
| splits.next(); |
| } |
| return new Pair<>(usedSubnets.size(), total); |
| } |
| |
| @Override |
| public Pair<Integer, Integer> getUsedTotalIpv6SubnetForZone(long zoneId) { |
| int used = 0; |
| int total = 0; |
| List<DataCenterGuestIpv6PrefixVO> prefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId); |
| for (DataCenterGuestIpv6PrefixVO prefix : prefixes) { |
| Pair<Integer, Integer> usedTotal = getUsedTotalIpv6SubnetForPrefix(prefix); |
| used += usedTotal.first(); |
| total += usedTotal.second(); |
| } |
| return new Pair<>(used, total); |
| } |
| |
| public Pair<String, String> preAllocateIpv6SubnetForNetwork(long zoneId) throws ResourceAllocationException { |
| return Transaction.execute((TransactionCallbackWithException<Pair<String, String>, ResourceAllocationException>) status -> { |
| List<DataCenterGuestIpv6PrefixVO> prefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId); |
| if (CollectionUtils.isEmpty(prefixes)) { |
| s_logger.error(String.format("IPv6 prefixes not found for the zone ID: %d", zoneId)); |
| throw new ResourceAllocationException("Unable to allocate IPv6 network", Resource.ResourceType.network); |
| } |
| Ipv6GuestPrefixSubnetNetworkMapVO ip6Subnet = null; |
| for (DataCenterGuestIpv6PrefixVO prefix : prefixes) { |
| ip6Subnet = ipv6GuestPrefixSubnetNetworkMapDao.findFirstAvailable(prefix.getId()); |
| if (ip6Subnet == null) { |
| ip6Subnet = preallocatePrefixSubnetRandomly(prefix); |
| } |
| if (ip6Subnet != null) { |
| break; |
| } |
| } |
| if (ip6Subnet == null) { |
| throw new ResourceAllocationException("Unable to allocate IPv6 guest subnet for the network", Resource.ResourceType.network); |
| } |
| ip6Subnet.setUpdated(new Date()); |
| if (Ipv6GuestPrefixSubnetNetworkMap.State.Free.equals(ip6Subnet.getState())) { |
| ip6Subnet.setState(Ipv6GuestPrefixSubnetNetworkMap.State.Allocating); |
| ipv6GuestPrefixSubnetNetworkMapDao.update(ip6Subnet.getId(), ip6Subnet); |
| } else { |
| ipv6GuestPrefixSubnetNetworkMapDao.persist(ip6Subnet); |
| } |
| IPv6Network network = IPv6Network.fromString(ip6Subnet.getSubnet()); |
| IPv6Address gateway = network.getFirst().add(1); |
| return new Pair<>(gateway.toString(), network.toString()); |
| }); |
| } |
| |
| @Override |
| public void assignIpv6SubnetToNetwork(String subnet, long networkId) { |
| Transaction.execute(new TransactionCallbackNoReturn() { |
| @Override |
| public void doInTransactionWithoutResult(TransactionStatus status) { |
| Ipv6GuestPrefixSubnetNetworkMapVO ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.findBySubnet(subnet); |
| if (ipv6GuestPrefixSubnetNetworkMapVO != null) { |
| ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.createForUpdate(ipv6GuestPrefixSubnetNetworkMapVO.getId()); |
| ipv6GuestPrefixSubnetNetworkMapVO.setState(Ipv6GuestPrefixSubnetNetworkMap.State.Allocated); |
| ipv6GuestPrefixSubnetNetworkMapVO.setNetworkId(networkId); |
| ipv6GuestPrefixSubnetNetworkMapVO.setUpdated(new Date()); |
| ipv6GuestPrefixSubnetNetworkMapDao.update(ipv6GuestPrefixSubnetNetworkMapVO.getId(), ipv6GuestPrefixSubnetNetworkMapVO); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void releaseIpv6SubnetForNetwork(long networkId) { |
| Transaction.execute(new TransactionCallbackNoReturn() { |
| @Override |
| public void doInTransactionWithoutResult(TransactionStatus status) { |
| Ipv6GuestPrefixSubnetNetworkMapVO ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.findByNetworkId(networkId); |
| if (ipv6GuestPrefixSubnetNetworkMapVO != null) { |
| releaseIpv6Subnet(ipv6GuestPrefixSubnetNetworkMapVO.getId()); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public List<String> getAllocatedIpv6FromVlanRange(Vlan vlan) { |
| if (ObjectUtils.allNull(vlan.getIp6Cidr(), vlan.getIp6Gateway())) { |
| return null; |
| } |
| List<NicVO> nics = nicDao.findNicsByIpv6GatewayIpv6CidrAndReserver(vlan.getIp6Gateway(), vlan.getIp6Cidr(), s_publicNetworkReserver); |
| if (CollectionUtils.isNotEmpty(nics)) { |
| return nics.stream().map(NicVO::getIPv6Address).collect(Collectors.toList()); |
| } |
| return null; |
| } |
| |
| @Override |
| public Nic assignPublicIpv6ToNetwork(Network network, Nic nic) { |
| if (StringUtils.isNotEmpty(nic.getIPv6Address())) { |
| processPublicIpv6AddressUpdateForVpcTier(network, nic); |
| return nic; |
| } |
| try { |
| Pair<String, ? extends Vlan> publicIpv6AddressVlanPair = assignPublicIpv6ToNetworkInternal(network, nic.getBroadcastUri().toString(), nic.getMacAddress()); |
| Vlan vlan = publicIpv6AddressVlanPair.second(); |
| final String ipv6Address = publicIpv6AddressVlanPair.first(); |
| final String ipv6Gateway = vlan.getIp6Gateway(); |
| final String ipv6Cidr = vlan.getIp6Cidr(); |
| NicVO nicVO = nicDao.createForUpdate(nic.getId()); |
| nicVO.setIPv6Address(ipv6Address); |
| nicVO.setIPv6Gateway(ipv6Gateway); |
| nicVO.setIPv6Cidr(ipv6Cidr); |
| nicDao.update(nic.getId(), nicVO); |
| return nicVO; |
| } catch (InsufficientAddressCapacityException ex) { |
| throw new CloudRuntimeException(ex.getMessage()); |
| } |
| } |
| |
| @Override |
| public void updateNicIpv6(NicProfile nic, DataCenter dc, Network network) throws InsufficientAddressCapacityException { |
| boolean isIpv6Supported = networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId()); |
| if (nic.getIPv6Address() == null && isIpv6Supported) { |
| Pair<String, ? extends Vlan> publicIpv6AddressVlanPair = assignPublicIpv6ToNetworkInternal(network, nic.getBroadCastUri().toString(), nic.getMacAddress()); |
| final Vlan vlan = publicIpv6AddressVlanPair.second(); |
| final String routerIpv6 = publicIpv6AddressVlanPair.first(); |
| final String routerIpv6Gateway = vlan.getIp6Gateway(); |
| final String routerIpv6Cidr = vlan.getIp6Cidr(); |
| nic.setIPv6Address(routerIpv6); |
| nic.setIPv6Gateway(routerIpv6Gateway); |
| nic.setIPv6Cidr(routerIpv6Cidr); |
| if (nic.getIPv4Address() != null) { |
| nic.setFormat(Networks.AddressFormat.DualStack); |
| } else { |
| nic.setFormat(Networks.AddressFormat.Ip6); |
| } |
| Pair<String, String> dns = networkModel.getNetworkIp6Dns(network, dc); |
| nic.setIPv6Dns1(dns.first()); |
| nic.setIPv6Dns2(dns.second()); |
| } |
| } |
| |
| @Override |
| public List<String> getPublicIpv6AddressesForNetwork(Network network) { |
| List<String> addresses = new ArrayList<>(); |
| List<DomainRouterVO> routers = domainRouterDao.findByNetwork(network.getId()); |
| for (DomainRouterVO router : routers) { |
| List<NicVO> nics = nicDao.listByVmId(router.getId()); |
| for (NicVO nic : nics) { |
| String address = nic.getIPv6Address(); |
| if (!s_publicNetworkReserver.equals(nic.getReserver()) || |
| StringUtils.isEmpty(address) || |
| addresses.contains(address)) { |
| continue; |
| } |
| addresses.add(address); |
| } |
| } |
| return addresses; |
| } |
| |
| @Override |
| public void updateIpv6RoutesForVpcResponse(Vpc vpc, VpcResponse response) { |
| Set<Ipv6RouteResponse> ipv6Routes = new LinkedHashSet<>(); |
| List<? extends Network> networks = networkModel.listNetworksByVpc(vpc.getId()); |
| for (Network network : networks) { |
| if (networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId())) { |
| List<String> networkPublicIpv6 = getPublicIpv6AddressesForNetwork(network); |
| for (String address : networkPublicIpv6) { |
| Ipv6RouteResponse route = new Ipv6RouteResponse(network.getIp6Cidr(), address); |
| ipv6Routes.add(route); |
| } |
| } |
| } |
| if (CollectionUtils.isNotEmpty(ipv6Routes)) { |
| response.setIpv6Routes(ipv6Routes); |
| } |
| } |
| |
| @Override |
| public void checkNetworkIpv6Upgrade(Network network) throws InsufficientAddressCapacityException, ResourceAllocationException { |
| List<DataCenterGuestIpv6PrefixVO> prefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(network.getDataCenterId()); |
| if (CollectionUtils.isEmpty(prefixes)) { |
| s_logger.error(String.format("IPv6 prefixes not found for the zone ID: %d", network.getDataCenterId())); |
| throw new ResourceAllocationException("Unable to allocate IPv6 network", Resource.ResourceType.network); |
| } |
| List<IPAddressVO> addresses = network.getVpcId() == null ? |
| ipAddressDao.listByAssociatedNetwork(network.getId(), true) : |
| ipAddressDao.listByAssociatedVpc(network.getVpcId(), true); |
| for (IPAddressVO address : addresses) { |
| VlanVO vlan = vlanDao.findById(address.getVlanId()); |
| final List<VlanVO> ranges = vlanDao.listIpv6RangeByZoneIdAndVlanId(network.getDataCenterId(), vlan.getVlanTag()); |
| if (CollectionUtils.isEmpty(ranges)) { |
| s_logger.error(String.format("Unable to find IPv6 address for zone ID: %d, physical network ID: %d, VLAN: %s", network.getDataCenterId(), network.getPhysicalNetworkId(), vlan.getVlanTag())); |
| InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", DataCenter.class, network.getDataCenterId()); |
| ex.addProxyObject(ApiDBUtils.findZoneById(network.getDataCenterId()).getUuid()); |
| throw ex; |
| } |
| } |
| } |
| |
| @Override |
| public Pair<List<? extends FirewallRule>, Integer> listIpv6FirewallRules(ListIpv6FirewallRulesCmd listIpv6FirewallRulesCmd) { |
| return firewallService.listFirewallRules(listIpv6FirewallRulesCmd); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating IPv6 firewall rule", create = true) |
| public FirewallRule createIpv6FirewallRule(CreateIpv6FirewallRuleCmd cmd) throws NetworkRuleConflictException { |
| final Account caller = CallContext.current().getCallingAccount(); |
| final long networkId = cmd.getNetworkId(); |
| final Integer portStart = cmd.getSourcePortStart(); |
| final Integer portEnd = cmd.getSourcePortEnd(); |
| final FirewallRule.TrafficType trafficType = cmd.getTrafficType(); |
| final String protocol = cmd.getProtocol(); |
| final Integer icmpCode = cmd.getIcmpCode(); |
| final Integer icmpType = cmd.getIcmpType(); |
| final boolean forDisplay = cmd.isDisplay(); |
| final FirewallRule.FirewallRuleType type = FirewallRule.FirewallRuleType.User; |
| final List<String> sourceCidrList = cmd.getSourceCidrList(); |
| final List<String> destinationCidrList = cmd.getDestinationCidrList(); |
| |
| for (String cidr : sourceCidrList) { |
| if (!NetUtils.isValidIp6Cidr(cidr)) { |
| throw new InvalidParameterValueException(String.format("Invalid source IPv6 CIDR: %s", cidr)); |
| } |
| } |
| for (String cidr : destinationCidrList) { |
| if (!NetUtils.isValidIp6Cidr(cidr)) { |
| throw new InvalidParameterValueException(String.format("Invalid destination IPv6 CIDR: %s", cidr)); |
| } |
| } |
| if (portStart != null && !NetUtils.isValidPort(portStart)) { |
| throw new InvalidParameterValueException("publicPort is an invalid value: " + portStart); |
| } |
| if (portEnd != null && !NetUtils.isValidPort(portEnd)) { |
| throw new InvalidParameterValueException("Public port range is an invalid value: " + portEnd); |
| } |
| if (ObjectUtils.allNotNull(portStart, portEnd) && portStart > portEnd) { |
| throw new InvalidParameterValueException("Start port can't be bigger than end port"); |
| } |
| |
| Network network = networkModel.getNetwork(networkId); |
| assert network != null : "Can't create rule as network is null?"; |
| |
| final long accountId = network.getAccountId(); |
| final long domainId = network.getDomainId(); |
| |
| if (FirewallRule.TrafficType.Egress.equals(trafficType)) { |
| accountManager.checkAccess(caller, null, true, network); |
| } |
| |
| // Verify that the network guru supports the protocol specified |
| Map<Network.Capability, String> caps = networkModel.getNetworkServiceCapabilities(network.getId(), Network.Service.Firewall); |
| |
| if (caps != null) { |
| String supportedProtocols; |
| String supportedTrafficTypes = null; |
| supportedTrafficTypes = caps.get(Network.Capability.SupportedTrafficDirection).toLowerCase(); |
| |
| if (trafficType == FirewallRule.TrafficType.Egress) { |
| supportedProtocols = caps.get(Network.Capability.SupportedEgressProtocols).toLowerCase(); |
| } else { |
| supportedProtocols = caps.get(Network.Capability.SupportedProtocols).toLowerCase(); |
| } |
| |
| if (!supportedProtocols.contains(protocol.toLowerCase())) { |
| throw new InvalidParameterValueException(String.format("Protocol %s is not supported in zone", protocol)); |
| } else if (!supportedTrafficTypes.contains(trafficType.toString().toLowerCase())) { |
| throw new InvalidParameterValueException("Traffic Type " + trafficType + " is currently supported by Firewall in network " + networkId); |
| } |
| } |
| |
| // icmp code and icmp type can't be passed in for any other protocol rather than icmp |
| if (!protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (icmpCode != null || icmpType != null)) { |
| throw new InvalidParameterValueException("Can specify icmpCode and icmpType for ICMP protocol only"); |
| } |
| |
| if (protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (portStart != null || portEnd != null)) { |
| throw new InvalidParameterValueException("Can't specify start/end port when protocol is ICMP"); |
| } |
| |
| return Transaction.execute(new TransactionCallbackWithException<FirewallRuleVO, NetworkRuleConflictException>() { |
| @Override |
| public FirewallRuleVO doInTransaction(TransactionStatus status) throws NetworkRuleConflictException { |
| FirewallRuleVO newRule = |
| new FirewallRuleVO(null, null, portStart, portEnd, protocol.toLowerCase(), networkId, accountId, domainId, FirewallRule.Purpose.Ipv6Firewall, |
| sourceCidrList, destinationCidrList, icmpCode, icmpType, null, trafficType); |
| newRule.setType(type); |
| newRule.setDisplay(forDisplay); |
| newRule = firewallDao.persist(newRule); |
| |
| if (FirewallRule.FirewallRuleType.User.equals(type)) { |
| firewallManager.detectRulesConflict(newRule); |
| } |
| |
| if (!firewallDao.setStateToAdd(newRule)) { |
| throw new CloudRuntimeException("Unable to update the state to add for " + newRule); |
| } |
| CallContext.current().setEventDetails("Rule Id: " + newRule.getId()); |
| |
| return newRule; |
| } |
| }); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_CLOSE, eventDescription = "revoking IPv6 firewall rule", async = true) |
| public boolean revokeIpv6FirewallRule(Long id) { |
| FirewallRuleVO rule = firewallDao.findById(id); |
| if (rule == null) { |
| throw new InvalidParameterValueException(String.format("Unable to find IPv6 firewall rule with id %d", id)); |
| } |
| if (FirewallRule.TrafficType.Ingress.equals(rule.getTrafficType())) { |
| return firewallManager.revokeIngressFirewallRule(rule.getId(), true); |
| } |
| return firewallManager.revokeEgressFirewallRule(rule.getId(), true); |
| } |
| |
| @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_UPDATE, eventDescription = "updating IPv6 firewall rule", async = true) |
| public FirewallRule updateIpv6FirewallRule(UpdateIpv6FirewallRuleCmd cmd) { |
| final long id = cmd.getId(); |
| final boolean forDisplay = cmd.isDisplay(); |
| FirewallRuleVO rule = firewallDao.findById(id); |
| if (rule == null) { |
| throw new InvalidParameterValueException(String.format("Unable to find IPv6 firewall rule with id %d", id)); |
| } |
| if (FirewallRule.TrafficType.Ingress.equals(rule.getTrafficType())) { |
| return firewallManager.updateIngressFirewallRule(rule.getId(), null, forDisplay); |
| } |
| return firewallManager.updateEgressFirewallRule(rule.getId(), null, forDisplay); |
| } |
| |
| @Override |
| public FirewallRule getIpv6FirewallRule(Long entityId) { |
| return firewallDao.findById(entityId); |
| } |
| |
| @Override |
| public boolean applyIpv6FirewallRule(long id) { |
| FirewallRuleVO rule = firewallDao.findById(id); |
| if (rule == null) { |
| s_logger.error(String.format("Unable to find IPv6 firewall rule with ID: %d", id)); |
| return false; |
| } |
| if (!FirewallRule.Purpose.Ipv6Firewall.equals(rule.getPurpose())) { |
| s_logger.error(String.format("Cannot apply IPv6 firewall rule with ID: %d as purpose %s is not %s", id, rule.getPurpose(), FirewallRule.Purpose.Ipv6Firewall)); |
| } |
| s_logger.debug(String.format("Applying IPv6 firewall rules for rule with ID: %s", rule.getUuid())); |
| List<FirewallRuleVO> rules = firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), rule.getPurpose(), FirewallRule.TrafficType.Egress); |
| rules.addAll(firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), FirewallRule.Purpose.Ipv6Firewall, FirewallRule.TrafficType.Ingress)); |
| return firewallManager.applyFirewallRules(rules, false, CallContext.current().getCallingAccount()); |
| } |
| |
| @Override |
| public void removePublicIpv6PlaceholderNics(Network network) { |
| try { |
| List<NicVO> nics = nicDao.listPlaceholderNicsByNetworkId(network.getId()) |
| .stream().filter(this::isPublicIpv6PlaceholderNic).collect(Collectors.toList()); |
| if (CollectionUtils.isNotEmpty(nics)) { |
| Transaction.execute(new TransactionCallbackNoReturn() { |
| @Override |
| public void doInTransactionWithoutResult(TransactionStatus status) { |
| for (Nic nic : nics) { |
| s_logger.debug("Removing placeholder nic " + nic); |
| nicDao.remove(nic.getId()); |
| publishPublicIpv6ReleaseActionEvent(network, nic.getIPv6Address()); |
| } |
| } |
| }); |
| } |
| } catch (Exception e) { |
| String msg = String.format("IPv6 Placeholder Nics trash. Exception: %s", e.getMessage()); |
| s_logger.error(msg); |
| throw new CloudRuntimeException(msg, e); |
| } |
| } |
| |
| public class Ipv6GuestPrefixSubnetNetworkMapStateScanner extends ManagedContextRunnable { |
| @Override |
| protected void runInContext() { |
| GlobalLock gcLock = GlobalLock.getInternLock("Ipv6GuestPrefixSubnetNetworkMap.State.Scanner.Lock"); |
| try { |
| if (gcLock.lock(3)) { |
| try { |
| reallyRun(); |
| } finally { |
| gcLock.unlock(); |
| } |
| } |
| } finally { |
| gcLock.releaseRef(); |
| } |
| } |
| |
| public void reallyRun() { |
| try { |
| List<Ipv6GuestPrefixSubnetNetworkMapVO> subnets = ipv6GuestPrefixSubnetNetworkMapDao.findPrefixesInStates(Ipv6GuestPrefixSubnetNetworkMap.State.Allocating); |
| for (Ipv6GuestPrefixSubnetNetworkMapVO subnet : subnets) { |
| if (s_logger.isInfoEnabled()) { |
| s_logger.info(String.format("Running state scanned on Ipv6GuestPrefixSubnetNetworkMap : %s", subnet.getSubnet())); |
| } |
| try { |
| if ((new Date()).getTime() - subnet.getUpdated().getTime() < Ipv6PrefixSubnetCleanupInterval.value()*1000) { |
| continue; |
| } |
| releaseIpv6Subnet(subnet.getId()); |
| } catch (CloudRuntimeException e) { |
| s_logger.warn(String.format("Failed to release IPv6 guest prefix subnet : %s during state scan", subnet.getSubnet()), e); |
| } |
| } |
| } catch (Exception e) { |
| s_logger.warn("Caught exception while running Ipv6GuestPrefixSubnetNetworkMap state scanner: ", e); |
| } |
| } |
| } |
| } |