diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index ba8cb23e81c..a46bc12f01c 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -882,12 +882,22 @@ class Dnsmasq(DhcpLocalProcess): addr_mode == constants.IPV6_SLAAC)): continue if subnet.dns_nameservers: - options.append( - self._format_option( - subnet.ip_version, i, 'dns-server', - ','.join( - Dnsmasq._convert_to_literal_addrs( - subnet.ip_version, 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 == ['::'])): + # Special case: Do not announce DNS servers + 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: # use the dnsmasq ip as nameservers only if there is no # dns-server submitted by the server diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index d35ed959148..8236e7c9db2 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -442,6 +442,13 @@ class FakeV4SubnetAgentWithManyDnsProvided(FakeV4Subnet): 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): def __init__(self): self.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' @@ -469,6 +476,15 @@ class FakeV4AgentWithManyDnsProvided(object): 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): def __init__(self): super(FakeV4SubnetMultipleAgentsWithDnsProvided, self).__init__() @@ -546,6 +562,19 @@ class FakeV6SubnetStateless(object): 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): def __init__(self): self.id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' @@ -898,6 +927,14 @@ class FakeV6NetworkStatelessDHCP(object): 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): def __init__(self): self.id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' @@ -1429,6 +1466,18 @@ class TestDnsmasq(TestBase): self._test_output_opts_file(expected, 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): expected = ('tag:tag0,option:dns-server,8.8.8.8\n' '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), 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): exp_host_name = '/dhcp/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee/host' exp_host_data = ( diff --git a/releasenotes/notes/bug-1311040-dhcp-no-dns-09291c23e2ce800a.yaml b/releasenotes/notes/bug-1311040-dhcp-no-dns-09291c23e2ce800a.yaml new file mode 100644 index 00000000000..546d6b72b9a --- /dev/null +++ b/releasenotes/notes/bug-1311040-dhcp-no-dns-09291c23e2ce800a.yaml @@ -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.