dhcp fails if extra_dhcp_opts for stateless subnet enabled

vm on a network having IPv4 and IPv6 dhcpv6 stateless subnets,
fails to get IPv4 address, when vm uses a port with extra_dhcp_opts.

neutron creates entries in dhcp host file for each subnet of a port.
Each of these entries will have same mac address as first field,
and may have client_id, fqdn, ipv4/ipv6 address for dhcp/dhcpv6 stateful,
or tag as other fields.
For dhcpv6 stateless subnet with extra_dhcp_opts,
host file will have only mac address and tag.

If the last entry in host file for the port with extra_dhcp_opts,
is for dhcpv6 stateless subnet, then dnsmasq tries to use this entry,
(as dnsmasq reads the hosts file from EOF) to resolve
dhcp request even for IPv4, treats as 'no address found'
and fails to send DHCPOFFER.

So we sort the fixed_ips, so that ipv6 subnets for the port are added
first in host file, to avoid this issue.

Kilo changes:
- test is modified not to include metadata IP static routes since the
  patch that introduced them in Liberty is not present in Kilo.

Change-Id: I3bea58d86a3508e49cbac1d03c6b640836b4a7a2
Closes-bug: #1466144
(cherry picked from commit 481d9a4f35)
This commit is contained in:
venkata anil 2015-06-24 07:33:09 +00:00 committed by Ihar Hrachyshka
parent 7a0b594567
commit e8bfd4b9dd
2 changed files with 93 additions and 1 deletions

View File

@ -428,6 +428,44 @@ class Dnsmasq(DhcpLocalProcess):
LOG.debug('Reloading allocations for network: %s', self.network.id)
self.device_manager.update(self.network, self.interface_name)
def _sort_fixed_ips_for_dnsmasq(self, fixed_ips, v6_nets):
"""Sort fixed_ips so that stateless IPv6 subnets appear first.
For example, If a port with v6 extra_dhcp_opts is on a network with
IPv4 and IPv6 stateless subnets. Then dhcp host file will have
below 2 entries for same MAC,
fa:16:3e:8f:9d:65,30.0.0.5,set:aabc7d33-4874-429e-9637-436e4232d2cd
(entry for IPv4 dhcp)
fa:16:3e:8f:9d:65,set:aabc7d33-4874-429e-9637-436e4232d2cd
(entry for stateless IPv6 for v6 options)
dnsmasq internal details for processing host file entries
1) dnsmaq reads the host file from EOF.
2) So it first picks up stateless IPv6 entry,
fa:16:3e:8f:9d:65,set:aabc7d33-4874-429e-9637-436e4232d2cd
3) But dnsmasq doesn't have sufficient checks to skip this entry and
pick next entry, to process dhcp IPv4 request.
4) So dnsmaq uses this this entry to process dhcp IPv4 request.
5) As there is no ip in this entry, dnsmaq logs "no address available"
and fails to send DHCPOFFER message.
As we rely on internal details of dnsmasq to understand and fix the
issue, Ihar sent a mail to dnsmasq-discuss mailing list
http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2015q2/
009650.html
So If we reverse the order of writing entries in host file,
so that entry for stateless IPv6 comes first,
then dnsmasq can correctly fetch the IPv4 address.
"""
return sorted(
fixed_ips,
key=lambda fip: ((fip.subnet_id in v6_nets) and (
v6_nets[fip.subnet_id].ipv6_address_mode == (
constants.DHCPV6_STATELESS))),
reverse=True)
def _iter_hosts(self):
"""Iterate over hosts.
@ -443,8 +481,11 @@ class Dnsmasq(DhcpLocalProcess):
"""
v6_nets = dict((subnet.id, subnet) for subnet in
self.network.subnets if subnet.ip_version == 6)
for port in self.network.ports:
for alloc in port.fixed_ips:
fixed_ips = self._sort_fixed_ips_for_dnsmasq(port.fixed_ips,
v6_nets)
for alloc in fixed_ips:
# Note(scollins) Only create entries that are
# associated with the subnet being managed by this
# dhcp agent

View File

@ -127,6 +127,23 @@ class FakeV6PortExtraOpt(object):
ip_version=6)]
class FakeDualPortWithV6ExtraOpt(object):
id = 'hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh'
admin_state_up = True
device_owner = 'foo3'
fixed_ips = [FakeIPAllocation('192.168.0.3',
'dddddddd-dddd-dddd-dddd-dddddddddddd'),
FakeIPAllocation('ffea:3ba5:a17a:4ba3:0216:3eff:fec2:771d',
'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee')]
mac_address = '00:16:3e:c2:77:1d'
def __init__(self):
self.extra_dhcp_opts = [
DhcpOpt(opt_name='dns-server',
opt_value='ffea:3ba5:a17a:4ba3::100',
ip_version=6)]
class FakeDualPort(object):
id = 'hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh'
admin_state_up = True
@ -577,6 +594,14 @@ class FakeV6NetworkStatelessDHCP(object):
namespace = 'qdhcp-ns'
class FakeNetworkWithV6SatelessAndV4DHCPSubnets(object):
id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'
subnets = [FakeV6SubnetStateless(), FakeV4Subnet()]
ports = [FakeDualPortWithV6ExtraOpt(), FakeRouterPort()]
namespace = 'qdhcp-ns'
class LocalChild(dhcp.DhcpLocalProcess):
PORTS = {4: [4], 6: [6]}
@ -1482,6 +1507,32 @@ 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_net_with_V6_stateless_and_V4_subnets(
self):
exp_host_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/host'
exp_host_data = (
'00:16:3e:c2:77:1d,set:hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh\n'
'00:16:3e:c2:77:1d,host-192-168-0-3.openstacklocal,'
'192.168.0.3,set:hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh\n'
'00:00:0f:rr:rr:rr,'
'host-192-168-0-1.openstacklocal,192.168.0.1\n').lstrip()
exp_opt_name = '/dhcp/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/opts'
exp_opt_data = (
'tag:tag0,option6:domain-search,openstacklocal\n'
'tag:tag1,option:dns-server,8.8.8.8\n'
'tag:tag1,option:classless-static-route,20.0.0.1/24,20.0.0.1,'
'0.0.0.0/0,192.168.0.1\n'
'tag:tag1,249,20.0.0.1/24,20.0.0.1,0.0.0.0/0,192.168.0.1\n'
'tag:tag1,option:router,192.168.0.1\n'
'tag:hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh,'
'option6:dns-server,ffea:3ba5:a17a:4ba3::100').lstrip()
dm = self._get_dnsmasq(FakeNetworkWithV6SatelessAndV4DHCPSubnets())
dm._output_hosts_file()
dm._output_opts_file()
self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data),
mock.call(exp_opt_name, exp_opt_data)])
def test_should_enable_metadata_namespaces_disabled_returns_false(self):
self.conf.set_override('use_namespaces', False)
self.assertFalse(dhcp.Dnsmasq.should_enable_metadata(self.conf,