summaryrefslogtreecommitdiff
path: root/cloudinit/net
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/net')
-rw-r--r--cloudinit/net/__init__.py3
-rwxr-xr-xcloudinit/net/cmdline.py36
-rw-r--r--cloudinit/net/netplan.py24
-rw-r--r--cloudinit/net/network_state.py4
-rw-r--r--cloudinit/net/sysconfig.py246
5 files changed, 200 insertions, 113 deletions
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
index a072a8d6..8c6cd057 100644
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -393,6 +393,7 @@ def get_interfaces_by_mac():
else:
raise
ret = {}
+ empty_mac = '00:00:00:00:00:00'
for name in devs:
if not interface_has_own_mac(name):
continue
@@ -404,6 +405,8 @@ def get_interfaces_by_mac():
# some devices may not have a mac (tun0)
if not mac:
continue
+ if mac == empty_mac and name != 'lo':
+ continue
if mac in ret:
raise RuntimeError(
"duplicate mac found! both '%s' and '%s' have mac '%s'" %
diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py
index 7c5d11a7..38b27a52 100755
--- a/cloudinit/net/cmdline.py
+++ b/cloudinit/net/cmdline.py
@@ -9,41 +9,12 @@ import base64
import glob
import gzip
import io
-import shlex
-import sys
-
-import six
from . import get_devicelist
from . import read_sys_net_safe
from cloudinit import util
-PY26 = sys.version_info[0:2] == (2, 6)
-
-
-def _shlex_split(blob):
- if PY26 and isinstance(blob, six.text_type):
- # Older versions don't support unicode input
- blob = blob.encode("utf8")
- return shlex.split(blob)
-
-
-def _load_shell_content(content, add_empty=False, empty_val=None):
- """Given shell like syntax (key=value\nkey2=value2\n) in content
- return the data in dictionary form. If 'add_empty' is True
- then add entries in to the returned dictionary for 'VAR='
- variables. Set their value to empty_val."""
- data = {}
- for line in _shlex_split(content):
- key, value = line.split("=", 1)
- if not value:
- value = empty_val
- if add_empty or value:
- data[key] = value
-
- return data
-
def _klibc_to_config_entry(content, mac_addrs=None):
"""Convert a klibc written shell content file to a 'config' entry
@@ -63,7 +34,7 @@ def _klibc_to_config_entry(content, mac_addrs=None):
if mac_addrs is None:
mac_addrs = {}
- data = _load_shell_content(content)
+ data = util.load_shell_content(content)
try:
name = data['DEVICE'] if 'DEVICE' in data else data['DEVICE6']
except KeyError:
@@ -100,6 +71,11 @@ def _klibc_to_config_entry(content, mac_addrs=None):
cur_proto = data.get(pre + 'PROTO', proto)
subnet = {'type': cur_proto, 'control': 'manual'}
+ # only populate address for static types. While the rendered config
+ # may have an address for dhcp, that is not really expected.
+ if cur_proto == 'static':
+ subnet['address'] = data[pre + 'ADDR']
+
# these fields go right on the subnet
for key in ('NETMASK', 'BROADCAST', 'GATEWAY'):
if pre + key in data:
diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py
index 825fe831..a715f3b0 100644
--- a/cloudinit/net/netplan.py
+++ b/cloudinit/net/netplan.py
@@ -4,7 +4,7 @@ import copy
import os
from . import renderer
-from .network_state import subnet_is_ipv6
+from .network_state import mask2cidr, subnet_is_ipv6
from cloudinit import log as logging
from cloudinit import util
@@ -41,7 +41,7 @@ NET_CONFIG_TO_V2 = {
'bond-num-grat-arp': 'gratuitious-arp',
'bond-primary-reselect': 'primary-reselect-policy',
'bond-updelay': 'up-delay',
- 'bond-xmit_hash_policy': 'transmit_hash_policy'},
+ 'bond-xmit-hash-policy': 'transmit-hash-policy'},
'bridge': {'bridge_ageing': 'ageing-time',
'bridge_bridgeprio': 'priority',
'bridge_fd': 'forward-delay',
@@ -118,9 +118,10 @@ def _extract_addresses(config, entry):
sn_type += '4'
entry.update({sn_type: True})
elif sn_type in ['static']:
- addr = "%s" % subnet.get('address')
- if 'netmask' in subnet:
- addr += "/%s" % subnet.get('netmask')
+ addr = '%s' % subnet.get('address')
+ netmask = subnet.get('netmask')
+ if netmask and '/' not in addr:
+ addr += '/%s' % mask2cidr(netmask)
if 'gateway' in subnet and subnet.get('gateway'):
gateway = subnet.get('gateway')
if ":" in gateway:
@@ -137,8 +138,9 @@ def _extract_addresses(config, entry):
mtukey += '6'
entry.update({mtukey: subnet.get('mtu')})
for route in subnet.get('routes', []):
- to_net = "%s/%s" % (route.get('network'),
- route.get('netmask'))
+ network = route.get('network')
+ netmask = route.get('netmask')
+ to_net = '%s/%s' % (network, mask2cidr(netmask))
route = {
'via': route.get('gateway'),
'to': to_net,
@@ -205,7 +207,7 @@ class Renderer(renderer.Renderer):
self._postcmds = config.get('postcmds', False)
self.clean_default = config.get('clean_default', True)
- def render_network_state(self, target, network_state):
+ def render_network_state(self, network_state, target):
# check network state for version
# if v2, then extract network_state.config
# else render_v2_from_state
@@ -294,7 +296,7 @@ class Renderer(renderer.Renderer):
for match in ['bond_', 'bond-']:
bond_params = _get_params_dict_by_match(ifcfg, match)
for (param, value) in bond_params.items():
- newname = v2_bond_map.get(param)
+ newname = v2_bond_map.get(param.replace('_', '-'))
if newname is None:
continue
bond_config.update({newname: value})
@@ -345,7 +347,9 @@ class Renderer(renderer.Renderer):
'id': ifcfg.get('vlan_id'),
'link': ifcfg.get('vlan-raw-device')
}
-
+ macaddr = ifcfg.get('mac_address', None)
+ if macaddr is not None:
+ vlan['macaddress'] = macaddr.lower()
_extract_addresses(ifcfg, vlan)
vlans.update({ifname: vlan})
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index db3c3579..9e9c05a0 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -734,9 +734,9 @@ def ipv6mask2cidr(mask):
def mask2cidr(mask):
- if ':' in mask:
+ if ':' in str(mask):
return ipv6mask2cidr(mask)
- elif '.' in mask:
+ elif '.' in str(mask):
return ipv4mask2cidr(mask)
else:
return mask
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 504e4d02..58c5713f 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -59,6 +59,9 @@ class ConfigMap(object):
def __setitem__(self, key, value):
self._conf[key] = value
+ def __getitem__(self, key):
+ return self._conf[key]
+
def drop(self, key):
self._conf.pop(key, None)
@@ -83,7 +86,8 @@ class ConfigMap(object):
class Route(ConfigMap):
"""Represents a route configuration."""
- route_fn_tpl = '%(base)s/network-scripts/route-%(name)s'
+ route_fn_tpl_ipv4 = '%(base)s/network-scripts/route-%(name)s'
+ route_fn_tpl_ipv6 = '%(base)s/network-scripts/route6-%(name)s'
def __init__(self, route_name, base_sysconf_dir):
super(Route, self).__init__()
@@ -102,9 +106,58 @@ class Route(ConfigMap):
return r
@property
- def path(self):
- return self.route_fn_tpl % ({'base': self._base_sysconf_dir,
- 'name': self._route_name})
+ def path_ipv4(self):
+ return self.route_fn_tpl_ipv4 % ({'base': self._base_sysconf_dir,
+ 'name': self._route_name})
+
+ @property
+ def path_ipv6(self):
+ return self.route_fn_tpl_ipv6 % ({'base': self._base_sysconf_dir,
+ 'name': self._route_name})
+
+ def is_ipv6_route(self, address):
+ return ':' in address
+
+ def to_string(self, proto="ipv4"):
+ # only accept ipv4 and ipv6
+ if proto not in ['ipv4', 'ipv6']:
+ raise ValueError("Unknown protocol '%s'" % (str(proto)))
+ buf = six.StringIO()
+ buf.write(_make_header())
+ if self._conf:
+ buf.write("\n")
+ # need to reindex IPv4 addresses
+ # (because Route can contain a mix of IPv4 and IPv6)
+ reindex = -1
+ for key in sorted(self._conf.keys()):
+ if 'ADDRESS' in key:
+ index = key.replace('ADDRESS', '')
+ address_value = str(self._conf[key])
+ # only accept combinations:
+ # if proto ipv6 only display ipv6 routes
+ # if proto ipv4 only display ipv4 routes
+ # do not add ipv6 routes if proto is ipv4
+ # do not add ipv4 routes if proto is ipv6
+ # (this array will contain a mix of ipv4 and ipv6)
+ if proto == "ipv4" and not self.is_ipv6_route(address_value):
+ netmask_value = str(self._conf['NETMASK' + index])
+ gateway_value = str(self._conf['GATEWAY' + index])
+ # increase IPv4 index
+ reindex = reindex + 1
+ buf.write("%s=%s\n" % ('ADDRESS' + str(reindex),
+ _quote_value(address_value)))
+ buf.write("%s=%s\n" % ('GATEWAY' + str(reindex),
+ _quote_value(gateway_value)))
+ buf.write("%s=%s\n" % ('NETMASK' + str(reindex),
+ _quote_value(netmask_value)))
+ elif proto == "ipv6" and self.is_ipv6_route(address_value):
+ netmask_value = str(self._conf['NETMASK' + index])
+ gateway_value = str(self._conf['GATEWAY' + index])
+ buf.write("%s/%s via %s\n" % (address_value,
+ netmask_value,
+ gateway_value))
+
+ return buf.getvalue()
class NetInterface(ConfigMap):
@@ -211,65 +264,119 @@ class Renderer(renderer.Renderer):
iface_cfg[new_key] = old_value
@classmethod
- def _render_subnet(cls, iface_cfg, route_cfg, subnet):
- subnet_type = subnet.get('type')
- if subnet_type == 'dhcp6':
- iface_cfg['DHCPV6C'] = True
- iface_cfg['IPV6INIT'] = True
- iface_cfg['BOOTPROTO'] = 'dhcp'
- elif subnet_type in ['dhcp4', 'dhcp']:
- iface_cfg['BOOTPROTO'] = 'dhcp'
- elif subnet_type == 'static':
- iface_cfg['BOOTPROTO'] = 'static'
- if subnet_is_ipv6(subnet):
- iface_cfg['IPV6ADDR'] = subnet['address']
+ def _render_subnets(cls, iface_cfg, subnets):
+ # setting base values
+ iface_cfg['BOOTPROTO'] = 'none'
+
+ # modifying base values according to subnets
+ for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
+ subnet_type = subnet.get('type')
+ if subnet_type == 'dhcp6':
iface_cfg['IPV6INIT'] = True
+ iface_cfg['DHCPV6C'] = True
+ iface_cfg['BOOTPROTO'] = 'dhcp'
+ elif subnet_type in ['dhcp4', 'dhcp']:
+ iface_cfg['BOOTPROTO'] = 'dhcp'
+ elif subnet_type == 'static':
+ # grep BOOTPROTO sysconfig.txt -A2 | head -3
+ # BOOTPROTO=none|bootp|dhcp
+ # 'bootp' or 'dhcp' cause a DHCP client
+ # to run on the device. Any other
+ # value causes any static configuration
+ # in the file to be applied.
+ # ==> the following should not be set to 'static'
+ # but should remain 'none'
+ # if iface_cfg['BOOTPROTO'] == 'none':
+ # iface_cfg['BOOTPROTO'] = 'static'
+ if subnet_is_ipv6(subnet):
+ iface_cfg['IPV6INIT'] = True
else:
- iface_cfg['IPADDR'] = subnet['address']
- else:
- raise ValueError("Unknown subnet type '%s' found"
- " for interface '%s'" % (subnet_type,
- iface_cfg.name))
- if 'netmask' in subnet:
- iface_cfg['NETMASK'] = subnet['netmask']
- for route in subnet.get('routes', []):
- if subnet.get('ipv6'):
- gw_cfg = 'IPV6_DEFAULTGW'
- else:
- gw_cfg = 'GATEWAY'
-
- if _is_default_route(route):
- if (
- (subnet.get('ipv4') and
- route_cfg.has_set_default_ipv4) or
- (subnet.get('ipv6') and
- route_cfg.has_set_default_ipv6)
- ):
- raise ValueError("Duplicate declaration of default "
- "route found for interface '%s'"
- % (iface_cfg.name))
- # NOTE(harlowja): ipv6 and ipv4 default gateways
- gw_key = 'GATEWAY0'
- nm_key = 'NETMASK0'
- addr_key = 'ADDRESS0'
- # The owning interface provides the default route.
- #
- # TODO(harlowja): add validation that no other iface has
- # also provided the default route?
- iface_cfg['DEFROUTE'] = True
- if 'gateway' in route:
- iface_cfg[gw_cfg] = route['gateway']
- route_cfg.has_set_default = True
- else:
- gw_key = 'GATEWAY%s' % route_cfg.last_idx
- nm_key = 'NETMASK%s' % route_cfg.last_idx
- addr_key = 'ADDRESS%s' % route_cfg.last_idx
- route_cfg.last_idx += 1
- for (old_key, new_key) in [('gateway', gw_key),
- ('netmask', nm_key),
- ('network', addr_key)]:
- if old_key in route:
- route_cfg[new_key] = route[old_key]
+ raise ValueError("Unknown subnet type '%s' found"
+ " for interface '%s'" % (subnet_type,
+ iface_cfg.name))
+
+ # set IPv4 and IPv6 static addresses
+ ipv4_index = -1
+ ipv6_index = -1
+ for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
+ subnet_type = subnet.get('type')
+ if subnet_type == 'dhcp6':
+ continue
+ elif subnet_type in ['dhcp4', 'dhcp']:
+ continue
+ elif subnet_type == 'static':
+ if subnet_is_ipv6(subnet):
+ ipv6_index = ipv6_index + 1
+ if 'netmask' in subnet and str(subnet['netmask']) != "":
+ ipv6_cidr = (subnet['address'] +
+ '/' +
+ str(subnet['netmask']))
+ else:
+ ipv6_cidr = subnet['address']
+ if ipv6_index == 0:
+ iface_cfg['IPV6ADDR'] = ipv6_cidr
+ elif ipv6_index == 1:
+ iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr
+ else:
+ iface_cfg['IPV6ADDR_SECONDARIES'] = (
+ iface_cfg['IPV6ADDR_SECONDARIES'] +
+ " " + ipv6_cidr)
+ else:
+ ipv4_index = ipv4_index + 1
+ if ipv4_index == 0:
+ iface_cfg['IPADDR'] = subnet['address']
+ if 'netmask' in subnet:
+ iface_cfg['NETMASK'] = subnet['netmask']
+ else:
+ iface_cfg['IPADDR' + str(ipv4_index)] = \
+ subnet['address']
+ if 'netmask' in subnet:
+ iface_cfg['NETMASK' + str(ipv4_index)] = \
+ subnet['netmask']
+
+ @classmethod
+ def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets):
+ for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
+ for route in subnet.get('routes', []):
+ is_ipv6 = subnet.get('ipv6')
+
+ if _is_default_route(route):
+ if (
+ (subnet.get('ipv4') and
+ route_cfg.has_set_default_ipv4) or
+ (subnet.get('ipv6') and
+ route_cfg.has_set_default_ipv6)
+ ):
+ raise ValueError("Duplicate declaration of default "
+ "route found for interface '%s'"
+ % (iface_cfg.name))
+ # NOTE(harlowja): ipv6 and ipv4 default gateways
+ gw_key = 'GATEWAY0'
+ nm_key = 'NETMASK0'
+ addr_key = 'ADDRESS0'
+ # The owning interface provides the default route.
+ #
+ # TODO(harlowja): add validation that no other iface has
+ # also provided the default route?
+ iface_cfg['DEFROUTE'] = True
+ if 'gateway' in route:
+ if is_ipv6:
+ iface_cfg['IPV6_DEFAULTGW'] = route['gateway']
+ route_cfg.has_set_default_ipv6 = True
+ else:
+ iface_cfg['GATEWAY'] = route['gateway']
+ route_cfg.has_set_default_ipv4 = True
+
+ else:
+ gw_key = 'GATEWAY%s' % route_cfg.last_idx
+ nm_key = 'NETMASK%s' % route_cfg.last_idx
+ addr_key = 'ADDRESS%s' % route_cfg.last_idx
+ route_cfg.last_idx += 1
+ for (old_key, new_key) in [('gateway', gw_key),
+ ('netmask', nm_key),
+ ('network', addr_key)]:
+ if old_key in route:
+ route_cfg[new_key] = route[old_key]
@classmethod
def _render_bonding_opts(cls, iface_cfg, iface):
@@ -295,15 +402,9 @@ class Renderer(renderer.Renderer):
iface_subnets = iface.get("subnets", [])
iface_cfg = iface_contents[iface_name]
route_cfg = iface_cfg.routes
- if len(iface_subnets) == 1:
- cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0])
- elif len(iface_subnets) > 1:
- for i, isubnet in enumerate(iface_subnets,
- start=len(iface_cfg.children)):
- iface_sub_cfg = iface_cfg.copy()
- iface_sub_cfg.name = "%s:%s" % (iface_name, i)
- iface_cfg.children.append(iface_sub_cfg)
- cls._render_subnet(iface_sub_cfg, route_cfg, isubnet)
+
+ cls._render_subnets(iface_cfg, iface_subnets)
+ cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
@classmethod
def _render_bond_interfaces(cls, network_state, iface_contents):
@@ -387,7 +488,10 @@ class Renderer(renderer.Renderer):
if iface_cfg:
contents[iface_cfg.path] = iface_cfg.to_string()
if iface_cfg.routes:
- contents[iface_cfg.routes.path] = iface_cfg.routes.to_string()
+ contents[iface_cfg.routes.path_ipv4] = \
+ iface_cfg.routes.to_string("ipv4")
+ contents[iface_cfg.routes.path_ipv6] = \
+ iface_cfg.routes.to_string("ipv6")
return contents
def render_network_state(self, network_state, target=None):