1
# Copyright (C) 2013-2014 Canonical Ltd.
3
# Author: Scott Moser <scott.moser@canonical.com>
4
# Author: Blake Rouse <blake.rouse@canonical.com>
6
# Curtin is free software: you can redistribute it and/or modify it under
7
# the terms of the GNU Affero General Public License as published by the
8
# Free Software Foundation, either version 3 of the License, or (at your
9
# option) any later version.
11
# Curtin is distributed in the hope that it will be useful, but WITHOUT ANY
12
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
16
# You should have received a copy of the GNU Affero General Public License
17
# along with Curtin. If not, see <http://www.gnu.org/licenses/>.
28
from cloudinit.net import get_devicelist
29
from cloudinit.net import sys_netdev_info
31
from cloudinit import util
33
PY26 = sys.version_info[0:2] == (2, 6)
36
def _shlex_split(blob):
37
if PY26 and isinstance(blob, six.text_type):
38
# Older versions don't support unicode input
39
blob = blob.encode("utf8")
40
return shlex.split(blob)
43
def _load_shell_content(content, add_empty=False, empty_val=None):
44
"""Given shell like syntax (key=value\nkey2=value2\n) in content
45
return the data in dictionary form. If 'add_empty' is True
46
then add entries in to the returned dictionary for 'VAR='
47
variables. Set their value to empty_val."""
49
for line in _shlex_split(content):
51
key, value = line.split("=", 1)
53
# Unsplittable line, skip it...
58
if add_empty or value:
63
def _klibc_to_config_entry(content, mac_addrs=None):
64
"""Convert a klibc writtent shell content file to a 'config' entry
65
When ip= is seen on the kernel command line in debian initramfs
66
and networking is brought up, ipconfig will populate
69
The files are shell style syntax, and examples are in the tests
70
provided here. There is no good documentation on this unfortunately.
72
DEVICE=<name> is expected/required and PROTO should indicate if
73
this is 'static' or 'dhcp'.
79
data = _load_shell_content(content)
83
raise ValueError("no 'DEVICE' entry in data")
85
# ipconfig on precise does not write PROTO
86
proto = data.get('PROTO')
88
if data.get('filename'):
93
if proto not in ('static', 'dhcp'):
94
raise ValueError("Unexpected value for PROTO: %s" % proto)
102
if name in mac_addrs:
103
iface['mac_address'] = mac_addrs[name]
105
# originally believed there might be IPV6* values
106
for v, pre in (('ipv4', 'IPV4'),):
107
# if no IPV4ADDR or IPV6ADDR, then go on.
108
if pre + "ADDR" not in data:
110
subnet = {'type': proto, 'control': 'manual'}
112
# these fields go right on the subnet
113
for key in ('NETMASK', 'BROADCAST', 'GATEWAY'):
114
if pre + key in data:
115
subnet[key.lower()] = data[pre + key]
118
# handle IPV4DNS0 or IPV6DNS0
119
for nskey in ('DNS0', 'DNS1'):
120
ns = data.get(pre + nskey)
121
# verify it has something other than 0.0.0.0 (or ipv6)
122
if ns and len(ns.strip(":.0")):
123
dns.append(data[pre + nskey])
125
subnet['dns_nameservers'] = dns
126
# add search to both ipv4 and ipv6, as it has no namespace
127
search = data.get('DOMAINSEARCH')
130
subnet['dns_search'] = search.split(",")
132
subnet['dns_search'] = search.split()
134
iface['subnets'].append(subnet)
139
def config_from_klibc_net_cfg(files=None, mac_addrs=None):
141
files = glob.glob('/run/net*.conf')
145
for cfg_file in files:
146
name, entry = _klibc_to_config_entry(util.load_file(cfg_file),
150
"device '%s' defined multiple times: %s and %s" % (
151
name, names[name], cfg_file))
153
names[name] = cfg_file
154
entries.append(entry)
155
return {'config': entries, 'version': 1}
158
def _decomp_gzip(blob, strict=True):
159
# decompress blob. raise exception if not compressed unless strict=False.
160
with io.BytesIO(blob) as iobuf:
163
gzfp = gzip.GzipFile(mode="rb", fileobj=iobuf)
174
def _b64dgz(b64str, gzipped="try"):
175
# decode a base64 string. If gzipped is true, transparently uncompresss
176
# if gzipped is 'try', then try gunzip, returning the original on fail.
178
blob = base64.b64decode(b64str)
180
raise ValueError("Invalid base64 text: %s" % b64str)
185
return _decomp_gzip(blob, strict=gzipped != "try")
188
def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None):
190
cmdline = util.get_cmdline()
192
if 'network-config=' in cmdline:
194
for tok in cmdline.split():
195
if tok.startswith("network-config="):
196
data64 = tok.split("=", 1)[1]
198
return util.load_yaml(_b64dgz(data64))
200
if 'ip=' not in cmdline:
203
if mac_addrs is None:
204
mac_addrs = dict((k, sys_netdev_info(k, 'address'))
205
for k in get_devicelist())
207
return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs)