Disable RA and IPv6 forwarding on backup HA routers

Neutron does not disable ipv6 forwarding for HA routers and it's
enabled by default in all router namespaces. For ipv6, this means
that it will automatically join the following groups:

* link-local all-routers multicast group (ff02::2)
* interface-local all routers multicast group (ff01::2)
* site-local all routers multicast group (ff05::2))

As a side effect it will answer to multicast listener queries, thus
causing external switch to learn its MAC address and disrupting traffic
to the master instance.

This patch will enable ipv6 forwarding on the gateway interface only
for master instances and disable it otherwise to fix the issue.

Also, the accept_ra procfs entry was enabled under certain
circumstances but it wasn't disabled otherwise. This patch, will
disable RA on the gateway interface for non master instances.

Conflicts:
	neutron/tests/functional/agent/l3/test_ha_router.py

Closes-Bug: #1667756

Change-Id: I9bc890b43f750cad68fc67f4c79f1426c3506863
(cherry picked from commit 676a3ebe2f)
(cherry picked from commit 9360fb90ba)
This commit is contained in:
Daniel Alvarez 2017-01-22 13:33:07 +00:00
parent f710a34b7b
commit 1495453768
7 changed files with 108 additions and 23 deletions

View File

@ -159,25 +159,35 @@ class AgentMixin(object):
if ri is None:
return
self._configure_ipv6_ra_on_ext_gw_port_if_necessary(ri, state)
# TODO(dalvarez): Fix bug 1677279 by moving the IPv6 parameters
# configuration to keepalived-state-change in order to remove the
# dependency that currently exists on l3-agent running for the IPv6
# failover.
self._configure_ipv6_params_on_ext_gw_port_if_necessary(ri, state)
if self.conf.enable_metadata_proxy:
self._update_metadata_proxy(ri, router_id, state)
self._update_radvd_daemon(ri, state)
self.state_change_notifier.queue_event((router_id, state))
def _configure_ipv6_ra_on_ext_gw_port_if_necessary(self, ri, state):
def _configure_ipv6_params_on_ext_gw_port_if_necessary(self, ri, state):
# If ipv6 is enabled on the platform, ipv6_gateway config flag is
# not set and external_network associated to the router does not
# include any IPv6 subnet, enable the gateway interface to accept
# Router Advts from upstream router for default route.
# Router Advts from upstream router for default route on master
# instances as well as ipv6 forwarding. Otherwise, disable them.
ex_gw_port_id = ri.ex_gw_port and ri.ex_gw_port['id']
if state == 'master' and ex_gw_port_id:
interface_name = ri.get_external_device_name(ex_gw_port_id)
if ri.router.get('distributed', False):
namespace = ri.ha_namespace
else:
namespace = ri.ns_name
ri._enable_ra_on_gw(ri.ex_gw_port, namespace, interface_name)
if not ex_gw_port_id:
return
interface_name = ri.get_external_device_name(ex_gw_port_id)
if ri.router.get('distributed', False):
namespace = ri.ha_namespace
else:
namespace = ri.ns_name
enable = state == 'master'
ri._configure_ipv6_params_on_gw(ri.ex_gw_port, namespace,
interface_name, enable)
def _update_metadata_proxy(self, ri, router_id, state):
if state == 'master':

View File

@ -388,8 +388,13 @@ class HaRouter(router.RouterInfo):
self._plug_external_gateway(ex_gw_port, interface_name, self.ns_name)
self._add_gateway_vip(ex_gw_port, interface_name)
self._disable_ipv6_addressing_on_interface(interface_name)
if self.ha_state == 'master':
self._enable_ra_on_gw(ex_gw_port, self.ns_name, interface_name)
# Enable RA and IPv6 forwarding only for master instances. This will
# prevent backup routers from sending packets to the upstream switch
# and disrupt connections.
enable = self.ha_state == 'master'
self._configure_ipv6_params_on_gw(ex_gw_port, self.ns_name,
interface_name, enable)
def external_gateway_updated(self, ex_gw_port, interface_name):
self._plug_external_gateway(

View File

@ -641,14 +641,22 @@ class RouterInfo(object):
device = ip_lib.IPDevice(device_name, namespace=namespace)
device.route.add_route(subnet['gateway_ip'], scope='link')
def _enable_ra_on_gw(self, ex_gw_port, ns_name, interface_name):
gateway_ips = self._get_external_gw_ips(ex_gw_port)
if not self.use_ipv6 or self.is_v6_gateway_set(gateway_ips):
def _configure_ipv6_params_on_gw(self, ex_gw_port, ns_name, interface_name,
enabled):
if not self.use_ipv6:
return
# There is no IPv6 gw_ip, use RouterAdvt for default route.
self.driver.configure_ipv6_ra(ns_name, interface_name,
n_const.ACCEPT_RA_WITH_FORWARDING)
if not enabled:
self.driver.configure_ipv6_ra(ns_name, interface_name,
n_const.ACCEPT_RA_DISABLED)
else:
gateway_ips = self._get_external_gw_ips(ex_gw_port)
if self.is_v6_gateway_set(gateway_ips):
return
# There is no IPv6 gw_ip, use RouterAdvt for default route.
self.driver.configure_ipv6_ra(ns_name, interface_name,
n_const.ACCEPT_RA_WITH_FORWARDING)
self.driver.configure_ipv6_forwarding(ns_name, interface_name, enabled)
def _external_gateway_added(self, ex_gw_port, interface_name,
ns_name, preserve_ips):
@ -684,7 +692,8 @@ class RouterInfo(object):
for ip in gateway_ips:
device.route.add_gateway(ip)
self._enable_ra_on_gw(ex_gw_port, ns_name, interface_name)
self._configure_ipv6_params_on_gw(ex_gw_port, ns_name, interface_name,
True)
for fixed_ip in ex_gw_port['fixed_ips']:
ip_lib.send_ip_addr_adv_notif(ns_name,

View File

@ -248,6 +248,14 @@ class LinuxInterfaceDriver(object):
'dev': dev_name,
'value': value}])
@staticmethod
def configure_ipv6_forwarding(namespace, dev_name, enabled):
"""Configure IPv6 forwarding on an interface."""
ip_lib.IPWrapper(namespace=namespace).netns.execute(
['sysctl', '-w', 'net.ipv6.conf.%(dev)s.forwarding=%(value)d' % {
'dev': dev_name,
'value': int(enabled)}])
@abc.abstractmethod
def plug_new(self, network_id, port_id, device_name, mac_address,
bridge=None, namespace=None, prefix=None, mtu=None):

View File

@ -34,6 +34,7 @@ from neutron.agent import l3_agent as l3_agent_main
from neutron.agent.linux import external_process
from neutron.agent.linux import ip_lib
from neutron.agent.linux import keepalived
from neutron.common import constants as n_const
from neutron.common import utils as common_utils
from neutron.conf import common as common_config
from neutron.tests.common import l3_test_common
@ -217,14 +218,23 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
def _assert_external_device(self, router):
self.assertTrue(self._check_external_device(router))
def _assert_ipv6_accept_ra(self, router):
def _assert_ipv6_accept_ra(self, router, enabled=True):
external_port = router.get_ex_gw_port()
external_device_name = router.get_external_device_name(
external_port['id'])
ip_wrapper = ip_lib.IPWrapper(namespace=router.ns_name)
ra_state = ip_wrapper.netns.execute(['sysctl', '-b',
'net.ipv6.conf.%s.accept_ra' % external_device_name])
self.assertEqual('2', ra_state)
self.assertEqual(enabled, int(ra_state) != n_const.ACCEPT_RA_DISABLED)
def _assert_ipv6_forwarding(self, router, enabled=True):
external_port = router.get_ex_gw_port()
external_device_name = router.get_external_device_name(
external_port['id'])
ip_wrapper = ip_lib.IPWrapper(namespace=router.ns_name)
fwd_state = ip_wrapper.netns.execute(['sysctl', '-b',
'net.ipv6.conf.%s.forwarding' % external_device_name])
self.assertEqual(int(enabled), int(fwd_state))
def _router_lifecycle(self, enable_ha, ip_version=4,
dual_stack=False, v6_ext_gw_with_sub=True,

View File

@ -100,9 +100,10 @@ class L3HATestCase(framework.L3AgentTestFramework):
v6_ext_gw_with_sub=False)
@testtools.skipUnless(ipv6_utils.is_enabled(), "IPv6 is not enabled")
def test_ipv6_router_advts_after_router_state_change(self):
def test_ipv6_router_advts_and_fwd_after_router_state_change_master(self):
# Schedule router to l3 agent, and then add router gateway. Verify
# that router gw interface is configured to receive Router Advts.
# that router gw interface is configured to receive Router Advts and
# IPv6 forwarding is enabled.
router_info = l3_test_common.prepare_router_data(
enable_snat=True, enable_ha=True, dual_stack=True, enable_gw=False)
router = self.manage_router(self.agent, router_info)
@ -112,6 +113,24 @@ class L3HATestCase(framework.L3AgentTestFramework):
router_info['gw_port'] = ex_port
router.process(self.agent)
self._assert_ipv6_accept_ra(router)
self._assert_ipv6_forwarding(router)
@testtools.skipUnless(ipv6_utils.is_enabled(), "IPv6 is not enabled")
def test_ipv6_router_advts_and_fwd_after_router_state_change_backup(self):
# Schedule router to l3 agent, and then add router gateway. Verify
# that router gw interface is configured to discard Router Advts and
# IPv6 forwarding is disabled.
router_info = l3_test_common.prepare_router_data(
enable_snat=True, enable_ha=True, dual_stack=True, enable_gw=False)
router = self.manage_router(self.agent, router_info)
self.fail_ha_router(router)
common_utils.wait_until_true(lambda: router.ha_state == 'backup')
_ext_dev_name, ex_port = l3_test_common.prepare_ext_gw_test(
mock.Mock(), router)
router_info['gw_port'] = ex_port
router.process(self.agent)
self._assert_ipv6_accept_ra(router, False)
self._assert_ipv6_forwarding(router, False)
def test_keepalived_configuration(self):
router_info = self.generate_router_info(enable_ha=True)

View File

@ -211,6 +211,30 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
agent.enqueue_state_change(router.id, 'master')
self.assertFalse(agent._update_metadata_proxy.call_count)
def _test__configure_ipv6_params_on_ext_gw_port_if_necessary_helper(
self, state, enable_expected):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router_info = l3router.RouterInfo(_uuid(), {}, **self.ri_kwargs)
router_info.ex_gw_port = {'id': _uuid()}
with mock.patch.object(router_info, '_configure_ipv6_params_on_gw'
) as mock_configure_ipv6:
agent._configure_ipv6_params_on_ext_gw_port_if_necessary(
router_info, state)
interface_name = router_info.get_external_device_name(
router_info.ex_gw_port['id'])
mock_configure_ipv6.assert_called_once_with(
router_info.ex_gw_port, router_info.ns_name, interface_name,
enable_expected)
def test__configure_ipv6_params_on_ext_gw_port_if_necessary_master(self):
self._test__configure_ipv6_params_on_ext_gw_port_if_necessary_helper(
'master', True)
def test__configure_ipv6_params_on_ext_gw_port_if_necessary_backup(self):
self._test__configure_ipv6_params_on_ext_gw_port_if_necessary_helper(
'backup', False)
def test_check_ha_state_for_router_master_standby(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = mock.Mock()