|
Packit Service |
a04d08 |
# Copyright (C) 2013-2014 Canonical Ltd.
|
|
Packit Service |
a04d08 |
#
|
|
Packit Service |
a04d08 |
# Author: Scott Moser <scott.moser@canonical.com>
|
|
Packit Service |
a04d08 |
# Author: Blake Rouse <blake.rouse@canonical.com>
|
|
Packit Service |
a04d08 |
#
|
|
Packit Service |
a04d08 |
# This file is part of cloud-init. See LICENSE file for license information.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
import abc
|
|
Packit Service |
a04d08 |
import base64
|
|
Packit Service |
a04d08 |
import glob
|
|
Packit Service |
a04d08 |
import gzip
|
|
Packit Service |
a04d08 |
import io
|
|
Packit Service |
9bfd13 |
import logging
|
|
Packit Service |
a04d08 |
import os
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from cloudinit import util
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from . import get_devicelist
|
|
Packit Service |
a04d08 |
from . import read_sys_net_safe
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
_OPEN_ISCSI_INTERFACE_FILE = "/run/initramfs/open-iscsi.interface"
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
KERNEL_CMDLINE_NETWORK_CONFIG_DISABLED = "disabled"
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
class InitramfsNetworkConfigSource(metaclass=abc.ABCMeta):
|
|
Packit Service |
a04d08 |
"""ABC for net config sources that read config written by initramfses"""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@abc.abstractmethod
|
|
Packit Service |
9bfd13 |
def is_applicable(self) -> bool:
|
|
Packit Service |
a04d08 |
"""Is this initramfs config source applicable to the current system?"""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@abc.abstractmethod
|
|
Packit Service |
9bfd13 |
def render_config(self) -> dict:
|
|
Packit Service |
a04d08 |
"""Render a v1 network config from the initramfs configuration"""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class KlibcNetworkConfigSource(InitramfsNetworkConfigSource):
|
|
Packit Service |
a04d08 |
"""InitramfsNetworkConfigSource for klibc initramfs (i.e. Debian/Ubuntu)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Has three parameters, but they are intended to make testing simpler, _not_
|
|
Packit Service |
a04d08 |
for use in production code. (This is indicated by the prepended
|
|
Packit Service |
a04d08 |
underscores.)
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __init__(self, _files=None, _mac_addrs=None, _cmdline=None):
|
|
Packit Service |
a04d08 |
self._files = _files
|
|
Packit Service |
a04d08 |
self._mac_addrs = _mac_addrs
|
|
Packit Service |
a04d08 |
self._cmdline = _cmdline
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Set defaults here, as they require computation that we don't want to
|
|
Packit Service |
a04d08 |
# do at method definition time
|
|
Packit Service |
a04d08 |
if self._files is None:
|
|
Packit Service |
a04d08 |
self._files = _get_klibc_net_cfg_files()
|
|
Packit Service |
a04d08 |
if self._cmdline is None:
|
|
Packit Service |
a04d08 |
self._cmdline = util.get_cmdline()
|
|
Packit Service |
a04d08 |
if self._mac_addrs is None:
|
|
Packit Service |
a04d08 |
self._mac_addrs = {}
|
|
Packit Service |
a04d08 |
for k in get_devicelist():
|
|
Packit Service |
a04d08 |
mac_addr = read_sys_net_safe(k, 'address')
|
|
Packit Service |
a04d08 |
if mac_addr:
|
|
Packit Service |
a04d08 |
self._mac_addrs[k] = mac_addr
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
def is_applicable(self) -> bool:
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
Return whether this system has klibc initramfs network config or not
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Will return True if:
|
|
Packit Service |
a04d08 |
(a) klibc files exist in /run, AND
|
|
Packit Service |
a04d08 |
(b) either:
|
|
Packit Service |
a04d08 |
(i) ip= or ip6= are on the kernel cmdline, OR
|
|
Packit Service |
a04d08 |
(ii) an open-iscsi interface file is present in the system
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
if self._files:
|
|
Packit Service |
a04d08 |
if 'ip=' in self._cmdline or 'ip6=' in self._cmdline:
|
|
Packit Service |
a04d08 |
return True
|
|
Packit Service |
a04d08 |
if os.path.exists(_OPEN_ISCSI_INTERFACE_FILE):
|
|
Packit Service |
a04d08 |
# iBft can configure networking without ip=
|
|
Packit Service |
a04d08 |
return True
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
def render_config(self) -> dict:
|
|
Packit Service |
a04d08 |
return config_from_klibc_net_cfg(
|
|
Packit Service |
a04d08 |
files=self._files, mac_addrs=self._mac_addrs,
|
|
Packit Service |
a04d08 |
)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
_INITRAMFS_CONFIG_SOURCES = [KlibcNetworkConfigSource]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _klibc_to_config_entry(content, mac_addrs=None):
|
|
Packit Service |
a04d08 |
"""Convert a klibc written shell content file to a 'config' entry
|
|
Packit Service |
a04d08 |
When ip= is seen on the kernel command line in debian initramfs
|
|
Packit Service |
a04d08 |
and networking is brought up, ipconfig will populate
|
|
Packit Service |
a04d08 |
/run/net-<name>.cfg.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
The files are shell style syntax, and examples are in the tests
|
|
Packit Service |
a04d08 |
provided here. There is no good documentation on this unfortunately.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
DEVICE=<name> is expected/required and PROTO should indicate if
|
|
Packit Service |
9bfd13 |
this is 'none' (static) or 'dhcp' or 'dhcp6' (LP: #1621507).
|
|
Packit Service |
a04d08 |
note that IPV6PROTO is also written by newer code to address the
|
|
Packit Service |
a04d08 |
possibility of both ipv4 and ipv6 getting addresses.
|
|
Packit Service |
9bfd13 |
|
|
Packit Service |
9bfd13 |
Full syntax is documented at:
|
|
Packit Service |
9bfd13 |
https://git.kernel.org/pub/scm/libs/klibc/klibc.git/plain/usr/kinit/ipconfig/README.ipconfig
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if mac_addrs is None:
|
|
Packit Service |
a04d08 |
mac_addrs = {}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
data = util.load_shell_content(content)
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
name = data['DEVICE'] if 'DEVICE' in data else data['DEVICE6']
|
|
Packit Service |
9bfd13 |
except KeyError as e:
|
|
Packit Service |
9bfd13 |
raise ValueError("no 'DEVICE' or 'DEVICE6' entry in data") from e
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# ipconfig on precise does not write PROTO
|
|
Packit Service |
a04d08 |
# IPv6 config gives us IPV6PROTO, not PROTO.
|
|
Packit Service |
a04d08 |
proto = data.get('PROTO', data.get('IPV6PROTO'))
|
|
Packit Service |
a04d08 |
if not proto:
|
|
Packit Service |
a04d08 |
if data.get('filename'):
|
|
Packit Service |
a04d08 |
proto = 'dhcp'
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
9bfd13 |
proto = 'none'
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
if proto not in ('none', 'dhcp', 'dhcp6'):
|
|
Packit Service |
a04d08 |
raise ValueError("Unexpected value for PROTO: %s" % proto)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
iface = {
|
|
Packit Service |
a04d08 |
'type': 'physical',
|
|
Packit Service |
a04d08 |
'name': name,
|
|
Packit Service |
a04d08 |
'subnets': [],
|
|
Packit Service |
a04d08 |
}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if name in mac_addrs:
|
|
Packit Service |
a04d08 |
iface['mac_address'] = mac_addrs[name]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Handle both IPv4 and IPv6 values
|
|
Packit Service |
a04d08 |
for pre in ('IPV4', 'IPV6'):
|
|
Packit Service |
a04d08 |
# if no IPV4ADDR or IPV6ADDR, then go on.
|
|
Packit Service |
a04d08 |
if pre + "ADDR" not in data:
|
|
Packit Service |
a04d08 |
continue
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# PROTO for ipv4, IPV6PROTO for ipv6
|
|
Packit Service |
a04d08 |
cur_proto = data.get(pre + 'PROTO', proto)
|
|
Packit Service |
9bfd13 |
# ipconfig's 'none' is called 'static'
|
|
Packit Service |
9bfd13 |
if cur_proto == 'none':
|
|
Packit Service |
9bfd13 |
cur_proto = 'static'
|
|
Packit Service |
a04d08 |
subnet = {'type': cur_proto, 'control': 'manual'}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# only populate address for static types. While the rendered config
|
|
Packit Service |
a04d08 |
# may have an address for dhcp, that is not really expected.
|
|
Packit Service |
a04d08 |
if cur_proto == 'static':
|
|
Packit Service |
a04d08 |
subnet['address'] = data[pre + 'ADDR']
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# these fields go right on the subnet
|
|
Packit Service |
a04d08 |
for key in ('NETMASK', 'BROADCAST', 'GATEWAY'):
|
|
Packit Service |
a04d08 |
if pre + key in data:
|
|
Packit Service |
a04d08 |
subnet[key.lower()] = data[pre + key]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
dns = []
|
|
Packit Service |
a04d08 |
# handle IPV4DNS0 or IPV6DNS0
|
|
Packit Service |
a04d08 |
for nskey in ('DNS0', 'DNS1'):
|
|
Packit Service |
a04d08 |
ns = data.get(pre + nskey)
|
|
Packit Service |
a04d08 |
# verify it has something other than 0.0.0.0 (or ipv6)
|
|
Packit Service |
a04d08 |
if ns and len(ns.strip(":.0")):
|
|
Packit Service |
a04d08 |
dns.append(data[pre + nskey])
|
|
Packit Service |
a04d08 |
if dns:
|
|
Packit Service |
a04d08 |
subnet['dns_nameservers'] = dns
|
|
Packit Service |
a04d08 |
# add search to both ipv4 and ipv6, as it has no namespace
|
|
Packit Service |
a04d08 |
search = data.get('DOMAINSEARCH')
|
|
Packit Service |
a04d08 |
if search:
|
|
Packit Service |
a04d08 |
if ',' in search:
|
|
Packit Service |
a04d08 |
subnet['dns_search'] = search.split(",")
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
subnet['dns_search'] = search.split()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
iface['subnets'].append(subnet)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return name, iface
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _get_klibc_net_cfg_files():
|
|
Packit Service |
a04d08 |
return glob.glob('/run/net-*.conf') + glob.glob('/run/net6-*.conf')
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def config_from_klibc_net_cfg(files=None, mac_addrs=None):
|
|
Packit Service |
a04d08 |
if files is None:
|
|
Packit Service |
a04d08 |
files = _get_klibc_net_cfg_files()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
entries = []
|
|
Packit Service |
a04d08 |
names = {}
|
|
Packit Service |
a04d08 |
for cfg_file in files:
|
|
Packit Service |
a04d08 |
name, entry = _klibc_to_config_entry(util.load_file(cfg_file),
|
|
Packit Service |
a04d08 |
mac_addrs=mac_addrs)
|
|
Packit Service |
a04d08 |
if name in names:
|
|
Packit Service |
a04d08 |
prev = names[name]['entry']
|
|
Packit Service |
a04d08 |
if prev.get('mac_address') != entry.get('mac_address'):
|
|
Packit Service |
a04d08 |
raise ValueError(
|
|
Packit Service |
a04d08 |
"device '{name}' was defined multiple times ({files})"
|
|
Packit Service |
a04d08 |
" but had differing mac addresses: {old} -> {new}.".format(
|
|
Packit Service |
a04d08 |
name=name, files=' '.join(names[name]['files']),
|
|
Packit Service |
a04d08 |
old=prev.get('mac_address'),
|
|
Packit Service |
a04d08 |
new=entry.get('mac_address')))
|
|
Packit Service |
a04d08 |
prev['subnets'].extend(entry['subnets'])
|
|
Packit Service |
a04d08 |
names[name]['files'].append(cfg_file)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
names[name] = {'files': [cfg_file], 'entry': entry}
|
|
Packit Service |
a04d08 |
entries.append(entry)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return {'config': entries, 'version': 1}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def read_initramfs_config():
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
Return v1 network config for initramfs-configured networking (or None)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
This will consider each _INITRAMFS_CONFIG_SOURCES entry in turn, and return
|
|
Packit Service |
a04d08 |
v1 network configuration for the first one that is applicable. If none are
|
|
Packit Service |
a04d08 |
applicable, return None.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
for src_cls in _INITRAMFS_CONFIG_SOURCES:
|
|
Packit Service |
a04d08 |
cfg_source = src_cls()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if not cfg_source.is_applicable():
|
|
Packit Service |
a04d08 |
continue
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return cfg_source.render_config()
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
def _decomp_gzip(blob):
|
|
Packit Service |
9bfd13 |
# decompress blob or return original blob
|
|
Packit Service |
a04d08 |
with io.BytesIO(blob) as iobuf:
|
|
Packit Service |
a04d08 |
gzfp = None
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
gzfp = gzip.GzipFile(mode="rb", fileobj=iobuf)
|
|
Packit Service |
a04d08 |
return gzfp.read()
|
|
Packit Service |
a04d08 |
except IOError:
|
|
Packit Service |
a04d08 |
return blob
|
|
Packit Service |
a04d08 |
finally:
|
|
Packit Service |
a04d08 |
if gzfp:
|
|
Packit Service |
a04d08 |
gzfp.close()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
def _b64dgz(data):
|
|
Packit Service |
9bfd13 |
"""Decode a string base64 encoding, if gzipped, uncompress as well
|
|
Packit Service |
b1601c |
|
|
Packit Service |
9bfd13 |
:return: decompressed unencoded string of the data or empty string on
|
|
Packit Service |
9bfd13 |
unencoded data.
|
|
Packit Service |
9bfd13 |
"""
|
|
Packit Service |
9bfd13 |
try:
|
|
Packit Service |
9bfd13 |
blob = base64.b64decode(data)
|
|
Packit Service |
9bfd13 |
except (TypeError, ValueError):
|
|
Packit Service |
9bfd13 |
logging.error(
|
|
Packit Service |
9bfd13 |
"Expected base64 encoded kernel commandline parameter"
|
|
Packit Service |
9bfd13 |
" network-config. Ignoring network-config=%s.", data)
|
|
Packit Service |
9bfd13 |
return ''
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
9bfd13 |
return _decomp_gzip(blob)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def read_kernel_cmdline_config(cmdline=None):
|
|
Packit Service |
a04d08 |
if cmdline is None:
|
|
Packit Service |
a04d08 |
cmdline = util.get_cmdline()
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if 'network-config=' in cmdline:
|
|
Packit Service |
a04d08 |
data64 = None
|
|
Packit Service |
a04d08 |
for tok in cmdline.split():
|
|
Packit Service |
a04d08 |
if tok.startswith("network-config="):
|
|
Packit Service |
a04d08 |
data64 = tok.split("=", 1)[1]
|
|
Packit Service |
a04d08 |
if data64:
|
|
Packit Service |
9bfd13 |
if data64 == KERNEL_CMDLINE_NETWORK_CONFIG_DISABLED:
|
|
Packit Service |
9bfd13 |
return {"config": "disabled"}
|
|
Packit Service |
a04d08 |
return util.load_yaml(_b64dgz(data64))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# vi: ts=4 expandtab
|