From a416f8b0ab810f34ea274183df590a73d150a137 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 7 Dec 2021 11:42:53 +0000 Subject: [PATCH] Do not announce any DNS resolver if "0.0.0.0" or "::" provided The DHCP server should not announce any DNS resolver at all on the subnet if "0.0.0.0" (IPv4) or "::" (IPv6) are configured as DNS name servers in any subnet. https://docs.openstack.org/neutron/latest/admin/config-dns-res.html Closes-Bug: #1950686 Change-Id: I78dd012764c7bd7a29aeb8d97c00b627d7723aeb --- neutron/agent/linux/dhcp.py | 6 +- neutron/common/ovn/utils.py | 22 ++++++++ neutron/common/utils.py | 7 +++ .../ovn/mech_driver/ovsdb/ovn_client.py | 11 ++-- neutron/tests/unit/common/ovn/test_utils.py | 56 ++++++++++++++++++- 5 files changed, 91 insertions(+), 11 deletions(-) diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 90ae0e9e915..18579858b7a 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -1153,10 +1153,8 @@ class Dnsmasq(DhcpLocalProcess): addr_mode == constants.IPV6_SLAAC)): continue if subnet.dns_nameservers: - if ((subnet.ip_version == 4 and - subnet.dns_nameservers == ['0.0.0.0']) or - (subnet.ip_version == 6 and - subnet.dns_nameservers == ['::'])): + if common_utils.is_dns_servers_any_address( + subnet.dns_nameservers, subnet.ip_version): # Special case: Do not announce DNS servers options.append( self._format_option( diff --git a/neutron/common/ovn/utils.py b/neutron/common/ovn/utils.py index 7369cffc5b2..aee875e9f57 100644 --- a/neutron/common/ovn/utils.py +++ b/neutron/common/ovn/utils.py @@ -37,6 +37,8 @@ from ovsdbapp import constants as ovsdbapp_const from neutron._i18n import _ from neutron.common.ovn import constants from neutron.common.ovn import exceptions as ovn_exc +from neutron.common import utils as common_utils +from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf from neutron.db import models_v2 from neutron.objects import ports as ports_obj @@ -414,6 +416,26 @@ def get_system_dns_resolvers(resolver_file=DNS_RESOLVER_FILE): return resolvers +def get_dhcp_dns_servers(subnet, ip_version=const.IP_VERSION_4): + """Retrieve the DHCP option DNS servers + + The DHCP should not announce any DNS resolver at all on the subnet if any + configured DNS server is "0.0.0.0" (IPv4) or "::" (IPv6). + https://docs.openstack.org/neutron/latest/admin/config-dns-res.html + """ + if ip_version == const.IP_VERSION_4: + dns_servers = (subnet.get('dns_nameservers') or + ovn_conf.get_dns_servers() or + get_system_dns_resolvers()) + else: + dns_servers = subnet['dns_nameservers'] + + if common_utils.is_dns_servers_any_address(dns_servers, ip_version): + return [] + + return dns_servers + + def get_port_subnet_ids(port): fixed_ips = list(port['fixed_ips']) return [f['subnet_id'] for f in fixed_ips] diff --git a/neutron/common/utils.py b/neutron/common/utils.py index 09750fbb644..853bce3a23d 100644 --- a/neutron/common/utils.py +++ b/neutron/common/utils.py @@ -145,6 +145,13 @@ def get_dhcp_agent_device_id(network_id, host): return 'dhcp%s-%s' % (host_uuid, network_id) +def is_dns_servers_any_address(dns_servers, ip_version): + """Checks if DNS server list matches the IP any address '0.0.0.0'/'::'""" + ip_any = netaddr.IPNetwork(n_const.IP_ANY[ip_version]).ip + return (len(dns_servers) == 1 and + netaddr.IPNetwork(dns_servers[0]).ip == ip_any) + + class exception_logger(object): """Wrap a function and log raised exception diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index f7d2dcac70c..6f7dee24e85 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -1925,9 +1925,7 @@ class OVNClient(object): options['server_mac'] = n_net.get_random_mac( cfg.CONF.base_mac.split(':')) - dns_servers = (subnet.get('dns_nameservers') or - ovn_conf.get_dns_servers() or - utils.get_system_dns_resolvers()) + dns_servers = utils.get_dhcp_dns_servers(subnet) if dns_servers: options['dns_server'] = '{%s}' % ', '.join(dns_servers) else: @@ -1966,9 +1964,10 @@ class OVNClient(object): cfg.CONF.base_mac.split(':')) } - if subnet['dns_nameservers']: - dns_servers = '{%s}' % ', '.join(subnet['dns_nameservers']) - dhcpv6_opts['dns_server'] = dns_servers + dns_servers = utils.get_dhcp_dns_servers(subnet, + ip_version=const.IP_VERSION_6) + if dns_servers: + dhcpv6_opts['dns_server'] = '{%s}' % ', '.join(dns_servers) if subnet.get('ipv6_address_mode') == const.DHCPV6_STATELESS: dhcpv6_opts[ovn_const.DHCPV6_STATELESS_OPT] = 'true' diff --git a/neutron/tests/unit/common/ovn/test_utils.py b/neutron/tests/unit/common/ovn/test_utils.py index 64f9e656745..85a9d098b11 100644 --- a/neutron/tests/unit/common/ovn/test_utils.py +++ b/neutron/tests/unit/common/ovn/test_utils.py @@ -14,10 +14,13 @@ # under the License. from collections import namedtuple +from os import path from unittest import mock import fixtures from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext +from neutron_lib import constants as n_const +from oslo_config import cfg from neutron.common.ovn import constants from neutron.common.ovn import utils @@ -33,6 +36,7 @@ nameserver foo 10.0.0.4 nameserver aef0::4 foo 10.0.0.5 """ +RESOLV_DNS_SERVERS = ['10.0.0.1', '10.0.0.3'] class TestUtils(base.BaseTestCase): @@ -43,7 +47,7 @@ class TestUtils(base.BaseTestCase): tmp_resolv_file = open(resolver_file_name, 'w') tmp_resolv_file.writelines(RESOLV_CONF_TEMPLATE) tmp_resolv_file.close() - expected_dns_resolvers = ['10.0.0.1', '10.0.0.3'] + expected_dns_resolvers = RESOLV_DNS_SERVERS observed_dns_resolvers = utils.get_system_dns_resolvers( resolver_file=resolver_file_name) self.assertEqual(expected_dns_resolvers, observed_dns_resolvers) @@ -379,3 +383,53 @@ class TestConnectionConfigToTargetString(base.BaseTestCase): for config, target in config_target: output = utils.connection_config_to_target_string(config) self.assertEqual(target, output) + + +class TestGetDhcpDnsServers(base.BaseTestCase): + + def test_ipv4(self): + # DNS servers from subnet. + dns_servers = utils.get_dhcp_dns_servers( + {'dns_nameservers': ['1.2.3.4', '5.6.7.8']}) + self.assertEqual(['1.2.3.4', '5.6.7.8'], dns_servers) + + # DNS servers from config parameter. + cfg.CONF.set_override('dns_servers', + '1.1.2.2,3.3.4.4', group='ovn') + dns_servers = utils.get_dhcp_dns_servers({}) + self.assertEqual(['1.1.2.2', '3.3.4.4'], dns_servers) + + # DNS servers from local DNS resolver. + cfg.CONF.set_override('dns_servers', '', group='ovn') + with mock.patch('builtins.open', + mock.mock_open(read_data=RESOLV_CONF_TEMPLATE)), \ + mock.patch.object(path, 'exists', return_value=True): + dns_servers = utils.get_dhcp_dns_servers({}) + self.assertEqual(RESOLV_DNS_SERVERS, dns_servers) + + # No DNS servers if only '0.0.0.0' configured. + dns_servers = utils.get_dhcp_dns_servers( + {'dns_nameservers': ['0.0.0.0', '5.6.7.8']}) + self.assertEqual(['0.0.0.0', '5.6.7.8'], dns_servers) + dns_servers = utils.get_dhcp_dns_servers( + {'dns_nameservers': ['0.0.0.0']}) + self.assertEqual([], dns_servers) + + def test_ipv6(self): + # DNS servers from subnet. + dns_servers = utils.get_dhcp_dns_servers( + {'dns_nameservers': ['2001:4860:4860::8888', + '2001:4860:4860::8844']}, + ip_version=n_const.IP_VERSION_6) + self.assertEqual(['2001:4860:4860::8888', + '2001:4860:4860::8844'], dns_servers) + + # No DNS servers if only '::' configured. + dns_servers = utils.get_dhcp_dns_servers( + {'dns_nameservers': ['2001:4860:4860::8888', '::']}, + ip_version=n_const.IP_VERSION_6) + self.assertEqual(['2001:4860:4860::8888', '::'], dns_servers) + dns_servers = utils.get_dhcp_dns_servers( + {'dns_nameservers': ['::']}, + ip_version=n_const.IP_VERSION_6) + self.assertEqual([], dns_servers)