]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Support for IPv6 RDNSS Option in Router Advts
authorsridhargaddam <sridhar.gaddam@enovance.com>
Mon, 19 Oct 2015 05:32:16 +0000 (05:32 +0000)
committerSridhar Gaddam <sridhar.gaddam@enovance.com>
Tue, 24 Nov 2015 15:38:28 +0000 (15:38 +0000)
RFC6106 standardizes IPv6 Router Advertisements to support
Recursive DNS server information. RDNSS info allows an IPv6
host to configure the DNS information via RA messages without
needing DHCPv6 for the DNS configuration.

This patch configures RADVD daemon to include RDNSS entries in
the Router Advertisements when the IPv6 subnet has dns_nameservers.

Closes-Bug: #1495465
Change-Id: Ia516d40b1c7a83cd7046b2b7f42d1204f44288a9

neutron/agent/linux/ra.py
neutron/db/l3_db.py
neutron/tests/common/l3_test_common.py
neutron/tests/unit/agent/l3/test_agent.py
neutron/tests/unit/db/test_l3_db.py

index d9eca8d6475c48bf2312c0a76501b21f720d4a07..67fdb31b24a61f503cb512dcdf517faee1341ffe 100644 (file)
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from itertools import chain as iter_chain
 import jinja2
 import netaddr
 from oslo_config import cfg
@@ -26,6 +27,8 @@ from neutron.common import constants
 
 RADVD_SERVICE_NAME = 'radvd'
 RADVD_SERVICE_CMD = 'radvd'
+# We can configure max of 3 DNS servers in radvd RDNSS section.
+MAX_RDNSS_ENTRIES = 3
 
 LOG = logging.getLogger(__name__)
 
@@ -51,6 +54,10 @@ CONFIG_TEMPLATE = jinja2.Template("""interface {{ interface_name }}
    AdvManagedFlag on;
    {% endif %}
 
+   {% if dns_servers %}
+   RDNSS {% for dns in dns_servers %} {{ dns }} {% endfor %} {};
+   {% endif %}
+
    {% for prefix in prefixes %}
    prefix {{ prefix }}
    {
@@ -88,10 +95,15 @@ class DaemonMonitor(object):
                     subnet['ipv6_ra_mode'] == constants.IPV6_SLAAC or
                     subnet['ipv6_ra_mode'] == constants.DHCPV6_STATELESS]
             interface_name = self._dev_name_helper(p['id'])
+            slaac_subnets = [subnet for subnet in v6_subnets if
+                subnet['ipv6_ra_mode'] == constants.IPV6_SLAAC]
+            dns_servers = list(iter_chain(*[subnet['dns_nameservers'] for
+                subnet in slaac_subnets if subnet.get('dns_nameservers')]))
             buf.write('%s' % CONFIG_TEMPLATE.render(
                 ra_modes=list(ra_modes),
                 interface_name=interface_name,
                 prefixes=auto_config_prefixes,
+                dns_servers=dns_servers[0:MAX_RDNSS_ENTRIES],
                 constants=constants))
 
         utils.replace_file(radvd_conf, buf.getvalue())
index 0f01040f5da257e19679d0fbacd7af166863f09a..d20bb4b174e77c0ac7c88d7491006e754b7cdbb2 100644 (file)
@@ -1207,7 +1207,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
             return {}
 
         filters = {'network_id': [id for id in network_ids]}
-        fields = ['id', 'cidr', 'gateway_ip',
+        fields = ['id', 'cidr', 'gateway_ip', 'dns_nameservers',
                   'network_id', 'ipv6_ra_mode', 'subnetpool_id']
 
         subnets_by_network = dict((id, []) for id in network_ids)
@@ -1238,6 +1238,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
                 subnet_info = {'id': subnet['id'],
                                'cidr': subnet['cidr'],
                                'gateway_ip': subnet['gateway_ip'],
+                               'dns_nameservers': subnet['dns_nameservers'],
                                'ipv6_ra_mode': subnet['ipv6_ra_mode'],
                                'subnetpool_id': subnet['subnetpool_id']}
                 for fixed_ip in port['fixed_ips']:
index 1c3a9f36db55aa34814abb37e951ccba7dc54305..18b9b896024b9eb4363560ee12572869f4c0eb43 100644 (file)
@@ -175,7 +175,8 @@ def router_append_interface(router, count=1, ip_version=4, ra_mode=None,
 
 
 def router_append_subnet(router, count=1, ip_version=4,
-                         ipv6_subnet_modes=None, interface_id=None):
+                         ipv6_subnet_modes=None, interface_id=None,
+                         dns_nameservers=None):
     if ip_version == 6:
         subnet_mode_none = {'ra_mode': None, 'address_mode': None}
         if not ipv6_subnet_modes:
@@ -222,6 +223,7 @@ def router_append_subnet(router, count=1, ip_version=4,
                 {'id': subnet_id,
                  'cidr': cidr_pool % (i + num_existing_subnets),
                  'gateway_ip': gw_pool % (i + num_existing_subnets),
+                 'dns_nameservers': dns_nameservers,
                  'ipv6_ra_mode': ipv6_subnet_modes[i]['ra_mode'],
                  'ipv6_address_mode': ipv6_subnet_modes[i]['address_mode']})
 
index c205e5d3e19ad2509c3e41a7a0c4099148de3674..e899feca7e82c1bbfdb493557496fcf5d92fa1de 100644 (file)
@@ -1190,7 +1190,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
         return expected_calls
 
     def _process_router_ipv6_subnet_added(
-            self, router, ipv6_subnet_modes=None):
+            self, router, ipv6_subnet_modes=None, dns_nameservers=None):
         agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
         ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
         agent.external_gateway_added = mock.Mock()
@@ -1201,7 +1201,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             router,
             count=len(ipv6_subnet_modes),
             ip_version=6,
-            ipv6_subnet_modes=ipv6_subnet_modes)
+            ipv6_subnet_modes=ipv6_subnet_modes,
+            dns_nameservers=dns_nameservers)
         # Reassign the router object to RouterInfo
         self._process_router_instance_for_agent(agent, ri, router)
         return ri
@@ -2169,6 +2170,21 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
             assertFlag(managed_flag)('AdvManagedFlag on;',
                 self.utils_replace_file.call_args[0][1])
 
+    def test_generate_radvd_rdnss_conf(self):
+        router = l3_test_common.prepare_router_data()
+        ipv6_subnet_modes = [{'ra_mode': l3_constants.IPV6_SLAAC,
+                             'address_mode': l3_constants.IPV6_SLAAC}]
+        dns_list = ['fd01:1::100', 'fd01:1::200', 'fd01::300', 'fd01::400']
+        ri = self._process_router_ipv6_subnet_added(router,
+                                                    ipv6_subnet_modes,
+                                                    dns_nameservers=dns_list)
+        ri.radvd._generate_radvd_conf(router[l3_constants.INTERFACE_KEY])
+        # Verify that radvd configuration file includes RDNSS entries
+        expected = "RDNSS  "
+        for dns in dns_list[0:ra.MAX_RDNSS_ENTRIES]:
+            expected += "%s  " % dns
+        self.assertIn(expected, self.utils_replace_file.call_args[0][1])
+
     def _pd_expected_call_external_process(self, requestor, ri, enable=True):
         expected_calls = []
         if enable:
index 19171c81ce875f9bcdd00413d9cde54c1d3b3871..e9784224018e6c8fef5789fb379cfeb47cdf26fc 100644 (file)
@@ -68,6 +68,7 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase):
         subnet = {'id': mock.sentinel.subnet_id,
                   'cidr': cidr,
                   'gateway_ip': mock.sentinel.gateway_ip,
+                  'dns_nameservers': mock.sentinel.dns_nameservers,
                   'ipv6_ra_mode': mock.sentinel.ipv6_ra_mode,
                   'subnetpool_id': mock.sentinel.subnetpool_id}
         get_subnets_by_network.return_value = {'net_id': [subnet]}