From b4707c16918b7f2e61c522de3884525fdc3f5c91 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 18 Jun 2020 17:03:47 +0000 Subject: [PATCH] Fixes dnsmasq host file parsing with "addr6_list" This patch fixes: - The IPv6 tag added in the "host" file if is supported in dnsmasq. That shifts all other parameters in the register. - IPv6 registers can have more than one IP address; in this case, the method "_read_hosts_file_leases" should return a tuple per IP address. Change-Id: I4d0bc1eb9448366d8f1b2dacc9c5c2e4e6958253 Closes-Bug: #1884105 (cherry picked from commit 8eb4955bb62ec02b7a5cb725067c6193c37f9b0e) (cherry picked from commit 68706b556bf5b0f65b418359c3248fe0c778c0c7) --- neutron/agent/linux/dhcp.py | 15 ++++- neutron/tests/unit/agent/linux/test_dhcp.py | 68 +++++++++++---------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 8409eeff582..a7d2d5f636b 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -28,6 +28,7 @@ from neutron_lib.utils import file as file_utils from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import fileutils +from oslo_utils import netutils from oslo_utils import uuidutils import six @@ -835,6 +836,11 @@ class Dnsmasq(DhcpLocalProcess): str(DHCP_OPT_CLIENT_ID_NUM)): return opt.opt_value + @staticmethod + def _parse_ip_addresses(ip_list): + ip_list = [ip.strip('[]') for ip in ip_list] + return [ip for ip in ip_list if netutils.is_valid_ip(ip)] + def _read_hosts_file_leases(self, filename): leases = set() try: @@ -846,11 +852,14 @@ class Dnsmasq(DhcpLocalProcess): if host[1].startswith('set:'): continue if host[1].startswith(self._ID): - ip = host[3].strip('[]') + ips = self._parse_ip_addresses(host[3:]) client_id = host[1][len(self._ID):] + elif host[1].startswith('tag:'): + ips = self._parse_ip_addresses(host[3:]) else: - ip = host[2].strip('[]') - leases.add((ip, mac, client_id)) + ips = self._parse_ip_addresses(host[2:]) + for ip in ips: + leases.add((ip, mac, client_id)) except (OSError, IOError): LOG.debug('Error while reading hosts file %s', filename) return leases diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index d1bae8495d9..eb4485e7183 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -2572,51 +2572,57 @@ class TestDnsmasq(TestBase): def test_release_unused_leases_one_lease_mult_times_removed(self): self._test_release_unused_leases_one_lease_mult_times(True) - def test_read_hosts_file_leases(self): + def test__parse_ip_addresses(self): + ip_list = ['192.168.0.1', '[fdca:3ba5:a17a::1]', 'no_ip_address'] + self.assertEqual(['192.168.0.1', 'fdca:3ba5:a17a::1'], + dhcp.Dnsmasq._parse_ip_addresses(ip_list)) + + def _test_read_hosts_file_leases(self, lines, expected_result): filename = '/path/to/file' - lines = ["00:00:80:aa:bb:cc,inst-name,192.168.0.1", - "00:00:80:aa:bb:cc,inst-name,[fdca:3ba5:a17a::1]"] mock_open = self.useFixture( lib_fixtures.OpenFixture(filename, '\n'.join(lines))).mock_open dnsmasq = self._get_dnsmasq(FakeDualNetwork()) leases = dnsmasq._read_hosts_file_leases(filename) - - self.assertEqual(set([("192.168.0.1", "00:00:80:aa:bb:cc", None), - ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", - None)]), leases) + self.assertEqual(expected_result, leases) mock_open.assert_called_once_with(filename) + def test_read_hosts_file_leases(self): + lines = ["00:00:80:aa:bb:cc,inst-name,192.168.0.1", + "00:00:80:aa:bb:cc,inst-name,[fdca:3ba5:a17a::1]"] + result = {("192.168.0.1", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", None)} + self._test_read_hosts_file_leases(lines, result) + def test_read_hosts_file_leases_with_client_id(self): - filename = '/path/to/file' lines = ["00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1", "00:00:80:aa:bb:cc,id:client2,inst-name," "[fdca:3ba5:a17a::1]"] - mock_open = self.useFixture( - lib_fixtures.OpenFixture(filename, '\n'.join(lines))).mock_open - dnsmasq = self._get_dnsmasq(FakeDualNetwork()) - leases = dnsmasq._read_hosts_file_leases(filename) - - self.assertEqual(set([("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), - ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", - 'client2')]), leases) - mock_open.assert_called_once_with(filename) + result = {("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", 'client2')} + self._test_read_hosts_file_leases(lines, result) def test_read_hosts_file_leases_with_stateless_IPv6_tag(self): - filename = self.get_temp_file_path('leases') - with open(filename, "w") as leasesfile: - lines = [ - "00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1\n", - "00:00:80:aa:bb:cc,set:ccccccccc-cccc-cccc-cccc-cccccccc\n", - "00:00:80:aa:bb:cc,id:client2,inst-name,[fdca:3ba5:a17a::1]\n"] - for line in lines: - leasesfile.write(line) + lines = [ + "00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1", + "00:00:80:aa:bb:cc,set:ccccccccc-cccc-cccc-cccc-cccccccc", + "00:00:80:aa:bb:cc,id:client2,inst-name,[fdca:3ba5:a17a::1]"] + result = {("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", 'client2')} + self._test_read_hosts_file_leases(lines, result) - dnsmasq = self._get_dnsmasq(FakeDualNetwork()) - leases = dnsmasq._read_hosts_file_leases(filename) - - self.assertEqual(set([("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), - ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", - 'client2')]), leases) + def test_read_hosts_file_leases_with_IPv6_tag_and_multiple_ips(self): + lines = [ + "00:00:80:aa:bb:cc,id:client1,inst-name,192.168.0.1", + "00:00:80:aa:bb:cc,set:ccccccccc-cccc-cccc-cccc-cccccccc", + "00:00:80:aa:bb:cc,tag:dhcpv6,inst-name,[fdca:3ba5:a17a::1]," + "[fdca:3ba5:a17a::2],[fdca:3ba5:a17a::3],[fdca:3ba5:a17a::4]," + "set:port-fe2baee9-aba9-4b67-be03-be4aeee40cca"] + result = {("192.168.0.1", "00:00:80:aa:bb:cc", 'client1'), + ("fdca:3ba5:a17a::1", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::2", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::3", "00:00:80:aa:bb:cc", None), + ("fdca:3ba5:a17a::4", "00:00:80:aa:bb:cc", None)} + self._test_read_hosts_file_leases(lines, result) def _test_read_leases_file_leases(self, ip_version, add_bad_line=False): filename = '/path/to/file'