Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
slapos.core
Commits
6272d68e
Commit
6272d68e
authored
Mar 08, 2023
by
Xavier Thompson
Browse files
Options
Browse Files
Download
Plain Diff
Slapformat: IPv6 range for partitions and tun
See merge request
nexedi/slapos.core!455
parents
7e2f8eec
c4e86b91
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
356 additions
and
120 deletions
+356
-120
slapos/cli/format.py
slapos/cli/format.py
+1
-1
slapos/format.py
slapos/format.py
+285
-105
slapos/tests/test_slapformat.py
slapos/tests/test_slapformat.py
+13
-14
slapos/util.py
slapos/util.py
+57
-0
No files found.
slapos/cli/format.py
View file @
6272d68e
...
...
@@ -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
)
...
...
slapos/format.py
View file @
6272d68e
...
...
@@ -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
.
addIPv6
Address
(
self
.
address
,
self
.
netmask
)
self
.
interface
.
_addSystem
Address
(
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
])
c
ode
,
_
=
c
allAndRead
([
'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'
:
...
...
slapos/tests/test_slapformat.py
View file @
6272d68e
...
...
@@ -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"
,
...
...
slapos/util.py
View file @
6272d68e
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment