Support for IPv6 RDNSS Option in Router Advts

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
This commit is contained in:
sridhargaddam 2015-10-19 05:32:16 +00:00 committed by Sridhar Gaddam
parent 3ed7371332
commit a3e102934c
5 changed files with 36 additions and 4 deletions

View 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())

View 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']:

View 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']})

View 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:

View 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]}