Commit 6272d68e authored by Xavier Thompson's avatar Xavier Thompson

Slapformat: IPv6 range for partitions and tun

See merge request nexedi/slapos.core!455
parents 7e2f8eec c4e86b91
...@@ -125,7 +125,7 @@ class FormatCommand(ConfigCommand): ...@@ -125,7 +125,7 @@ class FormatCommand(ConfigCommand):
try: try:
conf.setConfig() conf.setConfig()
except UsageError as err: except UsageError as err:
sys.stderr.write(err.message + '\n') sys.stderr.write(str(err) + '\n')
sys.stderr.write("For help use --help\n") sys.stderr.write("For help use --help\n")
sys.exit(1) sys.exit(1)
......
...@@ -58,7 +58,11 @@ import six ...@@ -58,7 +58,11 @@ import six
import lxml.etree import lxml.etree
import xml_marshaller.xml_marshaller import xml_marshaller.xml_marshaller
from slapos.util import dumps, mkdir_p, ipv6FromBin, binFromIpv6, lenNetmaskIpv6 from slapos.util import (dumps, mkdir_p, ipv6FromBin, binFromIpv6,
lenNetmaskIpv6, getPartitionIpv6Addr,
getPartitionIpv6Range, getTapIpv6Range,
getTunIpv6Range, netmaskFromLenIPv6,
getIpv6RangeFirstAddr)
import slapos.slap as slap import slapos.slap as slap
from slapos import version from slapos import version
from slapos import manager as slapmanager from slapos import manager as slapmanager
...@@ -243,7 +247,7 @@ class Computer(object): ...@@ -243,7 +247,7 @@ class Computer(object):
"""Object representing the computer""" """Object representing the computer"""
def __init__(self, reference, interface=None, addr=None, netmask=None, def __init__(self, reference, interface=None, addr=None, netmask=None,
ipv6_interface=None, software_user='slapsoft', ipv6_interface=None, partition_has_ipv6_range=None, software_user='slapsoft',
tap_gateway_interface=None, tap_ipv6=None, tap_gateway_interface=None, tap_ipv6=None,
instance_root=None, software_root=None, instance_storage_home=None, instance_root=None, software_root=None, instance_storage_home=None,
partition_list=None, config=None): partition_list=None, config=None):
...@@ -260,6 +264,7 @@ class Computer(object): ...@@ -260,6 +264,7 @@ class Computer(object):
self.address = addr self.address = addr
self.netmask = netmask self.netmask = netmask
self.ipv6_interface = ipv6_interface self.ipv6_interface = ipv6_interface
self.partition_has_ipv6_range = partition_has_ipv6_range
self.software_user = software_user self.software_user = software_user
self.tap_gateway_interface = tap_gateway_interface self.tap_gateway_interface = tap_gateway_interface
self.tap_ipv6 = tap_ipv6 self.tap_ipv6 = tap_ipv6
...@@ -410,7 +415,7 @@ class Computer(object): ...@@ -410,7 +415,7 @@ class Computer(object):
archive.writestr(saved_filename, xml_content, zipfile.ZIP_DEFLATED) archive.writestr(saved_filename, xml_content, zipfile.ZIP_DEFLATED)
@classmethod @classmethod
def load(cls, path_to_xml, reference, ipv6_interface, tap_gateway_interface, def load(cls, path_to_xml, reference, ipv6_interface, partition_has_ipv6_range, tap_gateway_interface,
tap_ipv6, instance_root=None, software_root=None, config=None): tap_ipv6, instance_root=None, software_root=None, config=None):
""" """
Create a computer object from a valid xml file. Create a computer object from a valid xml file.
...@@ -431,6 +436,7 @@ class Computer(object): ...@@ -431,6 +436,7 @@ class Computer(object):
addr=dumped_dict['address'], addr=dumped_dict['address'],
netmask=dumped_dict['netmask'], netmask=dumped_dict['netmask'],
ipv6_interface=ipv6_interface, ipv6_interface=ipv6_interface,
partition_has_ipv6_range=partition_has_ipv6_range,
software_user=dumped_dict.get('software_user', 'slapsoft'), software_user=dumped_dict.get('software_user', 'slapsoft'),
tap_gateway_interface=tap_gateway_interface, tap_gateway_interface=tap_gateway_interface,
tap_ipv6=tap_ipv6, tap_ipv6=tap_ipv6,
...@@ -460,13 +466,28 @@ class Computer(object): ...@@ -460,13 +466,28 @@ class Computer(object):
else: else:
tap = Tap(partition_dict['reference']) tap = Tap(partition_dict['reference'])
if partition_dict.get('tun') is not None and partition_dict['tun'].get('ipv4_addr') is not None: tun_dict = partition_dict.get('tun')
tun = Tun(partition_dict['tun']['name'], partition_index, partition_amount) if tun_dict is None:
tun.ipv4_addr = partition_dict['tun']['ipv4_addr'] if config.create_tun:
tun = Tun("slaptun" + str(partition_index), partition_index, partition_amount, config.tun_ipv6)
else:
tun = None
else: else:
tun = Tun("slaptun" + str(partition_index), partition_index, partition_amount) ipv4_addr = tun_dict.get('ipv4_addr')
ipv6_addr = tun_dict.get('ipv6_addr')
needs_ipv6 = not ipv6_addr and config.tun_ipv6
tun = Tun(tun_dict['name'], partition_index, partition_amount, needs_ipv6)
if ipv4_addr:
tun.ipv4_addr = ipv4_addr
tun.ipv4_netmask = tun_dict['ipv4_netmask']
tun.ipv4_network = tun_dict['ipv4_network']
if ipv6_addr:
tun.ipv6_addr = ipv6_addr
tun.ipv6_netmask = tun_dict['ipv6_netmask']
tun.ipv6_network = tun_dict['ipv6_network']
address_list = partition_dict['address_list'] address_list = partition_dict['address_list']
ipv6_range = partition_dict.get('ipv6_range', {})
external_storage_list = partition_dict.get('external_storage_list', []) external_storage_list = partition_dict.get('external_storage_list', [])
partition = Partition( partition = Partition(
...@@ -474,8 +495,9 @@ class Computer(object): ...@@ -474,8 +495,9 @@ class Computer(object):
path=partition_dict['path'], path=partition_dict['path'],
user=user, user=user,
address_list=address_list, address_list=address_list,
ipv6_range=ipv6_range,
tap=tap, tap=tap,
tun=tun if config.create_tun else None, tun=tun,
external_storage_list=external_storage_list, external_storage_list=external_storage_list,
) )
...@@ -562,7 +584,7 @@ class Computer(object): ...@@ -562,7 +584,7 @@ class Computer(object):
#################### ####################
if alter_network: if alter_network:
if self.address is not None: if self.address is not None:
self.interface.addIPv6Address(self.address, self.netmask) self.interface._addSystemAddress(self.address, self.netmask)
if create_tap and self.tap_gateway_interface: if create_tap and self.tap_gateway_interface:
gateway_addr_dict = getIfaceAddressIPv4(self.tap_gateway_interface) gateway_addr_dict = getIfaceAddressIPv4(self.tap_gateway_interface)
...@@ -571,6 +593,9 @@ class Computer(object): ...@@ -571,6 +593,9 @@ class Computer(object):
len(self.partition_list)) len(self.partition_list))
assert(len(self.partition_list) <= len(tap_address_list)) assert(len(self.partition_list) <= len(tap_address_list))
if self.partition_has_ipv6_range:
self.interface.allowNonlocalBind()
self._speedHackAddAllOldIpsToInterface() self._speedHackAddAllOldIpsToInterface()
try: try:
...@@ -614,23 +639,22 @@ class Computer(object): ...@@ -614,23 +639,22 @@ class Computer(object):
if self.tap_ipv6: if self.tap_ipv6:
if not partition.tap.ipv6_addr: if not partition.tap.ipv6_addr:
# create a new IPv6 randomly for the tap # create a new IPv6 randomly for the tap
ipv6_dict = self.interface.addIPv6Address(tap=partition.tap) ipv6_dict = self.interface.addIPv6Address(partition_index, tap=partition.tap)
partition.tap.ipv6_addr = ipv6_dict['addr'] partition.tap.ipv6_addr = ipv6_dict['addr']
partition.tap.ipv6_netmask = ipv6_dict['netmask'] partition.tap.ipv6_netmask = ipv6_dict['netmask']
else: else:
# make sure the tap has its IPv6 # make sure the tap has its IPv6
self.interface.addIPv6Address( self.interface.addIPv6Address(
partition_index=partition_index,
addr=partition.tap.ipv6_addr, addr=partition.tap.ipv6_addr,
netmask=partition.tap.ipv6_netmask, netmask=partition.tap.ipv6_netmask,
tap=partition.tap) tap=partition.tap)
# construct ipv6_network (16 bit more than the computer network) # construct ipv6_network (16 bit more than the computer network)
netmask_len = lenNetmaskIpv6(self.interface.getGlobalScopeAddressList()[0]['netmask']) + 16 prefixlen = lenNetmaskIpv6(self.interface.getGlobalScopeAddressList()[0]['netmask']) + 16
prefix = binFromIpv6(partition.tap.ipv6_addr)[:netmask_len] gateway_addr = getIpv6RangeFirstAddr(partition.tap.ipv6_addr, prefixlen)
network_addr = ipv6FromBin(prefix) partition.tap.ipv6_gateway = gateway_addr
partition.tap.ipv6_gateway = "{}1".format(network_addr) # address network::1 will be inside the VM partition.tap.ipv6_network = "{}/{}".format(gateway_addr, prefixlen)
partition.tap.ipv6_gateway = ipv6FromBin(binFromIpv6(partition.tap.ipv6_gateway)) # correctly format the IPv6
partition.tap.ipv6_network = "{}/{}".format(network_addr, netmask_len)
else: else:
partition.tap.ipv6_addr = '' partition.tap.ipv6_addr = ''
partition.tap.ipv6_netmask = '' partition.tap.ipv6_netmask = ''
...@@ -643,45 +667,72 @@ class Computer(object): ...@@ -643,45 +667,72 @@ class Computer(object):
if partition.tun is not None: if partition.tun is not None:
# create TUN interface per partition as well # create TUN interface per partition as well
partition.tun.createWithOwner(owner) partition.tun.createWithOwner(owner)
if partition.tun._needs_ipv6:
ipv6_dict = self.interface.generateIPv6Range(partition_index, tun=True)
prefixlen = ipv6_dict['prefixlen']
ipv6_addr = getIpv6RangeFirstAddr(ipv6_dict['addr'], prefixlen)
partition.tun.ipv6_addr = ipv6_addr
partition.tun.ipv6_netmask = ipv6_dict['netmask']
partition.tun.ipv6_network = "%s/%d" % (ipv6_addr, prefixlen)
partition.tun.createRoutes() partition.tun.createRoutes()
# Reconstructing partition's address
# There should be two addresses on each Computer Partition:
# * local IPv4, took from slapformat:ipv4_local_network
# * global IPv6
if not partition.address_list:
# generate new addresses
partition.address_list.append(self.interface.addIPv4LocalAddress())
partition_ipv6_dict = self.interface.addIPv6Address(partition_index)
# Avoid leaking prefixlen in dumped data because it is not loaded
# otherwise format dumps a different result after the first run
del partition_ipv6_dict['prefixlen']
partition.address_list.append(partition_ipv6_dict)
else:
# regenerate list of addresses
old_partition_address_list = partition.address_list
partition.address_list = []
if len(old_partition_address_list) != 2:
raise ValueError(
'There should be exactly 2 stored addresses. Got: %r' %
(old_partition_address_list,))
if not any(netaddr.valid_ipv6(q['addr'])
for q in old_partition_address_list):
raise ValueError('No valid IPv6 address loaded from XML config')
if not any(netaddr.valid_ipv4(q['addr'])
for q in old_partition_address_list):
raise ValueError('No valid IPv4 address loaded from XML config')
for address in old_partition_address_list:
if netaddr.valid_ipv6(address['addr']):
partition.address_list.append(self.interface.addIPv6Address(
partition_index,
address['addr'],
address['netmask']))
elif netaddr.valid_ipv4(address['addr']):
partition.address_list.append(self.interface.addIPv4LocalAddress(
address['addr']))
else:
# should never happen since there are exactly 1 valid IPv6 and 1
# valid IPv4 in old_partition_address_list
raise ValueError('Address %r is incorrect' % address['addr'])
# Reconstructing partition's IPv6 range
if self.partition_has_ipv6_range:
if not partition.ipv6_range:
# generate new IPv6 range
partition.ipv6_range = self.interface.generateIPv6Range(partition_index)
else:
if not netaddr.valid_ipv6(partition.ipv6_range['addr']):
raise ValueError('existing IPv6 range %r is incorrect', partition.ipv6_range['addr'])
self.interface.addLocalRouteIpv6Range(partition.ipv6_range)
else:
partition.ipv6_range = {}
# Reconstructing partition's directory # Reconstructing partition's directory
partition.createPath(alter_user) partition.createPath(alter_user)
partition.createExternalPath(alter_user) partition.createExternalPath(alter_user)
# Reconstructing partition's address
# There should be two addresses on each Computer Partition:
# * local IPv4, took from slapformat:ipv4_local_network
# * global IPv6
if not partition.address_list:
# regenerate
partition.address_list.append(self.interface.addIPv4LocalAddress())
partition.address_list.append(self.interface.addIPv6Address())
elif alter_network:
# regenerate list of addresses
old_partition_address_list = partition.address_list
partition.address_list = []
if len(old_partition_address_list) != 2:
raise ValueError(
'There should be exactly 2 stored addresses. Got: %r' %
(old_partition_address_list,))
if not any(netaddr.valid_ipv6(q['addr'])
for q in old_partition_address_list):
raise ValueError('Not valid ipv6 addresses loaded')
if not any(netaddr.valid_ipv4(q['addr'])
for q in old_partition_address_list):
raise ValueError('Not valid ipv6 addresses loaded')
for address in old_partition_address_list:
if netaddr.valid_ipv6(address['addr']):
partition.address_list.append(self.interface.addIPv6Address(
address['addr'],
address['netmask']))
elif netaddr.valid_ipv4(address['addr']):
partition.address_list.append(self.interface.addIPv4LocalAddress(
address['addr']))
else:
raise ValueError('Address %r is incorrect' % address['addr'])
finally: finally:
for manager in self._manager_list: for manager in self._manager_list:
manager.formatTearDown(self) manager.formatTearDown(self)
...@@ -693,13 +744,14 @@ class Partition(object): ...@@ -693,13 +744,14 @@ class Partition(object):
resource_file = ".slapos-resource" resource_file = ".slapos-resource"
def __init__(self, reference, path, user, address_list, def __init__(self, reference, path, user, address_list,
tap, external_storage_list=[], tun=None): ipv6_range, tap, tun=None, external_storage_list=[]):
""" """
Attributes: Attributes:
reference: String, the name of the partition. reference: String, the name of the partition.
path: String, the path to the partition folder. path: String, the path to the partition folder.
user: User, the user linked to this partition. user: User, the user linked to this partition.
address_list: List of associated IP addresses. address_list: List of associated IP addresses.
ipv6_range: IPv6 range given to this partition (dict with 'addr' and 'netmask').
tap: Tap, the tap interface linked to this partition e.g. used as a gateway for kvm tap: Tap, the tap interface linked to this partition e.g. used as a gateway for kvm
tun: Tun interface used for special apps simulating ethernet connections tun: Tun interface used for special apps simulating ethernet connections
external_storage_list: Base path list of folder to format for data storage external_storage_list: Base path list of folder to format for data storage
...@@ -709,12 +761,13 @@ class Partition(object): ...@@ -709,12 +761,13 @@ class Partition(object):
self.path = str(path) self.path = str(path)
self.user = user self.user = user
self.address_list = address_list or [] self.address_list = address_list or []
self.ipv6_range = ipv6_range or {}
self.tap = tap self.tap = tap
self.tun = tun self.tun = tun
self.external_storage_list = [] self.external_storage_list = []
def __getinitargs__(self): def __getinitargs__(self):
return (self.reference, self.path, self.user, self.address_list, self.tap, self.tun) return (self.reference, self.path, self.user, self.address_list, self.ipv6_range, self.tap, self.tun)
def createPath(self, alter_user=True): def createPath(self, alter_user=True):
""" """
...@@ -925,13 +978,14 @@ class Tun(Tap): ...@@ -925,13 +978,14 @@ class Tun(Tap):
BASE_MASK = 12 BASE_MASK = 12
BASE_NETWORK = "172.16.0.0" BASE_NETWORK = "172.16.0.0"
def __init__(self, name, sequence=None, partitions=None): def __init__(self, name, sequence=None, partitions=None, needs_ipv6=False):
"""Create TUN interface with subnet according to the optional ``sequence`` number. """Create TUN interface with subnet according to the optional ``sequence`` number.
:param name: name which will appear in ``ip list`` afterwards :param name: name which will appear in ``ip list`` afterwards
:param sequence: {int} position of this TUN among all ``partitions`` :param sequence: {int} position of this TUN among all ``partitions``
""" """
super(Tun, self).__init__(name) super(Tun, self).__init__(name)
self._needs_ipv6 = needs_ipv6
if sequence is not None: if sequence is not None:
assert 0 <= sequence < partitions, "0 <= {} < {}".format(sequence, partitions) assert 0 <= sequence < partitions, "0 <= {} < {}".format(sequence, partitions)
# create base IPNetwork # create base IPNetwork
...@@ -951,16 +1005,20 @@ class Tun(Tap): ...@@ -951,16 +1005,20 @@ class Tun(Tap):
"""Extend for physical addition of network address because TAP let this on external class.""" """Extend for physical addition of network address because TAP let this on external class."""
if self.ipv4_network: if self.ipv4_network:
# add an address # add an address
code, _ = callAndRead(['ip', 'addr', 'add', self.ipv4_network, 'dev', self.name], code, _ = callAndRead(
raise_on_error=False) ['ip', 'addr', 'add', self.ipv4_network, 'dev', self.name],
raise_on_error=False)
if code == 0:
# address added to the interface - wait
time.sleep(1)
if self.ipv6_network:
# add an address
code, _ = callAndRead(
['ip', 'addr', 'add', self.ipv6_network, 'dev', self.name],
raise_on_error=False)
if code == 0: if code == 0:
# address added to the interface - wait # address added to the interface - wait
time.sleep(1) time.sleep(1)
else:
raise RuntimeError("Cannot setup address on interface {}. "
"Address is missing.".format(self.name))
# create routes
super(Tun, self).createRoutes()
class Interface(object): class Interface(object):
...@@ -975,7 +1033,8 @@ class Interface(object): ...@@ -975,7 +1033,8 @@ class Interface(object):
self._logger = logger self._logger = logger
self.name = str(name) self.name = str(name)
self.ipv4_local_network = ipv4_local_network self.ipv4_local_network = ipv4_local_network
self.ipv6_interface = ipv6_interface self.ipv6_interface = ipv6_interface or name
self._ipv6_ranges = set()
# XXX no __getinitargs__, as instances of this class are never deserialized. # XXX no __getinitargs__, as instances of this class are never deserialized.
...@@ -998,7 +1057,7 @@ class Interface(object): ...@@ -998,7 +1057,7 @@ class Interface(object):
def getGlobalScopeAddressList(self, tap=None): def getGlobalScopeAddressList(self, tap=None):
"""Returns currently configured global scope IPv6 addresses""" """Returns currently configured global scope IPv6 addresses"""
interface_name = self.ipv6_interface or self.name interface_name = self.ipv6_interface
try: try:
address_list = [ address_list = [
q q
...@@ -1008,6 +1067,8 @@ class Interface(object): ...@@ -1008,6 +1067,8 @@ class Interface(object):
except KeyError: except KeyError:
raise ValueError("%s must have at least one IPv6 address assigned" % raise ValueError("%s must have at least one IPv6 address assigned" %
interface_name) interface_name)
if not address_list:
raise NoAddressOnInterface(interface_name)
if tap: if tap:
try: try:
...@@ -1042,13 +1103,12 @@ class Interface(object): ...@@ -1042,13 +1103,12 @@ class Interface(object):
if ipv6: if ipv6:
address_string = '%s/%s' % (address, lenNetmaskIpv6(netmask)) address_string = '%s/%s' % (address, lenNetmaskIpv6(netmask))
af = socket.AF_INET6 af = socket.AF_INET6
interface_name = self.ipv6_interface or self.name interface_name = self.ipv6_interface
else: else:
af = socket.AF_INET af = socket.AF_INET
address_string = '%s/%s' % (address, netmaskToPrefixIPv4(netmask)) address_string = '%s/%s' % (address, netmaskToPrefixIPv4(netmask))
interface_name = self.name interface_name = self.name
if tap: if tap:
interface_name = tap.name interface_name = tap.name
# check if address is already took by any other interface # check if address is already took by any other interface
...@@ -1066,13 +1126,10 @@ class Interface(object): ...@@ -1066,13 +1126,10 @@ class Interface(object):
for q in netifaces.ifaddresses(interface_name)[af] for q in netifaces.ifaddresses(interface_name)[af]
]: ]:
# add an address # add an address
callAndRead(['ip', 'addr', 'add', address_string, 'dev', interface_name]) code, _ = callAndRead(['ip', 'addr', 'add', address_string, 'dev', interface_name])
# Fake success for local ipv4 if code != 0:
if not ipv6: return False
return True
# wait few moments
time.sleep(2) time.sleep(2)
# Fake success for local ipv4 # Fake success for local ipv4
...@@ -1126,7 +1183,47 @@ class Interface(object): ...@@ -1126,7 +1183,47 @@ class Interface(object):
# confirmed to be configured # confirmed to be configured
return dict(addr=addr, netmask=netmask) return dict(addr=addr, netmask=netmask)
def addIPv6Address(self, addr=None, netmask=None, tap=None): def _checkIpv6Range(self, address, prefixlen):
network = str(netaddr.IPNetwork("%s/%d" % (address, prefixlen)).cidr)
if network in self._ipv6_ranges:
self._logger.warning(
"Address range %s/%d is already attributed", address, prefixlen)
return False
return True
def _reserveIpv6Range(self, address, prefixlen):
network = str(netaddr.IPNetwork("%s/%d" % (address, prefixlen)).cidr)
assert(network not in self._ipv6_ranges)
self._ipv6_ranges.add(network)
def _tryReserveIpv6Range(self, address, prefixlen):
if self._checkIpv6Range(address, prefixlen):
self._reserveIpv6Range(address, prefixlen)
return True
return False
def _generateRandomIPv6Addr(self, address_dict):
netmask = address_dict['netmask']
netmask_len = lenNetmaskIpv6(netmask)
r = random.randint(1, 65000)
addr = ':'.join(address_dict['addr'].split(':')[:-1] + ['%x' % r])
socket.inet_pton(socket.AF_INET6, address)
return dict(addr=addr, netmask=netmask)
def _generateRandomIPv6Range(self, address_dict, suffix):
prefixlen = lenNetmaskIpv6(address_dict['netmask'])
prefix = binFromIpv6(address_dict['addr'])[:prefixlen]
prefixlen += 16
if prefixlen >= 128:
msg = 'Address range %r is too small for IPv6 subranges'
self._logger.error(msg, address_dict)
raise AddressGenerationError('%s/%d' % (address_dict['addr'], prefixlen))
addr = ipv6FromBin(prefix
+ bin(random.randint(1, 65000))[2:].zfill(16)
+ suffix * (128 - prefixlen))
return dict(addr=addr, prefixlen=prefixlen, netmask=netmaskFromLenIPv6(prefixlen))
def addIPv6Address(self, partition_index, addr=None, netmask=None, tap=None):
""" """
Adds IPv6 address to interface. Adds IPv6 address to interface.
...@@ -1136,6 +1233,9 @@ class Interface(object): ...@@ -1136,6 +1233,9 @@ class Interface(object):
address. If it is not possible (ex. because network changed), calculate new address. If it is not possible (ex. because network changed), calculate new
address. address.
If tap is specified, tap will get actually an IPv6 range (and not a single
address) 16 bits smaller than the range of the interface.
Args: Args:
addr: Wished address to be added to interface. addr: Wished address to be added to interface.
netmask: Wished netmask to be used. netmask: Wished netmask to be used.
...@@ -1150,13 +1250,10 @@ class Interface(object): ...@@ -1150,13 +1250,10 @@ class Interface(object):
NoAddressOnInterface: There's no address on the interface to construct NoAddressOnInterface: There's no address on the interface to construct
an address with. an address with.
""" """
interface_name = self.ipv6_interface or self.name interface_name = self.ipv6_interface
# Getting one address of the interface as base of the next addresses # Getting one address of the interface as base of the next addresses
interface_addr_list = self.getGlobalScopeAddressList() interface_addr_list = self.getGlobalScopeAddressList()
# No address found
if len(interface_addr_list) == 0:
raise NoAddressOnInterface(interface_name)
address_dict = interface_addr_list[0] address_dict = interface_addr_list[0]
if addr is not None: if addr is not None:
...@@ -1173,7 +1270,8 @@ class Interface(object): ...@@ -1173,7 +1270,8 @@ class Interface(object):
if dict_addr_netmask in interface_addr_list or \ if dict_addr_netmask in interface_addr_list or \
(tap and dict_addr_netmask in self.getGlobalScopeAddressList(tap=tap)): (tap and dict_addr_netmask in self.getGlobalScopeAddressList(tap=tap)):
# confirmed to be configured # confirmed to be configured
return dict_addr_netmask # return without len to keep format stable, as the first time len is not included
return dict_addr_netmask_without_len
if netmask == address_dict['netmask'] or \ if netmask == address_dict['netmask'] or \
(tap and lenNetmaskIpv6(netmask) == 128): (tap and lenNetmaskIpv6(netmask) == 128):
# same netmask, so there is a chance to add good one # same netmask, so there is a chance to add good one
...@@ -1191,39 +1289,106 @@ class Interface(object): ...@@ -1191,39 +1289,106 @@ class Interface(object):
self._logger.warning('Impossible to add old public IPv6 %s. ' self._logger.warning('Impossible to add old public IPv6 %s. '
'Generating new IPv6 address.' % addr) 'Generating new IPv6 address.' % addr)
# Try 10 times to add address, raise in case if not possible # Try to use the IPv6 mapping based on partition index
try_num = 10 address_dict['prefixlen'] = lenNetmaskIpv6(address_dict['netmask'])
netmask = address_dict['netmask']
if tap: if tap:
netmask_len = lenNetmaskIpv6(netmask) result_addr = getTapIpv6Range(address_dict, partition_index)
prefix = binFromIpv6(address_dict['addr'])[:netmask_len] # the netmask of the tap itself is always 128 bits
netmask_len += 16 result_addr['netmask'] = netmaskFromLenIPv6(128)
# we generate a subnetwork for the tap else:
# the subnetwork has 16 bits more than the interface network result_addr = getPartitionIpv6Addr(address_dict, partition_index)
# make sure we have at least 2 IPs in the subnetwork result_addr['netmask'] = netmaskFromLenIPv6(result_addr['prefixlen'])
if netmask_len >= 128: if not tap or self._checkIpv6Range(result_addr['addr'], result_addr['prefixlen']):
self._logger.error('Interface %s has netmask %s which is too big for generating IPv6 on taps.' % (interface_name, netmask)) if self._addSystemAddress(result_addr['addr'], result_addr['netmask'], tap=tap):
raise AddressGenerationError(addr) if tap:
netmask = ipv6FromBin('1'*128) # the netmask of the tap itself is always 128 bits self._reserveIpv6Range(result_addr['addr'], result_addr['prefixlen'])
return result_addr
self._logger.warning(
"Falling back to random address selection for partition %s"
" because %s/%s is already taken" % (
'%s tap' % partition_index if tap else partition_index,
result_addr['addr'],
result_addr['prefixlen'],
))
while try_num > 0: # Try 10 times to add address, raise in case if not possible
for _ in range(10):
if tap: if tap:
addr = ipv6FromBin(prefix result_addr = self._generateRandomIPv6Range(address_dict, suffix='1')
+ bin(random.randint(1, 65000))[2:].zfill(16) # the netmask of the tap itself is always 128 bits
+ '1' * (128 - netmask_len)) result_addr['netmask'] = netmaskFromLenIPv6(128)
else: else:
addr = ':'.join(address_dict['addr'].split(':')[:-1] + ['%x' % ( result_addr = self._generateRandomIPv6Addr(address_dict)
random.randint(1, 65000), )]) # Checking the validity of the IPv6 address
socket.inet_pton(socket.AF_INET6, addr) addr = result_addr['addr']
if (dict(addr=addr, netmask=netmask) not in if not tap or self._checkIpv6Range(addr, result_addr['prefixlen']):
self.getGlobalScopeAddressList(tap=tap)): if self._addSystemAddress(addr, result_addr['netmask'], tap=tap):
# Checking the validity of the IPv6 address if tap:
if self._addSystemAddress(addr, netmask, tap=tap): self._reserveIpv6Range(addr, result_addr['prefixlen'])
return dict(addr=addr, netmask=netmask) return result_addr
try_num -= 1
raise AddressGenerationError(addr) raise AddressGenerationError(addr)
def generateIPv6Range(self, i, tun=False):
"""
Generate an IPv6 range included in the IPv6 range of the interface. The IPv6 range depends on the partition index i.
Returns:
dict(addr=address, netmask=netmask, network=addr/CIDR).
Raises:
ValueError: Couldn't construct valid address with existing
one's on the interface.
NoAddressOnInterface: There's no address on the interface to construct
an address with.
"""
interface_name = self.ipv6_interface or self.name
# Getting one address of the interface as base of the next addresses
interface_addr_list = self.getGlobalScopeAddressList()
address_dict = interface_addr_list[0]
address_dict['prefixlen'] = lenNetmaskIpv6(address_dict['netmask'])
if tun:
ipv6_range = getTunIpv6Range(address_dict, i)
else:
ipv6_range = getPartitionIpv6Range(address_dict, i)
ipv6_range['netmask'] = netmaskFromLenIPv6(ipv6_range['prefixlen'])
ipv6_range['network'] = '%(addr)s/%(prefixlen)d' % ipv6_range
if self._tryReserveIpv6Range(ipv6_range['addr'], ipv6_range['prefixlen']):
return ipv6_range
self._logger.warning(
"Falling back to random IPv6 range selection for partition %s"
" because %s is already taken" % (
'%s tun' % i if tun else i,
ipv6_range['network'],
))
# Try 10 times to add address, raise in case if not possible
for _ in range(10):
ipv6_range = self._generateRandomIPv6Range(address_dict, suffix='0')
if self._tryReserveIpv6Range(ipv6_range['addr'], ipv6_range['prefixlen']):
return ipv6_range
raise AddressGenerationError(ipv6_range['addr'])
def allowNonlocalBind(self):
# This will allow the usage of unexisting IPv6 adresses.
self._logger.debug('sysctl net.ipv6.ip_nonlocal_bind=1')
callAndRead(['sysctl', 'net.ipv6.ip_nonlocal_bind=1'])
def addLocalRouteIpv6Range(self, ipv6_range):
# Add the IPv6 range to local route table
# This will allow using the addresses in the range
# even if they are not added anywhere.
network = ipv6_range['network']
_, result = callAndRead(['ip', '-6', 'route', 'show', 'table', 'local', network])
if not 'dev lo' in result:
self._logger.debug(' ip -6 route add local %s dev lo', network)
callAndRead(['ip', '-6', 'route', 'add', 'local', network, 'dev', 'lo'])
def parse_computer_definition(conf, definition_path): def parse_computer_definition(conf, definition_path):
conf.logger.info('Using definition file %r' % definition_path) conf.logger.info('Using definition file %r' % definition_path)
...@@ -1248,6 +1413,7 @@ def parse_computer_definition(conf, definition_path): ...@@ -1248,6 +1413,7 @@ def parse_computer_definition(conf, definition_path):
addr=address, addr=address,
netmask=netmask, netmask=netmask,
ipv6_interface=conf.ipv6_interface, ipv6_interface=conf.ipv6_interface,
partition_has_ipv6_range=conf.partition_has_ipv6_range,
software_user=computer_definition.get('computer', 'software_user'), software_user=computer_definition.get('computer', 'software_user'),
tap_gateway_interface=conf.tap_gateway_interface, tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6, tap_ipv6=conf.tap_ipv6,
...@@ -1262,15 +1428,23 @@ def parse_computer_definition(conf, definition_path): ...@@ -1262,15 +1428,23 @@ def parse_computer_definition(conf, definition_path):
for a in computer_definition.get(section, 'address').split(): for a in computer_definition.get(section, 'address').split():
address, netmask = a.split('/') address, netmask = a.split('/')
address_list.append(dict(addr=address, netmask=netmask)) address_list.append(dict(addr=address, netmask=netmask))
if computer_definition.has_option(section, 'ipv6_range'):
ipv6_range_network = computer_definition.get(section, 'ipv6_range')
addr, netmask = ipv6_range_network.split('/')
netmask = netmaskFromLenIPv6(int(netmask))
ipv6_range = {'addr' : address, 'netmask' : netmask, 'network' : ipv6_range_network}
else:
ipv6_range = {}
tap = Tap(computer_definition.get(section, 'network_interface')) tap = Tap(computer_definition.get(section, 'network_interface'))
tun = Tun("slaptun" + str(partition_number), tun = Tun("slaptun" + str(partition_number),
partition_number, partition_number,
int(conf.partition_amount)) if conf.create_tun else None int(conf.partition_amount, conf.tun_ipv6)) if conf.create_tun else None
partition = Partition(reference=computer_definition.get(section, 'pathname'), partition = Partition(reference=computer_definition.get(section, 'pathname'),
path=os.path.join(conf.instance_root, path=os.path.join(conf.instance_root,
computer_definition.get(section, 'pathname')), computer_definition.get(section, 'pathname')),
user=user, user=user,
address_list=address_list, address_list=address_list,
ipv6_range=ipv6_range,
tap=tap, tun=tun) tap=tap, tun=tun)
partition_list.append(partition) partition_list.append(partition)
computer.partition_list = partition_list computer.partition_list = partition_list
...@@ -1288,6 +1462,7 @@ def parse_computer_xml(conf, xml_path): ...@@ -1288,6 +1462,7 @@ def parse_computer_xml(conf, xml_path):
computer = Computer.load(xml_path, computer = Computer.load(xml_path,
reference=conf.computer_id, reference=conf.computer_id,
ipv6_interface=conf.ipv6_interface, ipv6_interface=conf.ipv6_interface,
partition_has_ipv6_range=conf.partition_has_ipv6_range,
tap_gateway_interface=conf.tap_gateway_interface, tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6, tap_ipv6=conf.tap_ipv6,
software_root=conf.software_root, software_root=conf.software_root,
...@@ -1306,6 +1481,7 @@ def parse_computer_xml(conf, xml_path): ...@@ -1306,6 +1481,7 @@ def parse_computer_xml(conf, xml_path):
addr=None, addr=None,
netmask=None, netmask=None,
ipv6_interface=conf.ipv6_interface, ipv6_interface=conf.ipv6_interface,
partition_has_ipv6_range=conf.partition_has_ipv6_range,
software_user=conf.software_user, software_user=conf.software_user,
tap_gateway_interface=conf.tap_gateway_interface, tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6, tap_ipv6=conf.tap_ipv6,
...@@ -1332,8 +1508,9 @@ def parse_computer_xml(conf, xml_path): ...@@ -1332,8 +1508,9 @@ def parse_computer_xml(conf, xml_path):
conf.partition_base_name, i)), conf.partition_base_name, i)),
user=User('%s%s' % (conf.user_base_name, i)), user=User('%s%s' % (conf.user_base_name, i)),
address_list=None, address_list=None,
ipv6_range=None,
tap=Tap('%s%s' % (conf.tap_base_name, i)), tap=Tap('%s%s' % (conf.tap_base_name, i)),
tun=Tun('slaptun' + str(i), i, partition_amount) if conf.create_tun else None tun=Tun('slaptun' + str(i), i, partition_amount, conf.tun_ipv6) if conf.create_tun else None
) )
computer.partition_list.append(partition) computer.partition_list.append(partition)
...@@ -1353,6 +1530,7 @@ def write_computer_definition(conf, computer): ...@@ -1353,6 +1530,7 @@ def write_computer_definition(conf, computer):
for address in partition.address_list: for address in partition.address_list:
address_list.append('/'.join([address['addr'], address['netmask']])) address_list.append('/'.join([address['addr'], address['netmask']]))
computer_definition.set(section, 'address', ' '.join(address_list)) computer_definition.set(section, 'address', ' '.join(address_list))
computer_definition.set(section, 'ipv6_range', partition.ipv6_range['network'])
computer_definition.set(section, 'user', partition.user.name) computer_definition.set(section, 'user', partition.user.name)
computer_definition.set(section, 'network_interface', partition.tap.name) computer_definition.set(section, 'network_interface', partition.tap.name)
computer_definition.set(section, 'pathname', partition.reference) computer_definition.set(section, 'pathname', partition.reference)
...@@ -1418,12 +1596,14 @@ class FormatConfig(object): ...@@ -1418,12 +1596,14 @@ class FormatConfig(object):
alter_network = 'True' # modifiable by cmdline alter_network = 'True' # modifiable by cmdline
interface_name = None interface_name = None
ipv6_interface = None ipv6_interface = None
partition_has_ipv6_range = True
create_tap = True create_tap = True
create_tun = False
tap_base_name = None tap_base_name = None
tap_ipv6 = True tap_ipv6 = True
tap_gateway_interface = '' tap_gateway_interface = ''
ipv4_local_network = None ipv4_local_network = None
create_tun = False
tun_ipv6 = True
# User options # User options
alter_user = 'True' # modifiable by cmdline alter_user = 'True' # modifiable by cmdline
...@@ -1482,7 +1662,7 @@ class FormatConfig(object): ...@@ -1482,7 +1662,7 @@ class FormatConfig(object):
raise UsageError(message) raise UsageError(message)
# Convert strings to booleans # Convert strings to booleans
for option in ['alter_network', 'alter_user', 'create_tap', 'create_tun', 'tap_ipv6']: for option in ['alter_network', 'alter_user', 'partition_has_ipv6_range', 'create_tap', 'create_tun', 'tap_ipv6', 'tun_ipv6']:
attr = getattr(self, option) attr = getattr(self, option)
if isinstance(attr, str): if isinstance(attr, str):
if attr.lower() == 'true': if attr.lower() == 'true':
......
...@@ -301,7 +301,7 @@ class SlapformatMixin(unittest.TestCase): ...@@ -301,7 +301,7 @@ class SlapformatMixin(unittest.TestCase):
raise ValueError("{} already has logger attached".format(self.__class__.__name__)) raise ValueError("{} already has logger attached".format(self.__class__.__name__))
self.logger = logger self.logger = logger
self.partition = slapos.format.Partition('partition', '/part_path', self.partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None) slapos.format.User('testuser'), [], None, None)
global USER_LIST global USER_LIST
USER_LIST = [] USER_LIST = []
global GROUP_LIST global GROUP_LIST
...@@ -423,7 +423,8 @@ class TestComputer(SlapformatMixin): ...@@ -423,7 +423,8 @@ class TestComputer(SlapformatMixin):
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'), logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[ partition_list=[
slapos.format.Partition( slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [], tap=slapos.format.Tap('tap')), 'partition', '/part_path', slapos.format.User('testuser'), [],
ipv6_range=None, tap=slapos.format.Tap('tap')),
]) ])
global INTERFACE_DICT global INTERFACE_DICT
INTERFACE_DICT['myinterface'] = { INTERFACE_DICT['myinterface'] = {
...@@ -466,7 +467,8 @@ class TestComputer(SlapformatMixin): ...@@ -466,7 +467,8 @@ class TestComputer(SlapformatMixin):
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'), logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[ partition_list=[
slapos.format.Partition( slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [], tap=slapos.format.Tap('tap')), 'partition', '/part_path', slapos.format.User('testuser'), [],
ipv6_range=None, tap=slapos.format.Tap('tap')),
]) ])
global USER_LIST global USER_LIST
USER_LIST = ['testuser'] USER_LIST = ['testuser']
...@@ -517,7 +519,7 @@ class TestComputer(SlapformatMixin): ...@@ -517,7 +519,7 @@ class TestComputer(SlapformatMixin):
partition_list=[ partition_list=[
slapos.format.Partition( slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [], 'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')), ipv6_range=None, tap=slapos.format.Tap('tap')),
]) ])
global USER_LIST global USER_LIST
USER_LIST = ['testuser'] USER_LIST = ['testuser']
...@@ -577,7 +579,7 @@ class TestComputer(SlapformatMixin): ...@@ -577,7 +579,7 @@ class TestComputer(SlapformatMixin):
partition_list=[ partition_list=[
slapos.format.Partition( slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [], 'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')), ipv6_range=None, tap=slapos.format.Tap('tap')),
]) ])
global INTERFACE_DICT global INTERFACE_DICT
INTERFACE_DICT['myinterface'] = { INTERFACE_DICT['myinterface'] = {
...@@ -618,7 +620,7 @@ class TestComputer(SlapformatMixin): ...@@ -618,7 +620,7 @@ class TestComputer(SlapformatMixin):
partition_list=[ partition_list=[
slapos.format.Partition( slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [], 'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')), ipv6_range=None, tap=slapos.format.Tap('tap')),
]) ])
global INTERFACE_DICT global INTERFACE_DICT
INTERFACE_DICT['myinterface'] = { INTERFACE_DICT['myinterface'] = {
...@@ -636,12 +638,7 @@ class TestComputer(SlapformatMixin): ...@@ -636,12 +638,7 @@ class TestComputer(SlapformatMixin):
"chmod('/instance_root/partition', 488)" "chmod('/instance_root/partition', 488)"
], ],
self.test_result.bucket) self.test_result.bucket)
self.assertEqual([ self.assertEqual([],
'ip addr add ip/255.255.255.255 dev myinterface',
# 'ip addr list myinterface',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev myinterface',
'ip -6 addr list myinterface',
],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
...@@ -660,7 +657,8 @@ class TestFormatDump(SlapformatMixin): ...@@ -660,7 +657,8 @@ class TestFormatDump(SlapformatMixin):
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'), logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[ partition_list=[
slapos.format.Partition( slapos.format.Partition(
'partition', 'part_path', slapos.format.User('testuser'), [], tap=slapos.format.Tap('tap')), 'partition', 'part_path', slapos.format.User('testuser'), [],
ipv6_range=None, tap=slapos.format.Tap('tap')),
]) ])
global USER_LIST global USER_LIST
USER_LIST = ['testuser'] USER_LIST = ['testuser']
...@@ -745,7 +743,8 @@ class TestComputerWithCPUSet(SlapformatMixin): ...@@ -745,7 +743,8 @@ class TestComputerWithCPUSet(SlapformatMixin):
logger=self.logger, name='lo', ipv4_local_network='127.0.0.1/16'), logger=self.logger, name='lo', ipv4_local_network='127.0.0.1/16'),
partition_list=[ partition_list=[
slapos.format.Partition( slapos.format.Partition(
'partition', '/tmp/slapgrid/instance_root/part1', slapos.format.User('testuser'), [], tap=None), 'partition', '/tmp/slapgrid/instance_root/part1', slapos.format.User('testuser'), [],
ipv6_range=None, tap=None),
], ],
config={ config={
"manager_list": "cpuset", "manager_list": "cpuset",
......
...@@ -205,6 +205,59 @@ def ipv6FromBin(ip, suffix=''): ...@@ -205,6 +205,59 @@ def ipv6FromBin(ip, suffix=''):
return socket.inet_ntop(socket.AF_INET6, return socket.inet_ntop(socket.AF_INET6,
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2))) struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
def getPartitionIpv6Addr(ipv6_range, partition_index):
"""
from a IPv6 range in the form
{
'addr' : addr,
'prefixlen' : CIDR
}
returns the IPv6 addr
addr::(partition_index+2) (address 1 is is used by re6st)
"""
addr = ipv6_range['addr']
prefixlen = ipv6_range['prefixlen']
prefix = binFromIpv6(addr)[:prefixlen]
return dict(addr=ipv6FromBin(prefix + bin(partition_index+2)[2:].zfill(128 - prefixlen)), prefixlen=prefixlen)
def getIpv6RangeFactory(k, s):
def getIpv6Range(ipv6_range, partition_index):
"""
from a IPv6 range in the form
{
'addr' : addr,
'prefixlen' : CIDR
}
returns the IPv6 range
{
'addr' : addr:(k*(2^14) + partition_index+1)
'prefixlen' : CIDR+16
}
"""
addr = ipv6_range['addr']
prefixlen = ipv6_range['prefixlen']
prefix = binFromIpv6(addr)[:prefixlen]
# we generate a subnetwork for the partition
# the subnetwork has 16 bits more than our IPv6 range
# make sure we have at least 2 IPs in the subnetwork
prefixlen += 16
if prefixlen >= 128:
raise ValueError('The IPv6 range has prefixlen {} which is too big for generating IPv6 range for partitions.'.format(prefixlen))
return dict(addr=ipv6FromBin(prefix + bin((k << 14) + partition_index+1)[2:].zfill(16) + s * (128 - prefixlen)), prefixlen=prefixlen)
return getIpv6Range
getPartitionIpv6Range = getIpv6RangeFactory(1, '0')
getTapIpv6Range = getIpv6RangeFactory(2, '1')
getTunIpv6Range = getIpv6RangeFactory(3, '0')
def getIpv6RangeFirstAddr(addr, prefixlen):
addr_1 = "%s1" % ipv6FromBin(binFromIpv6(addr)[:prefixlen])
return ipv6FromBin(binFromIpv6(addr_1)) # correctly format the IPv6
def lenNetmaskIpv6(netmask): def lenNetmaskIpv6(netmask):
"""Convert string represented netmask to its integer prefix""" """Convert string represented netmask to its integer prefix"""
# Since version 0.10.7 of netifaces, the netmask is something like "ffff::/16", # Since version 0.10.7 of netifaces, the netmask is something like "ffff::/16",
...@@ -215,6 +268,10 @@ def lenNetmaskIpv6(netmask): ...@@ -215,6 +268,10 @@ def lenNetmaskIpv6(netmask):
except ValueError: except ValueError:
return netaddr.IPNetwork(netmask).prefixlen return netaddr.IPNetwork(netmask).prefixlen
def netmaskFromLenIPv6(netmask_len):
""" opposite of lenNetmaskIpv6"""
return ipv6FromBin('1' * netmask_len)
# Used for Python 2-3 compatibility # Used for Python 2-3 compatibility
if str is bytes: if str is bytes:
bytes2str = str2bytes = lambda s: s bytes2str = str2bytes = lambda s: s
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment