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
=======
1.4.16 (???)
-------------------
* format: new tap_iv6 configuration file option
1.4.15 (2018-12-11)
-------------------
......
......@@ -20,6 +20,9 @@ log_file = /opt/slapos/log/slapos-node-format.log
partition_base_name = slappart
user_base_name = slapuser
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
# current machine configuration
ipv4_local_network = 10.0.0.0/16
......
......@@ -246,7 +246,7 @@ class Computer(object):
def __init__(self, reference, interface=None, addr=None, netmask=None,
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,
partition_list=None, config=None):
"""
......@@ -264,6 +264,7 @@ class Computer(object):
self.ipv6_interface = ipv6_interface
self.software_user = software_user
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)
assert instance_root is not None and software_root is not None, \
......@@ -402,7 +403,7 @@ class Computer(object):
@classmethod
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.
......@@ -424,6 +425,7 @@ class Computer(object):
ipv6_interface=ipv6_interface,
software_user=dumped_dict.get('software_user', 'slapsoft'),
tap_gateway_interface=tap_gateway_interface,
tap_ipv6=tap_ipv6,
software_root=dumped_dict.get('software_root', software_root),
instance_root=dumped_dict.get('instance_root', instance_root),
config=config,
......@@ -612,24 +614,32 @@ class Computer(object):
partition.tap.ipv4_gateway = gateway_addr_dict['addr']
partition.tap.ipv4_network = gateway_addr_dict['network']
if not partition.tap.ipv6_addr:
# create a new IPv6 randomly for the tap
ipv6_dict = self.interface.addIPv6Address(tap=partition.tap)
partition.tap.ipv6_addr = ipv6_dict['addr']
partition.tap.ipv6_netmask = ipv6_dict['netmask']
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)
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:
# 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 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.ipv6_addr = ''
partition.tap.ipv6_netmask = ''
partition.tap.ipv6_gateway = ''
partition.tap.ipv6_network = ''
# create IPv4 and IPv6 routes
partition.tap.createRoutes()
if partition.tun is not None:
......@@ -878,19 +888,25 @@ class Tap(object):
def createRoutes(self):
"""
Configure ipv4 route to reach this interface from local network
Configure ipv4 and ipv6 routes
"""
if self.ipv4_addr:
# Check if this route exits
code, result = callAndRead(['ip', 'route', 'show', self.ipv4_addr],
raise_on_error=False)
if code == 0 and self.ipv4_addr in result and self.name in result:
return
callAndRead(['ip', 'route', 'add', self.ipv4_addr, 'dev', self.name])
if code != 0 or self.ipv4_addr not in result or self.name not in result:
callAndRead(['ip', 'route', 'add', self.ipv4_addr, 'dev', self.name])
else:
raise ValueError("%s should not be empty. No ipv4 address assigned to %s" %
(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):
"""Represent TUN interface which might be many per user."""
......@@ -1146,7 +1162,7 @@ class Interface(object):
# confirmed to be configured
return dict_addr_netmask
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
interface_network = netaddr.ip.IPNetwork('%s/%s' % (address_dict['addr'],
netmaskToPrefixIPv6(address_dict['netmask'])))
......@@ -1175,7 +1191,7 @@ class Interface(object):
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'*netmask_len)
netmask = ipv6FromBin('1'*128) # the netmask of the tap itself is always 128 bits
while try_num > 0:
if tap:
......@@ -1221,6 +1237,7 @@ def parse_computer_definition(conf, definition_path):
ipv6_interface=conf.ipv6_interface,
software_user=computer_definition.get('computer', 'software_user'),
tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6,
software_root=conf.software_root,
instance_root=conf.instance_root
)
......@@ -1259,6 +1276,7 @@ def parse_computer_xml(conf, xml_path):
reference=conf.computer_id,
ipv6_interface=conf.ipv6_interface,
tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6,
software_root=conf.software_root,
instance_root=conf.instance_root,
config=conf)
......@@ -1277,6 +1295,7 @@ def parse_computer_xml(conf, xml_path):
ipv6_interface=conf.ipv6_interface,
software_user=conf.software_user,
tap_gateway_interface=conf.tap_gateway_interface,
tap_ipv6=conf.tap_ipv6,
config=conf,
)
......@@ -1389,8 +1408,9 @@ class FormatConfig(object):
create_tap = True
create_tun = False
tap_base_name = None
ipv4_local_network = None
tap_ipv6 = True
tap_gateway_interface = ''
ipv4_local_network = None
use_unique_local_address_block = False
# User options
......@@ -1450,7 +1470,7 @@ class FormatConfig(object):
raise UsageError(message)
# 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)
if isinstance(attr, str):
if attr.lower() == 'true':
......@@ -1489,7 +1509,7 @@ class FormatConfig(object):
if self.dry_run:
self.logger.info("Dry-run mode enabled.")
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
self.computer_xml = os.path.abspath(self.computer_xml)
......
......@@ -121,6 +121,11 @@ class FakeCallAndRead:
retval = 0, str(INTERFACE_DICT)
elif argument_list[:3] == ['ip', 'route', 'show']:
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']:
retval = 0, 'OK'
self.external_command_list.append(' '.join(argument_list))
......@@ -448,6 +453,7 @@ class TestComputer(SlapformatMixin):
computer = slapos.format.Computer('computer',
instance_root='/instance_root',
software_root='/software_root',
tap_ipv6=True,
interface=slapos.format.Interface(
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[
......@@ -479,10 +485,12 @@ class TestComputer(SlapformatMixin):
self.assertEqual([
'ip tuntap add dev tap mode tap user testuser',
'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 route show 10.0.0.2',
'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 list myinterface',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev myinterface',
......@@ -495,6 +503,7 @@ class TestComputer(SlapformatMixin):
instance_root='/instance_root',
software_root='/software_root',
tap_gateway_interface='eth1',
tap_ipv6=True,
interface=slapos.format.Interface(
logger=self.logger, name='iface', ipv4_local_network='127.0.0.1/16'),
partition_list=[
......@@ -508,7 +517,7 @@ class TestComputer(SlapformatMixin):
INTERFACE_DICT['iface'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
'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'] = {
socket.AF_INET: [{'addr': '10.8.0.1', 'broadcast': '10.8.0.254',
......@@ -516,7 +525,7 @@ class TestComputer(SlapformatMixin):
}
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)
......@@ -531,10 +540,12 @@ class TestComputer(SlapformatMixin):
self.assertEqual([
'ip tuntap add dev tap mode tap user testuser',
'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 route show 10.8.0.2',
'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/ffff:ffff:ffff:ffff:ffff:: dev 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