From 676a3ebe2f5b62f0ce7a3f7f434526931d5504a5 Mon Sep 17 00:00:00 2001 From: Daniel Alvarez Date: Sun, 22 Jan 2017 13:33:07 +0000 Subject: [PATCH] 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 --- neutron/agent/l3/ha.py | 30 ++++++++++++------- neutron/agent/l3/ha_router.py | 9 ++++-- neutron/agent/l3/router_info.py | 23 +++++++++----- neutron/agent/linux/interface.py | 7 +++++ .../tests/functional/agent/l3/framework.py | 14 +++++++-- .../functional/agent/l3/test_ha_router.py | 24 +++++++++++++-- neutron/tests/unit/agent/l3/test_agent.py | 24 +++++++++++++++ 7 files changed, 108 insertions(+), 23 deletions(-) diff --git a/neutron/agent/l3/ha.py b/neutron/agent/l3/ha.py index 038e167053b..8c56726a866 100644 --- a/neutron/agent/l3/ha.py +++ b/neutron/agent/l3/ha.py @@ -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: - 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': diff --git a/neutron/agent/l3/ha_router.py b/neutron/agent/l3/ha_router.py index b9fba67e10a..136a57eff44 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -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( diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index f91b94c5f43..e401df4193a 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -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 - # 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): @@ -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, diff --git a/neutron/agent/linux/interface.py b/neutron/agent/linux/interface.py index 8be1cf7826a..bf4d51eb73f 100644 --- a/neutron/agent/linux/interface.py +++ b/neutron/agent/linux/interface.py @@ -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): diff --git a/neutron/tests/functional/agent/l3/framework.py b/neutron/tests/functional/agent/l3/framework.py index 27e8b8af2ec..e9cd8b8ecd5 100644 --- a/neutron/tests/functional/agent/l3/framework.py +++ b/neutron/tests/functional/agent/l3/framework.py @@ -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, diff --git a/neutron/tests/functional/agent/l3/test_ha_router.py b/neutron/tests/functional/agent/l3/test_ha_router.py index 886d9af5265..8d8e7c8dec1 100644 --- a/neutron/tests/functional/agent/l3/test_ha_router.py +++ b/neutron/tests/functional/agent/l3/test_ha_router.py @@ -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) diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index eecb42a5180..2b6b63358c6 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -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()