ipv6: Calculate IPv6 address instead of fetching one from a pool (#3077)

With IPv6 we are not using DHCP to allocate addresses, but using
StateLess Address Auto Configuration (SLAAC) a Instance will calculate
it's own address based on the Router Advertisements send out by the
routers in the network.

This Advertisement contains the IPv6 Subnet in use in that subnet and
allows to calculate the stable Address the Instance will obtain based
on it's MAC Address.

The existing code is 'dead code' as it has been written, but was never
used by any production code.

SLAAC only works properly with subnets of exactly 64-bits large.

Signed-off-by: Wido den Hollander <wido@widodh.nl>
This commit is contained in:
Wido den Hollander 2019-01-16 19:49:02 +01:00 committed by Gabriel Beims Bräscher
parent 093ab722b3
commit af9fefc6b4
6 changed files with 71 additions and 156 deletions

View File

@ -2257,8 +2257,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
}
}
if (ipv6 && !NetUtils.isValidIp6Cidr(ip6Cidr)) {
throw new InvalidParameterValueException("Invalid IPv6 cidr specified");
if (ipv6 && NetUtils.getIp6CidrSize(ip6Cidr) != 64) {
throw new InvalidParameterValueException("IPv6 subnet should be exactly 64-bits in size");
}
//TODO(VXLAN): Support VNI specified

View File

@ -57,7 +57,6 @@ import com.cloud.dc.DataCenterIpAddressVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.Pod;
import com.cloud.dc.PodVlanMapVO;
import com.cloud.dc.Vlan;
import com.cloud.dc.Vlan.VlanType;
import com.cloud.dc.VlanVO;
import com.cloud.dc.dao.AccountVlanMapDao;
@ -2014,7 +2013,6 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
if (network.getGateway() != null) {
if (nic.getIPv4Address() == null) {
ipv4 = true;
PublicIp ip = null;
//Get ip address from the placeholder and don't allocate a new one
@ -2050,30 +2048,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
nic.setIPv4Dns2(dc.getDns2());
}
//FIXME - get ipv6 address from the placeholder if it's stored there
if (network.getIp6Gateway() != null) {
if (nic.getIPv6Address() == null) {
UserIpv6Address ip = _ipv6Mgr.assignDirectIp6Address(dc.getId(), vm.getOwner(), network.getId(), requestedIpv6);
Vlan vlan = _vlanDao.findById(ip.getVlanId());
nic.setIPv6Address(ip.getAddress().toString());
nic.setIPv6Gateway(vlan.getIp6Gateway());
nic.setIPv6Cidr(vlan.getIp6Cidr());
if (ipv4) {
nic.setFormat(AddressFormat.DualStack);
} else {
nic.setIsolationUri(IsolationType.Vlan.toUri(vlan.getVlanTag()));
nic.setBroadcastType(BroadcastDomainType.Vlan);
nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(vlan.getVlanTag()));
nic.setFormat(AddressFormat.Ip6);
nic.setReservationId(String.valueOf(vlan.getVlanTag()));
if(nic.getMacAddress() == null) {
nic.setMacAddress(ip.getMacAddress());
}
}
}
nic.setIPv6Dns1(dc.getIp6Dns1());
nic.setIPv6Dns2(dc.getIp6Dns2());
}
_ipv6Mgr.setNicIp6Address(nic, dc, network);
}
});
}
@ -2123,29 +2098,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
nic.setIPv4Dns2(dc.getDns2());
}
// TODO: the IPv6 logic is not changed.
//FIXME - get ipv6 address from the placeholder if it's stored there
if (network.getIp6Gateway() != null) {
if (nic.getIPv6Address() == null) {
UserIpv6Address ip = _ipv6Mgr.assignDirectIp6Address(dc.getId(), vm.getOwner(), network.getId(), requestedIpv6);
Vlan vlan = _vlanDao.findById(ip.getVlanId());
nic.setIPv6Address(ip.getAddress().toString());
nic.setIPv6Gateway(vlan.getIp6Gateway());
nic.setIPv6Cidr(vlan.getIp6Cidr());
if (ipv4) {
nic.setFormat(AddressFormat.DualStack);
} else {
nic.setIsolationUri(IsolationType.Vlan.toUri(vlan.getVlanTag()));
nic.setBroadcastType(BroadcastDomainType.Vlan);
nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(vlan.getVlanTag()));
nic.setFormat(AddressFormat.Ip6);
nic.setReservationId(String.valueOf(vlan.getVlanTag()));
nic.setMacAddress(ip.getMacAddress());
}
}
nic.setIPv6Dns1(dc.getIp6Dns1());
nic.setIPv6Dns2(dc.getIp6Dns2());
}
_ipv6Mgr.setNicIp6Address(nic, dc, network);
}
});
}

View File

@ -17,20 +17,20 @@
package com.cloud.network;
import com.cloud.dc.DataCenter;
import com.cloud.exception.InsufficientAddressCapacityException;
import com.cloud.user.Account;
import com.cloud.utils.component.Manager;
import com.cloud.vm.NicProfile;
public interface Ipv6AddressManager extends Manager {
public UserIpv6Address assignDirectIp6Address(long dcId, Account owner, Long networkId, String requestedIp6) throws InsufficientAddressCapacityException;
public void revokeDirectIpv6Address(long networkId, String ip6Address);
public String allocateGuestIpv6(Network network, String requestedIpv6) throws InsufficientAddressCapacityException;
public String allocatePublicIp6ForGuestNic(Network network, Long podId, Account ipOwner, String requestedIp) throws InsufficientAddressCapacityException;
public String acquireGuestIpv6Address(Network network, String requestedIpv6) throws InsufficientAddressCapacityException;
public void setNicIp6Address(final NicProfile nic, final DataCenter dc, final Network network);
}

View File

@ -17,20 +17,18 @@
package com.cloud.network;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.vm.NicProfile;
import com.googlecode.ipv6.IPv6Address;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.log4j.Logger;
import com.cloud.configuration.Config;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.Vlan;
import com.cloud.dc.VlanVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.VlanDao;
import com.cloud.exception.InsufficientAddressCapacityException;
@ -39,13 +37,11 @@ import com.cloud.network.IpAddress.State;
import com.cloud.network.Network.IpAddresses;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.UserIpv6AddressDao;
import com.cloud.user.Account;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.DB;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.dao.NicSecondaryIpDao;
import com.cloud.vm.dao.NicSecondaryIpVO;
@ -65,8 +61,6 @@ public class Ipv6AddressManagerImpl extends ManagerBase implements Ipv6AddressMa
@Inject
UserIpv6AddressDao _ipv6Dao;
@Inject
NetworkDao _networkDao;
@Inject
ConfigurationDao _configDao;
@Inject
IpAddressManager ipAddressManager;
@ -83,87 +77,6 @@ public class Ipv6AddressManagerImpl extends ManagerBase implements Ipv6AddressMa
return true;
}
@Override
public UserIpv6Address assignDirectIp6Address(long dcId, Account owner, Long networkId, String requestedIp6) throws InsufficientAddressCapacityException {
Network network = _networkDao.findById(networkId);
if (network == null) {
return null;
}
List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(networkId);
if (vlans == null) {
s_logger.debug("Cannot find related vlan attached to network " + networkId);
return null;
}
String ip = null;
Vlan ipVlan = null;
if (requestedIp6 == null) {
if (!_networkModel.areThereIPv6AddressAvailableInNetwork(networkId)) {
throw new InsufficientAddressCapacityException("There is no more address available in the network " + network.getName(), DataCenter.class,
network.getDataCenterId());
}
for (Vlan vlan : vlans) {
if (!_networkModel.isIP6AddressAvailableInVlan(vlan.getId())) {
continue;
}
ip = NetUtils.getIp6FromRange(vlan.getIp6Range());
int count = 0;
while (_ipv6Dao.findByNetworkIdAndIp(networkId, ip) != null) {
ip = NetUtils.getNextIp6InRange(ip, vlan.getIp6Range());
count++;
// It's an arbitrate number to prevent the infinite loop
if (count > _ipv6RetryMax) {
ip = null;
break;
}
}
if (ip != null) {
ipVlan = vlan;
}
}
if (ip == null) {
throw new InsufficientAddressCapacityException("Cannot find a usable IP in the network " + network.getName() + " after " + _ipv6RetryMax +
"(network.ipv6.search.retry.max) times retry!", DataCenter.class, network.getDataCenterId());
}
} else {
for (Vlan vlan : vlans) {
if (NetUtils.isIp6InRange(requestedIp6, vlan.getIp6Range())) {
ipVlan = vlan;
break;
}
}
if (ipVlan == null) {
throw new CloudRuntimeException("Requested IPv6 is not in the predefined range!");
}
ip = requestedIp6;
if (_ipv6Dao.findByNetworkIdAndIp(networkId, ip) != null) {
throw new CloudRuntimeException("The requested IP is already taken!");
}
}
DataCenterVO dc = _dcDao.findById(dcId);
Long mac = dc.getMacAddress();
Long nextMac = mac + 1;
dc.setMacAddress(nextMac);
_dcDao.update(dc.getId(), dc);
String macAddress = NetUtils.long2Mac(NetUtils.createSequenceBasedMacAddress(mac, NetworkModel.MACIdentifier.value()));
UserIpv6AddressVO ipVO = new UserIpv6AddressVO(ip, dcId, macAddress, ipVlan.getId());
ipVO.setPhysicalNetworkId(network.getPhysicalNetworkId());
ipVO.setSourceNetworkId(networkId);
ipVO.setState(UserIpv6Address.State.Allocated);
ipVO.setDomainId(owner.getDomainId());
ipVO.setAccountId(owner.getAccountId());
_ipv6Dao.persist(ipVO);
return ipVO;
}
@Override
public void revokeDirectIpv6Address(long networkId, String ip6Address) {
UserIpv6AddressVO ip = _ipv6Dao.findByNetworkIdAndIp(networkId, ip6Address);
if (ip != null) {
_ipv6Dao.remove(ip.getId());
}
}
/**
* Executes method {@link #acquireGuestIpv6Address(Network, String)} and returns the requested IPv6 (String) in case of successfully allocating the guest IPv6 address.
*/
@ -260,4 +173,43 @@ public class Ipv6AddressManagerImpl extends ManagerBase implements Ipv6AddressMa
return ip6Vo != null || nicSecondaryIpVO != null;
}
/**
* Calculate the IPv6 Address the Instance will obtain using SLAAC and IPv6 EUI-64
*
* Linux, FreeBSD and Windows all calculate the same IPv6 address when configured properly. (SLAAC)
*
* Using Router Advertisements the routers in the network should announce the IPv6 CIDR which is configured
* for the network.
*
* It is up to the network administrator to make sure the IPv6 Routers in the network are sending out Router Advertisements
* with the correct IPv6 (Prefix, DNS, Lifetime) information.
*
* This way the NIC will be populated with a IPv6 address on which the Instance is reachable.
*
* This method calculates the IPv6 address the Instance will obtain and updates the Nic object with the correct
* address information.
*/
@Override
public void setNicIp6Address(final NicProfile nic, final DataCenter dc, final Network network) {
if (network.getIp6Gateway() != null) {
if (nic.getIPv6Address() == null) {
s_logger.debug("Found IPv6 CIDR " + network.getIp6Cidr() + " for Network " + network);
nic.setIPv6Cidr(network.getIp6Cidr());
nic.setIPv6Gateway(network.getIp6Gateway());
IPv6Address ipv6addr = NetUtils.EUI64Address(network.getIp6Cidr(), nic.getMacAddress());
s_logger.info("Calculated IPv6 address " + ipv6addr + " using EUI-64 for NIC " + nic.getUuid());
nic.setIPv6Address(ipv6addr.toString());
if (nic.getIPv4Address() != null) {
nic.setFormat(Networks.AddressFormat.DualStack);
} else {
nic.setFormat(Networks.AddressFormat.Ip6);
}
}
nic.setIPv6Dns1(dc.getIp6Dns1());
nic.setIPv6Dns2(dc.getIp6Dns2());
}
}
}

View File

@ -39,7 +39,6 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.network.IpAddressManager;
import com.cloud.network.Ipv6AddressManager;
import com.cloud.network.Network;
import com.cloud.network.Network.GuestType;
import com.cloud.network.Network.Service;
@ -55,9 +54,7 @@ import com.cloud.network.PhysicalNetwork.IsolationMethod;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.UserIpv6AddressDao;
import com.cloud.offering.NetworkOffering;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.user.Account;
import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.db.DB;
@ -94,12 +91,6 @@ public class DirectNetworkGuru extends AdapterBase implements NetworkGuru {
@Inject
IPAddressDao _ipAddressDao;
@Inject
NetworkOfferingDao _networkOfferingDao;
@Inject
UserIpv6AddressDao _ipv6Dao;
@Inject
Ipv6AddressManager _ipv6Mgr;
@Inject
NicSecondaryIpDao _nicSecondaryIpDao;
@Inject
NicDao _nicDao;
@ -367,9 +358,6 @@ public class DirectNetworkGuru extends AdapterBase implements NetworkGuru {
}
}
if (nic.getIPv6Address() != null) {
_ipv6Mgr.revokeDirectIpv6Address(nic.getNetworkId(), nic.getIPv6Address());
}
nic.deallocate();
}

View File

@ -19,6 +19,8 @@ package com.cloud.network;
import static org.mockito.Mockito.mock;
import com.cloud.dc.DataCenter;
import com.cloud.vm.NicProfile;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -226,4 +228,24 @@ public class Ipv6AddressManagerTest {
Mockito.when(ipVo.getState()).thenReturn(state);
}
@Test
public void setNICIPv6AddressTest() {
NicProfile nic = new NicProfile();
Network network = mock(Network.class);
DataCenter dc = mock(DataCenter.class);
nic.setMacAddress("1e:00:b1:00:0a:f6");
Mockito.when(network.getIp6Cidr()).thenReturn("2001:db8:100::/64");
Mockito.when(network.getIp6Gateway()).thenReturn("2001:db8:100::1");
Mockito.when(dc.getIp6Dns1()).thenReturn("2001:db8::53:1");
Mockito.when(dc.getIp6Dns1()).thenReturn("2001:db8::53:2");
String expected = "2001:db8:100:0:1c00:b1ff:fe00:af6";
ip6Manager.setNicIp6Address(nic, dc, network);
Assert.assertEquals(expected, nic.getIPv6Address());
}
}