[Stateless SG] Test if TCP packets with various conn state are allowed

In stateless SG there is no connection track so TCP packet of any type
should be allowed by SG. This patch adds test which spawns 2 vms,
connected to 2 different networks and the same router. Both vms are
using different stateless SGs. Finally test asserts that packets of any
of the TCP type (syn, ack, syn-ack, psh, rst, fin) sent from one of the
vms can reach second one.

Change-Id: I23a4b282c83101526af05aa309d578aecaef1fa9
This commit is contained in:
Slawek Kaplonski 2023-03-10 17:03:20 +01:00
parent 365373910c
commit 32b0f8b7ae
5 changed files with 120 additions and 35 deletions

View File

@ -675,3 +675,15 @@ class BaseTempestTestCase(base_api.BaseNetworkTest):
router = self.client.update_router( router = self.client.update_router(
router['id'], **kwargs)['router'] router['id'], **kwargs)['router']
self.assertEqual(admin_state_up, router['admin_state_up']) self.assertEqual(admin_state_up, router['admin_state_up'])
def _check_cmd_installed_on_server(self, ssh_client, server, cmd):
try:
ssh_client.execute_script('which %s' % cmd)
except SSH_EXC_TUPLE as ssh_e:
LOG.debug(ssh_e)
self._log_console_output([server])
self._log_local_network_status()
raise
except exceptions.SSHScriptFailed:
raise self.skipException(
"%s is not available on server %s" % (cmd, server['id']))

View File

@ -116,18 +116,6 @@ class MacLearningTest(base.BaseTempestTestCase):
pkey=self.keypair['private_key']) pkey=self.keypair['private_key'])
return server return server
def _check_cmd_installed_on_server(self, ssh_client, server, cmd):
try:
ssh_client.execute_script('which %s' % cmd)
except base.SSH_EXC_TUPLE as ssh_e:
LOG.debug(ssh_e)
self._log_console_output([server])
self._log_local_network_status()
raise
except exceptions.SSHScriptFailed:
raise self.skipException(
"%s is not available on server %s" % (cmd, server['id']))
def _prepare_sender(self, server, address): def _prepare_sender(self, server, address):
check_script = get_sender_script(self.sender_output_file, address, check_script = get_sender_script(self.sender_output_file, address,
self.completed_message) self.completed_message)

View File

@ -213,18 +213,6 @@ class BaseMulticastTest(object):
server, PYTHON3_BIN) server, PYTHON3_BIN)
return server return server
def _check_cmd_installed_on_server(self, ssh_client, server, cmd):
try:
ssh_client.execute_script('which %s' % cmd)
except base.SSH_EXC_TUPLE as ssh_e:
LOG.debug(ssh_e)
self._log_console_output([server])
self._log_local_network_status()
raise
except exceptions.SSHScriptFailed:
raise self.skipException(
"%s is not available on server %s" % (cmd, server['id']))
def _prepare_sender(self, server, mcast_address): def _prepare_sender(self, server, mcast_address):
check_script = get_sender_script( check_script = get_sender_script(
group=mcast_address, port=self.multicast_port, group=mcast_address, port=self.multicast_port,

View File

@ -23,6 +23,7 @@ from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils from tempest.lib.common.utils import test_utils
from tempest.lib import decorators from tempest.lib import decorators
from neutron_tempest_plugin.common import ip
from neutron_tempest_plugin.common import ssh from neutron_tempest_plugin.common import ssh
from neutron_tempest_plugin.common import utils from neutron_tempest_plugin.common import utils
from neutron_tempest_plugin import config from neutron_tempest_plugin import config
@ -35,6 +36,16 @@ CONF = config.CONF
EPHEMERAL_PORT_RANGE = {'min': 32768, 'max': 65535} EPHEMERAL_PORT_RANGE = {'min': 32768, 'max': 65535}
def get_capture_script(interface, tcp_port, packet_types, result_file):
return """#!/bin/bash
tcpdump -i %(interface)s -vvneA -s0 -l -c1 \
"dst port %(port)s and tcp[tcpflags] == %(packet_types)s" &> %(result)s &
""" % {'interface': interface,
'port': tcp_port,
'packet_types': packet_types,
'result': result_file}
class BaseNetworkSecGroupTest(base.BaseTempestTestCase): class BaseNetworkSecGroupTest(base.BaseTempestTestCase):
credentials = ['primary', 'admin'] credentials = ['primary', 'admin']
required_extensions = ['router', 'security-group'] required_extensions = ['router', 'security-group']
@ -98,20 +109,32 @@ class BaseNetworkSecGroupTest(base.BaseTempestTestCase):
self.network_client.update_quotas(self.project_id, security_group=-1) self.network_client.update_quotas(self.project_id, security_group=-1)
def create_vm_testing_sec_grp(self, num_servers=2, security_groups=None, def create_vm_testing_sec_grp(self, num_servers=2, security_groups=None,
ports=None, network_id=None): ports=None, network_id=None,
use_advanced_image=False):
"""Create instance for security group testing """Create instance for security group testing
:param num_servers (int): number of servers to spawn :param num_servers (int): number of servers to spawn
:param security_groups (list): list of security groups :param security_groups (list): list of security groups
:param ports* (list): list of ports :param ports* (list): list of ports
:param: use_advanced_image (bool): use Cirros (False) or
advanced guest image
*Needs to be the same length as num_servers *Needs to be the same length as num_servers
""" """
if (not use_advanced_image or
CONF.neutron_plugin_options.default_image_is_advanced):
flavor_ref = CONF.compute.flavor_ref
image_ref = CONF.compute.image_ref
username = CONF.validation.image_ssh_user
else:
flavor_ref = CONF.neutron_plugin_options.advanced_image_flavor_ref
image_ref = CONF.neutron_plugin_options.advanced_image_ref
username = CONF.neutron_plugin_options.advanced_image_ssh_user
network_id = network_id or self.network['id'] network_id = network_id or self.network['id']
servers, fips, server_ssh_clients = ([], [], []) servers, fips, server_ssh_clients = ([], [], [])
for i in range(num_servers): for i in range(num_servers):
server_args = { server_args = {
'flavor_ref': CONF.compute.flavor_ref, 'flavor_ref': flavor_ref,
'image_ref': CONF.compute.image_ref, 'image_ref': image_ref,
'key_name': self.keypair['name'], 'key_name': self.keypair['name'],
'networks': [{'uuid': network_id}], 'networks': [{'uuid': network_id}],
'security_groups': security_groups 'security_groups': security_groups
@ -128,7 +151,7 @@ class BaseNetworkSecGroupTest(base.BaseTempestTestCase):
'id'])['ports'][0] 'id'])['ports'][0]
fips.append(self.create_floatingip(port=port)) fips.append(self.create_floatingip(port=port))
server_ssh_clients.append(ssh.Client( server_ssh_clients.append(ssh.Client(
fips[i]['floating_ip_address'], CONF.validation.image_ssh_user, fips[i]['floating_ip_address'], username,
pkey=self.keypair['private_key'])) pkey=self.keypair['private_key']))
return server_ssh_clients, fips, servers return server_ssh_clients, fips, servers
@ -145,8 +168,8 @@ class BaseNetworkSecGroupTest(base.BaseTempestTestCase):
return super(BaseNetworkSecGroupTest, self).create_security_group( return super(BaseNetworkSecGroupTest, self).create_security_group(
name=data_utils.rand_name(name_prefix), **kwargs) name=data_utils.rand_name(name_prefix), **kwargs)
def _test_connectivity_between_vms_using_different_sec_groups(self): def _create_client_and_server_vms(
TEST_TCP_PORT = 1022 self, allowed_tcp_port, use_advanced_image=False):
networks = { networks = {
'server': self.network, 'server': self.network,
'client': self.create_network()} 'client': self.create_network()}
@ -162,13 +185,12 @@ class BaseNetworkSecGroupTest(base.BaseTempestTestCase):
security_group_id=sg['id'], security_group_id=sg['id'],
protocol=constants.PROTO_NAME_TCP, protocol=constants.PROTO_NAME_TCP,
direction=constants.INGRESS_DIRECTION, direction=constants.INGRESS_DIRECTION,
port_range_min=TEST_TCP_PORT, port_range_min=allowed_tcp_port,
port_range_max=TEST_TCP_PORT) port_range_max=allowed_tcp_port)
if self.stateless_sg: if self.stateless_sg:
self.create_ingress_metadata_secgroup_rule( self.create_ingress_metadata_secgroup_rule(
secgroup_id=sg['id']) secgroup_id=sg['id'])
security_groups[sg_name] = sg security_groups[sg_name] = sg
# NOTE(slaweq): we need to iterate over create_vm_testing_sec_grp as # NOTE(slaweq): we need to iterate over create_vm_testing_sec_grp as
# this method plugs all SGs to all VMs and we need each vm to use other # this method plugs all SGs to all VMs and we need each vm to use other
# SGs # SGs
@ -179,17 +201,23 @@ class BaseNetworkSecGroupTest(base.BaseTempestTestCase):
_ssh_clients, _fips, _servers = self.create_vm_testing_sec_grp( _ssh_clients, _fips, _servers = self.create_vm_testing_sec_grp(
num_servers=1, num_servers=1,
security_groups=[{'name': sg['name']}], security_groups=[{'name': sg['name']}],
network_id=networks[server_name]['id']) network_id=networks[server_name]['id'],
use_advanced_image=use_advanced_image)
ssh_clients[server_name] = _ssh_clients[0] ssh_clients[server_name] = _ssh_clients[0]
fips[server_name] = _fips[0] fips[server_name] = _fips[0]
servers[server_name] = _servers[0] servers[server_name] = _servers[0]
return ssh_clients, fips, servers, security_groups
def _test_connectivity_between_vms_using_different_sec_groups(self):
TEST_TCP_PORT = 1022
ssh_clients, fips, servers, security_groups = (
self._create_client_and_server_vms(TEST_TCP_PORT))
# make sure tcp connectivity between vms works fine # make sure tcp connectivity between vms works fine
for fip in fips.values(): for fip in fips.values():
self.check_connectivity(fip['floating_ip_address'], self.check_connectivity(fip['floating_ip_address'],
CONF.validation.image_ssh_user, CONF.validation.image_ssh_user,
self.keypair['private_key']) self.keypair['private_key'])
# Check connectivity between servers # Check connectivity between servers
def _message_received(server_ssh_client, client_ssh_client, def _message_received(server_ssh_client, client_ssh_client,
dest_fip, servers): dest_fip, servers):
@ -994,3 +1022,71 @@ class StatelessNetworkSecGroupTest(BaseNetworkSecGroupTest):
@decorators.idempotent_id('7ede9ab5-a615-46c5-9dea-cf2aa1ea43cb') @decorators.idempotent_id('7ede9ab5-a615-46c5-9dea-cf2aa1ea43cb')
def test_connectivity_between_vms_using_different_sec_groups(self): def test_connectivity_between_vms_using_different_sec_groups(self):
self._test_connectivity_between_vms_using_different_sec_groups() self._test_connectivity_between_vms_using_different_sec_groups()
@testtools.skipUnless(
(CONF.neutron_plugin_options.advanced_image_ref or
CONF.neutron_plugin_options.default_image_is_advanced),
"Advanced image is required to run this test.")
@decorators.idempotent_id('c3bb8073-97a2-4bea-a6fb-0a9d2e4df13f')
def test_packets_of_any_connection_state_can_reach_dest(self):
TEST_TCP_PORT = 1022
PKT_TYPES = [
{'nping': 'syn', 'tcpdump': 'tcp-syn'},
{'nping': 'ack', 'tcpdump': 'tcp-ack'},
{'nping': 'syn,ack', 'tcpdump': 'tcp-syn|tcp-ack'},
{'nping': 'rst', 'tcpdump': 'tcp-rst'},
{'nping': 'fin', 'tcpdump': 'tcp-fin'},
{'nping': 'psh', 'tcpdump': 'tcp-push'}]
ssh_clients, fips, servers, _ = self._create_client_and_server_vms(
TEST_TCP_PORT, use_advanced_image=True)
self._check_cmd_installed_on_server(
ssh_clients['server'], servers['server']['server'], 'nping')
self._check_cmd_installed_on_server(
ssh_clients['client'], servers['client']['server'], 'tcpdump')
server_port = self.network_client.show_port(
fips['server']['port_id'])['port']
server_ip_command = ip.IPCommand(ssh_client=ssh_clients['server'])
addresses = server_ip_command.list_addresses(port=server_port)
port_iface = ip.get_port_device_name(addresses, server_port)
def _get_file_suffix(pkt_type):
return pkt_type['tcpdump'].replace(
'tcp-', '').replace('|', '')
for pkt_type in PKT_TYPES:
file_suffix = _get_file_suffix(pkt_type)
capture_script_path = "/tmp/capture_%s.sh" % file_suffix
capture_out = "/tmp/capture_%s.out" % file_suffix
capture_script = get_capture_script(
port_iface, TEST_TCP_PORT, pkt_type['tcpdump'], capture_out)
ssh_clients['server'].execute_script(
'echo \'%s\' > %s' % (capture_script, capture_script_path))
ssh_clients['server'].execute_script(
"bash %s" % capture_script_path, become_root=True)
for pkt_type in PKT_TYPES:
ssh_clients['client'].execute_script(
"nping --tcp -p %(tcp_port)s --flags %(tcp_flag)s --ttl 10 "
"%(ip_address)s -c 3" % {
'tcp_port': TEST_TCP_PORT,
'tcp_flag': pkt_type['nping'],
'ip_address': fips['server']['fixed_ip_address']},
become_root=True)
def _packtet_received(pkt_type):
file_suffix = _get_file_suffix(pkt_type)
expected_msg = "1 packet captured"
result = ssh_clients['server'].execute_script(
"cat {path} || echo '{path} not exists yet'".format(
path="/tmp/capture_%s.out" % file_suffix))
return expected_msg in result
for pkt_type in PKT_TYPES:
utils.wait_until_true(
lambda: _packtet_received(pkt_type),
timeout=10,
exception=RuntimeError(
'No TCP packet of type %s received by server %s' % (
pkt_type['nping'],
fips['server']['fixed_ip_address'])))

View File

@ -19,6 +19,7 @@ INSTALL_GUEST_PACKAGES=(
iperf3 iperf3
iputils-ping iputils-ping
ncat ncat
nmap
psmisc # provides killall command psmisc # provides killall command
python3 python3
tcpdump tcpdump