xref: /dpdk/usertools/dpdk-rss-flows.py (revision c1d145834f287aa8cf53de914618a7312f2c360e)
19f4acbc6SRobin Jarry#!/usr/bin/env python3
29f4acbc6SRobin Jarry# SPDX-License-Identifier: BSD-3-Clause
39f4acbc6SRobin Jarry# Copyright (c) 2014 6WIND S.A.
49f4acbc6SRobin Jarry# Copyright (c) 2023 Robin Jarry
59f4acbc6SRobin Jarry
69f4acbc6SRobin Jarry"""
79f4acbc6SRobin JarryCraft IP{v6}/{TCP/UDP} traffic flows that will evenly spread over a given
89f4acbc6SRobin Jarrynumber of RX queues according to the RSS algorithm.
99f4acbc6SRobin Jarry"""
109f4acbc6SRobin Jarry
119f4acbc6SRobin Jarryimport argparse
129f4acbc6SRobin Jarryimport binascii
139f4acbc6SRobin Jarryimport ctypes
149f4acbc6SRobin Jarryimport ipaddress
159f4acbc6SRobin Jarryimport json
169f4acbc6SRobin Jarryimport struct
179f4acbc6SRobin Jarryimport typing
189f4acbc6SRobin Jarry
199f4acbc6SRobin Jarry
209f4acbc6SRobin JarryAddress = typing.Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
219f4acbc6SRobin JarryNetwork = typing.Union[ipaddress.IPv4Network, ipaddress.IPv6Network]
229f4acbc6SRobin JarryPortList = typing.Iterable[int]
239f4acbc6SRobin Jarry
249f4acbc6SRobin Jarry
259f4acbc6SRobin Jarryclass Packet:
269f4acbc6SRobin Jarry    def __init__(self, ip_src: Address, ip_dst: Address, l4_sport: int, l4_dport: int):
279f4acbc6SRobin Jarry        self.ip_src = ip_src
289f4acbc6SRobin Jarry        self.ip_dst = ip_dst
299f4acbc6SRobin Jarry        self.l4_sport = l4_sport
309f4acbc6SRobin Jarry        self.l4_dport = l4_dport
319f4acbc6SRobin Jarry
329f4acbc6SRobin Jarry    def reverse(self):
339f4acbc6SRobin Jarry        return Packet(
349f4acbc6SRobin Jarry            ip_src=self.ip_dst,
359f4acbc6SRobin Jarry            l4_sport=self.l4_dport,
369f4acbc6SRobin Jarry            ip_dst=self.ip_src,
379f4acbc6SRobin Jarry            l4_dport=self.l4_sport,
389f4acbc6SRobin Jarry        )
399f4acbc6SRobin Jarry
409f4acbc6SRobin Jarry    def hash_data(self, use_l4_port: bool = False) -> bytes:
419f4acbc6SRobin Jarry        data = self.ip_src.packed + self.ip_dst.packed
429f4acbc6SRobin Jarry        if use_l4_port:
439f4acbc6SRobin Jarry            data += struct.pack(">H", self.l4_sport)
449f4acbc6SRobin Jarry            data += struct.pack(">H", self.l4_dport)
459f4acbc6SRobin Jarry        return data
469f4acbc6SRobin Jarry
479f4acbc6SRobin Jarry
489f4acbc6SRobin Jarryclass TrafficTemplate:
499f4acbc6SRobin Jarry    def __init__(
509f4acbc6SRobin Jarry        self,
519f4acbc6SRobin Jarry        ip_src: Network,
529f4acbc6SRobin Jarry        ip_dst: Network,
539f4acbc6SRobin Jarry        l4_sport_range: PortList,
549f4acbc6SRobin Jarry        l4_dport_range: PortList,
559f4acbc6SRobin Jarry    ):
569f4acbc6SRobin Jarry        self.ip_src = ip_src
579f4acbc6SRobin Jarry        self.ip_dst = ip_dst
589f4acbc6SRobin Jarry        self.l4_sport_range = l4_sport_range
599f4acbc6SRobin Jarry        self.l4_dport_range = l4_dport_range
609f4acbc6SRobin Jarry
619f4acbc6SRobin Jarry    def __iter__(self) -> typing.Iterator[Packet]:
629f4acbc6SRobin Jarry        for ip_src in self.ip_src.hosts():
639f4acbc6SRobin Jarry            for ip_dst in self.ip_dst.hosts():
649f4acbc6SRobin Jarry                if ip_src == ip_dst:
659f4acbc6SRobin Jarry                    continue
669f4acbc6SRobin Jarry                for sport in self.l4_sport_range:
679f4acbc6SRobin Jarry                    for dport in self.l4_dport_range:
689f4acbc6SRobin Jarry                        yield Packet(ip_src, ip_dst, sport, dport)
699f4acbc6SRobin Jarry
709f4acbc6SRobin Jarry
719f4acbc6SRobin Jarryclass RSSAlgo:
729f4acbc6SRobin Jarry    def __init__(
739f4acbc6SRobin Jarry        self,
749f4acbc6SRobin Jarry        queues_count: int,
759f4acbc6SRobin Jarry        key: bytes,
769f4acbc6SRobin Jarry        reta_size: int,
779f4acbc6SRobin Jarry        use_l4_port: bool,
789f4acbc6SRobin Jarry    ):
799f4acbc6SRobin Jarry        self.queues_count = queues_count
809f4acbc6SRobin Jarry        self.reta = tuple(i % queues_count for i in range(reta_size))
819f4acbc6SRobin Jarry        self.key = key
829f4acbc6SRobin Jarry        self.use_l4_port = use_l4_port
839f4acbc6SRobin Jarry
849f4acbc6SRobin Jarry    def toeplitz_hash(self, data: bytes) -> int:
859f4acbc6SRobin Jarry        # see rte_softrss_* in lib/hash/rte_thash.h
869f4acbc6SRobin Jarry        hash_value = ctypes.c_uint32(0)
879f4acbc6SRobin Jarry
889f4acbc6SRobin Jarry        for i, byte in enumerate(data):
899f4acbc6SRobin Jarry            for j in range(8):
909f4acbc6SRobin Jarry                bit = (byte >> (7 - j)) & 0x01
919f4acbc6SRobin Jarry
929f4acbc6SRobin Jarry                if bit == 1:
939f4acbc6SRobin Jarry                    keyword = ctypes.c_uint32(0)
949f4acbc6SRobin Jarry                    keyword.value |= self.key[i] << 24
959f4acbc6SRobin Jarry                    keyword.value |= self.key[i + 1] << 16
969f4acbc6SRobin Jarry                    keyword.value |= self.key[i + 2] << 8
979f4acbc6SRobin Jarry                    keyword.value |= self.key[i + 3]
989f4acbc6SRobin Jarry
999f4acbc6SRobin Jarry                    if j > 0:
1009f4acbc6SRobin Jarry                        keyword.value <<= j
1019f4acbc6SRobin Jarry                        keyword.value |= self.key[i + 4] >> (8 - j)
1029f4acbc6SRobin Jarry
1039f4acbc6SRobin Jarry                    hash_value.value ^= keyword.value
1049f4acbc6SRobin Jarry
1059f4acbc6SRobin Jarry        return hash_value.value
1069f4acbc6SRobin Jarry
1079f4acbc6SRobin Jarry    def get_queue_index(self, packet: Packet) -> int:
1089f4acbc6SRobin Jarry        bytes_to_hash = packet.hash_data(self.use_l4_port)
1099f4acbc6SRobin Jarry
1109f4acbc6SRobin Jarry        # get the 32bit hash of the packet
1119f4acbc6SRobin Jarry        hash_value = self.toeplitz_hash(bytes_to_hash)
1129f4acbc6SRobin Jarry
1139f4acbc6SRobin Jarry        # determine the offset in the redirection table
1149f4acbc6SRobin Jarry        offset = hash_value & (len(self.reta) - 1)
1159f4acbc6SRobin Jarry
1169f4acbc6SRobin Jarry        return self.reta[offset]
1179f4acbc6SRobin Jarry
1189f4acbc6SRobin Jarry
1199f4acbc6SRobin Jarrydef balanced_traffic(
1209f4acbc6SRobin Jarry    algo: RSSAlgo,
1219f4acbc6SRobin Jarry    traffic_template: TrafficTemplate,
1229f4acbc6SRobin Jarry    check_reverse_traffic: bool = False,
1239f4acbc6SRobin Jarry    all_flows: bool = False,
1249f4acbc6SRobin Jarry) -> typing.Iterator[typing.Tuple[int, int, Packet]]:
1259f4acbc6SRobin Jarry    queues = set()
1269f4acbc6SRobin Jarry    if check_reverse_traffic:
1279f4acbc6SRobin Jarry        queues_reverse = set()
1289f4acbc6SRobin Jarry
1299f4acbc6SRobin Jarry    for pkt in traffic_template:
1309f4acbc6SRobin Jarry        q = algo.get_queue_index(pkt)
1319f4acbc6SRobin Jarry
1329f4acbc6SRobin Jarry        # check if q is already filled
1339f4acbc6SRobin Jarry        if not all_flows and q in queues:
1349f4acbc6SRobin Jarry            continue
1359f4acbc6SRobin Jarry
1369f4acbc6SRobin Jarry        qr = algo.get_queue_index(pkt.reverse())
1379f4acbc6SRobin Jarry
1389f4acbc6SRobin Jarry        if check_reverse_traffic:
1399f4acbc6SRobin Jarry            # check if q is already filled
1409f4acbc6SRobin Jarry            if not all_flows and qr in queues_reverse:
1419f4acbc6SRobin Jarry                continue
1429f4acbc6SRobin Jarry            # mark this queue as matched
1439f4acbc6SRobin Jarry            queues_reverse.add(qr)
1449f4acbc6SRobin Jarry
1459f4acbc6SRobin Jarry        # mark this queue as filled
1469f4acbc6SRobin Jarry        queues.add(q)
1479f4acbc6SRobin Jarry
1489f4acbc6SRobin Jarry        yield q, qr, pkt
1499f4acbc6SRobin Jarry
1509f4acbc6SRobin Jarry        # stop when all queues have been filled
1519f4acbc6SRobin Jarry        if not all_flows and len(queues) == algo.queues_count:
1529f4acbc6SRobin Jarry            break
1539f4acbc6SRobin Jarry
1549f4acbc6SRobin Jarry
1559f4acbc6SRobin JarryNO_PORT = (0,)
1569f4acbc6SRobin Jarry
157a9c28754SRobin Jarry
158a9c28754SRobin Jarryclass DriverInfo:
159a9c28754SRobin Jarry    def __init__(self, key: bytes = None, reta_size: int = None):
160a9c28754SRobin Jarry        self.__key = key
161a9c28754SRobin Jarry        self.__reta_size = reta_size
162a9c28754SRobin Jarry
163a9c28754SRobin Jarry    def rss_key(self) -> bytes:
164a9c28754SRobin Jarry        return self.__key
165a9c28754SRobin Jarry
166a9c28754SRobin Jarry    def reta_size(self, num_queues: int) -> int:
167a9c28754SRobin Jarry        return self.__reta_size
168a9c28754SRobin Jarry
169a9c28754SRobin Jarry
170a9c28754SRobin Jarryclass MlxDriverInfo(DriverInfo):
171a9c28754SRobin Jarry    def rss_key(self) -> bytes:
172a9c28754SRobin Jarry        return bytes(
173a9c28754SRobin Jarry            (
1749f4acbc6SRobin Jarry                # fmt: off
1759f4acbc6SRobin Jarry                # rss_hash_default_key, see drivers/net/mlx5/mlx5_rxq.c
1769f4acbc6SRobin Jarry                0x2c, 0xc6, 0x81, 0xd1, 0x5b, 0xdb, 0xf4, 0xf7,
1779f4acbc6SRobin Jarry                0xfc, 0xa2, 0x83, 0x19, 0xdb, 0x1a, 0x3e, 0x94,
1789f4acbc6SRobin Jarry                0x6b, 0x9e, 0x38, 0xd9, 0x2c, 0x9c, 0x03, 0xd1,
1799f4acbc6SRobin Jarry                0xad, 0x99, 0x44, 0xa7, 0xd9, 0x56, 0x3d, 0x59,
1809f4acbc6SRobin Jarry                0x06, 0x3c, 0x25, 0xf3, 0xfc, 0x1f, 0xdc, 0x2a,
181a9c28754SRobin Jarry                # fmt: on
1829f4acbc6SRobin Jarry            )
1839f4acbc6SRobin Jarry        )
184a9c28754SRobin Jarry
185a9c28754SRobin Jarry    def reta_size(self, num_queues: int) -> int:
186a9c28754SRobin Jarry        if num_queues & (num_queues - 1) == 0:
187a9c28754SRobin Jarry            # If the requested number of RX queues is power of two,
188a9c28754SRobin Jarry            # use a table of this size.
189a9c28754SRobin Jarry            return num_queues
190a9c28754SRobin Jarry        # otherwise, use the maximum table size
191a9c28754SRobin Jarry        return 512
192a9c28754SRobin Jarry
193a9c28754SRobin Jarry
194a9c28754SRobin JarryDEFAULT_DRIVERS = {
195202e5b2bSRobin Jarry    "cnxk": DriverInfo(
196202e5b2bSRobin Jarry        key=bytes(
197202e5b2bSRobin Jarry            (
198202e5b2bSRobin Jarry                # fmt: off
199202e5b2bSRobin Jarry                # roc_nix_rss_key_default_fill, see drivers/common/cnxk/roc_nix_rss.c
200202e5b2bSRobin Jarry                # Marvell cnxk NICs take 48 bytes keys
201202e5b2bSRobin Jarry                0xfe, 0xed, 0x0b, 0xad, 0xfe, 0xed, 0x0b, 0xad,
202202e5b2bSRobin Jarry                0xfe, 0xed, 0x0b, 0xad, 0xfe, 0xed, 0x0b, 0xad,
203202e5b2bSRobin Jarry                0xfe, 0xed, 0x0b, 0xad, 0xfe, 0xed, 0x0b, 0xad,
204202e5b2bSRobin Jarry                0xfe, 0xed, 0x0b, 0xad, 0xfe, 0xed, 0x0b, 0xad,
205202e5b2bSRobin Jarry                0xfe, 0xed, 0x0b, 0xad, 0xfe, 0xed, 0x0b, 0xad,
206202e5b2bSRobin Jarry                0xfe, 0xed, 0x0b, 0xad, 0xfe, 0xed, 0x0b, 0xad,
207202e5b2bSRobin Jarry                # fmt: on
208202e5b2bSRobin Jarry            )
209202e5b2bSRobin Jarry        ),
210202e5b2bSRobin Jarry        reta_size=64,
211202e5b2bSRobin Jarry    ),
212a9c28754SRobin Jarry    "intel": DriverInfo(
213a9c28754SRobin Jarry        key=bytes(
214a9c28754SRobin Jarry            (
215a9c28754SRobin Jarry                # fmt: off
216*c1d14583SBruce Richardson                # rss_intel_key, see drivers/net/intel/ixgbe/ixgbe_rxtx.c
217a9c28754SRobin Jarry                0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
218a9c28754SRobin Jarry                0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
219a9c28754SRobin Jarry                0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
220a9c28754SRobin Jarry                0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
221a9c28754SRobin Jarry                0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa,
222a9c28754SRobin Jarry                # fmt: on
223a9c28754SRobin Jarry            )
224a9c28754SRobin Jarry        ),
225a9c28754SRobin Jarry        reta_size=128,
226a9c28754SRobin Jarry    ),
227a9c28754SRobin Jarry    "i40e": DriverInfo(
228a9c28754SRobin Jarry        key=bytes(
229a9c28754SRobin Jarry            (
230a9c28754SRobin Jarry                # fmt: off
231*c1d14583SBruce Richardson                # rss_key_default, see drivers/net/intel/i40e/i40e_ethdev.c
2329f4acbc6SRobin Jarry                # i40e is the only driver that takes 52 bytes keys
2331cbed65dSRobin Jarry                0x44, 0x39, 0x79, 0x6b, 0xb5, 0x4c, 0x50, 0x23,
2341cbed65dSRobin Jarry                0xb6, 0x75, 0xea, 0x5b, 0x12, 0x4f, 0x9f, 0x30,
2351cbed65dSRobin Jarry                0xb8, 0xa2, 0xc0, 0x3d, 0xdf, 0xdc, 0x4d, 0x02,
2361cbed65dSRobin Jarry                0xa0, 0x8c, 0x9b, 0x33, 0x4a, 0xf6, 0x4a, 0x4c,
2371cbed65dSRobin Jarry                0x05, 0xc6, 0xfa, 0x34, 0x39, 0x58, 0xd8, 0x55,
2381cbed65dSRobin Jarry                0x7d, 0x99, 0x58, 0x3a, 0xe1, 0x38, 0xc9, 0x2e,
2391cbed65dSRobin Jarry                0x81, 0x15, 0x03, 0x66,
2409f4acbc6SRobin Jarry                # fmt: on
241a9c28754SRobin Jarry            )
242a9c28754SRobin Jarry        ),
243a9c28754SRobin Jarry        reta_size=512,
244a9c28754SRobin Jarry    ),
245a9c28754SRobin Jarry    "mlx": MlxDriverInfo(),
2469f4acbc6SRobin Jarry}
2479f4acbc6SRobin Jarry
2489f4acbc6SRobin Jarry
2499f4acbc6SRobin Jarrydef port_range(value):
2509f4acbc6SRobin Jarry    try:
2519f4acbc6SRobin Jarry        if "-" in value:
2529f4acbc6SRobin Jarry            start, stop = value.split("-")
2539f4acbc6SRobin Jarry            res = tuple(range(int(start), int(stop)))
2549f4acbc6SRobin Jarry        else:
2559f4acbc6SRobin Jarry            res = (int(value),)
2569f4acbc6SRobin Jarry        return res or NO_PORT
2579f4acbc6SRobin Jarry    except ValueError as e:
2589f4acbc6SRobin Jarry        raise argparse.ArgumentTypeError(str(e)) from e
2599f4acbc6SRobin Jarry
2609f4acbc6SRobin Jarry
2619f4acbc6SRobin Jarrydef positive_int(value):
2629f4acbc6SRobin Jarry    try:
2639f4acbc6SRobin Jarry        i = int(value)
2649f4acbc6SRobin Jarry        if i <= 0:
2659f4acbc6SRobin Jarry            raise argparse.ArgumentTypeError("must be strictly positive")
2669f4acbc6SRobin Jarry        return i
2679f4acbc6SRobin Jarry    except ValueError as e:
2689f4acbc6SRobin Jarry        raise argparse.ArgumentTypeError(str(e)) from e
2699f4acbc6SRobin Jarry
2709f4acbc6SRobin Jarry
2719f4acbc6SRobin Jarrydef power_of_two(value):
2729f4acbc6SRobin Jarry    i = positive_int(value)
2739f4acbc6SRobin Jarry    if i & (i - 1) != 0:
2749f4acbc6SRobin Jarry        raise argparse.ArgumentTypeError("must be a power of two")
2759f4acbc6SRobin Jarry    return i
2769f4acbc6SRobin Jarry
2779f4acbc6SRobin Jarry
2789f4acbc6SRobin Jarrydef parse_args():
2799f4acbc6SRobin Jarry    parser = argparse.ArgumentParser(description=__doc__)
2809f4acbc6SRobin Jarry
2819f4acbc6SRobin Jarry    parser.add_argument(
2829f4acbc6SRobin Jarry        "rx_queues",
2839f4acbc6SRobin Jarry        metavar="RX_QUEUES",
2849f4acbc6SRobin Jarry        type=positive_int,
2859f4acbc6SRobin Jarry        help="""
2869f4acbc6SRobin Jarry        The number of RX queues to fill.
2879f4acbc6SRobin Jarry        """,
2889f4acbc6SRobin Jarry    )
2899f4acbc6SRobin Jarry    parser.add_argument(
2909f4acbc6SRobin Jarry        "ip_src",
2919f4acbc6SRobin Jarry        metavar="SRC",
2929f4acbc6SRobin Jarry        type=ipaddress.ip_network,
2939f4acbc6SRobin Jarry        help="""
2949f4acbc6SRobin Jarry        The source IP network/address.
2959f4acbc6SRobin Jarry        """,
2969f4acbc6SRobin Jarry    )
2979f4acbc6SRobin Jarry    parser.add_argument(
2989f4acbc6SRobin Jarry        "ip_dst",
2999f4acbc6SRobin Jarry        metavar="DST",
3009f4acbc6SRobin Jarry        type=ipaddress.ip_network,
3019f4acbc6SRobin Jarry        help="""
3029f4acbc6SRobin Jarry        The destination IP network/address.
3039f4acbc6SRobin Jarry        """,
3049f4acbc6SRobin Jarry    )
3059f4acbc6SRobin Jarry    parser.add_argument(
3069f4acbc6SRobin Jarry        "-s",
3079f4acbc6SRobin Jarry        "--sport-range",
3089f4acbc6SRobin Jarry        type=port_range,
3099f4acbc6SRobin Jarry        default=NO_PORT,
3109f4acbc6SRobin Jarry        help="""
3119f4acbc6SRobin Jarry        The layer 4 (TCP/UDP) source port range.
3129f4acbc6SRobin Jarry        Can be a single fixed value or a range <start>-<end>.
3139f4acbc6SRobin Jarry        """,
3149f4acbc6SRobin Jarry    )
3159f4acbc6SRobin Jarry    parser.add_argument(
3169f4acbc6SRobin Jarry        "-d",
3179f4acbc6SRobin Jarry        "--dport-range",
3189f4acbc6SRobin Jarry        type=port_range,
3199f4acbc6SRobin Jarry        default=NO_PORT,
3209f4acbc6SRobin Jarry        help="""
3219f4acbc6SRobin Jarry        The layer 4 (TCP/UDP) destination port range.
3229f4acbc6SRobin Jarry        Can be a single fixed value or a range <start>-<end>.
3239f4acbc6SRobin Jarry        """,
3249f4acbc6SRobin Jarry    )
3259f4acbc6SRobin Jarry    parser.add_argument(
3269f4acbc6SRobin Jarry        "-r",
3279f4acbc6SRobin Jarry        "--check-reverse-traffic",
3289f4acbc6SRobin Jarry        action="store_true",
3299f4acbc6SRobin Jarry        help="""
3309f4acbc6SRobin Jarry        The reversed traffic (source <-> dest) should also be evenly balanced
3319f4acbc6SRobin Jarry        in the queues.
3329f4acbc6SRobin Jarry        """,
3339f4acbc6SRobin Jarry    )
3349f4acbc6SRobin Jarry    parser.add_argument(
3359f4acbc6SRobin Jarry        "-k",
3369f4acbc6SRobin Jarry        "--rss-key",
337a9c28754SRobin Jarry        default="intel",
338a9c28754SRobin Jarry        help=f"""
339a9c28754SRobin Jarry        The random key used to compute the RSS hash. This option
3409f4acbc6SRobin Jarry        supports either a well-known name or the hex value of the key
341a9c28754SRobin Jarry        (well-known names: {', '.join(DEFAULT_DRIVERS)}, default: intel).
3429f4acbc6SRobin Jarry        """,
3439f4acbc6SRobin Jarry    )
3449f4acbc6SRobin Jarry    parser.add_argument(
3459f4acbc6SRobin Jarry        "-t",
3469f4acbc6SRobin Jarry        "--reta-size",
3479f4acbc6SRobin Jarry        type=power_of_two,
3489f4acbc6SRobin Jarry        help="""
349a9c28754SRobin Jarry        Size of the redirection table or "RETA" (default: depends on driver if
350a9c28754SRobin Jarry        using a well-known driver name, otherwise 128).
3519f4acbc6SRobin Jarry        """,
3529f4acbc6SRobin Jarry    )
3539f4acbc6SRobin Jarry    parser.add_argument(
3549f4acbc6SRobin Jarry        "-a",
3559f4acbc6SRobin Jarry        "--all-flows",
3569f4acbc6SRobin Jarry        action="store_true",
3579f4acbc6SRobin Jarry        help="""
3589f4acbc6SRobin Jarry        Output ALL flows that can be created based on source and destination
3599f4acbc6SRobin Jarry        address/port ranges along their matched queue number. ATTENTION: this
3609f4acbc6SRobin Jarry        option can produce very long outputs depending on the address and port
3619f4acbc6SRobin Jarry        range sizes.
3629f4acbc6SRobin Jarry        """,
3639f4acbc6SRobin Jarry    )
3649f4acbc6SRobin Jarry    parser.add_argument(
3659f4acbc6SRobin Jarry        "-j",
3669f4acbc6SRobin Jarry        "--json",
3679f4acbc6SRobin Jarry        action="store_true",
3689f4acbc6SRobin Jarry        help="""
3699f4acbc6SRobin Jarry        Output in parseable JSON format.
3709f4acbc6SRobin Jarry        """,
3719f4acbc6SRobin Jarry    )
372bfdf091cSRobin Jarry    parser.add_argument(
373bfdf091cSRobin Jarry        "-i",
374bfdf091cSRobin Jarry        "--info",
375bfdf091cSRobin Jarry        action="store_true",
376bfdf091cSRobin Jarry        help="""
377bfdf091cSRobin Jarry        Print RETA size and RSS key above the results. Not available with --json.
378bfdf091cSRobin Jarry        """,
379bfdf091cSRobin Jarry    )
3809f4acbc6SRobin Jarry
3819f4acbc6SRobin Jarry    args = parser.parse_args()
3829f4acbc6SRobin Jarry
3839f4acbc6SRobin Jarry    if args.ip_src.version != args.ip_dst.version:
3849f4acbc6SRobin Jarry        parser.error(
3859f4acbc6SRobin Jarry            f"{args.ip_src} and {args.ip_dst} don't have the same protocol version"
3869f4acbc6SRobin Jarry        )
387a9c28754SRobin Jarry
388bfdf091cSRobin Jarry    if args.json and args.info:
389bfdf091cSRobin Jarry        parser.error("--json and --info are mutually exclusive")
390bfdf091cSRobin Jarry
391a9c28754SRobin Jarry    if args.rss_key in DEFAULT_DRIVERS:
392a9c28754SRobin Jarry        driver_info = DEFAULT_DRIVERS[args.rss_key]
393a9c28754SRobin Jarry    else:
394a9c28754SRobin Jarry        try:
395a9c28754SRobin Jarry            key = binascii.unhexlify(args.rss_key)
396a9c28754SRobin Jarry        except (TypeError, ValueError) as e:
397a9c28754SRobin Jarry            parser.error(f"RSS_KEY: {e}")
398a9c28754SRobin Jarry        driver_info = DriverInfo(key=key, reta_size=128)
399a9c28754SRobin Jarry
400a9c28754SRobin Jarry    if args.reta_size is None:
401a9c28754SRobin Jarry        args.reta_size = driver_info.reta_size(args.rx_queues)
402a9c28754SRobin Jarry
4039f4acbc6SRobin Jarry    if args.reta_size < args.rx_queues:
4049f4acbc6SRobin Jarry        parser.error("RETA_SIZE must be greater than or equal to RX_QUEUES")
4059f4acbc6SRobin Jarry
406a9c28754SRobin Jarry    args.rss_key = driver_info.rss_key()
407a9c28754SRobin Jarry
4089f4acbc6SRobin Jarry    return args
4099f4acbc6SRobin Jarry
4109f4acbc6SRobin Jarry
4119f4acbc6SRobin Jarrydef main():
4129f4acbc6SRobin Jarry    args = parse_args()
4139f4acbc6SRobin Jarry    use_l4_port = args.sport_range != NO_PORT or args.dport_range != NO_PORT
4149f4acbc6SRobin Jarry
4159f4acbc6SRobin Jarry    algo = RSSAlgo(
4169f4acbc6SRobin Jarry        queues_count=args.rx_queues,
4179f4acbc6SRobin Jarry        key=args.rss_key,
4189f4acbc6SRobin Jarry        reta_size=args.reta_size,
4199f4acbc6SRobin Jarry        use_l4_port=use_l4_port,
4209f4acbc6SRobin Jarry    )
4219f4acbc6SRobin Jarry    template = TrafficTemplate(
4229f4acbc6SRobin Jarry        args.ip_src,
4239f4acbc6SRobin Jarry        args.ip_dst,
4249f4acbc6SRobin Jarry        args.sport_range,
4259f4acbc6SRobin Jarry        args.dport_range,
4269f4acbc6SRobin Jarry    )
4279f4acbc6SRobin Jarry
4289f4acbc6SRobin Jarry    results = balanced_traffic(
4299f4acbc6SRobin Jarry        algo, template, args.check_reverse_traffic, args.all_flows
4309f4acbc6SRobin Jarry    )
4319f4acbc6SRobin Jarry
4329f4acbc6SRobin Jarry    if args.json:
4339f4acbc6SRobin Jarry        flows = []
4349f4acbc6SRobin Jarry        for q, qr, pkt in results:
4359f4acbc6SRobin Jarry            flows.append(
4369f4acbc6SRobin Jarry                {
4379f4acbc6SRobin Jarry                    "queue": q,
4389f4acbc6SRobin Jarry                    "queue_reverse": qr,
4399f4acbc6SRobin Jarry                    "src_ip": str(pkt.ip_src),
4409f4acbc6SRobin Jarry                    "dst_ip": str(pkt.ip_dst),
4419f4acbc6SRobin Jarry                    "src_port": pkt.l4_sport,
4429f4acbc6SRobin Jarry                    "dst_port": pkt.l4_dport,
4439f4acbc6SRobin Jarry                }
4449f4acbc6SRobin Jarry            )
4459f4acbc6SRobin Jarry        print(json.dumps(flows, indent=2))
4469f4acbc6SRobin Jarry        return
4479f4acbc6SRobin Jarry
4489f4acbc6SRobin Jarry    if use_l4_port:
4499f4acbc6SRobin Jarry        header = ["SRC_IP", "SPORT", "DST_IP", "DPORT", "QUEUE"]
4509f4acbc6SRobin Jarry    else:
4519f4acbc6SRobin Jarry        header = ["SRC_IP", "DST_IP", "QUEUE"]
4529f4acbc6SRobin Jarry    if args.check_reverse_traffic:
4539f4acbc6SRobin Jarry        header.append("QUEUE_REVERSE")
4549f4acbc6SRobin Jarry
4559f4acbc6SRobin Jarry    rows = [tuple(header)]
4569f4acbc6SRobin Jarry    widths = [len(h) for h in header]
4579f4acbc6SRobin Jarry
4589f4acbc6SRobin Jarry    for q, qr, pkt in results:
4599f4acbc6SRobin Jarry        if use_l4_port:
4609f4acbc6SRobin Jarry            row = [pkt.ip_src, pkt.l4_sport, pkt.ip_dst, pkt.l4_dport, q]
4619f4acbc6SRobin Jarry        else:
4629f4acbc6SRobin Jarry            row = [pkt.ip_src, pkt.ip_dst, q]
4639f4acbc6SRobin Jarry        if args.check_reverse_traffic:
4649f4acbc6SRobin Jarry            row.append(qr)
4659f4acbc6SRobin Jarry        cells = []
4669f4acbc6SRobin Jarry        for i, r in enumerate(row):
4679f4acbc6SRobin Jarry            r = str(r)
4689f4acbc6SRobin Jarry            if len(r) > widths[i]:
4699f4acbc6SRobin Jarry                widths[i] = len(r)
4709f4acbc6SRobin Jarry            cells.append(r)
4719f4acbc6SRobin Jarry        rows.append(tuple(cells))
4729f4acbc6SRobin Jarry
473bfdf091cSRobin Jarry    if args.info:
474bfdf091cSRobin Jarry        print(f"RSS key:     {binascii.hexlify(args.rss_key).decode()}")
475bfdf091cSRobin Jarry        print(f"RETA size:   {args.reta_size}")
476bfdf091cSRobin Jarry        print()
477bfdf091cSRobin Jarry
4789f4acbc6SRobin Jarry    fmt = [f"%-{w}s" for w in widths]
4799f4acbc6SRobin Jarry    fmt[-1] = "%s"  # avoid trailing whitespace
4809f4acbc6SRobin Jarry    fmt = "    ".join(fmt)
4819f4acbc6SRobin Jarry    for row in rows:
4829f4acbc6SRobin Jarry        print(fmt % row)
4839f4acbc6SRobin Jarry
4849f4acbc6SRobin Jarry
4859f4acbc6SRobin Jarryif __name__ == "__main__":
4869f4acbc6SRobin Jarry    main()
487