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 <pearl1594@gmail.com>

* 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 <pearl1594@gmail.com>
This commit is contained in:
Wei Zhou 2024-12-20 14:53:48 +01:00 committed by GitHub
parent 5ac35a2d8b
commit 8659d9691b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 1154 additions and 192 deletions

View File

@ -99,4 +99,5 @@ public interface IpAddress extends ControlledEntity, Identity, InternalIdentity,
boolean isForSystemVms();
boolean isForRouter();
}

View File

@ -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

View File

@ -33,7 +33,9 @@ public interface StaticRoute extends ControlledEntity, Identity, InternalIdentit
/**
* @return
*/
long getVpcGatewayId();
Long getVpcGatewayId();
String getNextHop();
/**
* @return

View File

@ -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;

View File

@ -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

View File

@ -254,6 +254,7 @@ public class ApiConstants {
public static final String PREVIOUS_OWNER_ID = "previousownerid";
public static final String PREVIOUS_OWNER_NAME = "previousownername";
public static final String NEXT_ACL_RULE_ID = "nextaclruleid";
public static final String NEXT_HOP = "nexthop";
public static final String MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash";
public static final String IMAGE_PATH = "imagepath";
public static final String INSTANCE_CONVERSION_SUPPORTED = "instanceconversionsupported";
@ -863,6 +864,8 @@ public class ApiConstants {
public static final String NETWORK = "network";
public static final String VPC_ID = "vpcid";
public static final String VPC_NAME = "vpcname";
public static final String VPC_GATEWAY_ID = "vpcgatewayid";
public static final String VPC_GATEWAY_IP = "vpcgatewayip";
public static final String GATEWAY_ID = "gatewayid";
public static final String CAN_USE_FOR_DEPLOY = "canusefordeploy";
public static final String RESOURCE_IDS = "resourceids";

View File

@ -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

View File

@ -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) {

View File

@ -82,6 +82,9 @@ public interface NetworkOrchestrationService {
ConfigKey<Integer> NetworkLockTimeout = new ConfigKey<Integer>(Integer.class, NetworkLockTimeoutCK, "Network", "600",
"Lock wait timeout (seconds) while implementing network", true, Scope.Global, null);
ConfigKey<String> DeniedRoutes = new ConfigKey<String>(String.class, "denied.routes", "Network", "",
"Routes that are denied, can not be used for Static Routes creation for the VPC Private Gateway", true, ConfigKey.Scope.Zone, null);
ConfigKey<String> GuestDomainSuffix = new ConfigKey<String>(String.class, GuestDomainSuffixCK, "Network", "cloud.internal",
"Default domain name for vms inside virtualized networks fronted by router", true, ConfigKey.Scope.Zone, null);

View File

@ -275,5 +275,9 @@ public class PublicIp implements PublicIpAddress {
return false;
}
@Override
public boolean isForRouter() {
return _addr.isForRouter();
}
}

View File

@ -53,6 +53,8 @@ public interface RulesManager extends RulesService {
boolean disableStaticNat(long ipAddressId, Account caller, long callerUserId, boolean releaseIpIfElastic) throws ResourceUnavailableException;
boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke);
/**
* @param networkId
* @param continueOnError

View File

@ -35,6 +35,7 @@ import com.cloud.network.Network.Provider;
import com.cloud.network.Network.Service;
import com.cloud.network.PhysicalNetwork;
import com.cloud.network.addr.PublicIp;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.offering.NetworkOffering;
import com.cloud.user.Account;
@ -174,4 +175,20 @@ public interface VpcManager {
* @return
*/
boolean isSrcNatIpRequired(long vpcOfferingId);
boolean isSrcNatIpRequiredForVpcVr(long vpcOfferingId);
List<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;
}

View File

@ -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);
}
}
@ -3294,6 +3303,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
}
}
}
if (network.getVpcId() != null) {
_vpcMgr.reconfigStaticNatForVpcVr(network.getVpcId());
try {
_vpcMgr.applyStaticRouteForVpcVpnIfNeeded(network.getVpcId(), true);
} catch (ResourceUnavailableException e) {
logger.error("Unable to apply static routes for vpc " + network.getVpcId() + " due to " + e.getMessage());
}
}
return success;
}
@ -4874,7 +4891,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout,
return new ConfigKey<?>[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout, DeniedRoutes,
GuestDomainSuffix, NetworkThrottlingRate, MinVRVersion,
PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RollingRestartEnabled,
TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED };

View File

@ -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);

View File

@ -116,6 +116,9 @@ public class IPAddressVO implements IpAddress {
@Column(name = "forsystemvms")
private boolean forSystemVms = false;
@Column(name = "for_router")
private boolean forRouter = false;
@Column(name= GenericDao.REMOVED_COLUMN)
private Date removed;
@ -385,4 +388,13 @@ public class IPAddressVO implements IpAddress {
public boolean isForSystemVms() {
return forSystemVms;
}
@Override
public boolean isForRouter() {
return forRouter;
}
public void setForRouter(boolean forRouter) {
this.forRouter = forRouter;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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"');

View File

@ -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());

View File

@ -49,6 +49,7 @@ import com.cloud.dc.dao.ASNumberDao;
import com.cloud.dc.dao.ASNumberRangeDao;
import com.cloud.dc.dao.VlanDetailsDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.vpc.VpcGateway;
import com.cloud.storage.BucketVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
@ -1036,6 +1037,9 @@ public class ApiResponseHelper implements ResponseGenerator {
addSystemVmInfoToIpResponse(nic, response);
}
}
if (ipAddress.isForRouter()) {
response.setVirtualMachineType(Type.DomainRouter.toString());
}
if (ipAddress.getAssociatedWithVmId() != null) {
addUserVmDetailsInIpResponse(response, ipAddress);
}
@ -1062,12 +1066,25 @@ public class ApiResponseHelper implements ResponseGenerator {
}
private void addUserVmDetailsInIpResponse(IPAddressResponse response, IpAddress ipAddress) {
UserVm userVm = ApiDBUtils.findUserVmById(ipAddress.getAssociatedWithVmId());
if (userVm != null) {
response.setVirtualMachineId(userVm.getUuid());
response.setVirtualMachineName(userVm.getHostName());
response.setVirtualMachineType(userVm.getType().toString());
response.setVirtualMachineDisplayName(ObjectUtils.firstNonNull(userVm.getDisplayName(), userVm.getHostName()));
VirtualMachine vm = ApiDBUtils.findVMInstanceById(ipAddress.getAssociatedWithVmId());
if (vm == null) {
return;
}
if (vm.getType().equals(Type.User)) {
UserVm userVm = ApiDBUtils.findUserVmById(ipAddress.getAssociatedWithVmId());
if (userVm != null) {
response.setVirtualMachineId(userVm.getUuid());
response.setVirtualMachineName(userVm.getHostName());
response.setVirtualMachineType(userVm.getType().toString());
response.setVirtualMachineDisplayName(ObjectUtils.firstNonNull(userVm.getDisplayName(), userVm.getHostName()));
}
} else if (vm.getType().equals(Type.DomainRouter)) {
final boolean isAdmin = Account.Type.ADMIN.equals(CallContext.current().getCallingAccount().getType());
if (isAdmin) {
response.setVirtualMachineId(vm.getUuid());
response.setVirtualMachineName(vm.getHostName());
}
response.setVirtualMachineType(vm.getType().toString());
}
}
@ -1234,6 +1251,13 @@ public class ApiResponseHelper implements ResponseGenerator {
ipResponse.setVirtualMachineDisplayName(vm.getHostName());
}
}
} else if (nic.getVmType() == Type.DomainRouter) {
VirtualMachine vm = ApiDBUtils.findVMInstanceById(nic.getInstanceId());
if (vm != null) {
ipResponse.setVirtualMachineId(vm.getUuid());
ipResponse.setVirtualMachineName(vm.getHostName());
ipResponse.setVirtualMachineType(vm.getType().toString());
}
} else if (nic.getVmType().isUsedBySystem()) {
ipResponse.setIsSystem(true);
addSystemVmInfoToIpResponse(nic, ipResponse);
@ -3156,12 +3180,6 @@ public class ApiResponseHelper implements ResponseGenerator {
List<? 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);
}
@ -3772,6 +3790,16 @@ public class ApiResponseHelper implements ResponseGenerator {
response.setVpcId(vpc.getUuid());
}
}
if (result.getVpcGatewayId() != null) {
VpcGateway vpcGateway = _entityMgr.findById(VpcGateway.class, result.getVpcGatewayId());
if (vpcGateway != null) {
response.setVpcGatewayId(vpcGateway.getUuid());
response.setVpcGatewayIp(vpcGateway.getIp4Address());
}
}
if (result.getNextHop() != null) {
response.setNextHop(result.getNextHop());
}
response.setCidr(result.getCidr());
StaticRoute.State state = result.getState();

View File

@ -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;
@ -744,6 +750,21 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
throw new CloudRuntimeException("Unable to acquire lock on public IP.");
}
if (ipToBeDisassociated.isForRouter()) {
if (remoteAccessVpnDao.findByPublicIpAddress(ipToBeDisassociated.getId()) != null) {
InvalidParameterValueException ex = new InvalidParameterValueException("Can't release IP address as the IP address is used by a Remote Access VPN");
ex.addProxyObject(ipToBeDisassociated.getUuid(), "ipId");
throw ex;
}
if (site2SiteVpnGatewayDao.findByPublicIpAddress(ipToBeDisassociated.getId()) != null) {
InvalidParameterValueException ex = new InvalidParameterValueException("Can't release IP address as the IP address is used by a VPC gateway");
ex.addProxyObject(ipToBeDisassociated.getUuid(), "ipId");
throw ex;
}
}
PublicIpQuarantine publicIpQuarantine = null;
// Cleanup all ip address resources - PF/LB/Static nat rules
if (!cleanupIpResources(addrId, userId, caller)) {

View File

@ -2300,6 +2300,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
}
if (capabilities != null && implementedProvider != null) {
for (Service service : capabilities.keySet()) {
logger.info("Add provider {} and service {}", implementedProvider.getName(), service.getName());
if (s_serviceToImplementedProvidersMap.containsKey(service)) {
List<Provider> providers = s_serviceToImplementedProvidersMap.get(service);
providers.add(implementedProvider);

View File

@ -543,7 +543,7 @@ NetworkMigrationResponder, AggregatedCommandExecutor, RedundantResource, DnsServ
// Set capabilities for vpn
final Map<Capability, String> vpnCapabilities = new HashMap<Capability, String>();
vpnCapabilities.put(Capability.SupportedVpnProtocols, "pptp,l2tp,ipsec");
vpnCapabilities.put(Capability.VpnTypes, "removeaccessvpn");
vpnCapabilities.put(Capability.VpnTypes, "remoteaccessvpn");
capabilities.put(Service.Vpn, vpnCapabilities);
final Map<Capability, String> dnsCapabilities = new HashMap<Capability, String>();

View File

@ -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) {

View File

@ -1219,8 +1219,9 @@ public class CommandSetupHelper {
final SetupGuestNetworkCommand setupCmd = new SetupGuestNetworkCommand(dhcpRange, networkDomain, router.getIsRedundantRouter(), defaultDns1, defaultDns2, add, _itMgr.toNicTO(nicProfile,
router.getHypervisorType()));
boolean isForNsx = _networkModel.isProviderForNetworkOffering(Provider.Nsx, networkOfferingVO.getId());
setupCmd.setVrGuestGateway(isForNsx);
boolean isVrGuestGateway = _networkModel.isAnyServiceSupportedInNetwork(network.getId(), Provider.VPCVirtualRouter, Service.SourceNat, Service.Gateway);
setupCmd.setVrGuestGateway(isVrGuestGateway);
NicVO publicNic = _nicDao.findDefaultNicForVM(router.getId());
if (publicNic != null) {
updateSetupGuestNetworkCommandIpv6(setupCmd, network, publicNic, defaultIp6Dns1, defaultIp6Dns2);

View File

@ -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<>();
@ -276,6 +279,10 @@ public class NetworkHelperImpl implements NetworkHelper {
_itMgr.expunge(router.getUuid());
_routerHealthCheckResultDao.expungeHealthChecks(router.getId());
_routerDao.remove(router.getId());
if (router.getVpcId() != null) {
vpcManager.reconfigStaticNatForVpcVr(router.getVpcId());
}
return router;
}
@ -773,7 +780,7 @@ public class NetworkHelperImpl implements NetworkHelper {
logger.debug("Adding nic for Virtual Router in Guest network " + guestNetwork);
String defaultNetworkStartIp = null, defaultNetworkStartIpv6 = null;
final Nic placeholder = _networkModel.getPlaceholderNicForRouter(guestNetwork, routerDeploymentDefinition.getPodId());
if (!routerDeploymentDefinition.isPublicNetwork()) {
if (!routerDeploymentDefinition.isPublicNetwork() || !vpcManager.isSrcNatIpRequiredForVpcVr(routerDeploymentDefinition.getVpc().getVpcOfferingId())) {
if (guestNetwork.getCidr() != null) {
if (placeholder != null && placeholder.getIPv4Address() != null) {
logger.debug("Requesting ipv4 address " + placeholder.getIPv4Address() + " stored in placeholder nic for the network "

View File

@ -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());

View File

@ -204,6 +204,7 @@ import com.cloud.network.rules.StaticNatImpl;
import com.cloud.network.rules.StaticNatRule;
import com.cloud.network.rules.dao.PortForwardingRulesDao;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.VpcManager;
import com.cloud.network.vpc.VpcService;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.network.vpn.Site2SiteVpnManager;
@ -336,6 +337,7 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
@Inject private NetworkService networkService;
@Inject private VpcService vpcService;
@Inject private VpcManager vpcManager;
@Autowired
@Qualifier("networkHelper")
@ -2039,6 +2041,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 {

View File

@ -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()) {

View File

@ -58,6 +58,8 @@ import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.LoadBalancerVMMapDao;
import com.cloud.network.dao.LoadBalancerVMMapVO;
import com.cloud.network.dao.RemoteAccessVpnDao;
import com.cloud.network.dao.Site2SiteVpnGatewayDao;
import com.cloud.network.rules.FirewallRule.FirewallRuleType;
import com.cloud.network.rules.FirewallRule.Purpose;
import com.cloud.network.rules.FirewallRule.State;
@ -153,6 +155,10 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
VpcService _vpcSvc;
@Inject
VMTemplateDao _templateDao;
@Inject
RemoteAccessVpnDao remoteAccessVpnDao;
@Inject
Site2SiteVpnGatewayDao site2SiteVpnGatewayDao;
protected void checkIpAndUserVm(IpAddress ipAddress, UserVm userVm, Account caller, Boolean ignoreVmState) {
if (ipAddress == null || ipAddress.getAllocatedTime() == null || ipAddress.getAllocatedToAccountId() == null) {
@ -1262,6 +1268,25 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
throw ex;
}
if (ipAddress.isForRouter()) {
if (remoteAccessVpnDao.findByPublicIpAddress(ipAddress.getId()) != null) {
InvalidParameterValueException ex = new InvalidParameterValueException("Can't disable static nat as the IP address is used by a Remote Access VPN");
ex.addProxyObject(ipAddress.getUuid(), "ipId");
throw ex;
}
if (site2SiteVpnGatewayDao.findByPublicIpAddress(ipAddress.getId()) != null) {
InvalidParameterValueException ex = new InvalidParameterValueException("Can't disable static nat as the IP address is used by a VPC gateway");
ex.addProxyObject(ipAddress.getUuid(), "ipId");
throw ex;
}
if (disableStaticNat(ipId, caller, ctx.getCallingUserId(), false)) {
return true;
}
return false;
}
Long vmId = ipAddress.getAssociatedWithVmId();
if (vmId == null) {
InvalidParameterValueException ex = new InvalidParameterValueException("Specified IP address id is not associated with any vm Id");
@ -1294,7 +1319,7 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
IPAddressVO ipAddress = _ipAddressDao.findById(ipId);
checkIpAndUserVm(ipAddress, null, caller, false);
long networkId = ipAddress.getAssociatedWithNetworkId();
Long networkId = ipAddress.getAssociatedWithNetworkId();
if (!ipAddress.isOneToOneNat()) {
InvalidParameterValueException ex = new InvalidParameterValueException("One to one nat is not enabled for the specified ip id");
@ -1329,11 +1354,14 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
ipAddress.setAssociatedWithVmId(null);
ipAddress.setRuleState(null);
ipAddress.setVmIp(null);
ipAddress.setForRouter(false);
if (isIpSystem && !releaseIpIfElastic) {
ipAddress.setSystem(false);
}
_ipAddressDao.update(ipAddress.getId(), ipAddress);
_vpcMgr.unassignIPFromVpcNetwork(ipAddress.getId(), networkId);
if (networkId != null) {
_vpcMgr.unassignIPFromVpcNetwork(ipAddress.getId(), networkId);
}
if (isIpSystem && releaseIpIfElastic && !_ipAddrMgr.handleSystemIpRelease(ipAddress)) {
logger.warn("Failed to release system ip address " + ipAddress);
@ -1371,7 +1399,8 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules
return new StaticNatRuleImpl(ruleVO, dstIp);
}
protected boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke) {
@Override
public boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke) {
IpAddress sourceIp = _ipAddressDao.findById(sourceIpId);
List<StaticNat> staticNats = createStaticNatForIp(sourceIp, caller, forRevoke);

View File

@ -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 {
@ -2225,6 +2250,12 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
logger.debug("Cleaning up existed site to site VPN gateways");
_s2sVpnMgr.cleanupVpnGatewayByVpc(vpcId);
List<RemoteAccessVpnVO> vpns = remoteAccessVpnDao.listByVpcId(vpcId);
for (RemoteAccessVpnVO vpn : vpns) {
logger.debug("Disabling remote access VPN on {}", vpn.getServerAddressId());
remoteAccessVpnMgr.destroyRemoteAccessVpnForIp(vpn.getServerAddressId(), caller, true);
}
// 2) release all ip addresses
final List<IPAddressVO> ipsToRelease = _ipAddressDao.listByAssociatedVpc(vpcId, null);
logger.debug("Releasing ips for vpc id=" + vpcId + " as a part of vpc cleanup");
@ -2360,6 +2391,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
restartRequired = true;
return false;
}
reconfigStaticNatForVpcVr(vpcId);
return true;
}
@ -2867,28 +2899,38 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
@Override
public boolean applyStaticRoutesForVpc(final long vpcId) throws ResourceUnavailableException {
final Account caller = CallContext.current().getCallingAccount();
final List<? 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");
@ -2951,7 +2993,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
@DB
protected boolean revokeStaticRoutesForVpc(final long vpcId, final Account caller) throws ResourceUnavailableException {
// get all static routes for the vpc
final List<StaticRouteVO> routes = _staticRouteDao.listByVpcId(vpcId);
final List<StaticRouteVO> routes = getVpcStaticRoutes(vpcId);
logger.debug("Found " + routes.size() + " to revoke for the vpc " + vpcId);
if (!routes.isEmpty()) {
// mark all of them as revoke
@ -2972,23 +3014,46 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_STATIC_ROUTE_CREATE, eventDescription = "creating static route", create = true)
public StaticRoute createStaticRoute(final long gatewayId, final String cidr) throws NetworkRuleConflictException {
public StaticRoute createStaticRoute(final Long gatewayId, Long vpcId, final String nextHop, final String cidr) throws NetworkRuleConflictException {
final Account caller = CallContext.current().getCallingAccount();
// parameters validation
final VpcGateway gateway = _vpcGatewayDao.findById(gatewayId);
if (gateway == null) {
throw new InvalidParameterValueException("Invalid gateway id is given");
if (gatewayId == null && nextHop == null) {
throw new InvalidParameterValueException("one of gatewayId and nextHop must be specified");
}
if (gateway.getState() != VpcGateway.State.Ready) {
throw new InvalidParameterValueException("Gateway is not in the " + VpcGateway.State.Ready + " state: " + gateway.getState());
if (gatewayId != null && nextHop != null) {
throw new InvalidParameterValueException("Only one of gatewayId and nextHop can be specified");
}
final Vpc vpc = getActiveVpc(gateway.getVpcId());
if (gatewayId != null) {
final VpcGateway gateway = _vpcGatewayDao.findById(gatewayId);
if (gateway == null) {
throw new InvalidParameterValueException("Invalid gateway id is given");
}
if (gateway.getState() != VpcGateway.State.Ready) {
throw new InvalidParameterValueException("Gateway is not in the " + VpcGateway.State.Ready + " state: " + gateway.getState());
}
if (vpcId != null) {
if (!vpcId.equals(gateway.getVpcId())) {
throw new InvalidParameterValueException("Invalid gateway id is given");
}
} else {
vpcId = gateway.getVpcId();
}
} else if (nextHop != null) {
if (vpcId == null) {
throw new InvalidParameterValueException("vpcId must be specified");
}
}
final Vpc vpc = getActiveVpc(vpcId);
if (vpc == null) {
throw new InvalidParameterValueException("Can't add static route to VPC that is being deleted");
}
_accountMgr.checkAccess(caller, null, false, vpc);
if (!NetUtils.isValidIp4Cidr(cidr)) {
@ -3002,7 +3067,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
// 2) CIDR should be outside of link-local cidr
if (NetUtils.isNetworksOverlap(vpc.getCidr(), NetUtils.getLinkLocalCIDR())) {
if (NetUtils.isNetworksOverlap(cidr, NetUtils.getLinkLocalCIDR())) {
throw new InvalidParameterValueException("CIDR should be outside of link local cidr " + NetUtils.getLinkLocalCIDR());
}
@ -3011,10 +3076,15 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
throw new InvalidParameterValueException("The static gateway cidr overlaps with one of the denied routes of the zone the VPC belongs to");
}
// 4) validate next hop
if (nextHop != null && !isNextHopValid(nextHop, vpc)) {
throw new InvalidParameterValueException(String.format("Next hop %s is invalid. It must be within VPC CIDR or on the same public or private network", nextHop));
}
return Transaction.execute(new TransactionCallbackWithException<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);
@ -3030,8 +3100,46 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
});
}
private boolean isNextHopValid(String nextHop, Vpc vpc) {
// Scenario 1: VM as next hop
if (NetUtils.isIpWithInCidrRange(nextHop, vpc.getCidr())) {
logger.debug("The next Hop {} is valid as it is within the VPC cidr {}", nextHop, vpc.getCidr());
return true;
}
// Scenario 2: Another public IP as next hop
List<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.GuestDomainSuffix.valueIn(zoneId);
final String routesStr = NetworkOrchestrationService.DeniedRoutes.valueIn(zoneId);
if (routesStr != null && !routesStr.isEmpty()) {
final String[] cidrDenyList = routesStr.split(",");
@ -3435,6 +3543,15 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
&& vpcOffSvcProvidersMap.get(Service.Gateway).contains(Network.Provider.VPCVirtualRouter));
}
@Override
public boolean isSrcNatIpRequiredForVpcVr(long vpcOfferingId) {
final Map<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
@ -3524,4 +3641,241 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
protected boolean isDefaultAcl(long aclId) {
return aclId == NetworkACL.DEFAULT_ALLOW || aclId == NetworkACL.DEFAULT_DENY;
}
@Override
public List<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;
}
}

View File

@ -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) {
@ -365,6 +424,14 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
if (vpn.getVpcId() != null) {
try {
vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpn.getVpcId(), false);
} catch (ResourceUnavailableException | CloudRuntimeException e) {
logger.error("Unable to apply static routes for vpc " + vpn.getVpcId() + " due to " + e.getMessage());
}
}
_remoteAccessVpnDao.remove(vpn.getId());
List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId());

View File

@ -48,6 +48,8 @@ import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.IpAddressManager;
import com.cloud.network.Network;
import com.cloud.network.Site2SiteCustomerGateway;
import com.cloud.network.Site2SiteVpnConnection;
import com.cloud.network.Site2SiteVpnConnection.State;
@ -61,9 +63,12 @@ import com.cloud.network.dao.Site2SiteVpnConnectionVO;
import com.cloud.network.dao.Site2SiteVpnGatewayDao;
import com.cloud.network.dao.Site2SiteVpnGatewayVO;
import com.cloud.network.element.Site2SiteVpnServiceProvider;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.VpcManager;
import com.cloud.network.vpc.VpcVO;
import com.cloud.network.vpc.VpcOfferingServiceMapVO;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao;
import com.cloud.projects.Project.ListProjectResourcesCriteria;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
@ -80,6 +85,7 @@ import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.DomainRouterVO;
import com.cloud.vm.dao.DomainRouterDao;
@Component
public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpnManager {
@ -100,11 +106,17 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn
@Inject
ConfigurationDao _configDao;
@Inject
VpcManager _vpcMgr;
@Inject
AccountManager _accountMgr;
@Inject
private AnnotationDao annotationDao;
@Inject
VpcOfferingServiceMapDao vpcOfferingServiceMapDao;
@Inject
private DomainRouterDao domainRouterDao;
@Inject
private IpAddressManager ipAddressManager;
@Inject
private VpcManager vpcManager;
int _connLimit;
int _subnetsLimit;
@ -136,13 +148,9 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn
if (gws != null) {
throw new InvalidParameterValueException("The VPN gateway of VPC " + vpcId + " already existed!");
}
//Use source NAT ip for VPC
List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(vpcId, true);
if (ips.size() != 1) {
throw new CloudRuntimeException("Cannot found source nat ip of vpc " + vpcId);
}
Site2SiteVpnGatewayVO gw = new Site2SiteVpnGatewayVO(owner.getAccountId(), owner.getDomainId(), ips.get(0).getId(), vpcId);
IPAddressVO ipAddress = getIpAddressIdForVpn(vpcId, vpc.getVpcOfferingId());
Site2SiteVpnGatewayVO gw = new Site2SiteVpnGatewayVO(owner.getAccountId(), owner.getDomainId(), ipAddress.getId(), vpcId);
if (cmd.getDisplay() != null) {
gw.setDisplay(cmd.getDisplay());
@ -152,6 +160,29 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn
return gw;
}
private IPAddressVO getIpAddressIdForVpn(Long vpcId, Long vpcOferingId) {
VpcOfferingServiceMapVO mapForSourceNat = vpcOfferingServiceMapDao.findByServiceProviderAndOfferingId(Network.Service.SourceNat.getName(), Network.Provider.VPCVirtualRouter.getName(), vpcOferingId);
VpcOfferingServiceMapVO mapForVpn = vpcOfferingServiceMapDao.findByServiceProviderAndOfferingId(Network.Service.Vpn.getName(), Network.Provider.VPCVirtualRouter.getName(), vpcOferingId);
if (mapForSourceNat == null && mapForVpn != null) {
// Use Static NAT IP of VPC VR
logger.debug(String.format("The VPC VR provides %s Service, however it does not provide %s service, trying to configure using IP of VPC VR", Network.Service.Vpn.getName(), Network.Service.SourceNat.getName()));
Vpc vpc = _vpcDao.findById(vpcId);
IPAddressVO ipAddressForVpcVR = vpcManager.getIpAddressForVpcVr(vpc, null, true);
if (!vpcManager.configStaticNatForVpcVr(vpc, ipAddressForVpcVR)) {
throw new CloudRuntimeException("Failed to enable static nat for VPC VR as part of vpn gateway");
}
return ipAddressForVpcVR;
} else {
//Use source NAT ip for VPC
List<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("The subnets of customer gateway " + customerGatewayId + "'s subnet " + cidr + " is overlapped with VPC cidr " +
@ -308,6 +340,13 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn
}
_vpnConnectionDao.persist(conn);
try {
vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpc.getId(), false);
} catch (ResourceUnavailableException | CloudRuntimeException e) {
logger.error("Unable to apply static routes for vpc " + vpc.getId() + " due to " + e.getMessage());
}
return conn;
}
@ -584,7 +623,19 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn
if (conn.getState() != State.Pending) {
stopVpnConnection(id);
}
conn.setState(State.Removed);
_vpnConnectionDao.update(id, conn);
final Site2SiteVpnGateway vpnGateway = _vpnGatewayDao.findById(conn.getVpnGatewayId());
try {
vpcManager.applyStaticRouteForVpcVpnIfNeeded(vpnGateway.getVpcId(), false);
} catch (ResourceUnavailableException | CloudRuntimeException e) {
logger.error("Unable to apply static routes for vpc " + vpnGateway.getVpcId() + " due to " + e.getMessage());
}
_vpnConnectionDao.remove(id);
return true;
}

View File

@ -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 {

View File

@ -1021,13 +1021,20 @@ class CsSite2SiteVpn(CsDataBag):
local_ip = self.dbag[vpn]['local_public_ip']
dev = CsHelper.get_device(local_ip)
if not self.config.has_public_network():
for interface in self.config.address().get_interfaces():
if interface.is_guest() and interface.is_added():
dev = interface.get_device()
local_ip = interface.get_ip()
break
if dev == "":
logging.error("Request for ipsec to %s not possible because ip is not configured", local_ip)
continue
CsHelper.start_if_stopped("ipsec")
self.configure_iptables(dev, self.dbag[vpn])
self.configure_ipsec(self.dbag[vpn])
self.configure_iptables(dev, local_ip, self.dbag[vpn])
self.configure_ipsec(local_ip, self.dbag[vpn])
# Delete vpns that are no longer in the configuration
for ip in self.confips:
@ -1043,10 +1050,10 @@ class CsSite2SiteVpn(CsDataBag):
os.remove(vpnsecretsfile)
CsHelper.execute("ipsec reload")
def configure_iptables(self, dev, obj):
self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])])
self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 4500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])])
self.fw.append(["", "front", "-A INPUT -i %s -p esp -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], obj['local_public_ip'])])
def configure_iptables(self, dev, local_ip, obj):
self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], local_ip)])
self.fw.append(["", "front", "-A INPUT -i %s -p udp -m udp --dport 4500 -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], local_ip)])
self.fw.append(["", "front", "-A INPUT -i %s -p esp -s %s -d %s -j ACCEPT" % (dev, obj['peer_gateway_ip'], local_ip)])
self.fw.append(["nat", "front", "-A POSTROUTING -t nat -o %s -m mark --mark 0x525 -j ACCEPT" % dev])
for net in obj['peer_guest_cidr_list'].lstrip().rstrip().split(','):
self.fw.append(["mangle", "front",
@ -1058,7 +1065,7 @@ class CsSite2SiteVpn(CsDataBag):
self.fw.append(["mangle", "",
"-A INPUT -s %s -d %s -j MARK --set-xmark 0x524/0xffffffff" % (net, obj['local_guest_cidr'])])
def configure_ipsec(self, obj):
def configure_ipsec(self, local_ip, obj):
leftpeer = obj['local_public_ip']
rightpeer = obj['peer_gateway_ip']
peerlist = obj['peer_guest_cidr_list'].replace(' ', '')
@ -1080,7 +1087,8 @@ class CsSite2SiteVpn(CsDataBag):
file.repopulate() # This avoids issues when switching off split_connections or removing subnets with split_connections == true
file.add("#conn for vpn-%s" % rightpeer, 0)
file.search("conn ", "conn vpn-%s" % rightpeer)
file.addeq(" left=%s" % leftpeer)
file.addeq(" left=%s" % local_ip)
file.addeq(" leftid=%s" % leftpeer)
file.addeq(" leftsubnet=%s" % obj['local_guest_cidr'])
file.addeq(" right=%s" % rightpeer)
file.addeq(" rightsubnet=%s" % peerlist)
@ -1100,7 +1108,7 @@ class CsSite2SiteVpn(CsDataBag):
file.addeq(" dpddelay=30")
file.addeq(" dpdtimeout=120")
file.addeq(" dpdaction=restart")
if splitconnections and peerlistarr.count > 1:
if splitconnections and len(peerlistarr) > 1:
logging.debug('Splitting connections for rightsubnets %s' % peerlistarr)
for peeridx in range(1, len(peerlistarr)):
logging.debug('Adding split connection -%d for subnet %s' % (peeridx + 1, peerlistarr[peeridx]))
@ -1221,9 +1229,17 @@ class CsRemoteAccessVpn(CsDataBag):
logging.debug("Enabling remote access vpn on " + public_ip)
CsHelper.start_if_stopped("ipsec")
self.configure_l2tpIpsec(public_ip, self.dbag[public_ip])
logging.debug("Remote accessvpn data bag %s", self.dbag)
self.remoteaccessvpn_iptables(public_ip, self.dbag[public_ip])
if not self.config.has_public_network():
for interface in self.config.address().get_interfaces():
if interface.is_guest() and interface.is_added():
self.configure_l2tpIpsec(interface.get_ip(), self.dbag[public_ip])
self.remoteaccessvpn_iptables(interface.get_device(), interface.get_ip(), self.dbag[public_ip])
break
else:
self.configure_l2tpIpsec(public_ip, self.dbag[public_ip])
self.remoteaccessvpn_iptables(self.dbag['public_interface'], public_ip, self.dbag[public_ip])
CsHelper.execute("ipsec update")
CsHelper.execute("systemctl start xl2tpd")
@ -1248,6 +1264,7 @@ class CsRemoteAccessVpn(CsDataBag):
# Left
l2tpfile = CsFile(l2tpconffile)
l2tpfile.addeq(" left=%s" % left)
l2tpfile.addeq(" leftid=%s" % obj['vpn_server_ip'])
l2tpfile.commit()
secret = CsFile(vpnsecretfilte)
@ -1264,8 +1281,7 @@ class CsRemoteAccessVpn(CsDataBag):
xl2tpoptions.search("ms-dns ", "ms-dns %s" % localip)
xl2tpoptions.commit()
def remoteaccessvpn_iptables(self, publicip, obj):
publicdev = obj['public_interface']
def remoteaccessvpn_iptables(self, publicdev, publicip, obj):
localcidr = obj['local_cidr']
local_ip = obj['local_ip']
@ -1640,7 +1656,7 @@ def main(argv):
("dhcp", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}),
("load_balancer", {"process_iptables": True, "executor": []}),
("monitor_service", {"process_iptables": False, "executor": [CsMonitor("monitorservice", config)]}),
("static_routes", {"process_iptables": False, "executor": [CsStaticRoutes("staticroutes", config)]})
("static_routes", {"process_iptables": True, "executor": [CsStaticRoutes("staticroutes", config)]})
])
if not config.is_vpc():

View File

@ -343,7 +343,7 @@ class CsIP:
interfaces = [CsInterface(address, self.config)]
CsHelper.reconfigure_interfaces(self.cl, interfaces)
if self.get_type() in ['public'] and not self.config.is_routed():
if self.get_type() in ['public'] and not self.config.is_routed() and self.config.has_public_network():
self.set_mark()
if 'gateway' in self.address:
@ -360,7 +360,14 @@ class CsIP:
# The code looks redundant here, but we actually have to cater for routers and
# VPC routers in a different manner. Please do not remove this block otherwise
# The VPC default route will be broken.
if self.get_type() in ["public"] and address["device"] == CsHelper.PUBLIC_INTERFACES[self.cl.get_type()]:
if not self.config.has_public_network():
for interface in self.config.address().get_interfaces():
if interface.is_guest() and interface.is_added() and interface.get_device() == self.dev:
gateway = interface.get_gateway()
route.add_defaultroute(gateway)
CsHelper.execute("sudo echo 0 > /proc/sys/net/ipv4/conf/%s/rp_filter" % interface.get_device())
break
elif self.get_type() in ["public"] and address["device"] == CsHelper.PUBLIC_INTERFACES[self.cl.get_type()]:
gateway = str(address["gateway"])
route.add_defaultroute(gateway)
else:
@ -426,7 +433,7 @@ class CsIP:
self.fw.append(["filter", "", "-P FORWARD DROP"])
def fw_router(self):
if self.config.is_vpc() or self.config.is_routed():
if self.config.is_vpc() or self.config.is_routed() or self.config.is_dhcp():
return
self.fw.append(["mangle", "front", "-A PREROUTING " +
@ -523,20 +530,24 @@ class CsIP:
self.fw.append(["mangle", "front", "-A PREROUTING " +
" -i %s -m state --state RELATED,ESTABLISHED " % self.dev +
"-j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff"])
guestNetworkCidr = self.address['network']
self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" %
(guestNetworkCidr, self.dev, self.dev)])
self.fw.append(
["filter", "front", "-A ACL_INBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev])
self.fw.append(
["filter", "front", "-A ACL_INBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev])
self.fw.append(
["filter", "", "-A ACL_INBOUND_%s -j DROP" % self.dev])
self.fw.append(
["mangle", "front", "-A ACL_OUTBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev])
self.fw.append(
["mangle", "front", "-A ACL_OUTBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev])
guestNetworkCidr = self.address['network']
if self.config.has_public_network():
self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" %
(guestNetworkCidr, self.dev, self.dev)])
self.fw.append(
["filter", "front", "-A ACL_INBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev])
self.fw.append(
["filter", "front", "-A ACL_INBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev])
self.fw.append(
["filter", "", "-A ACL_INBOUND_%s -j DROP" % self.dev])
self.fw.append(
["mangle", "front", "-A ACL_OUTBOUND_%s -d 225.0.0.50/32 -j ACCEPT" % self.dev])
self.fw.append(
["mangle", "front", "-A ACL_OUTBOUND_%s -d 224.0.0.18/32 -j ACCEPT" % self.dev])
else:
self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACCEPT" % (guestNetworkCidr, self.dev)])
self.fw.append(
["filter", "", "-A INPUT -i %s -p udp -m udp --dport 67 -j ACCEPT" % self.dev])
self.fw.append(
@ -551,9 +562,11 @@ class CsIP:
["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 443 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)])
self.fw.append(
["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 8080 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)])
self.fw.append(["mangle", "",
"-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" %
(self.dev, guestNetworkCidr, self.address['gateway'], self.dev)])
if self.config.has_public_network():
self.fw.append(["mangle", "",
"-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" %
(self.dev, guestNetworkCidr, self.address['gateway'], self.dev)])
if self.is_private_gateway():
self.fw.append(["filter", "", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" %
@ -686,6 +699,47 @@ class CsIP:
self.nft_ipv4_acl.append({'type': "", 'chain': 'FORWARD',
'rule': "iifname %s oifname %s ct state related,established counter accept" % (self.dev, self.dev)})
def fw_dhcpserver(self):
if not self.config.is_dhcp():
return
self.fw.append(["mangle", "front",
"-A POSTROUTING " +
"-p udp -m udp --dport 68 -j CHECKSUM --checksum-fill"])
self.fw.append(["filter", "", "-A INPUT -d 224.0.0.18/32 -j ACCEPT"])
self.fw.append(["filter", "", "-A INPUT -d 225.0.0.50/32 -j ACCEPT"])
self.fw.append(["filter", "", "-A INPUT -i %s -m state --state RELATED,ESTABLISHED -j ACCEPT" %
self.dev])
self.fw.append(["filter", "", "-A INPUT -p icmp -j ACCEPT"])
self.fw.append(["filter", "", "-A INPUT -i lo -j ACCEPT"])
if self.config.is_vpc():
self.fw.append(
["filter", "", "-A INPUT -i eth0 -p tcp -m tcp --dport 3922 -m state --state NEW,ESTABLISHED -j ACCEPT"])
else:
self.fw.append(
["filter", "", "-A INPUT -i eth1 -p tcp -m tcp --dport 3922 -m state --state NEW,ESTABLISHED -j ACCEPT"])
if self.get_type() in ["guest"]:
guestNetworkCidr = self.address['network']
self.fw.append(
["filter", "", "-A INPUT -i %s -p udp -m udp --dport 67 -j ACCEPT" % self.dev])
self.fw.append(
["filter", "", "-A INPUT -i %s -p udp -m udp --dport 53 -s %s -j ACCEPT" % (self.dev, guestNetworkCidr)])
self.fw.append(
["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 53 -s %s -j ACCEPT" % (self.dev, guestNetworkCidr)])
self.fw.append(
["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 80 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)])
self.fw.append(
["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 443 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)])
self.fw.append(
["filter", "", "-A INPUT -i %s -p tcp -m tcp --dport 8080 -s %s -m state --state NEW -j ACCEPT" % (self.dev, guestNetworkCidr)])
self.fw.append(
["filter", "", "-A FORWARD -i %s -o %s -m state --state RELATED,ESTABLISHED -j ACCEPT" % (self.dev, self.dev)])
self.fw.append(
["filter", "", "-A FORWARD -i %s -o %s -m state --state NEW -j ACCEPT" % (self.dev, self.dev)])
def post_config_change(self, method):
route = CsRoute()
@ -735,6 +789,7 @@ class CsIP:
self.fw_vpcrouter()
self.fw_router_routing()
self.fw_vpcrouter_routing()
self.fw_dhcpserver()
cmdline = self.config.cmdline()

View File

@ -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'

View File

@ -142,9 +142,9 @@ class CsDhcp(CsDataBag):
else:
listen_address.append(ip)
# Add localized "data-server" records in /etc/hosts for VPC routers
if self.config.is_vpc() or self.config.is_router():
if (self.config.is_vpc() and gn.is_vr_guest_gateway()) or self.config.is_router():
self.add_host(gateway, "%s data-server" % CsHelper.get_hostname())
elif self.config.is_dhcp():
elif self.config.is_dhcp() or (self.config.is_vpc() and not gn.is_vr_guest_gateway()):
self.add_host(ip, "%s data-server" % CsHelper.get_hostname())
idx += 1

View File

@ -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(','))

View File

@ -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'

View File

@ -1547,6 +1547,7 @@
"label.newinstance": "New Instance",
"label.newname": "New name",
"label.next": "Next",
"label.nexthop": "Next hop",
"label.nfs": "NFS",
"label.nfsmountopts": "NFS mount options",
"label.nfsserver": "NFS server",
@ -2083,7 +2084,7 @@
"label.simplified.chinese.keyboard": "Simplified Chinese keyboard",
"label.site": "Netris Site",
"label.site.to.site.vpn": "Site-to-site VPN",
"label.site.to.site.vpn.connections": "Site-to-site VPN Connections",
"label.site.to.site.vpn.connections": "VPN Connections",
"label.size": "Size",
"label.sizegb": "Size",
"label.smb.domain": "SMB domain",
@ -2546,6 +2547,7 @@
"label.volumetype": "Volume Type",
"label.vpc": "VPC",
"label.vpcs": "VPCs",
"label.vpc.gateway.ip": "VPC Gateway IP",
"label.vpc.id": "VPC ID",
"label.vpc.offerings": "VPC offerings",
"label.vpc.virtual.router": "VPC virtual router",
@ -3027,7 +3029,7 @@
"message.enable.vpn": "Please confirm that you want remote access VPN enabled for this IP address.",
"message.enable.vpn.failed": "Failed to enable VPN.",
"message.enable.vpn.processing": "Enabling VPN...",
"message.enabled.vpn": "Your remote access VPN is currently enabled and can be accessed via the IP.",
"message.enabled.vpn": "Your remote access VPN is currently enabled and can be accessed via the IP",
"message.enabled.vpn.ip.sec": "Your IPSec pre-shared key is",
"message.enabling.security.group.provider": "Enabling security group provider",
"message.enter.valid.nic.ip": "Please enter a valid IP address for NIC",

View File

@ -443,7 +443,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>

View File

@ -794,7 +794,7 @@ export default {
}, {
name: 'vpn',
component: shallowRef(defineAsyncComponent(() => import('@/views/network/VpnDetails.vue'))),
show: (record) => { return record.issourcenat }
show: (record) => { return record.issourcenat || record.virtualmachinetype === 'DomainRouter' || !record.hasrules }
},
{
name: 'events',
@ -1001,7 +1001,6 @@ export default {
title: 'label.site.to.site.vpn.connections',
docHelp: 'adminguide/networking_and_traffic.html#setting-up-a-site-to-site-vpn-connection',
icon: 'sync-outlined',
hidden: true,
permission: ['listVpnConnections'],
columns: ['publicip', 'state', 'gateway', 'ipsecpsk', 'ikepolicy', 'esppolicy'],
details: ['publicip', 'gateway', 'passive', 'cidrlist', 'ipsecpsk', 'ikepolicy', 'esppolicy', 'ikelifetime', 'ikeversion', 'esplifetime', 'dpd', 'splitconnections', 'forceencap', 'created'],

View File

@ -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,

View File

@ -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'),
@ -304,7 +354,6 @@ export default {
},
openTagsModal (route) {
this.selectedRule = route
this.rulesRef.value.resetFields()
this.fetchTags(this.selectedRule)
this.tagsModalVisible = true
}
@ -368,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>

View File

@ -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

View File

@ -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) {

View File

@ -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"/>

View File

@ -1825,6 +1825,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)
*/

View File

@ -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);
}
}