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

Fix IPv6 generation for taps.

Each tap will have a subnetwork with 16 more bits than the interface.
The IPv6 of the tap is the last of the subnetwork.

Note: the example IPv6 address is the address used in documentation (see https://en.wikipedia.org/wiki/IPv6_address#Documentation)
parent 086504fa
...@@ -58,6 +58,8 @@ import xml_marshaller.xml_marshaller ...@@ -58,6 +58,8 @@ import xml_marshaller.xml_marshaller
import slapos.util import slapos.util
from slapos.util import mkdir_p from slapos.util import mkdir_p
from slapos.util import ipv6FromBin
from slapos.util import binFromIpv6
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
...@@ -443,6 +445,10 @@ class Computer(object): ...@@ -443,6 +445,10 @@ class Computer(object):
tap.ipv4_netmask = partition_dict['tap'].get('ipv4_netmask', '') tap.ipv4_netmask = partition_dict['tap'].get('ipv4_netmask', '')
tap.ipv4_gateway = partition_dict['tap'].get('ipv4_gateway', '') tap.ipv4_gateway = partition_dict['tap'].get('ipv4_gateway', '')
tap.ipv4_network = partition_dict['tap'].get('ipv4_network', '') tap.ipv4_network = partition_dict['tap'].get('ipv4_network', '')
tap.ipv6_addr = partition_dict['tap'].get('ipv6_addr', '')
tap.ipv6_netmask = partition_dict['tap'].get('ipv6_netmask', '')
tap.ipv6_gateway = partition_dict['tap'].get('ipv6_gateway', '')
tap.ipv6_network = partition_dict['tap'].get('ipv6_network', '')
else: else:
tap = Tap(partition_dict['reference']) tap = Tap(partition_dict['reference'])
...@@ -613,9 +619,9 @@ class Computer(object): ...@@ -613,9 +619,9 @@ class Computer(object):
partition.tap.ipv4_network = gateway_addr_dict['network'] partition.tap.ipv4_network = gateway_addr_dict['network']
if not partition.tap.ipv6_addr: if not partition.tap.ipv6_addr:
ipv6_addr = self.interface.addIPv6Address(tap=partition.tap) ipv6_dict = self.interface.addIPv6Address(tap=partition.tap)
partition.tap.ipv6_addr = "" partition.tap.ipv6_addr = ipv6_dict['addr']
partition.tap.ipv6_netmask = "" partition.tap.ipv6_netmask = ipv6_dict['netmask']
partition.tap.ipv6_gateway = "" partition.tap.ipv6_gateway = ""
partition.tap.ipv6_network = "" partition.tap.ipv6_network = ""
...@@ -1115,7 +1121,7 @@ class Interface(object): ...@@ -1115,7 +1121,7 @@ class Interface(object):
tap: tap interface tap: tap interface
Returns: Returns:
Tuple of (address, netmask). dict(addr=address, netmask=netmask).
Raises: Raises:
AddressGenerationError: Couldn't construct valid address with existing AddressGenerationError: Couldn't construct valid address with existing
...@@ -1157,15 +1163,24 @@ class Interface(object): ...@@ -1157,15 +1163,24 @@ class Interface(object):
# Try 10 times to add address, raise in case if not possible # Try 10 times to add address, raise in case if not possible
try_num = 10 try_num = 10
netmask = address_dict['netmask'] netmask = address_dict['netmask']
if tap:
netmask_len = len(binFromIpv6(netmask).rstrip('0'))
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 {} has netmask {} which is too big for generating IPv6 on taps.'.format(
interface_name, netmask))
raise AddressGenerationError(addr)
netmask = ipv6FromBin('1'*netmask_len)
while try_num > 0: while try_num > 0:
if tap: if tap:
cut = -3 addr = ipv6FromBin(prefix
if "::" in address_dict['addr']: + bin(random.randint(1, 65000))[2:].zfill(16)
cut = -2 + '1' * (128 - netmask_len))
addr = ':'.join(address_dict['addr'].split(':')[:cut] + ['%x' % (
random.randint(1, 65000), )] + ["ff", "ff"])
netmask = "ffff:ffff:ffff:ffff:ffff:ffff::"
else: else:
addr = ':'.join(address_dict['addr'].split(':')[:-1] + ['%x' % ( addr = ':'.join(address_dict['addr'].split(':')[:-1] + ['%x' % (
random.randint(1, 65000), )]) random.randint(1, 65000), )])
......
...@@ -464,7 +464,7 @@ class TestComputer(SlapformatMixin): ...@@ -464,7 +464,7 @@ 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:: 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',
...@@ -493,7 +493,7 @@ class TestComputer(SlapformatMixin): ...@@ -493,7 +493,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::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}] socket.AF_INET6: [{'addr': '2a01:e35:2e27:3456::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',
...@@ -501,7 +501,7 @@ class TestComputer(SlapformatMixin): ...@@ -501,7 +501,7 @@ class TestComputer(SlapformatMixin):
} }
INTERFACE_DICT['tap'] = { INTERFACE_DICT['tap'] = {
socket.AF_INET6: [{'addr': '2a01:e35:2e27::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}] socket.AF_INET6: [{'addr': '2a01:e35:2e27:3456::e59c', 'netmask': 'ffff:ffff:ffff:ffff:ffff::'}]
} }
computer.format(alter_user=False) computer.format(alter_user=False)
...@@ -521,7 +521,7 @@ class TestComputer(SlapformatMixin): ...@@ -521,7 +521,7 @@ class TestComputer(SlapformatMixin):
'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 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:: dev iface', 'ip addr add ip/ffff:ffff:ffff:ffff:ffff:: dev iface',
'ip -6 addr list iface' 'ip -6 addr list iface'
], ],
self.fakeCallAndRead.external_command_list) self.fakeCallAndRead.external_command_list)
......
...@@ -29,6 +29,8 @@ ...@@ -29,6 +29,8 @@
import errno import errno
import os import os
import socket
import struct
import subprocess import subprocess
import sqlite3 import sqlite3
...@@ -104,3 +106,32 @@ def sqlite_connect(dburi, timeout=None): ...@@ -104,3 +106,32 @@ def sqlite_connect(dburi, timeout=None):
conn = sqlite3.connect(dburi, **connect_kw) conn = sqlite3.connect(dburi, **connect_kw)
conn.text_factory = str # allow 8-bit strings conn.text_factory = str # allow 8-bit strings
return conn return conn
# The 3 functions below were imported from re6st:
# https://lab.nexedi.com/nexedi/re6stnet/blob/master/re6st/utils.py
def binFromRawIpv6(ip):
ip1, ip2 = struct.unpack('>QQ', ip)
return bin(ip1)[2:].rjust(64, '0') + bin(ip2)[2:].rjust(64, '0')
def binFromIpv6(ip):
"""
convert an IPv6 to a 128 characters string containing 0 and 1
e.g.: '2001:db8::'-> '001000000000000100001101101110000000000...000'
"""
return binFromRawIpv6(socket.inet_pton(socket.AF_INET6, ip))
def ipv6FromBin(ip, suffix=''):
"""
convert a string containing 0 and 1 to an IPv6
if the string is less than 128 characters:
* consider the string is the first bits
* optionnaly can replace the last bits of the IP with a suffix (in binary string format)
"""
suffix_len = 128 - len(ip)
if suffix_len > 0:
ip += suffix.rjust(suffix_len, '0')
elif suffix_len:
sys.exit("Prefix exceeds 128 bits")
return socket.inet_ntop(socket.AF_INET6,
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
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