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 baca51c0ceb..011a03e98ec 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -396,8 +396,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 0c41e3c734d..de2a97d5976 100644 --- a/neutron/tests/functional/agent/l3/framework.py +++ b/neutron/tests/functional/agent/l3/framework.py @@ -33,6 +33,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.agent import common as agent_config from neutron.conf import common as common_config @@ -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 f45302487f6..d70a3055322 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()