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

Slapformat: IPv6 range for partitions and tun

See merge request !455
parents 7e2f8eec c4e86b91
Pipeline #27042 failed with stage
in 0 seconds
......@@ -125,7 +125,7 @@ class FormatCommand(ConfigCommand):
try:
conf.setConfig()
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.exit(1)
......
......@@ -58,7 +58,11 @@ import six
import lxml.etree
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
from slapos import version
from slapos import manager as slapmanager
......@@ -243,7 +247,7 @@ class Computer(object):
"""Object representing the computer"""
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,
instance_root=None, software_root=None, instance_storage_home=None,
partition_list=None, config=None):
......@@ -260,6 +264,7 @@ class Computer(object):
self.address = addr
self.netmask = netmask
self.ipv6_interface = ipv6_interface
self.partition_has_ipv6_range = partition_has_ipv6_range
self.software_user = software_user
self.tap_gateway_interface = tap_gateway_interface
self.tap_ipv6 = tap_ipv6
......@@ -410,7 +415,7 @@ class Computer(object):
archive.writestr(saved_filename, xml_content, zipfile.ZIP_DEFLATED)
@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):
"""
Create a computer object from a valid xml file.
......@@ -431,6 +436,7 @@ class Computer(object):
addr=dumped_dict['address'],
netmask=dumped_dict['netmask'],
ipv6_interface=ipv6_interface,
partition_has_ipv6_range=partition_has_ipv6_range,
software_user=dumped_dict.get('software_user', 'slapsoft'),
tap_gateway_interface=tap_gateway_interface,
tap_ipv6=tap_ipv6,
......@@ -460,13 +466,28 @@ class Computer(object):
else:
tap = Tap(partition_dict['reference'])
if partition_dict.get('tun') is not None and partition_dict['tun'].get('ipv4_addr') is not None:
tun = Tun(partition_dict['tun']['name'], partition_index, partition_amount)
tun.ipv4_addr = partition_dict['tun']['ipv4_addr']
tun_dict = partition_dict.get('tun')
if tun_dict is None:
if config.create_tun:
tun = Tun("slaptun" + str(partition_index), partition_index, partition_amount, config.tun_ipv6)
else:
tun = None
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']
ipv6_range = partition_dict.get('ipv6_range', {})
external_storage_list = partition_dict.get('external_storage_list', [])
partition = Partition(
......@@ -474,8 +495,9 @@ class Computer(object):
path=partition_dict['path'],
user=user,
address_list=address_list,
ipv6_range=ipv6_range,
tap=tap,
tun=tun if config.create_tun else None,
tun=tun,
external_storage_list=external_storage_list,
)
......@@ -562,7 +584,7 @@ class Computer(object):
####################
if alter_network:
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:
gateway_addr_dict = getIfaceAddressIPv4(self.tap_gateway_interface)
......@@ -571,6 +593,9 @@ class Computer(object):
len(self.partition_list))
assert(len(self.partition_list) <= len(tap_address_list))
if self.partition_has_ipv6_range:
self.interface.allowNonlocalBind()
self._speedHackAddAllOldIpsToInterface()
try:
......@@ -614,23 +639,22 @@ class Computer(object):
if self.tap_ipv6:
if not partition.tap.ipv6_addr:
# 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_netmask = ipv6_dict['netmask']
else:
# make sure the tap has its IPv6
self.interface.addIPv6Address(
partition_index=partition_index,
addr=partition.tap.ipv6_addr,
netmask=partition.tap.ipv6_netmask,
tap=partition.tap)
# construct ipv6_network (16 bit more than the computer network)
netmask_len = lenNetmaskIpv6(self.interface.getGlobalScopeAddressList()[0]['netmask']) + 16
prefix = binFromIpv6(partition.tap.ipv6_addr)[:netmask_len]
network_addr = ipv6FromBin(prefix)
partition.tap.ipv6_gateway = "{}1".format(network_addr) # address network::1 will be inside the VM
partition.tap.ipv6_gateway = ipv6FromBin(binFromIpv6(partition.tap.ipv6_gateway)) # correctly format the IPv6
partition.tap.ipv6_network = "{}/{}".format(network_addr, netmask_len)
prefixlen = lenNetmaskIpv6(self.interface.getGlobalScopeAddressList()[0]['netmask']) + 16
gateway_addr = getIpv6RangeFirstAddr(partition.tap.ipv6_addr, prefixlen)
partition.tap.ipv6_gateway = gateway_addr
partition.tap.ipv6_network = "{}/{}".format(gateway_addr, prefixlen)
else:
partition.tap.ipv6_addr = ''
partition.tap.ipv6_netmask = ''
......@@ -643,45 +667,72 @@ class Computer(object):
if partition.tun is not None:
# create TUN interface per partition as well
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()
# 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
partition.createPath(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:
for manager in self._manager_list:
manager.formatTearDown(self)
......@@ -693,13 +744,14 @@ class Partition(object):
resource_file = ".slapos-resource"
def __init__(self, reference, path, user, address_list,
tap, external_storage_list=[], tun=None):
ipv6_range, tap, tun=None, external_storage_list=[]):
"""
Attributes:
reference: String, the name of the partition.
path: String, the path to the partition folder.
user: User, the user linked to this partition.
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
tun: Tun interface used for special apps simulating ethernet connections
external_storage_list: Base path list of folder to format for data storage
......@@ -709,12 +761,13 @@ class Partition(object):
self.path = str(path)
self.user = user
self.address_list = address_list or []
self.ipv6_range = ipv6_range or {}
self.tap = tap
self.tun = tun
self.external_storage_list = []
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):
"""
......@@ -925,13 +978,14 @@ class Tun(Tap):
BASE_MASK = 12
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.
:param name: name which will appear in ``ip list`` afterwards
:param sequence: {int} position of this TUN among all ``partitions``
"""
super(Tun, self).__init__(name)
self._needs_ipv6 = needs_ipv6
if sequence is not None:
assert 0 <= sequence < partitions, "0 <= {} < {}".format(sequence, partitions)
# create base IPNetwork
......@@ -951,16 +1005,20 @@ class Tun(Tap):
"""Extend for physical addition of network address because TAP let this on external class."""
if self.ipv4_network:
# add an address
code, _ = callAndRead(['ip', 'addr', 'add', self.ipv4_network, 'dev', self.name],
raise_on_error=False)
code, _ = callAndRead(
['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:
# address added to the interface - wait
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):
......@@ -975,7 +1033,8 @@ class Interface(object):
self._logger = logger
self.name = str(name)
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.
......@@ -998,7 +1057,7 @@ class Interface(object):
def getGlobalScopeAddressList(self, tap=None):
"""Returns currently configured global scope IPv6 addresses"""
interface_name = self.ipv6_interface or self.name
interface_name = self.ipv6_interface
try:
address_list = [
q
......@@ -1008,6 +1067,8 @@ class Interface(object):
except KeyError:
raise ValueError("%s must have at least one IPv6 address assigned" %
interface_name)
if not address_list:
raise NoAddressOnInterface(interface_name)
if tap:
try:
......@@ -1042,13 +1103,12 @@ class Interface(object):
if ipv6:
address_string = '%s/%s' % (address, lenNetmaskIpv6(netmask))
af = socket.AF_INET6
interface_name = self.ipv6_interface or self.name
interface_name = self.ipv6_interface
else:
af = socket.AF_INET
address_string = '%s/%s' % (address, netmaskToPrefixIPv4(netmask))
interface_name = self.name
if tap:
interface_name = tap.name
# check if address is already took by any other interface
......@@ -1066,13 +1126,10 @@ class Interface(object):
for q in netifaces.ifaddresses(interface_name)[af]
]:
# 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 not ipv6:
return True
# wait few moments
if code != 0:
return False
time.sleep(2)
# Fake success for local ipv4
......@@ -1126,7 +1183,47 @@ class Interface(object):
# confirmed to be configured
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.
......@@ -1136,6 +1233,9 @@ class Interface(object):
address. If it is not possible (ex. because network changed), calculate new
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:
addr: Wished address to be added to interface.
netmask: Wished netmask to be used.
......@@ -1150,13 +1250,10 @@ class Interface(object):
NoAddressOnInterface: There's no address on the interface to construct
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
interface_addr_list = self.getGlobalScopeAddressList()
# No address found
if len(interface_addr_list) == 0:
raise NoAddressOnInterface(interface_name)
address_dict = interface_addr_list[0]
if addr is not None:
......@@ -1173,7 +1270,8 @@ class Interface(object):
if dict_addr_netmask in interface_addr_list or \
(tap and dict_addr_netmask in self.getGlobalScopeAddressList(tap=tap)):
# 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 \
(tap and lenNetmaskIpv6(netmask) == 128):
# same netmask, so there is a chance to add good one
......@@ -1191,39 +1289,106 @@ class Interface(object):
self._logger.warning('Impossible to add old public IPv6 %s. '
'Generating new IPv6 address.' % addr)
# Try 10 times to add address, raise in case if not possible
try_num = 10
netmask = address_dict['netmask']
# Try to use the IPv6 mapping based on partition index
address_dict['prefixlen'] = lenNetmaskIpv6(address_dict['netmask'])
if tap:
netmask_len = lenNetmaskIpv6(netmask)
prefix = binFromIpv6(address_dict['addr'])[:netmask_len]
netmask_len += 16
# we generate a subnetwork for the tap
# the subnetwork has 16 bits more than the interface network
# make sure we have at least 2 IPs in the subnetwork
if netmask_len >= 128:
self._logger.error('Interface %s has netmask %s which is too big for generating IPv6 on taps.' % (interface_name, netmask))
raise AddressGenerationError(addr)
netmask = ipv6FromBin('1'*128) # the netmask of the tap itself is always 128 bits
result_addr = getTapIpv6Range(address_dict, partition_index)
# the netmask of the tap itself is always 128 bits
result_addr['netmask'] = netmaskFromLenIPv6(128)
else:
result_addr = getPartitionIpv6Addr(address_dict, partition_index)
result_addr['netmask'] = netmaskFromLenIPv6(result_addr['prefixlen'])
if not tap or self._checkIpv6Range(result_addr['addr'], result_addr['prefixlen']):
if self._addSystemAddress(result_addr['addr'], result_addr['netmask'], tap=tap):
if tap:
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:
addr = ipv6FromBin(prefix
+ bin(random.randint(1, 65000))[2:].zfill(16)
+ '1' * (128 - netmask_len))
result_addr = self._generateRandomIPv6Range(address_dict, suffix='1')
# the netmask of the tap itself is always 128 bits
result_addr['netmask'] = netmaskFromLenIPv6(128)
else:
addr = ':'.join(address_dict['addr'].split(':')[:-1] + ['%x' % (
random.randint(1, 65000), )])
socket.inet_pton(socket.AF_INET6, addr)
if (dict(addr=addr, netmask=netmask) not in
self.getGlobalScopeAddressList(tap=tap)):
# Checking the validity of the IPv6 address
if self._addSystemAddress(addr, netmask, tap=tap):
return dict(addr=addr, netmask=netmask)
try_num -= 1
result_addr = self._generateRandomIPv6Addr(address_dict)
# Checking the validity of the IPv6 address
addr = result_addr['addr']
if not tap or self._checkIpv6Range(addr, result_addr['prefixlen']):
if self._addSystemAddress(addr, result_addr['netmask'], tap=tap):
if tap:
self._reserveIpv6Range(addr, result_addr['prefixlen'])
return result_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):
conf.logger.info('Using definition file %r' % definition_path)
......@@ -1248,6 +1413,7 @@ def parse_computer_definition(conf, definition_path):
addr=address,
netmask=netmask,
ipv6_interface=conf.ipv6_interface,
partition_has_ipv6_range=conf.partition_has_ipv6_range,
software_user=computer_definition.get('computer', 'software_user'),
tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6,
......@@ -1262,15 +1428,23 @@ def parse_computer_definition(conf, definition_path):
for a in computer_definition.get(section, 'address').split():
address, netmask = a.split('/')
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'))
tun = Tun("slaptun" + str(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'),
path=os.path.join(conf.instance_root,
computer_definition.get(section, 'pathname')),
user=user,
address_list=address_list,
ipv6_range=ipv6_range,
tap=tap, tun=tun)
partition_list.append(partition)
computer.partition_list = partition_list
......@@ -1288,6 +1462,7 @@ def parse_computer_xml(conf, xml_path):
computer = Computer.load(xml_path,
reference=conf.computer_id,
ipv6_interface=conf.ipv6_interface,
partition_has_ipv6_range=conf.partition_has_ipv6_range,
tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6,
software_root=conf.software_root,
......@@ -1306,6 +1481,7 @@ def parse_computer_xml(conf, xml_path):
addr=None,
netmask=None,
ipv6_interface=conf.ipv6_interface,
partition_has_ipv6_range=conf.partition_has_ipv6_range,
software_user=conf.software_user,
tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6,
......@@ -1332,8 +1508,9 @@ def parse_computer_xml(conf, xml_path):
conf.partition_base_name, i)),
user=User('%s%s' % (conf.user_base_name, i)),
address_list=None,
ipv6_range=None,
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)
......@@ -1353,6 +1530,7 @@ def write_computer_definition(conf, computer):
for address in partition.address_list:
address_list.append('/'.join([address['addr'], address['netmask']]))
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, 'network_interface', partition.tap.name)
computer_definition.set(section, 'pathname', partition.reference)
......@@ -1418,12 +1596,14 @@ class FormatConfig(object):
alter_network = 'True' # modifiable by cmdline
interface_name = None
ipv6_interface = None
partition_has_ipv6_range = True
create_tap = True
create_tun = False
tap_base_name = None
tap_ipv6 = True
tap_gateway_interface = ''
ipv4_local_network = None
create_tun = False
tun_ipv6 = True
# User options
alter_user = 'True' # modifiable by cmdline
......@@ -1482,7 +1662,7 @@ class FormatConfig(object):
raise UsageError(message)
# 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)
if isinstance(attr, str):
if attr.lower() == 'true':
......
......@@ -301,7 +301,7 @@ class SlapformatMixin(unittest.TestCase):
raise ValueError("{} already has logger attached".format(self.__class__.__name__))
self.logger = logger
self.partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None)
slapos.format.User('testuser'), [], None, None)
global USER_LIST
USER_LIST = []
global GROUP_LIST
......@@ -423,7 +423,8 @@ class TestComputer(SlapformatMixin):
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[
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
INTERFACE_DICT['myinterface'] = {
......@@ -466,7 +467,8 @@ class TestComputer(SlapformatMixin):
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[
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
USER_LIST = ['testuser']
......@@ -517,7 +519,7 @@ class TestComputer(SlapformatMixin):
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')),
ipv6_range=None, tap=slapos.format.Tap('tap')),
])
global USER_LIST
USER_LIST = ['testuser']
......@@ -577,7 +579,7 @@ class TestComputer(SlapformatMixin):
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')),
ipv6_range=None, tap=slapos.format.Tap('tap')),
])
global INTERFACE_DICT
INTERFACE_DICT['myinterface'] = {
......@@ -618,7 +620,7 @@ class TestComputer(SlapformatMixin):
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')),
ipv6_range=None, tap=slapos.format.Tap('tap')),
])
global INTERFACE_DICT
INTERFACE_DICT['myinterface'] = {
......@@ -636,12 +638,7 @@ class TestComputer(SlapformatMixin):
"chmod('/instance_root/partition', 488)"
],
self.test_result.bucket)
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.assertEqual([],
self.fakeCallAndRead.external_command_list)
......@@ -660,7 +657,8 @@ class TestFormatDump(SlapformatMixin):
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[
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
USER_LIST = ['testuser']
......@@ -745,7 +743,8 @@ class TestComputerWithCPUSet(SlapformatMixin):
logger=self.logger, name='lo', ipv4_local_network='127.0.0.1/16'),
partition_list=[
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={
"manager_list": "cpuset",
......
......@@ -205,6 +205,59 @@ def ipv6FromBin(ip, suffix=''):
return socket.inet_ntop(socket.AF_INET6,
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):
"""Convert string represented netmask to its integer prefix"""
# Since version 0.10.7 of netifaces, the netmask is something like "ffff::/16",
......@@ -215,6 +268,10 @@ def lenNetmaskIpv6(netmask):
except ValueError:
return netaddr.IPNetwork(netmask).prefixlen
def netmaskFromLenIPv6(netmask_len):
""" opposite of lenNetmaskIpv6"""
return ipv6FromBin('1' * netmask_len)
# Used for Python 2-3 compatibility
if str is bytes:
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