Correctly configure IPv6 addresses on upgrades
When starting the dhcp-agent after an upgrade, there could be stale IPv6 addresses in the namespace that had been configured via SLAAC. These need to be removed, and the same address added back statically, in order for the agent to start up correctly. To avoid the race condition where an IPv6 RA could arrive while we are making this change, we must move the call to disable RAs in the namespace from plug(), since devices may already exist that are receiving packets. Uncovered by the grenade tests. Change-Id: I7e1e5d6c1fa938918aac3fb63888d20ff4088ba7 Closes-bug: #1627902
This commit is contained in:
parent
8ee5b3d45c
commit
21bb776670
neutron
releasenotes/notes
@ -1346,15 +1346,6 @@ class DeviceManager(object):
|
||||
|
||||
def plug(self, network, port, interface_name):
|
||||
"""Plug device settings for the network's DHCP on this host."""
|
||||
# Disable acceptance of RAs in the namespace so we don't
|
||||
# auto-configure an IPv6 address since we explicitly configure
|
||||
# them on the device. This must be done before any interfaces
|
||||
# are plugged since it could receive an RA by the time
|
||||
# plug() returns, so we have to create the namespace first.
|
||||
if network.namespace:
|
||||
ip_lib.IPWrapper().ensure_namespace(network.namespace)
|
||||
self.driver.configure_ipv6_ra(network.namespace, 'default',
|
||||
n_const.ACCEPT_RA_DISABLED)
|
||||
self.driver.plug(network.id,
|
||||
port.id,
|
||||
interface_name,
|
||||
@ -1374,6 +1365,19 @@ class DeviceManager(object):
|
||||
self._update_dhcp_port(network, port)
|
||||
interface_name = self.get_interface_name(network, port)
|
||||
|
||||
# Disable acceptance of RAs in the namespace so we don't
|
||||
# auto-configure an IPv6 address since we explicitly configure
|
||||
# them on the device. This must be done before any interfaces
|
||||
# are plugged since it could receive an RA by the time
|
||||
# plug() returns, so we have to create the namespace first.
|
||||
# It must also be done in the case there is an existing IPv6
|
||||
# address here created via SLAAC, since it will be deleted
|
||||
# and added back statically in the call to init_l3() below.
|
||||
if network.namespace:
|
||||
ip_lib.IPWrapper().ensure_namespace(network.namespace)
|
||||
self.driver.configure_ipv6_ra(network.namespace, 'default',
|
||||
n_const.ACCEPT_RA_DISABLED)
|
||||
|
||||
if ip_lib.ensure_device_is_ready(interface_name,
|
||||
namespace=network.namespace):
|
||||
LOG.debug('Reusing existing device: %s.', interface_name)
|
||||
|
@ -111,30 +111,50 @@ class LinuxInterfaceDriver(object):
|
||||
# Neutron, so it would be deleted if we added it to the 'previous'
|
||||
# list here
|
||||
default_ipv6_lla = ip_lib.get_ipv6_lladdr(device.link.address)
|
||||
previous = {addr['cidr'] for addr in device.addr.list(
|
||||
filters=['permanent'])} - {default_ipv6_lla}
|
||||
|
||||
# add new addresses
|
||||
cidrs = set()
|
||||
remove_ips = set()
|
||||
|
||||
# normalize all the IP addresses first
|
||||
for ip_cidr in ip_cidrs:
|
||||
|
||||
net = netaddr.IPNetwork(ip_cidr)
|
||||
# Convert to compact IPv6 address because the return values of
|
||||
# "ip addr list" are compact.
|
||||
if net.version == 6:
|
||||
ip_cidr = str(net)
|
||||
if ip_cidr in previous:
|
||||
previous.remove(ip_cidr)
|
||||
cidrs.add(ip_cidr)
|
||||
|
||||
# Determine the addresses that must be added and removed
|
||||
for address in device.addr.list():
|
||||
cidr = address['cidr']
|
||||
dynamic = address['dynamic']
|
||||
|
||||
# skip the IPv6 link-local
|
||||
if cidr == default_ipv6_lla:
|
||||
continue
|
||||
|
||||
device.addr.add(ip_cidr)
|
||||
if cidr in preserve_ips:
|
||||
continue
|
||||
|
||||
# clean up any old addresses
|
||||
for ip_cidr in previous:
|
||||
if ip_cidr not in preserve_ips:
|
||||
if clean_connections:
|
||||
device.delete_addr_and_conntrack_state(ip_cidr)
|
||||
else:
|
||||
device.addr.delete(ip_cidr)
|
||||
# Statically created addresses are OK, dynamically created
|
||||
# addresses must be removed and replaced
|
||||
if cidr in cidrs and not dynamic:
|
||||
cidrs.remove(cidr)
|
||||
continue
|
||||
|
||||
remove_ips.add(cidr)
|
||||
|
||||
# Clean up any old addresses. This must be done first since there
|
||||
# could be a dynamic address being replaced with a static one.
|
||||
for ip_cidr in remove_ips:
|
||||
if clean_connections:
|
||||
device.delete_addr_and_conntrack_state(ip_cidr)
|
||||
else:
|
||||
device.addr.delete(ip_cidr)
|
||||
|
||||
# add any new addresses
|
||||
for ip_cidr in cidrs:
|
||||
device.addr.add(ip_cidr)
|
||||
|
||||
def init_router_port(self,
|
||||
device_name,
|
||||
|
@ -1450,15 +1450,13 @@ class TestDeviceManager(base.BaseTestCase):
|
||||
expected_ips = ['172.9.9.9/24', '169.254.169.254/16']
|
||||
expected = [
|
||||
mock.call.get_device_name(port),
|
||||
mock.call.configure_ipv6_ra(net.namespace, 'default', 0),
|
||||
mock.call.init_l3(
|
||||
'tap12345678-12',
|
||||
expected_ips,
|
||||
namespace=net.namespace)]
|
||||
|
||||
if not device_is_ready:
|
||||
expected.insert(1,
|
||||
mock.call.configure_ipv6_ra(net.namespace,
|
||||
'default', 0))
|
||||
expected.insert(2,
|
||||
mock.call.plug(net.id,
|
||||
port.id,
|
||||
|
@ -121,9 +121,9 @@ class TestABCDriver(TestBase):
|
||||
extra_subnets=[{'cidr': '172.20.0.0/24'}])
|
||||
self.ip_dev.assert_has_calls(
|
||||
[mock.call('tap0', namespace=ns),
|
||||
mock.call().addr.list(filters=['permanent']),
|
||||
mock.call().addr.add('192.168.1.2/24'),
|
||||
mock.call().addr.list(),
|
||||
mock.call().addr.delete('172.16.77.240/24'),
|
||||
mock.call().addr.add('192.168.1.2/24'),
|
||||
mock.call('tap0', namespace=ns),
|
||||
mock.call().route.list_onlink_routes(constants.IP_VERSION_4),
|
||||
mock.call().route.list_onlink_routes(constants.IP_VERSION_6),
|
||||
@ -155,7 +155,7 @@ class TestABCDriver(TestBase):
|
||||
preserve_ips=['192.168.1.3/32'])
|
||||
self.ip_dev.assert_has_calls(
|
||||
[mock.call('tap0', namespace=ns),
|
||||
mock.call().addr.list(filters=['permanent']),
|
||||
mock.call().addr.list(),
|
||||
mock.call().addr.add('192.168.1.2/24')])
|
||||
self.assertFalse(self.ip_dev().addr.delete.called)
|
||||
self.assertFalse(self.ip_dev().delete_addr_and_conntrack_state.called)
|
||||
@ -198,9 +198,9 @@ class TestABCDriver(TestBase):
|
||||
bc.init_router_port('tap0', [new_cidr], **kwargs)
|
||||
expected_calls = (
|
||||
[mock.call('tap0', namespace=ns),
|
||||
mock.call().addr.list(filters=['permanent']),
|
||||
mock.call().addr.add('2001:db8:a::124/64'),
|
||||
mock.call().addr.delete('2001:db8:a::123/64')])
|
||||
mock.call().addr.list(),
|
||||
mock.call().addr.delete('2001:db8:a::123/64'),
|
||||
mock.call().addr.add('2001:db8:a::124/64')])
|
||||
expected_calls += (
|
||||
[mock.call('tap0', namespace=ns),
|
||||
mock.call().route.list_onlink_routes(constants.IP_VERSION_4),
|
||||
@ -222,7 +222,7 @@ class TestABCDriver(TestBase):
|
||||
extra_subnets=[{'cidr': '172.20.0.0/24'}])
|
||||
self.ip_dev.assert_has_calls(
|
||||
[mock.call('tap0', namespace=ns),
|
||||
mock.call().addr.list(filters=['permanent']),
|
||||
mock.call().addr.list(),
|
||||
mock.call().addr.add('192.168.1.2/24'),
|
||||
mock.call().addr.add('2001:db8:a::124/64'),
|
||||
mock.call().addr.delete('172.16.77.240/24'),
|
||||
@ -269,6 +269,22 @@ class TestABCDriver(TestBase):
|
||||
namespace=ns)
|
||||
self.assertFalse(self.ip_dev().addr.add.called)
|
||||
|
||||
def test_l3_init_with_duplicated_ipv6_dynamic(self):
|
||||
device_name = 'tap0'
|
||||
cidr = '2001:db8:a::123/64'
|
||||
ns = '12345678-1234-5678-90ab-ba0987654321'
|
||||
addresses = [dict(scope='global',
|
||||
dynamic=True,
|
||||
cidr=cidr)]
|
||||
self.ip_dev().addr.list = mock.Mock(return_value=addresses)
|
||||
bc = BaseChild(self.conf)
|
||||
bc.init_l3(device_name, [cidr], namespace=ns)
|
||||
self.ip_dev.assert_has_calls(
|
||||
[mock.call(device_name, namespace=ns),
|
||||
mock.call().addr.list(),
|
||||
mock.call().addr.delete(cidr),
|
||||
mock.call().addr.add(cidr)])
|
||||
|
||||
def test_add_ipv6_addr(self):
|
||||
device_name = 'tap0'
|
||||
cidr = '2001:db8::/64'
|
||||
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
prelude: >
|
||||
IPv6 addresses in DHCP namespaces will now be
|
||||
(correctly) statically configured by the DHCP agent.
|
||||
fixes:
|
||||
- There is a race condition when adding ports in
|
||||
DHCP namespaces where an IPv6 address could be
|
||||
dynamically created via SLAAC from a Router
|
||||
Advertisement sent from the L3 agent, leading to
|
||||
a failure to start the DHCP agent. This bug has
|
||||
been fixed, but care must be taken on an upgrade
|
||||
dealing with any possibly stale dynamic addresses.
|
||||
For more information, see bug
|
||||
`1627902 <https://launchpad.net/bugs/1627902>`_.
|
||||
upgrade:
|
||||
- On upgrade, IPv6 addresses in the DHCP namespaces
|
||||
that have been created dynmically via SLAAC will be
|
||||
removed, and a static IPv6 address will be added
|
||||
instead.
|
Loading…
x
Reference in New Issue
Block a user