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
import slapos.util
from slapos.util import mkdir_p
from slapos.util import ipv6FromBin
from slapos.util import binFromIpv6
import slapos.slap as slap
from slapos import version
from slapos import manager as slapmanager
......@@ -443,6 +445,10 @@ class Computer(object):
tap.ipv4_netmask = partition_dict['tap'].get('ipv4_netmask', '')
tap.ipv4_gateway = partition_dict['tap'].get('ipv4_gateway', '')
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:
tap = Tap(partition_dict['reference'])
......@@ -613,9 +619,9 @@ class Computer(object):
partition.tap.ipv4_network = gateway_addr_dict['network']
if not partition.tap.ipv6_addr:
ipv6_addr = self.interface.addIPv6Address(tap=partition.tap)
partition.tap.ipv6_addr = ""
partition.tap.ipv6_netmask = ""
ipv6_dict = self.interface.addIPv6Address(tap=partition.tap)
partition.tap.ipv6_addr = ipv6_dict['addr']
partition.tap.ipv6_netmask = ipv6_dict['netmask']
partition.tap.ipv6_gateway = ""
partition.tap.ipv6_network = ""
......@@ -1115,7 +1121,7 @@ class Interface(object):
tap: tap interface
Returns:
Tuple of (address, netmask).
dict(addr=address, netmask=netmask).
Raises:
AddressGenerationError: Couldn't construct valid address with existing
......@@ -1157,15 +1163,24 @@ class Interface(object):
# Try 10 times to add address, raise in case if not possible
try_num = 10
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:
if tap:
cut = -3
if "::" in address_dict['addr']:
cut = -2
addr = ':'.join(address_dict['addr'].split(':')[:cut] + ['%x' % (
random.randint(1, 65000), )] + ["ff", "ff"])
netmask = "ffff:ffff:ffff:ffff:ffff:ffff::"
addr = ipv6FromBin(prefix
+ bin(random.randint(1, 65000))[2:].zfill(16)
+ '1' * (128 - netmask_len))
else:
addr = ':'.join(address_dict['addr'].split(':')[:-1] + ['%x' % (
random.randint(1, 65000), )])
......
......@@ -464,7 +464,7 @@ 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:: dev tap',
'ip -6 addr list tap',
'ip route show 10.0.0.2',
'ip route add 10.0.0.2 dev tap',
......@@ -493,7 +493,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::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}]
socket.AF_INET6: [{'addr': '2a01:e35:2e27:3456::e59c', 'netmask': 'ffff:ffff:ffff:ffff:ffff::'}]
}
INTERFACE_DICT['eth1'] = {
socket.AF_INET: [{'addr': '10.8.0.1', 'broadcast': '10.8.0.254',
......@@ -501,7 +501,7 @@ class TestComputer(SlapformatMixin):
}
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)
......@@ -521,7 +521,7 @@ class TestComputer(SlapformatMixin):
'ip route show 10.8.0.2',
'ip route add 10.8.0.2 dev tap',
'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'
],
self.fakeCallAndRead.external_command_list)
......
......@@ -29,6 +29,8 @@
import errno
import os
import socket
import struct
import subprocess
import sqlite3
......@@ -104,3 +106,32 @@ def sqlite_connect(dburi, timeout=None):
conn = sqlite3.connect(dburi, **connect_kw)
conn.text_factory = str # allow 8-bit strings
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