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

Conflicts:
    neutron/common/ovn/utils.py
    neutron/tests/unit/common/ovn/test_utils.py

Closes-Bug: #1950686
Change-Id: I78dd012764c7bd7a29aeb8d97c00b627d7723aeb
(cherry picked from commit a416f8b0ab)
(cherry picked from commit 625fa3a5b2)
This commit is contained in:
Rodolfo Alonso Hernandez 2021-12-07 11:42:53 +00:00 committed by Rodolfo Alonso
parent 7bca2cc526
commit 329d25cce3
5 changed files with 92 additions and 11 deletions

View File

@ -1150,10 +1150,8 @@ class Dnsmasq(DhcpLocalProcess):
addr_mode == constants.IPV6_SLAAC)): addr_mode == constants.IPV6_SLAAC)):
continue continue
if subnet.dns_nameservers: if subnet.dns_nameservers:
if ((subnet.ip_version == 4 and if common_utils.is_dns_servers_any_address(
subnet.dns_nameservers == ['0.0.0.0']) or subnet.dns_nameservers, subnet.ip_version):
(subnet.ip_version == 6 and
subnet.dns_nameservers == ['::'])):
# Special case: Do not announce DNS servers # Special case: Do not announce DNS servers
options.append( options.append(
self._format_option( self._format_option(

View File

@ -39,6 +39,8 @@ from ovsdbapp.backend.ovs_idl import idlutils
from neutron._i18n import _ from neutron._i18n import _
from neutron.common.ovn import constants from neutron.common.ovn import constants
from neutron.common.ovn import exceptions as ovn_exc 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
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -418,6 +420,26 @@ def get_system_dns_resolvers(resolver_file=DNS_RESOLVER_FILE):
return resolvers 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): def get_port_subnet_ids(port):
fixed_ips = [ip for ip in port['fixed_ips']] fixed_ips = [ip for ip in port['fixed_ips']]
return [f['subnet_id'] for f in fixed_ips] return [f['subnet_id'] for f in fixed_ips]

View File

@ -143,6 +143,13 @@ def get_dhcp_agent_device_id(network_id, host):
return 'dhcp%s-%s' % (host_uuid, network_id) 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): class exception_logger(object):
"""Wrap a function and log raised exception """Wrap a function and log raised exception

View File

@ -1853,9 +1853,7 @@ class OVNClient(object):
options['server_mac'] = n_net.get_random_mac( options['server_mac'] = n_net.get_random_mac(
cfg.CONF.base_mac.split(':')) cfg.CONF.base_mac.split(':'))
dns_servers = (subnet.get('dns_nameservers') or dns_servers = utils.get_dhcp_dns_servers(subnet)
ovn_conf.get_dns_servers() or
utils.get_system_dns_resolvers())
if dns_servers: if dns_servers:
options['dns_server'] = '{%s}' % ', '.join(dns_servers) options['dns_server'] = '{%s}' % ', '.join(dns_servers)
else: else:
@ -1894,9 +1892,10 @@ class OVNClient(object):
cfg.CONF.base_mac.split(':')) cfg.CONF.base_mac.split(':'))
} }
if subnet['dns_nameservers']: dns_servers = utils.get_dhcp_dns_servers(subnet,
dns_servers = '{%s}' % ', '.join(subnet['dns_nameservers']) ip_version=const.IP_VERSION_6)
dhcpv6_opts['dns_server'] = dns_servers if dns_servers:
dhcpv6_opts['dns_server'] = '{%s}' % ', '.join(dns_servers)
if subnet.get('ipv6_address_mode') == const.DHCPV6_STATELESS: if subnet.get('ipv6_address_mode') == const.DHCPV6_STATELESS:
dhcpv6_opts[ovn_const.DHCPV6_STATELESS_OPT] = 'true' dhcpv6_opts[ovn_const.DHCPV6_STATELESS_OPT] = 'true'

View File

@ -13,9 +13,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from os import path
import fixtures import fixtures
import mock import mock
from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext 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 constants
from neutron.common.ovn import utils from neutron.common.ovn import utils
@ -31,6 +35,7 @@ nameserver foo 10.0.0.4
nameserver aef0::4 nameserver aef0::4
foo 10.0.0.5 foo 10.0.0.5
""" """
RESOLV_DNS_SERVERS = ['10.0.0.1', '10.0.0.3']
class TestUtils(base.BaseTestCase): class TestUtils(base.BaseTestCase):
@ -41,7 +46,7 @@ class TestUtils(base.BaseTestCase):
tmp_resolv_file = open(resolver_file_name, 'w') tmp_resolv_file = open(resolver_file_name, 'w')
tmp_resolv_file.writelines(RESOLV_CONF_TEMPLATE) tmp_resolv_file.writelines(RESOLV_CONF_TEMPLATE)
tmp_resolv_file.close() 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( observed_dns_resolvers = utils.get_system_dns_resolvers(
resolver_file=resolver_file_name) resolver_file=resolver_file_name)
self.assertEqual(expected_dns_resolvers, observed_dns_resolvers) self.assertEqual(expected_dns_resolvers, observed_dns_resolvers)
@ -281,3 +286,53 @@ class TestConnectionConfigToTargetString(base.BaseTestCase):
for config, target in config_target: for config, target in config_target:
output = utils.connection_config_to_target_string(config) output = utils.connection_config_to_target_string(config)
self.assertEqual(target, output) 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)