This is an automated email from the ASF dual-hosted git repository.

pearl11594 pushed a commit to branch netris-integration-upstream
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 0e4dde9111489d98f2ff598502e7fb23f743dabc
Author: Wei Zhou <[email protected]>
AuthorDate: Fri Dec 20 14:53:48 2024 +0100

    Netris FR1b: Support Remote Access VPN and Site-to-Site VPN in VPC VR (#41)
    
    * Static Routes: support nexthop
    
    * Update 
api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java
    
    Co-authored-by: Pearl Dsilva <[email protected]>
    
    * PR#10064 VR: apply iptables rules when add/remove static routes
    
    * PR#10065 UI: fix cannot open 'Edit tags' modal for static routes
    
    * PR#10066 Static Routes: fix check on wrong global configuration
    
    * PR#10067 VR: fix site-2-site VPN if split connections is enabled
    
    * PR#10081 server: do not allocate nic on public network for NSX VPC VR
    
    * PR#10082 UI: create VPC network offering with conserve mode
    
    * PR#10083 VR: allow outgoing traffic from RAS/VPN clients
    
    * PR#10086 server: fix typo removeaccessvpn in VirtualRouterElement
    
    * server: Add check on Public IP for remote access VPN
    
    * Revert "PR#10083 VR: allow outgoing traffic from RAS/VPN clients"
    
    This reverts commit 2f9b9f428947cac91de322fbdf4a980902a1c0a0.
    
    * VPC: fetch same used IP for domain router if VR is not Source NAT
    
    * VR: pass has_public_network to VR and configure RA/S2S VPN left peers
    
    * Revert "PR#10081 server: do not allocate nic on public network for NSX 
VPC VR"
    
    This reverts commit 809e269ed6b361d9df1fcef6537762c5612863e0.
    
    * VPC: fetch same used IP for domain router if VR is not Source NAT (v2)
    
    * VR: fix /etc/hosts and nameservers in dnsmasq.conf if VPC VR is not guest 
gateway
    
    prior to this PR
    ```
    root@r-1167-VM:~# cat /etc/hosts
    127.0.0.1       localhost
    127.0.1.1       r-1167-VM
    ::1     localhost ip6-localhost ip6-loopback
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    172.21.1.33     dummy-vpc-vpn-001
    172.21.1.1      r-1167-VM data-server
    
    root@r-1167-VM:~# cat /etc/dnsmasq.d/cloud.conf
    dhcp-hostsfile=/etc/dhcphosts.txt
    listen-address=127.0.0.1,172.21.1.234
    dhcp-range=set:interface-eth1-0,172.21.1.234,static
    dhcp-option=tag:interface-eth1-0,15,cs2cloud.internal
    dhcp-option=tag:interface-eth1-0,6,172.21.1.1,10.0.32.1,8.8.8.8
    dhcp-option=tag:interface-eth1-0,3,172.21.1.1
    dhcp-option=eth1,26,1500
    dhcp-option=tag:interface-eth1-0,1,255.255.255.0
    ```
    
    the lines should be
    ```
    172.21.1.234  r-1167-VM data-server
    
    dhcp-option=tag:interface-eth1-0,6,10.0.32.1,8.8.8.8
    ```
    
    * server: Enable static NAT for Domain router if it is not Source NAT
    
    * server: Enable static NAT for Domain router on UI
    
    * server: assign Public IP to VPC VR and enable static nat if VR is not 
Source NAT
    
    * server: configure dns1 if VR is not Source NAT
    
    * server: remove check on Firewall service when list network service 
providers
    
    * UI: remove dot from message.enabled.vpn
    
    * systemvm: add default route via first guest gateway if VR does not have 
public IP/interface
    
    * VR: add fw_dhcpserver for shared network
    
    * VR: pass has_public_network to VR and configure RA/S2S VPN left peers (v2)
    
    * UI: fix request error when create a VPC tier in a non-Netris/NSX env
    
    * systemvm: add default route via first guest gateway (v2)
    
    * VR: configure iptables rules for S2S vpn on first guest interface
    
    * VR: allow FORWARD to guest interfaces if VR is not Public
    
    * VR: configure remote access vpn on first guest interface if not public
    
    * VR: fix error 789 in RA VPN client when both RA and S2S are configured
    
    * server: Apply Static Route for RA/S2S VPN in VPC VR
    
    * VR: do not set mark for Public interface when VR is not really public
    
    * VPN: do not disable static nat if it is used by a RA/S2S VPN
    
    * server: skip check on network conserve mode if disable/enable RA VPN on 
Router IP
    
    * server: set forRouter to false when release a IP
    
    * VR: diable IP spoofing protection on default guest network
    
    * VR: fix iptables rules only when only S2S vpn is enabled
    
    * UI: show 'VPN Connections' section
    
    * VPC: new methods to configure/reconfigure Static NAT for VPC VR
    
    * API: set Type in ip address response to DomainRouter if it is used by VR
    
    * server: do not allow IP release if it is used by RA or S2S VPN gateway
    
    * VR: check if interface is added
    
    * VR: add default route only when ip is associated to first guest interface
    
    * VR: fix ipsec conf for l2tp and s2s vpn
    
    * server: save placeholder IP for VPC VR to fix the new VR IP when vpc tier 
is auto-shutdown
    
    * server: get non-placeholder NIC for VPC VR
    
    * VR: wait 15 seconds after starting password server
    
    * server: fix unable to configure static nat due to 'invalid virtual 
machine id'
    
    * UI: fix link of router in info card
    
    * VPC: apply static route for VPC VPN if needed (refactoring)
    
    * server: fix VR IP of first VPC tier is the VM gateway
    
    * server: update or remove all existing static routes when shutdown a 
network
    
    * server: update ipaddress after disabling static nat to fix vpc deletion 
issue
    
    * servr: disable remote access VPN as part of VPC dstroy
    
    * server: apply static routes when implement a vpc tier
    
    * server: apply static routes even if next hop is null
    
    * server: fix Cannot invoke "com.cloud.vm.NicProfile.getRequestedIPv4()" 
because "requested" is null
    
    * Netris: Update Vpn provider to VpcVirtualRouter
    
    * Netris: Add Vpn service to network offerings and networks
    
    * server: fix CIDR of VPN ip range
    
    * server: set isVrGuestGateway by SoureNat/Gateway service with 
Provider.VPCVirtualRouter
    
    * VR: password server takes 10-15 seconds to start if VR IP is not 
configured in /etc/hosts
    
    * Netris: add back routesPutBody.setStateStatus
    
    * engine/schema: remove SQL changes in schema-41910to42000.sql
    
    ---------
    
    Co-authored-by: Pearl Dsilva <[email protected]>
---
 api/src/main/java/com/cloud/network/IpAddress.java |   1 +
 .../com/cloud/network/Site2SiteVpnConnection.java  |   2 +-
 .../java/com/cloud/network/vpc/StaticRoute.java    |   4 +-
 .../com/cloud/network/vpc/StaticRouteProfile.java  |  22 +-
 .../java/com/cloud/network/vpc/VpcService.java     |   2 +-
 .../org/apache/cloudstack/api/ApiConstants.java    |   3 +
 .../api/command/user/vpc/CreateStaticRouteCmd.java |  51 ++-
 .../api/response/StaticRouteResponse.java          |  24 +-
 .../main/java/com/cloud/network/addr/PublicIp.java |   4 +
 .../java/com/cloud/network/rules/RulesManager.java |   2 +
 .../java/com/cloud/network/vpc/VpcManager.java     |  17 +
 .../engine/orchestration/NetworkOrchestrator.java  |  19 +-
 .../com/cloud/network/dao/IPAddressDaoImpl.java    |   1 +
 .../java/com/cloud/network/dao/IPAddressVO.java    |  12 +
 .../com/cloud/network/dao/RemoteAccessVpnDao.java  |   2 +
 .../cloud/network/dao/RemoteAccessVpnDaoImpl.java  |   7 +
 .../cloud/network/dao/Site2SiteVpnGatewayDao.java  |   2 +
 .../network/dao/Site2SiteVpnGatewayDaoImpl.java    |   8 +
 .../java/com/cloud/network/vpc/StaticRouteVO.java  |  35 +-
 .../network/vpc/dao/VpcServiceMapDaoImpl.java      |  11 +-
 .../src/main/java/com/cloud/vm/dao/NicDao.java     |   4 +
 .../src/main/java/com/cloud/vm/dao/NicDaoImpl.java |  20 ++
 .../resources/META-INF/db/schema-41910to42000.sql  |   6 +
 .../cloudstack/service/NetrisApiClientImpl.java    |   1 +
 .../main/java/com/cloud/api/ApiResponseHelper.java |  52 ++-
 .../com/cloud/network/IpAddressManagerImpl.java    |  21 ++
 .../java/com/cloud/network/NetworkModelImpl.java   |   1 +
 .../network/guru/ExternalGuestNetworkGuru.java     |   5 +-
 .../cloud/network/router/CommandSetupHelper.java   |   5 +-
 .../cloud/network/router/NetworkHelperImpl.java    |   9 +-
 .../cloud/network/router/NicProfileHelperImpl.java |   4 +-
 .../router/VirtualNetworkApplianceManagerImpl.java |   8 +
 .../VpcVirtualNetworkApplianceManagerImpl.java     |  20 +-
 .../com/cloud/network/rules/RulesManagerImpl.java  |  35 +-
 .../java/com/cloud/network/vpc/VpcManagerImpl.java | 398 +++++++++++++++++++--
 .../network/vpn/RemoteAccessVpnManagerImpl.java    |  71 +++-
 .../cloud/network/vpn/Site2SiteVpnManagerImpl.java |  69 +++-
 .../com/cloud/server/ConfigurationServerImpl.java  |   1 +
 systemvm/debian/opt/cloud/bin/configure.py         |  40 ++-
 systemvm/debian/opt/cloud/bin/cs/CsAddress.py      |  91 ++++-
 systemvm/debian/opt/cloud/bin/cs/CsConfig.py       |   3 +
 systemvm/debian/opt/cloud/bin/cs/CsDhcp.py         |   4 +-
 systemvm/debian/opt/cloud/bin/cs/CsGuestNetwork.py |  10 +-
 systemvm/debian/opt/cloud/bin/passwd_server_ip.py  |  13 +-
 ui/public/locales/en.json                          |   6 +-
 ui/src/components/view/InfoCard.vue                |   2 +-
 ui/src/config/section/network.js                   |   3 +-
 ui/src/views/network/PublicIpResource.vue          |  31 +-
 ui/src/views/network/StaticRoutesTab.vue           | 130 +++++--
 ui/src/views/network/VpcTab.vue                    |   5 +
 ui/src/views/network/VpcTiersTab.vue               |   2 +-
 ui/src/views/offering/AddNetworkOffering.vue       |   2 +-
 .../main/java/com/cloud/utils/net/NetUtils.java    |  16 +
 .../java/com/cloud/utils/net/NetUtilsTest.java     |  11 +
 54 files changed, 1144 insertions(+), 184 deletions(-)

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


Reply via email to