From 88f8b150ec4faf4e8de9e81a80ddbb3347dd851f Mon Sep 17 00:00:00 2001 From: Shraddha Pandhe Date: Fri, 21 Nov 2014 18:32:30 +0000 Subject: [PATCH 1/4] Add IPv6 Support for Rhel. This patch does the following: 1. Adds support to process network config with IPv6 2. Adds support to display 'ifconfig -a' information for IPv6 3. Adds support to display routing information for IPv6 --- cloudinit/distros/net_util.py | 60 +++++++++++++++++++++++------------ cloudinit/distros/rhel.py | 11 +++++++ cloudinit/netinfo.py | 60 +++++++++++++++++++++++++++++------ 3 files changed, 102 insertions(+), 29 deletions(-) diff --git a/cloudinit/distros/net_util.py b/cloudinit/distros/net_util.py index b9bcfd8b..f56f6ccd 100644 --- a/cloudinit/distros/net_util.py +++ b/cloudinit/distros/net_util.py @@ -113,6 +113,10 @@ def translate_network(settings): for info in ifaces: if 'iface' not in info: continue + use_ipv6 = False + # Check if current device has an ipv6 IP + if 'inet6' in info['iface']: + use_ipv6 = True iface_details = info['iface'].split(None) dev_name = None if len(iface_details) >= 1: @@ -122,6 +126,7 @@ def translate_network(settings): 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 @@ -129,26 +134,39 @@ def translate_network(settings): if proto_type in ['dhcp', 'static']: iface_info['bootproto'] = proto_type # These can just be copied over - for k in ['netmask', 'address', 'gateway', 'broadcast']: - if k in info: - val = info[k].strip().lower() - if val: - iface_info[k] = val - # 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 - real_ifaces[dev_name] = iface_info + 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 + # 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. + # 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: if cmd == 'auto': @@ -160,4 +178,6 @@ def translate_network(settings): dev_name = args[0].strip().lower() 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 diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index 1a269e08..fa3ccb38 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -71,6 +71,7 @@ class Distro(distros.Distro): nameservers = [] searchservers = [] dev_names = entries.keys() + use_ipv6 = False for (dev, info) in entries.iteritems(): net_fn = self.network_script_tpl % (dev) net_cfg = { @@ -83,6 +84,13 @@ class Distro(distros.Distro): 'MACADDR': info.get('hwaddress'), 'ONBOOT': _make_sysconfig_bool(info.get('auto')), } + if info.get('inet6'): + use_ipv6 = True + net_cfg.update({ + 'IPV6INIT': _make_sysconfig_bool(True), + 'IPV6ADDR': info.get('ipv6').get('address'), + 'IPV6_DEFAULTGW': info.get('ipv6').get('gateway'), + }) rhel_util.update_sysconfig_file(net_fn, net_cfg) if 'dns-nameservers' in info: nameservers.extend(info['dns-nameservers']) @@ -95,6 +103,9 @@ class Distro(distros.Distro): net_cfg = { 'NETWORKING': _make_sysconfig_bool(True), } + # If IPv6 interface present, enable ipv6 networking + if use_ipv6: + net_cfg['NETWORKING_IPV6'] = _make_sysconfig_bool(True) rhel_util.update_sysconfig_file(self.network_conf_fn, net_cfg) return dev_names diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index 8d4df342..d891315b 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -72,6 +72,7 @@ def netdev_info(empty=""): "bcast:": "bcast", "broadcast": "bcast", "mask:": "mask", "netmask": "mask", "hwaddr": "hwaddr", "ether": "hwaddr", + "scope": "scope", } for origfield, field in ifconfigfields.items(): target = "%s%s" % (field, fieldpost) @@ -96,7 +97,12 @@ def netdev_info(empty=""): def route_info(): (route_out, _err) = util.subp(["netstat", "-rn"]) - routes = [] + (route_out6, _err6) = util.subp(["netstat", "-A inet6", "-n"]) + + routes = {} + routes['ipv4'] = [] + routes['ipv6'] = [] + entries = route_out.splitlines()[1:] for line in entries: if not line: @@ -132,7 +138,26 @@ def route_info(): 'iface': toks[7], } - routes.append(entry) + routes['ipv4'].append(entry) + + entries6 = route_out6.splitlines()[1:] + for line in entries6: + if not line: + continue + toks = line.split() + + if (len(toks) < 6 or toks[0] == "Kernel" or + toks[0] == "Proto" or toks[0] == "Active"): + continue + entry = { + 'proto': toks[0], + 'recv-q': toks[1], + 'send-q': toks[2], + 'local address': toks[3], + 'foreign address': toks[4], + 'state': toks[5], + } + routes['ipv6'].append(entry) return routes @@ -156,10 +181,12 @@ def netdev_pformat(): lines.append(util.center("Net device info failed", '!', 80)) netdev = None if netdev is not None: - fields = ['Device', 'Up', 'Address', 'Mask', 'Hw-Address'] + fields = ['Device', 'Up', 'Address', 'Mask', 'Scope', 'Hw-Address'] tbl = PrettyTable(fields) for (dev, d) in netdev.iteritems(): - tbl.add_row([dev, d["up"], d["addr"], d["mask"], d["hwaddr"]]) + tbl.add_row([dev, d["up"], d["addr"], d["mask"], ".", d["hwaddr"]]) + if d["addr6"]: + tbl.add_row([dev, d["up"], d["addr6"], ".", d["scope6"], d["hwaddr"]]) netdev_s = tbl.get_string() max_len = len(max(netdev_s.splitlines(), key=len)) header = util.center("Net device info", "+", max_len) @@ -176,15 +203,30 @@ def route_pformat(): util.logexc(LOG, "Route info failed: %s" % e) routes = None if routes is not None: - fields = ['Route', 'Destination', 'Gateway', + fields_v4 = ['Route', 'Destination', 'Gateway', 'Genmask', 'Interface', 'Flags'] - tbl = PrettyTable(fields) - for (n, r) in enumerate(routes): + + if routes.get('ipv6') is not None: + fields_v6 = ['Route', 'Proto', 'Recv-Q', 'Send-Q', 'Local Address', + 'Foreign Address', 'State'] + + tbl_v4 = PrettyTable(fields_v4) + for (n, r) in enumerate(routes.get('ipv4')): route_id = str(n) - tbl.add_row([route_id, r['destination'], + tbl_v4.add_row([route_id, r['destination'], r['gateway'], r['genmask'], r['iface'], r['flags']]) - route_s = tbl.get_string() + route_s = tbl_v4.get_string() + if fields_v6: + tbl_v6 = PrettyTable(fields_v6) + for (n, r) in enumerate(routes.get('ipv6')): + route_id = str(n) + tbl_v6.add_row([route_id, r['proto'], + r['recv-q'], r['send-q'], + r['local address'], r['foreign address'], + r['state']]) + route_s = route_s + tbl_v6.get_string() + max_len = len(max(route_s.splitlines(), key=len)) header = util.center("Route info", "+", max_len) lines.extend([header, route_s]) From d11b2b5aedea3fd6c0f2bcf603eeb1c5ff7a6d61 Mon Sep 17 00:00:00 2001 From: Shraddha Pandhe Date: Fri, 21 Nov 2014 22:17:26 +0000 Subject: [PATCH 2/4] Added unittests for IPv6 support for RHEL --- cloudinit/distros/rhel.py | 1 + .../unittests/test_distros/test_netconfig.py | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index fa3ccb38..13335df5 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -106,6 +106,7 @@ class Distro(distros.Distro): # If IPv6 interface present, enable ipv6 networking if use_ipv6: net_cfg['NETWORKING_IPV6'] = _make_sysconfig_bool(True) + net_cfg['IPV6_AUTOCONF'] = _make_sysconfig_bool(False) rhel_util.update_sysconfig_file(self.network_conf_fn, net_cfg) return dev_names diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 35cc1f43..cb385da7 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -30,6 +30,24 @@ auto eth1 iface eth1 inet dhcp ''' +BASE_NET_CFG_IPV6 = ''' +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 192.168.1.5 + netmask 255.255.255.0 + network 192.168.0.0 + broadcast 192.168.1.0 + gateway 192.168.1.254 + +iface eth0 inet6 static + address 2607:f0d0:1002:0011::2 + netmask 64 + gateway 2607:f0d0:1002:0011::1 +''' + class WriteBuffer(object): def __init__(self): @@ -174,6 +192,81 @@ NETWORKING=yes self.assertCfgEquals(expected_buf, str(write_buf)) self.assertEquals(write_buf.mode, 0644) + def test_write_ipv6_rhel(self): + rh_distro = self._get_distro('rhel') + write_mock = self.mocker.replace(util.write_file, + spec=False, passthrough=False) + load_mock = self.mocker.replace(util.load_file, + spec=False, passthrough=False) + exists_mock = self.mocker.replace(os.path.isfile, + spec=False, passthrough=False) + + write_bufs = {} + + def replace_write(filename, content, mode=0644, omode="wb"): + buf = WriteBuffer() + buf.mode = mode + buf.omode = omode + buf.write(content) + write_bufs[filename] = buf + + exists_mock(mocker.ARGS) + self.mocker.count(0, None) + self.mocker.result(False) + + load_mock(mocker.ARGS) + self.mocker.count(0, None) + self.mocker.result('') + + for _i in range(0, 2): + write_mock(mocker.ARGS) + self.mocker.call(replace_write) + + write_mock(mocker.ARGS) + self.mocker.call(replace_write) + + self.mocker.replay() + rh_distro.apply_network(BASE_NET_CFG_IPV6, False) + + self.assertEquals(len(write_bufs), 3) + self.assertIn('/etc/sysconfig/network-scripts/ifcfg-lo', write_bufs) + write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-lo'] + expected_buf = ''' +DEVICE="lo" +ONBOOT=yes +''' + self.assertCfgEquals(expected_buf, str(write_buf)) + self.assertEquals(write_buf.mode, 0644) + + self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth0', write_bufs) + write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth0'] + expected_buf = ''' +DEVICE="eth0" +BOOTPROTO="static" +NETMASK="255.255.255.0" +IPADDR="192.168.1.5" +ONBOOT=yes +GATEWAY="192.168.1.254" +BROADCAST="192.168.1.0" +IPV6INIT=yes +IPV6ADDR="2607:f0d0:1002:0011::2" +IPV6_DEFAULTGW="2607:f0d0:1002:0011::1" +''' + self.assertCfgEquals(expected_buf, str(write_buf)) + self.assertEquals(write_buf.mode, 0644) + + self.assertIn('/etc/sysconfig/network', write_bufs) + write_buf = write_bufs['/etc/sysconfig/network'] + expected_buf = ''' +# Created by cloud-init v. 0.7 +NETWORKING=yes +NETWORKING_IPV6=yes +IPV6_AUTOCONF=no +''' + self.assertCfgEquals(expected_buf, str(write_buf)) + self.assertEquals(write_buf.mode, 0644) + + def test_simple_write_freebsd(self): fbsd_distro = self._get_distro('freebsd') util_mock = self.mocker.replace(util.write_file, From 959293aba5e1a8d62b91e7aa16e22e140f791961 Mon Sep 17 00:00:00 2001 From: Shraddha Pandhe Date: Mon, 24 Nov 2014 19:36:18 +0000 Subject: [PATCH 3/4] IPv6 support for rhel distro - Saw an issue in my earlier commit with multiple NICs. This commit fixes that issue, along with the indentation issue --- cloudinit/distros/net_util.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cloudinit/distros/net_util.py b/cloudinit/distros/net_util.py index f56f6ccd..f8c34846 100644 --- a/cloudinit/distros/net_util.py +++ b/cloudinit/distros/net_util.py @@ -169,15 +169,16 @@ def translate_network(settings): 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 - args = args.split(None) - if not args: - continue - dev_name = args[0].strip().lower() 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 + real_ifaces[dev_name]['inet6'] = True return real_ifaces + From fa849e6699a9296af1fe873f18216d0894a50a11 Mon Sep 17 00:00:00 2001 From: Shraddha Pandhe Date: Mon, 24 Nov 2014 19:54:22 +0000 Subject: [PATCH 4/4] Updated unittests + Scenario with multiple NICs --- .../unittests/test_distros/test_netconfig.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index cb385da7..dbbf9617 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -46,6 +46,18 @@ iface eth0 inet6 static address 2607:f0d0:1002:0011::2 netmask 64 gateway 2607:f0d0:1002:0011::1 + +iface eth1 inet static + address 192.168.1.6 + netmask 255.255.255.0 + network 192.168.0.0 + broadcast 192.168.1.0 + gateway 192.168.1.254 + +iface eth1 inet6 static + address 2607:f0d0:1002:0011::3 + netmask 64 + gateway 2607:f0d0:1002:0011::1 ''' @@ -218,7 +230,7 @@ NETWORKING=yes self.mocker.count(0, None) self.mocker.result('') - for _i in range(0, 2): + for _i in range(0, 3): write_mock(mocker.ARGS) self.mocker.call(replace_write) @@ -228,7 +240,7 @@ NETWORKING=yes self.mocker.replay() rh_distro.apply_network(BASE_NET_CFG_IPV6, False) - self.assertEquals(len(write_bufs), 3) + self.assertEquals(len(write_bufs), 4) self.assertIn('/etc/sysconfig/network-scripts/ifcfg-lo', write_bufs) write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-lo'] expected_buf = ''' @@ -251,6 +263,22 @@ BROADCAST="192.168.1.0" IPV6INIT=yes IPV6ADDR="2607:f0d0:1002:0011::2" IPV6_DEFAULTGW="2607:f0d0:1002:0011::1" +''' + self.assertCfgEquals(expected_buf, str(write_buf)) + self.assertEquals(write_buf.mode, 0644) + self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth1', write_bufs) + write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth1'] + expected_buf = ''' +DEVICE="eth1" +BOOTPROTO="static" +NETMASK="255.255.255.0" +IPADDR="192.168.1.6" +ONBOOT=no +GATEWAY="192.168.1.254" +BROADCAST="192.168.1.0" +IPV6INIT=yes +IPV6ADDR="2607:f0d0:1002:0011::3" +IPV6_DEFAULTGW="2607:f0d0:1002:0011::1" ''' self.assertCfgEquals(expected_buf, str(write_buf)) self.assertEquals(write_buf.mode, 0644)