|
|
|
@ -567,7 +567,89 @@ class Dnsmasq(DhcpLocalProcess):
|
|
|
|
|
constants.DHCPV6_STATELESS))), |
|
|
|
|
reverse=True) |
|
|
|
|
|
|
|
|
|
def _iter_hosts(self): |
|
|
|
|
def _merge_alloc_addr6_list(self, fixed_ips, v6_nets): |
|
|
|
|
"""Merge fixed_ips to ipv6 addr lists |
|
|
|
|
|
|
|
|
|
If a port have multiple IPv6 addresses in the same subnet, merge the |
|
|
|
|
into one entry listing all the addresess, creating a single dhcp-host |
|
|
|
|
entry with the list of addresses defined allow dnsmasq to make all |
|
|
|
|
addresses available as requests for leases arrive. |
|
|
|
|
|
|
|
|
|
See dnsmasq-discuss mailing list: http://lists.thekelleys.org.uk/ |
|
|
|
|
pipermail/dnsmasq-discuss/2020q1/013743.html |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
by_subnet = {} |
|
|
|
|
NewFip = collections.namedtuple('NewFip', 'subnet_id ip_address') |
|
|
|
|
merged = [] |
|
|
|
|
|
|
|
|
|
for fip in fixed_ips: |
|
|
|
|
if (fip.subnet_id in v6_nets and |
|
|
|
|
v6_nets[fip.subnet_id].ipv6_address_mode == ( |
|
|
|
|
constants.DHCPV6_STATEFUL)): |
|
|
|
|
if fip.subnet_id not in by_subnet: |
|
|
|
|
by_subnet.update({fip.subnet_id: []}) |
|
|
|
|
by_subnet[fip.subnet_id].append(fip.ip_address) |
|
|
|
|
else: |
|
|
|
|
merged.append(fip) |
|
|
|
|
|
|
|
|
|
for subnet_id in by_subnet: |
|
|
|
|
addr6_list = ','.join([self._format_address_for_dnsmasq(ip) |
|
|
|
|
for ip in by_subnet[subnet_id]]) |
|
|
|
|
merged.append(NewFip(subnet_id=subnet_id, |
|
|
|
|
ip_address=addr6_list)) |
|
|
|
|
|
|
|
|
|
return merged |
|
|
|
|
|
|
|
|
|
def _get_dns_assignment(self, ip_address, dns_assignment): |
|
|
|
|
"""Get DNS assignment hostname and fqdn |
|
|
|
|
|
|
|
|
|
In dnsmasq it is not possible to configure two dhcp-host |
|
|
|
|
entries mapped to a single client mac address with IP |
|
|
|
|
addresses in the same subnet. When recieving a requst |
|
|
|
|
dnsmasq will match on the first entry in it's config, |
|
|
|
|
and lease that address. The second entry will never be |
|
|
|
|
used. |
|
|
|
|
|
|
|
|
|
For IPv6 it is possible to add multiple IPv6 addresses |
|
|
|
|
to a single dhcp-host entry by placing a list of addresses |
|
|
|
|
in brackets, i.e [addr1][addr2][...]. See dnsmasq mailing |
|
|
|
|
list: http://lists.thekelleys.org.uk/pipermail/ |
|
|
|
|
dnsmasq-discuss/2020q1/013671.html. Since we cannot have |
|
|
|
|
two hostnames in the dhcp-host entry this method picks the |
|
|
|
|
first hostname and fqdn it find's matching one of the IP's |
|
|
|
|
in the fixed-ips in dns_assignment or the hostname is |
|
|
|
|
generated based on the first fixed-ip. |
|
|
|
|
|
|
|
|
|
:param ip_address: IP address or a list of IPv6 addresses |
|
|
|
|
:param dns_ip_map: DNS IP Mapping |
|
|
|
|
:param dns_assignment: DNS assignments |
|
|
|
|
:return: hostname, fqdn |
|
|
|
|
""" |
|
|
|
|
hostname, fqdn = None, None |
|
|
|
|
ip_addresses = ip_address.replace('[', '').split(']') |
|
|
|
|
|
|
|
|
|
if dns_assignment: |
|
|
|
|
dns_ip_map = {d.ip_address: d for d in dns_assignment} |
|
|
|
|
for addr in ip_addresses: |
|
|
|
|
# If dns_name attribute is supported by ports API, return the |
|
|
|
|
# dns_assignment generated by the Neutron server. Otherwise, |
|
|
|
|
# generate hostname and fqdn locally (previous behaviour) |
|
|
|
|
if addr in dns_ip_map: |
|
|
|
|
hostname = dns_ip_map[addr].hostname |
|
|
|
|
fqdn = dns_ip_map[addr].fqdn |
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
if hostname is None: |
|
|
|
|
hostname = ('host-%s' % |
|
|
|
|
ip_addresses[0].replace('.', '-').replace(':', '-')) |
|
|
|
|
fqdn = hostname |
|
|
|
|
if self.conf.dns_domain: |
|
|
|
|
fqdn = '%s.%s' % (fqdn, self.conf.dns_domain) |
|
|
|
|
|
|
|
|
|
return hostname, fqdn |
|
|
|
|
|
|
|
|
|
def _iter_hosts(self, merge_addr6_list=False): |
|
|
|
|
"""Iterate over hosts. |
|
|
|
|
|
|
|
|
|
For each host on the network we yield a tuple containing: |
|
|
|
@ -590,11 +672,13 @@ class Dnsmasq(DhcpLocalProcess):
|
|
|
|
|
for port in self.network.ports: |
|
|
|
|
fixed_ips = self._sort_fixed_ips_for_dnsmasq(port.fixed_ips, |
|
|
|
|
v6_nets) |
|
|
|
|
# TODO(hjensas): Drop this conditional and option once distros |
|
|
|
|
# generally have dnsmasq supporting addr6 list and range. |
|
|
|
|
if self.conf.dnsmasq_enable_addr6_list and merge_addr6_list: |
|
|
|
|
fixed_ips = self._merge_alloc_addr6_list(fixed_ips, v6_nets) |
|
|
|
|
# Confirm whether Neutron server supports dns_name attribute in the |
|
|
|
|
# ports API |
|
|
|
|
dns_assignment = getattr(port, 'dns_assignment', None) |
|
|
|
|
if dns_assignment: |
|
|
|
|
dns_ip_map = {d.ip_address: d for d in dns_assignment} |
|
|
|
|
for alloc in fixed_ips: |
|
|
|
|
no_dhcp = False |
|
|
|
|
no_opts = False |
|
|
|
@ -606,18 +690,9 @@ class Dnsmasq(DhcpLocalProcess):
|
|
|
|
|
# to provide options for a client that won't use DHCP |
|
|
|
|
no_opts = addr_mode == constants.IPV6_SLAAC |
|
|
|
|
|
|
|
|
|
# If dns_name attribute is supported by ports API, return the |
|
|
|
|
# dns_assignment generated by the Neutron server. Otherwise, |
|
|
|
|
# generate hostname and fqdn locally (previous behaviour) |
|
|
|
|
if dns_assignment: |
|
|
|
|
hostname = dns_ip_map[alloc.ip_address].hostname |
|
|
|
|
fqdn = dns_ip_map[alloc.ip_address].fqdn |
|
|
|
|
else: |
|
|
|
|
hostname = 'host-%s' % alloc.ip_address.replace( |
|
|
|
|
'.', '-').replace(':', '-') |
|
|
|
|
fqdn = hostname |
|
|
|
|
if self.conf.dns_domain: |
|
|
|
|
fqdn = '%s.%s' % (fqdn, self.conf.dns_domain) |
|
|
|
|
hostname, fqdn = self._get_dns_assignment(alloc.ip_address, |
|
|
|
|
dns_assignment) |
|
|
|
|
|
|
|
|
|
yield (port, alloc, hostname, fqdn, no_dhcp, no_opts) |
|
|
|
|
|
|
|
|
|
def _get_port_extra_dhcp_opts(self, port): |
|
|
|
@ -702,7 +777,7 @@ class Dnsmasq(DhcpLocalProcess):
|
|
|
|
|
if s.enable_dhcp] |
|
|
|
|
# NOTE(ihrachyshka): the loop should not log anything inside it, to |
|
|
|
|
# avoid potential performance drop when lots of hosts are dumped |
|
|
|
|
for host_tuple in self._iter_hosts(): |
|
|
|
|
for host_tuple in self._iter_hosts(merge_addr6_list=True): |
|
|
|
|
port, alloc, hostname, name, no_dhcp, no_opts = host_tuple |
|
|
|
|
if no_dhcp: |
|
|
|
|
if not no_opts and self._get_port_extra_dhcp_opts(port): |
|
|
|
|