From 8659d9691b860c987231c2a75b92c2b873e9168f Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Fri, 20 Dec 2024 14:53:48 +0100 Subject: [PATCH] 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 * 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 --- .../java/com/cloud/network/IpAddress.java | 1 + .../cloud/network/Site2SiteVpnConnection.java | 2 +- .../com/cloud/network/vpc/StaticRoute.java | 4 +- .../cloud/network/vpc/StaticRouteProfile.java | 22 +- .../com/cloud/network/vpc/VpcService.java | 2 +- .../apache/cloudstack/api/ApiConstants.java | 3 + .../user/vpc/CreateStaticRouteCmd.java | 51 ++- .../api/response/StaticRouteResponse.java | 24 +- .../service/NetworkOrchestrationService.java | 3 + .../java/com/cloud/network/addr/PublicIp.java | 4 + .../com/cloud/network/rules/RulesManager.java | 2 + .../com/cloud/network/vpc/VpcManager.java | 17 + .../orchestration/NetworkOrchestrator.java | 21 +- .../cloud/network/dao/IPAddressDaoImpl.java | 1 + .../com/cloud/network/dao/IPAddressVO.java | 12 + .../cloud/network/dao/RemoteAccessVpnDao.java | 2 + .../network/dao/RemoteAccessVpnDaoImpl.java | 7 + .../network/dao/Site2SiteVpnGatewayDao.java | 2 + .../dao/Site2SiteVpnGatewayDaoImpl.java | 8 + .../com/cloud/network/vpc/StaticRouteVO.java | 35 +- .../network/vpc/dao/VpcServiceMapDaoImpl.java | 11 +- .../main/java/com/cloud/vm/dao/NicDao.java | 4 + .../java/com/cloud/vm/dao/NicDaoImpl.java | 20 + .../META-INF/db/schema-41910to42000.sql | 6 + .../service/NetrisApiClientImpl.java | 1 + .../java/com/cloud/api/ApiResponseHelper.java | 52 ++- .../cloud/network/IpAddressManagerImpl.java | 21 + .../com/cloud/network/NetworkModelImpl.java | 1 + .../network/element/VirtualRouterElement.java | 2 +- .../guru/ExternalGuestNetworkGuru.java | 5 +- .../network/router/CommandSetupHelper.java | 5 +- .../network/router/NetworkHelperImpl.java | 9 +- .../network/router/NicProfileHelperImpl.java | 4 +- .../VirtualNetworkApplianceManagerImpl.java | 8 + ...VpcVirtualNetworkApplianceManagerImpl.java | 20 +- .../cloud/network/rules/RulesManagerImpl.java | 35 +- .../com/cloud/network/vpc/VpcManagerImpl.java | 402 ++++++++++++++++-- .../vpn/RemoteAccessVpnManagerImpl.java | 71 +++- .../network/vpn/Site2SiteVpnManagerImpl.java | 69 ++- .../cloud/server/ConfigurationServerImpl.java | 1 + systemvm/debian/opt/cloud/bin/configure.py | 44 +- systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 93 +++- systemvm/debian/opt/cloud/bin/cs/CsConfig.py | 3 + systemvm/debian/opt/cloud/bin/cs/CsDhcp.py | 4 +- .../debian/opt/cloud/bin/cs/CsGuestNetwork.py | 10 +- .../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 | 131 ++++-- ui/src/views/network/VpcTab.vue | 5 + ui/src/views/network/VpcTiersTab.vue | 2 +- ui/src/views/offering/AddNetworkOffering.vue | 2 +- .../java/com/cloud/utils/net/NetUtils.java | 16 + .../com/cloud/utils/net/NetUtilsTest.java | 11 + 56 files changed, 1154 insertions(+), 192 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 17fe3d624d8..744dd22aae5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -254,6 +254,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"; @@ -863,6 +864,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/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 363dcd31b52..adce5f2f8b4 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -82,6 +82,9 @@ public interface NetworkOrchestrationService { ConfigKey NetworkLockTimeout = new ConfigKey(Integer.class, NetworkLockTimeoutCK, "Network", "600", "Lock wait timeout (seconds) while implementing network", true, Scope.Global, null); + ConfigKey DeniedRoutes = new ConfigKey(String.class, "denied.routes", "Network", "", + "Routes that are denied, can not be used for Static Routes creation for the VPC Private Gateway", true, ConfigKey.Scope.Zone, null); + ConfigKey GuestDomainSuffix = new ConfigKey(String.class, GuestDomainSuffixCK, "Network", "cloud.internal", "Default domain name for vms inside virtualized networks fronted by router", true, ConfigKey.Scope.Zone, null); 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 c77874329fc..7bab2a26fb4 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 @@ -53,6 +53,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 a340f49c13f..9018c0d17a3 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 @@ -35,6 +35,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; @@ -174,4 +175,20 @@ public interface VpcManager { * @return */ boolean isSrcNatIpRequired(long vpcOfferingId); + + boolean isSrcNatIpRequiredForVpcVr(long vpcOfferingId); + + List getVpcStaticRoutes(Long vpcId); + + List getVpcStaticRoutes(List 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 15681b27c6f..99a3ec3caca 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() { @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); } } @@ -3294,6 +3303,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; } @@ -4874,7 +4891,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout, + return new ConfigKey[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout, DeniedRoutes, GuestDomainSuffix, NetworkThrottlingRate, MinVRVersion, PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RollingRestartEnabled, TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED }; 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 aa143838c34..2b5277f1560 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 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 4c7569a55b9..6be7d24e367 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 @@ -116,6 +116,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; @@ -385,4 +388,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 List findByAccount(Long accountId); List listByNetworkId(Long networkId); + + List 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 listByVpcId(Long vpcId) { + SearchCriteria 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 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 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 @Override public boolean canProviderSupportServiceInVpc(long vpcId, Service service, Provider provider) { - // TODO Auto-generated method stub - return false; + SearchCriteria 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 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 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 implements NicDao { return findByNetworkIdAndTypeInternal(networkId, vmType, true); } + @Override + public NicVO findNonPlaceHolderByNetworkIdAndType(long networkId, VirtualMachine.Type vmType) { + SearchCriteria 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 sc = AllFieldsSearch.create(); @@ -222,6 +232,16 @@ public class NicDaoImpl extends GenericDaoBase implements NicDao { return findOneBy(sc); } + @Override + public NicVO findNonPlaceHolderByIp4AddressAndNetworkId(String ip4Address, long networkId) { + SearchCriteria 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 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 43b3932b00c..5404cc46c85 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); @@ -3156,12 +3180,6 @@ public class ApiResponseHelper implements ResponseGenerator { List serviceProviders = ApiDBUtils.getProvidersForService(service); List serviceProvidersResponses = new ArrayList(); 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); } @@ -3772,6 +3790,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 e440d39462e..2c54a5d890c 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 AssignIpAddressSearch; SearchBuilder AssignIpAddressFromPodVlanSearch; @@ -744,6 +750,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(addrId, 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 b8533b472ec..a9c5725d489 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -2300,6 +2300,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 providers = s_serviceToImplementedProvidersMap.get(service); providers.add(implementedProvider); diff --git a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java index a87504cd07a..53cf838ca87 100644 --- a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java +++ b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java @@ -543,7 +543,7 @@ NetworkMigrationResponder, AggregatedCommandExecutor, RedundantResource, DnsServ // Set capabilities for vpn final Map vpnCapabilities = new HashMap(); vpnCapabilities.put(Capability.SupportedVpnProtocols, "pptp,l2tp,ipsec"); - vpnCapabilities.put(Capability.VpnTypes, "removeaccessvpn"); + vpnCapabilities.put(Capability.VpnTypes, "remoteaccessvpn"); capabilities.put(Service.Vpn, vpnCapabilities); final Map dnsCapabilities = new HashMap(); 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 83a3dbcfaa7..3481efc2b51 100644 --- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java +++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java @@ -1219,8 +1219,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 1f4642bbd85..dd82a2b8203 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> hypervisorsMap = new HashMap<>(); @@ -276,6 +279,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; } @@ -773,7 +780,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 8f07fcfca3b..7e263eaed84 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -204,6 +204,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; @@ -336,6 +337,7 @@ Configurable, StateListener 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 routes = _staticRouteDao.listByVpcId(domainRouterVO.getVpcId()); - final List staticRouteProfiles = new ArrayList(routes.size()); - final Map gatewayMap = new HashMap(); - 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 routes = _vpcMgr.getVpcStaticRoutes(domainRouterVO.getVpcId()); + final List 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 55f7609f9c6..1678015dc4e 100644 --- a/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java +++ b/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java @@ -58,6 +58,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; @@ -153,6 +155,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) { @@ -1262,6 +1268,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"); @@ -1294,7 +1319,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"); @@ -1329,11 +1354,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); @@ -1371,7 +1399,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 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 aef3f06f28d..db90aef30c8 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 vpcElements = null; @@ -461,7 +486,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis final Map> svcProviderMap = new HashMap<>(); final Set 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 userDataProvider = Set.of(Provider.VPCVirtualRouter); svcProviderMap.put(svc, userDataProvider); } else { @@ -2225,6 +2250,12 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis logger.debug("Cleaning up existed site to site VPN gateways"); _s2sVpnMgr.cleanupVpnGatewayByVpc(vpcId); + List vpns = remoteAccessVpnDao.listByVpcId(vpcId); + 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 ipsToRelease = _ipAddressDao.listByAssociatedVpc(vpcId, null); logger.debug("Releasing ips for vpc id=" + vpcId + " as a part of vpc cleanup"); @@ -2360,6 +2391,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis restartRequired = true; return false; } + reconfigStaticNatForVpcVr(vpcId); return true; } @@ -2867,28 +2899,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 routes = _staticRouteDao.listByVpcId(vpcId); + final List routes = getVpcStaticRoutes(vpcId); return applyStaticRoutes(routes, caller, true); } - protected boolean applyStaticRoutes(final List routes, final Account caller, final boolean updateRoutesInDB) throws ResourceUnavailableException { - final boolean success = true; - final List staticRouteProfiles = new ArrayList(routes.size()); - final Map gatewayMap = new HashMap(); - 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 routes = getVpcStaticRoutes(vpcId, updateAllVpn); + return applyStaticRoutes(routes, caller, false); } + return true; + } + + protected boolean applyStaticRoutes(final List routes, final Account caller, final boolean updateRoutesInDB) throws ResourceUnavailableException { + final boolean success = true; + final List 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"); @@ -2951,7 +2993,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @DB protected boolean revokeStaticRoutesForVpc(final long vpcId, final Account caller) throws ResourceUnavailableException { // get all static routes for the vpc - final List routes = _staticRouteDao.listByVpcId(vpcId); + final List routes = getVpcStaticRoutes(vpcId); logger.debug("Found " + routes.size() + " to revoke for the vpc " + vpcId); if (!routes.isEmpty()) { // mark all of them as revoke @@ -2972,23 +3014,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"); } - final Vpc vpc = getActiveVpc(gateway.getVpcId()); + 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(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)) { @@ -3002,7 +3067,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis } // 2) CIDR should be outside of link-local cidr - if (NetUtils.isNetworksOverlap(vpc.getCidr(), NetUtils.getLinkLocalCIDR())) { + if (NetUtils.isNetworksOverlap(cidr, NetUtils.getLinkLocalCIDR())) { throw new InvalidParameterValueException("CIDR should be outside of link local cidr " + NetUtils.getLinkLocalCIDR()); } @@ -3011,10 +3076,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() { @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); @@ -3030,8 +3100,46 @@ 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 ips = _ipAddressDao.listByAssociatedVpc(vpc.getId(), null); + List 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 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.GuestDomainSuffix.valueIn(zoneId); + final String routesStr = NetworkOrchestrationService.DeniedRoutes.valueIn(zoneId); if (routesStr != null && !routesStr.isEmpty()) { final String[] cidrDenyList = routesStr.split(","); @@ -3435,6 +3543,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> 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 @@ -3524,4 +3641,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 getVpcStaticRoutes(final List routes) { + final List staticRouteProfiles = new ArrayList<>(routes.size()); + final Map gatewayMap = new HashMap(); + 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 getVpcStaticRoutes(Long vpcId) { + return getVpcStaticRoutes(vpcId, false); + } + + public List getVpcStaticRoutes(Long vpcId, boolean updateAllVpn) { + final List 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 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 staticRoutesForSite2SiteVpn = new ArrayList<>(); + List 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 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 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 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 6cef834b0f7..9be925d36a0 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 _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) { @@ -365,6 +424,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 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 094f81607fe..5c31bb3bbb2 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("The VPN gateway of VPC " + vpcId + " already existed!"); } - //Use source NAT ip for VPC - List ips = _ipAddressDao.listByAssociatedVpc(vpcId, true); - if (ips.size() != 1) { - throw new CloudRuntimeException("Cannot found source nat ip of vpc " + vpcId); - } - 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 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("The subnets of customer gateway " + customerGatewayId + "'s subnet " + cidr + " is overlapped with VPC cidr " + @@ -308,6 +340,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; } @@ -584,7 +623,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 76efd339fa9..782734c8c8b 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 21e125e3bb6..c4201b34e3b 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -1021,13 +1021,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: @@ -1043,10 +1050,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", @@ -1058,7 +1065,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(' ', '') @@ -1080,7 +1087,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) @@ -1100,7 +1108,7 @@ class CsSite2SiteVpn(CsDataBag): file.addeq(" dpddelay=30") file.addeq(" dpdtimeout=120") file.addeq(" dpdaction=restart") - if splitconnections and peerlistarr.count > 1: + if splitconnections and len(peerlistarr) > 1: logging.debug('Splitting connections for rightsubnets %s' % peerlistarr) for peeridx in range(1, len(peerlistarr)): logging.debug('Adding split connection -%d for subnet %s' % (peeridx + 1, peerlistarr[peeridx])) @@ -1221,9 +1229,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") @@ -1248,6 +1264,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) @@ -1264,8 +1281,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'] @@ -1640,7 +1656,7 @@ def main(argv): ("dhcp", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}), ("load_balancer", {"process_iptables": True, "executor": []}), ("monitor_service", {"process_iptables": False, "executor": [CsMonitor("monitorservice", config)]}), - ("static_routes", {"process_iptables": False, "executor": [CsStaticRoutes("staticroutes", config)]}) + ("static_routes", {"process_iptables": True, "executor": [CsStaticRoutes("staticroutes", config)]}) ]) if not config.is_vpc(): diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py index 0d1c31ac14b..864c6fb504c 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py @@ -343,7 +343,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: @@ -360,7 +360,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: @@ -426,7 +433,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 " + @@ -523,20 +530,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]) - 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]) + guestNetworkCidr = self.address['network'] + 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( ["filter", "", "-A INPUT -i %s -p udp -m udp --dport 67 -j ACCEPT" % self.dev]) self.fw.append( @@ -551,9 +562,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", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" % @@ -686,6 +699,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() @@ -735,6 +789,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 46259dcb356..29a4318a2a9 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py @@ -142,9 +142,9 @@ class CsDhcp(CsDataBag): else: 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 16c364f4280..9331cac80b2 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1547,6 +1547,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", @@ -2083,7 +2084,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", @@ -2546,6 +2547,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", @@ -3027,7 +3029,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 06775d8efaf..58f0aa4a918 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@ -443,7 +443,7 @@
{{ $t('label.vmname') }}
- {{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} + {{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }}
diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 79eae34f537..8464b789509 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -794,7 +794,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', @@ -1001,7 +1001,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 0c960244902..bae5fcce168 100644 --- a/ui/src/views/network/StaticRoutesTab.vue +++ b/ui/src/views/network/StaticRoutesTab.vue @@ -17,29 +17,44 @@