mirror of https://github.com/apache/cloudstack.git
CLOUDSTACK-9359: IPv6 for Basic Networking with KVM
This commit adds the initial functionality for IPv6 in Basic Networking.
When a valid IPv6 CIDR is configured for the POD/VLAN the DirectPodBasedNetworkGuru
will use the EUI-64 calculation to calculate the IPv6 Address the Instance will obtain.
For this it is required that the physical routers in the Layer 2 network (POD/VLAN) send out
Router Advertisements with the same subnet as configured in CloudStack.
A example subnet could be 2001:db8::/64
Using radvd a Linux Router could send out Router Advertisements using this configuration:
interface eth0
{
MinRtrAdvInterval 5;
MaxRtrAdvInterval 60;
AdvSendAdvert on;
AdvOtherConfigFlag off;
IgnoreIfMissing off;
prefix 2001:db8::/64 {
};
RDNSS 2001:db8:ffff::53 {
};
};
A Instance with MAC Address 06:7a:88:00:00:8b will obtain IPv6 address 2001:db8:100::47a:88ff:fe00:8b
Both Windows, Linux and FreeBSD use the same calculation for their IPv6 Addresses, this is specified
in RFC4862 (IPv6 Stateless Address Autoconfiguration).
Under Linux it is mandatory that IPv6 Privacy Extensions are disabled:
$ sysctl -w net.ipv6.conf.all.use_tempaddr=0
Windows should be configured to use the MAC Address as the identifier for the EUI-64/SLAAC calculation.
$ netsh interface ipv6 set privacy state=disabled store=persistent
$ netsh interface ipv6 set global randomizeidentifiers=disabled store=persistent
The IPv6 address is stored in the 'nics' table and is then returned by the API and will be shown in the UI.
Searching for a conflicting IPv6 Address it NOT required as each IPv6 address is based on the MAC Address
of the Instance and therefor unique.
Security Grouping has not been implemented yet and will follow in a upcoming commit.
Signed-off-by: Wido den Hollander <wido@widodh.nl>
This commit is contained in:
parent
9513053f42
commit
c0e7766713
|
|
@ -38,6 +38,8 @@ public class NetworkTO {
|
|||
protected URI isolationUri;
|
||||
protected boolean isSecurityGroupEnabled;
|
||||
protected String name;
|
||||
protected String ip6address;
|
||||
protected String ip6cidr;
|
||||
|
||||
public NetworkTO() {
|
||||
}
|
||||
|
|
@ -62,6 +64,14 @@ public class NetworkTO {
|
|||
this.ip = ip;
|
||||
}
|
||||
|
||||
public void setIp6Address(String addr) {
|
||||
this.ip6address = addr;
|
||||
}
|
||||
|
||||
public void setIp6Cidr(String cidr) {
|
||||
this.ip6cidr = cidr;
|
||||
}
|
||||
|
||||
public void setNetmask(String netmask) {
|
||||
this.netmask = netmask;
|
||||
}
|
||||
|
|
@ -114,6 +124,7 @@ public class NetworkTO {
|
|||
* the full information about what is needed.
|
||||
*
|
||||
* @param ip
|
||||
* @param ip6address
|
||||
* @param vlan
|
||||
* @param netmask
|
||||
* @param mac
|
||||
|
|
@ -130,10 +141,30 @@ public class NetworkTO {
|
|||
this.dns2 = dns2;
|
||||
}
|
||||
|
||||
public NetworkTO(String ip, String netmask, String mac, String gateway, String dns1, String dns2, String ip6address,
|
||||
String ip6cidr) {
|
||||
this.ip = ip;
|
||||
this.netmask = netmask;
|
||||
this.mac = mac;
|
||||
this.gateway = gateway;
|
||||
this.dns1 = dns1;
|
||||
this.dns2 = dns2;
|
||||
this.ip6address = ip6address;
|
||||
this.ip6cidr = ip6cidr;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public String getIp6Address() {
|
||||
return ip6address;
|
||||
}
|
||||
|
||||
public String getIp6Cidr() {
|
||||
return ip6cidr;
|
||||
}
|
||||
|
||||
public String getNetmask() {
|
||||
return netmask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
|
|||
to.setNetworkRateMbps(profile.getNetworkRate());
|
||||
to.setName(profile.getName());
|
||||
to.setSecurityGroupEnabled(profile.isSecurityGroupEnabled());
|
||||
to.setIp6Address(profile.getIPv6Address());
|
||||
to.setIp6Cidr(profile.getIPv6Cidr());
|
||||
|
||||
NetworkVO network = _networkDao.findById(profile.getNetworkId());
|
||||
to.setNetworkUuid(network.getUuid());
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.cloud.dc.DataCenterVO;
|
|||
import com.cloud.dc.Pod;
|
||||
import com.cloud.dc.PodVlanMapVO;
|
||||
import com.cloud.dc.Vlan;
|
||||
import com.cloud.dc.VlanVO;
|
||||
import com.cloud.dc.Vlan.VlanType;
|
||||
import com.cloud.dc.dao.DataCenterDao;
|
||||
import com.cloud.dc.dao.PodVlanMapDao;
|
||||
|
|
@ -54,12 +55,14 @@ import com.cloud.utils.db.TransactionCallbackNoReturn;
|
|||
import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn;
|
||||
import com.cloud.utils.db.TransactionStatus;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.net.NetUtils;
|
||||
import com.cloud.vm.Nic;
|
||||
import com.cloud.vm.Nic.ReservationStrategy;
|
||||
import com.cloud.vm.NicProfile;
|
||||
import com.cloud.vm.ReservationContext;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
import com.googlecode.ipv6.IPv6Address;
|
||||
|
||||
public class DirectPodBasedNetworkGuru extends DirectNetworkGuru {
|
||||
private static final Logger s_logger = Logger.getLogger(DirectPodBasedNetworkGuru.class);
|
||||
|
|
@ -166,54 +169,81 @@ public class DirectPodBasedNetworkGuru extends DirectNetworkGuru {
|
|||
protected void getIp(final NicProfile nic, final Pod pod, final VirtualMachineProfile vm, final Network network) throws InsufficientVirtualNetworkCapacityException,
|
||||
InsufficientAddressCapacityException, ConcurrentOperationException {
|
||||
final DataCenter dc = _dcDao.findById(pod.getDataCenterId());
|
||||
if (nic.getIPv4Address() == null) {
|
||||
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<InsufficientAddressCapacityException>() {
|
||||
@Override
|
||||
public void doInTransactionWithoutResult(TransactionStatus status) throws InsufficientAddressCapacityException {
|
||||
PublicIp ip = null;
|
||||
List<PodVlanMapVO> podRefs = _podVlanDao.listPodVlanMapsByPod(pod.getId());
|
||||
String podRangeGateway = null;
|
||||
if (!podRefs.isEmpty()) {
|
||||
podRangeGateway = _vlanDao.findById(podRefs.get(0).getVlanDbId()).getVlanGateway();
|
||||
}
|
||||
//Get ip address from the placeholder and don't allocate a new one
|
||||
if (vm.getType() == VirtualMachine.Type.DomainRouter) {
|
||||
Nic placeholderNic = _networkModel.getPlaceholderNicForRouter(network, pod.getId());
|
||||
if (placeholderNic != null) {
|
||||
IPAddressVO userIp = _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), placeholderNic.getIPv4Address());
|
||||
ip = PublicIp.createFromAddrAndVlan(userIp, _vlanDao.findById(userIp.getVlanId()));
|
||||
s_logger.debug("Nic got an ip address " + placeholderNic.getIPv4Address() + " stored in placeholder nic for the network " + network +
|
||||
" and gateway " + podRangeGateway);
|
||||
VlanVO vlan = _vlanDao.findById(podRefs.get(0).getVlanDbId());
|
||||
|
||||
if (nic.getIPv4Address() == null) {
|
||||
String podRangeGateway = null;
|
||||
if (!podRefs.isEmpty()) {
|
||||
podRangeGateway = vlan.getVlanGateway();
|
||||
}
|
||||
}
|
||||
|
||||
if (ip == null) {
|
||||
ip = _ipAddrMgr.assignPublicIpAddress(dc.getId(), pod.getId(), vm.getOwner(), VlanType.DirectAttached, network.getId(), null, false);
|
||||
}
|
||||
|
||||
nic.setIPv4Address(ip.getAddress().toString());
|
||||
nic.setFormat(AddressFormat.Ip4);
|
||||
nic.setIPv4Gateway(ip.getGateway());
|
||||
nic.setIPv4Netmask(ip.getNetmask());
|
||||
if (ip.getVlanTag() != null && ip.getVlanTag().equalsIgnoreCase(Vlan.UNTAGGED)) {
|
||||
nic.setIsolationUri(IsolationType.Ec2.toUri(Vlan.UNTAGGED));
|
||||
nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(Vlan.UNTAGGED));
|
||||
nic.setBroadcastType(BroadcastDomainType.Native);
|
||||
}
|
||||
nic.setReservationId(String.valueOf(ip.getVlanTag()));
|
||||
nic.setMacAddress(ip.getMacAddress());
|
||||
|
||||
//save the placeholder nic if the vm is the Virtual router
|
||||
if (vm.getType() == VirtualMachine.Type.DomainRouter) {
|
||||
Nic placeholderNic = _networkModel.getPlaceholderNicForRouter(network, pod.getId());
|
||||
if (placeholderNic == null) {
|
||||
s_logger.debug("Saving placeholder nic with ip4 address " + nic.getIPv4Address() + " for the network " + network);
|
||||
_networkMgr.savePlaceholderNic(network, nic.getIPv4Address(), null, VirtualMachine.Type.DomainRouter);
|
||||
//Get ip address from the placeholder and don't allocate a new one
|
||||
if (vm.getType() == VirtualMachine.Type.DomainRouter) {
|
||||
Nic placeholderNic = _networkModel.getPlaceholderNicForRouter(network, pod.getId());
|
||||
if (placeholderNic != null) {
|
||||
IPAddressVO userIp = _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), placeholderNic.getIPv4Address());
|
||||
ip = PublicIp.createFromAddrAndVlan(userIp, _vlanDao.findById(userIp.getVlanId()));
|
||||
s_logger.debug("Nic got an ip address " + placeholderNic.getIPv4Address() + " stored in placeholder nic for the network " + network +
|
||||
" and gateway " + podRangeGateway);
|
||||
}
|
||||
}
|
||||
|
||||
if (ip == null) {
|
||||
ip = _ipAddrMgr.assignPublicIpAddress(dc.getId(), pod.getId(), vm.getOwner(), VlanType.DirectAttached, network.getId(), null, false);
|
||||
}
|
||||
|
||||
nic.setIPv4Address(ip.getAddress().toString());
|
||||
nic.setFormat(AddressFormat.Ip4);
|
||||
nic.setIPv4Gateway(ip.getGateway());
|
||||
nic.setIPv4Netmask(ip.getNetmask());
|
||||
if (ip.getVlanTag() != null && ip.getVlanTag().equalsIgnoreCase(Vlan.UNTAGGED)) {
|
||||
nic.setIsolationUri(IsolationType.Ec2.toUri(Vlan.UNTAGGED));
|
||||
nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(Vlan.UNTAGGED));
|
||||
nic.setBroadcastType(BroadcastDomainType.Native);
|
||||
}
|
||||
nic.setReservationId(String.valueOf(ip.getVlanTag()));
|
||||
nic.setMacAddress(ip.getMacAddress());
|
||||
|
||||
//save the placeholder nic if the vm is the Virtual router
|
||||
if (vm.getType() == VirtualMachine.Type.DomainRouter) {
|
||||
Nic placeholderNic = _networkModel.getPlaceholderNicForRouter(network, pod.getId());
|
||||
if (placeholderNic == null) {
|
||||
s_logger.debug("Saving placeholder nic with ip4 address " + nic.getIPv4Address() + " for the network " + network);
|
||||
_networkMgr.savePlaceholderNic(network, nic.getIPv4Address(), null, VirtualMachine.Type.DomainRouter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Using Router Advertisements the routers in the network should announce the IPv6 CIDR which is configured
|
||||
* in in the vlan table in the database.
|
||||
*
|
||||
* This way the NIC will be populated with a IPv6 address on which the Instance is reachable.
|
||||
*/
|
||||
if (vlan.getIp6Cidr() != null) {
|
||||
if (nic.getIPv6Address() == null) {
|
||||
s_logger.debug("Found IPv6 CIDR " + vlan.getIp6Cidr() + " for VLAN " + vlan.getId());
|
||||
nic.setIPv6Cidr(vlan.getIp6Cidr());
|
||||
nic.setIPv6Gateway(vlan.getIp6Gateway());
|
||||
|
||||
IPv6Address ipv6addr = NetUtils.EUI64Address(vlan.getIp6Cidr(), nic.getMacAddress());
|
||||
s_logger.info("Calculated IPv6 address " + ipv6addr + " using EUI-64 for NIC " + nic.getUuid());
|
||||
nic.setIPv6Address(ipv6addr.toString());
|
||||
}
|
||||
} else {
|
||||
s_logger.debug("No IPv6 CIDR configured for VLAN " + vlan.getId());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
nic.setIPv4Dns1(dc.getDns1());
|
||||
nic.setIPv4Dns2(dc.getDns2());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1578,5 +1578,24 @@ public class NetUtils {
|
|||
return !isInRange;
|
||||
}
|
||||
|
||||
public static IPv6Address EUI64Address(final IPv6Network cidr, final String macAddress) {
|
||||
if (cidr.getNetmask().asPrefixLength() > 64) {
|
||||
throw new IllegalArgumentException("IPv6 subnet " + cidr.toString() + " is not 64 bits or larger in size");
|
||||
}
|
||||
|
||||
String mac[] = macAddress.toLowerCase().split(":");
|
||||
|
||||
return IPv6Address.fromString(cidr.getFirst().toString() +
|
||||
Integer.toHexString(Integer.parseInt(mac[0], 16) ^ 2) +
|
||||
mac[1] + ":" + mac[2] + "ff:fe" + mac[3] +":" + mac[4] + mac[5]);
|
||||
}
|
||||
|
||||
public static IPv6Address EUI64Address(final String cidr, final String macAddress) {
|
||||
return EUI64Address(IPv6Network.fromString(cidr), macAddress);
|
||||
}
|
||||
|
||||
public static IPv6Address ipv6LinkLocal(final String macAddress) {
|
||||
return EUI64Address(IPv6Network.fromString("fe80::/64"), macAddress);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -526,4 +526,25 @@ public class NetUtilsTest {
|
|||
assertFalse(NetUtils.isNetworkorBroadcastIP("192.168.0.63","255.255.255.128"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6EUI64Address() {
|
||||
assertEquals(IPv6Address.fromString("2001:db8:100::47a:88ff:fe00:8b"),
|
||||
NetUtils.EUI64Address("2001:db8:100::/64", "06:7a:88:00:00:8b"));
|
||||
|
||||
assertEquals(IPv6Address.fromString("2a00:f10:121:b00:434:a0ff:fe00:1bc7"),
|
||||
NetUtils.EUI64Address("2a00:f10:121:b00::/64", "06:34:a0:00:1b:c7"));
|
||||
|
||||
assertEquals(IPv6Address.fromString("2001:980:7936:0:ea2a:eaff:fe58:eb98"),
|
||||
NetUtils.EUI64Address("2001:980:7936::/64", "e8:2a:ea:58:eb:98"));
|
||||
|
||||
assertEquals(IPv6Address.fromString("2001:980:7936:0:c23f:d5ff:fe68:2808"),
|
||||
NetUtils.EUI64Address("2001:980:7936::/64", "c0:3f:d5:68:28:08"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6LinkLocal() {
|
||||
assertEquals(IPv6Address.fromString("fe80::fc54:ff:fe00:3e05"), NetUtils.ipv6LinkLocal("fe:54:00:00:3e:05"));
|
||||
assertEquals(IPv6Address.fromString("fe80::42:e0ff:fee8:d6a3"), NetUtils.ipv6LinkLocal("02:42:e0:e8:d6:a3"));
|
||||
assertEquals(IPv6Address.fromString("fe80::47a:88ff:fe00:8b"), NetUtils.ipv6LinkLocal("06:7a:88:00:00:8b"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue