This is an automated email from the ASF dual-hosted git repository. pearl11594 pushed a commit to branch netris-integration-upstream in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 0e4dde9111489d98f2ff598502e7fb23f743dabc Author: Wei Zhou <[email protected]> AuthorDate: Fri Dec 20 14:53:48 2024 +0100 Netris FR1b: Support Remote Access VPN and Site-to-Site VPN in VPC VR (#41) * Static Routes: support nexthop * Update api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java Co-authored-by: Pearl Dsilva <[email protected]> * PR#10064 VR: apply iptables rules when add/remove static routes * PR#10065 UI: fix cannot open 'Edit tags' modal for static routes * PR#10066 Static Routes: fix check on wrong global configuration * PR#10067 VR: fix site-2-site VPN if split connections is enabled * PR#10081 server: do not allocate nic on public network for NSX VPC VR * PR#10082 UI: create VPC network offering with conserve mode * PR#10083 VR: allow outgoing traffic from RAS/VPN clients * PR#10086 server: fix typo removeaccessvpn in VirtualRouterElement * server: Add check on Public IP for remote access VPN * Revert "PR#10083 VR: allow outgoing traffic from RAS/VPN clients" This reverts commit 2f9b9f428947cac91de322fbdf4a980902a1c0a0. * VPC: fetch same used IP for domain router if VR is not Source NAT * VR: pass has_public_network to VR and configure RA/S2S VPN left peers * Revert "PR#10081 server: do not allocate nic on public network for NSX VPC VR" This reverts commit 809e269ed6b361d9df1fcef6537762c5612863e0. * VPC: fetch same used IP for domain router if VR is not Source NAT (v2) * VR: fix /etc/hosts and nameservers in dnsmasq.conf if VPC VR is not guest gateway prior to this PR ``` root@r-1167-VM:~# cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 r-1167-VM ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.21.1.33 dummy-vpc-vpn-001 172.21.1.1 r-1167-VM data-server root@r-1167-VM:~# cat /etc/dnsmasq.d/cloud.conf dhcp-hostsfile=/etc/dhcphosts.txt listen-address=127.0.0.1,172.21.1.234 dhcp-range=set:interface-eth1-0,172.21.1.234,static dhcp-option=tag:interface-eth1-0,15,cs2cloud.internal dhcp-option=tag:interface-eth1-0,6,172.21.1.1,10.0.32.1,8.8.8.8 dhcp-option=tag:interface-eth1-0,3,172.21.1.1 dhcp-option=eth1,26,1500 dhcp-option=tag:interface-eth1-0,1,255.255.255.0 ``` the lines should be ``` 172.21.1.234 r-1167-VM data-server dhcp-option=tag:interface-eth1-0,6,10.0.32.1,8.8.8.8 ``` * server: Enable static NAT for Domain router if it is not Source NAT * server: Enable static NAT for Domain router on UI * server: assign Public IP to VPC VR and enable static nat if VR is not Source NAT * server: configure dns1 if VR is not Source NAT * server: remove check on Firewall service when list network service providers * UI: remove dot from message.enabled.vpn * systemvm: add default route via first guest gateway if VR does not have public IP/interface * VR: add fw_dhcpserver for shared network * VR: pass has_public_network to VR and configure RA/S2S VPN left peers (v2) * UI: fix request error when create a VPC tier in a non-Netris/NSX env * systemvm: add default route via first guest gateway (v2) * VR: configure iptables rules for S2S vpn on first guest interface * VR: allow FORWARD to guest interfaces if VR is not Public * VR: configure remote access vpn on first guest interface if not public * VR: fix error 789 in RA VPN client when both RA and S2S are configured * server: Apply Static Route for RA/S2S VPN in VPC VR * VR: do not set mark for Public interface when VR is not really public * VPN: do not disable static nat if it is used by a RA/S2S VPN * server: skip check on network conserve mode if disable/enable RA VPN on Router IP * server: set forRouter to false when release a IP * VR: diable IP spoofing protection on default guest network * VR: fix iptables rules only when only S2S vpn is enabled * UI: show 'VPN Connections' section * VPC: new methods to configure/reconfigure Static NAT for VPC VR * API: set Type in ip address response to DomainRouter if it is used by VR * server: do not allow IP release if it is used by RA or S2S VPN gateway * VR: check if interface is added * VR: add default route only when ip is associated to first guest interface * VR: fix ipsec conf for l2tp and s2s vpn * server: save placeholder IP for VPC VR to fix the new VR IP when vpc tier is auto-shutdown * server: get non-placeholder NIC for VPC VR * VR: wait 15 seconds after starting password server * server: fix unable to configure static nat due to 'invalid virtual machine id' * UI: fix link of router in info card * VPC: apply static route for VPC VPN if needed (refactoring) * server: fix VR IP of first VPC tier is the VM gateway * server: update or remove all existing static routes when shutdown a network * server: update ipaddress after disabling static nat to fix vpc deletion issue * servr: disable remote access VPN as part of VPC dstroy * server: apply static routes when implement a vpc tier * server: apply static routes even if next hop is null * server: fix Cannot invoke "com.cloud.vm.NicProfile.getRequestedIPv4()" because "requested" is null * Netris: Update Vpn provider to VpcVirtualRouter * Netris: Add Vpn service to network offerings and networks * server: fix CIDR of VPN ip range * server: set isVrGuestGateway by SoureNat/Gateway service with Provider.VPCVirtualRouter * VR: password server takes 10-15 seconds to start if VR IP is not configured in /etc/hosts * Netris: add back routesPutBody.setStateStatus * engine/schema: remove SQL changes in schema-41910to42000.sql --------- Co-authored-by: Pearl Dsilva <[email protected]> --- api/src/main/java/com/cloud/network/IpAddress.java | 1 + .../com/cloud/network/Site2SiteVpnConnection.java | 2 +- .../java/com/cloud/network/vpc/StaticRoute.java | 4 +- .../com/cloud/network/vpc/StaticRouteProfile.java | 22 +- .../java/com/cloud/network/vpc/VpcService.java | 2 +- .../org/apache/cloudstack/api/ApiConstants.java | 3 + .../api/command/user/vpc/CreateStaticRouteCmd.java | 51 ++- .../api/response/StaticRouteResponse.java | 24 +- .../main/java/com/cloud/network/addr/PublicIp.java | 4 + .../java/com/cloud/network/rules/RulesManager.java | 2 + .../java/com/cloud/network/vpc/VpcManager.java | 17 + .../engine/orchestration/NetworkOrchestrator.java | 19 +- .../com/cloud/network/dao/IPAddressDaoImpl.java | 1 + .../java/com/cloud/network/dao/IPAddressVO.java | 12 + .../com/cloud/network/dao/RemoteAccessVpnDao.java | 2 + .../cloud/network/dao/RemoteAccessVpnDaoImpl.java | 7 + .../cloud/network/dao/Site2SiteVpnGatewayDao.java | 2 + .../network/dao/Site2SiteVpnGatewayDaoImpl.java | 8 + .../java/com/cloud/network/vpc/StaticRouteVO.java | 35 +- .../network/vpc/dao/VpcServiceMapDaoImpl.java | 11 +- .../src/main/java/com/cloud/vm/dao/NicDao.java | 4 + .../src/main/java/com/cloud/vm/dao/NicDaoImpl.java | 20 ++ .../resources/META-INF/db/schema-41910to42000.sql | 6 + .../cloudstack/service/NetrisApiClientImpl.java | 1 + .../main/java/com/cloud/api/ApiResponseHelper.java | 52 ++- .../com/cloud/network/IpAddressManagerImpl.java | 21 ++ .../java/com/cloud/network/NetworkModelImpl.java | 1 + .../network/guru/ExternalGuestNetworkGuru.java | 5 +- .../cloud/network/router/CommandSetupHelper.java | 5 +- .../cloud/network/router/NetworkHelperImpl.java | 9 +- .../cloud/network/router/NicProfileHelperImpl.java | 4 +- .../router/VirtualNetworkApplianceManagerImpl.java | 8 + .../VpcVirtualNetworkApplianceManagerImpl.java | 20 +- .../com/cloud/network/rules/RulesManagerImpl.java | 35 +- .../java/com/cloud/network/vpc/VpcManagerImpl.java | 398 +++++++++++++++++++-- .../network/vpn/RemoteAccessVpnManagerImpl.java | 71 +++- .../cloud/network/vpn/Site2SiteVpnManagerImpl.java | 69 +++- .../com/cloud/server/ConfigurationServerImpl.java | 1 + systemvm/debian/opt/cloud/bin/configure.py | 40 ++- systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 91 ++++- systemvm/debian/opt/cloud/bin/cs/CsConfig.py | 3 + systemvm/debian/opt/cloud/bin/cs/CsDhcp.py | 4 +- systemvm/debian/opt/cloud/bin/cs/CsGuestNetwork.py | 10 +- systemvm/debian/opt/cloud/bin/passwd_server_ip.py | 13 +- ui/public/locales/en.json | 6 +- ui/src/components/view/InfoCard.vue | 2 +- ui/src/config/section/network.js | 3 +- ui/src/views/network/PublicIpResource.vue | 31 +- ui/src/views/network/StaticRoutesTab.vue | 130 +++++-- ui/src/views/network/VpcTab.vue | 5 + ui/src/views/network/VpcTiersTab.vue | 2 +- ui/src/views/offering/AddNetworkOffering.vue | 2 +- .../main/java/com/cloud/utils/net/NetUtils.java | 16 + .../java/com/cloud/utils/net/NetUtilsTest.java | 11 + 54 files changed, 1144 insertions(+), 184 deletions(-) diff --git a/api/src/main/java/com/cloud/network/IpAddress.java b/api/src/main/java/com/cloud/network/IpAddress.java index ae1af450577..70d652b54e9 100644 --- a/api/src/main/java/com/cloud/network/IpAddress.java +++ b/api/src/main/java/com/cloud/network/IpAddress.java @@ -99,4 +99,5 @@ public interface IpAddress extends ControlledEntity, Identity, InternalIdentity, boolean isForSystemVms(); + boolean isForRouter(); } diff --git a/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java b/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java index 994df875f7d..51036abe060 100644 --- a/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java +++ b/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java @@ -24,7 +24,7 @@ import org.apache.cloudstack.api.InternalIdentity; public interface Site2SiteVpnConnection extends ControlledEntity, InternalIdentity, Displayable { enum State { - Pending, Connecting, Connected, Disconnected, Error, + Pending, Connecting, Connected, Disconnected, Error, Removed } @Override diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java index b52ed980893..739fca328b8 100644 --- a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java +++ b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java @@ -33,7 +33,9 @@ public interface StaticRoute extends ControlledEntity, Identity, InternalIdentit /** * @return */ - long getVpcGatewayId(); + Long getVpcGatewayId(); + + String getNextHop(); /** * @return diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java index cb4849f1f7b..c8fc073911f 100644 --- a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java +++ b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java @@ -23,7 +23,8 @@ public class StaticRouteProfile implements StaticRoute { private String targetCidr; private long accountId; private long domainId; - private long gatewayId; + private Long gatewayId; + private String nextHop; private StaticRoute.State state; private long vpcId; String vlanTag; @@ -46,6 +47,18 @@ public class StaticRouteProfile implements StaticRoute { ipAddress = gateway.getIp4Address(); } + public StaticRouteProfile(StaticRoute staticRoute) { + id = staticRoute.getId(); + uuid = staticRoute.getUuid(); + targetCidr = staticRoute.getCidr(); + accountId = staticRoute.getAccountId(); + domainId = staticRoute.getDomainId(); + gatewayId = staticRoute.getVpcGatewayId(); + state = staticRoute.getState(); + vpcId = staticRoute.getVpcId(); + gateway = staticRoute.getNextHop(); + } + @Override public long getAccountId() { return accountId; @@ -57,10 +70,15 @@ public class StaticRouteProfile implements StaticRoute { } @Override - public long getVpcGatewayId() { + public Long getVpcGatewayId() { return gatewayId; } + @Override + public String getNextHop() { + return nextHop; + } + @Override public String getCidr() { return targetCidr; diff --git a/api/src/main/java/com/cloud/network/vpc/VpcService.java b/api/src/main/java/com/cloud/network/vpc/VpcService.java index af2a9847a62..4477f10d1a4 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java @@ -238,7 +238,7 @@ public interface VpcService { * @param cidr * @return */ - StaticRoute createStaticRoute(long gatewayId, String cidr) throws NetworkRuleConflictException; + StaticRoute createStaticRoute(Long gatewayId, Long vpcId, String nextHop, String cidr) throws NetworkRuleConflictException; /** * Lists static routes based on parameters passed to the call diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 196f80264cd..9b66f4c455e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -257,6 +257,7 @@ public class ApiConstants { public static final String PREVIOUS_OWNER_ID = "previousownerid"; public static final String PREVIOUS_OWNER_NAME = "previousownername"; public static final String NEXT_ACL_RULE_ID = "nextaclruleid"; + public static final String NEXT_HOP = "nexthop"; public static final String MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash"; public static final String IMAGE_PATH = "imagepath"; public static final String INSTANCE_CONVERSION_SUPPORTED = "instanceconversionsupported"; @@ -876,6 +877,8 @@ public class ApiConstants { public static final String NETWORK = "network"; public static final String VPC_ID = "vpcid"; public static final String VPC_NAME = "vpcname"; + public static final String VPC_GATEWAY_ID = "vpcgatewayid"; + public static final String VPC_GATEWAY_IP = "vpcgatewayip"; public static final String GATEWAY_ID = "gatewayid"; public static final String CAN_USE_FOR_DEPLOY = "canusefordeploy"; public static final String RESOURCE_IDS = "resourceids"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java index b28c02cb800..41779ccdeb1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.PrivateGatewayResponse; import org.apache.cloudstack.api.response.StaticRouteResponse; +import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.context.CallContext; import com.cloud.event.EventTypes; @@ -45,20 +46,38 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.GATEWAY_ID, type = CommandType.UUID, entityType = PrivateGatewayResponse.class, - required = true, - description = "the gateway id we are creating static route for") + description = "the gateway id we are creating static route for. Mutually exclusive with the nexthop parameter") private Long gatewayId; + @Parameter(name = ApiConstants.VPC_ID, + type = CommandType.UUID, + entityType = VpcResponse.class, + description = "the vpc id for which the static route is created. This is required for nexthop parameter") + private Long vpcId; + + @Parameter(name = ApiConstants.NEXT_HOP, + type = CommandType.STRING, + description = "the next hop of static route. Mutually exclusive with the gatewayid parameter") + private String nextHop; + @Parameter(name = ApiConstants.CIDR, required = true, type = CommandType.STRING, description = "static route cidr") private String cidr; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public long getGatewayId() { + public Long getGatewayId() { return gatewayId; } + public Long getVpcId() { + return vpcId; + } + + public String getNextHop() { + return nextHop; + } + public String getCidr() { return cidr; } @@ -69,7 +88,7 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd { @Override public void create() throws ResourceAllocationException { try { - StaticRoute result = _vpcService.createStaticRoute(getGatewayId(), getCidr()); + StaticRoute result = _vpcService.createStaticRoute(getGatewayId(), getVpcId(), getNextHop(), getCidr()); setEntityId(result.getId()); setEntityUuid(result.getUuid()); } catch (NetworkRuleConflictException ex) { @@ -114,11 +133,8 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd { @Override public long getEntityOwnerId() { - VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId); - if (gateway == null) { - throw new InvalidParameterValueException("Invalid gateway id is specified"); - } - return _entityMgr.findById(Vpc.class, gateway.getVpcId()).getAccountId(); + Long vpcId = getSyncObjId(); + return _entityMgr.findById(Vpc.class, vpcId).getAccountId(); } @Override @@ -128,11 +144,20 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd { @Override public Long getSyncObjId() { - VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId); - if (gateway == null) { - throw new InvalidParameterValueException("Invalid id is specified for the gateway"); + if (gatewayId != null) { + VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId); + if (gateway == null) { + throw new InvalidParameterValueException("Invalid id is specified for the gateway"); + } + return gateway.getVpcId(); + } else if (vpcId != null) { + Vpc vpc = _entityMgr.findById(Vpc.class, vpcId); + if (vpc == null) { + throw new InvalidParameterValueException("Invalid vpc id is specified"); + } + return vpc.getId(); } - return gateway.getVpcId(); + throw new InvalidParameterValueException("One of vpcId or gatewayId must be specified"); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java index 51f8a130383..9d0f90459a7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java @@ -42,9 +42,17 @@ public class StaticRouteResponse extends BaseResponse implements ControlledEntit @Param(description = "VPC the static route belongs to") private String vpcId; - @SerializedName(ApiConstants.GATEWAY_ID) + @SerializedName(ApiConstants.VPC_GATEWAY_ID) @Param(description = "VPC gateway the route is created for") - private String gatewayId; + private String vpcGatewayId; + + @SerializedName(ApiConstants.VPC_GATEWAY_IP) + @Param(description = "IP of VPC gateway the route is created for") + private String vpcGatewayIp; + + @SerializedName(ApiConstants.NEXT_HOP) + @Param(description = "Next hop of the static route") + private String nextHop; @SerializedName(ApiConstants.CIDR) @Param(description = "static route CIDR") @@ -95,8 +103,16 @@ public class StaticRouteResponse extends BaseResponse implements ControlledEntit this.vpcId = vpcId; } - public void setGatewayId(String gatewayId) { - this.gatewayId = gatewayId; + public void setVpcGatewayId(String vpcGatewayId) { + this.vpcGatewayId = vpcGatewayId; + } + + public void setVpcGatewayIp(String vpcGatewayIp) { + this.vpcGatewayIp = vpcGatewayIp; + } + + public void setNextHop(String nextHop) { + this.nextHop = nextHop; } public void setCidr(String cidr) { diff --git a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java index d69a72a02c5..7132094ce11 100644 --- a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java +++ b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java @@ -275,5 +275,9 @@ public class PublicIp implements PublicIpAddress { return false; } + @Override + public boolean isForRouter() { + return _addr.isForRouter(); + } } diff --git a/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java b/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java index 79ffdfdb973..5ba74402a4c 100644 --- a/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java @@ -54,6 +54,8 @@ public interface RulesManager extends RulesService { boolean disableStaticNat(long ipAddressId, Account caller, long callerUserId, boolean releaseIpIfElastic) throws ResourceUnavailableException; + boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke); + /** * @param networkId * @param continueOnError diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java index 15158b72fab..efa039345f8 100644 --- a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java @@ -36,6 +36,7 @@ import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.PhysicalNetwork; import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.IPAddressVO; import com.cloud.offering.NetworkOffering; import com.cloud.user.Account; @@ -177,4 +178,20 @@ public interface VpcManager { * @return */ boolean isSrcNatIpRequired(long vpcOfferingId); + + boolean isSrcNatIpRequiredForVpcVr(long vpcOfferingId); + + List<StaticRouteVO> getVpcStaticRoutes(Long vpcId); + + List<StaticRouteProfile> getVpcStaticRoutes(List<? extends StaticRoute> routes); + + boolean isProviderSupportServiceInVpc(long vpcId, Service service, Provider provider); + + IPAddressVO getIpAddressForVpcVr(Vpc vpc, IPAddressVO ipAddress, boolean allocateIpIfNeeded); + + boolean configStaticNatForVpcVr(Vpc vpc, IPAddressVO ipAddress); + + void reconfigStaticNatForVpcVr(Long vpcId); + + boolean applyStaticRouteForVpcVpnIfNeeded(Long vpcId, boolean updateAllVpn) throws ResourceUnavailableException; } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 47aab7ebbf0..31d42ec65a7 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -1058,7 +1058,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return Transaction.execute(new TransactionCallback<NicVO>() { @Override public NicVO doInTransaction(TransactionStatus status) { - NicVO vo = _nicDao.findByIp4AddressAndNetworkId(profile.getIPv4Address(), networkId); + NicVO vo = _nicDao.findNonPlaceHolderByIp4AddressAndNetworkId(profile.getIPv4Address(), networkId); if (vo == null) { applyProfileToNic(nic, profile, deviceId); vo = _nicDao.persist(nic); @@ -1707,6 +1707,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } } } + if (network.getVpcId() != null) { + _vpcMgr.reconfigStaticNatForVpcVr(network.getVpcId()); + try { + _vpcMgr.applyStaticRouteForVpcVpnIfNeeded(network.getVpcId(), true); + } catch (ResourceUnavailableException e) { + logger.error("Unable to apply static routes for vpc " + network.getVpcId() + " due to " + e.getMessage()); + } + } } finally { for (final NetworkElement element : networkElements) { if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) { @@ -1952,6 +1960,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra ip.setOneToOneNat(false); ip.setAssociatedWithVmId(null); ip.setVmIp(null); + ip.setForRouter(false); _ipAddressDao.update(ip.getId(), ip); } } @@ -3304,6 +3313,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } } } + if (network.getVpcId() != null) { + _vpcMgr.reconfigStaticNatForVpcVr(network.getVpcId()); + try { + _vpcMgr.applyStaticRouteForVpcVpnIfNeeded(network.getVpcId(), true); + } catch (ResourceUnavailableException e) { + logger.error("Unable to apply static routes for vpc " + network.getVpcId() + " due to " + e.getMessage()); + } + } return success; } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java index 5499d04e3a1..0a5ecd25667 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java @@ -197,6 +197,7 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen address.setSourceNat(false); address.setOneToOneNat(false); address.setAssociatedWithVmId(null); + address.setForRouter(false); address.setState(State.Free); address.setAssociatedWithNetworkId(null); address.setVpcId(null); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java index 88e146d2a80..a3a65fdb01b 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java @@ -117,6 +117,9 @@ public class IPAddressVO implements IpAddress { @Column(name = "forsystemvms") private boolean forSystemVms = false; + @Column(name = "for_router") + private boolean forRouter = false; + @Column(name= GenericDao.REMOVED_COLUMN) private Date removed; @@ -388,4 +391,13 @@ public class IPAddressVO implements IpAddress { public boolean isForSystemVms() { return forSystemVms; } + + @Override + public boolean isForRouter() { + return forRouter; + } + + public void setForRouter(boolean forRouter) { + this.forRouter = forRouter; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java index 8113c06c866..f4120a138ac 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java @@ -33,4 +33,6 @@ public interface RemoteAccessVpnDao extends GenericDao<RemoteAccessVpnVO, Long> List<RemoteAccessVpnVO> findByAccount(Long accountId); List<RemoteAccessVpnVO> listByNetworkId(Long networkId); + + List<RemoteAccessVpnVO> listByVpcId(Long vpcId); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java index 484aa6f6631..ccbc60a5562 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java @@ -85,4 +85,11 @@ public class RemoteAccessVpnDaoImpl extends GenericDaoBase<RemoteAccessVpnVO, Lo sc.setParameters("networkId", networkId); return listBy(sc); } + + @Override + public List<RemoteAccessVpnVO> listByVpcId(Long vpcId) { + SearchCriteria<RemoteAccessVpnVO> sc = AllFieldsSearch.create(); + sc.setParameters("vpcId", vpcId); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java index d3fef252f50..3475003c269 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java @@ -20,4 +20,6 @@ import com.cloud.utils.db.GenericDao; public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> { Site2SiteVpnGatewayVO findByVpcId(long vpcId); + + Site2SiteVpnGatewayVO findByPublicIpAddress(long ipAddressId); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java index d1fde963217..0aeefe90c29 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java @@ -35,6 +35,7 @@ public class Site2SiteVpnGatewayDaoImpl extends GenericDaoBase<Site2SiteVpnGatew protected Site2SiteVpnGatewayDaoImpl() { AllFieldsSearch = createSearchBuilder(); AllFieldsSearch.and("vpcId", AllFieldsSearch.entity().getVpcId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("ipAddressId", AllFieldsSearch.entity().getAddrId(), SearchCriteria.Op.EQ); AllFieldsSearch.done(); } @@ -44,4 +45,11 @@ public class Site2SiteVpnGatewayDaoImpl extends GenericDaoBase<Site2SiteVpnGatew sc.setParameters("vpcId", vpcId); return findOneBy(sc); } + + @Override + public Site2SiteVpnGatewayVO findByPublicIpAddress(long ipAddressId) { + SearchCriteria<Site2SiteVpnGatewayVO> sc = AllFieldsSearch.create(); + sc.setParameters("ipAddressId", ipAddressId); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java index 2246bd6eed2..632d96819cd 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java @@ -27,6 +27,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Transient; import com.cloud.utils.db.GenericDao; @@ -42,7 +43,10 @@ public class StaticRouteVO implements StaticRoute { String uuid; @Column(name = "vpc_gateway_id", updatable = false) - long vpcGatewayId; + Long vpcGatewayId; + + @Column(name = "next_hop") + private String nextHop; @Column(name = "cidr") private String cidr; @@ -67,6 +71,9 @@ public class StaticRouteVO implements StaticRoute { uuid = UUID.randomUUID().toString(); } + @Transient + boolean forVpn = false; + /** * @param vpcGatewayId * @param cidr @@ -74,7 +81,7 @@ public class StaticRouteVO implements StaticRoute { * @param accountId TODO * @param domainId TODO */ - public StaticRouteVO(long vpcGatewayId, String cidr, Long vpcId, long accountId, long domainId) { + public StaticRouteVO(Long vpcGatewayId, String cidr, Long vpcId, long accountId, long domainId, String nextHop) { super(); this.vpcGatewayId = vpcGatewayId; this.cidr = cidr; @@ -82,14 +89,32 @@ public class StaticRouteVO implements StaticRoute { this.vpcId = vpcId; this.accountId = accountId; this.domainId = domainId; + this.nextHop = nextHop; + uuid = UUID.randomUUID().toString(); + } + + public StaticRouteVO(String cidr, Long vpcId, long accountId, long domainId, String nextHop, State state, boolean forVpn) { + super(); + this.cidr = cidr; + this.state = state; + this.vpcId = vpcId; + this.accountId = accountId; + this.domainId = domainId; + this.nextHop = nextHop; uuid = UUID.randomUUID().toString(); + this.forVpn = forVpn; } @Override - public long getVpcGatewayId() { + public Long getVpcGatewayId() { return vpcGatewayId; } + @Override + public String getNextHop() { + return nextHop; + } + @Override public String getCidr() { return cidr; @@ -145,4 +170,8 @@ public class StaticRouteVO implements StaticRoute { public String getName() { return null; } + + public boolean isForVpn() { + return forVpn; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java index 753c45fcc78..a5c4c83ff0f 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java @@ -68,8 +68,15 @@ public class VpcServiceMapDaoImpl extends GenericDaoBase<VpcServiceMapVO, Long> @Override public boolean canProviderSupportServiceInVpc(long vpcId, Service service, Provider provider) { - // TODO Auto-generated method stub - return false; + SearchCriteria<VpcServiceMapVO> sc = AllFieldsSearch.create(); + sc.setParameters("vpcId", vpcId); + sc.setParameters("service", service.getName()); + sc.setParameters("provider", provider.getName()); + if (findOneBy(sc) != null) { + return true; + } else { + return false; + } } @Override diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java index d34b03c4cb0..70a2558e2d4 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java @@ -46,8 +46,12 @@ public interface NicDao extends GenericDao<NicVO, Long> { NicVO findByNetworkIdAndTypeIncludingRemoved(long networkId, VirtualMachine.Type vmType); + NicVO findNonPlaceHolderByNetworkIdAndType(long networkId, VirtualMachine.Type vmType); + NicVO findByIp4AddressAndNetworkId(String ip4Address, long networkId); + NicVO findNonPlaceHolderByIp4AddressAndNetworkId(String ip4Address, long networkId); + NicVO findByNetworkIdAndMacAddress(long networkId, String mac); NicVO findDefaultNicForVM(long instanceId); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java index 7d1af1982ae..3618785c1b8 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java @@ -69,6 +69,7 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, Long> implements NicDao { AllFieldsSearch.and("secondaryip", AllFieldsSearch.entity().getSecondaryIp(), Op.EQ); AllFieldsSearch.and("nicid", AllFieldsSearch.entity().getId(), Op.EQ); AllFieldsSearch.and("strategy", AllFieldsSearch.entity().getReservationStrategy(), Op.EQ); + AllFieldsSearch.and("strategyNEQ", AllFieldsSearch.entity().getReservationStrategy(), Op.NEQ); AllFieldsSearch.and("reserverName",AllFieldsSearch.entity().getReserver(),Op.EQ); AllFieldsSearch.and("macAddress", AllFieldsSearch.entity().getMacAddress(), Op.EQ); AllFieldsSearch.and("deviceid", AllFieldsSearch.entity().getDeviceId(), Op.EQ); @@ -195,6 +196,15 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, Long> implements NicDao { return findByNetworkIdAndTypeInternal(networkId, vmType, true); } + @Override + public NicVO findNonPlaceHolderByNetworkIdAndType(long networkId, VirtualMachine.Type vmType) { + SearchCriteria<NicVO> sc = AllFieldsSearch.create(); + sc.setParameters("network", networkId); + sc.setParameters("vmType", vmType); + sc.setParameters("strategyNEQ", Nic.ReservationStrategy.PlaceHolder.toString()); + return findOneBy(sc); + } + @Override public NicVO findByNetworkIdTypeAndGateway(long networkId, VirtualMachine.Type vmType, String gateway) { SearchCriteria<NicVO> sc = AllFieldsSearch.create(); @@ -222,6 +232,16 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, Long> implements NicDao { return findOneBy(sc); } + @Override + public NicVO findNonPlaceHolderByIp4AddressAndNetworkId(String ip4Address, long networkId) { + SearchCriteria<NicVO> sc = AllFieldsSearch.create(); + sc.setParameters("address", ip4Address); + sc.setParameters("network", networkId); + sc.setParameters("strategyNEQ", Nic.ReservationStrategy.PlaceHolder.toString()); + return findOneBy(sc); + } + + @Override public NicVO findByNetworkIdAndMacAddress(long networkId, String mac) { SearchCriteria<NicVO> sc = AllFieldsSearch.create(); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql index 3f39a7da089..739c5644859 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql @@ -453,3 +453,9 @@ ALTER TABLE `cloud`.`network_offerings` DROP COLUMN `for_nsx`; -- Drop the Tungsten and NSX columns from the VPC offerings (replaced by checking the provider on the vpc_offering_service_map table) ALTER TABLE `cloud`.`vpc_offerings` DROP COLUMN `for_nsx`; + +-- Add next_hop to the static_routes table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.static_routes', 'next_hop', 'varchar(50) COMMENT "next hop of the static route" AFTER `vpc_gateway_id`'); + +-- Add `for_router` to `user_ip_address` table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user_ip_address', 'for_router', 'tinyint(1) DEFAULT 0 COMMENT "True if the ip address is used by Domain Router to expose services"'); diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java index e2d13e47daa..2c409482a19 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java @@ -381,6 +381,7 @@ public class NetrisApiClientImpl implements NetrisApiClient { routesPutBody.setPrefix(prefix); routesPutBody.setNextHop(nextHop); routesPutBody.setSiteId(new BigDecimal(siteId)); + routesPutBody.setStateStatus(RoutesPutBody.StateStatusEnum.ACTIVE); routesPutBody.setDescription(staticRouteId); routesPutBody.setSwitches(Collections.emptyList()); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 24dd07d1097..2683c772119 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -49,6 +49,7 @@ import com.cloud.dc.dao.ASNumberDao; import com.cloud.dc.dao.ASNumberRangeDao; import com.cloud.dc.dao.VlanDetailsDao; import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.vpc.VpcGateway; import com.cloud.storage.BucketVO; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; @@ -1036,6 +1037,9 @@ public class ApiResponseHelper implements ResponseGenerator { addSystemVmInfoToIpResponse(nic, response); } } + if (ipAddress.isForRouter()) { + response.setVirtualMachineType(Type.DomainRouter.toString()); + } if (ipAddress.getAssociatedWithVmId() != null) { addUserVmDetailsInIpResponse(response, ipAddress); } @@ -1062,12 +1066,25 @@ public class ApiResponseHelper implements ResponseGenerator { } private void addUserVmDetailsInIpResponse(IPAddressResponse response, IpAddress ipAddress) { - UserVm userVm = ApiDBUtils.findUserVmById(ipAddress.getAssociatedWithVmId()); - if (userVm != null) { - response.setVirtualMachineId(userVm.getUuid()); - response.setVirtualMachineName(userVm.getHostName()); - response.setVirtualMachineType(userVm.getType().toString()); - response.setVirtualMachineDisplayName(ObjectUtils.firstNonNull(userVm.getDisplayName(), userVm.getHostName())); + VirtualMachine vm = ApiDBUtils.findVMInstanceById(ipAddress.getAssociatedWithVmId()); + if (vm == null) { + return; + } + if (vm.getType().equals(Type.User)) { + UserVm userVm = ApiDBUtils.findUserVmById(ipAddress.getAssociatedWithVmId()); + if (userVm != null) { + response.setVirtualMachineId(userVm.getUuid()); + response.setVirtualMachineName(userVm.getHostName()); + response.setVirtualMachineType(userVm.getType().toString()); + response.setVirtualMachineDisplayName(ObjectUtils.firstNonNull(userVm.getDisplayName(), userVm.getHostName())); + } + } else if (vm.getType().equals(Type.DomainRouter)) { + final boolean isAdmin = Account.Type.ADMIN.equals(CallContext.current().getCallingAccount().getType()); + if (isAdmin) { + response.setVirtualMachineId(vm.getUuid()); + response.setVirtualMachineName(vm.getHostName()); + } + response.setVirtualMachineType(vm.getType().toString()); } } @@ -1234,6 +1251,13 @@ public class ApiResponseHelper implements ResponseGenerator { ipResponse.setVirtualMachineDisplayName(vm.getHostName()); } } + } else if (nic.getVmType() == Type.DomainRouter) { + VirtualMachine vm = ApiDBUtils.findVMInstanceById(nic.getInstanceId()); + if (vm != null) { + ipResponse.setVirtualMachineId(vm.getUuid()); + ipResponse.setVirtualMachineName(vm.getHostName()); + ipResponse.setVirtualMachineType(vm.getType().toString()); + } } else if (nic.getVmType().isUsedBySystem()) { ipResponse.setIsSystem(true); addSystemVmInfoToIpResponse(nic, ipResponse); @@ -3157,12 +3181,6 @@ public class ApiResponseHelper implements ResponseGenerator { List<? extends Network.Provider> serviceProviders = ApiDBUtils.getProvidersForService(service); List<ProviderResponse> serviceProvidersResponses = new ArrayList<ProviderResponse>(); for (Network.Provider serviceProvider : serviceProviders) { - // return only Virtual Router/JuniperSRX/CiscoVnmc as a provider for the firewall - if (service == Service.Firewall - && !(serviceProvider == Provider.VirtualRouter || serviceProvider == Provider.CiscoVnmc || serviceProvider == Provider.PaloAlto || serviceProvider == Provider.BigSwitchBcf || serviceProvider == Provider.Tungsten)) { - continue; - } - ProviderResponse serviceProviderResponse = createServiceProviderResponse(serviceProvider); serviceProvidersResponses.add(serviceProviderResponse); } @@ -3773,6 +3791,16 @@ public class ApiResponseHelper implements ResponseGenerator { response.setVpcId(vpc.getUuid()); } } + if (result.getVpcGatewayId() != null) { + VpcGateway vpcGateway = _entityMgr.findById(VpcGateway.class, result.getVpcGatewayId()); + if (vpcGateway != null) { + response.setVpcGatewayId(vpcGateway.getUuid()); + response.setVpcGatewayIp(vpcGateway.getIp4Address()); + } + } + if (result.getNextHop() != null) { + response.setNextHop(result.getNextHop()); + } response.setCidr(result.getCidr()); StaticRoute.State state = result.getState(); diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index 32a37aa7277..f28ef6ab53f 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -38,6 +38,8 @@ import com.cloud.dc.dao.VlanDetailsDao; import com.cloud.network.dao.NetrisProviderDao; import com.cloud.network.dao.NsxProviderDao; import com.cloud.network.dao.PublicIpQuarantineDao; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.Site2SiteVpnGatewayDao; import com.cloud.network.element.NetrisProviderVO; import com.cloud.network.element.NsxProviderVO; import com.cloud.network.vo.PublicIpQuarantineVO; @@ -329,6 +331,10 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage @Inject PublicIpQuarantineDao publicIpQuarantineDao; + @Inject + RemoteAccessVpnDao remoteAccessVpnDao; + @Inject + Site2SiteVpnGatewayDao site2SiteVpnGatewayDao; SearchBuilder<IPAddressVO> AssignIpAddressSearch; SearchBuilder<IPAddressVO> AssignIpAddressFromPodVlanSearch; @@ -746,6 +752,21 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage throw new CloudRuntimeException("Unable to acquire lock on public IP."); } + if (ipToBeDisassociated.isForRouter()) { + if (remoteAccessVpnDao.findByPublicIpAddress(ipToBeDisassociated.getId()) != null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Can't release IP address as the IP address is used by a Remote Access VPN"); + ex.addProxyObject(ipToBeDisassociated.getUuid(), "ipId"); + throw ex; + + } + if (site2SiteVpnGatewayDao.findByPublicIpAddress(ipToBeDisassociated.getId()) != null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Can't release IP address as the IP address is used by a VPC gateway"); + ex.addProxyObject(ipToBeDisassociated.getUuid(), "ipId"); + throw ex; + + } + } + PublicIpQuarantine publicIpQuarantine = null; // Cleanup all ip address resources - PF/LB/Static nat rules if (!cleanupIpResources(ipAddress, userId, caller)) { diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index 48eb38a6b35..5a7df7df32d 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -2312,6 +2312,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi } if (capabilities != null && implementedProvider != null) { for (Service service : capabilities.keySet()) { + logger.info("Add provider {} and service {}", implementedProvider.getName(), service.getName()); if (s_serviceToImplementedProvidersMap.containsKey(service)) { List<Provider> providers = s_serviceToImplementedProvidersMap.get(service); providers.add(implementedProvider); diff --git a/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java index 4f76488337d..0c670ae83e0 100644 --- a/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java @@ -288,8 +288,9 @@ public class ExternalGuestNetworkGuru extends GuestNetworkGuru { profile.setIPv4Netmask(null); } - if (config.getVpcId() == null && vm.getType() == VirtualMachine.Type.DomainRouter) { - boolean isPublicNetwork = _networkModel.isProviderSupportServiceInNetwork(config.getId(), Service.SourceNat, Provider.VirtualRouter); + if (vm.getType() == VirtualMachine.Type.DomainRouter) { + boolean isPublicNetwork = _networkModel.isProviderSupportServiceInNetwork(config.getId(), Service.SourceNat, Provider.VirtualRouter) + || _networkModel.isProviderSupportServiceInNetwork(config.getId(), Service.SourceNat, Provider.VPCVirtualRouter); if (!isPublicNetwork) { Nic placeholderNic = _networkModel.getPlaceholderNicForRouter(config, null); if (placeholderNic == null) { diff --git a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java index 97349420ab5..f20b2092040 100644 --- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java +++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java @@ -1221,8 +1221,9 @@ public class CommandSetupHelper { final SetupGuestNetworkCommand setupCmd = new SetupGuestNetworkCommand(dhcpRange, networkDomain, router.getIsRedundantRouter(), defaultDns1, defaultDns2, add, _itMgr.toNicTO(nicProfile, router.getHypervisorType())); - boolean isForNsx = _networkModel.isProviderForNetworkOffering(Provider.Nsx, networkOfferingVO.getId()); - setupCmd.setVrGuestGateway(isForNsx); + boolean isVrGuestGateway = _networkModel.isAnyServiceSupportedInNetwork(network.getId(), Provider.VPCVirtualRouter, Service.SourceNat, Service.Gateway); + setupCmd.setVrGuestGateway(isVrGuestGateway); + NicVO publicNic = _nicDao.findDefaultNicForVM(router.getId()); if (publicNic != null) { updateSetupGuestNetworkCommandIpv6(setupCmd, network, publicNic, defaultIp6Dns1, defaultIp6Dns2); diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java index f33a6c2f632..8d665eec96e 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java @@ -28,6 +28,7 @@ import java.util.Map; import javax.annotation.PostConstruct; import javax.inject.Inject; +import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.utils.validation.ChecksumUtil; import org.apache.cloudstack.api.ApiConstants; @@ -176,6 +177,8 @@ public class NetworkHelperImpl implements NetworkHelper { CapacityManager capacityMgr; @Inject VpcDao vpcDao; + @Inject + VpcManager vpcManager; protected final Map<HypervisorType, ConfigKey<String>> hypervisorsMap = new HashMap<>(); @@ -275,6 +278,10 @@ public class NetworkHelperImpl implements NetworkHelper { _itMgr.expunge(router.getUuid()); _routerHealthCheckResultDao.expungeHealthChecks(router.getId()); _routerDao.remove(router.getId()); + + if (router.getVpcId() != null) { + vpcManager.reconfigStaticNatForVpcVr(router.getVpcId()); + } return router; } @@ -770,7 +777,7 @@ public class NetworkHelperImpl implements NetworkHelper { logger.debug("Adding nic for Virtual Router in Guest network " + guestNetwork); String defaultNetworkStartIp = null, defaultNetworkStartIpv6 = null; final Nic placeholder = _networkModel.getPlaceholderNicForRouter(guestNetwork, routerDeploymentDefinition.getPodId()); - if (!routerDeploymentDefinition.isPublicNetwork()) { + if (!routerDeploymentDefinition.isPublicNetwork() || !vpcManager.isSrcNatIpRequiredForVpcVr(routerDeploymentDefinition.getVpc().getVpcOfferingId())) { if (guestNetwork.getCidr() != null) { if (placeholder != null && placeholder.getIPv4Address() != null) { logger.debug("Requesting ipv4 address " + placeholder.getIPv4Address() + " stored in placeholder nic for the network " diff --git a/server/src/main/java/com/cloud/network/router/NicProfileHelperImpl.java b/server/src/main/java/com/cloud/network/router/NicProfileHelperImpl.java index 399019db3e2..649689d8d77 100644 --- a/server/src/main/java/com/cloud/network/router/NicProfileHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NicProfileHelperImpl.java @@ -120,7 +120,9 @@ public class NicProfileHelperImpl implements NicProfileHelper { public NicProfile createGuestNicProfileForVpcRouter(final RouterDeploymentDefinition vpcRouterDeploymentDefinition, final Network guestNetwork) { final NicProfile guestNic = new NicProfile(); - if (BroadcastDomainType.NSX == guestNetwork.getBroadcastDomainType()) { + if (BroadcastDomainType.NSX == guestNetwork.getBroadcastDomainType() || + BroadcastDomainType.Netris == guestNetwork.getBroadcastDomainType() || + !_vpcMgr.isSrcNatIpRequiredForVpcVr(vpcRouterDeploymentDefinition.getVpc().getVpcOfferingId())) { NicVO vrNic = _nicDao.findByNetworkIdAndTypeIncludingRemoved(guestNetwork.getId(), VirtualMachine.Type.DomainRouter); if (vrNic != null) { guestNic.setIPv4Address(vrNic.getIPv4Address()); diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index e171b68399b..2edd8455cfd 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -203,6 +203,7 @@ import com.cloud.network.rules.StaticNatImpl; import com.cloud.network.rules.StaticNatRule; import com.cloud.network.rules.dao.PortForwardingRulesDao; import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcService; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.network.vpn.Site2SiteVpnManager; @@ -335,6 +336,7 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM @Inject private NetworkService networkService; @Inject private VpcService vpcService; + @Inject private VpcManager vpcManager; @Autowired @Qualifier("networkHelper") @@ -2043,6 +2045,12 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM if (_disableRpFilter) { rpFilter = " disable_rp_filter=true"; } + Vpc vpc = vpcManager.getActiveVpc(router.getVpcId()); + if (vpcManager.isSrcNatIpRequiredForVpcVr(vpc.getVpcOfferingId())) { + buf.append(" has_public_network=true"); + } else { + buf.append(" has_public_network=false"); + } } else if (!publicNetwork) { type = "dhcpsrvr"; } else { diff --git a/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java index 66b5cf6c8b6..85ee35f81fe 100644 --- a/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java @@ -82,10 +82,9 @@ import com.cloud.network.vpc.NetworkACLManager; import com.cloud.network.vpc.PrivateGateway; import com.cloud.network.vpc.PrivateIpAddress; import com.cloud.network.vpc.PrivateIpVO; -import com.cloud.network.vpc.StaticRoute; +import com.cloud.network.vpc.StaticRouteVO; import com.cloud.network.vpc.StaticRouteProfile; import com.cloud.network.vpc.Vpc; -import com.cloud.network.vpc.VpcGateway; import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.PrivateIpDao; @@ -321,17 +320,19 @@ public class VpcVirtualNetworkApplianceManagerImpl extends VirtualNetworkApplian String defaultDns2 = null; String defaultIp6Dns1 = null; String defaultIp6Dns2 = null; + boolean isDnsConfigured = false; // remove public and guest nics as we will plug them later final Iterator<NicProfile> it = profile.getNics().iterator(); while (it.hasNext()) { final NicProfile nic = it.next(); if (nic.getTrafficType() == TrafficType.Public || nic.getTrafficType() == TrafficType.Guest) { // save dns information - if (nic.getTrafficType() == TrafficType.Public) { + if (nic.getTrafficType() == TrafficType.Public || !isDnsConfigured) { defaultDns1 = nic.getIPv4Dns1(); defaultDns2 = nic.getIPv4Dns2(); defaultIp6Dns1 = nic.getIPv6Dns1(); defaultIp6Dns2 = nic.getIPv6Dns2(); + isDnsConfigured = true; } logger.debug("Removing nic " + nic + " of type " + nic.getTrafficType() + " from the nics passed on vm start. " + "The nic will be plugged later"); it.remove(); @@ -532,17 +533,8 @@ public class VpcVirtualNetworkApplianceManagerImpl extends VirtualNetworkApplian } // 4) RE-APPLY ALL STATIC ROUTE RULES - final List<? extends StaticRoute> routes = _staticRouteDao.listByVpcId(domainRouterVO.getVpcId()); - final List<StaticRouteProfile> staticRouteProfiles = new ArrayList<StaticRouteProfile>(routes.size()); - final Map<Long, VpcGateway> gatewayMap = new HashMap<Long, VpcGateway>(); - for (final StaticRoute route : routes) { - VpcGateway gateway = gatewayMap.get(route.getVpcGatewayId()); - if (gateway == null) { - gateway = _entityMgr.findById(VpcGateway.class, route.getVpcGatewayId()); - gatewayMap.put(gateway.getId(), gateway); - } - staticRouteProfiles.add(new StaticRouteProfile(route, gateway)); - } + final List<StaticRouteVO> routes = _vpcMgr.getVpcStaticRoutes(domainRouterVO.getVpcId()); + final List<StaticRouteProfile> staticRouteProfiles = _vpcMgr.getVpcStaticRoutes(routes); logger.debug("Found " + staticRouteProfiles.size() + " static routes to apply as a part of vpc route " + domainRouterVO + " start"); if (!staticRouteProfiles.isEmpty()) { diff --git a/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java b/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java index bfcaca72b31..db79cad9d65 100644 --- a/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java +++ b/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java @@ -59,6 +59,8 @@ import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.LoadBalancerVMMapDao; import com.cloud.network.dao.LoadBalancerVMMapVO; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.Site2SiteVpnGatewayDao; import com.cloud.network.rules.FirewallRule.FirewallRuleType; import com.cloud.network.rules.FirewallRule.Purpose; import com.cloud.network.rules.FirewallRule.State; @@ -155,6 +157,10 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules VpcService _vpcSvc; @Inject VMTemplateDao _templateDao; + @Inject + RemoteAccessVpnDao remoteAccessVpnDao; + @Inject + Site2SiteVpnGatewayDao site2SiteVpnGatewayDao; protected void checkIpAndUserVm(IpAddress ipAddress, UserVm userVm, Account caller, Boolean ignoreVmState) { if (ipAddress == null || ipAddress.getAllocatedTime() == null || ipAddress.getAllocatedToAccountId() == null) { @@ -1270,6 +1276,25 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules throw ex; } + if (ipAddress.isForRouter()) { + if (remoteAccessVpnDao.findByPublicIpAddress(ipAddress.getId()) != null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Can't disable static nat as the IP address is used by a Remote Access VPN"); + ex.addProxyObject(ipAddress.getUuid(), "ipId"); + throw ex; + + } + if (site2SiteVpnGatewayDao.findByPublicIpAddress(ipAddress.getId()) != null) { + InvalidParameterValueException ex = new InvalidParameterValueException("Can't disable static nat as the IP address is used by a VPC gateway"); + ex.addProxyObject(ipAddress.getUuid(), "ipId"); + throw ex; + + } + if (disableStaticNat(ipId, caller, ctx.getCallingUserId(), false)) { + return true; + } + return false; + } + Long vmId = ipAddress.getAssociatedWithVmId(); if (vmId == null) { InvalidParameterValueException ex = new InvalidParameterValueException("Specified IP address id is not associated with any vm Id"); @@ -1302,7 +1327,7 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules IPAddressVO ipAddress = _ipAddressDao.findById(ipId); checkIpAndUserVm(ipAddress, null, caller, false); - long networkId = ipAddress.getAssociatedWithNetworkId(); + Long networkId = ipAddress.getAssociatedWithNetworkId(); if (!ipAddress.isOneToOneNat()) { InvalidParameterValueException ex = new InvalidParameterValueException("One to one nat is not enabled for the specified ip id"); @@ -1337,11 +1362,14 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules ipAddress.setAssociatedWithVmId(null); ipAddress.setRuleState(null); ipAddress.setVmIp(null); + ipAddress.setForRouter(false); if (isIpSystem && !releaseIpIfElastic) { ipAddress.setSystem(false); } _ipAddressDao.update(ipAddress.getId(), ipAddress); - _vpcMgr.unassignIPFromVpcNetwork(ipAddress.getId(), networkId); + if (networkId != null) { + _vpcMgr.unassignIPFromVpcNetwork(ipAddress.getId(), networkId); + } if (isIpSystem && releaseIpIfElastic && !_ipAddrMgr.handleSystemIpRelease(ipAddress)) { logger.warn("Failed to release system ip address " + ipAddress); @@ -1379,7 +1407,8 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules return new StaticNatRuleImpl(ruleVO, dstIp); } - protected boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke) { + @Override + public boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke) { IpAddress sourceIp = _ipAddressDao.findById(sourceIpId); List<StaticNat> staticNats = createStaticNatForIp(sourceIp, caller, forRevoke); diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 2012f77e338..0ae9d3d893d 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -48,11 +48,22 @@ import com.cloud.bgp.BGPService; import com.cloud.dc.ASNumberVO; import com.cloud.dc.dao.ASNumberDao; import com.cloud.dc.Vlan; +import com.cloud.network.RemoteAccessVpn; +import com.cloud.network.Site2SiteVpnConnection; import com.cloud.network.dao.NetrisProviderDao; import com.cloud.network.dao.NsxProviderDao; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.RemoteAccessVpnVO; +import com.cloud.network.dao.Site2SiteCustomerGatewayDao; +import com.cloud.network.dao.Site2SiteCustomerGatewayVO; +import com.cloud.network.dao.Site2SiteVpnConnectionDao; +import com.cloud.network.dao.Site2SiteVpnConnectionVO; import com.cloud.network.element.NetrisProviderVO; import com.cloud.network.element.NsxProviderVO; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.resourcelimit.CheckedReservation; +import com.cloud.vm.dao.VMInstanceDao; import com.google.common.collect.Sets; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.alert.AlertService; @@ -296,6 +307,20 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis private NetrisProviderDao netrisProviderDao; @Inject RoutedIpv4Manager routedIpv4Manager; + @Inject + DomainRouterDao domainRouterDao; + @Inject + RulesManager rulesManager; + @Inject + VMInstanceDao vmInstanceDao; + @Inject + RemoteAccessVpnDao remoteAccessVpnDao; + @Inject + RemoteAccessVpnService remoteAccessVpnMgr; + @Inject + Site2SiteVpnConnectionDao site2SiteVpnConnectionDao; + @Inject + Site2SiteCustomerGatewayDao site2SiteCustomerGatewayDao; private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("VpcChecker")); private List<VpcProvider> vpcElements = null; @@ -461,7 +486,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis final Map<Service, Set<Provider>> svcProviderMap = new HashMap<>(); final Set<Provider> defaultProviders = Set.of(Provider.Netris); for (final Service svc : getSupportedServices()) { - if (List.of(Service.UserData, Service.Dhcp, Service.Dns).contains(svc)) { + if (List.of(Service.UserData, Service.Dhcp, Service.Dns, Service.Vpn).contains(svc)) { final Set<Provider> userDataProvider = Set.of(Provider.VPCVirtualRouter); svcProviderMap.put(svc, userDataProvider); } else { @@ -2242,6 +2267,12 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis logger.debug("Cleaning up existed site to site VPN gateways"); _s2sVpnMgr.cleanupVpnGatewayByVpc(vpc.getId()); + List<RemoteAccessVpnVO> vpns = remoteAccessVpnDao.listByVpcId(vpc.getId()); + for (RemoteAccessVpnVO vpn : vpns) { + logger.debug("Disabling remote access VPN on {}", vpn.getServerAddressId()); + remoteAccessVpnMgr.destroyRemoteAccessVpnForIp(vpn.getServerAddressId(), caller, true); + } + // 2) release all ip addresses final List<IPAddressVO> ipsToRelease = _ipAddressDao.listByAssociatedVpc(vpc.getId(), null); logger.debug("Releasing ips for vpc {} as a part of vpc cleanup", vpc); @@ -2376,6 +2407,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis restartRequired = true; return false; } + reconfigStaticNatForVpcVr(vpcId); return true; } @@ -2882,28 +2914,38 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Override public boolean applyStaticRoutesForVpc(final long vpcId) throws ResourceUnavailableException { final Account caller = CallContext.current().getCallingAccount(); - final List<? extends StaticRoute> routes = _staticRouteDao.listByVpcId(vpcId); + final List<StaticRouteVO> routes = getVpcStaticRoutes(vpcId); return applyStaticRoutes(routes, caller, true); } - protected boolean applyStaticRoutes(final List<? extends StaticRoute> routes, final Account caller, final boolean updateRoutesInDB) throws ResourceUnavailableException { - final boolean success = true; - final List<StaticRouteProfile> staticRouteProfiles = new ArrayList<StaticRouteProfile>(routes.size()); - final Map<Long, VpcGateway> gatewayMap = new HashMap<Long, VpcGateway>(); - for (final StaticRoute route : routes) { - VpcGateway gateway = gatewayMap.get(route.getVpcGatewayId()); - if (gateway == null) { - gateway = _vpcGatewayDao.findById(route.getVpcGatewayId()); - gatewayMap.put(gateway.getId(), gateway); + @Override + public boolean applyStaticRouteForVpcVpnIfNeeded(final Long vpcId, boolean updateAllVpn) throws ResourceUnavailableException { + if (isProviderSupportServiceInVpc(vpcId, Service.Vpn, Network.Provider.VPCVirtualRouter)) { + boolean isVpcVRSourceNat = isProviderSupportServiceInVpc(vpcId, Service.SourceNat, Network.Provider.VPCVirtualRouter); + if (isVpcVRSourceNat) { + logger.debug("Skipping static route configuration as VPC VR is Source NAT"); + return true; } - staticRouteProfiles.add(new StaticRouteProfile(route, gateway)); + logger.debug("Configuring static route for VPC VR of VPC " + vpcId); + final Account caller = CallContext.current().getCallingAccount(); + final List<StaticRouteVO> routes = getVpcStaticRoutes(vpcId, updateAllVpn); + return applyStaticRoutes(routes, caller, false); } + return true; + } + + protected boolean applyStaticRoutes(final List<StaticRouteVO> routes, final Account caller, final boolean updateRoutesInDB) throws ResourceUnavailableException { + final boolean success = true; + final List<StaticRouteProfile> staticRouteProfiles = getVpcStaticRoutes(routes); if (!applyStaticRoutes(staticRouteProfiles)) { logger.warn("Routes are not completely applied"); return false; } else { if (updateRoutesInDB) { - for (final StaticRoute route : routes) { + for (final StaticRouteVO route : routes) { + if (route.isForVpn()) { + continue; + } if (route.getState() == StaticRoute.State.Revoke) { _staticRouteDao.remove(route.getId()); logger.debug("Removed route " + route + " from the DB"); @@ -2966,7 +3008,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @DB protected boolean revokeStaticRoutesForVpc(final Vpc vpc, final Account caller) throws ResourceUnavailableException { // get all static routes for the vpc - final List<StaticRouteVO> routes = _staticRouteDao.listByVpcId(vpc.getId()); + final List<StaticRouteVO> routes = getVpcStaticRoutes(vpc.getId()); logger.debug("Found {} to revoke for the vpc {}", routes.size(), vpc); if (!routes.isEmpty()) { // mark all of them as revoke @@ -2987,23 +3029,46 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Override @DB @ActionEvent(eventType = EventTypes.EVENT_STATIC_ROUTE_CREATE, eventDescription = "creating static route", create = true) - public StaticRoute createStaticRoute(final long gatewayId, final String cidr) throws NetworkRuleConflictException { + public StaticRoute createStaticRoute(final Long gatewayId, Long vpcId, final String nextHop, final String cidr) throws NetworkRuleConflictException { final Account caller = CallContext.current().getCallingAccount(); // parameters validation - final VpcGateway gateway = _vpcGatewayDao.findById(gatewayId); - if (gateway == null) { - throw new InvalidParameterValueException("Invalid gateway id is given"); + if (gatewayId == null && nextHop == null) { + throw new InvalidParameterValueException("one of gatewayId and nextHop must be specified"); } - if (gateway.getState() != VpcGateway.State.Ready) { - throw new InvalidParameterValueException("Gateway is not in the " + VpcGateway.State.Ready + " state: " + gateway.getState()); + if (gatewayId != null && nextHop != null) { + throw new InvalidParameterValueException("Only one of gatewayId and nextHop can be specified"); + } + + if (gatewayId != null) { + final VpcGateway gateway = _vpcGatewayDao.findById(gatewayId); + if (gateway == null) { + throw new InvalidParameterValueException("Invalid gateway id is given"); + } + + if (gateway.getState() != VpcGateway.State.Ready) { + throw new InvalidParameterValueException("Gateway is not in the " + VpcGateway.State.Ready + " state: " + gateway.getState()); + } + + if (vpcId != null) { + if (!vpcId.equals(gateway.getVpcId())) { + throw new InvalidParameterValueException("Invalid gateway id is given"); + } + } else { + vpcId = gateway.getVpcId(); + } + } else if (nextHop != null) { + if (vpcId == null) { + throw new InvalidParameterValueException("vpcId must be specified"); + } } - final Vpc vpc = getActiveVpc(gateway.getVpcId()); + final Vpc vpc = getActiveVpc(vpcId); if (vpc == null) { throw new InvalidParameterValueException("Can't add static route to VPC that is being deleted"); } + _accountMgr.checkAccess(caller, null, false, vpc); if (!NetUtils.isValidIp4Cidr(cidr)) { @@ -3026,10 +3091,15 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis throw new InvalidParameterValueException("The static gateway cidr overlaps with one of the denied routes of the zone the VPC belongs to"); } + // 4) validate next hop + if (nextHop != null && !isNextHopValid(nextHop, vpc)) { + throw new InvalidParameterValueException(String.format("Next hop %s is invalid. It must be within VPC CIDR or on the same public or private network", nextHop)); + } + return Transaction.execute(new TransactionCallbackWithException<StaticRouteVO, NetworkRuleConflictException>() { @Override public StaticRouteVO doInTransaction(final TransactionStatus status) throws NetworkRuleConflictException { - StaticRouteVO newRoute = new StaticRouteVO(gateway.getId(), cidr, vpc.getId(), vpc.getAccountId(), vpc.getDomainId()); + StaticRouteVO newRoute = new StaticRouteVO(gatewayId, cidr, vpc.getId(), vpc.getAccountId(), vpc.getDomainId(), nextHop); logger.debug("Adding static route " + newRoute); newRoute = _staticRouteDao.persist(newRoute); @@ -3045,6 +3115,44 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis }); } + private boolean isNextHopValid(String nextHop, Vpc vpc) { + // Scenario 1: VM as next hop + if (NetUtils.isIpWithInCidrRange(nextHop, vpc.getCidr())) { + logger.debug("The next Hop {} is valid as it is within the VPC cidr {}", nextHop, vpc.getCidr()); + return true; + } + // Scenario 2: Another public IP as next hop + List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(vpc.getId(), null); + List<Long> vlanIds = new ArrayList<>(); + for (IPAddressVO ip : ips) { + if (vlanIds.contains(ip.getVlanId())) { + continue; + } + VlanVO vlan = _vlanDao.findById(ip.getVlanId()); + if (vlan != null) { + String vlanCidr = NetUtils.getCidrFromGatewayAndNetmask(vlan.getVlanGateway(), vlan.getVlanNetmask()); + if (NetUtils.isIpWithInCidrRange(nextHop, vlanCidr)) { + logger.debug("The next Hop {} is valid as it is on the same network as Public IP address {} ", nextHop, ip.getAddress()); + return true; + } + } + vlanIds.add(ip.getVlanId()); + } + + // Scenario 3: An IP on private gateway as next hop + List<VpcGatewayVO> vpcGateways = _vpcGatewayDao.listByVpcId(vpc.getId()); + for (VpcGatewayVO vpcGateway : vpcGateways) { + String vpcGatewayCidr = NetUtils.getCidrFromGatewayAndNetmask(vpcGateway.getGateway(), vpcGateway.getNetmask()); + if (NetUtils.isIpWithInCidrRange(nextHop, vpcGatewayCidr)) { + logger.debug("The next Hop {} is valid as it is on the same network as private gateway {} ", nextHop, vpcGateway.getIp4Address()); + return true; + } + } + + logger.debug("The next Hop {} is invalid", nextHop); + return false; + } + protected boolean isCidrDenylisted(final String cidr, final long zoneId) { final String routesStr = NetworkOrchestrationService.DeniedRoutes.valueIn(zoneId); if (routesStr != null && !routesStr.isEmpty()) { @@ -3456,6 +3564,15 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis && vpcOffSvcProvidersMap.get(Service.Gateway).contains(Network.Provider.VPCVirtualRouter)); } + @Override + public boolean isSrcNatIpRequiredForVpcVr(long vpcOfferingId) { + final Map<Network.Service, Set<Network.Provider>> vpcOffSvcProvidersMap = getVpcOffSvcProvidersMap(vpcOfferingId); + return (Objects.nonNull(vpcOffSvcProvidersMap.get(Network.Service.SourceNat)) + && vpcOffSvcProvidersMap.get(Network.Service.SourceNat).contains(Network.Provider.VPCVirtualRouter)) + || (Objects.nonNull(vpcOffSvcProvidersMap.get(Network.Service.Gateway)) + && vpcOffSvcProvidersMap.get(Service.Gateway).contains(Network.Provider.VPCVirtualRouter)); + } + /** * rollingRestartVpc performs restart of routers of a VPC by first * deploying a new VR and then destroying old VRs in rolling fashion. For @@ -3545,4 +3662,241 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis protected boolean isDefaultAcl(long aclId) { return aclId == NetworkACL.DEFAULT_ALLOW || aclId == NetworkACL.DEFAULT_DENY; } + + @Override + public List<StaticRouteProfile> getVpcStaticRoutes(final List<? extends StaticRoute> routes) { + final List<StaticRouteProfile> staticRouteProfiles = new ArrayList<>(routes.size()); + final Map<Long, VpcGateway> gatewayMap = new HashMap<Long, VpcGateway>(); + for (final StaticRoute route : routes) { + if (route.getVpcGatewayId() != null) { + VpcGateway gateway = gatewayMap.get(route.getVpcGatewayId()); + if (gateway == null) { + gateway = _entityMgr.findById(VpcGateway.class, route.getVpcGatewayId()); + gatewayMap.put(gateway.getId(), gateway); + } + staticRouteProfiles.add(new StaticRouteProfile(route, gateway)); + } else { + staticRouteProfiles.add(new StaticRouteProfile(route)); + } + } + return staticRouteProfiles; + } + + @Override + public List<StaticRouteVO> getVpcStaticRoutes(Long vpcId) { + return getVpcStaticRoutes(vpcId, false); + } + + public List<StaticRouteVO> getVpcStaticRoutes(Long vpcId, boolean updateAllVpn) { + final List<StaticRouteVO> routes = _staticRouteDao.listByVpcId(vpcId); + + if (isProviderSupportServiceInVpc(vpcId, Service.Vpn, Network.Provider.VPCVirtualRouter) + && !isProviderSupportServiceInVpc(vpcId, Service.SourceNat, Network.Provider.VPCVirtualRouter)) { + + Vpc vpc = vpcDao.findById(vpcId); + IPAddressVO ipAddressForVpcVR = getIpAddressForVpcVr(vpc, null, false); + String nextHop = getFirstGuestIpAddressForVpcVr(vpc.getId()); + + if (ipAddressForVpcVR != null && (updateAllVpn || nextHop != null)) { + // Add Static Routes for Remote Access VPN + List<StaticRouteVO> staticRoutesForRemoteAccessVpn = new ArrayList<>(); + RemoteAccessVpnVO remoteAccessVpn = remoteAccessVpnDao.findByPublicIpAddress(ipAddressForVpcVR.getId()); + if (remoteAccessVpn != null) { + String ipRange = remoteAccessVpn.getIpRange(); + String startIp = ipRange.split("-")[0]; + String endIp = ipRange.split("-")[1]; + int cidrSize = NetUtils.getBigCidrSizeOfIpRange(NetUtils.ip2Long(startIp), NetUtils.ip2Long(endIp)); + String cidr = NetUtils.transformCidr(startIp + "/" + cidrSize); + if (nextHop == null || RemoteAccessVpn.State.Removed.equals(remoteAccessVpn.getState())) { + StaticRouteVO newRoute = new StaticRouteVO(cidr, vpc.getId(), vpc.getAccountId(), vpc.getDomainId(), null, + StaticRoute.State.Revoke, true); + staticRoutesForRemoteAccessVpn.add(newRoute); + } else { + StaticRoute.State state = updateAllVpn ? StaticRoute.State.Update : StaticRoute.State.Add; + StaticRouteVO newRoute = new StaticRouteVO(cidr, vpc.getId(), vpc.getAccountId(), vpc.getDomainId(), nextHop, + state, true); + staticRoutesForRemoteAccessVpn.add(newRoute); + } + } + logger.debug("Adding {} static routes for Remote Access VPN", staticRoutesForRemoteAccessVpn.size()); + routes.addAll(staticRoutesForRemoteAccessVpn); + + // Add Static Routes for Site-to-Site VPN connections + List<StaticRouteVO> staticRoutesForSite2SiteVpn = new ArrayList<>(); + List<Site2SiteVpnConnectionVO> vpnConnections = site2SiteVpnConnectionDao.listByVpcId(vpcId); + for (Site2SiteVpnConnectionVO vpnConnection : vpnConnections) { + Site2SiteCustomerGatewayVO customerGateway = site2SiteCustomerGatewayDao.findById(vpnConnection.getCustomerGatewayId()); + if (nextHop == null || Site2SiteVpnConnection.State.Removed.equals(vpnConnection.getState())) { + for (String cidr: customerGateway.getGuestCidrList().split(",")) { + StaticRouteVO newRoute = new StaticRouteVO(cidr, vpc.getId(), vpc.getAccountId(), vpc.getDomainId(), null, + StaticRoute.State.Revoke, true); + staticRoutesForSite2SiteVpn.add(newRoute); + } + } else { + StaticRoute.State state = updateAllVpn ? StaticRoute.State.Update : StaticRoute.State.Add; + for (String cidr : customerGateway.getGuestCidrList().split(",")) { + StaticRouteVO newRoute = new StaticRouteVO(cidr, vpc.getId(), vpc.getAccountId(), vpc.getDomainId(), nextHop, + state, true); + staticRoutesForSite2SiteVpn.add(newRoute); + } + } + } + logger.debug("Adding {} static routes for {} Site-to-Site VPN connections", + staticRoutesForSite2SiteVpn.size(), vpnConnections.size()); + routes.addAll(staticRoutesForSite2SiteVpn); + } + } + + logger.debug("Found {} static routes for VPC {}", routes.size(), vpcId); + return routes; + } + + @Override + public boolean isProviderSupportServiceInVpc(long vpcId, Service service, Provider provider) { + return _vpcSrvcDao.canProviderSupportServiceInVpc(vpcId, service, provider); + } + + @Override + public IPAddressVO getIpAddressForVpcVr(Vpc vpc, IPAddressVO ipAddress, boolean allocateIpIfNeeded) { + // Validate if the IP address is associated to a VPC VR + final List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(vpc.getId(), null); + IPAddressVO ipAddressForVR = ips.stream().filter(ip -> ip.isForRouter()).findFirst().orElse(null); + if (ipAddressForVR != null) { + if (ipAddress != null && ipAddressForVR.getId() != ipAddress.getId()) { + throw new InvalidParameterValueException(String.format("Cannot assign Public IP %s to VPC VR as %s has been associated to the VPC VR.", + ipAddress.getAddress().addr(), ipAddressForVR.getAddress().addr())); + } + return ipAddressForVR; + } else if (ipAddress != null) { + return ipAddress; + } + + if (allocateIpIfNeeded) { + Account account = _accountMgr.getAccount(vpc.getAccountId()); + DataCenter zone = _dcDao.findById(vpc.getZoneId()); + try { + IpAddress ip = _ipAddrMgr.allocateIp(account, false, CallContext.current().getCallingAccount(), + CallContext.current().getCallingUserId(), zone, null, null); + this.associateIPToVpc(ip.getId(), vpc.getId()); + return _ipAddressDao.findById(ip.getId()); + } catch (InsufficientAddressCapacityException | ResourceAllocationException | + ResourceUnavailableException ex) { + throw new InvalidParameterValueException("Cannot assign Public IP to VPC VR: " + ex.getMessage()); + } + } else { + return null; + } + } + + /* This method configures the Static Nat for VPC VR if it is used for VPN but not Source NAT. + * (1) Update forRouter to true and one-to-one to true + * (2) Get current network and router ID/IP + * (3) Get new network and router ID/IP + * (4) If network or IP is changed (in case VPC tier is removed or shutdown), disable/apply Static NAT with new VM ID and VM IP + * (5) otherwise, If VPC VR ID does not exist or is changed, update the VM ID. + * (6) otherwise, do nothing + * + * This is used in the following processes + * (1) create remote access VPN + * (2) create S2S VPN + * (3) destroy Router + * (4) restart Vpc with cleanup + * (5) add VPC tier + * (6) delete VPC tier + * (7) remove VPC + */ + + @Override + public boolean configStaticNatForVpcVr(Vpc vpc, IPAddressVO ipAddress) { + logger.debug("Configuring static nat for VPC VR of VPC " + vpc.getId()); + // (1) Update forRouter to true and one-to-one to true + if (!ipAddress.isForRouter()) { + ipAddress.setForRouter(true); + ipAddress.setOneToOneNat(true); + _ipAddressDao.update(ipAddress.getId(), ipAddress); + } + + // (2) Get current network and router ID/IP + Long currentNetworkId = ipAddress.getAssociatedWithNetworkId(); + Long currentRouterId = ipAddress.getAssociatedWithVmId(); + String currentRouterIp = ipAddress.getVmIp(); + + // (3) Get new network and router ID/IP + Long newNetworkId = null; + Long newRouterId = null; + String newRouterIp = null; + List<NetworkVO> networks = _ntwkDao.listByVpc(vpc.getId()); + for (NetworkVO network : networks) { + NicVO newNic = nicDao.findNonPlaceHolderByNetworkIdAndType(network.getId(), VirtualMachine.Type.DomainRouter); + if (newNic != null) { + logger.debug("Got VPC VR NIC for network {}: {}", network.getId(), newNic); + newNetworkId = network.getId(); + newRouterId = newNic.getInstanceId(); + newRouterIp = newNic.getIPv4Address(); + break; + } + } + + // Do nothing if the current and new network and router are Null + if (ObjectUtils.allNull(currentNetworkId, currentRouterId, newNetworkId, newRouterId)) { + logger.debug("The current and new network and router are Null, do nothing"); + return true; + } + + if (currentNetworkId == null || !currentNetworkId.equals(newNetworkId)) { + // (4) If network or IP is changed (in case VPC tier is removed or shutdown), disable/apply Static NAT with new VM ID and VM IP + if (currentNetworkId != null) { + // Disable Static NAT for current VPC VR + logger.debug("Disabling static nat for VPC VR (network: {}, router: {})", currentNetworkId, currentRouterId); + CallContext ctx = CallContext.current(); + if (!rulesManager.applyStaticNatForIp(ipAddress.getId(), false, ctx.getCallingAccount(),true)) { + throw new CloudRuntimeException("Failed to disable static nat for VPC VR"); + } + ipAddress.setAssociatedWithNetworkId(null); + ipAddress.setAssociatedWithVmId(null); + ipAddress.setVmIp(null); + _ipAddressDao.update(ipAddress.getId(), ipAddress); + } + if (newNetworkId != null) { + // Enable static nat for the new VPC VR + logger.debug("Enabling static nat for VPC VR (network: {}, router: {})", newNetworkId, newRouterId); + ipAddress.setAssociatedWithNetworkId(newNetworkId); + ipAddress.setAssociatedWithVmId(newRouterId); + ipAddress.setVmIp(newRouterIp); + _ipAddressDao.update(ipAddress.getId(), ipAddress); + CallContext ctx = CallContext.current(); + if (!rulesManager.applyStaticNatForIp(ipAddress.getId(), false, ctx.getCallingAccount(),false)) { + throw new CloudRuntimeException("Failed to enable static nat for VPC VR"); + } + } + } else if (currentRouterId == null || !currentRouterId.equals(newRouterId)) { + // (5) otherwise, If VPC VR ID does not exist or is changed, update the VM ID. + ipAddress.setAssociatedWithVmId(newRouterId); + ipAddress.setVmIp(newRouterIp); + _ipAddressDao.update(ipAddress.getId(), ipAddress); + } + return true; + } + + @Override + public void reconfigStaticNatForVpcVr(Long vpcId) { + Vpc vpc = vpcDao.findById(vpcId); + IPAddressVO ipAddressForVpcVR = getIpAddressForVpcVr(vpc, null, false); + if (ipAddressForVpcVR != null && !configStaticNatForVpcVr(vpc, ipAddressForVpcVR)) { + throw new CloudRuntimeException("Failed to reconfig static nat for VPC VR as part of the process"); + } + } + + private String getFirstGuestIpAddressForVpcVr(Long vpcId) { + String nextHop = null; + List<NetworkVO> networks = _ntwkDao.listByVpc(vpcId); + for (NetworkVO network : networks) { + NicVO nic = nicDao.findNonPlaceHolderByNetworkIdAndType(network.getId(), VirtualMachine.Type.DomainRouter); + if (nic != null) { + nextHop = nic.getIPv4Address(); + break; + } + } + return nextHop; + } } diff --git a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java index 29c0106dc18..172b2bc1255 100644 --- a/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java @@ -67,6 +67,7 @@ import com.cloud.network.rules.FirewallRule.Purpose; import com.cloud.network.rules.FirewallRuleVO; import com.cloud.network.rules.RulesManager; import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.server.ConfigurationServer; @@ -124,6 +125,9 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc UsageEventDao _usageEventDao; @Inject ConfigurationDao _configDao; + @Inject + VpcManager vpcManager; + List<RemoteAccessVPNServiceProvider> _vpnServiceProviders; @Inject @@ -181,7 +185,11 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc Long networkId = ipAddress.getAssociatedWithNetworkId(); if (networkId != null) { - _networkMgr.checkIpForService(ipAddress, Service.Vpn, null); + if (ipAddress.isForRouter()) { + logger.debug("The IP address is reserved for Domain Router, skipping the check now"); + } else { + _networkMgr.checkIpForService(ipAddress, Service.Vpn, null); + } } final Long vpcId = ipAddress.getVpcId(); @@ -232,9 +240,11 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc throw new InvalidParameterValueException("Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId()); } cidr = NetUtils.getCidr(network.getCidr()); + validateIpAddressForVpnServiceOnNetwork(network, ipAddress); } else { Vpc vpc = _vpcDao.findById(vpcId); cidr = NetUtils.getCidr(vpc.getCidr()); + validateIpAddressForVpnServiceOnVpc(vpc, ipAddress); } String[] guestIpRange = NetUtils.getIpRangeFromCidr(cidr.first(), cidr.second()); @@ -257,13 +267,62 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc if (forDisplay != null) { remoteAccessVpnVO.setDisplay(forDisplay); } - return _remoteAccessVpnDao.persist(remoteAccessVpnVO); + remoteAccessVpnVO = _remoteAccessVpnDao.persist(remoteAccessVpnVO); + if (vpcId != null) { + try { + vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpcId, false); + } catch (ResourceUnavailableException | CloudRuntimeException e) { + logger.error("Unable to apply static routes for vpc " + vpcId + " due to " + e.getMessage()); + } + } + return remoteAccessVpnVO; }); } finally { _ipAddressDao.releaseFromLockTable(publicIpId); } } + private void validateIpAddressForVpnServiceOnNetwork(Network network, IPAddressVO ipAddress) { + Long networkId = network.getId(); + if (_networkMgr.isProviderSupportServiceInNetwork(networkId, Service.Vpn, Network.Provider.VirtualRouter)) { + // if VR is the VPN provider, + // (1) if VR is Source NAT, the IP address must be used as Source NAT + // (2) if VR is not Source NAT, the IP address must not be used as Source NAT + boolean isVRSourceNat = _networkMgr.isProviderSupportServiceInNetwork(networkId, Service.SourceNat, Network.Provider.VirtualRouter); + if (isVRSourceNat && !ipAddress.isSourceNat()) { + throw new InvalidParameterValueException("Vpn service can only be configured on the Source NAT IP of network id=" + ipAddress.getAssociatedWithNetworkId()); + } + if (!isVRSourceNat && ipAddress.isSourceNat()) { + throw new InvalidParameterValueException("Vpn service can not be configured on the Source NAT IP of network id=" + ipAddress.getAssociatedWithNetworkId()); + } + if (!isVRSourceNat) { + throw new InvalidParameterValueException("Currently it is not supported to create Vpn service on a non-Source NAT IP of network id=" + ipAddress.getAssociatedWithNetworkId()); + } + } + } + + private void validateIpAddressForVpnServiceOnVpc(Vpc vpc, IPAddressVO ipAddress) { + Long vpcId = vpc.getId(); + if (vpcManager.isProviderSupportServiceInVpc(vpcId, Service.Vpn, Network.Provider.VPCVirtualRouter)) { + // if VPC VR is the VPN provider, + // (1) if VPC VR is Source NAT, the IP address must be used as Source NAT + // (2) if VPC VR is not Source NAT, the IP address must not be used as Source NAT + boolean isVpcVRSourceNat = vpcManager.isProviderSupportServiceInVpc(vpcId, Service.SourceNat, Network.Provider.VPCVirtualRouter); + if (isVpcVRSourceNat && !ipAddress.isSourceNat()) { + throw new InvalidParameterValueException("Vpn service can only be configured on the Source NAT IP of VPC id=" + ipAddress.getVpcId()); + } + if (!isVpcVRSourceNat && ipAddress.isSourceNat()) { + throw new InvalidParameterValueException("Vpn service can not be configured on the Source NAT IP of VPC id=" + ipAddress.getVpcId()); + } + if (!isVpcVRSourceNat) { + IPAddressVO ipAddressForVpcVR = vpcManager.getIpAddressForVpcVr(vpc, ipAddress, true); + if (!vpcManager.configStaticNatForVpcVr(vpc, ipAddressForVpcVR)) { + throw new CloudRuntimeException("Failed to enable static nat for VPC VR as part of remote access vpn creation"); + } + } + } + } + private void validateRemoteAccessVpnConfiguration() throws ConfigurationException { String ipRange = RemoteAccessVpnClientIpRange.value(); if (ipRange == null) { @@ -364,6 +423,14 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { + if (vpn.getVpcId() != null) { + try { + vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpn.getVpcId(), false); + } catch (ResourceUnavailableException | CloudRuntimeException e) { + logger.error("Unable to apply static routes for vpc " + vpn.getVpcId() + " due to " + e.getMessage()); + } + } + _remoteAccessVpnDao.remove(vpn.getId()); List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId()); diff --git a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java index 8be97644bf5..79cb0361f3b 100644 --- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java @@ -48,6 +48,8 @@ import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; import com.cloud.network.Site2SiteCustomerGateway; import com.cloud.network.Site2SiteVpnConnection; import com.cloud.network.Site2SiteVpnConnection.State; @@ -61,9 +63,12 @@ import com.cloud.network.dao.Site2SiteVpnConnectionVO; import com.cloud.network.dao.Site2SiteVpnGatewayDao; import com.cloud.network.dao.Site2SiteVpnGatewayVO; import com.cloud.network.element.Site2SiteVpnServiceProvider; +import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.VpcOfferingServiceMapVO; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.user.Account; import com.cloud.user.AccountManager; @@ -80,6 +85,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.dao.DomainRouterDao; @Component public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager { @@ -100,11 +106,17 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn @Inject ConfigurationDao _configDao; @Inject - VpcManager _vpcMgr; - @Inject AccountManager _accountMgr; @Inject private AnnotationDao annotationDao; + @Inject + VpcOfferingServiceMapDao vpcOfferingServiceMapDao; + @Inject + private DomainRouterDao domainRouterDao; + @Inject + private IpAddressManager ipAddressManager; + @Inject + private VpcManager vpcManager; int _connLimit; int _subnetsLimit; @@ -136,13 +148,9 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn if (gws != null) { throw new InvalidParameterValueException(String.format("The VPN gateway of VPC %s already existed!", vpc)); } - //Use source NAT ip for VPC - List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(vpcId, true); - if (ips.size() != 1) { - throw new CloudRuntimeException(String.format("Cannot found source nat ip of vpc %s", vpc)); - } - Site2SiteVpnGatewayVO gw = new Site2SiteVpnGatewayVO(owner.getAccountId(), owner.getDomainId(), ips.get(0).getId(), vpcId); + IPAddressVO ipAddress = getIpAddressIdForVpn(vpcId, vpc.getVpcOfferingId()); + Site2SiteVpnGatewayVO gw = new Site2SiteVpnGatewayVO(owner.getAccountId(), owner.getDomainId(), ipAddress.getId(), vpcId); if (cmd.getDisplay() != null) { gw.setDisplay(cmd.getDisplay()); @@ -152,6 +160,29 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn return gw; } + private IPAddressVO getIpAddressIdForVpn(Long vpcId, Long vpcOferingId) { + VpcOfferingServiceMapVO mapForSourceNat = vpcOfferingServiceMapDao.findByServiceProviderAndOfferingId(Network.Service.SourceNat.getName(), Network.Provider.VPCVirtualRouter.getName(), vpcOferingId); + VpcOfferingServiceMapVO mapForVpn = vpcOfferingServiceMapDao.findByServiceProviderAndOfferingId(Network.Service.Vpn.getName(), Network.Provider.VPCVirtualRouter.getName(), vpcOferingId); + if (mapForSourceNat == null && mapForVpn != null) { + // Use Static NAT IP of VPC VR + logger.debug(String.format("The VPC VR provides %s Service, however it does not provide %s service, trying to configure using IP of VPC VR", Network.Service.Vpn.getName(), Network.Service.SourceNat.getName())); + + Vpc vpc = _vpcDao.findById(vpcId); + IPAddressVO ipAddressForVpcVR = vpcManager.getIpAddressForVpcVr(vpc, null, true); + if (!vpcManager.configStaticNatForVpcVr(vpc, ipAddressForVpcVR)) { + throw new CloudRuntimeException("Failed to enable static nat for VPC VR as part of vpn gateway"); + } + return ipAddressForVpcVR; + } else { + //Use source NAT ip for VPC + List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(vpcId, true); + if (ips.size() != 1) { + throw new CloudRuntimeException("Cannot found source nat ip of vpc " + vpcId); + } + return ips.get(0); + } + } + protected void checkCustomerGatewayCidrList(String guestCidrList) { String[] cidrList = guestCidrList.split(","); if (cidrList.length > _subnetsLimit) { @@ -272,7 +303,8 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn String[] cidrList = customerGateway.getGuestCidrList().split(","); // Remote sub nets cannot overlap VPC's sub net - String vpcCidr = _vpcDao.findById(vpnGateway.getVpcId()).getCidr(); + Vpc vpc = _vpcDao.findById(vpnGateway.getVpcId()); + String vpcCidr = vpc.getCidr(); for (String cidr : cidrList) { if (NetUtils.isNetworksOverlap(vpcCidr, cidr)) { throw new InvalidParameterValueException(String.format("The subnets of customer gateway %s subnet %s is overlapped with VPC cidr %s!", customerGateway, cidr, vpcCidr)); @@ -307,6 +339,13 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn } _vpnConnectionDao.persist(conn); + + try { + vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpc.getId(), false); + } catch (ResourceUnavailableException | CloudRuntimeException e) { + logger.error("Unable to apply static routes for vpc " + vpc.getId() + " due to " + e.getMessage()); + } + return conn; } @@ -582,7 +621,19 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn if (conn.getState() != State.Pending) { stopVpnConnection(id); } + + conn.setState(State.Removed); + _vpnConnectionDao.update(id, conn); + + final Site2SiteVpnGateway vpnGateway = _vpnGatewayDao.findById(conn.getVpnGatewayId()); + try { + vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpnGateway.getVpcId(), false); + } catch (ResourceUnavailableException | CloudRuntimeException e) { + logger.error("Unable to apply static routes for vpc " + vpnGateway.getVpcId() + " due to " + e.getMessage()); + } + _vpnConnectionDao.remove(id); + return true; } diff --git a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java index 8ff0e70ec72..9038fcd720a 100644 --- a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java @@ -1255,6 +1255,7 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio serviceProviderMap.put(Service.Dhcp, routerProvider); serviceProviderMap.put(Service.Dns, routerProvider); serviceProviderMap.put(Service.UserData, routerProvider); + serviceProviderMap.put(Service.Vpn, routerProvider); if (forVpc) { serviceProviderMap.put(Service.NetworkACL, provider); } else { diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index cf0b71ab436..7d07cf73f49 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -1023,13 +1023,20 @@ class CsSite2SiteVpn(CsDataBag): local_ip = self.dbag[vpn]['local_public_ip'] dev = CsHelper.get_device(local_ip) + if not self.config.has_public_network(): + for interface in self.config.address().get_interfaces(): + if interface.is_guest() and interface.is_added(): + dev = interface.get_device() + local_ip = interface.get_ip() + break + if dev == "": logging.error("Request for ipsec to %s not possible because ip is not configured", local_ip) continue CsHelper.start_if_stopped("ipsec") - self.configure_iptables(dev, self.dbag[vpn]) - self.configure_ipsec(self.dbag[vpn]) + self.configure_iptables(dev, local_ip, self.dbag[vpn]) + self.configure_ipsec(local_ip, self.dbag[vpn]) # Delete vpns that are no longer in the configuration for ip in self.confips: @@ -1045,10 +1052,10 @@ class CsSite2SiteVpn(CsDataBag): os.remove(vpnsecretsfile) CsHelper.execute("ipsec reload") - def configure_iptables(self, dev, obj): - self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])]) - self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 4500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])]) - self.fw.append(["", "front", "-A INPUT -i %s -p esp -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])]) + def configure_iptables(self, dev, local_ip, obj): + self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], local_ip)]) + self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 4500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], local_ip)]) + self.fw.append(["", "front", "-A INPUT -i %s -p esp -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], local_ip)]) self.fw.append(["nat", "front", "-A POSTROUTING -t nat -o %s -m mark --mark 0x525 -j ACCEPT" % dev]) for net in obj['peer_guest_cidr_list'].lstrip().rstrip().split(','): self.fw.append(["mangle", "front", @@ -1060,7 +1067,7 @@ class CsSite2SiteVpn(CsDataBag): self.fw.append(["mangle", "", "-A INPUT -s %s -d %s -j MARK --set-xmark 0x524/0xffffffff" % (net, obj['local_guest_cidr'])]) - def configure_ipsec(self, obj): + def configure_ipsec(self, local_ip, obj): leftpeer = obj['local_public_ip'] rightpeer = obj['peer_gateway_ip'] peerlist = obj['peer_guest_cidr_list'].replace(' ', '') @@ -1082,7 +1089,8 @@ class CsSite2SiteVpn(CsDataBag): file.repopulate() # This avoids issues when switching off split_connections or removing subnets with split_connections == true file.add("#conn for vpn-%s" % rightpeer, 0) file.search("conn ", "conn vpn-%s" % rightpeer) - file.addeq(" left=%s" % leftpeer) + file.addeq(" left=%s" % local_ip) + file.addeq(" leftid=%s" % leftpeer) file.addeq(" leftsubnet=%s" % obj['local_guest_cidr']) file.addeq(" right=%s" % rightpeer) file.addeq(" rightsubnet=%s" % peerlist) @@ -1223,9 +1231,17 @@ class CsRemoteAccessVpn(CsDataBag): logging.debug("Enabling remote access vpn on " + public_ip) CsHelper.start_if_stopped("ipsec") - self.configure_l2tpIpsec(public_ip, self.dbag[public_ip]) + logging.debug("Remote accessvpn data bag %s", self.dbag) - self.remoteaccessvpn_iptables(public_ip, self.dbag[public_ip]) + if not self.config.has_public_network(): + for interface in self.config.address().get_interfaces(): + if interface.is_guest() and interface.is_added(): + self.configure_l2tpIpsec(interface.get_ip(), self.dbag[public_ip]) + self.remoteaccessvpn_iptables(interface.get_device(), interface.get_ip(), self.dbag[public_ip]) + break + else: + self.configure_l2tpIpsec(public_ip, self.dbag[public_ip]) + self.remoteaccessvpn_iptables(self.dbag['public_interface'], public_ip, self.dbag[public_ip]) CsHelper.execute("ipsec update") CsHelper.execute("systemctl start xl2tpd") @@ -1250,6 +1266,7 @@ class CsRemoteAccessVpn(CsDataBag): # Left l2tpfile = CsFile(l2tpconffile) l2tpfile.addeq(" left=%s" % left) + l2tpfile.addeq(" leftid=%s" % obj['vpn_server_ip']) l2tpfile.commit() secret = CsFile(vpnsecretfilte) @@ -1266,8 +1283,7 @@ class CsRemoteAccessVpn(CsDataBag): xl2tpoptions.search("ms-dns ", "ms-dns %s" % localip) xl2tpoptions.commit() - def remoteaccessvpn_iptables(self, publicip, obj): - publicdev = obj['public_interface'] + def remoteaccessvpn_iptables(self, publicdev, publicip, obj): localcidr = obj['local_cidr'] local_ip = obj['local_ip'] diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py index 1c4695df806..8d91186c005 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py @@ -344,7 +344,7 @@ class CsIP: interfaces = [CsInterface(address, self.config)] CsHelper.reconfigure_interfaces(self.cl, interfaces) - if self.get_type() in ['public'] and not self.config.is_routed(): + if self.get_type() in ['public'] and not self.config.is_routed() and self.config.has_public_network(): self.set_mark() if 'gateway' in self.address: @@ -361,7 +361,14 @@ class CsIP: # The code looks redundant here, but we actually have to cater for routers and # VPC routers in a different manner. Please do not remove this block otherwise # The VPC default route will be broken. - if self.get_type() in ["public"] and address["device"] == CsHelper.PUBLIC_INTERFACES[self.cl.get_type()]: + if not self.config.has_public_network(): + for interface in self.config.address().get_interfaces(): + if interface.is_guest() and interface.is_added() and interface.get_device() == self.dev: + gateway = interface.get_gateway() + route.add_defaultroute(gateway) + CsHelper.execute("sudo echo 0 > /proc/sys/net/ipv4/conf/%s/rp_filter" % interface.get_device()) + break + elif self.get_type() in ["public"] and address["device"] == CsHelper.PUBLIC_INTERFACES[self.cl.get_type()]: gateway = str(address["gateway"]) route.add_defaultroute(gateway) else: @@ -427,7 +434,7 @@ class CsIP: self.fw.append(["filter", "", "-P FORWARD DROP"]) def fw_router(self): - if self.config.is_vpc() or self.config.is_routed(): + if self.config.is_vpc() or self.config.is_routed() or self.config.is_dhcp(): return self.fw.append(["mangle", "front", "-A PREROUTING " + @@ -524,20 +531,24 @@ class CsIP: self.fw.append(["mangle", "front", "-A PREROUTING " + " -i %s -m state --state RELATED,ESTABLISHED " % self.dev + "-j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff"]) + guestNetworkCidr = self.address['network'] - self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" % - (guestNetworkCidr, self.dev, self.dev)]) - self.fw.append( - ["filter", "front", "-A ACL_INBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev]) - self.fw.append( - ["filter", "front", "-A ACL_INBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev]) - self.fw.append( - ["filter", "", "-A ACL_INBOUND_%s -j DROP" % self.dev]) + if self.config.has_public_network(): + self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" % + (guestNetworkCidr, self.dev, self.dev)]) + self.fw.append( + ["filter", "front", "-A ACL_INBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev]) + self.fw.append( + ["filter", "front", "-A ACL_INBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev]) + self.fw.append( + ["filter", "", "-A ACL_INBOUND_%s -j DROP" % self.dev]) + self.fw.append( + ["mangle", "front", "-A ACL_OUTBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev]) + self.fw.append( + ["mangle", "front", "-A ACL_OUTBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev]) + else: + self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACCEPT" % (guestNetworkCidr, self.dev)]) - self.fw.append( - ["mangle", "front", "-A ACL_OUTBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev]) - self.fw.append( - ["mangle", "front", "-A ACL_OUTBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev]) self.fw.append( ["filter", "", "-A INPUT -i %s -p udp -m udp --dport 67 -j ACCEPT" % self.dev]) self.fw.append( @@ -552,9 +563,11 @@ class CsIP: ["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 443 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)]) self.fw.append( ["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 8080 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)]) - self.fw.append(["mangle", "", - "-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" % - (self.dev, guestNetworkCidr, self.address['gateway'], self.dev)]) + + if self.config.has_public_network(): + self.fw.append(["mangle", "", + "-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" % + (self.dev, guestNetworkCidr, self.address['gateway'], self.dev)]) if self.is_private_gateway(): self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" % @@ -706,6 +719,47 @@ class CsIP: self.nft_ipv4_acl.append({'type': "", 'chain': 'FORWARD', 'rule': "iifname %s oifname %s ct state related,established counter accept" % (self.dev, self.dev)}) + def fw_dhcpserver(self): + if not self.config.is_dhcp(): + return + + self.fw.append(["mangle", "front", + "-A POSTROUTING " + + "-p udp -m udp --dport 68 -j CHECKSUM --checksum-fill"]) + + self.fw.append(["filter", "", "-A INPUT -d 224.0.0.18/32 -j ACCEPT"]) + self.fw.append(["filter", "", "-A INPUT -d 225.0.0.50/32 -j ACCEPT"]) + self.fw.append(["filter", "", "-A INPUT -i %s -m state --state RELATED,ESTABLISHED -j ACCEPT" % + self.dev]) + self.fw.append(["filter", "", "-A INPUT -p icmp -j ACCEPT"]) + self.fw.append(["filter", "", "-A INPUT -i lo -j ACCEPT"]) + + if self.config.is_vpc(): + self.fw.append( + ["filter", "", "-A INPUT -i eth0 -p tcp -m tcp --dport 3922 -m state --state NEW,ESTABLISHED -j ACCEPT"]) + else: + self.fw.append( + ["filter", "", "-A INPUT -i eth1 -p tcp -m tcp --dport 3922 -m state --state NEW,ESTABLISHED -j ACCEPT"]) + + if self.get_type() in ["guest"]: + guestNetworkCidr = self.address['network'] + self.fw.append( + ["filter", "", "-A INPUT -i %s -p udp -m udp --dport 67 -j ACCEPT" % self.dev]) + self.fw.append( + ["filter", "", "-A INPUT -i %s -p udp -m udp --dport 53 -s %s -j ACCEPT" % (self.dev, guestNetworkCidr)]) + self.fw.append( + ["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 53 -s %s -j ACCEPT" % (self.dev, guestNetworkCidr)]) + self.fw.append( + ["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 80 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)]) + self.fw.append( + ["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 443 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)]) + self.fw.append( + ["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 8080 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)]) + self.fw.append( + ["filter", "", "-A FORWARD -i %s -o %s -m state --state RELATED,ESTABLISHED -j ACCEPT" % (self.dev, self.dev)]) + self.fw.append( + ["filter", "", "-A FORWARD -i %s -o %s -m state --state NEW -j ACCEPT" % (self.dev, self.dev)]) + def post_config_change(self, method): route = CsRoute() @@ -755,6 +809,7 @@ class CsIP: self.fw_vpcrouter() self.fw_router_routing() self.fw_vpcrouter_routing() + self.fw_dhcpserver() cmdline = self.config.cmdline() diff --git a/systemvm/debian/opt/cloud/bin/cs/CsConfig.py b/systemvm/debian/opt/cloud/bin/cs/CsConfig.py index a17f6ac4aa5..33a78fc03e1 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsConfig.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsConfig.py @@ -148,3 +148,6 @@ class CsConfig(object): return 'mangle' else: return "" + + def has_public_network(self): + return self.cmdline().idata().get('has_public_network', 'true') == 'true' diff --git a/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py b/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py index 2ce181c17a6..3db6ffd33b1 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py @@ -141,9 +141,9 @@ class CsDhcp(CsDataBag): listen_address.append(gateway) listen_address.append(ip) # Add localized "data-server" records in /etc/hosts for VPC routers - if self.config.is_vpc() or self.config.is_router(): + if (self.config.is_vpc() and gn.is_vr_guest_gateway()) or self.config.is_router(): self.add_host(gateway, "%s data-server" % CsHelper.get_hostname()) - elif self.config.is_dhcp(): + elif self.config.is_dhcp() or (self.config.is_vpc() and not gn.is_vr_guest_gateway()): self.add_host(ip, "%s data-server" % CsHelper.get_hostname()) idx += 1 diff --git a/systemvm/debian/opt/cloud/bin/cs/CsGuestNetwork.py b/systemvm/debian/opt/cloud/bin/cs/CsGuestNetwork.py index 615c61d98e3..db9ad7701d7 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsGuestNetwork.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsGuestNetwork.py @@ -35,13 +35,19 @@ class CsGuestNetwork: def is_guestnetwork(self): return self.guest + def is_vr_guest_gateway(self): + return self.guest and ('is_vr_guest_gateway' not in self.data or self.data['is_vr_guest_gateway']) + def get_dns(self): if not self.guest: return self.config.get_dns() dns = [] - if 'router_guest_gateway' in self.data and not self.config.use_extdns() and ('is_vr_guest_gateway' not in self.data or not self.data['is_vr_guest_gateway']): - dns.append(self.data['router_guest_gateway']) + if not self.config.use_extdns(): + if 'router_guest_gateway' in self.data and self.is_vr_guest_gateway(): + dns.append(self.data['router_guest_gateway']) + elif 'router_guest_ip' in self.data and not self.is_vr_guest_gateway(): + dns.append(self.data['router_guest_ip']) if 'dns' in self.data: dns.extend(self.data['dns'].split(',')) diff --git a/systemvm/debian/opt/cloud/bin/passwd_server_ip.py b/systemvm/debian/opt/cloud/bin/passwd_server_ip.py index 8051951a18f..bf1eab2db02 100755 --- a/systemvm/debian/opt/cloud/bin/passwd_server_ip.py +++ b/systemvm/debian/opt/cloud/bin/passwd_server_ip.py @@ -28,6 +28,7 @@ import binascii import cgi import os +import socketserver import sys import syslog import threading @@ -97,9 +98,17 @@ def removePassword(ip): del passMap[ip] -class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): - pass +class CloudStackPasswordServer(socketserver.TCPServer): + allow_reuse_address = 1 + def server_bind(self): + """Override server_bind to store the server name.""" + socketserver.TCPServer.server_bind(self) + host, port = self.server_address[:2] + self.server_name = host + self.server_port = port +class ThreadedHTTPServer(ThreadingMixIn, CloudStackPasswordServer): + pass class PasswordRequestHandler(BaseHTTPRequestHandler): server_version = 'CloudStack Password Server' diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index baedf21ecbf..4ea1a31f9cb 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1556,6 +1556,7 @@ "label.newinstance": "New Instance", "label.newname": "New name", "label.next": "Next", +"label.nexthop": "Next hop", "label.nfs": "NFS", "label.nfsmountopts": "NFS mount options", "label.nfsserver": "NFS server", @@ -2101,7 +2102,7 @@ "label.simplified.chinese.keyboard": "Simplified Chinese keyboard", "label.site": "Netris Site", "label.site.to.site.vpn": "Site-to-site VPN", -"label.site.to.site.vpn.connections": "Site-to-site VPN Connections", +"label.site.to.site.vpn.connections": "VPN Connections", "label.size": "Size", "label.sizegb": "Size", "label.smb.domain": "SMB domain", @@ -2569,6 +2570,7 @@ "label.volumetype": "Volume Type", "label.vpc": "VPC", "label.vpcs": "VPCs", +"label.vpc.gateway.ip": "VPC Gateway IP", "label.vpc.id": "VPC ID", "label.vpc.offerings": "VPC offerings", "label.vpc.virtual.router": "VPC virtual router", @@ -3050,7 +3052,7 @@ "message.enable.vpn": "Please confirm that you want remote access VPN enabled for this IP address.", "message.enable.vpn.failed": "Failed to enable VPN.", "message.enable.vpn.processing": "Enabling VPN...", -"message.enabled.vpn": "Your remote access VPN is currently enabled and can be accessed via the IP.", +"message.enabled.vpn": "Your remote access VPN is currently enabled and can be accessed via the IP", "message.enabled.vpn.ip.sec": "Your IPSec pre-shared key is", "message.enabling.security.group.provider": "Enabling security group provider", "message.enter.valid.nic.ip": "Please enter a valid IP address for NIC", diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue index e19456ecd5c..7691fc9fb11 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@ -473,7 +473,7 @@ <div class="resource-detail-item__label">{{ $t('label.vmname') }}</div> <div class="resource-detail-item__details"> <desktop-outlined /> - <router-link :to="{ path: createPathBasedOnVmType(resource.vmtype, resource.virtualmachineid) }">{{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} </router-link> + <router-link :to="{ path: createPathBasedOnVmType(resource.vmtype || resource.virtualmachinetype, resource.virtualmachineid) }">{{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} </router-link> <status class="status status--end" :text="resource.vmstate" v-if="resource.vmstate"/> </div> </div> diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 1e09c318b01..a434848bda2 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -795,7 +795,7 @@ export default { }, { name: 'vpn', component: shallowRef(defineAsyncComponent(() => import('@/views/network/VpnDetails.vue'))), - show: (record) => { return record.issourcenat } + show: (record) => { return record.issourcenat || record.virtualmachinetype === 'DomainRouter' || !record.hasrules } }, { name: 'events', @@ -962,7 +962,6 @@ export default { title: 'label.site.to.site.vpn.connections', docHelp: 'adminguide/networking_and_traffic.html#setting-up-a-site-to-site-vpn-connection', icon: 'sync-outlined', - hidden: true, permission: ['listVpnConnections'], columns: ['publicip', 'state', 'gateway', 'ipsecpsk', 'ikepolicy', 'esppolicy'], details: ['publicip', 'gateway', 'passive', 'cidrlist', 'ipsecpsk', 'ikepolicy', 'esppolicy', 'ikelifetime', 'ikeversion', 'esplifetime', 'dpd', 'splitconnections', 'forceencap', 'created'], diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index 03b56fab2ee..6855f6e1738 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -134,21 +134,21 @@ export default { this.tabs = this.defaultTabs return } - // VPC IPs with source nat have only VPN - if (this.resource && this.resource.vpcid && this.resource.issourcenat) { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) - return - } - // VPC IPs with vpnenabled have only VPN - if (this.resource && this.resource.vpcid && this.resource.vpnenabled) { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) - return - } - // VPC IPs with static nat have nothing - if (this.resource && this.resource.vpcid && this.resource.isstaticnat) { - return - } if (this.resource && this.resource.vpcid) { + // VPC IPs with source nat have only VPN + if (this.resource.issourcenat) { + this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) + return + } + + // VPC IPs with static nat have nothing + if (this.resource.isstaticnat) { + if (this.resource.virtualmachinetype === 'DomainRouter') { + this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) + } + return + } + // VPC IPs don't have firewall let tabs = this.$route.meta.tabs.filter(tab => tab.name !== 'firewall') @@ -194,6 +194,9 @@ export default { this.actions = this.$route.meta.actions || [] }, fetchNetwork () { + if (!this.resource.associatednetworkid) { + return null + } return new Promise((resolve, reject) => { api('listNetworks', { listAll: true, diff --git a/ui/src/views/network/StaticRoutesTab.vue b/ui/src/views/network/StaticRoutesTab.vue index 9265f30fac1..bae5fcce168 100644 --- a/ui/src/views/network/StaticRoutesTab.vue +++ b/ui/src/views/network/StaticRoutesTab.vue @@ -17,29 +17,44 @@ <template> <a-spin :spinning="componentLoading"> - <div class="new-route" v-ctrl-enter="handleAdd"> - <a-input v-model:value="newRoute" :placeholder="$t('label.cidr.destination.network')" v-focus="true"></a-input> + <div class="form" v-ctrl-enter="handleAdd"> + <div class="form__label"> + <a-input v-model:value="newRoute" :placeholder="$t('label.cidr.destination.network')" v-focus="true"></a-input> + </div> + <div class="form__label" v-if="this.$route.fullPath.startsWith('/vpc')"> + <div :span="24" class="form__label">via</div> + </div> + <div class="form__label" v-if="this.$route.fullPath.startsWith('/vpc')"> + <a-input v-model:value="nexthop" :placeholder="$t('label.nexthop')"></a-input> + </div> <a-button type="primary" :disabled="!('createStaticRoute' in $store.getters.apis)" @click="handleAdd">{{ $t('label.add.route') }}</a-button> </div> - <div class="list"> - <div v-for="(route, index) in routes" :key="index" class="list__item"> - <div class="list__col"> - <div class="list__label">{{ $t('label.cidr.destination.network') }}</div> - <div>{{ route.cidr }}</div> - </div> - <div class="actions"> - <tooltip-button :tooltip="$t('label.edit.tags')" icon="tag-outlined" @onClick="() => openTagsModal(route)" /> + <a-divider/> + <a-table + size="small" + style="overflow-y: auto" + :loading="loading" + :columns="columns" + :dataSource="routes" + :pagination="false" + :rowKey="record => record.id"> + <template #bodyCell="{ column, text, record }"> + <template v-if="column.key === 'vpcgatewayip'"> + <router-link :to="{ path: '/privategw/' + record.vpcgatewayid }" >{{ text }}</router-link> + </template> + <template v-if="column.key === 'actions'"> + <tooltip-button :tooltip="$t('label.edit.tags')" icon="tag-outlined" @onClick="() => openTagsModal(record)" /> <tooltip-button :tooltip="$t('label.delete')" :disabled="!('deleteStaticRoute' in $store.getters.apis)" icon="delete-outlined" type="primary" :danger="true" - @onClick="() => handleDelete(route)" /> - </div> - </div> - </div> + @onClick="() => handleDelete(record)" /> + </template> + </template> + </a-table> <a-modal :title="$t('label.edit.tags')" @@ -90,10 +105,12 @@ import { ref, reactive, toRaw } from 'vue' import { api } from '@/api' import TooltipButton from '@/components/widgets/TooltipButton' +import TooltipLabel from '@/components/widgets/TooltipLabel.vue' export default { name: 'StaticRoutesTab', components: { + TooltipLabel, TooltipButton }, props: { @@ -114,7 +131,27 @@ export default { tagsModalVisible: false, tags: [], tagsLoading: false, - newRoute: null + newRoute: null, + nexthop: null, + columns: [ + { + title: this.$t('label.cidr.destination.network'), + dataIndex: 'cidr' + }, + { + title: this.$t('label.vpc.gateway.ip'), + key: 'vpcgatewayip', + dataIndex: 'vpcgatewayip' + }, + { + title: this.$t('label.nexthop'), + dataIndex: 'nexthop' + }, + { + title: this.$t('label.actions'), + key: 'actions' + } + ] } }, created () { @@ -141,10 +178,15 @@ export default { }, fetchData () { this.componentLoading = true - api('listStaticRoutes', { - gatewayid: this.resource.id, - listall: true - }).then(json => { + var params = { + listAll: true + } + if (this.$route.fullPath.startsWith('/vpc')) { + params.vpcid = this.resource.id + } else { + params.gatewayid = this.resource.id + } + api('listStaticRoutes', params).then(json => { this.routes = json.liststaticroutesresponse.staticroute }).catch(error => { this.$notifyError(error) @@ -157,10 +199,18 @@ export default { if (!this.newRoute) return this.componentLoading = true - api('createStaticRoute', { - cidr: this.newRoute, - gatewayid: this.resource.id - }).then(response => { + var params = { + cidr: this.newRoute + } + if (this.$route.fullPath.startsWith('/vpc')) { + params.vpcid = this.resource.id + if (this.nexthop) { + params.nexthop = this.nexthop + } + } else { + params.gatewayid = this.resource.id + } + api('createStaticRoute', params).then(response => { this.$pollJob({ jobId: response.createstaticrouteresponse.jobid, title: this.$t('message.success.add.static.route'), @@ -367,20 +417,38 @@ export default { margin-left: auto; } - .new-route { + .form { display: flex; - padding-top: 10px; + margin-right: -20px; + margin-bottom: 20px; + flex-direction: column; + align-items: flex-start; - input { - margin-right: 10px; + @media (min-width: 760px) { + flex-direction: row; } - button { - &:not(:last-child) { - margin-right: 10px; + &__item { + display: flex; + flex-direction: column; + flex: 1; + padding-right: 20px; + margin-bottom: 20px; + + @media (min-width: 760px) { + margin-bottom: 0; } + + input, + .ant-select { + margin-top: auto; + } + } + &__label { + font-size: 18px; + font-weight: bold; + } } - </style> diff --git a/ui/src/views/network/VpcTab.vue b/ui/src/views/network/VpcTab.vue index 5c0838ff7e0..a3072dbc502 100644 --- a/ui/src/views/network/VpcTab.vue +++ b/ui/src/views/network/VpcTab.vue @@ -360,6 +360,9 @@ </a-spin> </a-modal> </a-tab-pane> + <a-tab-pane :tab="$t('label.static.routes')" key="staticroutes"> + <StaticRoutesTab :resource="resource" :loading="loading" /> + </a-tab-pane> <a-tab-pane :tab="$t('label.virtual.routers')" key="vr" v-if="$store.getters.userInfo.roletype === 'Admin'"> <RoutersTab :resource="resource" :loading="loading" /> </a-tab-pane> @@ -393,6 +396,7 @@ import EventsTab from '@/components/view/EventsTab' import AnnotationsTab from '@/components/view/AnnotationsTab' import ResourceIcon from '@/components/view/ResourceIcon' import BgpPeersTab from '@/views/infra/zone/BgpPeersTab.vue' +import StaticRoutesTab from './StaticRoutesTab' export default { name: 'VpcTab', @@ -404,6 +408,7 @@ export default { RoutersTab, VpcTiersTab, VnfAppliancesTab, + StaticRoutesTab, EventsTab, AnnotationsTab, ResourceIcon diff --git a/ui/src/views/network/VpcTiersTab.vue b/ui/src/views/network/VpcTiersTab.vue index 814b4d8bf17..e033432c1bc 100644 --- a/ui/src/views/network/VpcTiersTab.vue +++ b/ui/src/views/network/VpcTiersTab.vue @@ -637,7 +637,7 @@ export default { } } this.networkOfferings = filteredOfferings - if (this.isNsxEnabled || ['netris', 'nsx'].includes(this.zoneExtNetProvider.toLowerCase())) { + if (this.isNsxEnabled || (this.zoneExtNetProvider && ['netris', 'nsx'].includes(this.zoneExtNetProvider.toLowerCase()))) { this.networkOfferings = this.networkOfferings.filter(offering => offering.networkmode === (this.isOfferingNatMode ? 'NATTED' : 'ROUTED')) } if (this.resource.asnumberid) { diff --git a/ui/src/views/offering/AddNetworkOffering.vue b/ui/src/views/offering/AddNetworkOffering.vue index 8f7a743ded7..4d66fb46c0b 100644 --- a/ui/src/views/offering/AddNetworkOffering.vue +++ b/ui/src/views/offering/AddNetworkOffering.vue @@ -464,7 +464,7 @@ <a-form-item name="conservemode" ref="conservemode" - v-if="(guestType === 'shared' || guestType === 'isolated') && !isVpcVirtualRouterForAtLeastOneService && + v-if="(guestType === 'shared' || guestType === 'isolated') && (form.provider !== 'NSX' && form.provider !== 'Netris') && networkmode !== 'ROUTED'"> <template #label> <tooltip-label :title="$t('label.conservemode')" :tooltip="apiParams.conservemode.description"/> diff --git a/utils/src/main/java/com/cloud/utils/net/NetUtils.java b/utils/src/main/java/com/cloud/utils/net/NetUtils.java index 500e2401fca..17fea87cb49 100644 --- a/utils/src/main/java/com/cloud/utils/net/NetUtils.java +++ b/utils/src/main/java/com/cloud/utils/net/NetUtils.java @@ -1829,6 +1829,22 @@ public class NetUtils { return MAX_CIDR; } + /** + Return the size of smallest CIDR which contains the IP range (startIp-endIp). + */ + public static int getBigCidrSizeOfIpRange(long startIp, long endIp) { + assert startIp <= MAX_IPv4_ADDR : "Keep startIp smaller than or equals to " + MAX_IPv4_ADDR; + assert endIp <= MAX_IPv4_ADDR : "Keep endIp smaller than or equals to " + MAX_IPv4_ADDR; + for (int cidrSize = MAX_CIDR; cidrSize >= 1; cidrSize--) { + long minStartIp = startIp & (((long) 0xffffffff) >> (MAX_CIDR - cidrSize) << (MAX_CIDR - cidrSize)); + long maxEndIp = (minStartIp | (((long) 0x1) << (MAX_CIDR - cidrSize)) - 1); + if (minStartIp <= startIp && maxEndIp >= endIp) { + return cidrSize; + } + } + return MAX_CIDR; + } + /** Return the list of pairs (Network Address, Network cidrsize) */ diff --git a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java index 2f0666a39c6..13f6f245d25 100644 --- a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java +++ b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java @@ -932,4 +932,15 @@ public class NetUtilsTest { Assert.assertEquals("192.168.0.0/24", NetUtils.transformCidr("192.168.0.100/24")); Assert.assertEquals("10.10.10.10/32", NetUtils.transformCidr("10.10.10.10/32")); } + + @Test + public void testVpnIpRange() { + String ipRange = "10.1.2.1-10.1.2.8"; + String startIp = ipRange.split("-")[0]; + String endIp = ipRange.split("-")[1]; + int cidrSize = NetUtils.getBigCidrSizeOfIpRange(NetUtils.ip2Long(startIp), NetUtils.ip2Long(endIp)); + Assert.assertEquals(28, cidrSize); + String cidr = NetUtils.transformCidr(startIp + "/" + cidrSize); + Assert.assertEquals("10.1.2.0/28", cidr); + } }
