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