1812c4071SJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause 2812c4071SJuraj Linkeš# Copyright(c) 2010-2014 Intel Corporation 3680d8a24SJuraj Linkeš# Copyright(c) 2022-2023 PANTHEON.tech s.r.o. 4680d8a24SJuraj Linkeš# Copyright(c) 2022-2023 University of New Hampshire 5a23f2245SLuca Vizzarro# Copyright(c) 2024 Arm Limited 6812c4071SJuraj Linkeš 76ef07151SJuraj Linkeš"""Various utility classes and functions. 86ef07151SJuraj Linkeš 96ef07151SJuraj LinkešThese are used in multiple modules across the framework. They're here because 106ef07151SJuraj Linkešthey provide some non-specific functionality, greatly simplify imports or just don't 116ef07151SJuraj Linkešfit elsewhere. 126ef07151SJuraj Linkeš 136ef07151SJuraj LinkešAttributes: 146ef07151SJuraj Linkeš REGEX_FOR_PCI_ADDRESS: The regex representing a PCI address, e.g. ``0000:00:08.0``. 156ef07151SJuraj Linkeš""" 166ef07151SJuraj Linkeš 17c9d31a7eSJuraj Linkešimport atexit 18*80158fd4STomáš Ďurovecimport fnmatch 19cecfe0aaSJuraj Linkešimport json 20c9d31a7eSJuraj Linkešimport os 21f51557fbSLuca Vizzarroimport random 22c9d31a7eSJuraj Linkešimport subprocess 23*80158fd4STomáš Ďurovecimport tarfile 24f51557fbSLuca Vizzarrofrom enum import Enum, Flag 25c9d31a7eSJuraj Linkešfrom pathlib import Path 26c9d31a7eSJuraj Linkešfrom subprocess import SubprocessError 27*80158fd4STomáš Ďurovecfrom typing import Any, Callable 28c9d31a7eSJuraj Linkeš 29f51557fbSLuca Vizzarrofrom scapy.layers.inet import IP, TCP, UDP, Ether # type: ignore[import-untyped] 30282688eaSLuca Vizzarrofrom scapy.packet import Packet # type: ignore[import-untyped] 31cecfe0aaSJuraj Linkeš 32f51557fbSLuca Vizzarrofrom .exception import ConfigurationError, InternalError 33c9d31a7eSJuraj Linkeš 34840b1e01SJuraj LinkešREGEX_FOR_PCI_ADDRESS: str = "/[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}.[0-9]{1}/" 35a91d5f47SJeremy Spewock_REGEX_FOR_COLON_OR_HYPHEN_SEP_MAC: str = r"(?:[\da-fA-F]{2}[:-]){5}[\da-fA-F]{2}" 36a91d5f47SJeremy Spewock_REGEX_FOR_DOT_SEP_MAC: str = r"(?:[\da-fA-F]{4}.){2}[\da-fA-F]{4}" 37a91d5f47SJeremy SpewockREGEX_FOR_MAC_ADDRESS: str = rf"{_REGEX_FOR_COLON_OR_HYPHEN_SEP_MAC}|{_REGEX_FOR_DOT_SEP_MAC}" 3899740300SJeremy SpewockREGEX_FOR_BASE64_ENCODING: str = "[-a-zA-Z0-9+\\/]*={0,3}" 3957c58bf8SJuraj Linkeš 40812c4071SJuraj Linkeš 41c020b7ceSJuraj Linkešdef expand_range(range_str: str) -> list[int]: 426ef07151SJuraj Linkeš """Process `range_str` into a list of integers. 43c020b7ceSJuraj Linkeš 446ef07151SJuraj Linkeš There are two possible formats of `range_str`: 456ef07151SJuraj Linkeš 466ef07151SJuraj Linkeš * ``n`` - a single integer, 476ef07151SJuraj Linkeš * ``n-m`` - a range of integers. 486ef07151SJuraj Linkeš 496ef07151SJuraj Linkeš The returned range includes both ``n`` and ``m``. Empty string returns an empty list. 506ef07151SJuraj Linkeš 516ef07151SJuraj Linkeš Args: 526ef07151SJuraj Linkeš range_str: The range to expand. 536ef07151SJuraj Linkeš 546ef07151SJuraj Linkeš Returns: 556ef07151SJuraj Linkeš All the numbers from the range. 56c020b7ceSJuraj Linkeš """ 57c020b7ceSJuraj Linkeš expanded_range: list[int] = [] 58c020b7ceSJuraj Linkeš if range_str: 59c020b7ceSJuraj Linkeš range_boundaries = range_str.split("-") 60c020b7ceSJuraj Linkeš # will throw an exception when items in range_boundaries can't be converted, 61c020b7ceSJuraj Linkeš # serving as type check 62517b4b26SJuraj Linkeš expanded_range.extend(range(int(range_boundaries[0]), int(range_boundaries[-1]) + 1)) 63c020b7ceSJuraj Linkeš 64c020b7ceSJuraj Linkeš return expanded_range 65c020b7ceSJuraj Linkeš 66c020b7ceSJuraj Linkeš 67840b1e01SJuraj Linkešdef get_packet_summaries(packets: list[Packet]) -> str: 686ef07151SJuraj Linkeš """Format a string summary from `packets`. 696ef07151SJuraj Linkeš 706ef07151SJuraj Linkeš Args: 716ef07151SJuraj Linkeš packets: The packets to format. 726ef07151SJuraj Linkeš 736ef07151SJuraj Linkeš Returns: 746ef07151SJuraj Linkeš The summary of `packets`. 756ef07151SJuraj Linkeš """ 76cecfe0aaSJuraj Linkeš if len(packets) == 1: 77cecfe0aaSJuraj Linkeš packet_summaries = packets[0].summary() 78cecfe0aaSJuraj Linkeš else: 79517b4b26SJuraj Linkeš packet_summaries = json.dumps(list(map(lambda pkt: pkt.summary(), packets)), indent=4) 80cecfe0aaSJuraj Linkeš return f"Packet contents: \n{packet_summaries}" 81cecfe0aaSJuraj Linkeš 82cecfe0aaSJuraj Linkeš 83a23f2245SLuca Vizzarrodef get_commit_id(rev_id: str) -> str: 84a23f2245SLuca Vizzarro """Given a Git revision ID, return the corresponding commit ID. 85a23f2245SLuca Vizzarro 86a23f2245SLuca Vizzarro Args: 87a23f2245SLuca Vizzarro rev_id: The Git revision ID. 88a23f2245SLuca Vizzarro 89a23f2245SLuca Vizzarro Raises: 90a23f2245SLuca Vizzarro ConfigurationError: The ``git rev-parse`` command failed, suggesting 91a23f2245SLuca Vizzarro an invalid or ambiguous revision ID was supplied. 92a23f2245SLuca Vizzarro """ 93a23f2245SLuca Vizzarro result = subprocess.run( 94a23f2245SLuca Vizzarro ["git", "rev-parse", "--verify", rev_id], 95a23f2245SLuca Vizzarro text=True, 96a23f2245SLuca Vizzarro capture_output=True, 97a23f2245SLuca Vizzarro ) 98a23f2245SLuca Vizzarro if result.returncode != 0: 99a23f2245SLuca Vizzarro raise ConfigurationError( 100a23f2245SLuca Vizzarro f"{rev_id} is not a valid git reference.\n" 101a23f2245SLuca Vizzarro f"Command: {result.args}\n" 102a23f2245SLuca Vizzarro f"Stdout: {result.stdout}\n" 103a23f2245SLuca Vizzarro f"Stderr: {result.stderr}" 104a23f2245SLuca Vizzarro ) 105a23f2245SLuca Vizzarro return result.stdout.strip() 106a23f2245SLuca Vizzarro 107a23f2245SLuca Vizzarro 108840b1e01SJuraj Linkešclass StrEnum(Enum): 1096ef07151SJuraj Linkeš """Enum with members stored as strings.""" 1106ef07151SJuraj Linkeš 111840b1e01SJuraj Linkeš @staticmethod 112840b1e01SJuraj Linkeš def _generate_next_value_(name: str, start: int, count: int, last_values: object) -> str: 113840b1e01SJuraj Linkeš return name 114840b1e01SJuraj Linkeš 115840b1e01SJuraj Linkeš def __str__(self) -> str: 1166ef07151SJuraj Linkeš """The string representation is the name of the member.""" 117840b1e01SJuraj Linkeš return self.name 118680d8a24SJuraj Linkeš 119680d8a24SJuraj Linkeš 1203e967643SJuraj Linkešclass MesonArgs: 1216ef07151SJuraj Linkeš """Aggregate the arguments needed to build DPDK.""" 122680d8a24SJuraj Linkeš 123680d8a24SJuraj Linkeš _default_library: str 124680d8a24SJuraj Linkeš 125680d8a24SJuraj Linkeš def __init__(self, default_library: str | None = None, **dpdk_args: str | bool): 1266ef07151SJuraj Linkeš """Initialize the meson arguments. 1276ef07151SJuraj Linkeš 1286ef07151SJuraj Linkeš Args: 1296ef07151SJuraj Linkeš default_library: The default library type, Meson supports ``shared``, ``static`` and 1306ef07151SJuraj Linkeš ``both``. Defaults to :data:`None`, in which case the argument won't be used. 1316ef07151SJuraj Linkeš dpdk_args: The arguments found in ``meson_options.txt`` in root DPDK directory. 1326ef07151SJuraj Linkeš Do not use ``-D`` with them. 1336ef07151SJuraj Linkeš 1346ef07151SJuraj Linkeš Example: 1356ef07151SJuraj Linkeš :: 1366ef07151SJuraj Linkeš 1376ef07151SJuraj Linkeš meson_args = MesonArgs(enable_kmods=True). 1386ef07151SJuraj Linkeš """ 139517b4b26SJuraj Linkeš self._default_library = f"--default-library={default_library}" if default_library else "" 140680d8a24SJuraj Linkeš self._dpdk_args = " ".join( 141680d8a24SJuraj Linkeš ( 142680d8a24SJuraj Linkeš f"-D{dpdk_arg_name}={dpdk_arg_value}" 143680d8a24SJuraj Linkeš for dpdk_arg_name, dpdk_arg_value in dpdk_args.items() 144680d8a24SJuraj Linkeš ) 145680d8a24SJuraj Linkeš ) 146680d8a24SJuraj Linkeš 147680d8a24SJuraj Linkeš def __str__(self) -> str: 1486ef07151SJuraj Linkeš """The actual args.""" 149680d8a24SJuraj Linkeš return " ".join(f"{self._default_library} {self._dpdk_args}".split()) 150c9d31a7eSJuraj Linkeš 151c9d31a7eSJuraj Linkeš 152*80158fd4STomáš Ďurovecclass TarCompressionFormat(StrEnum): 153c9d31a7eSJuraj Linkeš """Compression formats that tar can use. 154c9d31a7eSJuraj Linkeš 155c9d31a7eSJuraj Linkeš Enum names are the shell compression commands 156c9d31a7eSJuraj Linkeš and Enum values are the associated file extensions. 157*80158fd4STomáš Ďurovec 158*80158fd4STomáš Ďurovec The 'none' member represents no compression, only archiving with tar. 159*80158fd4STomáš Ďurovec Its value is set to 'tar' to indicate that the file is an uncompressed tar archive. 160c9d31a7eSJuraj Linkeš """ 161c9d31a7eSJuraj Linkeš 162*80158fd4STomáš Ďurovec none = "tar" 163c9d31a7eSJuraj Linkeš gzip = "gz" 164c9d31a7eSJuraj Linkeš compress = "Z" 165c9d31a7eSJuraj Linkeš bzip2 = "bz2" 166c9d31a7eSJuraj Linkeš lzip = "lz" 167c9d31a7eSJuraj Linkeš lzma = "lzma" 168c9d31a7eSJuraj Linkeš lzop = "lzo" 169c9d31a7eSJuraj Linkeš xz = "xz" 170c9d31a7eSJuraj Linkeš zstd = "zst" 171c9d31a7eSJuraj Linkeš 172*80158fd4STomáš Ďurovec @property 173*80158fd4STomáš Ďurovec def extension(self): 174*80158fd4STomáš Ďurovec """Return the extension associated with the compression format. 175*80158fd4STomáš Ďurovec 176*80158fd4STomáš Ďurovec If the compression format is 'none', the extension will be in the format 'tar'. 177*80158fd4STomáš Ďurovec For other compression formats, the extension will be in the format 178*80158fd4STomáš Ďurovec 'tar.{compression format}'. 179*80158fd4STomáš Ďurovec """ 180*80158fd4STomáš Ďurovec return f"{self.value}" if self == self.none else f"{self.none.value}.{self.value}" 181*80158fd4STomáš Ďurovec 182c9d31a7eSJuraj Linkeš 1833e967643SJuraj Linkešclass DPDKGitTarball: 1846ef07151SJuraj Linkeš """Compressed tarball of DPDK from the repository. 185c9d31a7eSJuraj Linkeš 1866ef07151SJuraj Linkeš The class supports the :class:`os.PathLike` protocol, 187c9d31a7eSJuraj Linkeš which is used to get the Path of the tarball:: 188c9d31a7eSJuraj Linkeš 189c9d31a7eSJuraj Linkeš from pathlib import Path 190c9d31a7eSJuraj Linkeš tarball = DPDKGitTarball("HEAD", "output") 191c9d31a7eSJuraj Linkeš tarball_path = Path(tarball) 192c9d31a7eSJuraj Linkeš """ 193c9d31a7eSJuraj Linkeš 194c9d31a7eSJuraj Linkeš _git_ref: str 195*80158fd4STomáš Ďurovec _tar_compression_format: TarCompressionFormat 196c9d31a7eSJuraj Linkeš _tarball_dir: Path 197c9d31a7eSJuraj Linkeš _tarball_name: str 198c9d31a7eSJuraj Linkeš _tarball_path: Path | None 199c9d31a7eSJuraj Linkeš 200c9d31a7eSJuraj Linkeš def __init__( 201c9d31a7eSJuraj Linkeš self, 202c9d31a7eSJuraj Linkeš git_ref: str, 203c9d31a7eSJuraj Linkeš output_dir: str, 204*80158fd4STomáš Ďurovec tar_compression_format: TarCompressionFormat = TarCompressionFormat.xz, 205c9d31a7eSJuraj Linkeš ): 2066ef07151SJuraj Linkeš """Create the tarball during initialization. 2076ef07151SJuraj Linkeš 2086ef07151SJuraj Linkeš The DPDK version is specified with `git_ref`. The tarball will be compressed with 2096ef07151SJuraj Linkeš `tar_compression_format`, which must be supported by the DTS execution environment. 2106ef07151SJuraj Linkeš The resulting tarball will be put into `output_dir`. 2116ef07151SJuraj Linkeš 2126ef07151SJuraj Linkeš Args: 2136ef07151SJuraj Linkeš git_ref: A git commit ID, tag ID or tree ID. 2146ef07151SJuraj Linkeš output_dir: The directory where to put the resulting tarball. 2156ef07151SJuraj Linkeš tar_compression_format: The compression format to use. 2166ef07151SJuraj Linkeš """ 217c9d31a7eSJuraj Linkeš self._git_ref = git_ref 218c9d31a7eSJuraj Linkeš self._tar_compression_format = tar_compression_format 219c9d31a7eSJuraj Linkeš 220c9d31a7eSJuraj Linkeš self._tarball_dir = Path(output_dir, "tarball") 221c9d31a7eSJuraj Linkeš 222c9d31a7eSJuraj Linkeš self._create_tarball_dir() 223c9d31a7eSJuraj Linkeš 224c9d31a7eSJuraj Linkeš self._tarball_name = ( 225*80158fd4STomáš Ďurovec f"dpdk-tarball-{self._git_ref}.{self._tar_compression_format.extension}" 226c9d31a7eSJuraj Linkeš ) 227c9d31a7eSJuraj Linkeš self._tarball_path = self._check_tarball_path() 228c9d31a7eSJuraj Linkeš if not self._tarball_path: 229c9d31a7eSJuraj Linkeš self._create_tarball() 230c9d31a7eSJuraj Linkeš 231c9d31a7eSJuraj Linkeš def _create_tarball_dir(self) -> None: 232c9d31a7eSJuraj Linkeš os.makedirs(self._tarball_dir, exist_ok=True) 233c9d31a7eSJuraj Linkeš 234c9d31a7eSJuraj Linkeš def _check_tarball_path(self) -> Path | None: 235c9d31a7eSJuraj Linkeš if self._tarball_name in os.listdir(self._tarball_dir): 236c9d31a7eSJuraj Linkeš return Path(self._tarball_dir, self._tarball_name) 237c9d31a7eSJuraj Linkeš return None 238c9d31a7eSJuraj Linkeš 239c9d31a7eSJuraj Linkeš def _create_tarball(self) -> None: 240c9d31a7eSJuraj Linkeš self._tarball_path = Path(self._tarball_dir, self._tarball_name) 241c9d31a7eSJuraj Linkeš 242c9d31a7eSJuraj Linkeš atexit.register(self._delete_tarball) 243c9d31a7eSJuraj Linkeš 244c9d31a7eSJuraj Linkeš result = subprocess.run( 245c9d31a7eSJuraj Linkeš 'git -C "$(git rev-parse --show-toplevel)" archive ' 246c9d31a7eSJuraj Linkeš f'{self._git_ref} --prefix="dpdk-tarball-{self._git_ref + os.sep}" | ' 247c9d31a7eSJuraj Linkeš f"{self._tar_compression_format} > {Path(self._tarball_path.absolute())}", 248c9d31a7eSJuraj Linkeš shell=True, 249c9d31a7eSJuraj Linkeš text=True, 250c9d31a7eSJuraj Linkeš capture_output=True, 251c9d31a7eSJuraj Linkeš ) 252c9d31a7eSJuraj Linkeš 253c9d31a7eSJuraj Linkeš if result.returncode != 0: 254c9d31a7eSJuraj Linkeš raise SubprocessError( 255c9d31a7eSJuraj Linkeš f"Git archive creation failed with exit code {result.returncode}.\n" 256c9d31a7eSJuraj Linkeš f"Command: {result.args}\n" 257c9d31a7eSJuraj Linkeš f"Stdout: {result.stdout}\n" 258c9d31a7eSJuraj Linkeš f"Stderr: {result.stderr}" 259c9d31a7eSJuraj Linkeš ) 260c9d31a7eSJuraj Linkeš 261c9d31a7eSJuraj Linkeš atexit.unregister(self._delete_tarball) 262c9d31a7eSJuraj Linkeš 263c9d31a7eSJuraj Linkeš def _delete_tarball(self) -> None: 264c9d31a7eSJuraj Linkeš if self._tarball_path and os.path.exists(self._tarball_path): 265c9d31a7eSJuraj Linkeš os.remove(self._tarball_path) 266c9d31a7eSJuraj Linkeš 267840b1e01SJuraj Linkeš def __fspath__(self) -> str: 2686ef07151SJuraj Linkeš """The os.PathLike protocol implementation.""" 269c9d31a7eSJuraj Linkeš return str(self._tarball_path) 270f51557fbSLuca Vizzarro 271f51557fbSLuca Vizzarro 272*80158fd4STomáš Ďurovecdef convert_to_list_of_string(value: Any | list[Any]) -> list[str]: 273*80158fd4STomáš Ďurovec """Convert the input to the list of strings.""" 274*80158fd4STomáš Ďurovec return list(map(str, value) if isinstance(value, list) else str(value)) 275*80158fd4STomáš Ďurovec 276*80158fd4STomáš Ďurovec 277*80158fd4STomáš Ďurovecdef create_tarball( 278*80158fd4STomáš Ďurovec dir_path: Path, 279*80158fd4STomáš Ďurovec compress_format: TarCompressionFormat = TarCompressionFormat.none, 280*80158fd4STomáš Ďurovec exclude: Any | list[Any] | None = None, 281*80158fd4STomáš Ďurovec) -> Path: 282*80158fd4STomáš Ďurovec """Create a tarball from the contents of the specified directory. 283*80158fd4STomáš Ďurovec 284*80158fd4STomáš Ďurovec This method creates a tarball containing all files and directories within `dir_path`. 285*80158fd4STomáš Ďurovec The tarball will be saved in the directory of `dir_path` and will be named based on `dir_path`. 286*80158fd4STomáš Ďurovec 287*80158fd4STomáš Ďurovec Args: 288*80158fd4STomáš Ďurovec dir_path: The directory path. 289*80158fd4STomáš Ďurovec compress_format: The compression format to use. Defaults to no compression. 290*80158fd4STomáš Ďurovec exclude: Patterns for files or directories to exclude from the tarball. 291*80158fd4STomáš Ďurovec These patterns are used with `fnmatch.fnmatch` to filter out files. 292*80158fd4STomáš Ďurovec 293*80158fd4STomáš Ďurovec Returns: 294*80158fd4STomáš Ďurovec The path to the created tarball. 295*80158fd4STomáš Ďurovec """ 296*80158fd4STomáš Ďurovec 297*80158fd4STomáš Ďurovec def create_filter_function(exclude_patterns: str | list[str] | None) -> Callable | None: 298*80158fd4STomáš Ďurovec """Create a filter function based on the provided exclude patterns. 299*80158fd4STomáš Ďurovec 300*80158fd4STomáš Ďurovec Args: 301*80158fd4STomáš Ďurovec exclude_patterns: Patterns for files or directories to exclude from the tarball. 302*80158fd4STomáš Ďurovec These patterns are used with `fnmatch.fnmatch` to filter out files. 303*80158fd4STomáš Ďurovec 304*80158fd4STomáš Ďurovec Returns: 305*80158fd4STomáš Ďurovec The filter function that excludes files based on the patterns. 306*80158fd4STomáš Ďurovec """ 307*80158fd4STomáš Ďurovec if exclude_patterns: 308*80158fd4STomáš Ďurovec exclude_patterns = convert_to_list_of_string(exclude_patterns) 309*80158fd4STomáš Ďurovec 310*80158fd4STomáš Ďurovec def filter_func(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None: 311*80158fd4STomáš Ďurovec file_name = os.path.basename(tarinfo.name) 312*80158fd4STomáš Ďurovec if any(fnmatch.fnmatch(file_name, pattern) for pattern in exclude_patterns): 313*80158fd4STomáš Ďurovec return None 314*80158fd4STomáš Ďurovec return tarinfo 315*80158fd4STomáš Ďurovec 316*80158fd4STomáš Ďurovec return filter_func 317*80158fd4STomáš Ďurovec return None 318*80158fd4STomáš Ďurovec 319*80158fd4STomáš Ďurovec target_tarball_path = dir_path.with_suffix(f".{compress_format.extension}") 320*80158fd4STomáš Ďurovec with tarfile.open(target_tarball_path, f"w:{compress_format.value}") as tar: 321*80158fd4STomáš Ďurovec tar.add(dir_path, arcname=dir_path.name, filter=create_filter_function(exclude)) 322*80158fd4STomáš Ďurovec 323*80158fd4STomáš Ďurovec return target_tarball_path 324*80158fd4STomáš Ďurovec 325*80158fd4STomáš Ďurovec 326*80158fd4STomáš Ďurovecdef extract_tarball(tar_path: str | Path): 327*80158fd4STomáš Ďurovec """Extract the contents of a tarball. 328*80158fd4STomáš Ďurovec 329*80158fd4STomáš Ďurovec The tarball will be extracted in the same path as `tar_path` parent path. 330*80158fd4STomáš Ďurovec 331*80158fd4STomáš Ďurovec Args: 332*80158fd4STomáš Ďurovec tar_path: The path to the tarball file to extract. 333*80158fd4STomáš Ďurovec """ 334*80158fd4STomáš Ďurovec with tarfile.open(tar_path, "r") as tar: 335*80158fd4STomáš Ďurovec tar.extractall(path=Path(tar_path).parent) 336*80158fd4STomáš Ďurovec 337*80158fd4STomáš Ďurovec 338f51557fbSLuca Vizzarroclass PacketProtocols(Flag): 339f51557fbSLuca Vizzarro """Flag specifying which protocols to use for packet generation.""" 340f51557fbSLuca Vizzarro 341f51557fbSLuca Vizzarro #: 342f51557fbSLuca Vizzarro IP = 1 343f51557fbSLuca Vizzarro #: 344f51557fbSLuca Vizzarro TCP = 2 | IP 345f51557fbSLuca Vizzarro #: 346f51557fbSLuca Vizzarro UDP = 4 | IP 347f51557fbSLuca Vizzarro #: 348f51557fbSLuca Vizzarro ALL = TCP | UDP 349f51557fbSLuca Vizzarro 350f51557fbSLuca Vizzarro 351f51557fbSLuca Vizzarrodef generate_random_packets( 352f51557fbSLuca Vizzarro number_of: int, 353f51557fbSLuca Vizzarro payload_size: int = 1500, 354f51557fbSLuca Vizzarro protocols: PacketProtocols = PacketProtocols.ALL, 355f51557fbSLuca Vizzarro ports_range: range = range(1024, 49152), 356f51557fbSLuca Vizzarro mtu: int = 1500, 357f51557fbSLuca Vizzarro) -> list[Packet]: 358f51557fbSLuca Vizzarro """Generate a number of random packets. 359f51557fbSLuca Vizzarro 360f51557fbSLuca Vizzarro The payload of the packets will consist of random bytes. If `payload_size` is too big, then the 361f51557fbSLuca Vizzarro maximum payload size allowed for the specific packet type is used. The size is calculated based 362f51557fbSLuca Vizzarro on the specified `mtu`, therefore it is essential that `mtu` is set correctly to match the MTU 363f51557fbSLuca Vizzarro of the port that will send out the generated packets. 364f51557fbSLuca Vizzarro 365f51557fbSLuca Vizzarro If `protocols` has any L4 protocol enabled then all the packets are generated with any of 366f51557fbSLuca Vizzarro the specified L4 protocols chosen at random. If only :attr:`~PacketProtocols.IP` is set, then 367f51557fbSLuca Vizzarro only L3 packets are generated. 368f51557fbSLuca Vizzarro 369f51557fbSLuca Vizzarro If L4 packets will be generated, then the TCP/UDP ports to be used will be chosen at random from 370f51557fbSLuca Vizzarro `ports_range`. 371f51557fbSLuca Vizzarro 372f51557fbSLuca Vizzarro Args: 373f51557fbSLuca Vizzarro number_of: The number of packets to generate. 374f51557fbSLuca Vizzarro payload_size: The packet payload size to generate, capped based on `mtu`. 375f51557fbSLuca Vizzarro protocols: The protocols to use for the generated packets. 376f51557fbSLuca Vizzarro ports_range: The range of L4 port numbers to use. Used only if `protocols` has L4 protocols. 377f51557fbSLuca Vizzarro mtu: The MTU of the NIC port that will send out the generated packets. 378f51557fbSLuca Vizzarro 379f51557fbSLuca Vizzarro Raises: 380f51557fbSLuca Vizzarro InternalError: If the `payload_size` is invalid. 381f51557fbSLuca Vizzarro 382f51557fbSLuca Vizzarro Returns: 383f51557fbSLuca Vizzarro A list containing the randomly generated packets. 384f51557fbSLuca Vizzarro """ 385f51557fbSLuca Vizzarro if payload_size < 0: 386f51557fbSLuca Vizzarro raise InternalError(f"An invalid payload_size of {payload_size} was given.") 387f51557fbSLuca Vizzarro 388f51557fbSLuca Vizzarro l4_factories = [] 389f51557fbSLuca Vizzarro if protocols & PacketProtocols.TCP: 390f51557fbSLuca Vizzarro l4_factories.append(TCP) 391f51557fbSLuca Vizzarro if protocols & PacketProtocols.UDP: 392f51557fbSLuca Vizzarro l4_factories.append(UDP) 393f51557fbSLuca Vizzarro 394f51557fbSLuca Vizzarro def _make_packet() -> Packet: 395f51557fbSLuca Vizzarro packet = Ether() 396f51557fbSLuca Vizzarro 397f51557fbSLuca Vizzarro if protocols & PacketProtocols.IP: 398f51557fbSLuca Vizzarro packet /= IP() 399f51557fbSLuca Vizzarro 400f51557fbSLuca Vizzarro if len(l4_factories) > 0: 401f51557fbSLuca Vizzarro src_port, dst_port = random.choices(ports_range, k=2) 402f51557fbSLuca Vizzarro packet /= random.choice(l4_factories)(sport=src_port, dport=dst_port) 403f51557fbSLuca Vizzarro 404f51557fbSLuca Vizzarro max_payload_size = mtu - len(packet) 405f51557fbSLuca Vizzarro usable_payload_size = payload_size if payload_size < max_payload_size else max_payload_size 406f51557fbSLuca Vizzarro return packet / random.randbytes(usable_payload_size) 407f51557fbSLuca Vizzarro 408f51557fbSLuca Vizzarro return [_make_packet() for _ in range(number_of)] 40999740300SJeremy Spewock 41099740300SJeremy Spewock 41199740300SJeremy Spewockclass MultiInheritanceBaseClass: 41299740300SJeremy Spewock """A base class for classes utilizing multiple inheritance. 41399740300SJeremy Spewock 41499740300SJeremy Spewock This class enables it's subclasses to support both single and multiple inheritance by acting as 41599740300SJeremy Spewock a stopping point in the tree of calls to the constructors of superclasses. This class is able 41699740300SJeremy Spewock to exist at the end of the Method Resolution Order (MRO) so that subclasses can call 41799740300SJeremy Spewock :meth:`super.__init__` without repercussion. 41899740300SJeremy Spewock """ 41999740300SJeremy Spewock 42099740300SJeremy Spewock def __init__(self, *args, **kwargs) -> None: 42199740300SJeremy Spewock """Call the init method of :class:`object`.""" 42299740300SJeremy Spewock super().__init__() 423