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.firewall;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import org.apache.commons.lang3.ObjectUtils;
  import org.springframework.stereotype.Component;
  
  import com.cloud.dc.DataCenter;
  import com.cloud.network.dao.NsxProviderDao;
  import com.cloud.network.element.NsxProviderVO;
  import com.cloud.utils.db.EntityManager;
  import org.apache.cloudstack.api.command.user.firewall.IListFirewallRulesCmd;
  import org.apache.cloudstack.api.command.user.ipv6.ListIpv6FirewallRulesCmd;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.network.RoutedIpv4Manager;
  
  import com.cloud.configuration.Config;
  import com.cloud.domain.dao.DomainDao;
  import com.cloud.event.ActionEvent;
  import com.cloud.event.EventTypes;
  import com.cloud.event.UsageEventUtils;
  import com.cloud.event.dao.EventDao;
  import com.cloud.event.dao.UsageEventDao;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.exception.NetworkRuleConflictException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.network.IpAddress;
  import com.cloud.network.IpAddressManager;
  import com.cloud.network.Network;
  import com.cloud.network.Network.Capability;
  import com.cloud.network.Network.Service;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.NetworkRuleApplier;
  import com.cloud.network.dao.FirewallRulesCidrsDao;
  import com.cloud.network.dao.FirewallRulesDao;
  import com.cloud.network.dao.FirewallRulesDcidrsDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.IPAddressVO;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.element.FirewallServiceProvider;
  import com.cloud.network.element.NetworkACLServiceProvider;
  import com.cloud.network.element.PortForwardingServiceProvider;
  import com.cloud.network.element.StaticNatServiceProvider;
  import com.cloud.network.rules.FirewallManager;
  import com.cloud.network.rules.FirewallRule;
  import com.cloud.network.rules.FirewallRule.FirewallRuleType;
  import com.cloud.network.rules.FirewallRule.Purpose;
  import com.cloud.network.rules.FirewallRule.State;
  import com.cloud.network.rules.FirewallRuleVO;
  import com.cloud.network.rules.PortForwardingRule;
  import com.cloud.network.rules.PortForwardingRuleVO;
  import com.cloud.network.rules.dao.PortForwardingRulesDao;
  import com.cloud.network.vpc.VpcManager;
  import com.cloud.projects.Project.ListProjectResourcesCriteria;
  import com.cloud.server.ResourceTag.ResourceObjectType;
  import com.cloud.tags.ResourceTagVO;
  import com.cloud.tags.dao.ResourceTagDao;
  import com.cloud.user.Account;
  import com.cloud.user.AccountManager;
  import com.cloud.user.DomainManager;
  import com.cloud.utils.Pair;
  import com.cloud.utils.Ternary;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.Filter;
  import com.cloud.utils.db.JoinBuilder;
  import com.cloud.utils.db.SearchBuilder;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.utils.db.SearchCriteria.Op;
  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.UserVmVO;
  import com.cloud.vm.dao.UserVmDao;
  
  @Component
  public class FirewallManagerImpl extends ManagerBase implements FirewallService, FirewallManager, NetworkRuleApplier {
  
      @Inject
      FirewallRulesDao _firewallDao;
      @Inject
      IPAddressDao _ipAddressDao;
      @Inject
      EventDao _eventDao;
      @Inject
      DomainDao _domainDao;
      @Inject
      FirewallRulesCidrsDao _firewallCidrsDao;
      @Inject
      FirewallRulesDcidrsDao _firewallDcidrsDao;
      @Inject
      AccountManager _accountMgr;
      @Inject
      NetworkOrchestrationService _networkMgr;
      @Inject
      NetworkModel _networkModel;
      @Inject
      UsageEventDao _usageEventDao;
      @Inject
      ConfigurationDao _configDao;
      @Inject
      DomainManager _domainMgr;
      @Inject
      PortForwardingRulesDao _pfRulesDao;
      @Inject
      UserVmDao _vmDao;
      @Inject
      ResourceTagDao _resourceTagDao;
      @Inject
      NetworkDao _networkDao;
      @Inject
      VpcManager _vpcMgr;
      @Inject
      EntityManager entityManager;
      @Inject
      NsxProviderDao nsxProviderDao;
      List<FirewallServiceProvider> _firewallElements;
  
      List<PortForwardingServiceProvider> _pfElements;
  
      List<StaticNatServiceProvider> _staticNatElements;
  
      List<NetworkACLServiceProvider> _networkAclElements;
      @Inject
      IpAddressManager _ipAddrMgr;
      @Inject
      RoutedIpv4Manager routedIpv4Manager;
  
      private boolean _elbEnabled = false;
      static Boolean rulesContinueOnErrFlag = true;
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          _name = name;
          String elbEnabledString = _configDao.getValue(Config.ElasticLoadBalancerEnabled.key());
          _elbEnabled = Boolean.parseBoolean(elbEnabledString);
          if (_ipAddrMgr.RulesContinueOnError.value() != null) {
              rulesContinueOnErrFlag = _ipAddrMgr.RulesContinueOnError.value();
          }
          return true;
      }
  
      @Override
      public boolean start() {
          logger.info("Firewall provider list is " + _firewallElements.iterator().next());
          return super.start();
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_EGRESS_OPEN, eventDescription = "creating egress firewall rule for network", create = true)
      public FirewallRule createEgressFirewallRule(FirewallRule rule) throws NetworkRuleConflictException {
          Account caller = CallContext.current().getCallingAccount();
  
          Network network = _networkDao.findById(rule.getNetworkId());
          if (network.getGuestType() == Network.GuestType.Shared) {
              throw new InvalidParameterValueException("Egress firewall rules are not supported for " + network.getGuestType() + "  networks");
          }
  
          List<String> sourceCidrs = rule.getSourceCidrList();
          if (sourceCidrs != null && !sourceCidrs.isEmpty())
          Collections.replaceAll(sourceCidrs, "0.0.0.0/0", network.getCidr());
  
          return createFirewallRule(null, caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), sourceCidrs, rule.getDestinationCidrList(),
                  rule.getIcmpCode(), rule.getIcmpType(), null, rule.getType(), rule.getNetworkId(), rule.getTrafficType(), rule.isDisplay());
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating firewall rule", create = true)
      public FirewallRule createIngressFirewallRule(FirewallRule rule) throws NetworkRuleConflictException {
           Account caller = CallContext.current().getCallingAccount();
          Long sourceIpAddressId = rule.getSourceIpAddressId();
  
          return createFirewallRule(sourceIpAddressId, caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(),
              rule.getSourceCidrList(), null, rule.getIcmpCode(), rule.getIcmpType(), null, rule.getType(), rule.getNetworkId(), rule.getTrafficType(), rule.isDisplay());
      }
  
      //Destination CIDR capability is currently implemented for egress rules only. For others, the field is passed as null.
      @DB
      protected FirewallRule createFirewallRule(final Long ipAddrId, Account caller, final String xId, final Integer portStart, final Integer portEnd, final String protocol,
                                                final List<String> sourceCidrList, final List<String> destCidrList, final Integer icmpCode, final Integer icmpType, final Long relatedRuleId,
                                                final FirewallRule.FirewallRuleType type, final Long networkId, final FirewallRule.TrafficType trafficType, final Boolean forDisplay) throws NetworkRuleConflictException {
          IPAddressVO ipAddress = null;
          try {
              // Validate ip address
              if (ipAddrId != null) {
                  // this for ingress firewall rule, for egress id is null
                  ipAddress = _ipAddressDao.acquireInLockTable(ipAddrId);
                  if (ipAddress == null) {
                      throw new InvalidParameterValueException("Unable to create firewall rule; " + "couldn't locate IP address by id in the system");
                  }
                  _networkModel.checkIpForService(ipAddress, Service.Firewall, null);
              }
  
              validateFirewallRule(caller, ipAddress, portStart, portEnd, protocol, Purpose.Firewall, type, networkId, trafficType);
  
              // 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");
              }
  
              Long accountId = null;
              Long domainId = null;
  
              if (ipAddress != null) {
                  //Ingress firewall rule
                  accountId = ipAddress.getAllocatedToAccountId();
                  domainId = ipAddress.getAllocatedInDomainId();
              } else if (networkId != null) {
                  //egress firewall rule
                  Network network = _networkModel.getNetwork(networkId);
                  accountId = network.getAccountId();
                  domainId = network.getDomainId();
              }
  
              final Long accountIdFinal = accountId;
              final Long domainIdFinal = domainId;
              return Transaction.execute((TransactionCallbackWithException<FirewallRuleVO, NetworkRuleConflictException>) status -> {
                  FirewallRuleVO newRule = new FirewallRuleVO(xId, ipAddrId, portStart, portEnd, protocol.toLowerCase(), networkId, accountIdFinal, domainIdFinal, Purpose.Firewall,
                          sourceCidrList, destCidrList, icmpCode, icmpType, relatedRuleId, trafficType);
                  newRule.setType(type);
                  if (forDisplay != null) {
                      newRule.setDisplay(forDisplay);
                  }
                  newRule = _firewallDao.persist(newRule);
  
                  if (type == FirewallRuleType.User)
                      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());
                  CallContext.current().putContextParameter(FirewallRule.class, newRule.getId());
  
                  return newRule;
              });
          } finally {
              if (ipAddrId != null) {
                  _ipAddressDao.releaseFromLockTable(ipAddrId);
              }
          }
      }
  
      @Override
      public Pair<List<? extends FirewallRule>, Integer> listFirewallRules(IListFirewallRulesCmd cmd) {
          Long ipId = cmd.getIpAddressId();
          Long id = cmd.getId();
          Long networkId = cmd.getNetworkId();
          Map<String, String> tags = cmd.getTags();
          FirewallRule.TrafficType trafficType = cmd.getTrafficType();
          Boolean display = cmd.getDisplay();
  
          Account caller = CallContext.current().getCallingAccount();
          List<Long> permittedAccounts = new ArrayList<Long>();
  
          if (ipId != null) {
              IPAddressVO ipAddressVO = _ipAddressDao.findById(ipId);
              if (ipAddressVO == null || !ipAddressVO.readyToUse()) {
                  throw new InvalidParameterValueException("Ip address id=" + ipId + " not ready for firewall rules yet");
              }
              _accountMgr.checkAccess(caller, null, true, ipAddressVO);
          }
  
          Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null);
          _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false);
          Long domainId = domainIdRecursiveListProject.first();
          Boolean isRecursive = domainIdRecursiveListProject.second();
          ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
  
          Filter filter = new Filter(FirewallRuleVO.class, "id", false, cmd.getStartIndex(), cmd.getPageSizeVal());
          SearchBuilder<FirewallRuleVO> sb = _firewallDao.createSearchBuilder();
          _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
  
          sb.and("id", sb.entity().getId(), Op.EQ);
          sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ);
              sb.and("networkId", sb.entity().getNetworkId(), Op.EQ);
          sb.and("ip", sb.entity().getSourceIpAddressId(), Op.EQ);
          sb.and("purpose", sb.entity().getPurpose(), Op.EQ);
          sb.and("display", sb.entity().isDisplay(), Op.EQ);
  
          if (tags != null && !tags.isEmpty()) {
              SearchBuilder<ResourceTagVO> tagSearch = _resourceTagDao.createSearchBuilder();
              for (int count = 0; count < tags.size(); count++) {
                  tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
                  tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
                  tagSearch.cp();
              }
              tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
              sb.groupBy(sb.entity().getId());
              sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER);
          }
  
          SearchCriteria<FirewallRuleVO> sc = sb.create();
          _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
  
          if (id != null) {
              sc.setParameters("id", id);
          }
  
          if (tags != null && !tags.isEmpty()) {
              int count = 0;
              sc.setJoinParameters("tagSearch", "resourceType", ResourceObjectType.FirewallRule.toString());
              for (String key : tags.keySet()) {
                  sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key);
                  sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key));
                  count++;
              }
          }
  
          if (display != null) {
              sc.setParameters("display", display);
          }
  
          if (ipId != null) {
              sc.setParameters("ip", ipId);
          }
  
          if (networkId != null) {
              sc.setParameters("networkId", networkId);
          }
  
          if (cmd instanceof ListIpv6FirewallRulesCmd) {
              sc.setParameters("purpose", Purpose.Ipv6Firewall);
          } else {
              sc.setParameters("purpose", Purpose.Firewall);
          }
  
          if (trafficType != null) {
              sc.setParameters("trafficType", trafficType);
          }
  
          Pair<List<FirewallRuleVO>, Integer> result = _firewallDao.searchAndCount(sc, filter);
          return new Pair<List<? extends FirewallRule>, Integer>(result.first(), result.second());
      }
  
      //Intermediate function used in detectRulesConflict to check for the conflicting cidrs in the rules already applied and the newRule being applied.
      boolean detectConflictingCidrs(List<String> cidrList1, List<String> cidrList2){
          if(cidrList1.isEmpty() && cidrList2.isEmpty()){
              return true;
          }
  
          Collection<String> similar = new HashSet<String>(cidrList1);
          similar.retainAll(cidrList2);
          return (similar.size()>0);
      }
  
      @Override
      public void detectRulesConflict(FirewallRule newRule) throws NetworkRuleConflictException {
          List<FirewallRuleVO> rules;
          if (newRule.getSourceIpAddressId() != null) {
               rules = _firewallDao.listByIpAndPurposeAndNotRevoked(newRule.getSourceIpAddressId(), null);
              assert (rules.size() >= 1) : "For network rules, we now always first persist the rule and then check for "
                  + "network conflicts so we should at least have one rule at this point.";
          } else {
              // fetches only firewall egress rules.
              rules = _firewallDao.listByNetworkPurposeTrafficTypeAndNotRevoked(newRule.getNetworkId(), Purpose.Firewall, newRule.getTrafficType());
              assert (rules.size() >= 1);
          }
  
          for (FirewallRuleVO rule : rules) {
              if (rule.getId() == newRule.getId()) {
                  continue; // Skips my own rule.
              }
  
              boolean oneOfRulesIsFirewall =
                  ((rule.getPurpose() == Purpose.Firewall || newRule.getPurpose() == Purpose.Firewall) && ((newRule.getPurpose() != rule.getPurpose()) || (!newRule.getProtocol()
                              .equalsIgnoreCase(rule.getProtocol()))));
  
              // if both rules are firewall/port forwarding and their cidrs are different, we can skip port ranges verification
              boolean duplicatedCidrs = false;
  
              boolean bothRulesFirewall = (rule.getPurpose() == newRule.getPurpose() && rule.getPurpose() == Purpose.Firewall);
              if (bothRulesFirewall) {
                  _firewallDao.loadSourceCidrs(rule);
                  _firewallDao.loadSourceCidrs((FirewallRuleVO)newRule);
  
                  if (ObjectUtils.anyNull(rule.getSourceCidrList(), newRule.getSourceCidrList())) {
                      continue;
                  }
  
                  _firewallDao.loadDestinationCidrs(rule);
                  _firewallDao.loadDestinationCidrs((FirewallRuleVO) newRule);
  
                  duplicatedCidrs = detectConflictingCidrs(rule.getSourceCidrList(), newRule.getSourceCidrList()) && detectConflictingCidrs(rule.getDestinationCidrList(), newRule.getDestinationCidrList());
              }
  
              boolean bothRulesPortForwarding = rule.getPurpose() == newRule.getPurpose() && rule.getPurpose() == Purpose.PortForwarding;
              if (bothRulesPortForwarding) {
                  _firewallDao.loadSourceCidrs(rule);
                  _firewallDao.loadSourceCidrs((FirewallRuleVO) newRule);
  
                  if (ObjectUtils.anyNull(rule.getSourceCidrList(), newRule.getSourceCidrList())) {
                      continue;
                  }
  
                  duplicatedCidrs = detectConflictingCidrs(rule.getSourceCidrList(), newRule.getSourceCidrList());
              }
  
              if (!oneOfRulesIsFirewall) {
                  if (rule.getPurpose() == Purpose.StaticNat && newRule.getPurpose() != Purpose.StaticNat) {
                      throw new NetworkRuleConflictException("There is 1 to 1 Nat rule specified for the ip address id=" + newRule.getSourceIpAddressId());
                  } else if (rule.getPurpose() != Purpose.StaticNat && newRule.getPurpose() == Purpose.StaticNat) {
                      throw new NetworkRuleConflictException("There is already firewall rule specified for the ip address id=" + newRule.getSourceIpAddressId());
                  }
              }
  
              // Checking if the rule applied is to the same network that is passed in the rule.
              if (rule.getNetworkId() != newRule.getNetworkId() && rule.getState() != State.Revoke) {
                  throw new NetworkRuleConflictException("New rule is for a different network than what's specified in rule " + rule.getXid());
              }
  
              //Check for the ICMP protocol. This has to be done separately from other protocols as we need to check the ICMP codes and ICMP type also.
              if (newRule.getProtocol().equalsIgnoreCase(NetUtils.ICMP_PROTO) && newRule.getProtocol().equalsIgnoreCase(rule.getProtocol())) {
                  if (newRule.getIcmpCode().longValue() == rule.getIcmpCode().longValue() && newRule.getIcmpType().longValue() == rule.getIcmpType().longValue() &&
                      newRule.getProtocol().equalsIgnoreCase(rule.getProtocol()) && duplicatedCidrs) {
                      throw new InvalidParameterValueException(String.format("New rule conflicts with existing rule: %s", rule));
                  }
              }
  
              boolean notNullPorts =
                  (newRule.getSourcePortStart() != null && newRule.getSourcePortEnd() != null && rule.getSourcePortStart() != null && rule.getSourcePortEnd() != null);
              boolean nullPorts =
                  (newRule.getSourcePortStart() == null && newRule.getSourcePortEnd() == null && rule.getSourcePortStart() == null && rule.getSourcePortEnd() == null);
  
              // If ports are not specified and cidrs are same and protocol is also same(NOT ICMP as it is separately checked above)
              if(nullPorts && duplicatedCidrs && (rule.getProtocol().equalsIgnoreCase(newRule.getProtocol())) && !newRule.getProtocol().equalsIgnoreCase(NetUtils.ICMP_PROTO)) {
                  throw new NetworkRuleConflictException("There is already a firewall rule specified with protocol = " +newRule.getProtocol()+ " and no ports");
              }
  
              if (!notNullPorts) {
                  continue;
              } else if (checkIfRulesHaveConflictingPortRanges(newRule, rule, oneOfRulesIsFirewall, bothRulesFirewall, bothRulesPortForwarding, duplicatedCidrs)) {
                  // we allow port forwarding rules with the same parameters but different protocols
                  boolean allowPf =
                      (rule.getPurpose() == Purpose.PortForwarding && newRule.getPurpose() == Purpose.PortForwarding && !newRule.getProtocol().equalsIgnoreCase(
                          rule.getProtocol())) || (rule.getPurpose() == Purpose.Vpn && newRule.getPurpose() == Purpose.PortForwarding && !newRule.getProtocol().equalsIgnoreCase(
                              rule.getProtocol()));
                  boolean allowStaticNat =
                      (rule.getPurpose() == Purpose.StaticNat && newRule.getPurpose() == Purpose.StaticNat && !newRule.getProtocol().equalsIgnoreCase(rule.getProtocol()));
  
                  boolean allowVpnPf =
                          (rule.getPurpose() == Purpose.PortForwarding && newRule.getPurpose() == Purpose.Vpn && !newRule.getProtocol().equalsIgnoreCase(rule.getProtocol()));
  
                  boolean allowVpnLb =
                          (rule.getPurpose() == Purpose.LoadBalancing && newRule.getPurpose() == Purpose.Vpn && !newRule.getProtocol().equalsIgnoreCase(rule.getProtocol()));
  
                  if (!(allowPf || allowStaticNat || oneOfRulesIsFirewall || allowVpnPf || allowVpnLb)) {
                      throw new NetworkRuleConflictException(String.format("The range specified, %d-%d, conflicts with rule %s which has %d-%d", newRule.getSourcePortStart(), newRule.getSourcePortEnd(), rule, rule.getSourcePortStart(), rule.getSourcePortEnd()));
                  }
              }
          }
  
          if (logger.isDebugEnabled()) {
              logger.debug("No network rule conflicts detected for " + newRule + " against " + (rules.size() - 1) + " existing rules");
          }
      }
  
      protected boolean checkIfRulesHaveConflictingPortRanges(FirewallRule newRule, FirewallRule rule, boolean oneOfRulesIsFirewall, boolean bothRulesFirewall, boolean bothRulesPortForwarding, boolean duplicatedCidrs) {
          String rulesAsString = String.format("[%s] and [%s]", rule, newRule);
  
          if (oneOfRulesIsFirewall) {
              logger.debug(String.format("Only one of the rules (%s) is firewall; therefore, their port ranges will not conflict.",
                      rulesAsString));
              return false;
          }
  
          if ((bothRulesFirewall || bothRulesPortForwarding) && !duplicatedCidrs) {
              logger.debug(String.format("Both rules (%s) are firewall/port forwarding, but they do not have duplicated CIDRs; therefore, their port ranges will not conflict.",
                      rulesAsString));
              return false;
          }
  
          if (rule.getSourcePortStart() <= newRule.getSourcePortStart() && rule.getSourcePortEnd() >= newRule.getSourcePortStart()) {
              logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString));
              return true;
          }
  
          if (rule.getSourcePortStart() <= newRule.getSourcePortEnd() && rule.getSourcePortEnd() >= newRule.getSourcePortEnd()) {
              logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString));
              return true;
          }
  
          if (newRule.getSourcePortStart() <= rule.getSourcePortStart() && newRule.getSourcePortEnd() >= rule.getSourcePortStart()) {
              logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString));
              return true;
          }
  
          if (newRule.getSourcePortStart() <= rule.getSourcePortEnd() && newRule.getSourcePortEnd() >= rule.getSourcePortEnd()) {
              logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString));
              return true;
          }
  
          logger.debug(String.format("Rules (%s) do not have conflicting port ranges.", rulesAsString));
          return false;
      }
  
      @Override
      public void validateFirewallRule(Account caller, IPAddressVO ipAddress, Integer portStart, Integer portEnd, String proto, Purpose purpose, FirewallRuleType type,
          Long networkId, FirewallRule.TrafficType trafficType) {
          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);
          }
  
          // start port can't be bigger than end port
          if (portStart != null && portEnd != null && portStart > portEnd) {
              throw new InvalidParameterValueException("Start port can't be bigger than end port");
          }
  
          if (ipAddress == null && type == FirewallRuleType.System) {
              return;
          }
  
          if (ipAddress != null) {
              if (ipAddress.getAssociatedWithNetworkId() == null) {
                      throw new InvalidParameterValueException("Unable to create firewall rule ; ip with specified id is not associated with any network");
              } else {
                  networkId = ipAddress.getAssociatedWithNetworkId();
              }
  
              // Validate ip address
              _accountMgr.checkAccess(caller, null, true, ipAddress);
          }
  
          //network id either has to be passed explicitly, or implicitly as a part of ipAddress object
          if (networkId == null) {
              throw new InvalidParameterValueException("Unable to retrieve network id to validate the rule");
          }
  
          Network network = _networkModel.getNetwork(networkId);
          assert network != null : "Can't create rule as network associated with public ip address is null?";
  
          if (trafficType == FirewallRule.TrafficType.Egress) {
              _accountMgr.checkAccess(caller, null, true, network);
          }
  
          // Verify that the network guru supports the protocol specified
          Map<Network.Capability, String> caps = null;
  
          if (purpose == Purpose.LoadBalancing) {
              if (!_elbEnabled) {
                  caps = _networkModel.getNetworkServiceCapabilities(network.getId(), Service.Lb);
              }
          } else if (purpose == Purpose.PortForwarding) {
              caps = _networkModel.getNetworkServiceCapabilities(network.getId(), Service.PortForwarding);
          } else if (purpose == Purpose.Firewall) {
              if (routedIpv4Manager.isVirtualRouterGateway(network)) {
                  throw new CloudRuntimeException("Unable to create routing firewall rule. Please use routing firewall API instead.");
              }
              caps = _networkModel.getNetworkServiceCapabilities(network.getId(), Service.Firewall);
          }
  
          if (caps != null) {
              String supportedProtocols;
              String supportedTrafficTypes = null;
              if (purpose == FirewallRule.Purpose.Firewall) {
                  supportedTrafficTypes = caps.get(Capability.SupportedTrafficDirection).toLowerCase();
              }
  
              if (purpose == FirewallRule.Purpose.Firewall && trafficType == FirewallRule.TrafficType.Egress) {
                  supportedProtocols = caps.get(Capability.SupportedEgressProtocols).toLowerCase();
              } else {
                  supportedProtocols = caps.get(Capability.SupportedProtocols).toLowerCase();
              }
  
              if (!supportedProtocols.contains(proto.toLowerCase())) {
                  throw new InvalidParameterValueException("Protocol " + proto + " is not supported in zone " + network.getDataCenterId());
              } else if (proto.equalsIgnoreCase(NetUtils.ICMP_PROTO) && purpose != Purpose.Firewall) {
                  throw new InvalidParameterValueException("Protocol " + proto + " is currently supported only for rules with purpose " + Purpose.Firewall);
              } else if (purpose == Purpose.Firewall && !supportedTrafficTypes.contains(trafficType.toString().toLowerCase())) {
                  throw new InvalidParameterValueException(String.format("Traffic Type %s is currently supported by Firewall in network %s", trafficType, network));
              }
          }
  
      }
  
      @Override
      public boolean applyRules(List<? extends FirewallRule> rules, boolean continueOnError, boolean updateRulesInDB) throws ResourceUnavailableException {
          boolean success = true;
          if (rules == null || rules.size() == 0) {
              logger.debug("There are no rules to forward to the network elements");
              return true;
          }
          Purpose purpose = rules.get(0).getPurpose();
          boolean applied;
          if (purpose.equals(Purpose.Ipv6Firewall)) {
              Network network = _networkDao.findById(rules.get(0).getNetworkId());
              applied = applyRules(network, purpose, rules);
          } else {
              applied = _ipAddrMgr.applyRules(rules, purpose, this, continueOnError);
          }
          if (!applied) {
              logger.warn("Rules are not completely applied");
              return false;
          } else {
              if (updateRulesInDB) {
                  for (FirewallRule rule : rules) {
                      if (rule.getState() == FirewallRule.State.Revoke) {
                          FirewallRuleVO relatedRule = _firewallDao.findByRelatedId(rule.getId());
                          if (relatedRule != null) {
                              logger.warn(String.format("Can't remove the firewall rule [%s] as it has related firewall rule [%s]; leaving it in Revoke state", rule, relatedRule));
                              success = false;
                          } else {
                              removeRule(rule);
                              if (rule.getSourceIpAddressId() != null) {
                                  //if the rule is the last one for the ip address assigned to VPC, unassign it from the network
                                  _vpcMgr.unassignIPFromVpcNetwork(rule.getSourceIpAddressId(), rule.getNetworkId());
                              }
                          }
                      } else if (rule.getState() == FirewallRule.State.Add) {
                          FirewallRuleVO ruleVO = _firewallDao.findById(rule.getId());
                          ruleVO.setState(FirewallRule.State.Active);
                          _firewallDao.update(ruleVO.getId(), ruleVO);
                      }
                  }
              }
          }
  
          return success;
      }
  
      @Override
      public boolean applyRules(Network network, Purpose purpose, List<? extends FirewallRule> rules) throws ResourceUnavailableException {
          boolean handled = false;
          switch (purpose) {
          /* StaticNatRule would be applied by Firewall provider, since the incompatible of two object */
          case StaticNat:
          case Firewall:
          case Ipv6Firewall:
              for (FirewallServiceProvider fwElement : _firewallElements) {
                  Network.Provider provider = fwElement.getProvider();
                  boolean  isFwProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Firewall, provider);
                  if (!isFwProvider) {
                      continue;
                  }
                  handled = fwElement.applyFWRules(network, rules);
                  if (handled)
                      break;
              }
              break;
          case PortForwarding:
                  for (PortForwardingServiceProvider element : _pfElements) {
                  Network.Provider provider = element.getProvider();
                  boolean  isPfProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.PortForwarding, provider);
                  if (!isPfProvider) {
                      continue;
                  }
                      handled = element.applyPFRules(network, (List<PortForwardingRule>)rules);
                  if (handled)
                      break;
              }
              break;
              /*        case NetworkACL:
              for (NetworkACLServiceProvider element: _networkAclElements) {
                  Network.Provider provider = element.getProvider();
                  boolean  isAclProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.NetworkACL, provider);
                  if (!isAclProvider) {
                      continue;
                  }
                  handled = element.applyNetworkACLs(network, rules);
                  if (handled)
                      break;
              }
              break;*/
          default:
                  assert (false) : "Unexpected fall through in applying rules to the network elements";
              logger.error("FirewallManager cannot process rules of type " + purpose);
              throw new CloudRuntimeException("FirewallManager cannot process rules of type " + purpose);
          }
          return handled;
      }
  
      @Override
      public void removeRule(FirewallRule rule) {
  
          //remove the rule
          _firewallDao.remove(rule.getId());
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating firewall rule", async = true)
      public boolean applyIngressFwRules(long ipId, Account caller) throws ResourceUnavailableException {
          return applyIngressFirewallRules(ipId, caller);
      }
  
      @Override
      @DB
      public boolean applyIngressFirewallRules(long ipId, Account caller) throws ResourceUnavailableException {
          try {
              IPAddressVO ipAddress = _ipAddressDao.acquireInLockTable(ipId);
              if (ipAddress == null) {
                  logger.error(String.format("Unable to acquire lock for public IP [%s].", ipId));
                  throw new CloudRuntimeException("Unable to acquire lock for public IP.");
              }
              List<FirewallRuleVO> rules = _firewallDao.listByIpAndPurpose(ipId, Purpose.Firewall);
              return applyFirewallRules(rules, false, caller);
          } finally {
              _ipAddressDao.releaseFromLockTable(ipId);
          }
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_EGRESS_OPEN, eventDescription = "creating egress firewall rule", async = true)
      public boolean applyEgressFirewallRules(FirewallRule rule, Account caller) throws ResourceUnavailableException {
          List<FirewallRuleVO> rules = _firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), Purpose.Firewall, FirewallRule.TrafficType.Egress);
          return applyFirewallRules(rules, false, caller);
      }
  
      @Override
      public boolean applyFirewallRules(List<FirewallRuleVO> rules, boolean continueOnError, Account caller) {
  
          if (rules.size() == 0) {
              logger.debug("There are no firewall rules to apply");
              return true;
          }
  
          for (FirewallRuleVO rule : rules) {
              // validate rule - for NSX
              long networkId = rule.getNetworkId();
              validateNsxConstraints(networkId, rule);
              // load cidrs if any
              rule.setSourceCidrList(_firewallCidrsDao.getSourceCidrs(rule.getId()));
              rule.setDestinationCidrsList(_firewallDcidrsDao.getDestCidrs(rule.getId()));
          }
  
          if (caller != null) {
              _accountMgr.checkAccess(caller, null, true, rules.toArray(new FirewallRuleVO[rules.size()]));
          }
  
          try {
              if (!applyRules(rules, continueOnError, true)) {
                  return false;
              }
          } catch (ResourceUnavailableException ex) {
              logger.warn("Failed to apply firewall rules due to : "+ ex.getMessage());
              return false;
          }
  
          return true;
      }
  
      private void validateNsxConstraints(long networkId, FirewallRuleVO rule) {
          String protocol = rule.getProtocol();
          final Network network = entityManager.findById(Network.class, networkId);
          final DataCenter dc = entityManager.findById(DataCenter.class, network.getDataCenterId());
          final NsxProviderVO nsxProvider = nsxProviderDao.findByZoneId(dc.getId());
          if (Objects.isNull(nsxProvider)) {
              return;
          }
  
          if (NetUtils.ICMP_PROTO.equals(protocol.toLowerCase(Locale.ROOT)) && (rule.getIcmpType() == -1 || rule.getIcmpCode() == -1)
                  && State.Add.equals(rule.getState())) {
              String errorMsg = "Passing -1 for ICMP type is not supported for NSX enabled zones";
              logger.error(errorMsg);
              throw new InvalidParameterValueException(errorMsg);
          }
  
          if (List.of(NetUtils.TCP_PROTO, NetUtils.UDP_PROTO).contains(protocol.toLowerCase(Locale.ROOT)) &&
                  (Objects.isNull(rule.getSourcePortStart()) || Objects.isNull(rule.getSourcePortEnd())) &&
              State.Add.equals(rule.getState())) {
              String errorMsg = "Source start and end ports are required to be passed";
              logger.error(errorMsg);
              throw new InvalidParameterValueException(errorMsg);
          }
      }
  
      @Override
      public boolean applyDefaultEgressFirewallRule(Long networkId, boolean defaultPolicy, boolean add) throws ResourceUnavailableException {
  
          logger.debug("applying default firewall egress rules ");
  
          NetworkVO network = _networkDao.findById(networkId);
          List<String> sourceCidr = new ArrayList<String>();
          List<String> destCidr = new ArrayList<String>();
  
  
          sourceCidr.add(network.getCidr());
          destCidr.add(NetUtils.ALL_IP4_CIDRS);
  
          FirewallRuleVO ruleVO =
              new FirewallRuleVO(null, null, null, null, "all", networkId, network.getAccountId(), network.getDomainId(), Purpose.Firewall, sourceCidr, destCidr, null, null, null,
                  FirewallRule.TrafficType.Egress, FirewallRuleType.System);
          ruleVO.setState(add ? State.Add : State.Revoke);
          List<FirewallRuleVO> rules = new ArrayList<FirewallRuleVO>();
          rules.add(ruleVO);
  
          try {
              //this is not required to store in db because we don't to add this rule along with the normal rules
              if (!applyRules(rules, false, false)) {
                  return  false;
              }
          } catch (ResourceUnavailableException ex) {
              logger.warn("Failed to apply default egress rules for guest network due to ", ex);
              return false;
          }
          return true;
      }
  
      protected boolean revokeFirewallRule(long ruleId, boolean apply, Account caller, long userId) {
          FirewallRuleVO rule = _firewallDao.findById(ruleId);
          if (rule == null || !Arrays.asList(Purpose.Firewall, Purpose.Ipv6Firewall).contains(rule.getPurpose())) {
              throw new InvalidParameterValueException("Unable to find " + ruleId + " having purpose " + Arrays.asList(Purpose.Firewall, Purpose.Ipv6Firewall));
          }
  
          if (rule.getType() == FirewallRuleType.System && !_accountMgr.isRootAdmin(caller.getId())) {
              throw new InvalidParameterValueException("Only root admin can delete the system wide firewall rule");
          }
  
          _accountMgr.checkAccess(caller, null, true, rule);
  
          revokeRule(rule, caller, userId, false);
  
          boolean success = false;
          Long networkId = rule.getNetworkId();
  
          if (apply) {
              // ingress firewall rule
              if (rule.getSourceIpAddressId() != null) {
                  //fetches ingress firewall, ingress firewall rules associated with the ip
                  List<FirewallRuleVO> rules = _firewallDao.listByIpAndPurpose(rule.getSourceIpAddressId(), Purpose.Firewall);
                  return applyFirewallRules(rules, false, caller);
              } else if (networkId != null) {
                  //egress firewall rule, or ipv4/ipv6 routing firewall rule
                  List<FirewallRuleVO> rules = _firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), rule.getPurpose(), rule.getTrafficType());
                  return applyFirewallRules(rules, false, caller);
              }
          } else {
              success = true;
          }
  
          return success;
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_CLOSE, eventDescription = "revoking firewall rule", async = true)
      public boolean revokeIngressFwRule(long ruleId, boolean apply) {
          return revokeIngressFirewallRule(ruleId, apply);
      }
  
  
      @Override
      public boolean revokeIngressFirewallRule(long ruleId, boolean apply) {
          Account caller = CallContext.current().getCallingAccount();
          long userId = CallContext.current().getCallingUserId();
          return revokeFirewallRule(ruleId, apply, caller, userId);
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_EGRESS_CLOSE, eventDescription = "revoking egress firewall rule", async = true)
      public boolean revokeEgressFirewallRule(long ruleId, boolean apply) {
          Account caller = CallContext.current().getCallingAccount();
          long userId = CallContext.current().getCallingUserId();
          return revokeFirewallRule(ruleId, apply, caller, userId);
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_UPDATE, eventDescription = "updating firewall rule", async = true)
      public FirewallRule updateIngressFirewallRule(long ruleId, String customId, Boolean forDisplay) {
          Account caller = CallContext.current().getCallingAccount();
          return updateFirewallRule(ruleId, customId, caller, forDisplay);
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_EGRESS_UPDATE, eventDescription = "updating egress firewall rule", async = true)
      public FirewallRule updateEgressFirewallRule(long ruleId, String customId, Boolean forDisplay) {
          Account caller = CallContext.current().getCallingAccount();
          return updateFirewallRule(ruleId, customId, caller, forDisplay);
      }
  
      protected FirewallRule updateFirewallRule(long ruleId, String customId, Account caller, Boolean forDisplay) {
          FirewallRuleVO rule = _firewallDao.findById(ruleId);
          if (rule == null || rule.getPurpose() != Purpose.Firewall) {
              throw new InvalidParameterValueException("Unable to find " + ruleId + " having purpose " + Purpose.Firewall);
          }
  
          if (rule.getType() == FirewallRuleType.System && caller.getType() != Account.Type.ADMIN) {
              throw new InvalidParameterValueException("Only root admin can update the system wide firewall rule");
          }
  
          _accountMgr.checkAccess(caller, null, true, rule);
  
          if (customId != null) {
              rule.setUuid(customId);
          }
  
          if (forDisplay != null) {
              rule.setDisplay(forDisplay);
          }
  
          _firewallDao.update(ruleId, rule);
  
          return _firewallDao.findById(ruleId);
      }
  
      @Override
      @DB
      public void revokeRule(final FirewallRuleVO rule, Account caller, long userId, final boolean needUsageEvent) {
          if (caller != null) {
              _accountMgr.checkAccess(caller, null, true, rule);
          }
  
          Transaction.execute(new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
          boolean generateUsageEvent = false;
  
          if (rule.getState() == State.Staged) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Found a rule that is still in stage state so just removing it: " + rule);
              }
              removeRule(rule);
              generateUsageEvent = true;
          } else if (rule.getState() == State.Add || rule.getState() == State.Active) {
              rule.setState(State.Revoke);
              _firewallDao.update(rule.getId(), rule);
              generateUsageEvent = true;
          }
  
          if (generateUsageEvent && needUsageEvent) {
                      UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_RULE_DELETE, rule.getAccountId(), 0, rule.getId(), null, rule.getClass().getName(),
                          rule.getUuid());
          }
              }
          });
      }
  
      @Override
      public FirewallRule getFirewallRule(long ruleId) {
          return _firewallDao.findById(ruleId);
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_CLOSE, eventDescription = "revoking firewall rule", async = true)
      public boolean revokeFirewallRulesForIp(IpAddress ip, long userId, Account caller) throws ResourceUnavailableException {
          List<FirewallRule> rules = new ArrayList<FirewallRule>();
  
          List<FirewallRuleVO> fwRules = _firewallDao.listByIpAndPurposeAndNotRevoked(ip.getId(), Purpose.Firewall);
          if (logger.isDebugEnabled()) {
              logger.debug("Releasing {} firewall rules for ip {}", fwRules.size(), ip);
          }
  
          for (FirewallRuleVO rule : fwRules) {
              // Mark all Firewall rules as Revoke, but don't revoke them yet - we have to revoke all rules for ip, no
              // need to send them one by one
              revokeFirewallRule(rule.getId(), false, caller, Account.ACCOUNT_ID_SYSTEM);
          }
  
          // now send everything to the backend
          List<FirewallRuleVO> rulesToApply = _firewallDao.listByIpAndPurpose(ip.getId(), Purpose.Firewall);
          //apply rules
          if (!applyFirewallRules(rulesToApply, rulesContinueOnErrFlag, caller)) {
              if (!rulesContinueOnErrFlag) {
                  return false;
              }
          }
          // Now we check again in case more rules have been inserted.
          rules.addAll(_firewallDao.listByIpAndPurposeAndNotRevoked(ip.getId(), Purpose.Firewall));
  
          if (logger.isDebugEnabled()) {
              logger.debug("Successfully released firewall rules for ip {} and # of rules now = {}", ip, rules.size());
          }
  
          return rules.size() == 0;
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating firewall rule", create = true)
      public FirewallRule createRuleForAllCidrs(long ipAddrId, Account caller, Integer startPort, Integer endPort, String protocol, Integer icmpCode, Integer icmpType,
          Long relatedRuleId, long networkId) throws NetworkRuleConflictException {
  
          // If firwallRule for this port range already exists, return it
          List<FirewallRuleVO> rules = _firewallDao.listByIpPurposePortsProtocolAndNotRevoked(ipAddrId, startPort, endPort, protocol, Purpose.Firewall);
          if (!rules.isEmpty()) {
              return rules.get(0);
          }
  
          List<String> oneCidr = new ArrayList<String>();
          oneCidr.add(NetUtils.ALL_IP4_CIDRS);
          return createFirewallRule(ipAddrId, caller, null, startPort, endPort, protocol, oneCidr, null, icmpCode, icmpType, relatedRuleId, FirewallRule.FirewallRuleType.User,
              networkId, FirewallRule.TrafficType.Ingress, true);
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_CLOSE, eventDescription = "revoking firewall rule", async = true)
      public boolean revokeAllFirewallRulesForNetwork(Network network, long userId, Account caller) throws ResourceUnavailableException {
          List<FirewallRule> rules = new ArrayList<FirewallRule>();
  
          List<FirewallRuleVO> fwRules = _firewallDao.listByNetworkAndPurposeAndNotRevoked(network.getId(), Purpose.Firewall);
          if (logger.isDebugEnabled()) {
              logger.debug("Releasing {} firewall rules for network {}", fwRules.size(), network);
          }
  
          for (FirewallRuleVO rule : fwRules) {
              // Mark all Firewall rules as Revoke, but don't revoke them yet - we have to revoke all rules for ip, no
              // need to send them one by one
              revokeFirewallRule(rule.getId(), false, caller, Account.ACCOUNT_ID_SYSTEM);
          }
  
          // now send everything to the backend
          List<FirewallRuleVO> rulesToApply = _firewallDao.listByNetworkAndPurpose(network.getId(), Purpose.Firewall);
          boolean success = applyFirewallRules(rulesToApply, true, caller);
  
          // Now we check again in case more rules have been inserted.
          rules.addAll(_firewallDao.listByNetworkAndPurposeAndNotRevoked(network.getId(), Purpose.Firewall));
  
          if (logger.isDebugEnabled()) {
              logger.debug("Successfully released firewall rules for network {} and # of rules now = {}", network, rules.size());
          }
  
          return success && rules.size() == 0;
      }
  
      @Override
      public boolean revokeRelatedFirewallRule(long ruleId, boolean apply) {
          FirewallRule fwRule = _firewallDao.findByRelatedId(ruleId);
  
          if (fwRule == null) {
              logger.trace("No related firewall rule exists for rule id=" + ruleId + " so returning true here");
              return true;
          }
  
          logger.debug("Revoking Firewall rule [{}] as a part of rule delete id={} with apply={}", fwRule, ruleId, apply);
          return revokeIngressFirewallRule(fwRule.getId(), apply);
  
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_CLOSE, eventDescription = "revoking firewall rule", async = true)
      public boolean revokeFirewallRulesForVm(long vmId) {
          boolean success = true;
          UserVmVO vm = _vmDao.findByIdIncludingRemoved(vmId);
          if (vm == null) {
              return false;
          }
  
          List<PortForwardingRuleVO> pfRules = _pfRulesDao.listByVm(vmId);
          List<FirewallRuleVO> staticNatRules = _firewallDao.listStaticNatByVmId(vm.getId());
          List<FirewallRuleVO> firewallRules = new ArrayList<FirewallRuleVO>();
  
          // Make a list of firewall rules to reprogram
          for (PortForwardingRuleVO pfRule : pfRules) {
              FirewallRuleVO relatedRule = _firewallDao.findByRelatedId(pfRule.getId());
              if (relatedRule != null) {
                  firewallRules.add(relatedRule);
              }
          }
  
          for (FirewallRuleVO staticNatRule : staticNatRules) {
              FirewallRuleVO relatedRule = _firewallDao.findByRelatedId(staticNatRule.getId());
              if (relatedRule != null) {
                  firewallRules.add(relatedRule);
              }
          }
  
          Set<Long> ipsToReprogram = new HashSet<Long>();
  
          if (firewallRules.isEmpty()) {
              logger.debug("No firewall rules are found for vm: {}", vm);
              return true;
          } else {
              logger.debug("Found {} to cleanup for vm: {}", firewallRules.size(), vm);
          }
  
          for (FirewallRuleVO rule : firewallRules) {
              // Mark firewall rules as Revoked, but don't revoke it yet (apply=false)
              revokeFirewallRule(rule.getId(), false, _accountMgr.getSystemAccount(), Account.ACCOUNT_ID_SYSTEM);
              ipsToReprogram.add(rule.getSourceIpAddressId());
          }
  
          // apply rules for all ip addresses
          for (Long ipId : ipsToReprogram) {
              IPAddressVO ip = _ipAddressDao.findById(ipId);
              logger.debug("Applying firewall rules for ip address {} with id={} as a part of vm expunge", ip, ipId);
              try {
                  success = success && applyIngressFirewallRules(ipId, _accountMgr.getSystemAccount());
              } catch (ResourceUnavailableException ex) {
                  logger.warn("Failed to apply firewall rules for ip {}", ip);
                  success = false;
              }
          }
  
          return success;
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating firewall rule", create = true)
      public boolean addSystemFirewallRules(IPAddressVO ip, Account acct) {
          List<FirewallRuleVO> systemRules = _firewallDao.listSystemRules();
          for (FirewallRuleVO rule : systemRules) {
              try {
                  if (rule.getSourceCidrList() == null && (rule.getPurpose() == Purpose.Firewall || rule.getPurpose() == Purpose.NetworkACL)) {
                      _firewallDao.loadSourceCidrs(rule);
                  }
                  createFirewallRule(ip.getId(), acct, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), rule.getSourceCidrList(),null,
                          rule.getIcmpCode(), rule.getIcmpType(), rule.getRelated(), FirewallRuleType.System, rule.getNetworkId(), rule.getTrafficType(), true);
              } catch (Exception e) {
                  logger.debug("Failed to add system wide firewall rule, due to:" + e.toString());
              }
          }
          return true;
      }
  
      public List<FirewallServiceProvider> getFirewallElements() {
          return _firewallElements;
      }
  
      @Inject
      public void setFirewallElements(List<FirewallServiceProvider> firewallElements) {
          _firewallElements = firewallElements;
      }
  
      public List<PortForwardingServiceProvider> getPfElements() {
          return _pfElements;
      }
  
      @Inject
      public void setPfElements(List<PortForwardingServiceProvider> pfElements) {
          _pfElements = pfElements;
      }
  
      public List<StaticNatServiceProvider> getStaticNatElements() {
          return _staticNatElements;
      }
  
      @Inject
      public void setStaticNatElements(List<StaticNatServiceProvider> staticNatElements) {
          _staticNatElements = staticNatElements;
      }
  
      public List<NetworkACLServiceProvider> getNetworkAclElements() {
          return _networkAclElements;
      }
  
      @Inject
      public void setNetworkAclElements(List<NetworkACLServiceProvider> networkAclElements) {
          _networkAclElements = networkAclElements;
      }
  
  }
  