Handle public IP race conditions (#9234)

* Lock public IP

* Release IP if ID is not null

* Fix NPEs

Co-authored-by: Henrique Sato <henrique.sato@scclouds.com.br>
This commit is contained in:
Henrique Sato 2024-06-29 01:58:01 -03:00 committed by GitHub
parent 063dc60114
commit d79735606b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 340 additions and 330 deletions

View File

@ -725,50 +725,59 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
@Override @Override
@DB @DB
public boolean disassociatePublicIpAddress(long addrId, long userId, Account caller) { public boolean disassociatePublicIpAddress(long addrId, long userId, Account caller) {
boolean success = true; boolean success = true;
IPAddressVO ipToBeDisassociated = _ipAddressDao.findById(addrId);
PublicIpQuarantine publicIpQuarantine = null; try {
// Cleanup all ip address resources - PF/LB/Static nat rules IPAddressVO ipToBeDisassociated = _ipAddressDao.acquireInLockTable(addrId);
if (!cleanupIpResources(addrId, userId, caller)) {
success = false;
s_logger.warn("Failed to release resources for ip address id=" + addrId);
}
IPAddressVO ip = markIpAsUnavailable(addrId); if (ipToBeDisassociated == null) {
if (ip == null) { s_logger.error(String.format("Unable to acquire lock on public IP %s.", addrId));
return true; throw new CloudRuntimeException("Unable to acquire lock on public IP.");
} }
if (s_logger.isDebugEnabled()) { PublicIpQuarantine publicIpQuarantine = null;
s_logger.debug("Releasing ip id=" + addrId + "; sourceNat = " + ip.isSourceNat()); // Cleanup all ip address resources - PF/LB/Static nat rules
} if (!cleanupIpResources(addrId, userId, caller)) {
success = false;
s_logger.warn("Failed to release resources for ip address id=" + addrId);
}
if (ip.getAssociatedWithNetworkId() != null) { IPAddressVO ip = markIpAsUnavailable(addrId);
Network network = _networksDao.findById(ip.getAssociatedWithNetworkId()); if (ip == null) {
try { return true;
if (!applyIpAssociations(network, rulesContinueOnErrFlag)) { }
s_logger.warn("Unable to apply ip address associations for " + network);
success = false; if (s_logger.isDebugEnabled()) {
s_logger.debug("Releasing ip id=" + addrId + "; sourceNat = " + ip.isSourceNat());
}
if (ip.getAssociatedWithNetworkId() != null) {
Network network = _networksDao.findById(ip.getAssociatedWithNetworkId());
try {
if (!applyIpAssociations(network, rulesContinueOnErrFlag)) {
s_logger.warn("Unable to apply ip address associations for " + network);
success = false;
}
} catch (ResourceUnavailableException e) {
throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e);
} }
} catch (ResourceUnavailableException e) { } else if (ip.getState() == State.Releasing) {
throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e); publicIpQuarantine = addPublicIpAddressToQuarantine(ipToBeDisassociated, caller.getDomainId());
_ipAddressDao.unassignIpAddress(ip.getId());
} }
} else if (ip.getState() == State.Releasing) {
publicIpQuarantine = addPublicIpAddressToQuarantine(ipToBeDisassociated, caller.getDomainId());
_ipAddressDao.unassignIpAddress(ip.getId());
}
annotationDao.removeByEntityType(AnnotationService.EntityType.PUBLIC_IP_ADDRESS.name(), ip.getUuid()); annotationDao.removeByEntityType(AnnotationService.EntityType.PUBLIC_IP_ADDRESS.name(), ip.getUuid());
if (success) { if (success) {
if (ip.isPortable()) { if (ip.isPortable()) {
releasePortableIpAddress(addrId); releasePortableIpAddress(addrId);
}
s_logger.debug("Released a public ip id=" + addrId);
} else if (publicIpQuarantine != null) {
removePublicIpAddressFromQuarantine(publicIpQuarantine.getId(), "Public IP address removed from quarantine as there was an error while disassociating it.");
} }
s_logger.debug("Released a public ip id=" + addrId); } finally {
} else if (publicIpQuarantine != null) { _ipAddressDao.releaseFromLockTable(addrId);
removePublicIpAddressFromQuarantine(publicIpQuarantine.getId(), "Public IP address removed from quarantine as there was an error while disassociating it.");
} }
return success; return success;

View File

@ -1612,6 +1612,10 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
} }
NetworkVO network = _networksDao.findById(networkId); NetworkVO network = _networksDao.findById(networkId);
if (network == null) {
throw new CloudRuntimeException("Could not find network associated with public IP.");
}
NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId()); NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
if (offering.getGuestType() != GuestType.Isolated) { if (offering.getGuestType() != GuestType.Isolated) {
return true; return true;

View File

@ -194,57 +194,54 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
return createFirewallRule(sourceIpAddressId, caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), 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()); 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. //Destination CIDR capability is currently implemented for egress rules only. For others, the field is passed as null.
@DB @DB
protected FirewallRule createFirewallRule(final Long ipAddrId, Account caller, final String xId, final Integer portStart, final Integer portEnd, protected FirewallRule createFirewallRule(final Long ipAddrId, Account caller, final String xId, final Integer portStart, final Integer portEnd, final String protocol,
final String protocol, final List<String> sourceCidrList, final List<String> destCidrList, final Integer icmpCode, final Integer icmpType, final Long relatedRuleId, final List<String> sourceCidrList, final List<String> destCidrList, final Integer icmpCode, final Integer icmpType, final Long relatedRuleId,
final FirewallRule.FirewallRuleType type, final FirewallRule.FirewallRuleType type, final Long networkId, final FirewallRule.TrafficType trafficType, final Boolean forDisplay) throws NetworkRuleConflictException {
final Long networkId, final FirewallRule.TrafficType trafficType, final Boolean forDisplay) throws NetworkRuleConflictException {
IPAddressVO ipAddress = null; IPAddressVO ipAddress = null;
if (ipAddrId != null) { try {
// this for ingress firewall rule, for egress id is null // Validate ip address
ipAddress = _ipAddressDao.findById(ipAddrId); if (ipAddrId != null) {
// Validate ip address // this for ingress firewall rule, for egress id is null
if (ipAddress == null && type == FirewallRule.FirewallRuleType.User) { ipAddress = _ipAddressDao.acquireInLockTable(ipAddrId);
throw new InvalidParameterValueException("Unable to create firewall rule; " + "couldn't locate IP address by id in the system"); 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); }
} _networkModel.checkIpForService(ipAddress, Service.Firewall, null);
}
validateFirewallRule(caller, ipAddress, portStart, portEnd, protocol, Purpose.Firewall, type, networkId, trafficType); 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 // 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)) { if (!protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (icmpCode != null || icmpType != null)) {
throw new InvalidParameterValueException("Can specify icmpCode and icmpType for ICMP protocol only"); throw new InvalidParameterValueException("Can specify icmpCode and icmpType for ICMP protocol only");
} }
if (protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (portStart != null || portEnd != null)) { if (protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (portStart != null || portEnd != null)) {
throw new InvalidParameterValueException("Can't specify start/end port when protocol is ICMP"); throw new InvalidParameterValueException("Can't specify start/end port when protocol is ICMP");
} }
Long accountId = null; Long accountId = null;
Long domainId = null; Long domainId = null;
if (ipAddress != null) { if (ipAddress != null) {
//Ingress firewall rule //Ingress firewall rule
accountId = ipAddress.getAllocatedToAccountId(); accountId = ipAddress.getAllocatedToAccountId();
domainId = ipAddress.getAllocatedInDomainId(); domainId = ipAddress.getAllocatedInDomainId();
} else if (networkId != null) { } else if (networkId != null) {
//egress firewall rule //egress firewall rule
Network network = _networkModel.getNetwork(networkId); Network network = _networkModel.getNetwork(networkId);
accountId = network.getAccountId(); accountId = network.getAccountId();
domainId = network.getDomainId(); domainId = network.getDomainId();
} }
final Long accountIdFinal = accountId; final Long accountIdFinal = accountId;
final Long domainIdFinal = domainId; final Long domainIdFinal = domainId;
return Transaction.execute(new TransactionCallbackWithException<FirewallRuleVO, NetworkRuleConflictException>() { return Transaction.execute((TransactionCallbackWithException<FirewallRuleVO, NetworkRuleConflictException>) status -> {
@Override FirewallRuleVO newRule = new FirewallRuleVO(xId, ipAddrId, portStart, portEnd, protocol.toLowerCase(), networkId, accountIdFinal, domainIdFinal, Purpose.Firewall,
public FirewallRuleVO doInTransaction(TransactionStatus status) throws NetworkRuleConflictException { sourceCidrList, destCidrList, icmpCode, icmpType, relatedRuleId, trafficType);
FirewallRuleVO newRule =
new FirewallRuleVO(xId, ipAddrId, portStart, portEnd, protocol.toLowerCase(), networkId, accountIdFinal, domainIdFinal, Purpose.Firewall,
sourceCidrList, destCidrList, icmpCode, icmpType, relatedRuleId, trafficType);
newRule.setType(type); newRule.setType(type);
if (forDisplay != null) { if (forDisplay != null) {
newRule.setDisplay(forDisplay); newRule.setDisplay(forDisplay);
@ -261,8 +258,12 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
CallContext.current().putContextParameter(FirewallRule.class, newRule.getId()); CallContext.current().putContextParameter(FirewallRule.class, newRule.getId());
return newRule; return newRule;
});
} finally {
if (ipAddrId != null) {
_ipAddressDao.releaseFromLockTable(ipAddrId);
} }
}); }
} }
@Override @Override
@ -668,9 +669,19 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
} }
@Override @Override
@DB
public boolean applyIngressFirewallRules(long ipId, Account caller) throws ResourceUnavailableException { public boolean applyIngressFirewallRules(long ipId, Account caller) throws ResourceUnavailableException {
List<FirewallRuleVO> rules = _firewallDao.listByIpAndPurpose(ipId, Purpose.Firewall); try {
return applyFirewallRules(rules, false, caller); IPAddressVO ipAddress = _ipAddressDao.acquireInLockTable(ipId);
if (ipAddress == null) {
s_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 @Override

View File

@ -1814,13 +1814,12 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
} }
return cidr; return cidr;
} }
@DB @DB
@Override @Override
public LoadBalancer createPublicLoadBalancer(final String xId, final String name, final String description, final int srcPort, final int destPort, public LoadBalancer createPublicLoadBalancer(final String xId, final String name, final String description, final int srcPort, final int destPort, final long sourceIpId,
final long sourceIpId, final String protocol, final String algorithm, final boolean openFirewall, final CallContext caller, final String lbProtocol,
final String protocol, final String algorithm, final boolean openFirewall, final CallContext caller, final String lbProtocol, final Boolean forDisplay, String cidrList) final Boolean forDisplay, String cidrList) throws NetworkRuleConflictException {
throws NetworkRuleConflictException {
if (!NetUtils.isValidPort(destPort)) { if (!NetUtils.isValidPort(destPort)) {
throw new InvalidParameterValueException("privatePort is an invalid value: " + destPort); throw new InvalidParameterValueException("privatePort is an invalid value: " + destPort);
} }
@ -1829,55 +1828,41 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
throw new InvalidParameterValueException("Invalid algorithm: " + algorithm); throw new InvalidParameterValueException("Invalid algorithm: " + algorithm);
} }
final IPAddressVO ipAddr = _ipAddressDao.findById(sourceIpId); try {
// make sure ip address exists final IPAddressVO ipAddr = _ipAddressDao.acquireInLockTable(sourceIpId);
if (ipAddr == null || !ipAddr.readyToUse()) {
InvalidParameterValueException ex = new InvalidParameterValueException("Unable to create load balancer rule, invalid IP address id specified"); // make sure ip address exists
if (ipAddr == null) { if (ipAddr == null || !ipAddr.readyToUse()) {
ex.addProxyObject(String.valueOf(sourceIpId), "sourceIpId"); InvalidParameterValueException ex = new InvalidParameterValueException("Unable to create load balancer rule, invalid IP address id specified");
} else { if (ipAddr == null) {
ex.addProxyObject(String.valueOf(sourceIpId), "sourceIpId");
} else {
ex.addProxyObject(ipAddr.getUuid(), "sourceIpId");
}
throw ex;
} else if (ipAddr.isOneToOneNat()) {
InvalidParameterValueException ex = new InvalidParameterValueException("Unable to create load balancer rule; specified sourceip id has static nat enabled");
ex.addProxyObject(ipAddr.getUuid(), "sourceIpId"); ex.addProxyObject(ipAddr.getUuid(), "sourceIpId");
throw ex;
} }
throw ex;
} else if (ipAddr.isOneToOneNat()) {
InvalidParameterValueException ex = new InvalidParameterValueException("Unable to create load balancer rule; specified sourceip id has static nat enabled");
ex.addProxyObject(ipAddr.getUuid(), "sourceIpId");
throw ex;
}
_accountMgr.checkAccess(caller.getCallingAccount(), null, true, ipAddr); _accountMgr.checkAccess(caller.getCallingAccount(), null, true, ipAddr);
final Long networkId = ipAddr.getAssociatedWithNetworkId(); final Long networkId = ipAddr.getAssociatedWithNetworkId();
if (networkId == null) { if (networkId == null) {
InvalidParameterValueException ex = InvalidParameterValueException ex =
new InvalidParameterValueException("Unable to create load balancer rule ; specified sourceip id is not associated with any network"); new InvalidParameterValueException("Unable to create load balancer rule ; specified sourceip id is not associated with any network");
ex.addProxyObject(ipAddr.getUuid(), "sourceIpId"); ex.addProxyObject(ipAddr.getUuid(), "sourceIpId");
throw ex; throw ex;
} }
// verify that lb service is supported by the network // verify that lb service is supported by the network
isLbServiceSupportedInNetwork(networkId, Scheme.Public); isLbServiceSupportedInNetwork(networkId, Scheme.Public);
_firewallMgr.validateFirewallRule(caller.getCallingAccount(), ipAddr, srcPort, srcPort, protocol, Purpose.LoadBalancing, FirewallRuleType.User, networkId, null); _firewallMgr.validateFirewallRule(caller.getCallingAccount(), ipAddr, srcPort, srcPort, protocol, Purpose.LoadBalancing, FirewallRuleType.User, networkId, null);
LoadBalancerVO newRule = return Transaction.execute((TransactionCallbackWithException<LoadBalancerVO, NetworkRuleConflictException>) status -> {
new LoadBalancerVO(xId, name, description, sourceIpId, srcPort, destPort, algorithm, networkId, ipAddr.getAllocatedToAccountId(), LoadBalancerVO newRule = new LoadBalancerVO(xId, name, description, sourceIpId, srcPort, destPort, algorithm, networkId, ipAddr.getAllocatedToAccountId(),
ipAddr.getAllocatedInDomainId(), lbProtocol, cidrList);
// verify rule is supported by Lb provider of the network
Ip sourceIp = getSourceIp(newRule);
LoadBalancingRule loadBalancing =
new LoadBalancingRule(newRule, new ArrayList<LbDestination>(), new ArrayList<LbStickinessPolicy>(), new ArrayList<LbHealthCheckPolicy>(), sourceIp, null,
lbProtocol);
if (!validateLbRule(loadBalancing)) {
throw new InvalidParameterValueException("LB service provider cannot support this rule");
}
return Transaction.execute(new TransactionCallbackWithException<LoadBalancerVO, NetworkRuleConflictException>() {
@Override
public LoadBalancerVO doInTransaction(TransactionStatus status) throws NetworkRuleConflictException {
LoadBalancerVO newRule =
new LoadBalancerVO(xId, name, description, sourceIpId, srcPort, destPort, algorithm, networkId, ipAddr.getAllocatedToAccountId(),
ipAddr.getAllocatedInDomainId(), lbProtocol, cidrList); ipAddr.getAllocatedInDomainId(), lbProtocol, cidrList);
if (forDisplay != null) { if (forDisplay != null) {
@ -1886,9 +1871,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
// verify rule is supported by Lb provider of the network // verify rule is supported by Lb provider of the network
Ip sourceIp = getSourceIp(newRule); Ip sourceIp = getSourceIp(newRule);
LoadBalancingRule loadBalancing = LoadBalancingRule loadBalancing = new LoadBalancingRule(newRule, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), sourceIp, null, lbProtocol);
new LoadBalancingRule(newRule, new ArrayList<LbDestination>(), new ArrayList<LbStickinessPolicy>(), new ArrayList<LbHealthCheckPolicy>(), sourceIp,
null, lbProtocol);
if (!validateLbRule(loadBalancing)) { if (!validateLbRule(loadBalancing)) {
throw new InvalidParameterValueException("LB service provider cannot support this rule"); throw new InvalidParameterValueException("LB service provider cannot support this rule");
} }
@ -1908,10 +1891,10 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
throw new CloudRuntimeException("Unable to update the state to add for " + newRule); throw new CloudRuntimeException("Unable to update the state to add for " + newRule);
} }
s_logger.debug("Load balancer " + newRule.getId() + " for Ip address id=" + sourceIpId + ", public port " + srcPort + ", private port " + destPort + s_logger.debug("Load balancer " + newRule.getId() + " for Ip address id=" + sourceIpId + ", public port " + srcPort + ", private port " + destPort +
" is added successfully."); " is added successfully.");
CallContext.current().setEventDetails("Load balancer Id: " + newRule.getId()); CallContext.current().setEventDetails("Load balancer Id: " + newRule.getId());
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_LOAD_BALANCER_CREATE, ipAddr.getAllocatedToAccountId(), ipAddr.getDataCenterId(), newRule.getId(), UsageEventUtils.publishUsageEvent(EventTypes.EVENT_LOAD_BALANCER_CREATE, ipAddr.getAllocatedToAccountId(), ipAddr.getDataCenterId(), newRule.getId(),
null, LoadBalancingRule.class.getName(), newRule.getUuid()); null, LoadBalancingRule.class.getName(), newRule.getUuid());
return newRule; return newRule;
} catch (Exception e) { } catch (Exception e) {
@ -1926,9 +1909,10 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
removeLBRule(newRule); removeLBRule(newRule);
} }
} }
} });
}); } finally {
_ipAddressDao.releaseFromLockTable(sourceIpId);
}
} }
@Override @Override

View File

@ -207,124 +207,122 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
final Long ipAddrId = rule.getSourceIpAddressId(); final Long ipAddrId = rule.getSourceIpAddressId();
IPAddressVO ipAddress = _ipAddressDao.findById(ipAddrId);
// Validate ip address
if (ipAddress == null) {
throw new InvalidParameterValueException("Unable to create port forwarding rule; ip id=" + ipAddrId + " doesn't exist in the system");
} else if (ipAddress.isOneToOneNat()) {
throw new InvalidParameterValueException("Unable to create port forwarding rule; ip id=" + ipAddrId + " has static nat enabled");
}
final Long networkId = rule.getNetworkId();
Network network = _networkModel.getNetwork(networkId);
//associate ip address to network (if needed)
boolean performedIpAssoc = false;
Nic guestNic;
if (ipAddress.getAssociatedWithNetworkId() == null) {
boolean assignToVpcNtwk = network.getVpcId() != null && ipAddress.getVpcId() != null && ipAddress.getVpcId().longValue() == network.getVpcId();
if (assignToVpcNtwk) {
_networkModel.checkIpForService(ipAddress, Service.PortForwarding, networkId);
s_logger.debug("The ip is not associated with the VPC network id=" + networkId + ", so assigning");
try {
ipAddress = _ipAddrMgr.associateIPToGuestNetwork(ipAddrId, networkId, false);
performedIpAssoc = true;
} catch (Exception ex) {
throw new CloudRuntimeException("Failed to associate ip to VPC network as " + "a part of port forwarding rule creation");
}
}
} else {
_networkModel.checkIpForService(ipAddress, Service.PortForwarding, null);
}
if (ipAddress.getAssociatedWithNetworkId() == null) {
throw new InvalidParameterValueException("Ip address " + ipAddress + " is not assigned to the network " + network);
}
try { try {
_firewallMgr.validateFirewallRule(caller, ipAddress, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), Purpose.PortForwarding, IPAddressVO ipAddress = _ipAddressDao.acquireInLockTable(ipAddrId);
FirewallRuleType.User, networkId, rule.getTrafficType());
final Long accountId = ipAddress.getAllocatedToAccountId(); // Validate ip address
final Long domainId = ipAddress.getAllocatedInDomainId(); if (ipAddress == null) {
throw new InvalidParameterValueException("Unable to create port forwarding rule; ip id=" + ipAddrId + " doesn't exist in the system");
// start port can't be bigger than end port } else if (ipAddress.isOneToOneNat()) {
if (rule.getDestinationPortStart() > rule.getDestinationPortEnd()) { throw new InvalidParameterValueException("Unable to create port forwarding rule; ip id=" + ipAddrId + " has static nat enabled");
throw new InvalidParameterValueException("Start port can't be bigger than end port");
} }
// check that the port ranges are of equal size final Long networkId = rule.getNetworkId();
if ((rule.getDestinationPortEnd() - rule.getDestinationPortStart()) != (rule.getSourcePortEnd() - rule.getSourcePortStart())) { Network network = _networkModel.getNetwork(networkId);
throw new InvalidParameterValueException("Source port and destination port ranges should be of equal sizes."); //associate ip address to network (if needed)
} boolean performedIpAssoc = false;
Nic guestNic;
if (ipAddress.getAssociatedWithNetworkId() == null) {
boolean assignToVpcNtwk = network.getVpcId() != null && ipAddress.getVpcId() != null && ipAddress.getVpcId().longValue() == network.getVpcId();
if (assignToVpcNtwk) {
_networkModel.checkIpForService(ipAddress, Service.PortForwarding, networkId);
// validate user VM exists s_logger.debug("The ip is not associated with the VPC network id=" + networkId + ", so assigning");
UserVm vm = _vmDao.findById(vmId); try {
if (vm == null) { ipAddress = _ipAddrMgr.associateIPToGuestNetwork(ipAddrId, networkId, false);
throw new InvalidParameterValueException("Unable to create port forwarding rule on address " + ipAddress + ", invalid virtual machine id specified (" + performedIpAssoc = true;
vmId + ")."); } catch (Exception ex) {
} else if (vm.getState() == VirtualMachine.State.Destroyed || vm.getState() == VirtualMachine.State.Expunging) { throw new CloudRuntimeException("Failed to associate ip to VPC network as " + "a part of port forwarding rule creation");
throw new InvalidParameterValueException("Invalid user vm: " + vm.getId());
}
// Verify that vm has nic in the network
Ip dstIp = rule.getDestinationIpAddress();
guestNic = _networkModel.getNicInNetwork(vmId, networkId);
if (guestNic == null || guestNic.getIPv4Address() == null) {
throw new InvalidParameterValueException("Vm doesn't belong to network associated with ipAddress");
} else {
dstIp = new Ip(guestNic.getIPv4Address());
}
if (vmIp != null) {
//vm ip is passed so it can be primary or secondary ip addreess.
if (!dstIp.equals(vmIp)) {
//the vm ip is secondary ip to the nic.
// is vmIp is secondary ip or not
NicSecondaryIp secondaryIp = _nicSecondaryDao.findByIp4AddressAndNicId(vmIp.toString(), guestNic.getId());
if (secondaryIp == null) {
throw new InvalidParameterValueException("IP Address is not in the VM nic's network ");
} }
dstIp = vmIp;
} }
} else {
_networkModel.checkIpForService(ipAddress, Service.PortForwarding, null);
} }
//if start port and end port are passed in, and they are not equal to each other, perform the validation if (ipAddress.getAssociatedWithNetworkId() == null) {
boolean validatePortRange = false; throw new InvalidParameterValueException("Ip address " + ipAddress + " is not assigned to the network " + network);
if (rule.getSourcePortStart().intValue() != rule.getSourcePortEnd().intValue() || rule.getDestinationPortStart() != rule.getDestinationPortEnd()) {
validatePortRange = true;
} }
if (validatePortRange) { try {
//source start port and source dest port should be the same. The same applies to dest ports _firewallMgr.validateFirewallRule(caller, ipAddress, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), Purpose.PortForwarding,
if (rule.getSourcePortStart().intValue() != rule.getDestinationPortStart()) { FirewallRuleType.User, networkId, rule.getTrafficType());
throw new InvalidParameterValueException("Private port start should be equal to public port start");
final Long accountId = ipAddress.getAllocatedToAccountId();
final Long domainId = ipAddress.getAllocatedInDomainId();
// start port can't be bigger than end port
if (rule.getDestinationPortStart() > rule.getDestinationPortEnd()) {
throw new InvalidParameterValueException("Start port can't be bigger than end port");
} }
if (rule.getSourcePortEnd().intValue() != rule.getDestinationPortEnd()) { // check that the port ranges are of equal size
throw new InvalidParameterValueException("Private port end should be equal to public port end"); if ((rule.getDestinationPortEnd() - rule.getDestinationPortStart()) != (rule.getSourcePortEnd() - rule.getSourcePortStart())) {
throw new InvalidParameterValueException("Source port and destination port ranges should be of equal sizes.");
} }
}
final Ip dstIpFinal = dstIp; // validate user VM exists
final IPAddressVO ipAddressFinal = ipAddress; UserVm vm = _vmDao.findById(vmId);
return Transaction.execute(new TransactionCallbackWithException<PortForwardingRuleVO, NetworkRuleConflictException>() { if (vm == null) {
@Override throw new InvalidParameterValueException("Unable to create port forwarding rule on address " + ipAddress + ", invalid virtual machine id specified (" +
public PortForwardingRuleVO doInTransaction(TransactionStatus status) throws NetworkRuleConflictException { vmId + ").");
} else if (vm.getState() == VirtualMachine.State.Destroyed || vm.getState() == VirtualMachine.State.Expunging) {
throw new InvalidParameterValueException("Invalid user vm: " + vm.getId());
}
// Verify that vm has nic in the network
Ip dstIp = rule.getDestinationIpAddress();
guestNic = _networkModel.getNicInNetwork(vmId, networkId);
if (guestNic == null || guestNic.getIPv4Address() == null) {
throw new InvalidParameterValueException("Vm doesn't belong to network associated with ipAddress");
} else {
dstIp = new Ip(guestNic.getIPv4Address());
}
if (vmIp != null) {
//vm ip is passed so it can be primary or secondary ip addreess.
if (!dstIp.equals(vmIp)) {
//the vm ip is secondary ip to the nic.
// is vmIp is secondary ip or not
NicSecondaryIp secondaryIp = _nicSecondaryDao.findByIp4AddressAndNicId(vmIp.toString(), guestNic.getId());
if (secondaryIp == null) {
throw new InvalidParameterValueException("IP Address is not in the VM nic's network ");
}
dstIp = vmIp;
}
}
//if start port and end port are passed in, and they are not equal to each other, perform the validation
boolean validatePortRange = false;
if (rule.getSourcePortStart().intValue() != rule.getSourcePortEnd().intValue() || rule.getDestinationPortStart() != rule.getDestinationPortEnd()) {
validatePortRange = true;
}
if (validatePortRange) {
//source start port and source dest port should be the same. The same applies to dest ports
if (rule.getSourcePortStart().intValue() != rule.getDestinationPortStart()) {
throw new InvalidParameterValueException("Private port start should be equal to public port start");
}
if (rule.getSourcePortEnd().intValue() != rule.getDestinationPortEnd()) {
throw new InvalidParameterValueException("Private port end should be equal to public port end");
}
}
final Ip dstIpFinal = dstIp;
final IPAddressVO ipAddressFinal = ipAddress;
return Transaction.execute((TransactionCallbackWithException<PortForwardingRuleVO, NetworkRuleConflictException>) status -> {
PortForwardingRuleVO newRule = PortForwardingRuleVO newRule =
new PortForwardingRuleVO(rule.getXid(), rule.getSourceIpAddressId(), rule.getSourcePortStart(), rule.getSourcePortEnd(), dstIpFinal, new PortForwardingRuleVO(rule.getXid(), rule.getSourceIpAddressId(), rule.getSourcePortStart(), rule.getSourcePortEnd(), dstIpFinal,
rule.getDestinationPortStart(), rule.getDestinationPortEnd(), rule.getProtocol().toLowerCase(), networkId, accountId, domainId, vmId); rule.getDestinationPortStart(), rule.getDestinationPortEnd(), rule.getProtocol().toLowerCase(), networkId, accountId, domainId, vmId);
if (forDisplay != null) { if (forDisplay != null) {
newRule.setDisplay(forDisplay); newRule.setDisplay(forDisplay);
} }
newRule = _portForwardingDao.persist(newRule); newRule = _portForwardingDao.persist(newRule);
// create firewallRule for 0.0.0.0/0 cidr // create firewallRule for 0.0.0.0/0 cidr
if (openFirewall) { if (openFirewall) {
_firewallMgr.createRuleForAllCidrs(ipAddrId, caller, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), null, null, _firewallMgr.createRuleForAllCidrs(ipAddrId, caller, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), null, null,
newRule.getId(), networkId); newRule.getId(), networkId);
} }
try { try {
@ -334,7 +332,7 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
} }
CallContext.current().setEventDetails("Rule Id: " + newRule.getId()); CallContext.current().setEventDetails("Rule Id: " + newRule.getId());
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_RULE_ADD, newRule.getAccountId(), ipAddressFinal.getDataCenterId(), newRule.getId(), null, UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_RULE_ADD, newRule.getAccountId(), ipAddressFinal.getDataCenterId(), newRule.getId(), null,
PortForwardingRule.class.getName(), newRule.getUuid()); PortForwardingRule.class.getName(), newRule.getUuid());
return newRule; return newRule;
} catch (Exception e) { } catch (Exception e) {
if (newRule != null) { if (newRule != null) {
@ -349,16 +347,17 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
throw new CloudRuntimeException("Unable to add rule for the ip id=" + ipAddrId, e); throw new CloudRuntimeException("Unable to add rule for the ip id=" + ipAddrId, e);
} }
});
} finally {
// release ip address if ipassoc was perfored
if (performedIpAssoc) {
//if the rule is the last one for the ip address assigned to VPC, unassign it from the network
IpAddress ip = _ipAddressDao.findById(ipAddress.getId());
_vpcMgr.unassignIPFromVpcNetwork(ip.getId(), networkId);
} }
});
} finally {
// release ip address if ipassoc was perfored
if (performedIpAssoc) {
//if the rule is the last one for the ip address assigned to VPC, unassign it from the network
IpAddress ip = _ipAddressDao.findById(ipAddress.getId());
_vpcMgr.unassignIPFromVpcNetwork(ip.getId(), networkId);
} }
} finally {
_ipAddressDao.releaseFromLockTable(ipAddrId);
} }
} }
@ -370,46 +369,44 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
final Long ipAddrId = rule.getSourceIpAddressId(); final Long ipAddrId = rule.getSourceIpAddressId();
IPAddressVO ipAddress = _ipAddressDao.findById(ipAddrId); try {
IPAddressVO ipAddress = _ipAddressDao.acquireInLockTable(ipAddrId);
// Validate ip address // Validate ip address
if (ipAddress == null) { if (ipAddress == null) {
throw new InvalidParameterValueException("Unable to create static nat rule; ip id=" + ipAddrId + " doesn't exist in the system"); throw new InvalidParameterValueException("Unable to create static nat rule; ip id=" + ipAddrId + " doesn't exist in the system");
} else if (ipAddress.isSourceNat() || !ipAddress.isOneToOneNat() || ipAddress.getAssociatedWithVmId() == null) { } else if (ipAddress.isSourceNat() || !ipAddress.isOneToOneNat() || ipAddress.getAssociatedWithVmId() == null) {
throw new NetworkRuleConflictException("Can't do static nat on ip address: " + ipAddress.getAddress()); throw new NetworkRuleConflictException("Can't do static nat on ip address: " + ipAddress.getAddress());
} }
_firewallMgr.validateFirewallRule(caller, ipAddress, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), Purpose.StaticNat, _firewallMgr.validateFirewallRule(caller, ipAddress, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), Purpose.StaticNat,
FirewallRuleType.User, null, rule.getTrafficType()); FirewallRuleType.User, null, rule.getTrafficType());
final Long networkId = ipAddress.getAssociatedWithNetworkId(); final Long networkId = ipAddress.getAssociatedWithNetworkId();
final Long accountId = ipAddress.getAllocatedToAccountId(); final Long accountId = ipAddress.getAllocatedToAccountId();
final Long domainId = ipAddress.getAllocatedInDomainId(); final Long domainId = ipAddress.getAllocatedInDomainId();
_networkModel.checkIpForService(ipAddress, Service.StaticNat, null); _networkModel.checkIpForService(ipAddress, Service.StaticNat, null);
Network network = _networkModel.getNetwork(networkId); Network network = _networkModel.getNetwork(networkId);
NetworkOffering off = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId()); NetworkOffering off = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId());
if (off.isElasticIp()) { if (off.isElasticIp()) {
throw new InvalidParameterValueException("Can't create ip forwarding rules for the network where elasticIP service is enabled"); throw new InvalidParameterValueException("Can't create ip forwarding rules for the network where elasticIP service is enabled");
} }
//String dstIp = _networkModel.getIpInNetwork(ipAddress.getAssociatedWithVmId(), networkId);
final String dstIp = ipAddress.getVmIp();
return Transaction.execute(new TransactionCallbackWithException<StaticNatRule, NetworkRuleConflictException>() {
@Override
public StaticNatRule doInTransaction(TransactionStatus status) throws NetworkRuleConflictException {
//String dstIp = _networkModel.getIpInNetwork(ipAddress.getAssociatedWithVmId(), networkId);
final String dstIp = ipAddress.getVmIp();
return Transaction.execute((TransactionCallbackWithException<StaticNatRule, NetworkRuleConflictException>) status -> {
FirewallRuleVO newRule = FirewallRuleVO newRule =
new FirewallRuleVO(rule.getXid(), rule.getSourceIpAddressId(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol().toLowerCase(), new FirewallRuleVO(rule.getXid(), rule.getSourceIpAddressId(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol().toLowerCase(),
networkId, accountId, domainId, rule.getPurpose(), null, null, null, null, null); networkId, accountId, domainId, rule.getPurpose(), null, null, null, null, null);
newRule = _firewallDao.persist(newRule); newRule = _firewallDao.persist(newRule);
// create firewallRule for 0.0.0.0/0 cidr // create firewallRule for 0.0.0.0/0 cidr
if (openFirewall) { if (openFirewall) {
_firewallMgr.createRuleForAllCidrs(ipAddrId, caller, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), null, null, _firewallMgr.createRuleForAllCidrs(ipAddrId, caller, rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), null, null,
newRule.getId(), networkId); newRule.getId(), networkId);
} }
try { try {
@ -419,11 +416,9 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
} }
CallContext.current().setEventDetails("Rule Id: " + newRule.getId()); CallContext.current().setEventDetails("Rule Id: " + newRule.getId());
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_RULE_ADD, newRule.getAccountId(), 0, newRule.getId(), null, FirewallRule.class.getName(), UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_RULE_ADD, newRule.getAccountId(), 0, newRule.getId(), null, FirewallRule.class.getName(),
newRule.getUuid()); newRule.getUuid());
StaticNatRule staticNatRule = new StaticNatRuleImpl(newRule, dstIp); return new StaticNatRuleImpl(newRule, dstIp);
return staticNatRule;
} catch (Exception e) { } catch (Exception e) {
if (newRule != null) { if (newRule != null) {
// no need to apply the rule as it wasn't programmed on the backend yet // no need to apply the rule as it wasn't programmed on the backend yet
@ -436,9 +431,10 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
} }
throw new CloudRuntimeException("Unable to add static nat rule for the ip id=" + newRule.getSourceIpAddressId(), e); throw new CloudRuntimeException("Unable to add static nat rule for the ip id=" + newRule.getSourceIpAddressId(), e);
} }
} });
}); } finally {
_ipAddressDao.releaseFromLockTable(ipAddrId);
}
} }
@Override @Override

View File

@ -155,6 +155,7 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc
return vpns; return vpns;
} }
@Override @Override
@DB @DB
public RemoteAccessVpn createRemoteAccessVpn(final long publicIpId, String ipRange, boolean openFirewall, final Boolean forDisplay) throws NetworkRuleConflictException { public RemoteAccessVpn createRemoteAccessVpn(final long publicIpId, String ipRange, boolean openFirewall, final Boolean forDisplay) throws NetworkRuleConflictException {
@ -172,92 +173,97 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc
throw new InvalidParameterValueException("The Ip address is not ready to be used yet: " + ipAddr.getAddress()); throw new InvalidParameterValueException("The Ip address is not ready to be used yet: " + ipAddr.getAddress());
} }
IPAddressVO ipAddress = _ipAddressDao.findById(publicIpId); try {
IPAddressVO ipAddress = _ipAddressDao.acquireInLockTable(publicIpId);
Long networkId = ipAddress.getAssociatedWithNetworkId(); if (ipAddress == null) {
if (networkId != null) { s_logger.error(String.format("Unable to acquire lock on public IP %s.", publicIpId));
_networkMgr.checkIpForService(ipAddress, Service.Vpn, null); throw new CloudRuntimeException("Unable to acquire lock on public IP.");
}
final Long vpcId = ipAddress.getVpcId();
if (vpcId != null && ipAddress.isSourceNat()) {
assert networkId == null;
openFirewall = false;
}
final boolean openFirewallFinal = openFirewall;
if (networkId == null && vpcId == null) {
throw new InvalidParameterValueException("Unable to create remote access vpn for the ipAddress: " + ipAddr.getAddress().addr() +
" as ip is not associated with any network or VPC");
}
RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddress(publicIpId);
if (vpnVO != null) {
if (vpnVO.getState() == RemoteAccessVpn.State.Added) {
return vpnVO;
} }
throw new InvalidParameterValueException(String.format("A remote Access VPN already exists for the public IP address [%s].", ipAddr.getAddress().toString())); Long networkId = ipAddress.getAssociatedWithNetworkId();
} if (networkId != null) {
_networkMgr.checkIpForService(ipAddress, Service.Vpn, null);
}
if (ipRange == null) { final Long vpcId = ipAddress.getVpcId();
ipRange = RemoteAccessVpnClientIpRange.valueIn(ipAddr.getAccountId()); if (vpcId != null && ipAddress.isSourceNat()) {
} assert networkId == null;
openFirewall = false;
}
validateIpRange(ipRange, InvalidParameterValueException.class); final boolean openFirewallFinal = openFirewall;
String[] range = ipRange.split("-"); if (networkId == null && vpcId == null) {
throw new InvalidParameterValueException("Unable to create remote access vpn for the ipAddress: " + ipAddr.getAddress().addr() +
" as ip is not associated with any network or VPC");
}
Pair<String, Integer> cidr = null; RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddress(publicIpId);
if (networkId != null) {
long ipAddressOwner = ipAddr.getAccountId();
vpnVO = _remoteAccessVpnDao.findByAccountAndNetwork(ipAddressOwner, networkId);
if (vpnVO != null) { if (vpnVO != null) {
if (vpnVO.getState() == RemoteAccessVpn.State.Added) { if (vpnVO.getState() == RemoteAccessVpn.State.Added) {
return vpnVO; return vpnVO;
} }
throw new InvalidParameterValueException(String.format("A remote access VPN already exists for the account [%s].", ipAddressOwner)); throw new InvalidParameterValueException(String.format("A remote Access VPN already exists for the public IP address [%s].", ipAddr.getAddress().toString()));
} }
Network network = _networkMgr.getNetwork(networkId);
if (!_networkMgr.areServicesSupportedInNetwork(network.getId(), Service.Vpn)) { if (ipRange == null) {
throw new InvalidParameterValueException("Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId()); ipRange = RemoteAccessVpnClientIpRange.valueIn(ipAddr.getAccountId());
} }
cidr = NetUtils.getCidr(network.getCidr());
} else {
Vpc vpc = _vpcDao.findById(vpcId);
cidr = NetUtils.getCidr(vpc.getCidr());
}
String[] guestIpRange = NetUtils.getIpRangeFromCidr(cidr.first(), cidr.second()); validateIpRange(ipRange, InvalidParameterValueException.class);
if (NetUtils.ipRangesOverlap(range[0], range[1], guestIpRange[0], guestIpRange[1])) {
throw new InvalidParameterValueException("Invalid ip range: " + ipRange + " overlaps with guest ip range " + guestIpRange[0] + "-" + guestIpRange[1]);
}
long startIp = NetUtils.ip2Long(range[0]); String[] range = ipRange.split("-");
final String newIpRange = NetUtils.long2Ip(++startIp) + "-" + range[1];
final String sharedSecret = PasswordGenerator.generatePresharedKey(_pskLength);
return Transaction.execute(new TransactionCallbackWithException<RemoteAccessVpn, NetworkRuleConflictException>() { Pair<String, Integer> cidr = null;
@Override
public RemoteAccessVpn doInTransaction(TransactionStatus status) throws NetworkRuleConflictException { if (networkId != null) {
long ipAddressOwner = ipAddr.getAccountId();
vpnVO = _remoteAccessVpnDao.findByAccountAndNetwork(ipAddressOwner, networkId);
if (vpnVO != null) {
if (vpnVO.getState() == RemoteAccessVpn.State.Added) {
return vpnVO;
}
throw new InvalidParameterValueException(String.format("A remote access VPN already exists for the account [%s].", ipAddressOwner));
}
Network network = _networkMgr.getNetwork(networkId);
if (!_networkMgr.areServicesSupportedInNetwork(network.getId(), Service.Vpn)) {
throw new InvalidParameterValueException("Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId());
}
cidr = NetUtils.getCidr(network.getCidr());
} else {
Vpc vpc = _vpcDao.findById(vpcId);
cidr = NetUtils.getCidr(vpc.getCidr());
}
String[] guestIpRange = NetUtils.getIpRangeFromCidr(cidr.first(), cidr.second());
if (NetUtils.ipRangesOverlap(range[0], range[1], guestIpRange[0], guestIpRange[1])) {
throw new InvalidParameterValueException("Invalid ip range: " + ipRange + " overlaps with guest ip range " + guestIpRange[0] + "-" + guestIpRange[1]);
}
long startIp = NetUtils.ip2Long(range[0]);
final String newIpRange = NetUtils.long2Ip(++startIp) + "-" + range[1];
final String sharedSecret = PasswordGenerator.generatePresharedKey(_pskLength);
return Transaction.execute((TransactionCallbackWithException<RemoteAccessVpn, NetworkRuleConflictException>) status -> {
if (vpcId == null) { if (vpcId == null) {
_rulesMgr.reservePorts(ipAddr, NetUtils.UDP_PROTO, Purpose.Vpn, openFirewallFinal, caller, NetUtils.VPN_PORT, NetUtils.VPN_L2TP_PORT, _rulesMgr.reservePorts(ipAddr, NetUtils.UDP_PROTO, Purpose.Vpn, openFirewallFinal, caller, NetUtils.VPN_PORT, NetUtils.VPN_L2TP_PORT,
NetUtils.VPN_NATT_PORT); NetUtils.VPN_NATT_PORT);
} }
RemoteAccessVpnVO vpnVO = RemoteAccessVpnVO remoteAccessVpnVO = new RemoteAccessVpnVO(ipAddr.getAccountId(), ipAddr.getDomainId(), ipAddr.getAssociatedWithNetworkId(),
new RemoteAccessVpnVO(ipAddr.getAccountId(), ipAddr.getDomainId(), ipAddr.getAssociatedWithNetworkId(), publicIpId, vpcId, range[0], newIpRange, publicIpId, vpcId, range[0], newIpRange, sharedSecret);
sharedSecret);
if (forDisplay != null) { if (forDisplay != null) {
vpnVO.setDisplay(forDisplay); remoteAccessVpnVO.setDisplay(forDisplay);
} }
return _remoteAccessVpnDao.persist(vpnVO); return _remoteAccessVpnDao.persist(remoteAccessVpnVO);
} });
}); } finally {
_ipAddressDao.releaseFromLockTable(publicIpId);
}
} }
private void validateRemoteAccessVpnConfiguration() throws ConfigurationException { private void validateRemoteAccessVpnConfiguration() throws ConfigurationException {