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. Closes-Bug: #1667756 Change-Id: I9bc890b43f750cad68fc67f4c79f1426c3506863
This commit is contained in:
parent
1414ea9538
commit
676a3ebe2f
|
@ -121,26 +121,36 @@ 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.pd.process_ha_state(router_id, state == 'master')
|
||||
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:
|
||||
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
|
||||
ri._enable_ra_on_gw(ri.ex_gw_port, namespace, interface_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':
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -647,14 +647,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
|
||||
|
||||
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):
|
||||
|
@ -690,7 +698,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,
|
||||
|
|
|
@ -246,6 +246,13 @@ class LinuxInterfaceDriver(object):
|
|||
'value': value}]
|
||||
ip_lib.sysctl(cmd, namespace=namespace)
|
||||
|
||||
@staticmethod
|
||||
def configure_ipv6_forwarding(namespace, dev_name, enabled):
|
||||
"""Configure IPv6 forwarding on an interface."""
|
||||
cmd = ['net.ipv6.conf.%(dev)s.forwarding=%(enabled)s' %
|
||||
{'dev': dev_name, 'enabled': int(enabled)}]
|
||||
ip_lib.sysctl(cmd, namespace=namespace)
|
||||
|
||||
@abc.abstractmethod
|
||||
def plug_new(self, network_id, port_id, device_name, mac_address,
|
||||
bridge=None, namespace=None, prefix=None, mtu=None):
|
||||
|
|
|
@ -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
|
||||
|
@ -215,14 +216,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,
|
||||
|
|
|
@ -101,9 +101,10 @@ class L3HATestCase(framework.L3AgentTestFramework):
|
|||
|
||||
@testtools.skipUnless(ipv6_utils.is_enabled_and_bind_by_default(),
|
||||
"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)
|
||||
|
@ -113,6 +114,25 @@ class L3HATestCase(framework.L3AgentTestFramework):
|
|||
router_info['gw_port'] = ex_port
|
||||
router.process()
|
||||
self._assert_ipv6_accept_ra(router)
|
||||
self._assert_ipv6_forwarding(router)
|
||||
|
||||
@testtools.skipUnless(ipv6_utils.is_enabled_and_bind_by_default(),
|
||||
"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._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)
|
||||
|
|
|
@ -210,6 +210,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(agent, _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()
|
||||
|
|
Loading…
Reference in New Issue