69219a
From a22a059e36ec56d0d6d7e2a63ccff56d6c19f9d6 Mon Sep 17 00:00:00 2001
69219a
From: Eduardo Otubo <otubo@redhat.com>
69219a
Date: Mon, 4 May 2020 12:39:55 +0200
69219a
Subject: [PATCH 2/6] net/sysconfig: Handle default route setup for dhcp
69219a
 configured NICs
69219a
69219a
RH-Author: Eduardo Otubo <otubo@redhat.com>
69219a
Message-id: <20200327152826.13343-3-otubo@redhat.com>
69219a
Patchwork-id: 94457
69219a
O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 2/6] net/sysconfig: Handle default route setup for dhcp configured NICs
69219a
Bugzilla: 1811753
69219a
RH-Acked-by: Cathy Avery <cavery@redhat.com>
69219a
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
69219a
69219a
commit 3acaacc92be1b7d7bad099c323d6e923664a8afa
69219a
Author: Robert Schweikert <rjschwei@suse.com>
69219a
Date:   Tue Mar 12 21:08:22 2019 +0000
69219a
69219a
    net/sysconfig: Handle default route setup for dhcp configured NICs
69219a
69219a
    When the network configuration has a default route configured and
69219a
    another network device that is configured with dhcp, SUSE sysconfig
69219a
    output should not accept the default route provided by the dhcp
69219a
    server.
69219a
69219a
    LP: #1812117
69219a
69219a
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
69219a
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
69219a
---
69219a
 cloudinit/net/network_state.py | 41 +++++++++++++++++++++------
69219a
 cloudinit/net/sysconfig.py     | 31 +++++++++++++++------
69219a
 tests/unittests/test_net.py    | 63 ++++++++++++++++++++++++++++++++++++++++++
69219a
 3 files changed, 118 insertions(+), 17 deletions(-)
69219a
69219a
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
69219a
index 539b76d..4d19f56 100644
69219a
--- a/cloudinit/net/network_state.py
69219a
+++ b/cloudinit/net/network_state.py
69219a
@@ -148,6 +148,7 @@ class NetworkState(object):
69219a
         self._network_state = copy.deepcopy(network_state)
69219a
         self._version = version
69219a
         self.use_ipv6 = network_state.get('use_ipv6', False)
69219a
+        self._has_default_route = None
69219a
 
69219a
     @property
69219a
     def config(self):
69219a
@@ -157,14 +158,6 @@ class NetworkState(object):
69219a
     def version(self):
69219a
         return self._version
69219a
 
69219a
-    def iter_routes(self, filter_func=None):
69219a
-        for route in self._network_state.get('routes', []):
69219a
-            if filter_func is not None:
69219a
-                if filter_func(route):
69219a
-                    yield route
69219a
-            else:
69219a
-                yield route
69219a
-
69219a
     @property
69219a
     def dns_nameservers(self):
69219a
         try:
69219a
@@ -179,6 +172,12 @@ class NetworkState(object):
69219a
         except KeyError:
69219a
             return []
69219a
 
69219a
+    @property
69219a
+    def has_default_route(self):
69219a
+        if self._has_default_route is None:
69219a
+            self._has_default_route = self._maybe_has_default_route()
69219a
+        return self._has_default_route
69219a
+
69219a
     def iter_interfaces(self, filter_func=None):
69219a
         ifaces = self._network_state.get('interfaces', {})
69219a
         for iface in six.itervalues(ifaces):
69219a
@@ -188,6 +187,32 @@ class NetworkState(object):
69219a
                 if filter_func(iface):
69219a
                     yield iface
69219a
 
69219a
+    def iter_routes(self, filter_func=None):
69219a
+        for route in self._network_state.get('routes', []):
69219a
+            if filter_func is not None:
69219a
+                if filter_func(route):
69219a
+                    yield route
69219a
+            else:
69219a
+                yield route
69219a
+
69219a
+    def _maybe_has_default_route(self):
69219a
+        for route in self.iter_routes():
69219a
+            if self._is_default_route(route):
69219a
+                return True
69219a
+        for iface in self.iter_interfaces():
69219a
+            for subnet in iface.get('subnets', []):
69219a
+                for route in subnet.get('routes', []):
69219a
+                    if self._is_default_route(route):
69219a
+                        return True
69219a
+        return False
69219a
+
69219a
+    def _is_default_route(self, route):
69219a
+        default_nets = ('::', '0.0.0.0')
69219a
+        return (
69219a
+            route.get('prefix') == 0
69219a
+            and route.get('network') in default_nets
69219a
+            )
69219a
+
69219a
 
69219a
 @six.add_metaclass(CommandHandlerMeta)
69219a
 class NetworkStateInterpreter(object):
69219a
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
69219a
index 52bb848..5c1b4eb 100644
69219a
--- a/cloudinit/net/sysconfig.py
69219a
+++ b/cloudinit/net/sysconfig.py
69219a
@@ -320,7 +320,7 @@ class Renderer(renderer.Renderer):
69219a
                 iface_cfg[new_key] = old_value
69219a
 
69219a
     @classmethod
69219a
-    def _render_subnets(cls, iface_cfg, subnets):
69219a
+    def _render_subnets(cls, iface_cfg, subnets, has_default_route):
69219a
         # setting base values
69219a
         iface_cfg['BOOTPROTO'] = 'none'
69219a
 
69219a
@@ -329,6 +329,7 @@ class Renderer(renderer.Renderer):
69219a
             mtu_key = 'MTU'
69219a
             subnet_type = subnet.get('type')
69219a
             if subnet_type == 'dhcp6':
69219a
+                # TODO need to set BOOTPROTO to dhcp6 on SUSE
69219a
                 iface_cfg['IPV6INIT'] = True
69219a
                 iface_cfg['DHCPV6C'] = True
69219a
             elif subnet_type in ['dhcp4', 'dhcp']:
69219a
@@ -372,9 +373,9 @@ class Renderer(renderer.Renderer):
69219a
         ipv6_index = -1
69219a
         for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
69219a
             subnet_type = subnet.get('type')
69219a
-            if subnet_type == 'dhcp6':
69219a
-                continue
69219a
-            elif subnet_type in ['dhcp4', 'dhcp']:
69219a
+            if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']:
69219a
+                if has_default_route and iface_cfg['BOOTPROTO'] != 'none':
69219a
+                    iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False
69219a
                 continue
69219a
             elif subnet_type == 'static':
69219a
                 if subnet_is_ipv6(subnet):
69219a
@@ -440,6 +441,8 @@ class Renderer(renderer.Renderer):
69219a
                     # TODO(harlowja): add validation that no other iface has
69219a
                     # also provided the default route?
69219a
                     iface_cfg['DEFROUTE'] = True
69219a
+                    if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4', 'dhcp6'):
69219a
+                        iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = True
69219a
                     if 'gateway' in route:
69219a
                         if is_ipv6 or is_ipv6_addr(route['gateway']):
69219a
                             iface_cfg['IPV6_DEFAULTGW'] = route['gateway']
69219a
@@ -490,7 +493,9 @@ class Renderer(renderer.Renderer):
69219a
             iface_cfg = iface_contents[iface_name]
69219a
             route_cfg = iface_cfg.routes
69219a
 
69219a
-            cls._render_subnets(iface_cfg, iface_subnets)
69219a
+            cls._render_subnets(
69219a
+                iface_cfg, iface_subnets, network_state.has_default_route
69219a
+            )
69219a
             cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
69219a
 
69219a
     @classmethod
69219a
@@ -515,7 +520,9 @@ class Renderer(renderer.Renderer):
69219a
 
69219a
             iface_subnets = iface.get("subnets", [])
69219a
             route_cfg = iface_cfg.routes
69219a
-            cls._render_subnets(iface_cfg, iface_subnets)
69219a
+            cls._render_subnets(
69219a
+                iface_cfg, iface_subnets, network_state.has_default_route
69219a
+            )
69219a
             cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
69219a
 
69219a
             # iter_interfaces on network-state is not sorted to produce
69219a
@@ -544,7 +551,9 @@ class Renderer(renderer.Renderer):
69219a
 
69219a
             iface_subnets = iface.get("subnets", [])
69219a
             route_cfg = iface_cfg.routes
69219a
-            cls._render_subnets(iface_cfg, iface_subnets)
69219a
+            cls._render_subnets(
69219a
+                iface_cfg, iface_subnets, network_state.has_default_route
69219a
+            )
69219a
             cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
69219a
 
69219a
     @staticmethod
69219a
@@ -603,7 +612,9 @@ class Renderer(renderer.Renderer):
69219a
 
69219a
             iface_subnets = iface.get("subnets", [])
69219a
             route_cfg = iface_cfg.routes
69219a
-            cls._render_subnets(iface_cfg, iface_subnets)
69219a
+            cls._render_subnets(
69219a
+                iface_cfg, iface_subnets, network_state.has_default_route
69219a
+            )
69219a
             cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
69219a
 
69219a
     @classmethod
69219a
@@ -615,7 +626,9 @@ class Renderer(renderer.Renderer):
69219a
             iface_cfg.kind = 'infiniband'
69219a
             iface_subnets = iface.get("subnets", [])
69219a
             route_cfg = iface_cfg.routes
69219a
-            cls._render_subnets(iface_cfg, iface_subnets)
69219a
+            cls._render_subnets(
69219a
+                iface_cfg, iface_subnets, network_state.has_default_route
69219a
+            )
69219a
             cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
69219a
 
69219a
     @classmethod
69219a
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
69219a
index 4224301..a975678 100644
69219a
--- a/tests/unittests/test_net.py
69219a
+++ b/tests/unittests/test_net.py
69219a
@@ -546,6 +546,7 @@ NETWORK_CONFIGS = {
69219a
                 BOOTPROTO=dhcp
69219a
                 DEFROUTE=yes
69219a
                 DEVICE=eth99
69219a
+                DHCLIENT_SET_DEFAULT_ROUTE=yes
69219a
                 DNS1=8.8.8.8
69219a
                 DNS2=8.8.4.4
69219a
                 DOMAIN="barley.maas sach.maas"
69219a
@@ -913,6 +914,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
69219a
             'ifcfg-bond0.200': textwrap.dedent("""\
69219a
                 BOOTPROTO=dhcp
69219a
                 DEVICE=bond0.200
69219a
+                DHCLIENT_SET_DEFAULT_ROUTE=no
69219a
                 ONBOOT=yes
69219a
                 PHYSDEV=bond0
69219a
                 TYPE=Ethernet
69219a
@@ -996,6 +998,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
69219a
             'ifcfg-eth5': textwrap.dedent("""\
69219a
                 BOOTPROTO=dhcp
69219a
                 DEVICE=eth5
69219a
+                DHCLIENT_SET_DEFAULT_ROUTE=no
69219a
                 HWADDR=98:bb:9f:2c:e8:8a
69219a
                 ONBOOT=no
69219a
                 TYPE=Ethernet
69219a
@@ -1624,6 +1627,23 @@ CONFIG_V1_SIMPLE_SUBNET = {
69219a
                              'type': 'static'}],
69219a
                 'type': 'physical'}]}
69219a
 
69219a
+CONFIG_V1_MULTI_IFACE = {
69219a
+    'version': 1,
69219a
+    'config': [{'type': 'physical',
69219a
+                'mtu': 1500,
69219a
+                'subnets': [{'type': 'static',
69219a
+                             'netmask': '255.255.240.0',
69219a
+                             'routes': [{'netmask': '0.0.0.0',
69219a
+                                         'network': '0.0.0.0',
69219a
+                                         'gateway': '51.68.80.1'}],
69219a
+                             'address': '51.68.89.122',
69219a
+                             'ipv4': True}],
69219a
+                'mac_address': 'fa:16:3e:25:b4:59',
69219a
+                'name': 'eth0'},
69219a
+               {'type': 'physical',
69219a
+                'mtu': 9000,
69219a
+                'subnets': [{'type': 'dhcp4'}],
69219a
+                'mac_address': 'fa:16:3e:b1:ca:29', 'name': 'eth1'}]}
69219a
 
69219a
 DEFAULT_DEV_ATTRS = {
69219a
     'eth1000': {
69219a
@@ -2088,6 +2108,49 @@ USERCTL=no
69219a
 """
69219a
         self.assertEqual(expected, found[nspath + 'ifcfg-interface0'])
69219a
 
69219a
+    def test_network_config_v1_multi_iface_samples(self):
69219a
+        ns = network_state.parse_net_config_data(CONFIG_V1_MULTI_IFACE)
69219a
+        render_dir = self.tmp_path("render")
69219a
+        os.makedirs(render_dir)
69219a
+        renderer = self._get_renderer()
69219a
+        renderer.render_network_state(ns, target=render_dir)
69219a
+        found = dir2dict(render_dir)
69219a
+        nspath = '/etc/sysconfig/network-scripts/'
69219a
+        self.assertNotIn(nspath + 'ifcfg-lo', found.keys())
69219a
+        expected_i1 = """\
69219a
+# Created by cloud-init on instance boot automatically, do not edit.
69219a
+#
69219a
+BOOTPROTO=none
69219a
+DEFROUTE=yes
69219a
+DEVICE=eth0
69219a
+GATEWAY=51.68.80.1
69219a
+HWADDR=fa:16:3e:25:b4:59
69219a
+IPADDR=51.68.89.122
69219a
+MTU=1500
69219a
+NETMASK=255.255.240.0
69219a
+NM_CONTROLLED=no
69219a
+ONBOOT=yes
69219a
+STARTMODE=auto
69219a
+TYPE=Ethernet
69219a
+USERCTL=no
69219a
+"""
69219a
+        self.assertEqual(expected_i1, found[nspath + 'ifcfg-eth0'])
69219a
+        expected_i2 = """\
69219a
+# Created by cloud-init on instance boot automatically, do not edit.
69219a
+#
69219a
+BOOTPROTO=dhcp
69219a
+DEVICE=eth1
69219a
+DHCLIENT_SET_DEFAULT_ROUTE=no
69219a
+HWADDR=fa:16:3e:b1:ca:29
69219a
+MTU=9000
69219a
+NM_CONTROLLED=no
69219a
+ONBOOT=yes
69219a
+STARTMODE=auto
69219a
+TYPE=Ethernet
69219a
+USERCTL=no
69219a
+"""
69219a
+        self.assertEqual(expected_i2, found[nspath + 'ifcfg-eth1'])
69219a
+
69219a
     def test_config_with_explicit_loopback(self):
69219a
         ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
69219a
         render_dir = self.tmp_path("render")
69219a
-- 
69219a
1.8.3.1
69219a