summaryrefslogtreecommitdiff
path: root/cloudinit/distros/net_util.py
blob: e37fb19b89a3045a48ecedb6c0608e24a866aaec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# Copyright (C) 2012 Canonical Ltd.
# Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P.
# Copyright (C) 2012 Yahoo! Inc.
#
# Author: Scott Moser <scott.moser@canonical.com>
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
#
# This file is part of cloud-init. See LICENSE file for license information.


# This is a util function to translate debian based distro interface blobs as
# given in /etc/network/interfaces to an *somewhat* agnostic format for
# distributions that use other formats.
#
# TODO(harlowja) remove when we have python-netcf active...
#
# The format is the following:
# {
#    <device-name>: {
#        # All optional (if not existent in original format)
#        "netmask": <ip>,
#        "broadcast": <ip>,
#        "gateway": <ip>,
#        "address": <ip>,
#        "bootproto": "static"|"dhcp",
#        "dns-search": <hostname>,
#        "hwaddress": <mac-address>,
#        "auto": True (or non-existent),
#        "dns-nameservers": [<ip/hostname>, ...],
#    }
# }
#
# Things to note, comments are removed, if a ubuntu/debian interface is
# marked as auto then only then first segment (?) is retained, ie
# 'auto eth0 eth0:1' just marks eth0 as auto (not eth0:1).
#
# Example input:
#
# auto lo
# iface lo inet loopback
#
# auto eth0
# iface eth0 inet static
#         address 10.0.0.1
#         netmask 255.255.252.0
#         broadcast 10.0.0.255
#         gateway 10.0.0.2
#         dns-nameservers 98.0.0.1 98.0.0.2
#
# Example output:
# {
#     "lo": {
#         "auto": true
#     },
#     "eth0": {
#         "auto": true,
#         "dns-nameservers": [
#             "98.0.0.1",
#             "98.0.0.2"
#         ],
#         "broadcast": "10.0.0.255",
#         "netmask": "255.255.252.0",
#         "bootproto": "static",
#         "address": "10.0.0.1",
#         "gateway": "10.0.0.2"
#     }
# }

from cloudinit.net.network_state import (
    mask_and_ipv4_to_bcast_addr,
    net_prefix_to_ipv4_mask,
)


def translate_network(settings):
    # Get the standard cmd, args from the ubuntu format
    entries = []
    for line in settings.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        split_up = line.split(None, 1)
        if len(split_up) <= 1:
            continue
        entries.append(split_up)
    # Figure out where each iface section is
    ifaces = []
    consume = {}
    for (cmd, args) in entries:
        if cmd == "iface":
            if consume:
                ifaces.append(consume)
                consume = {}
            consume[cmd] = args
        else:
            consume[cmd] = args
    # Check if anything left over to consume
    absorb = False
    for (cmd, args) in consume.items():
        if cmd == "iface":
            absorb = True
    if absorb:
        ifaces.append(consume)
    # Now translate
    real_ifaces = {}
    for info in ifaces:
        if "iface" not in info:
            continue
        iface_details = info["iface"].split(None)
        # Check if current device *may* have an ipv6 IP
        use_ipv6 = False
        if "inet6" in iface_details:
            use_ipv6 = True
        dev_name = None
        if len(iface_details) >= 1:
            dev = iface_details[0].strip().lower()
            if dev:
                dev_name = dev
        if not dev_name:
            continue
        iface_info = {}
        iface_info["ipv6"] = {}
        if len(iface_details) >= 3:
            proto_type = iface_details[2].strip().lower()
            # Seems like this can be 'loopback' which we don't
            # really care about
            if proto_type in ["dhcp", "static"]:
                iface_info["bootproto"] = proto_type
        # These can just be copied over
        if use_ipv6:
            for k in ["address", "gateway"]:
                if k in info:
                    val = info[k].strip().lower()
                    if val:
                        iface_info["ipv6"][k] = val
        else:
            for k in ["netmask", "address", "gateway", "broadcast"]:
                if k in info:
                    val = info[k].strip().lower()
                    if val:
                        iface_info[k] = val
            # handle static ip configurations using
            # ipaddress/prefix-length format
            if "address" in iface_info:
                if "netmask" not in iface_info:
                    # check if the address has a network prefix
                    addr, _, prefix = iface_info["address"].partition("/")
                    if prefix:
                        iface_info["netmask"] = net_prefix_to_ipv4_mask(prefix)
                        iface_info["address"] = addr
                        # if we set the netmask, we also can set the broadcast
                        iface_info["broadcast"] = mask_and_ipv4_to_bcast_addr(
                            iface_info["netmask"], addr
                        )

            # Name server info provided??
            if "dns-nameservers" in info:
                iface_info["dns-nameservers"] = info["dns-nameservers"].split()
            # Name server search info provided??
            if "dns-search" in info:
                iface_info["dns-search"] = info["dns-search"].split()
            # Is any mac address spoofing going on??
            if "hwaddress" in info:
                hw_info = info["hwaddress"].lower().strip()
                hw_split = hw_info.split(None, 1)
                if len(hw_split) == 2 and hw_split[0].startswith("ether"):
                    hw_addr = hw_split[1]
                    if hw_addr:
                        iface_info["hwaddress"] = hw_addr
        # If ipv6 is enabled, device will have multiple IPs, so we need to
        # update the dictionary instead of overwriting it...
        if dev_name in real_ifaces:
            real_ifaces[dev_name].update(iface_info)
        else:
            real_ifaces[dev_name] = iface_info
    # Check for those that should be started on boot via 'auto'
    for (cmd, args) in entries:
        args = args.split(None)
        if not args:
            continue
        dev_name = args[0].strip().lower()
        if cmd == "auto":
            # Seems like auto can be like 'auto eth0 eth0:1' so just get the
            # first part out as the device name
            if dev_name in real_ifaces:
                real_ifaces[dev_name]["auto"] = True
        if cmd == "iface" and "inet6" in args:
            real_ifaces[dev_name]["inet6"] = True
    return real_ifaces


# vi: ts=4 expandtab