| # SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB) |
| # Copyright (c) 2020 Nvidia All rights reserved. See COPYING file |
| """ |
| Test module for pyverbs' mlx5 flow module. |
| """ |
| |
| import unittest |
| import errno |
| |
| from pyverbs.providers.mlx5.mlx5dv_flow import Mlx5FlowMatcher, \ |
| Mlx5FlowMatcherAttr, Mlx5FlowMatchParameters, Mlx5FlowActionAttr, Mlx5Flow,\ |
| Mlx5PacketReformatFlowAction |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr |
| from pyverbs.pyverbs_error import PyverbsRDMAError, PyverbsUserError |
| from tests.utils import requires_root_on_eth, PacketConsts |
| import pyverbs.providers.mlx5.mlx5_enums as dve |
| from tests.mlx5_base import Mlx5RDMATestCase |
| from tests.base import RawResources |
| import pyverbs.enums as e |
| import tests.utils as u |
| import struct |
| |
| |
| MAX_MATCH_PARAM_SIZE = 0x180 |
| |
| |
| @u.skip_unsupported |
| def requires_reformat_support(func): |
| def func_wrapper(instance): |
| nic_tbl_caps = u.query_nic_flow_table_caps(instance) |
| # Verify that both NIC RX and TX support reformat actions by checking |
| # the following PRM fields: encap_general_header, |
| # log_max_packet_reformat, and reformat (for both RX and TX). |
| if not(nic_tbl_caps.encap_general_header and |
| nic_tbl_caps.log_max_packet_reformat_context and |
| nic_tbl_caps.flow_table_properties_nic_receive.reformat and |
| nic_tbl_caps.flow_table_properties_nic_transmit.reformat): |
| raise unittest.SkipTest('NIC flow table does not support reformat') |
| return func(instance) |
| return func_wrapper |
| |
| |
| def gen_vxlan_l2_tunnel_encap_header(msg_size): |
| vxlan_header = u.gen_vxlan_header() |
| udp_header = u.gen_udp_header(packet_len=msg_size + len(vxlan_header), |
| dst_port=PacketConsts.VXLAN_PORT) |
| ip_header = u.gen_ipv4_header(packet_len=msg_size + len(vxlan_header) + len(udp_header)) |
| mac_header = u.gen_ethernet_header() |
| return mac_header + ip_header + udp_header + vxlan_header |
| |
| |
| class Mlx5FlowResources(RawResources): |
| |
| def create_matcher(self, mask, match_criteria_enable, flags=0, |
| ft_type=dve.MLX5DV_FLOW_TABLE_TYPE_NIC_RX_): |
| """ |
| Creates a matcher from a provided mask. |
| :param mask: The mask to match on (in bytes) |
| :param match_criteria_enable: Bitmask representing which of the |
| headers and parameters in match_criteria |
| are used |
| :param flags: Flow matcher flags |
| :param ft_type: Flow table type |
| :return: Resulting matcher |
| """ |
| try: |
| flow_match_param = Mlx5FlowMatchParameters(len(mask), mask) |
| attr = Mlx5FlowMatcherAttr(match_mask=flow_match_param, |
| match_criteria_enable=match_criteria_enable, |
| flags=flags, ft_type=ft_type) |
| matcher = Mlx5FlowMatcher(self.ctx, attr) |
| except PyverbsRDMAError as ex: |
| if ex.error_code in [errno.EOPNOTSUPP, errno.EPROTONOSUPPORT]: |
| raise unittest.SkipTest('Matcher creation is not supported') |
| raise ex |
| return matcher |
| |
| @requires_root_on_eth() |
| def create_qps(self): |
| super().create_qps() |
| |
| |
| class Mlx5MatcherTest(Mlx5RDMATestCase): |
| def setUp(self): |
| super().setUp() |
| self.iters = 10 |
| self.server = None |
| self.client = None |
| |
| @u.skip_unsupported |
| def test_create_empty_matcher(self): |
| """ |
| Creates an empty matcher |
| """ |
| self.res = Mlx5FlowResources(**self.dev_info) |
| empty_mask = bytes(MAX_MATCH_PARAM_SIZE) |
| self.res.create_matcher(empty_mask, u.MatchCriteriaEnable.NONE) |
| |
| @u.skip_unsupported |
| def test_create_smac_matcher(self): |
| """ |
| Creates a matcher to match on outer source mac |
| """ |
| self.res = Mlx5FlowResources(**self.dev_info) |
| smac_mask = bytes([0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) |
| self.res.create_matcher(smac_mask, u.MatchCriteriaEnable.OUTER) |
| |
| @u.skip_unsupported |
| def test_smac_matcher_to_qp_flow(self): |
| """ |
| Creates a matcher to match on outer source mac and a flow that forwards |
| packets to QP when matching on source mac. |
| """ |
| self.create_players(Mlx5FlowResources) |
| smac_mask = bytes([0xff] * 6) |
| matcher = self.server.create_matcher(smac_mask, |
| u.MatchCriteriaEnable.OUTER) |
| smac_value = struct.pack('!6s', |
| bytes.fromhex(PacketConsts.SRC_MAC.replace(':', ''))) |
| value_param = Mlx5FlowMatchParameters(len(smac_value), smac_value) |
| action_qp = Mlx5FlowActionAttr(action_type=dve.MLX5DV_FLOW_ACTION_DEST_IBV_QP, |
| qp=self.server.qp) |
| self.server.flow = Mlx5Flow(matcher, value_param, [action_qp], 1) |
| u.raw_traffic(self.client, self.server, self.iters) |
| |
| @requires_reformat_support |
| @u.requires_encap_disabled_if_eswitch_on |
| def test_tx_packet_reformat(self): |
| """ |
| Creates packet reformat (encap) action on TX and with QP action on RX |
| verifies that the packet was encapsulated as expected. |
| """ |
| self.client = Mlx5FlowResources(**self.dev_info) |
| outer = gen_vxlan_l2_tunnel_encap_header(self.client.msg_size) |
| # Due to encapsulation action Ipv4 and UDP checksum of the outer header |
| # will be recalculated, need to skip them during packet validation. |
| ipv4_id_idx = [18, 19] |
| ipv4_chksum_idx = [24, 25] |
| udp_chksum_idx = [34, 35] |
| # Server will receive encaped packet so message size must include the |
| # length of the outer part. |
| self.server = Mlx5FlowResources(msg_size=self.client.msg_size + len(outer), |
| **self.dev_info) |
| empty_bytes_arr = bytes(MAX_MATCH_PARAM_SIZE) |
| empty_value_param = Mlx5FlowMatchParameters(len(empty_bytes_arr), |
| empty_bytes_arr) |
| |
| # TX steering |
| tx_matcher = self.client.create_matcher(empty_bytes_arr, |
| u.MatchCriteriaEnable.NONE, |
| e.IBV_FLOW_ATTR_FLAGS_EGRESS, |
| dve.MLX5DV_FLOW_TABLE_TYPE_NIC_TX_) |
| # Create encap action |
| reformat_action = Mlx5PacketReformatFlowAction( |
| self.client.ctx, data=outer, |
| reformat_type=dve.MLX5DV_FLOW_ACTION_PACKET_REFORMAT_TYPE_L2_TO_L2_TUNNEL_, |
| ft_type=dve.MLX5DV_FLOW_TABLE_TYPE_NIC_TX_) |
| action_reformat_attr = Mlx5FlowActionAttr(flow_action=reformat_action, |
| action_type=dve.MLX5DV_FLOW_ACTION_IBV_FLOW_ACTION) |
| self.client.flow = Mlx5Flow(tx_matcher, empty_value_param, |
| [action_reformat_attr], 1) |
| |
| # RX steering |
| rx_matcher = self.server.create_matcher(empty_bytes_arr, u.MatchCriteriaEnable.NONE) |
| action_qp_attr = Mlx5FlowActionAttr(action_type=dve.MLX5DV_FLOW_ACTION_DEST_IBV_QP, |
| qp=self.server.qp) |
| self.server.flow = Mlx5Flow(rx_matcher, empty_value_param, [action_qp_attr], 1) |
| |
| # Send traffic and validate packet |
| packet = u.gen_packet(self.client.msg_size) |
| u.raw_traffic(self.client, self.server, self.iters, |
| expected_packet=outer + packet, |
| skip_idxs=ipv4_id_idx + ipv4_chksum_idx + udp_chksum_idx) |