From db7ac1f4d0dcd191b16976be61f70efa80a5b885 Mon Sep 17 00:00:00 2001 From: Elena Ezhova Date: Tue, 28 Jul 2015 16:17:53 +0300 Subject: [PATCH] [OpenSwan] Enable usage of the MTU value of an IPSec connection It is possible to specify the MTU parameter when creating IPSec Site Connection but this parameter is ignored, because it is missing in ipsec.conf.template. This change adds the mtu option to OpenSwan ipsec.conf template. Refactored existing test_scenario and added a functional test for OpenSwan driver. DocImpact Change-Id: If822454a7acaa3fd003cae3e5e342c8b66ef656c Closes-Bug: #1478949 --- .../template/openswan/ipsec.conf.template | 8 +- .../tests/functional/common/test_scenario.py | 83 +++++++++---------- .../openswan/test_openswan_driver.py | 51 +++++++++++- .../services/vpn/device_drivers/test_ipsec.py | 15 +++- 4 files changed, 107 insertions(+), 50 deletions(-) diff --git a/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template b/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template index 4ea345f64..1f812654e 100644 --- a/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template +++ b/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template @@ -28,6 +28,11 @@ conn {{ipsec_site_connection.id}} # [subnet] leftsubnet={{vpnservice.subnet.cidr}} # leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) + # [updown] + # What "updown" script to run to adjust routing and/or firewalling when + # the status of the connection changes (default "ipsec _updown"). + # "--route yes" allows to specify such routing options as mtu and metric. + leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ###################### @@ -39,8 +44,7 @@ conn {{ipsec_site_connection.id}} rightsubnets={ {{ipsec_site_connection['peer_cidrs']|join(' ')}} } # rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) # [mtu] - # Note It looks like not supported in the strongswan driver - # ignore it now + mtu={{ipsec_site_connection.mtu}} # [dpd_action] dpdaction={{ipsec_site_connection.dpd_action}} # [dpd_interval] diff --git a/neutron_vpnaas/tests/functional/common/test_scenario.py b/neutron_vpnaas/tests/functional/common/test_scenario.py index a28f4eff6..7d78d3422 100644 --- a/neutron_vpnaas/tests/functional/common/test_scenario.py +++ b/neutron_vpnaas/tests/functional/common/test_scenario.py @@ -155,14 +155,13 @@ def get_ovs_bridge(br_name): return ovs_lib.OVSBridge(br_name) -class TestIPSecScenario(base.BaseSudoTestCase): - +class TestIPSecBase(base.BaseSudoTestCase): vpn_agent_ini = os.environ.get('VPN_AGENT_INI', '/etc/neutron/vpn_agent.ini') NESTED_NAMESPACE_SEPARATOR = '@' def setUp(self): - super(TestIPSecScenario, self).setUp() + super(TestIPSecBase, self).setUp() mock.patch('neutron.agent.l3.agent.L3PluginApi').start() # avoid report_status running periodically mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall').start() @@ -172,7 +171,12 @@ class TestIPSecScenario(base.BaseSudoTestCase): # root_helper_daemon and instead use root_helper # https://bugs.launchpad.net/neutron/+bug/1482622 cfg.CONF.set_override('root_helper_daemon', None, group='AGENT') + + self.fake_vpn_service = copy.deepcopy(FAKE_VPN_SERVICE) + self.fake_ipsec_connection = copy.deepcopy(FAKE_IPSEC_CONNECTION) + self.vpn_agent = self._configure_agent('agent1') + self.driver = self.vpn_agent.device_drivers[0] def connect_agents(self, agent1, agent2): """Simulate both agents in the same host. @@ -301,7 +305,7 @@ class TestIPSecScenario(base.BaseSudoTestCase): return agent.router_info[router['id']] def prepare_vpn_service_info(self, router_id, external_ip, subnet_cidr): - service = copy.deepcopy(FAKE_VPN_SERVICE) + service = copy.deepcopy(self.fake_vpn_service) service.update({ 'id': _uuid(), 'router_id': router_id, @@ -310,7 +314,7 @@ class TestIPSecScenario(base.BaseSudoTestCase): return service def prepare_ipsec_conn_info(self, vpn_service, peer_vpn_service): - ipsec_conn = copy.deepcopy(FAKE_IPSEC_CONNECTION) + ipsec_conn = copy.deepcopy(self.fake_ipsec_connection) ipsec_conn.update({ 'id': _uuid(), 'vpnservice_id': vpn_service['id'], @@ -402,8 +406,7 @@ class TestIPSecScenario(base.BaseSudoTestCase): break return pm.active - def test_ipsec_site_connections(self): - device = self.vpn_agent.device_drivers[0] + def _create_ipsec_site_connection(self, l3ha=False): # Mock the method below because it causes Exception: # RuntimeError: Second simultaneous read on fileno 5 detected. # Unless you really know what you're doing, make sure that only @@ -414,32 +417,49 @@ class TestIPSecScenario(base.BaseSudoTestCase): ip_lib.send_ip_addr_adv_notif = mock.Mock() # There are no vpn services yet. get_vpn_services_on_host returns # empty list - device.agent_rpc.get_vpn_services_on_host = mock.Mock( + self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[]) # instantiate network resources "router", "private network" private_nets = list(PRIVATE_NET.subnet(24)) site1 = self.site_setup(PUBLIC_NET[4], private_nets[1]) - site2 = self.site_setup(PUBLIC_NET[5], private_nets[2]) + if l3ha: + site2 = self.setup_ha_routers(PUBLIC_NET[5], private_nets[2]) + else: + site2 = self.site_setup(PUBLIC_NET[5], private_nets[2]) # build vpn resources self.prepare_ipsec_conn_info(site1['vpn_service'], site2['vpn_service']) self.prepare_ipsec_conn_info(site2['vpn_service'], site1['vpn_service']) - device.report_status = mock.Mock() - device.agent_rpc.get_vpn_services_on_host = mock.Mock( + self.driver.report_status = mock.Mock() + self.driver.agent_rpc.get_vpn_services_on_host = mock.Mock( return_value=[site1['vpn_service'], site2['vpn_service']]) + if l3ha: + self.failover_agent_driver.agent_rpc.get_vpn_services_on_host = ( + mock.Mock(return_value=[])) + self.failover_agent_driver.report_status = mock.Mock() + self.failover_agent_driver.agent_rpc.get_vpn_services_on_host = ( + mock.Mock(return_value=[site2['vpn_service']])) + + return site1, site2 + + +class TestIPSecScenario(TestIPSecBase): + + def test_ipsec_site_connections(self): + site1, site2 = self._create_ipsec_site_connection() net_helpers.assert_no_ping(site1['port_namespace'], site2['port_ip'], timeout=8, count=4) net_helpers.assert_no_ping(site2['port_namespace'], site1['port_ip'], timeout=8, count=4) - device.sync(mock.Mock(), [{'id': site1['router'].router_id}, - {'id': site2['router'].router_id}]) + self.driver.sync(mock.Mock(), [{'id': site1['router'].router_id}, + {'id': site2['router'].router_id}]) self.addCleanup( - device._delete_vpn_processes, + self.driver._delete_vpn_processes, [site1['router'].router_id, site2['router'].router_id], []) net_helpers.assert_ping(site1['port_namespace'], site2['port_ip'], @@ -466,39 +486,14 @@ class TestIPSecScenario(base.BaseSudoTestCase): self.connect_agents(self.vpn_agent, self.failover_agent) vpn_agent_driver = self.vpn_agent.device_drivers[0] - failover_agent_driver = self.failover_agent.device_drivers[0] - ip_lib.send_ip_addr_adv_notif = mock.Mock() + self.failover_agent_driver = self.failover_agent.device_drivers[0] - # There are no vpn services yet. get_vpn_services_on_host returns - # empty list - vpn_agent_driver.agent_rpc.get_vpn_services_on_host = mock.Mock( - return_value=[]) - failover_agent_driver.agent_rpc.get_vpn_services_on_host = mock.Mock( - return_value=[]) + site1, site2 = self._create_ipsec_site_connection(l3ha=True) - # instantiate network resources "router", "private network" - private_nets = list(PRIVATE_NET.subnet(24)) - site1 = self.site_setup(PUBLIC_NET[4], private_nets[1]) - site2 = self.setup_ha_routers(PUBLIC_NET[5], private_nets[2]) router = site1['router'] router1 = site2['router1'] router2 = site2['router2'] - # build vpn resources - self.prepare_ipsec_conn_info(site1['vpn_service'], - site2['vpn_service']) - self.prepare_ipsec_conn_info(site2['vpn_service'], - site1['vpn_service']) - - vpn_agent_driver.report_status = mock.Mock() - failover_agent_driver.report_status = mock.Mock() - - vpn_agent_driver.agent_rpc.get_vpn_services_on_host = mock.Mock( - return_value=[site1['vpn_service'], - site2['vpn_service']]) - failover_agent_driver.agent_rpc.get_vpn_services_on_host = mock.Mock( - return_value=[site2['vpn_service']]) - # No ipsec connection between legacy router and HA routers net_helpers.assert_no_ping(site1['port_namespace'], site2['port_ip'], timeout=8, count=4) @@ -508,7 +503,8 @@ class TestIPSecScenario(base.BaseSudoTestCase): # sync the routers vpn_agent_driver.sync(mock.Mock(), [{'id': router.router_id}, {'id': router1.router_id}]) - failover_agent_driver.sync(mock.Mock(), [{'id': router1.router_id}]) + self.failover_agent_driver.sync(mock.Mock(), + [{'id': router1.router_id}]) self.addCleanup( vpn_agent_driver._delete_vpn_processes, @@ -529,7 +525,8 @@ class TestIPSecScenario(base.BaseSudoTestCase): # wait until ipsec process running in failover agent's HA router # check for both strongswan and openswan processes - path = failover_agent_driver.processes[router2.router_id].config_dir + path = self.failover_agent_driver.processes[ + router2.router_id].config_dir pid_files = ['%s/var/run/charon.pid' % path, '%s/var/run/pluto.pid' % path] linux_utils.wait_until_true( diff --git a/neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py b/neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py index bb0d7049c..344a7fb90 100644 --- a/neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py +++ b/neutron_vpnaas/tests/functional/openswan/test_openswan_driver.py @@ -20,16 +20,34 @@ # functional test for the OpenSwan reference implementation. For now, just # ignore the test cases herein. -from neutron.tests.functional import base +import mock +from neutron.agent.linux import ip_lib +from neutron.agent.linux import utils as linux_utils + +from neutron_vpnaas.tests.functional.common import test_scenario -class TestOpenSwanDeviceDriver(base.BaseSudoTestCase): +class TestOpenSwanDeviceDriver(test_scenario.TestIPSecBase): - """Test the OpenSwan reference implmentation of the device driver.""" + """Test the OpenSwan reference implementation of the device driver.""" # NOTE: Tests may be added/removed/changed, when this is fleshed out # in future commits. + def _ping_mtu(self, namespace, ip, size): + """Pings ip address using packets of given size and with DF=1. + + In order to ping it uses following cli command: + ip netns exec ping -c 4 -M do -s + """ + try: + cmd = ['ping', '-c', 4, '-M', 'do', '-s', size, ip] + cmd = ip_lib.add_namespace_to_cmd(cmd, namespace) + linux_utils.execute(cmd, run_as_root=True) + return True + except RuntimeError: + return False + def test_config_files_created_on_ipsec_connection_create(self): """Verify that directory and config files are correct on create.""" pass @@ -64,3 +82,30 @@ class TestOpenSwanDeviceDriver(base.BaseSudoTestCase): def test_status_reporting(self): """Test status reported correctly to agent.""" pass + + def test_ipsec_site_connections_mtu_enforcement(self): + """Test that mtu of ipsec site connections is enforced.""" + + # Set up non-default mtu value + self.fake_ipsec_connection['mtu'] = 1200 + + # Establish an ipsec connection between two sites + site1, site2 = self._create_ipsec_site_connection() + + self.driver.sync(mock.Mock(), [{'id': site1['router'].router_id}, + {'id': site2['router'].router_id}]) + self.addCleanup( + self.driver._delete_vpn_processes, + [site1['router'].router_id, site2['router'].router_id], []) + + # Validate that ip packets with 1172 (1200) bytes of data pass + self.assertTrue(self._ping_mtu(site1['port_namespace'], + site2['port_ip'], 1172)) + self.assertTrue(self._ping_mtu(site2['port_namespace'], + site1['port_ip'], 1172)) + + # Validate that ip packets with 1173 (1201) bytes of data are dropped + self.assertFalse(self._ping_mtu(site1['port_namespace'], + site2['port_ip'], 1173)) + self.assertFalse(self._ping_mtu(site2['port_namespace'], + site1['port_ip'], 1173)) diff --git a/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py b/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py index dd1da7df9..d95a91575 100644 --- a/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py +++ b/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py @@ -66,6 +66,7 @@ FAKE_VPN_SERVICE = { 'id': FAKE_IPSEC_SITE_CONNECTION1_ID, 'external_ip': '50.0.0.4', 'peer_address': '30.0.0.5', + 'mtu': 1500, 'peer_id': '30.0.0.5', 'psk': 'password', 'initiator': 'bi-directional', @@ -78,6 +79,7 @@ FAKE_VPN_SERVICE = { 'external_ip': '50.0.0.4', 'peer_address': '50.0.0.5', 'peer_id': '50.0.0.5', + 'mtu': 1500, 'psk': 'password', 'id': FAKE_IPSEC_SITE_CONNECTION2_ID, 'initiator': 'bi-directional', @@ -98,8 +100,7 @@ AUTH_AH = '''ah OPENSWAN_CONNECTION_DETAILS = '''# rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) # [mtu] - # Note It looks like not supported in the strongswan driver - # ignore it now + mtu=1500 # [dpd_action] dpdaction= # [dpd_interval] @@ -151,6 +152,11 @@ conn %(conn1_id)s # [subnet] leftsubnet=10.0.0.0/24 # leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) + # [updown] + # What "updown" script to run to adjust routing and/or firewalling when + # the status of the connection changes (default "ipsec _updown"). + # "--route yes" allows to specify such routing options as mtu and metric. + leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ###################### @@ -171,6 +177,11 @@ conn %(conn1_id)s # [subnet] leftsubnet=10.0.0.0/24 # leftsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) + # [updown] + # What "updown" script to run to adjust routing and/or firewalling when + # the status of the connection changes (default "ipsec _updown"). + # "--route yes" allows to specify such routing options as mtu and metric. + leftupdown="ipsec _updown --route yes" ###################### # ipsec_site_connections ######################