Allow to disable DNS server announcement per subnet

Currently there is no way to have DHCP agents not announce DNS servers
for a subnet. The current behaviour when the dns_nameservers option is
set to '0.0.0.0' is that each agent will only announce itself instead of
announcing the list of all dhcp agents for that subnet, which seems not
too useful. So we redefine the meaning of this option to instruct the
DHCP agent to not announce any DNS server in that case.

Actually, going back to square one, it would be more natural to swap the
meaning of "option unset" and "option 0.0.0.0", but that would change
the default behaviour for all existing installation and thus does not
seem feasible.

Change-Id: I32d943360162c483ac1364100a21ab56b13517fb
Closes-Bug: 1311040
This commit is contained in:
Jens Harbott 2017-08-21 09:13:33 +00:00 committed by Brian Haley
parent f311b42d28
commit 584b7561c1
3 changed files with 93 additions and 6 deletions

View File

@ -882,12 +882,22 @@ class Dnsmasq(DhcpLocalProcess):
addr_mode == constants.IPV6_SLAAC)): addr_mode == constants.IPV6_SLAAC)):
continue continue
if subnet.dns_nameservers: if subnet.dns_nameservers:
options.append( if ((subnet.ip_version == 4 and
self._format_option( subnet.dns_nameservers == ['0.0.0.0']) or
subnet.ip_version, i, 'dns-server', (subnet.ip_version == 6 and
','.join( subnet.dns_nameservers == ['::'])):
Dnsmasq._convert_to_literal_addrs( # Special case: Do not announce DNS servers
subnet.ip_version, subnet.dns_nameservers)))) options.append(
self._format_option(
subnet.ip_version, i, 'dns-server'))
else:
options.append(
self._format_option(
subnet.ip_version, i, 'dns-server',
','.join(
Dnsmasq._convert_to_literal_addrs(
subnet.ip_version,
subnet.dns_nameservers))))
else: else:
# use the dnsmasq ip as nameservers only if there is no # use the dnsmasq ip as nameservers only if there is no
# dns-server submitted by the server # dns-server submitted by the server

View File

@ -442,6 +442,13 @@ class FakeV4SubnetAgentWithManyDnsProvided(FakeV4Subnet):
self.host_routes = [] self.host_routes = []
class FakeV4SubnetAgentWithNoDnsProvided(FakeV4Subnet):
def __init__(self):
super(FakeV4SubnetAgentWithNoDnsProvided, self).__init__()
self.dns_nameservers = ['0.0.0.0']
self.host_routes = []
class FakeV4MultipleAgentsWithoutDnsProvided(object): class FakeV4MultipleAgentsWithoutDnsProvided(object):
def __init__(self): def __init__(self):
self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
@ -469,6 +476,15 @@ class FakeV4AgentWithManyDnsProvided(object):
self.namespace = 'qdhcp-ns' self.namespace = 'qdhcp-ns'
class FakeV4AgentWithNoDnsProvided(object):
def __init__(self):
self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
self.subnets = [FakeV4SubnetAgentWithNoDnsProvided()]
self.ports = [FakePort1(), FakePort2(), FakePort3(), FakeRouterPort(),
FakePortMultipleAgents1()]
self.namespace = 'qdhcp-ns'
class FakeV4SubnetMultipleAgentsWithDnsProvided(FakeV4Subnet): class FakeV4SubnetMultipleAgentsWithDnsProvided(FakeV4Subnet):
def __init__(self): def __init__(self):
super(FakeV4SubnetMultipleAgentsWithDnsProvided, self).__init__() super(FakeV4SubnetMultipleAgentsWithDnsProvided, self).__init__()
@ -546,6 +562,19 @@ class FakeV6SubnetStateless(object):
self.ipv6_ra_mode = None self.ipv6_ra_mode = None
class FakeV6SubnetStatelessNoDnsProvided(object):
def __init__(self):
self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
self.ip_version = 6
self.cidr = 'ffea:3ba5:a17a:4ba3::/64'
self.gateway_ip = 'ffea:3ba5:a17a:4ba3::1'
self.enable_dhcp = True
self.dns_nameservers = ['::']
self.host_routes = []
self.ipv6_address_mode = constants.DHCPV6_STATELESS
self.ipv6_ra_mode = None
class FakeV6SubnetStatelessBadPrefixLength(object): class FakeV6SubnetStatelessBadPrefixLength(object):
def __init__(self): def __init__(self):
self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
@ -898,6 +927,14 @@ class FakeV6NetworkStatelessDHCP(object):
self.namespace = 'qdhcp-ns' self.namespace = 'qdhcp-ns'
class FakeV6NetworkStatelessDHCPNoDnsProvided(object):
def __init__(self):
self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'
self.subnets = [FakeV6SubnetStatelessNoDnsProvided()]
self.ports = [FakeV6Port()]
self.namespace = 'qdhcp-ns'
class FakeV6NetworkStatelessDHCPBadPrefixLength(object): class FakeV6NetworkStatelessDHCPBadPrefixLength(object):
def __init__(self): def __init__(self):
self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'
@ -1429,6 +1466,18 @@ class TestDnsmasq(TestBase):
self._test_output_opts_file(expected, self._test_output_opts_file(expected,
FakeV4AgentWithManyDnsProvided()) FakeV4AgentWithManyDnsProvided())
def test_output_opts_file_agent_with_no_dns_provided(self):
expected = ('tag:tag0,'
'option:dns-server\n'
'tag:tag0,option:classless-static-route,'
'169.254.169.254/32,192.168.0.1,0.0.0.0/0,192.168.0.1\n'
'tag:tag0,249,169.254.169.254/32,192.168.0.1,0.0.0.0/0,'
'192.168.0.1\n'
'tag:tag0,option:router,192.168.0.1').lstrip()
self._test_output_opts_file(expected,
FakeV4AgentWithNoDnsProvided())
def test_output_opts_file_multiple_agents_with_dns_provided(self): def test_output_opts_file_multiple_agents_with_dns_provided(self):
expected = ('tag:tag0,option:dns-server,8.8.8.8\n' expected = ('tag:tag0,option:dns-server,8.8.8.8\n'
'tag:tag0,option:classless-static-route,' 'tag:tag0,option:classless-static-route,'
@ -2239,6 +2288,18 @@ class TestDnsmasq(TestBase):
self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data),
mock.call(exp_opt_name, exp_opt_data)]) mock.call(exp_opt_name, exp_opt_data)])
def test_host_and_opts_file_on_stateless_dhcpv6_network_no_dns(self):
exp_host_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/host'
exp_opt_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/opts'
exp_opt_data = ('tag:tag0,option6:dns-server\n'
'tag:tag0,'
'option6:domain-search,openstacklocal').lstrip()
dm = self._get_dnsmasq(FakeV6NetworkStatelessDHCPNoDnsProvided())
dm._output_hosts_file()
dm._output_opts_file()
self.safe.assert_has_calls([mock.call(exp_host_name, ''),
mock.call(exp_opt_name, exp_opt_data)])
def test_host_file_on_net_with_v6_slaac_and_v4(self): def test_host_file_on_net_with_v6_slaac_and_v4(self):
exp_host_name = '/dhcp/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee/host' exp_host_name = '/dhcp/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee/host'
exp_host_data = ( exp_host_data = (

View File

@ -0,0 +1,16 @@
---
prelude: >
DNS server assignment can now be disabled in replies sent from the DHCP agent.
features:
- |
It is now possible to instruct the DHCP agent not to supply any DNS server
address to their clients by setting the ``dns_nameservers`` attribute for
the corresponding subnet to ``0.0.0.0`` or ``::``, for IPv4 or IPv6 subnets
(respectively).
upgrade:
- |
The functionality when a subnet has its DNS server set to ``0.0.0.0`` or
``::`` has been changed with this release. The old behaviour was that each
DHCP agent would supply only its own IP address as the DNS server to its
clients. The new behaviour is that the DHCP agent will not supply any DNS
server IP address at all.