Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 90 additions & 7 deletions cloudinit/netinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#
# This file is part of cloud-init. See LICENSE file for license information.

import json
import re
from copy import copy, deepcopy
from ipaddress import IPv4Network

from cloudinit import log as logging
from cloudinit import subp, util
Expand All @@ -18,13 +20,84 @@

LOG = logging.getLogger()


# Example netdev format:
# {'eth0': {'hwaddr': '00:16:3e:16:db:54',
# 'ipv4': [{'bcast': '10.85.130.255',
# 'ip': '10.85.130.116',
# 'mask': '255.255.255.0',
# 'scope': 'global'}],
# 'ipv6': [{'ip': 'fd42:baa2:3dd:17a:216:3eff:fe16:db54/64',
# 'scope6': 'global'},
# {'ip': 'fe80::216:3eff:fe16:db54/64', 'scope6': 'link'}],
# 'up': True},
# 'lo': {'hwaddr': '',
# 'ipv4': [{'bcast': '',
# 'ip': '127.0.0.1',
# 'mask': '255.0.0.0',
# 'scope': 'host'}],
# 'ipv6': [{'ip': '::1/128', 'scope6': 'host'}],
# 'up': True}}
DEFAULT_NETDEV_INFO = {"ipv4": [], "ipv6": [], "hwaddr": "", "up": False}


def _netdev_info_iproute_json(ipaddr_json):
"""Get network device dicts from ip route and ip link info.

ipaddr_json: Output string from 'ip --json addr' command.

Returns a dict of device info keyed by network device name containing
device configuration values.

Raises json.JSONDecodeError if json could not be decoded
"""
ipaddr_data = json.loads(ipaddr_json)
devs = {}

for dev in ipaddr_data:
flags = dev["flags"] if "flags" in dev else []
address = dev["address"] if dev.get("link_type") == "ether" else ""
dev_info = {
"hwaddr": address,
"up": bool("UP" in flags and "LOWER_UP" in flags),
"ipv4": [],
"ipv6": [],
}
for addr in dev.get("addr_info", []):
if addr.get("family") == "inet":
mask = (
str(IPv4Network(f'0.0.0.0/{addr["prefixlen"]}').netmask)
if "prefixlen" in addr
else ""
)
parsed_addr = {
"ip": addr.get("local", ""),
"mask": mask,
"bcast": addr.get("broadcast", ""),
"scope": addr.get("scope", ""),
}
dev_info["ipv4"].append(parsed_addr)
elif addr["family"] == "inet6":
ip = addr.get("local", "")
# address here refers to a peer address, and according
# to "man 8 ip-address":
# If a peer address is specified, the local address cannot
# have a prefix length. The network prefix is associated
# with the peer rather than with the local address.
if ip and not addr.get("address"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 thanks for this clarification here + comments

ip = f"{ip}/{addr.get('prefixlen', 64)}"
parsed_addr = {
"ip": ip,
"scope6": addr.get("scope", ""),
}
dev_info["ipv6"].append(parsed_addr)
devs[dev["ifname"]] = dev_info
return devs


def _netdev_info_iproute(ipaddr_out):
"""
Get network device dicts from ip route and ip link info.
DEPRECATED: Only used on distros that don't support ip json output
Use _netdev_info_iproute_json() when possible.

@param ipaddr_out: Output string from 'ip addr show' command.

Expand All @@ -47,7 +120,10 @@ def _netdev_info_iproute(ipaddr_out):
}
elif "inet6" in line:
m = re.match(
r"\s+inet6\s(?P<ip>\S+)\sscope\s(?P<scope6>\S+).*", line
r"\s+inet6\s(?P<ip>\S+)"
r"(\s(peer\s\S+))?"
r"\sscope\s(?P<scope6>\S+).*",
line,
)
if not m:
LOG.warning(
Expand All @@ -57,8 +133,10 @@ def _netdev_info_iproute(ipaddr_out):
devs[dev_name]["ipv6"].append(m.groupdict())
elif "inet" in line:
m = re.match(
r"\s+inet\s(?P<cidr4>\S+)(\sbrd\s(?P<bcast>\S+))?\sscope\s"
r"(?P<scope>\S+).*",
r"\s+inet\s(?P<cidr4>\S+)"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, since you've added it, let's keep it. But yes no need to maintain both long-term. Ultimately, let's plan on dropping this function in 22.2 upstream release

r"(\smetric\s(?P<metric>\d+))?"
r"(\sbrd\s(?P<bcast>\S+))?"
r"\sscope\s(?P<scope>\S+).*",
line,
)
if not m:
Expand Down Expand Up @@ -209,8 +287,13 @@ def netdev_info(empty=""):
devs = _netdev_info_ifconfig_netbsd(ifcfg_out)
elif subp.which("ip"):
# Try iproute first of all
(ipaddr_out, _err) = subp.subp(["ip", "addr", "show"])
devs = _netdev_info_iproute(ipaddr_out)
try:
(ipaddr_out, _err) = subp.subp(["ip", "--json", "addr"])
devs = _netdev_info_iproute_json(ipaddr_out)
except subp.ProcessExecutionError:
# Can be removed when "ip --json" is available everywhere
(ipaddr_out, _err) = subp.subp(["ip", "addr", "show"])
devs = _netdev_info_iproute(ipaddr_out)
elif subp.which("ifconfig"):
# Fall back to net-tools if iproute2 is not present
(ifcfg_out, _err) = subp.subp(["ifconfig", "-a"], rcs=[0, 1])
Expand Down
91 changes: 91 additions & 0 deletions tests/data/netinfo/sample-ipaddrshow-json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
[
{
"ifindex": 1,
"ifname": "lo",
"flags": [
"LOOPBACK",
"UP",
"LOWER_UP"
],
"mtu": 65536,
"qdisc": "noqueue",
"operstate": "UNKNOWN",
"group": "default",
"txqlen": 1000,
"link_type": "loopback",
"address": "00:00:00:00:00:00",
"broadcast": "00:00:00:00:00:00",
"addr_info": [
{
"family": "inet",
"local": "127.0.0.1",
"prefixlen": 8,
"scope": "host",
"label": "lo",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
},
{
"family": "inet6",
"local": "::1",
"prefixlen": 128,
"scope": "host",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
},
{
"ifindex": 23,
"link_index": 24,
"ifname": "enp0s25",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "50:7b:9d:2c:af:91",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 0,
"addr_info": [
{
"family": "inet",
"local": "192.168.2.18",
"prefixlen": 24,
"metric": 100,
"broadcast": "192.168.2.255",
"scope": "global",
"dynamic": true,
"label": "enp0s25",
"valid_life_time": 2339,
"preferred_life_time": 2339
},
{
"family": "inet6",
"local": "fe80::7777:2222:1111:eeee",
"prefixlen": 64,
"scope": "global",
"dynamic": true,
"mngtmpaddr": true,
"noprefixroute": true,
"valid_life_time": 6823,
"preferred_life_time": 3223
},
{
"family": "inet6",
"local": "fe80::8107:2b92:867e:f8a6",
"prefixlen": 64,
"scope": "link",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
}
]
57 changes: 57 additions & 0 deletions tests/data/netinfo/sample-ipaddrshow-json-down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[
{
"ifindex": 1,
"ifname": "lo",
"flags": [
"LOOPBACK",
"UP",
"LOWER_UP"
],
"mtu": 65536,
"qdisc": "noqueue",
"operstate": "UNKNOWN",
"group": "default",
"txqlen": 1000,
"link_type": "loopback",
"address": "00:00:00:00:00:00",
"broadcast": "00:00:00:00:00:00",
"addr_info": [
{
"family": "inet",
"local": "127.0.0.1",
"prefixlen": 8,
"scope": "host",
"label": "lo",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
},
{
"family": "inet6",
"local": "::1",
"prefixlen": 128,
"scope": "host",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
},
{
"ifindex": 23,
"link_index": 24,
"ifname": "eth0",
"flags": [
"BROADCAST",
"MULTICAST"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "DOWN",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "00:16:3e:de:51:a6",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 0,
"addr_info": []
}
]
3 changes: 1 addition & 2 deletions tests/data/netinfo/sample-ipaddrshow-output
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
inet6 ::1/128 scope host \ valid_lft forever preferred_lft forever
2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 50:7b:9d:2c:af:91 brd ff:ff:ff:ff:ff:ff
inet 192.168.2.18/24 brd 192.168.2.255 scope global dynamic enp0s25
inet 192.168.2.18/24 metric 100 brd 192.168.2.255 scope global dynamic enp0s25
valid_lft 84174sec preferred_lft 84174sec
inet6 fe80::7777:2222:1111:eeee/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::8107:2b92:867e:f8a6/64 scope link
valid_lft forever preferred_lft forever

Loading