content: // 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 com.cloud.dc.DataCenterVO;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.network.dao.PhysicalNetworkDao;
  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 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 {
  
      private static final String s_publicNetworkReserver = PublicNetworkGuru.class.getSimpleName();
  
      ScheduledExecutorService _ipv6GuestPrefixSubnetNetworkMapStateScanner;
  
      @Inject
      NetworkOfferingDao networkOfferingDao;
      @Inject
      VlanDao vlanDao;
      @Inject
      DataCenterDao zoneDao;
      @Inject
      DataCenterGuestIpv6PrefixDao dataCenterGuestIpv6PrefixDao;
      @Inject
      Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao;
      @Inject
      FirewallRulesDao firewallDao;
      @Inject
      FirewallService firewallService;
      @Inject
      NetworkDetailsDao networkDetailsDao;
      @Inject
      NicDao nicDao;
      @Inject
      PhysicalNetworkDao physicalNetworkDao;
      @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()) {
              logger.error("Public IPv6 placeholder NIC {} with cidr: {}, gateway: {} for network: {} is not present in the allocated VLAN: {}",
                      nic, nic.getIPv6Cidr(), nic.getIPv6Gateway(), network, 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)) {
              DataCenterVO zone = zoneDao.findById(network.getDataCenterId());
              logger.error("Unable to find IPv6 address for zone: {}, physical network: {}, VLAN: {}",
                      zone, physicalNetworkDao.findById(network.getPhysicalNetworkId()), vlanId);
              InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", DataCenter.class, network.getDataCenterId());
              ex.addProxyObject(zone.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(DataCenter zone) throws ResourceAllocationException {
          return Transaction.execute((TransactionCallbackWithException<Pair<String, String>, ResourceAllocationException>) status -> {
              List<DataCenterGuestIpv6PrefixVO> prefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(zone.getId());
              if (CollectionUtils.isEmpty(prefixes)) {
                  logger.error("IPv6 prefixes not found for the zone: {}", zone);
                  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)) {
              logger.error("IPv6 prefixes not found for the zone: {}", zoneDao.findById(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)) {
                  DataCenterVO zone = zoneDao.findById(network.getDataCenterId());
                  logger.error("Unable to find IPv6 address for zone: {}, physical network: {}, VLAN: {}",
                          zone, physicalNetworkDao.findById(network.getPhysicalNetworkId()), vlan.getVlanTag());
                  InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", DataCenter.class, network.getDataCenterId());
                  ex.addProxyObject(zone.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();
  
          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(String.format("Traffic Type %s is currently supported by Firewall in network %s", trafficType, network));
              }
          }
  
          // 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) {
              logger.error(String.format("Unable to find IPv6 firewall rule with ID: %d", id));
              return false;
          }
          if (!FirewallRule.Purpose.Ipv6Firewall.equals(rule.getPurpose())) {
              logger.error("Cannot apply IPv6 firewall rule: {} as purpose {} is not {}", rule, rule.getPurpose(), FirewallRule.Purpose.Ipv6Firewall);
          }
          logger.debug(String.format("Applying IPv6 firewall rules for rule: %s", rule));
          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) {
                              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());
              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 (logger.isInfoEnabled()) {
                          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) {
                          logger.warn(String.format("Failed to release IPv6 guest prefix subnet : %s during state scan", subnet.getSubnet()), e);
                      }
                  }
              } catch (Exception e) {
                  logger.warn("Caught exception while running Ipv6GuestPrefixSubnetNetworkMap state scanner: ", e);
              }
          }
      }
  }
  