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 f71232ddb..29ef2b6a8 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 @@ -67,6 +67,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', @@ -79,6 +80,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', @@ -99,8 +101,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] @@ -152,6 +153,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 ###################### @@ -172,6 +178,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 ######################