Commit 40ee3280 authored by Thomas Gambier's avatar Thomas Gambier 🚴🏼 Committed by Rafael Monnerat

Make IPv6 on tap optionnal and put only 1 IPv6 address on the tap itself.

tap_ipv6 option let you chose if you want IPv6 on the taps or not. Some
customers may decide to have only IPv4 inside their VMs.

We put only 1 address on the tap itself (e.g.
2001:67c:1254:e:89:69b0:ffff:ffff/128 instead of
2001:67c:1254:e:89:69b0:ffff:ffff/96) otherwise, when we assign the
address 2001:67c:1254:e:89:69b0::/96 inside the VM, the address has
"dadfailed" status (dad = "Duplicate Address Detection"). The problem
appears only with address "0" (ending in ::) but not with others
addresses. We had 2 solutions:
 * put only one address on the tap (the solution we choose)
 * forbid address "0" inside the VM

The chosen solution has the advantage that the host machine won't
answer the ping on behalf of the VM since the tap has only 1 address
(its own).

/cc @alain.takoudjou

/reviewed-on nexedi/slapos.core!86
parent 765c89f8
Changes Changes
======= =======
1.4.16 (???)
-------------------
* format: new tap_iv6 configuration file option
1.4.15 (2018-12-11) 1.4.15 (2018-12-11)
------------------- -------------------
......
...@@ -20,6 +20,9 @@ log_file = /opt/slapos/log/slapos-node-format.log ...@@ -20,6 +20,9 @@ log_file = /opt/slapos/log/slapos-node-format.log
partition_base_name = slappart partition_base_name = slappart
user_base_name = slapuser user_base_name = slapuser
tap_base_name = slaptap tap_base_name = slaptap
# Change "tap_ipv6" into "false" if you don't want the tap to have IPv6 addresses (tap will have only IPv4)
# This option has no effect if create_tap is false
tap_ipv6 = true
# You can choose any other local network which does not conflict with your # You can choose any other local network which does not conflict with your
# current machine configuration # current machine configuration
ipv4_local_network = 10.0.0.0/16 ipv4_local_network = 10.0.0.0/16
......
...@@ -246,7 +246,7 @@ class Computer(object): ...@@ -246,7 +246,7 @@ class Computer(object):
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, software_user='slapsoft',
tap_gateway_interface=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):
""" """
...@@ -264,6 +264,7 @@ class Computer(object): ...@@ -264,6 +264,7 @@ class Computer(object):
self.ipv6_interface = ipv6_interface self.ipv6_interface = ipv6_interface
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
# Used to be static attributes of the class object - didn't make sense (Marco again) # Used to be static attributes of the class object - didn't make sense (Marco again)
assert instance_root is not None and software_root is not None, \ assert instance_root is not None and software_root is not None, \
...@@ -402,7 +403,7 @@ class Computer(object): ...@@ -402,7 +403,7 @@ class Computer(object):
@classmethod @classmethod
def load(cls, path_to_xml, reference, ipv6_interface, tap_gateway_interface, def load(cls, path_to_xml, reference, ipv6_interface, tap_gateway_interface,
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.
...@@ -424,6 +425,7 @@ class Computer(object): ...@@ -424,6 +425,7 @@ class Computer(object):
ipv6_interface=ipv6_interface, ipv6_interface=ipv6_interface,
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,
software_root=dumped_dict.get('software_root', software_root), software_root=dumped_dict.get('software_root', software_root),
instance_root=dumped_dict.get('instance_root', instance_root), instance_root=dumped_dict.get('instance_root', instance_root),
config=config, config=config,
...@@ -612,24 +614,32 @@ class Computer(object): ...@@ -612,24 +614,32 @@ class Computer(object):
partition.tap.ipv4_gateway = gateway_addr_dict['addr'] partition.tap.ipv4_gateway = gateway_addr_dict['addr']
partition.tap.ipv4_network = gateway_addr_dict['network'] partition.tap.ipv4_network = gateway_addr_dict['network']
if not partition.tap.ipv6_addr: if self.tap_ipv6:
# create a new IPv6 randomly for the tap if not partition.tap.ipv6_addr:
ipv6_dict = self.interface.addIPv6Address(tap=partition.tap) # create a new IPv6 randomly for the tap
partition.tap.ipv6_addr = ipv6_dict['addr'] ipv6_dict = self.interface.addIPv6Address(tap=partition.tap)
partition.tap.ipv6_netmask = ipv6_dict['netmask'] 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(
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 = partition.tap.ipv6_addr
partition.tap.ipv6_network = "{}/{}".format(network_addr, netmask_len)
else: else:
# make sure the tap has its IPv6 partition.tap.ipv6_addr = ''
self.interface.addIPv6Address( partition.tap.ipv6_netmask = ''
addr=partition.tap.ipv6_addr, partition.tap.ipv6_gateway = ''
netmask=partition.tap.ipv6_netmask, partition.tap.ipv6_network = ''
tap=partition.tap)
# create IPv4 and IPv6 routes
# construct ipv6_network and create routes
netmask_len = lenNetmaskIpv6(partition.tap.ipv6_netmask)
prefix = binFromIpv6(partition.tap.ipv6_addr)[:netmask_len]
network_addr = ipv6FromBin(prefix)
partition.tap.ipv6_gateway = partition.tap.ipv6_addr
partition.tap.ipv6_network = "{}/{}".format(network_addr, netmask_len)
partition.tap.createRoutes() partition.tap.createRoutes()
if partition.tun is not None: if partition.tun is not None:
...@@ -878,19 +888,25 @@ class Tap(object): ...@@ -878,19 +888,25 @@ class Tap(object):
def createRoutes(self): def createRoutes(self):
""" """
Configure ipv4 route to reach this interface from local network Configure ipv4 and ipv6 routes
""" """
if self.ipv4_addr: if self.ipv4_addr:
# Check if this route exits # Check if this route exits
code, result = callAndRead(['ip', 'route', 'show', self.ipv4_addr], code, result = callAndRead(['ip', 'route', 'show', self.ipv4_addr],
raise_on_error=False) raise_on_error=False)
if code == 0 and self.ipv4_addr in result and self.name in result: if code != 0 or self.ipv4_addr not in result or self.name not in result:
return callAndRead(['ip', 'route', 'add', self.ipv4_addr, 'dev', self.name])
callAndRead(['ip', 'route', 'add', self.ipv4_addr, 'dev', self.name])
else: else:
raise ValueError("%s should not be empty. No ipv4 address assigned to %s" % raise ValueError("%s should not be empty. No ipv4 address assigned to %s" %
(self.ipv4_addr, self.name)) (self.ipv4_addr, self.name))
if self.ipv6_network:
# Check if this route exits
code, result = callAndRead(['ip', '-6', 'route', 'show', self.ipv6_network],
raise_on_error=False)
if code != 0 or self.ipv6_network not in result or self.name not in result:
callAndRead(['ip', '-6', 'route', 'add', self.ipv6_network, 'dev', self.name])
class Tun(Tap): class Tun(Tap):
"""Represent TUN interface which might be many per user.""" """Represent TUN interface which might be many per user."""
...@@ -1146,7 +1162,7 @@ class Interface(object): ...@@ -1146,7 +1162,7 @@ class Interface(object):
# confirmed to be configured # confirmed to be configured
return dict_addr_netmask return dict_addr_netmask
if netmask == address_dict['netmask'] or \ if netmask == address_dict['netmask'] or \
(tap and lenNetmaskIpv6(netmask) == lenNetmaskIpv6(address_dict['netmask']) + 16): (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
interface_network = netaddr.ip.IPNetwork('%s/%s' % (address_dict['addr'], interface_network = netaddr.ip.IPNetwork('%s/%s' % (address_dict['addr'],
netmaskToPrefixIPv6(address_dict['netmask']))) netmaskToPrefixIPv6(address_dict['netmask'])))
...@@ -1175,7 +1191,7 @@ class Interface(object): ...@@ -1175,7 +1191,7 @@ class Interface(object):
if netmask_len >= 128: if netmask_len >= 128:
self._logger.error('Interface %s has netmask %s which is too big for generating IPv6 on taps.' % (interface_name, netmask)) self._logger.error('Interface %s has netmask %s which is too big for generating IPv6 on taps.' % (interface_name, netmask))
raise AddressGenerationError(addr) raise AddressGenerationError(addr)
netmask = ipv6FromBin('1'*netmask_len) netmask = ipv6FromBin('1'*128) # the netmask of the tap itself is always 128 bits
while try_num > 0: while try_num > 0:
if tap: if tap:
...@@ -1221,6 +1237,7 @@ def parse_computer_definition(conf, definition_path): ...@@ -1221,6 +1237,7 @@ def parse_computer_definition(conf, definition_path):
ipv6_interface=conf.ipv6_interface, ipv6_interface=conf.ipv6_interface,
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,
software_root=conf.software_root, software_root=conf.software_root,
instance_root=conf.instance_root instance_root=conf.instance_root
) )
...@@ -1259,6 +1276,7 @@ def parse_computer_xml(conf, xml_path): ...@@ -1259,6 +1276,7 @@ def parse_computer_xml(conf, xml_path):
reference=conf.computer_id, reference=conf.computer_id,
ipv6_interface=conf.ipv6_interface, ipv6_interface=conf.ipv6_interface,
tap_gateway_interface=conf.tap_gateway_interface, tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6,
software_root=conf.software_root, software_root=conf.software_root,
instance_root=conf.instance_root, instance_root=conf.instance_root,
config=conf) config=conf)
...@@ -1277,6 +1295,7 @@ def parse_computer_xml(conf, xml_path): ...@@ -1277,6 +1295,7 @@ def parse_computer_xml(conf, xml_path):
ipv6_interface=conf.ipv6_interface, ipv6_interface=conf.ipv6_interface,
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,
config=conf, config=conf,
) )
...@@ -1389,8 +1408,9 @@ class FormatConfig(object): ...@@ -1389,8 +1408,9 @@ class FormatConfig(object):
create_tap = True create_tap = True
create_tun = False create_tun = False
tap_base_name = None tap_base_name = None
ipv4_local_network = None tap_ipv6 = True
tap_gateway_interface = '' tap_gateway_interface = ''
ipv4_local_network = None
use_unique_local_address_block = False use_unique_local_address_block = False
# User options # User options
...@@ -1450,7 +1470,7 @@ class FormatConfig(object): ...@@ -1450,7 +1470,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', 'use_unique_local_address_block']: for option in ['alter_network', 'alter_user', 'create_tap', 'create_tun', 'use_unique_local_address_block', 'tap_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':
...@@ -1489,7 +1509,7 @@ class FormatConfig(object): ...@@ -1489,7 +1509,7 @@ class FormatConfig(object):
if self.dry_run: if self.dry_run:
self.logger.info("Dry-run mode enabled.") self.logger.info("Dry-run mode enabled.")
if self.create_tap: if self.create_tap:
self.logger.info("Tap creation mode enabled.") self.logger.info("Tap creation mode enabled (%s IPv6).", "with" if with_ipv6 else "without")
# Calculate path once # Calculate path once
self.computer_xml = os.path.abspath(self.computer_xml) self.computer_xml = os.path.abspath(self.computer_xml)
......
...@@ -121,6 +121,11 @@ class FakeCallAndRead: ...@@ -121,6 +121,11 @@ class FakeCallAndRead:
retval = 0, str(INTERFACE_DICT) retval = 0, str(INTERFACE_DICT)
elif argument_list[:3] == ['ip', 'route', 'show']: elif argument_list[:3] == ['ip', 'route', 'show']:
retval = 0, 'OK' retval = 0, 'OK'
elif argument_list[:3] == ['ip', '-6', 'route']:
retval = 0, 'OK'
ip = argument_list[4]
netmask = int(ip.split('/')[1])
argument_list[4] = 'ip/%s' % netmask
elif argument_list[:3] == ['route', 'add', '-host']: elif argument_list[:3] == ['route', 'add', '-host']:
retval = 0, 'OK' retval = 0, 'OK'
self.external_command_list.append(' '.join(argument_list)) self.external_command_list.append(' '.join(argument_list))
...@@ -448,6 +453,7 @@ class TestComputer(SlapformatMixin): ...@@ -448,6 +453,7 @@ class TestComputer(SlapformatMixin):
computer = slapos.format.Computer('computer', computer = slapos.format.Computer('computer',
instance_root='/instance_root', instance_root='/instance_root',
software_root='/software_root', software_root='/software_root',
tap_ipv6=True,
interface=slapos.format.Interface( interface=slapos.format.Interface(
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=[
...@@ -479,10 +485,12 @@ class TestComputer(SlapformatMixin): ...@@ -479,10 +485,12 @@ class TestComputer(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'ip tuntap add dev tap mode tap user testuser', 'ip tuntap add dev tap mode tap user testuser',
'ip link set tap up', 'ip link set tap up',
'ip addr add ip/ffff:ffff:ffff:ffff:ffff:: dev tap', 'ip addr add ip/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff dev tap',
'ip -6 addr list tap', 'ip -6 addr list tap',
'ip route show 10.0.0.2', 'ip route show 10.0.0.2',
'ip route add 10.0.0.2 dev tap', 'ip route add 10.0.0.2 dev tap',
'ip -6 route show ip/80',
'ip -6 route add ip/80 dev tap',
'ip addr add ip/255.255.255.255 dev myinterface', 'ip addr add ip/255.255.255.255 dev myinterface',
# 'ip addr list myinterface', # 'ip addr list myinterface',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev myinterface', 'ip addr add ip/ffff:ffff:ffff:ffff:: dev myinterface',
...@@ -495,6 +503,7 @@ class TestComputer(SlapformatMixin): ...@@ -495,6 +503,7 @@ class TestComputer(SlapformatMixin):
instance_root='/instance_root', instance_root='/instance_root',
software_root='/software_root', software_root='/software_root',
tap_gateway_interface='eth1', tap_gateway_interface='eth1',
tap_ipv6=True,
interface=slapos.format.Interface( interface=slapos.format.Interface(
logger=self.logger, name='iface', ipv4_local_network='127.0.0.1/16'), logger=self.logger, name='iface', ipv4_local_network='127.0.0.1/16'),
partition_list=[ partition_list=[
...@@ -508,7 +517,7 @@ class TestComputer(SlapformatMixin): ...@@ -508,7 +517,7 @@ class TestComputer(SlapformatMixin):
INTERFACE_DICT['iface'] = { INTERFACE_DICT['iface'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1', socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
'netmask': '255.255.255.0'}], 'netmask': '255.255.255.0'}],
socket.AF_INET6: [{'addr': '2a01:e35:2e27:3456::e59c', 'netmask': 'ffff:ffff:ffff:ffff:ffff::'}] socket.AF_INET6: [{'addr': '2a01:e35:2e27:3456:1357::e59c', 'netmask': 'ffff:ffff:ffff:ffff:ffff::'}]
} }
INTERFACE_DICT['eth1'] = { INTERFACE_DICT['eth1'] = {
socket.AF_INET: [{'addr': '10.8.0.1', 'broadcast': '10.8.0.254', socket.AF_INET: [{'addr': '10.8.0.1', 'broadcast': '10.8.0.254',
...@@ -516,7 +525,7 @@ class TestComputer(SlapformatMixin): ...@@ -516,7 +525,7 @@ class TestComputer(SlapformatMixin):
} }
INTERFACE_DICT['tap'] = { INTERFACE_DICT['tap'] = {
socket.AF_INET6: [{'addr': '2a01:e35:2e27:3456::e59c', 'netmask': 'ffff:ffff:ffff:ffff:ffff::'}] socket.AF_INET6: [{'addr': '2a01:e35:2e27:3456:1357:7890:ffff:ffff', 'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff'}]
} }
computer.format(alter_user=False) computer.format(alter_user=False)
...@@ -531,10 +540,12 @@ class TestComputer(SlapformatMixin): ...@@ -531,10 +540,12 @@ class TestComputer(SlapformatMixin):
self.assertEqual([ self.assertEqual([
'ip tuntap add dev tap mode tap user testuser', 'ip tuntap add dev tap mode tap user testuser',
'ip link set tap up', 'ip link set tap up',
'ip addr add ip/ffff:ffff:ffff:ffff:ffff:ffff:: dev tap', 'ip addr add ip/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff dev tap',
'ip -6 addr list tap', 'ip -6 addr list tap',
'ip route show 10.8.0.2', 'ip route show 10.8.0.2',
'ip route add 10.8.0.2 dev tap', 'ip route add 10.8.0.2 dev tap',
'ip -6 route show ip/96',
'ip -6 route add ip/96 dev tap',
'ip addr add ip/255.255.255.255 dev iface', 'ip addr add ip/255.255.255.255 dev iface',
'ip addr add ip/ffff:ffff:ffff:ffff:ffff:: dev iface', 'ip addr add ip/ffff:ffff:ffff:ffff:ffff:: dev iface',
'ip -6 addr list iface' 'ip -6 addr list iface'
......
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