diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index 6c89de2f812..dd38acd3703 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -20,6 +20,7 @@ from neutron_lib import constants as lib_constants from neutron_lib.exceptions import l3 as l3_exc from neutron_lib.utils import helpers from oslo_log import log as logging +from oslo_utils import netutils from pyroute2.netlink import exceptions as pyroute2_exc from neutron._i18n import _ @@ -1088,6 +1089,18 @@ class RouterInfo(BaseRouterInfo): self.iptables_manager.ipv4['mangle'].add_rule( 'PREROUTING', mark_metadata_for_internal_interfaces) + if netutils.is_ipv6_enabled(): + mark_metadata_v6_for_internal_interfaces = ( + '-d fe80::a9fe:a9fe/128 ' + '-i %(interface_name)s ' + '-p tcp -m tcp --dport 80 ' + '-j MARK --set-xmark %(value)s/%(mask)s' % + {'interface_name': INTERNAL_DEV_PREFIX + '+', + 'value': self.agent_conf.metadata_access_mark, + 'mask': lib_constants.ROUTER_MARK_MASK}) + self.iptables_manager.ipv6['mangle'].add_rule( + 'PREROUTING', mark_metadata_v6_for_internal_interfaces) + def _get_port_devicename_scopemark(self, ports, name_generator): devicename_scopemark = {lib_constants.IP_VERSION_4: dict(), lib_constants.IP_VERSION_6: dict()} diff --git a/neutron/agent/linux/iptables_manager.py b/neutron/agent/linux/iptables_manager.py index db2bb12ca84..cfe80805261 100644 --- a/neutron/agent/linux/iptables_manager.py +++ b/neutron/agent/linux/iptables_manager.py @@ -372,9 +372,12 @@ class IptablesManager(object): def initialize_nat_table(self): self.ipv4.update( {'nat': IptablesTable(binary_name=self.wrap_name)}) + self.ipv6.update( + {'nat': IptablesTable(binary_name=self.wrap_name)}) builtin_chains = { - 4: {'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING']}} + 4: {'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING']}, + 6: {'nat': ['PREROUTING']}} self._configure_builtin_chains(builtin_chains) # Add a neutron-postrouting-bottom chain. It's intended to be diff --git a/neutron/agent/linux/ra.py b/neutron/agent/linux/ra.py index 01f367de979..339a4954c01 100644 --- a/neutron/agent/linux/ra.py +++ b/neutron/agent/linux/ra.py @@ -73,6 +73,9 @@ CONFIG_TEMPLATE = jinja2.Template("""interface {{ interface_name }} AdvAutonomous off; }; {% endfor %} + + route fe80::a9fe:a9fe/128 { + }; }; """) diff --git a/neutron/agent/metadata/agent.py b/neutron/agent/metadata/agent.py index 52a6f7ccce0..7ea1968066c 100644 --- a/neutron/agent/metadata/agent.py +++ b/neutron/agent/metadata/agent.py @@ -16,6 +16,8 @@ import hashlib import hmac import urllib +import netaddr + from neutron_lib.agent import topics from neutron_lib import constants from neutron_lib import context @@ -168,7 +170,7 @@ class MetadataProxyHandler(object): skip_cache=skip_cache) def _get_instance_and_tenant_id(self, req, skip_cache=False): - remote_address = req.headers.get('X-Forwarded-For') + forwarded_for = req.headers.get('X-Forwarded-For') network_id = req.headers.get('X-Neutron-Network-ID') router_id = req.headers.get('X-Neutron-Router-ID') @@ -179,12 +181,19 @@ class MetadataProxyHandler(object): "dropping") return None, None - ports = self._get_ports(remote_address, network_id, router_id, + remote_ip = netaddr.IPAddress(forwarded_for) + if remote_ip.version == constants.IP_VERSION_6: + if remote_ip.is_ipv4_mapped(): + # When haproxy listens on v4 AND v6 then it inserts ipv4 + # addresses as ipv4-mapped v6 addresses into X-Forwarded-For. + forwarded_for = str(remote_ip.ipv4()) + + ports = self._get_ports(forwarded_for, network_id, router_id, skip_cache=skip_cache) LOG.debug("Gotten ports for remote_address %(remote_address)s, " "network_id %(network_id)s, router_id %(router_id)s are: " "%(ports)s", - {"remote_address": remote_address, + {"remote_address": forwarded_for, "network_id": network_id, "router_id": router_id, "ports": ports}) diff --git a/neutron/agent/metadata/driver.py b/neutron/agent/metadata/driver.py index 8bc77cdcaf6..c453dc4a2bb 100644 --- a/neutron/agent/metadata/driver.py +++ b/neutron/agent/metadata/driver.py @@ -25,10 +25,12 @@ from neutron_lib import constants from neutron_lib import exceptions from oslo_config import cfg from oslo_log import log as logging +from oslo_utils import netutils from neutron._i18n import _ from neutron.agent.l3 import ha_router from neutron.agent.l3 import namespaces +from neutron.agent.linux import dhcp from neutron.agent.linux import external_process from neutron.agent.linux import ip_lib @@ -205,12 +207,14 @@ class MetadataDriver(object): '-j DROP' % port)] @classmethod - def metadata_nat_rules(cls, port): - return [('PREROUTING', '-d 169.254.169.254/32 ' + def metadata_nat_rules( + cls, port, metadata_address=(dhcp.METADATA_DEFAULT_IP + '/32')): + return [('PREROUTING', '-d %(metadata_address)s ' '-i %(interface_name)s ' '-p tcp -m tcp --dport 80 -j REDIRECT ' '--to-ports %(port)s' % - {'interface_name': namespaces.INTERNAL_DEV_PREFIX + '+', + {'metadata_address': metadata_address, + 'interface_name': namespaces.INTERNAL_DEV_PREFIX + '+', 'port': port})] @classmethod @@ -297,20 +301,34 @@ class MetadataDriver(object): def after_router_added(resource, event, l3_agent, **kwargs): router = kwargs['router'] proxy = l3_agent.metadata_driver + ipv6_enabled = netutils.is_ipv6_enabled() for c, r in proxy.metadata_filter_rules(proxy.metadata_port, proxy.metadata_access_mark): router.iptables_manager.ipv4['filter'].add_rule(c, r) + if ipv6_enabled: + for c, r in proxy.metadata_filter_rules(proxy.metadata_port, + proxy.metadata_access_mark): + router.iptables_manager.ipv6['filter'].add_rule(c, r) for c, r in proxy.metadata_nat_rules(proxy.metadata_port): router.iptables_manager.ipv4['nat'].add_rule(c, r) + if ipv6_enabled: + for c, r in proxy.metadata_nat_rules( + proxy.metadata_port, + metadata_address=(dhcp.METADATA_V6_IP + '/128')): + router.iptables_manager.ipv6['nat'].add_rule(c, r) router.iptables_manager.apply() if not isinstance(router, ha_router.HaRouter): + spawn_kwargs = {} + if ipv6_enabled: + spawn_kwargs['bind_address'] = '::' proxy.spawn_monitored_metadata_proxy( l3_agent.process_monitor, router.ns_name, proxy.metadata_port, l3_agent.conf, - router_id=router.router_id) + router_id=router.router_id, + **spawn_kwargs) def after_router_updated(resource, event, l3_agent, **kwargs): @@ -318,12 +336,16 @@ def after_router_updated(resource, event, l3_agent, **kwargs): proxy = l3_agent.metadata_driver if (not proxy.monitors.get(router.router_id) and not isinstance(router, ha_router.HaRouter)): + spawn_kwargs = {} + if netutils.is_ipv6_enabled(): + spawn_kwargs['bind_address'] = '::' proxy.spawn_monitored_metadata_proxy( l3_agent.process_monitor, router.ns_name, proxy.metadata_port, l3_agent.conf, - router_id=router.router_id) + router_id=router.router_id, + **spawn_kwargs) def before_router_removed(resource, event, l3_agent, payload=None): diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index 91caa6e0e75..1d6e88dddc8 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -2664,28 +2664,31 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): 'distributed': False} driver = metadata_driver.MetadataDriver with mock.patch.object( - driver, 'destroy_monitored_metadata_proxy') as destroy_proxy: - with mock.patch.object( - driver, 'spawn_monitored_metadata_proxy') as spawn_proxy: - agent._process_added_router(router) - if enableflag: - spawn_proxy.assert_called_with( - mock.ANY, - mock.ANY, - self.conf.metadata_port, - mock.ANY, - router_id=router_id - ) - else: - self.assertFalse(spawn_proxy.call_count) - agent._safe_router_removed(router_id) - if enableflag: - destroy_proxy.assert_called_with(mock.ANY, - router_id, - mock.ANY, - 'qrouter-' + router_id) - else: - self.assertFalse(destroy_proxy.call_count) + driver, 'destroy_monitored_metadata_proxy') as destroy_proxy, \ + mock.patch.object( + driver, 'spawn_monitored_metadata_proxy') as spawn_proxy, \ + mock.patch.object(netutils, 'is_ipv6_enabled') as ipv6_mock: + ipv6_mock.return_value = True + agent._process_added_router(router) + if enableflag: + spawn_proxy.assert_called_with( + mock.ANY, + mock.ANY, + self.conf.metadata_port, + mock.ANY, + router_id=router_id, + bind_address='::', + ) + else: + self.assertFalse(spawn_proxy.call_count) + agent._safe_router_removed(router_id) + if enableflag: + destroy_proxy.assert_called_with(mock.ANY, + router_id, + mock.ANY, + 'qrouter-' + router_id) + else: + self.assertFalse(destroy_proxy.call_count) def test_enable_metadata_proxy(self): self._configure_metadata_proxy() diff --git a/neutron/tests/unit/agent/linux/test_iptables_manager.py b/neutron/tests/unit/agent/linux/test_iptables_manager.py index 2cec969bdc9..0dfd964b32a 100644 --- a/neutron/tests/unit/agent/linux/test_iptables_manager.py +++ b/neutron/tests/unit/agent/linux/test_iptables_manager.py @@ -314,6 +314,16 @@ def _generate_mangle_dump_v6(iptables_args): '# Completed by iptables_manager\n' % iptables_args) +def _generate_nat_dump_v6(iptables_args): + return ('# Generated by iptables_manager\n' + '*nat\n' + ':PREROUTING - [0:0]\n' + ':%(bn)s-PREROUTING - [0:0]\n' + '-I PREROUTING 1 -j %(bn)s-PREROUTING\n' + 'COMMIT\n' + '# Completed by iptables_manager\n' % iptables_args) + + def _generate_raw_dump(iptables_args): return ('# Generated by iptables_manager\n' '*raw\n' @@ -366,6 +376,7 @@ def _generate_raw_restore_dump(iptables_args): MANGLE_DUMP = _generate_mangle_dump(IPTABLES_ARG) MANGLE_DUMP_V6 = _generate_mangle_dump_v6(IPTABLES_ARG) +NAT_DUMP_V6 = _generate_nat_dump_v6(IPTABLES_ARG) RAW_DUMP = _generate_raw_dump(IPTABLES_ARG) MANGLE_RESTORE_DUMP = _generate_mangle_restore_dump(IPTABLES_ARG) RAW_RESTORE_DUMP = _generate_raw_restore_dump(IPTABLES_ARG) @@ -468,7 +479,7 @@ class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase): if self.use_ipv6: self._extend_with_ip6tables_filter( expected_calls_and_values, - FILTER_DUMP + MANGLE_DUMP_V6 + RAW_DUMP) + FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -512,7 +523,7 @@ class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase): if self.use_ipv6: self._extend_with_ip6tables_filter( expected_calls_and_values, - FILTER_DUMP + MANGLE_DUMP_V6 + raw_dump) + FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + raw_dump) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -588,7 +599,7 @@ class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase): if self.use_ipv6: self._extend_with_ip6tables_filter( expected_calls_and_values, - FILTER_DUMP + MANGLE_DUMP_V6 + raw_dump) + FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + raw_dump) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -654,7 +665,7 @@ class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase): if self.use_ipv6: self._extend_with_ip6tables_filter( expected_calls_and_values, - FILTER_DUMP + MANGLE_DUMP_V6 + RAW_DUMP) + FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -725,7 +736,7 @@ class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase): if self.use_ipv6: self._extend_with_ip6tables_filter( expected_calls_and_values, - FILTER_DUMP + MANGLE_DUMP_V6 + raw_dump) + FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + raw_dump) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -787,7 +798,7 @@ class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase): if self.use_ipv6: self._extend_with_ip6tables_filter( expected_calls_and_values, - FILTER_DUMP + MANGLE_DUMP_V6 + RAW_DUMP) + FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -1164,7 +1175,7 @@ class IptablesManagerStateFulTestCase(IptablesManagerBaseTestCase): if self.use_ipv6: self._extend_with_ip6tables_filter_end( expected_calls_and_values, - FILTER_DUMP + MANGLE_DUMP_V6 + RAW_DUMP) + FILTER_DUMP + MANGLE_DUMP_V6 + NAT_DUMP_V6 + RAW_DUMP) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -1227,9 +1238,10 @@ class IptablesManagerStateFulTestCaseCustomBinaryName( ] if self.use_ipv6: mangle_dump_v6 = _generate_mangle_dump_v6(iptables_args) + nat_dump_v6 = _generate_nat_dump_v6(iptables_args) self._extend_with_ip6tables_filter( expected_calls_and_values, - filter_dump_ipv6 + mangle_dump_v6 + raw_dump) + filter_dump_ipv6 + mangle_dump_v6 + nat_dump_v6 + raw_dump) tools.setup_mock_calls(self.execute, expected_calls_and_values) @@ -1294,9 +1306,10 @@ class IptablesManagerStateFulTestCaseEmptyCustomBinaryName( ] if self.use_ipv6: mangle_dump_v6 = _generate_mangle_dump_v6(iptables_args) + nat_dump_v6 = _generate_nat_dump_v6(iptables_args) self._extend_with_ip6tables_filter( expected_calls_and_values, - filter_dump + mangle_dump_v6 + raw_dump) + filter_dump + mangle_dump_v6 + nat_dump_v6 + raw_dump) tools.setup_mock_calls(self.execute, expected_calls_and_values) diff --git a/neutron/tests/unit/agent/metadata/test_agent.py b/neutron/tests/unit/agent/metadata/test_agent.py index e28f4ab7634..2848da7051f 100644 --- a/neutron/tests/unit/agent/metadata/test_agent.py +++ b/neutron/tests/unit/agent/metadata/test_agent.py @@ -14,6 +14,7 @@ from unittest import mock +import ddt from neutron_lib import constants as n_const import testtools import webob @@ -102,6 +103,7 @@ class TestMetadataProxyHandlerRpc(TestMetadataProxyHandlerBase): self.assertEqual(expected, ports) +@ddt.ddt class _TestMetadataProxyHandlerCacheMixin(object): def test_call(self): @@ -242,8 +244,8 @@ class _TestMetadataProxyHandlerCacheMixin(object): self.assertRaises(TypeError, self.handler._get_ports, 'remote_address') def _get_instance_and_tenant_id_helper(self, headers, list_ports_retval, - networks=None, router_id=None): - remote_address = '192.168.1.1' + networks=None, router_id=None, + remote_address='192.168.1.1'): headers['X-Forwarded-For'] = remote_address req = mock.Mock(headers=headers) @@ -279,7 +281,8 @@ class _TestMetadataProxyHandlerCacheMixin(object): return (instance_id, tenant_id) - def test_get_instance_id_router_id(self): + @ddt.data('192.168.1.1', '::ffff:192.168.1.1') + def test_get_instance_id_router_id(self, remote_address): router_id = 'the_id' headers = { 'X-Neutron-Router-ID': router_id @@ -294,12 +297,13 @@ class _TestMetadataProxyHandlerCacheMixin(object): self.assertEqual( ('device_id', 'tenant_id'), - self._get_instance_and_tenant_id_helper(headers, ports, - networks=networks, - router_id=router_id) + self._get_instance_and_tenant_id_helper( + headers, ports, networks=networks, router_id=router_id, + remote_address=remote_address) ) - def test_get_instance_id_router_id_no_match(self): + @ddt.data('192.168.1.1', '::ffff:192.168.1.1') + def test_get_instance_id_router_id_no_match(self, remote_address): router_id = 'the_id' headers = { 'X-Neutron-Router-ID': router_id @@ -312,12 +316,13 @@ class _TestMetadataProxyHandlerCacheMixin(object): ] self.assertEqual( (None, None), - self._get_instance_and_tenant_id_helper(headers, ports, - networks=networks, - router_id=router_id) + self._get_instance_and_tenant_id_helper( + headers, ports, networks=networks, router_id=router_id, + remote_address=remote_address) ) - def test_get_instance_id_network_id(self): + @ddt.data('192.168.1.1', '::ffff:192.168.1.1') + def test_get_instance_id_network_id(self, remote_address): network_id = 'the_id' headers = { 'X-Neutron-Network-ID': network_id @@ -331,11 +336,13 @@ class _TestMetadataProxyHandlerCacheMixin(object): self.assertEqual( ('device_id', 'tenant_id'), - self._get_instance_and_tenant_id_helper(headers, ports, - networks=('the_id',)) + self._get_instance_and_tenant_id_helper( + headers, ports, networks=('the_id',), + remote_address=remote_address) ) - def test_get_instance_id_network_id_no_match(self): + @ddt.data('192.168.1.1', '::ffff:192.168.1.1') + def test_get_instance_id_network_id_no_match(self, remote_address): network_id = 'the_id' headers = { 'X-Neutron-Network-ID': network_id @@ -345,11 +352,14 @@ class _TestMetadataProxyHandlerCacheMixin(object): self.assertEqual( (None, None), - self._get_instance_and_tenant_id_helper(headers, ports, - networks=('the_id',)) + self._get_instance_and_tenant_id_helper( + headers, ports, networks=('the_id',), + remote_address=remote_address) ) - def test_get_instance_id_network_id_and_router_id_invalid(self): + @ddt.data('192.168.1.1', '::ffff:192.168.1.1') + def test_get_instance_id_network_id_and_router_id_invalid( + self, remote_address): network_id = 'the_nid' router_id = 'the_rid' headers = { @@ -366,9 +376,9 @@ class _TestMetadataProxyHandlerCacheMixin(object): self.assertEqual( (None, None), - self._get_instance_and_tenant_id_helper(headers, ports, - networks=(network_id,), - router_id=router_id) + self._get_instance_and_tenant_id_helper( + headers, ports, networks=(network_id,), router_id=router_id, + remote_address=remote_address) ) def _proxy_request_test_helper(self, response_code=200, method='GET'): diff --git a/neutron/tests/unit/agent/metadata/test_driver.py b/neutron/tests/unit/agent/metadata/test_driver.py index 2dd00e8c878..40f3c73ca4b 100644 --- a/neutron/tests/unit/agent/metadata/test_driver.py +++ b/neutron/tests/unit/agent/metadata/test_driver.py @@ -44,6 +44,14 @@ class TestMetadataDriverRules(base.BaseTestCase): [rules], metadata_driver.MetadataDriver.metadata_nat_rules(9697)) + def test_metadata_nat_rules_ipv6(self): + rules = ('PREROUTING', '-d fe80::a9fe:a9fe/128 -i qr-+ ' + '-p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697') + self.assertEqual( + [rules], + metadata_driver.MetadataDriver.metadata_nat_rules( + 9697, metadata_address='fe80::a9fe:a9fe/128')) + def test_metadata_filter_rules(self): rules = [('INPUT', '-m mark --mark 0x1/%s -j ACCEPT' % constants.ROUTER_MARK_MASK),